Skip to content

NativeEngine: divisor-driven per-instance vertex attributes#1739

Open
bkaradzic-microsoft wants to merge 1 commit into
BabylonJS:masterfrom
bkaradzic-microsoft:fix/generic-divisor-instancing
Open

NativeEngine: divisor-driven per-instance vertex attributes#1739
bkaradzic-microsoft wants to merge 1 commit into
BabylonJS:masterfrom
bkaradzic-microsoft:fix/generic-divisor-instancing

Conversation

@bkaradzic-microsoft
Copy link
Copy Markdown
Contributor

Problem

Consumer-declared per-instance vertex attributes were routed to per-vertex shader slots, so their data was read incorrectly at draw time. The ShaderCompiler only recognized the built-in instanced names (world0-3, instanceColor, splatIndex); any other attribute backed by an instance buffer (divisor > 0) — e.g. GPU-particle position/color/angle/size, or a custom instanced color buffer — was assigned a per-vertex location.

Fix

Drive instancing purely from the vertex-buffer divisor instead of a fixed name list:

  • ShaderCompiler traversers (D3D, OpenGL, Metal) now take a map<name, target-location> of instanced attributes and route each declared attribute to an explicit bgfx i_data slot (TEXCOORD7..TEXCOORD3 = i_data0..i_data4). Built-in instanced names keep their existing reverse-count assignment.
  • Program caches lazily-compiled instanced shader variants keyed by the instanced-attribute map; the base program is used when nothing is instanced.
  • NativeEngine computes the instanced-attribute set at draw time from the bound vertex array's per-attribute divisors and submits the matching variant. No JS-side hint is required.

This generalizes the instancing path so it works for any divisor-driven attribute, not just the hardcoded names, and supersedes the prior name-list approach.

Test enablement

