From 77826c5d69b8b41533e6077324f4deb110089d35 Mon Sep 17 00:00:00 2001 From: "Hector, Tobias" Date: Wed, 26 Mar 2025 08:21:06 +0000 Subject: [PATCH 1/9] Initial draft proposal to modify resource typing in HLSL - Resource type handling overhauled for consistency - Stronger typing added to resource heap access - "Raw" buffer types added for handling GPU VAs - Recursive typing (e.g. buffers can contain buffers or images) - "Bindless" access for root and local root data - Enables deprecation of descriptor tables --- proposals/0TBD-improved-resource-typing.md | 524 +++++++++++++++++++++ 1 file changed, 524 insertions(+) create mode 100644 proposals/0TBD-improved-resource-typing.md diff --git a/proposals/0TBD-improved-resource-typing.md b/proposals/0TBD-improved-resource-typing.md new file mode 100644 index 000000000..93e197806 --- /dev/null +++ b/proposals/0TBD-improved-resource-typing.md @@ -0,0 +1,524 @@ + + +# Typed resource heaps + +* Proposal: [0TBD](0TBD-improved-resource-typing.md) +* Author(s): [Tobias Hector](https://github.com/tobski) +* Sponsor: Chris Bieneman +* Status: **Under Consideration** + + +## Introduction + +This proposal adds a method to access the resource heap in HLSL with stronger +typing, and adds "bindless" methods of accessing root constants and shader +record data, fully removing the need to use descriptor tables. + + +## Motivation + +The `ResourceHeap` added in Shader Model 6.6 exposed a typeless, unsized heap +to shader authors which could be used to access any descriptor placed in the +heap through the client API. + +While useful, without type information, it is incredibly difficult to +validate whether an application is doing what it's supposed to be doing, both +for debugging tools and for a shader author to reason about it in the first +place. + +In addition to this, some APIs now provide methods to treat descriptors as +plain old data (POD), even interleaving descriptors and other POD types in +the heap. (E.g. +[PSSL](https://gdcvault.com/play/1024241/Higher-Res-Without-Sacrificing-Quality) +[see 11 minutes in].) + +While regular POD types are currently off the table for DirectX 12 in its +current iteration of resource heaps, enabling stronger typing on the heap +would lay groundwork for exposing interleaved POD in future. + +Notably, PSSL as linked above also enables resources to be accessed freely +from any source; allowing nested structures and arbitrary handling of data. +Until or unless hardware is modified to allow descriptors in arbitrary +memory, handling actual descriptors in this way is not possible portably. +However, it should be possible to better enable passing handles to the heap +around by modifying the type system to handle resources more consistently. + + +## Proposed solution + +### Consistent Resource Type Handling + +This change proposes that resource type declarations are _always_ considered +as 32-bit offsets into the heap, allowing them to be declared in arbitrary +external memory. +This includes storing them in buffers of indeterminate size, allowing fairly +arbitrary nesting of resource declarations. + +Example declarations: + +```hlsl +// A constant buffer containing other resource indices, including a buffer that points to further resources. +struct SomeResources { + Texture2D texture; + RWBuffer buffer; +}; + +ConstantBuffer someResources; + +// RW Buffer; resource offsets can be written out too! +RWBuffer buffer; +``` + +Declaring these is equivalent to declaring integer values and passing those +values to `ResourceDescriptorHeap` with the same resource type. +This serves primarily to make accessing the heap and reasoning about +descriptors in the heap easier, rather than passing loose integers around. + +Samplers declarations work in the same way, but are offsets into the +`SamplerDescriptorHeap` instead. + +The offset is the index that would be provided to `ResourceDescriptorHeap`. +If non-homogenous descriptors are advertised in future, the offset can +instead be treated as a byte offset. + + +#### Open Issue: Does this cause any incompatibility issues? + +Switching out the type system like this will inevitably mean a lot of +codebase changes for the compiler. +It's entirely possible this means some existing behavior stops working which +some developer is relying on somewhere. +There needs to be at least some sort of large hammer switch for this behavior +change, as it's unlikely that any corner case behavior in the existing model +can be usefully ported over. + + +#### Open Issue: Recursive Nesting + +The spec above allows nesting of buffer references within each other, +enabling some fairly powerful data structures to be constructed. +However, HLSL does not have the capability currently to express recursive +structures in the same way that you could do in most languages. + +The cleanest fix from the user side would be to allow buffer typing to work +with forward declarations, similarly to pointer declarations. +E.g.: + +```hlsl +struct RecursiveType; + +typedef LinkedList RawConstantBuffer; + +struct RecursiveType { + uint Data; + LinkedList next; +}; +``` + +However, the practicality of implementing this is unknown at the moment. +This could be made to work with the rest of this proposal as-is, but it +requires manually loading index values via `ResourceEntry`, which is +syntactically awkward. + + +#### Open Issue: Resource Registers + +There's no real reason why a user couldn't continue to use resources declared +with register mappings alongside this proposal; and allowing this will enable +shaders to be more gradually transitioned to this new way of accessing +descriptors. +The only thing that may need to be restricted is mapping root data to +resources in the table, both to prevent aliasing with the new built-ins, and +because the mapped resources would not map to heap indices. + + +### Resource Entries + +A new function is provided to quickly access multiple consecutive resources +in the resource heap by using composite types: + +```hlsl +template +T ResourceEntry(uint offset); +``` + +* `T` must be or only include resource types. +* `offset` is the base offset into the resource heap that resources should + be read from. + +If `T` is a single resource type, it is retrieved from the resource heap at +`offset`. +If `T` is an array or struct, the first element or member will be retrieved +from the resource heap at `offset`, and each subsequent element or member +will be accessed at an offset equal to the sum of `offset` and the offset of +all elements/members defined before it. + +For example: + +```hlsl +struct SomeResources { + Texture2D texture; + RWBuffer buffer; +}; + +SomeResources someResources = ResourceEntry(16); +``` + +In DirectX 12, this would be equivalent to: + +```hlsl +SomeResources someResources; +someResources.texture = ResourceDescriptorHeap[16]; +someResources.buffer = ResourceDescriptorHeap[17]; +``` + +`offset` is the same index that would be provided to +`ResourceDescriptorHeap`, and each subsequent resource in `T` simply +increments the offset by 1. +In future, if non-homogenous descriptor sizes are advertised, as with +[VK_EXT_descriptor_buffer](https://docs.vulkan.org/features/latest/features/proposals/VK_EXT_descriptor_buffer.html), +`offset` could instead become a byte offset, enabling resources to be packed +much more tightly. + + +#### Open Issue: Should this replace the ResourceDescriptorHeap built-in? + +If non-homogenous resource sizes are exposed to shaders, +`ResourceDescriptorHeap` poses a problem as it assumes uniform resource +sizes. +`ResourceEntry()` uses strong typing, so could seamlessly switch to byte +offsets on the user's behalf. +Supporting both models in the same shader is likely to be very messy. + +As `ResourceEntry()` enables a superset of same functionality (the templated +type can just be a single resource type), and to avoid future +incompatibility, `ResourceDescriptorHeap` should be deprecated by this +proposal. + + +### Bindless Constants + +Currently, accessing root constants or shader table entries must be done via +root signature mappings. +Two new System Value semantics are added that can be declared with inputs +to an entry point, avoiding root signature mappings: + +* `SV_Constants` +* `SV_LocalConstants` + +Both of these can be declared as composite types, and refer to either the +global root or local root data provided in the client API. + +DirectX 12 does not distinguish root data as a homogenous array, instead +separating it into "root parameter indices" that define a set of up to 4 +32-bit root constants, a 64-bit root descriptor, or a 32-bit descriptor +table, with a total maximum size of 256 bytes across all indices. +See https://learn.microsoft.com/en-us/windows/win32/direct3d12/root-signatures-overview#root-constants-descriptors-and-tables +for information on how this API works. +When consumed via `SV_Constants` in the shader, these are packed tightly, in +root parameter index order. +Root constants appear exactly as the data set by the application, descriptor +table entries become a 32-bit integer index into the resource heap, and +root descriptors are the 64-bit data passed in by the application, which can +be interpreted as a Raw Buffer to be used in the shader. + +For Vulkan `SV_Constants` maps directly to push constants. + +As each shader table entry is simply a block of memory in both Vulkan and +DirectX, `SV_LocalConstants` is read as-is. +`SV_LocalConstants` is only available in shaders that can use local root data +(i.e. ray tracing and workgraphs). + +Example usage of SV_Constants with resources: + +```hlsl +struct DescriptorTable0 { + Texture2D a; + Texture2D b; + ConstantBuffer c; + RWBuffer d; + ... +}; + +struct RootData { + uint descriptorTable0Offset; + uint4 constants; + RawConstantBuffer<...> buffer; +}; + +void main(RootData root : SV_Constants) +{ + DescriptorTable0 descriptorTable0 = ResourceEntry(root.descriptorTable0); +} +``` + +In DirectX, this could be specified in the root signature as: + + * Binding 0 is a descriptor table + * Binding 1 is 4 root constants + * Binding 2 is a root descriptor CBV + +In Vulkan this would map to push constants, where `buffer` maps to a +`VkDeviceAddress` value from a buffer. + + +#### Open Issue: Allow resource entries to be constructed in-place? + +The above example shows the specification of a descriptor table from an index +in `SV_Constants`. +The manual step of construction is somewhat awkward, and it might make sense +to have a way to define those directly in storage. +Just specifying a structure would not be enough, as it would be treated as a +structure of multiple offsets, rather than a single offset to a contiguous +set of resources. + +Something like the following might be desirable: + +```hlsl +struct RootData { + ResourceEntry descriptorTable0; + uint4 constants; + RawConstantBuffer<...> buffer; +}; +``` + + +#### Open Issue: Allow explicit offsets? + +When defining a descriptor table, the offsets can be manually specified for +a subset of the entries, with other entries calculated manually, taking into +account any manually specified offsets as relevant. +It might be useful to have an attribute in resource entry structures +indicating this same functionality for compatibility reasons if nothing else. + +For example: + +```hlsl +struct DescriptorTable0 { + Texture2D a; + Texture2D b; + [[resourceoffset(16)]] + ConstantBuffer c; + RWBuffer d; + ... +}; +``` + +In this example the offset for `c` would be equal to 16, and `d` would take +an offset as the sum of 16 and the size of `c`. +It may also be beneficial to have a rolling offset variant, where the value +is added to the otherwise calculated offset. + + +### Raw Buffer types + +In the above example, a root descriptor (in that case a CBV) is passed in. +When using a root descriptor in DirectX, these are mapped to buffers when +using registers. +However, declaring a standard buffer type directly here would result in the +assumption of a 32-bit index into the heap, not a 64-bit root descriptor VA. + +New resource types are provided that can be declared to access a root +descriptor VA: + + * `RawConstantBuffer` + * `RawStructuredBuffer` + * `RawRWStructuredBuffer` + * `RawByteAddressBuffer` + * `RawRWByteAddressBuffer` + * `RawRasterizerOrderedBuffer` + * `RawRasterizerOrderedByteAddressBuffer` + * `RawRaytracingAccelerationStructure` + +These can be used in exactly the same way as their non-raw counterparts, +except that when declared in memory they correspond to a 64-bit GPU VA, +rather than a 32-bit heap index. + +Raw structured and byte address buffers have an extra optional `Size` +parameter to indicate the size of the buffer. +If this value is provided, accesses will be bounds checked against it, +providing zero values if the index is exceeded, and discarding writes. +Partial out of bounds conditions are treated as fully out of bounds. +The size of a constant buffer is implied from `T`, and acceleration +structures have no useful OOB behavior currently. + +These can thus be declared in _any_ external memory, and used freely in the +same way as other resource types. +For example: + +```hlsl +struct Data { + uint value; + float value2; +}; + +struct MoreBuffers { + RawConstantBuffer<...> a; + RawConstantBuffer<...> b; + RawConstantBuffer<...> c; +}; + +struct RootData { + RawConstantBuffer buffer; +}; + +void main(RootData root : SV_Constants) +{ + uint value = root.buffer.a.value; +} +``` + +NOTE: This usage outside of root constants may require driver changes in +DirectX. Vulkan works out of the box with device addresses. + + +#### Open Issue: Why are raw buffer types separate from their counterparts? + +Standard buffer types by this proposal are resources which live in the heap, +and are thus represented as a 32-bit index when read or written to memory. +Raw buffer types however, do not need to live in the heap, and are +represented as 64-bit pointers when accessed in external memory. +The only way to enable them to be the same type would impose heavy and +awkward restrictions on when and how they could be accessed in external +memory. +Having separate types feels like a cleaner compromise. + +A future direction might be to deprecate non-raw buffers, but this proposal +aims to remain compatible with the existing DirectX API and, to a degree, +with existing shaders. + + +#### Open Issue: Raw resource construction + +It would be useful to enable raw resources to be constructed from existing +heap resources of a matching type, possibly with an offset and reduced size +for non-constant buffer types. +This is not currently possible in any API, but if we could make it work it +would be one way to solve the "slice" problem, particularly if we enforce +that slices must be subsets of the original buffer. + +That might look, for example, something like a new member functions for +buffers: + +```hlsl +GetRaw(); +GetRawSlice(uint offset); +GetRawSlice(uint offset, uint size); +``` + +The sum of `offset` and `size` must be less than or equal to the original +buffer's size. +The returned raw buffer would be identical to the original buffer resource, +except that it would now behave as a 64-bit pointer when accessed (including +OOB semantics), would be a potentially smaller range of data, and would no +longer be associated with the heap. + + +#### Open Issue: Pointer math + +Currently there's no way to directly adjust the value of a pointer in a +shader legally, as aliasing is disallowed, and casting/construction of raw +resources is not supported. + +It's unlikely we want this to change, but it might be useful considering that +an API to get subsets of raw buffers would solve this "safely". +The previous issue suggests one such option. + + + +## Detailed design + +*The detailed design is not required until the feature is under review.* + +This section should grow into a feature specification that will live in the +specifications directory once complete. Each feature will need different levels +of detail here, but some common things to think through are: + +### HLSL Additions + +* How is this feature represented in the grammar? +* How does it interact with different shader stages? +* How does it work interact other HLSL features (semantics, buffers, etc)? +* How does this interact with C++ features that aren't already in HLSL? +* Does this have implications for existing HLSL source code compatibility? + +### Interchange Format Additions + +* What DXIL changes does this change require? +* What Metadata changes does this require? +* How will SPIRV be supported? + +### Diagnostic Changes + +* What additional errors or warnings does this introduce? +* What existing errors or warnings does this remove? + +#### Validation Changes + +* What additional validation failures does this introduce? +* What existing validation failures does this remove? + +### Runtime Additions + +#### Runtime information + +* What information does the compiler need to provide for the runtime and how? + +#### Device Capability + +* How does it interact with other Shader Model options? +* What shader model and/or optional feature is prerequisite for the bulk of + this feature? +* What portions are only available if an existing or new optional feature + is present? +* Can this feature be supported through emulation or some other means + in older shader models? + +## Testing + +* How will correct codegen for DXIL/SPIRV be tested? +* How will the diagnostics be tested? +* How will validation errors be tested? +* How will validation of new DXIL elements be tested? +* How will the execution results be tested? + + +## Transition Strategy for Breaking Changes (Optional) + +* Newly-introduced errors that cause existing shaders to newly produce errors + fall into two categories: + * Changes that produce errors from already broken shaders that previously + worked due to a flaw in the compiler. + * Changes that break previously valid shaders due to changes in what the compiler + accepts related to this feature. +* It's not always obvious which category a new error falls into +* Trickier still are changes that alter codegen of existing shader code. + +* If there are changes that will change how existing shaders compile, + what transition support will we provide? + * New compilation failures should have a clear error message and ideally a FIXIT + * Changes in codegen should include a warning and possibly a rewriter + * Errors that are produced for previously valid shader code would give ample + notice to developers that the change is coming and might involve rollout stages + +* Note that changes that allow shaders that failed to compile before to compile + require testing that the code produced is appropriate, but they do not require + any special transition support. In these cases, this section might be skipped. + + +## Alternatives considered (Optional) + +If alternative solutions were considered, please provide a brief overview. This +section can also be populated based on conversations that occur during +reviewing. Having these solutions and why they were rejected documented may save +trouble from those who might want to suggest feedback or additional features that +might build on this on. Even variations on the chosen solution can be interesting. + + +## Acknowledgments (Optional) + + - Chris Bieneman + - Nicolai Haehnle + - Alexander Johnston + + From 9b3397d9f76353a8fb6a9d2884a4708aa684f6ad Mon Sep 17 00:00:00 2001 From: "Hector, Tobias" Date: Wed, 26 Mar 2025 11:38:19 +0000 Subject: [PATCH 2/9] Various small cleanups --- proposals/0TBD-improved-resource-typing.md | 33 +++++++++++++--------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/proposals/0TBD-improved-resource-typing.md b/proposals/0TBD-improved-resource-typing.md index 93e197806..0edebbdd2 100644 --- a/proposals/0TBD-improved-resource-typing.md +++ b/proposals/0TBD-improved-resource-typing.md @@ -57,16 +57,14 @@ arbitrary nesting of resource declarations. Example declarations: ```hlsl -// A constant buffer containing other resource indices, including a buffer that points to further resources. +// A constant buffer containing other resource indices +// including a buffer that points to further resources. struct SomeResources { - Texture2D texture; - RWBuffer buffer; + Texture2D texture; + RWStructuredBuffer bufferOfTextures; // Can be written! }; ConstantBuffer someResources; - -// RW Buffer; resource offsets can be written out too! -RWBuffer buffer; ``` Declaring these is equivalent to declaring integer values and passing those @@ -157,14 +155,14 @@ For example: ```hlsl struct SomeResources { - Texture2D texture; - RWBuffer buffer; + Texture2D texture; + RWStructuredBuffer bufferOfTextures; }; SomeResources someResources = ResourceEntry(16); ``` -In DirectX 12, this would be equivalent to: +This would be equivalent to: ```hlsl SomeResources someResources; @@ -175,6 +173,7 @@ someResources.buffer = ResourceDescriptorHeap[17]; `offset` is the same index that would be provided to `ResourceDescriptorHeap`, and each subsequent resource in `T` simply increments the offset by 1. + In future, if non-homogenous descriptor sizes are advertised, as with [VK_EXT_descriptor_buffer](https://docs.vulkan.org/features/latest/features/proposals/VK_EXT_descriptor_buffer.html), `offset` could instead become a byte offset, enabling resources to be packed @@ -232,7 +231,7 @@ DirectX, `SV_LocalConstants` is read as-is. Example usage of SV_Constants with resources: ```hlsl -struct DescriptorTable0 { +struct DescriptorTableData { Texture2D a; Texture2D b; ConstantBuffer c; @@ -241,14 +240,14 @@ struct DescriptorTable0 { }; struct RootData { - uint descriptorTable0Offset; + uint descriptorTableOffset; uint4 constants; RawConstantBuffer<...> buffer; }; void main(RootData root : SV_Constants) { - DescriptorTable0 descriptorTable0 = ResourceEntry(root.descriptorTable0); + DescriptorTableData descriptorTable = ResourceEntry(root.descriptorTableOffset); } ``` @@ -276,12 +275,15 @@ Something like the following might be desirable: ```hlsl struct RootData { - ResourceEntry descriptorTable0; + DescriptorTable descriptorTable; uint4 constants; RawConstantBuffer<...> buffer; }; ``` +This would also allow applications to load/store descriptor table handles +directly in the same way as individual resource types. + #### Open Issue: Allow explicit offsets? @@ -294,7 +296,7 @@ indicating this same functionality for compatibility reasons if nothing else. For example: ```hlsl -struct DescriptorTable0 { +struct DescriptorTableData { Texture2D a; Texture2D b; [[resourceoffset(16)]] @@ -309,6 +311,9 @@ an offset as the sum of 16 and the size of `c`. It may also be beneficial to have a rolling offset variant, where the value is added to the otherwise calculated offset. +Potentially this could be applicable to POD in regular structs as well, to +enable more control over struct padding. + ### Raw Buffer types From 7c402cc1098c20d96c5110db4f040a6316f308bb Mon Sep 17 00:00:00 2001 From: "Hector, Tobias" Date: Wed, 26 Mar 2025 12:42:23 +0000 Subject: [PATCH 3/9] Typo: ResourceHeap -> ResourceDescriptorHeap --- proposals/0TBD-improved-resource-typing.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/0TBD-improved-resource-typing.md b/proposals/0TBD-improved-resource-typing.md index 0edebbdd2..d7dc229ce 100644 --- a/proposals/0TBD-improved-resource-typing.md +++ b/proposals/0TBD-improved-resource-typing.md @@ -17,9 +17,9 @@ record data, fully removing the need to use descriptor tables. ## Motivation -The `ResourceHeap` added in Shader Model 6.6 exposed a typeless, unsized heap -to shader authors which could be used to access any descriptor placed in the -heap through the client API. +The `ResourceDescriptorHeap` added in Shader Model 6.6 exposed a typeless, +unsized heap to shader authors which could be used to access any descriptor +placed in the heap through the client API. While useful, without type information, it is incredibly difficult to validate whether an application is doing what it's supposed to be doing, both From 95796438e3b4e3c7aab50fa189056faaddc8cece Mon Sep 17 00:00:00 2001 From: "Hector, Tobias" Date: Fri, 28 Mar 2025 14:09:09 +0000 Subject: [PATCH 4/9] Retool ResourceEntry to be a type rather than a function --- proposals/0TBD-improved-resource-typing.md | 182 ++++++++++++++------- 1 file changed, 119 insertions(+), 63 deletions(-) diff --git a/proposals/0TBD-improved-resource-typing.md b/proposals/0TBD-improved-resource-typing.md index d7dc229ce..cf342a002 100644 --- a/proposals/0TBD-improved-resource-typing.md +++ b/proposals/0TBD-improved-resource-typing.md @@ -105,7 +105,7 @@ E.g.: ```hlsl struct RecursiveType; -typedef LinkedList RawConstantBuffer; +typedef LinkedList ConstantBuffer; struct RecursiveType { uint Data; @@ -132,47 +132,64 @@ because the mapped resources would not map to heap indices. ### Resource Entries -A new function is provided to quickly access multiple consecutive resources +A new object is provided to quickly access multiple _consecutive_ resources in the resource heap by using composite types: ```hlsl template -T ResourceEntry(uint offset); +class ResourceEntry; ``` -* `T` must be or only include resource types. -* `offset` is the base offset into the resource heap that resources should - be read from. +This is treated in the same manner as resource types are in the prior +section, in that they are handled as a single underlying 32-bit offset into +the heap, and can be read and written as such. -If `T` is a single resource type, it is retrieved from the resource heap at -`offset`. -If `T` is an array or struct, the first element or member will be retrieved -from the resource heap at `offset`, and each subsequent element or member -will be accessed at an offset equal to the sum of `offset` and the offset of -all elements/members defined before it. +However, unlike single resource types, `T` may be a composite object of +_multiple_ resource types, with the first struct member or array element +read from the underlying offset, and and each subsequent element or member +will be accessed at an offset equal to the sum of the base offset and the +relative offset of all elements/members defined before it. +This works with nested types just as well, in the case of a struct being +nested inside another struct or array, for example. + +The `ResourceEntry` itself can be read and written, though its contents are +read-only, as the offsets are fixed from the base. +The contents may be written freely to other locations however. For example: ```hlsl -struct SomeResources { - Texture2D texture; - RWStructuredBuffer bufferOfTextures; +struct Resources { + Texture2D texture; + Texture2D anotherTexture; }; -SomeResources someResources = ResourceEntry(16); -``` - -This would be equivalent to: - -```hlsl -SomeResources someResources; -someResources.texture = ResourceDescriptorHeap[16]; -someResources.buffer = ResourceDescriptorHeap[17]; +RWStructuredBuffer > resourceBuffer; + +void main(...) { + // Read a single index from the resource buffer; + ResourceEntry resources0 = resourceBuffer[0]; + + // Can be accessed as a const version of its target type + Resources someResources = resources0; + + // Can be stored as its own type + resourceBuffer[1] = resources0; + + // Can access sub-parts of the ResourceEntry's structs as independent resources + someResources.texture = resourceBuffer[1].anotherTexture; + + // **Cannot** store a variable of type T into a ResourceEntry with type T + /* resourceBuffer[2] = someResources; */ + + // **Cannot** write members/elements of a ResourceEntry; contents are read-only as offsets are fixed + /* resourceBuffer[1].texture = resourceBuffer[1].anotherTexture; */ +} ``` -`offset` is the same index that would be provided to -`ResourceDescriptorHeap`, and each subsequent resource in `T` simply -increments the offset by 1. +For the current DirectX12 interface where `ResourceDescriptorHeap` is a +homogenous array, the offset of every element/member of a ResourceEntry's +type can be treated as 1. In future, if non-homogenous descriptor sizes are advertised, as with [VK_EXT_descriptor_buffer](https://docs.vulkan.org/features/latest/features/proposals/VK_EXT_descriptor_buffer.html), @@ -180,19 +197,87 @@ In future, if non-homogenous descriptor sizes are advertised, as with much more tightly. -#### Open Issue: Should this replace the ResourceDescriptorHeap built-in? +#### Open Issue: Should there be a way to construct resource entries from integers? + +The proposed access method is that if you're passing in a resource index +from outside the shader, then that should be declared as such in the shader; +dynamic indices can be handled by specifying the base index as having an +array type. +For example: + +```hlsl +ConstantBuffer > textures; + +void main(...) +{ + ... + uint dynamicIndex = ...; + Texture2D myDynamicTexture = textures[dynamicIndex]; +} +``` + +A constructor function could be added roughly as: + +```hlsl +ResourceEntry(uint offset); +``` + +which would allow the construction of ResourceEntry objects from any +arbitrary integer the shader generates. + +This would not drastically change the implementation complexity or expected +usage patterns, so it'd be "ok" to have something like this from that +perspective. + +However, it allows shader authors to pass around integers until the very last +second, making debugging and interrogation of the shader code much more +difficult. +If this is deemed necessary to add, it should be done in a way that is +clearly marked as unsafe. + + +#### Open Issue: Could we avoid retyping all the resource handles by making use of ResourceEntry instead? + +The first part of this proposal suggested throwing away the existing (very +confusing) type handling of resource objects, and replacing it with +consistent handling of these types as 32-bit heap offsets. + +This proposal still could work even if that overhaul does not occur, if +`ResourceEntry`, where `T` is a single resource type, still works as +specified above. + +The disadvantage of this is primarily semantic overhead, as nested resource +types would now need to be declared differently, with the extra +`ResourceEntry` annotation between each nesting level. For example: + +```hlsl +struct Resources { + ResourceEntry texture; + ResourceEntry anotherTexture; +}; + +RWStructuredBuffer > resourceBuffer; +``` + +There may also be a cognitive burden with developers expected to understand +two independent type systems, which will almost inevitably lead to errors. + + +#### Open Issue: Should this deprecate the ResourceDescriptorHeap built-in? If non-homogenous resource sizes are exposed to shaders, `ResourceDescriptorHeap` poses a problem as it assumes uniform resource sizes. -`ResourceEntry()` uses strong typing, so could seamlessly switch to byte +`ResourceEntry` uses strong typing, so could seamlessly switch to byte offsets on the user's behalf. Supporting both models in the same shader is likely to be very messy. -As `ResourceEntry()` enables a superset of same functionality (the templated -type can just be a single resource type), and to avoid future -incompatibility, `ResourceDescriptorHeap` should be deprecated by this -proposal. +This also has similar problems with exposing a constructor for +ResourceEntry types from an integer, in that there is no indication +of the meaning of the type until it is used. + +For these reasons, the use of `ResourceDescriptorHeap` should be deprecated +as part of this proposal. ### Bindless Constants @@ -240,15 +325,10 @@ struct DescriptorTableData { }; struct RootData { - uint descriptorTableOffset; + ResourceEntry descriptorTable; uint4 constants; RawConstantBuffer<...> buffer; }; - -void main(RootData root : SV_Constants) -{ - DescriptorTableData descriptorTable = ResourceEntry(root.descriptorTableOffset); -} ``` In DirectX, this could be specified in the root signature as: @@ -261,30 +341,6 @@ In Vulkan this would map to push constants, where `buffer` maps to a `VkDeviceAddress` value from a buffer. -#### Open Issue: Allow resource entries to be constructed in-place? - -The above example shows the specification of a descriptor table from an index -in `SV_Constants`. -The manual step of construction is somewhat awkward, and it might make sense -to have a way to define those directly in storage. -Just specifying a structure would not be enough, as it would be treated as a -structure of multiple offsets, rather than a single offset to a contiguous -set of resources. - -Something like the following might be desirable: - -```hlsl -struct RootData { - DescriptorTable descriptorTable; - uint4 constants; - RawConstantBuffer<...> buffer; -}; -``` - -This would also allow applications to load/store descriptor table handles -directly in the same way as individual resource types. - - #### Open Issue: Allow explicit offsets? When defining a descriptor table, the offsets can be manually specified for From 84891f34f4b1aa3329bd75bd2b7cdce8d9b1129d Mon Sep 17 00:00:00 2001 From: "Hector, Tobias" Date: Fri, 28 Mar 2025 14:09:48 +0000 Subject: [PATCH 5/9] Rename "Raw" to "VA" for now, rework slices --- proposals/0TBD-improved-resource-typing.md | 81 +++++++++++++--------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/proposals/0TBD-improved-resource-typing.md b/proposals/0TBD-improved-resource-typing.md index cf342a002..4ee36f2ab 100644 --- a/proposals/0TBD-improved-resource-typing.md +++ b/proposals/0TBD-improved-resource-typing.md @@ -304,7 +304,7 @@ root parameter index order. Root constants appear exactly as the data set by the application, descriptor table entries become a 32-bit integer index into the resource heap, and root descriptors are the 64-bit data passed in by the application, which can -be interpreted as a Raw Buffer to be used in the shader. +be interpreted as a VA Buffer to be used in the shader. For Vulkan `SV_Constants` maps directly to push constants. @@ -327,7 +327,7 @@ struct DescriptorTableData { struct RootData { ResourceEntry descriptorTable; uint4 constants; - RawConstantBuffer<...> buffer; + VAConstantBuffer<...> buffer; }; ``` @@ -371,7 +371,7 @@ Potentially this could be applicable to POD in regular structs as well, to enable more control over struct padding. -### Raw Buffer types +### VA Buffer types In the above example, a root descriptor (in that case a CBV) is passed in. When using a root descriptor in DirectX, these are mapped to buffers when @@ -382,20 +382,20 @@ assumption of a 32-bit index into the heap, not a 64-bit root descriptor VA. New resource types are provided that can be declared to access a root descriptor VA: - * `RawConstantBuffer` - * `RawStructuredBuffer` - * `RawRWStructuredBuffer` - * `RawByteAddressBuffer` - * `RawRWByteAddressBuffer` - * `RawRasterizerOrderedBuffer` - * `RawRasterizerOrderedByteAddressBuffer` - * `RawRaytracingAccelerationStructure` + * `VAConstantBuffer` + * `VAStructuredBuffer` + * `VARWStructuredBuffer` + * `VAByteAddressBuffer` + * `VARWByteAddressBuffer` + * `VARasterizerOrderedBuffer` + * `VARasterizerOrderedByteAddressBuffer` + * `VARaytracingAccelerationStructure` -These can be used in exactly the same way as their non-raw counterparts, +These can be used in exactly the same way as their non-VA counterparts, except that when declared in memory they correspond to a 64-bit GPU VA, rather than a 32-bit heap index. -Raw structured and byte address buffers have an extra optional `Size` +VA structured and byte address buffers have an extra optional `Size` parameter to indicate the size of the buffer. If this value is provided, accesses will be bounds checked against it, providing zero values if the index is exceeded, and discarding writes. @@ -414,13 +414,13 @@ struct Data { }; struct MoreBuffers { - RawConstantBuffer<...> a; - RawConstantBuffer<...> b; - RawConstantBuffer<...> c; + VAConstantBuffer<...> a; + VAConstantBuffer<...> b; + VAConstantBuffer<...> c; }; struct RootData { - RawConstantBuffer buffer; + VAConstantBuffer buffer; }; void main(RootData root : SV_Constants) @@ -433,25 +433,45 @@ NOTE: This usage outside of root constants may require driver changes in DirectX. Vulkan works out of the box with device addresses. -#### Open Issue: Why are raw buffer types separate from their counterparts? +#### Open Issue: Why are VA buffer types separate from their counterparts? Standard buffer types by this proposal are resources which live in the heap, and are thus represented as a 32-bit index when read or written to memory. -Raw buffer types however, do not need to live in the heap, and are +VA buffer types however, do not need to live in the heap, and are represented as 64-bit pointers when accessed in external memory. The only way to enable them to be the same type would impose heavy and awkward restrictions on when and how they could be accessed in external memory. Having separate types feels like a cleaner compromise. -A future direction might be to deprecate non-raw buffers, but this proposal +A future direction might be to deprecate non-VA buffers, but this proposal aims to remain compatible with the existing DirectX API and, to a degree, with existing shaders. -#### Open Issue: Raw resource construction +#### Open Issue: Slices -It would be useful to enable raw resources to be constructed from existing +It would be useful to enable developers to get slices of an existing VA +resource for at least arrayed resources, such that subsets of the original +resource can be more tightly bounds checked at least during debugging. + +That could look like the following additional members for arrayed resource +types: + +```hlsl +T GetSlice(uint offset); +T GetSlice(uint offset, uint size); +``` + +The sum of `offset` and `size` must be less than or equal to the original +buffer's size. +The returned VA buffer would be identical to the original VA buffer resource, +except that it would be a smaller range of data. + + +#### Open Issue: VA resource construction + +It would be useful to enable VA resources to be constructed from existing heap resources of a matching type, possibly with an offset and reduced size for non-constant buffer types. This is not currently possible in any API, but if we could make it work it @@ -459,30 +479,25 @@ would be one way to solve the "slice" problem, particularly if we enforce that slices must be subsets of the original buffer. That might look, for example, something like a new member functions for -buffers: +non-VA buffer types: ```hlsl -GetRaw(); -GetRawSlice(uint offset); -GetRawSlice(uint offset, uint size); +T GetVAResource(); ``` -The sum of `offset` and `size` must be less than or equal to the original -buffer's size. -The returned raw buffer would be identical to the original buffer resource, +The returned VA buffer would be identical to the original buffer resource, except that it would now behave as a 64-bit pointer when accessed (including -OOB semantics), would be a potentially smaller range of data, and would no -longer be associated with the heap. +OOB semantics), and would no longer be associated with the heap. #### Open Issue: Pointer math Currently there's no way to directly adjust the value of a pointer in a -shader legally, as aliasing is disallowed, and casting/construction of raw +shader legally, as aliasing is disallowed, and casting/construction of VA resources is not supported. It's unlikely we want this to change, but it might be useful considering that -an API to get subsets of raw buffers would solve this "safely". +an API to get subsets of VA buffers would solve this "safely". The previous issue suggests one such option. From 2b36fc7db712f59e70d7ca15f068edf0f3d27877 Mon Sep 17 00:00:00 2001 From: "Hector, Tobias" Date: Fri, 28 Mar 2025 14:24:21 +0000 Subject: [PATCH 6/9] Add issue about VA buffer sizes and bounds checking --- proposals/0TBD-improved-resource-typing.md | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/proposals/0TBD-improved-resource-typing.md b/proposals/0TBD-improved-resource-typing.md index 4ee36f2ab..ebe08f3eb 100644 --- a/proposals/0TBD-improved-resource-typing.md +++ b/proposals/0TBD-improved-resource-typing.md @@ -433,6 +433,37 @@ NOTE: This usage outside of root constants may require driver changes in DirectX. Vulkan works out of the box with device addresses. +#### Open Issue: Should VA types with indeterminate size have a user-defined size? + +Yes, no, and also maybe. + +For code that has to go fast (which is usually every part of a shader), not +having bounds checks can be a performance win, so it's plausible that it +should be disabled for actual deployment. + +For debugging, having a size is actually quite useful - because it lets the +debugger alert users to any OOB conditions that arise, which may cause errors +in the applications. + +Having a size defined seems useful, but it might be beneficial to be able to +switch actual bounds checking on or off based on a build switch. +This shouldn't be something that necessarily has to be done when compiling +from HLSL, so needs some thought. +Having it fully dynamic likely wouldn't save much however, so it may be +desirable to ultimately have it under API control. + + +#### Open Issue: Should VA sizes be specified statically? + +It's likely that at least some applications would want to provide the size +dynamically as an argument to the shader, so supplying this should absolutely +be possible. +However, this doesn't lend itself to being part of a static declaration in +constants or other memory. +The proposed slice API below would be one way to solve this, but if there's a +more reasonable way to set the initial size that would be useful. + + #### Open Issue: Why are VA buffer types separate from their counterparts? Standard buffer types by this proposal are resources which live in the heap, From 03a63f5ece0c8fd7eb95928b4985dfcde8f73b7f Mon Sep 17 00:00:00 2001 From: "Hector, Tobias" Date: Thu, 3 Apr 2025 15:13:45 +0100 Subject: [PATCH 7/9] Add ResourceEntry construction from integers --- proposals/0TBD-improved-resource-typing.md | 32 ++++++++++------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/proposals/0TBD-improved-resource-typing.md b/proposals/0TBD-improved-resource-typing.md index ebe08f3eb..476cfe194 100644 --- a/proposals/0TBD-improved-resource-typing.md +++ b/proposals/0TBD-improved-resource-typing.md @@ -196,13 +196,10 @@ In future, if non-homogenous descriptor sizes are advertised, as with `offset` could instead become a byte offset, enabling resources to be packed much more tightly. - -#### Open Issue: Should there be a way to construct resource entries from integers? - -The proposed access method is that if you're passing in a resource index -from outside the shader, then that should be declared as such in the shader; -dynamic indices can be handled by specifying the base index as having an -array type. +When passing in a resource index from outside the shader, then that should +generally be declared as such in the shader; dynamic indices generated inside +the shader can be handled by specifying the base index as having an array +type. For example: ```hlsl @@ -216,24 +213,23 @@ void main(...) } ``` -A constructor function could be added roughly as: +However, there are cases where a developer may wish to pack the resource +index into other data, rather than consuming a full 32-bits for it. +Rather than having to fabricate an empty type and index into it in this case, +a constructor function is included: ```hlsl ResourceEntry(uint offset); ``` -which would allow the construction of ResourceEntry objects from any -arbitrary integer the shader generates. +This allows the construction of `ResourceEntry` object from any arbitrary +integer the shader generates. This would not drastically change the implementation complexity or expected -usage patterns, so it'd be "ok" to have something like this from that -perspective. - -However, it allows shader authors to pass around integers until the very last -second, making debugging and interrogation of the shader code much more -difficult. -If this is deemed necessary to add, it should be done in a way that is -clearly marked as unsafe. +usage patterns; however shader authors are advised to avoid using this unless +they have a clear need to, as the added context of a resource handle is +useful for debugging and validation. + #### Open Issue: Could we avoid retyping all the resource handles by making use of ResourceEntry instead? From 1da1e71d24899c8c133414db1f75acf8a3f5a89c Mon Sep 17 00:00:00 2001 From: "Hector, Tobias" Date: Thu, 3 Apr 2025 15:14:27 +0100 Subject: [PATCH 8/9] Add open issue about NURI --- proposals/0TBD-improved-resource-typing.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/proposals/0TBD-improved-resource-typing.md b/proposals/0TBD-improved-resource-typing.md index 476cfe194..1daeddaa0 100644 --- a/proposals/0TBD-improved-resource-typing.md +++ b/proposals/0TBD-improved-resource-typing.md @@ -230,6 +230,25 @@ usage patterns; however shader authors are advised to avoid using this unless they have a clear need to, as the added context of a resource handle is useful for debugging and validation. +#### Open Issue: Non-uniform indexing + +In the past, when indexing into resources with a non-uniform value, the index +needed to be decorated with the `NonUniformResourceIndex` attribute. + +As a language feature this is clunky - instead, this could be swapped around, +with indexing being non-uniform by default (unless the compiler can prove +otherwise). + +In such a world, it would be useful for shader authors to be able to provide +a hint indicating that a value is uniform at a given scope, enabling +compilers to generate more optimal code with minimal input. + +Something like what is outlined in +https://github.com/microsoft/hlsl-specs/pull/405 would be a good fit for +this, with the proposed uniformity qualifiers acting as a clear indicator to +the compiler about the uniformity of the index. + +This proposal would be a good place to make such a switch. #### Open Issue: Could we avoid retyping all the resource handles by making use of ResourceEntry instead? From 75c5da8edac149d84a227824aa2ba8ac34aeb757 Mon Sep 17 00:00:00 2001 From: "Hector, Tobias" Date: Thu, 3 Apr 2025 16:24:49 +0100 Subject: [PATCH 9/9] Update issue about ResourceEntry wrapper --- proposals/0TBD-improved-resource-typing.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/proposals/0TBD-improved-resource-typing.md b/proposals/0TBD-improved-resource-typing.md index 1daeddaa0..8d1109000 100644 --- a/proposals/0TBD-improved-resource-typing.md +++ b/proposals/0TBD-improved-resource-typing.md @@ -277,6 +277,13 @@ RWStructuredBuffer > resourceBuffer; There may also be a cognitive burden with developers expected to understand two independent type systems, which will almost inevitably lead to errors. +The advantage of this method however would be avoiding the need for separate +"VA" Buffer types, as both resource entries and VAs could be handled as +wrapper types, and it might be possible (if desired) to continue supporting +conversion to shading languages with legacy style bindings, as Slang has +chosen to do with their +[DescriptorHandle\ type](https://shader-slang.org/slang/user-guide/convenience-features.html#descriptorhandle-for-bindless-descriptor-access). + #### Open Issue: Should this deprecate the ResourceDescriptorHeap built-in?