Quick answer: Declare explicit layout(set = N, binding = M) on every resource. Without explicit qualifiers, the compiler auto-assigns — reflection just reports whatever it picked.

An engine builds descriptor set layouts from SPIRV-Reflect output. After a shader edit, bindings shift and the renderer binds the wrong resources. The shader relied on implicit binding numbers.

Always Be Explicit

// GLSL — explicit, stable
layout(set = 0, binding = 0) uniform CameraUBO { ... };
layout(set = 0, binding = 1) uniform sampler2D albedoTex;
layout(set = 1, binding = 0) uniform LightUBO { ... };

With explicit set/binding, reflection reports exactly what you declared — stable across edits. Without them, the compiler assigns numbers by declaration order, so adding a resource shifts everything after it.

Reflection Is a Mirror

SPIRV-Reflect isn’t “wrong” — it faithfully reports the SPIR-V’s binding decorations. If those came from auto-assignment, they’re fragile. The fix is in the shader source, not the reflection step.

HLSL via DXC

For HLSL compiled to SPIR-V, use explicit register spaces and [[vk::binding(M, N)]] attributes, or pass -fvk-*-binding-shift flags consistently — same principle: don’t leave it implicit.

Validate Against a Convention

Adopt a project convention (set 0 = per-frame, set 1 = per-material, set 2 = per-object) and assert it in code after reflection. A mismatch then fails loudly at load instead of rendering garbage.

Verifying

Edit a shader (add/remove a uniform). Reflection output for the unchanged resources stays identical. The renderer binds correctly without code changes.

“Reflection reports what’s in the SPIR-V. Make it deterministic with explicit set/binding qualifiers.”

Lint your shaders in CI for any resource missing an explicit set/binding — catches the fragile case before it ships.