Re-enables 18 previously-excluded Playground validation tests that failed under the old name-list instancing (most were marked as follow-ups to the #1691 instance-data stride fix). These are the GPU-particle suite plus the instanced color-buffer test:

Particles, Instances with color buffer, Prepass SSAO + particles, halo-particle-system, particle-system-with-custom-nme-shader, and Particles - Basic Properties / Change / Emitters / Attractors variants.

Each was confirmed to actually exercise the fixed path (it compiles a per-instance shader variant) and now matches its reference image.

Verification

  • D3D11: all 18 enabled tests pass in default automatic mode (ran=18 passed=18 failed=0); no regression in existing instanced tests (Gaussian Splatting, mesh instancing).
  • OpenGL / Metal: use the identical explicit-slot routing and are compile-checked; runtime coverage is via CI.

Copilot AI review requested due to automatic review settings June 6, 2026 00:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes incorrect routing of consumer-declared per-instance vertex attributes that previously ended up bound to per-vertex shader inputs unless they matched a hardcoded instanced-name list. The solution makes instancing behavior depend on the vertex-buffer divisor by compiling and selecting shader variants that explicitly route divisor-driven attributes into bgfx’s i_data slots.

Changes:

  • Extend shader compilation (D3D/OpenGL/Metal/Vulkan paths) to accept an instancedAttributes map (name -> i_data target location) and route those attributes to explicit i_data0..4 slots.
  • Add lazy, per-program caching of instanced shader variants keyed by the instanced-attribute map, and select the correct variant at draw time.
  • Re-enable previously excluded Playground validation tests by removing excludeFromAutomaticTesting entries for the affected particle/instancing scenarios.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Plugins/ShaderCompiler/Source/ShaderCompilerVulkan.cpp Passes instanced-attribute routing info into the traverser for Vulkan compilation.
Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.h Adds instancedAttributes parameter (with docs) to varying-location assigners; includes needed headers.
Plugins/ShaderCompiler/Source/ShaderCompilerTraversers.cpp Implements generic instanced-attribute routing to explicit i_data slots across D3D/OpenGL/Metal traversers.
Plugins/ShaderCompiler/Source/ShaderCompilerOpenGL.cpp Threads instancedAttributes into OpenGL shader compilation.
Plugins/ShaderCompiler/Source/ShaderCompilerMetal.cpp Threads instancedAttributes into Metal shader compilation.
Plugins/ShaderCompiler/Source/ShaderCompilerDXIL.cpp Threads instancedAttributes into DXIL shader compilation.
Plugins/ShaderCompiler/Source/ShaderCompilerDXBC.cpp Threads instancedAttributes into DXBC shader compilation.
Plugins/ShaderCompiler/InternalInclude/Babylon/Plugins/ShaderCompiler.h Updates public compiler API to accept instanced-attribute routing map (default empty).
Plugins/NativeEngine/Source/VertexArray.h Exposes recorded instance-attribute bindings for draw-time variant selection.
Plugins/NativeEngine/Source/ShaderProvider.h Updates shader-provider API to accept instanced-attribute routing map.
Plugins/NativeEngine/Source/ShaderProvider.cpp Bypasses source-only shader cache for instanced variants and compiles with routing map.
Plugins/NativeEngine/Source/Program.h Stores sources and adds instanced-variant caching keyed by the instanced-attribute map.
Plugins/NativeEngine/Source/Program.cpp Implements lazy instanced-variant compilation and destroys cached variant program handles on dispose.
Plugins/NativeEngine/Source/NativeEngine.cpp Computes divisor-driven instanced attribute routing from bound vertex array and submits the matching program variant.
Apps/Playground/Scripts/config.json Re-enables excluded tests by removing excludeFromAutomaticTesting flags for impacted cases.

Comment on lines +5 to +7
#include <set>
#include <string>

Comment on lines +2359 to +2360
for (const auto& [attrib, instanceInfo] : instances)
{
Comment on lines +672 to +673
if (slot >= BX_COUNTOF(s_attribInstanceName))
throw std::runtime_error("Instanced attribute location is not a valid bgfx i_data slot.");
Consumer-declared per-instance vertex attributes (e.g. GPU particle
position/color/angle/size, or a custom instanced color buffer) were
routed to per-vertex shader slots because the ShaderCompiler only
recognized the built-in instanced names (world0-3, instanceColor,
splatIndex). Any other attribute backed by an instance buffer
(divisor > 0) was assigned a per-vertex location, so its data was read
incorrectly at draw time.

Drive instancing purely from the vertex-buffer divisor instead of a
fixed name list:

- ShaderCompiler traversers (D3D, OpenGL, Metal) now take a
  map<name, target-location> of instanced attributes and route each
  declared attribute to an explicit bgfx i_data slot
  (TEXCOORD7..TEXCOORD3 = i_data0..i_data4). Built-in instanced names
  keep their existing reverse-count assignment.
- Program caches lazily-compiled instanced shader variants keyed by the
  instanced-attribute map; the base program is used when nothing is
  instanced.
- NativeEngine computes the instanced-attribute set at draw time from
  the bound vertex array's per-attribute divisors and submits the
  matching variant. This removes the need for any JS-side hint.

Enables 17 previously-excluded Playground validation tests that failed
under the prior name-list instancing (the GPU particle suite plus the
instanced color-buffer test), verified passing on D3D11 and OpenGL.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bkaradzic-microsoft bkaradzic-microsoft force-pushed the fix/generic-divisor-instancing branch from 49f4b03 to 64df62e Compare June 6, 2026 00:47
@bkaradzic-microsoft
Copy link
Copy Markdown
Contributor Author

Pushed an update addressing CI + review feedback:

CI fix (Linux GL): Prepass SSAO + particles aborted the OpenGL run, but the failure is unrelated to instancing — it's the SSAO2 blur post-process fragment shader, whose samples uniform (declared vec4) is used as an int loop bound, which desktop GL (GLSL ES 3.0) rejects:

mediump int _42 = -samples.x;                 // float -> int
for (mediump int i = _42; i < samples.x; ...) // int < float

That test's original exclusion reason was "crashes or hangs" (not the instancing follow-up), so it has been re-excluded. The genuinely instancing-attributable particle tests are confirmed passing on OpenGL (e.g. Particles, same per-instance attributes, validates on the Linux GL build). Net enablement is now 17 tests.

Copilot review nits (all addressed):

  • Removed the now-unused <set> include from NativeEngine.cpp (replaced with <map>).
  • Removed the unused instanceInfo structured binding in the draw-time loop.
  • The invalid-i_data-slot error now includes the attribute name and computed location/slot (all three traverser paths).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants