Skip to content

Conversation

@Nielsbishere
Copy link
Contributor

@Nielsbishere Nielsbishere commented Jul 16, 2025

Currently, when resources are declared without explicit register bindings, DXC assigns them after GlobalDCE runs. This can lead to inconsistent register assignments across entry points (depending on optimizations), which causes mismatched resource IDs.

A common case is bindless-style includes, e.g. declaring multiple textures without explicit bindings but with spaces. In this situation, the generated binaries may not match across different entry points or when compiling as a library.

Solution

This PR introduces a new option (with two values currently; strip (default behavior, bindings get assigned after optimization) and reserve-all).

--fhlsl-unused-resource-bindings=reserve-all

When enabled, DXC will generate stable bindings by letting GlobalDCE eliminate only unused resources' symbols. Then, the bindings are generated and finally the unused resources are stripped again.

This ensures consistent register assignments across all entry points, whether compiling as individual shaders or as a library.

  • Resources are preserved until final binding assignment.
  • After assignment, unused bindings are stripped to avoid breaking optimization assumptions.
  • The behavior is similar to -flegacy-resource-reservation, but tailored for ensuring binding consistency.

Example

Command:

dxc -fhlsl-unused-resource-bindings=reserve-all -E main -T cs_6_0 myFile.hlsl

Shader:

Texture2D a;
Texture2D b;
RWTexture2D<float4> c;

[numthreads(1,1,1)]
void main() {
   c[0.xx] = b[0.xx];
}

[numthreads(1,1,1)]
void main2() {
   c[0.xx] = a[0.xx];
}

Generated bindings:

  • at0
  • bt1
  • cu0

Both main and main2 will consistently use the same bindings (b at t1, c at u0), regardless of entry point or library compilation (provided all resources appear in the exact same order).

Notes

…he IDxcRewriter, this will allow generating register ids for registers that don't fully define them.
…allow passing different rewriter flags. Also added support for cbuffer auto bindings.
…dxil lowering properly, it's similar to -flegacy-resource-reservation but distinct in some key ways: the option only works for reserved registers and we need it to work on any register that has been compiled out. We also don't necessarily need the register to stay at the end of DXIL generation, in fact we'd rather not. Because having the register present can cause unintended performance penalties (for example, I use this reflection to know what transitions to issue)
@github-actions
Copy link
Contributor

github-actions bot commented Sep 1, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@llvm-beanz
Copy link
Collaborator

@hekota & @bogner, please look over this. I'm not sure how this aligns with our work in Clang and how we want to handle bindings in the future.

@damyanp damyanp requested review from bogner and hekota October 21, 2025 19:46
@Nielsbishere Nielsbishere changed the title Rewriter generate consistent bindings Generate consistent bindings Nov 4, 2025
@Nielsbishere
Copy link
Contributor Author

Important information to add is that the spirv backend already handles this correctly, so no fix is needed there. Will add a unit test as well.

…g registers, $Globals, samplers, readonly, readwrite, etc.)
@hekota
Copy link
Member

hekota commented Nov 5, 2025

@Nielsbishere - Looking at this change and your proposed update to add -keep-all-resources flag, could you please help me understand what the benefit of the -consistent-bindings flag is compared to -keep-all-resources?

It seems to me that -keep-all-resources should already handle the consistent binding issue since it basically means “don’t strip unused resources.” The bindings in the example above end up inconsistent across different entry functions because the unused resources get stripped.

Also, as you’ve seen, implicit resource bindings tend to cause trouble since they make shader–application interfaces unpredictable and potentially compiler-dependent. It’d be great if you could find a way to stick with the explicit bindings to make sure your resource layouts stay stable and consistent.

bChanged |= ResourceRegisterAllocator.AllocateRegisters(DM);

if (DM.GetConsistentBindings())
DM.RemoveResourcesWithUnusedSymbols();
Copy link
Contributor

Choose a reason for hiding this comment

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

Above, after the call to LegalizeResources, it calls RemoveResourcesWithUnusedSymbols if bLocalChanged. Don't you want to predicate that with if (!DM.GetConsistentBindings())? Otherwise, resources may be removed before calling AllocateRegisters.

Also, it seems we should be tracking changes from this with bChanged. bChanged = bChanged || (numResources != newResources); is used earlier for this, and a similar thing could be done here after updating newResources. An alternative is to modify RemoveResourcesWithUnusedSymbols to return true if any changes were made.

Side note: Looking a little further up, I noticed when ApplyBindingTableFromMetadata is called, it doesn't update bChanged either, but that's an existing bug.

Copy link
Contributor

Choose a reason for hiding this comment

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

We probably need a test where LegalizeResources eliminates a resource use to test that code path. This relies on DxilValueCache, which was added largely to handle known-constant values in -Od cases where these values aren't simplified in the IR, but certain transformations we need to perform require these known values. In other words, you may be able to hit this code path using -Od with something that uses one resource or another in two code paths, depending on some condition that can be simplified to a constant, but is not normally simplified for -Od.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catches, have adjusted RemoveResourcesWithUnusedSymbols to return isModified and passing it along to bChanged. Also fixed the issue you said with LegalizeResources.
Since the issue ApplyBindingTableFromMetadata was already in there, it might be worth tackling this in a further PR/issue.
As for the test case for LegalizeResources; I'm not entirely sure where this would happen, I tried to read the code of LegalizeResources but it wasn't completely clear to me. Is there a unit test that tests legalize resources that I can combine with -consistent-bindings as a test case?

@Nielsbishere
Copy link
Contributor Author

Nielsbishere commented Nov 6, 2025

@hekota excuse me for the (following) long message, but these two have two very different uses in my previous work place. I'm not maintaining there anymore, but I still want them to have these changes to continue my work and will tell them how it can be used.
The compile process is quite complex, so I won't fully go over it here, but essentially:

  • Legacy technique syntax is parsed and replaced (PSO combinations, entrypoint definitions); coming from a DX9 codebase to DXR/DX12.
  • Shader parsing & preprocessing is ran to acquire register bindings & even optimized out registers (and to ensure consistent bindings manually by inserting register annotations)
  • Compilation & linking (shader variants with IDxcLinker)
  • Shader reflection is used on the final binary to find non optimized out resources (for transitions and knowing if certain debug commands are used)

The pipeline going forward could be:

  • Legacy technique syntax the same way, or a different system managing this in C++
  • Compile lib with -keep-all-resources to retrieve all resources
  • Compile & link each unique define combination with -consistent-bindings (getting rid of optimized out resources, but having consistent bindings)
  • Shader reflection (same thing as before) and validate that no new registers are created or have mismatching bindings for safety (though never happens in practice)

-keep-all-resources

is intended to (currently not working as it should in reflection, due to a missing createHandle) allow behavior similar to -Od with spirv; keep all resources even if optimized out. The reason why you don't want this by default is because reflection (presence of a register) may be used to guide for example transitions (or to know which real registers to keep). Why would you even want optimized out registers? Because sometimes you want a consistent interface that knows about the inputs to a shader, even if those shaders don't use the resource. It can still optimize it in the background but the interface will still show this variable as present. An example could be a visual scripting language that relies on this. The one I'm talking about relies on manual shader parsing to find out optimized out registers for the stability, adding this feature would allow to remove the manual parsing eventually. Consistent bindings in comparison, doesn't affect the emitted registers, only the bindings. So -keep-all-resources could be used to discover reflection info first and then a final compile per entrypoint could be used with -consistent-bindings to ensure no registers will be leftover.

-consistent-bindings

is a separate issue in the same system. In modern or legacy pipelines, shaders might have been written without explicit bindings or legacy semantics. These get registers auto assigned. Once again, the same shader parser is used to replace these legacy semantics with register bindings to avoid this issue this PR fixes. A legit use case for this is poor man's bindless, where you have arrays of textures/buffers. You could have 1 central include that defines this layout and puts them at space1 (no explicit bind point). If you change the first array size or add a register to this include it will automatically update. Otherwise you need to adjust all subsequent resource bindings. The system I'm talking about also validates that the final shader reflection matches the expected bindings to avoid accidental mismatches.
With both of those solved, the parser can drop all HLSL preprocessing and rely on C++ annotations for the remainder that can't easily be parsed. I'm not maintaining this system anymore, but in my own framework this would also be useful.

…ymbols now return bool isModified. Passing isModified along from RemoveResourcesWithUnusedSymbols to bChanged and fixed the LegalizeResources + -consistent-bindings oversight.
@Nielsbishere
Copy link
Contributor Author

Nielsbishere commented Nov 7, 2025

I see what you mean though, I think this is very possible and I've added behavior to keep-all-resources that would make this possible, but it has a bug I'm trying to figure out now (duplicates functions somehow?).
Basically what it does is it maintains all resources (except $Globals if no members or buffers of 0 size) and has a flag if the register is unused or not. Libraries use something similar but have more to worry about because one function might not reference a register but one of the others might.

The big problem with that PR relative to the consistent bindings (when it works) is that I'm not sure how this would interact with any tools out in the wild. Anything that might assume registers will be stripped if they're unused would get issues if they see it, because they don't utilize this flag to detect if it's really unused or not. So personally I'd see it more as a 'give me more reflection' option rather than something people should ship binaries with until other tools/drivers are aware of this change in behavior.

@hekota
Copy link
Member

hekota commented Nov 10, 2025

There is also the option to combine -keep-all-resources with -Qstrip_reflect for shipping binaries to make sure the implicit bindings are consistent and that there is no reflection bloat from it. Would that work in your case?

@Nielsbishere
Copy link
Contributor Author

Nielsbishere commented Nov 11, 2025

@hekota In theory yes, especially with my latest changes made that mark a resource with an unused flag to determine if a resource is used in the final binary. However, with -Qstrip_reflect It ends up only stripping the name for me (context: the simplified consistent bindings test from the other PR):

; Name                                 Type  Format         Dim      ID      HLSL Bind  Count
; ------------------------------ ---------- ------- ----------- ------- -------------- ------
;                                       UAV    byte         r/w      U0             u0     1
;                                       UAV    byte         r/w      U1             u1     1

This one still contains the unused resource. Or do you want me to modify Qstrip_reflect to drop unused resources?

@hekota
Copy link
Member

hekota commented Nov 11, 2025

Or do you want me to modify Qstrip_reflect to drop unused resources?

No, I don't think the flag should be overloaded like that.

@hekota
Copy link
Member

hekota commented Nov 11, 2025

@Nielsbishere The HLSL team has reviewed the flags you proposed, and the scenarios you outlined make sense. Thank you for the detailed explanation. We’re happy to move forward with these changes. However, since the flags serve a similar purpose, we would like you to consolidate them into a single flag with multiple options:

-fhlsl-unused-resource-bindings=strip    (default)
-fhlsl-unused-resource-bindings=reserve  (was: -consistent-bindings)
-fhlsl-unused-resource-bindings=keep     (was: -keep-all-resources)

It’s fine to implement these in separate PRs if that’s easier.

New features should always include a release note in the changelog:
https://github.com/microsoft/DirectXShaderCompiler/blob/main/docs/ReleaseNotes.md.
Please add an entry there as part of the PR that introduces these flags to make sure the documentation stays up to date.

Thanks! :)

@hekota
Copy link
Member

hekota commented Nov 13, 2025

Hi @Nielsbishere! You are very fast with the updates :)

We have realized we did not take into account the existing -flegacy-resource-reservation option in relation with these new flags. The legacy flag reserves the binding slots but only for explicitly bound resources, so it would be good to incorporate it into this new multi-value setting. We would have:

-fhlsl-unused-resource-bindings=strip             (default)
-fhlsl-unused-resource-bindings=reserve-explicit  (same as: -flegacy-resource-reservation)
-fhlsl-unused-resource-bindings=reserve-all       (was: -consistent-bindings)
-fhlsl-unused-resource-bindings=keep-all          (was: -keep-all-resources)

with the option to add keep-explicit option in the future if needed.

Could you please rename the values you are adding to reserve-all and keep-all? I can take care of adding reserve-explicit and mapping it to the -flegacy-resource-reservation later on.

Copy link
Member

@hekota hekota left a comment

Choose a reason for hiding this comment

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

Thank you for your contribution! As I mentioned above, we would like the options to
be called reserve-all and keep-all to enable adding additional options like reserve-explicit in the future.

If would be best if you could just add reserve-all value in this change, and once it is completed, you can update your other PR to add keep-all value to the enumeration. We generally want to avoid adding options that are not implemented or tested.

Also, the HLModule, DxilModule and CodeGenOptions should all use the UnusedResourceBindings enumeration instead of having separate flags for values (to avoid dealing with unsupported combinations of flags).

…release notes done by my markdown editor. TODO: Use UnusedResourceBindings in HLModule, DxilModule and CodeGenOptions
…ilShaderFlags instead. This is because 'keep-all' from the other PR relies on this Enum to be serialized; because reflection needs to know if unused resources are preserved.
@Nielsbishere
Copy link
Contributor Author

No problem, fixed it. I moved the enum to DxilConstants.h, not sure if that's the right place but seems like it's the easiest to access from all of these various places.
Instead of using Intermediate data I ended up adding it to the shader flags to ensure it lands into the final binary; this is required for the other PR since it needs to insert some shader variables to access the unbound resources in ID3D12FunctionReflection.

Copy link
Member

@hekota hekota left a comment

Choose a reason for hiding this comment

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

Looking good and getting close! Could you please update the title and description to reflect the latest changes since that will become the commit message?

@tex3d - could you please comment on whether DxilConstants.h is a good place for the new enumeration?

Comment on lines +20 to +23
static_assert(UnusedResourceBinding::Count <= UnusedResourceBinding(7),
"Only 3 bits are reserved for UnusedResourceBinding by HLOptions "
"and ShaderFlags");

Copy link
Member

Choose a reason for hiding this comment

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

Love this!

@Nielsbishere Nielsbishere changed the title Generate consistent bindings Add -fhlsl-unused-resource-bindings=reserve-all for deciding how to handle unused resources without register annotation Nov 14, 2025
@Nielsbishere
Copy link
Contributor Author

@hekota updated it with new info, but I'm not sure if too verbose/it's too similar to an issue.
Do you want me to write it more similar to for example s-perron's style? (e.g .#7880)

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

Labels

None yet

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

4 participants