diff options
| author | Yong He <yonghe@outlook.com> | 2025-01-10 10:57:04 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-01-10 10:57:04 -0800 |
| commit | 5290c580632cfb56847b863a32dc020a21d1c93e (patch) | |
| tree | 4c543f28d13f62a1dc3293b76151dda7585743ab | |
| parent | 4cfae806a6f9c0203ce44c4ce04df5ad66cdc8a2 (diff) | |
Initial implementation of SP#015 `DescriptorHandle<T>`. (#6028)
* Initial implementation of `ResourcePtr<T>`.
* Update docs
* Fix build error.
* Add more discussion.
* Update documentation.
* Update TOC.
* Fix.
* Fix.
* Add test case for custom `getResourceFromBindlessHandle`.
* Add namehint to generated descriptor heap param.
* Fix.
* Fix.
* format code
* Rename to `DescriptorHandle`, and add `T.Handle` alias.
* Fix compiler error.
* Fix.
* Fix build.
* Renames.
* Fix documentation.
* Documentation fix.
---------
Co-authored-by: slangbot <186143334+slangbot@users.noreply.github.com>
41 files changed, 1300 insertions, 406 deletions
diff --git a/docs/proposals/015-bindless-t.md b/docs/proposals/015-bindless-t.md deleted file mode 100644 index 89640f29a..000000000 --- a/docs/proposals/015-bindless-t.md +++ /dev/null @@ -1,199 +0,0 @@ -SP #015 - `Bindless<T>` type -============== - -## Status - -Author: Yong He - -Status: Design review. - -Implementation: - -Reviewed by: Theresa Foley - -## Background - -Textures, sampler states and buffers are typically passed to shader as opaque handles whose size and storage address is undefined. These handles are communicated to the GPU via "bind states" that are modified with host-side APIs. Because the handle has unknown size, it is not possible to read, copy or construct such a handle from the shader code, and it is not possible to store the handle in buffer memory. This makes both host code and shader code difficult to write and prevents more flexible encapsulation or clean object-oriented designs. - -With the recent advancement in hardware capabilities, a lot of modern graphics systems are adopting a "bindless" parameter passing idiom, where all resource handles are passed to the shader in a single global array, and all remaining references to texture, buffers or sampler states are represented as a single integer index into the array. This allows the shader code to workaround the restrictions around the opaque handle types. - -Direct3D Shader Model 6.6 introduces the "Dynamic Resources" capability, which further simplifies the way to write bindless shader code by removing the need to even declare the global array. - -We believe that graphics developers will greatly benefit from a system defined programming model for the bindless parameter passing idom that is versatile and cross-platform, which will provide a consistent interface so that different shader libraries using the bindless pattern can interop with each other without barriers. - -## Proposed Approach - -We introduce a `Bindless<T>` type that is defined as: -``` -struct Bindless<T> : IComparable<Bindless<T>> - where T : IOpaqueHandle -{ - __init(uint64_t value); -} -``` -Where `IOpaqueHandle` is an interface that is implemented by all texture, buffer and sampler state types: - -```slang -enum ResourceKind -{ - Unknown, Texture, ConstantBuffer, StorageBuffer, Sampler -} -interface IOpaqueHandle -{ - static const ResourceKind kind; -} -``` - -### Basic Usage - -`Bindless<T>` should provide the following features: - -- Construction/explicit cast from a 64-bit integer. -- Explicit cast to a 64-bit integer. -- Equality comparison. -- Implicit dereference to `T`. -- Implicit conversion to `T`. - -For example: - -```slang -uniform Bindless<Texture2D> texture; -uniform Bindless<SamplerState> sampler; - -void test() -{ - // Explicit cast from bindless handle to uint64_t value. - let idx = (uint64_t)texture; - - // Constructing bindless handle from uint64_t value. - let t = Bindless<Texture2D>(idx); - - // Comparison. - ASSERT(t == texture); - - // OK, `t` is first implicitly dereferenced to producee `Texture2D`, and - // then `Texture2D::Sample` is called. - // The `sampler` argument is implicitly converted from `Bindless<SamplerState>` - // to `SamplerState`. - t.Sample(sampler, float2(0,0)); -} -``` - -A `Bindless<T>` type is a concrete type whose size is always 8 bytes and is internally a 64-bit integer. -This means that you can use a `Bindless<T>` type in any context where an ordinary data type, e.g. `int` type -is allowed, such as in buffer elements. - -On targets where resource handles are already concrete and sized types, `Bindless<T>` simply translates to just `T`. -If `T` has native size or alignment that is less than 8 bytes, it will be rounded up to 8 bytes. If the native size for -`T` is greater than 8 bytes, it will be treated as an opaque type instead of translating to `T`. - -### Obtaining Actual Resource Handle from `Bindless<T>` - -Depending on the target platform and the design choices of the user's application, the way to obtain the actual -resource handle from a `Bindless<T>` integer handle can vary. Slang does not dictate how this conversion is done, -and instead, this is left to the user via Slang's link-time specialization ability. - -Slang defines the following core module declarations: - -```slang -extern T getResourceFromBindlessHandle(uint64_t handle) where T : IOpaqueHandle -{ - // Default Implementation - // ... -} -``` - -The `getResourceFromBindlessHandle` is used to convert from a bindless handle to actual opaque resource handle. -If this function is not provided by the user, the default implementation defined in the core module will be used. - -By default, the core module implementation of `getResourceFromBindlessHandle` should use the `ResourceDescriptorHeap` and -`SamplerDescriptorHeap` builtin object when generating HLSL code. When generating code on other targets, `getResourceFromBindlessHandle` -will fetch the resource handle from a system defined global array of the corresponding resource type. - -If/when SPIRV is extended to expose similar capabilities as D3D's `ResourceDescriptorHeap` feature, we should change the default implementation -to use that instead. Until we know the default implementation of `getResourceFromBindlessHandle` is stable, we should advise users -to provide their own implementation of `getResourceFromBindlessHandle` to prevent breakages. - -If the user application requires a different bindless implementation, this default behavior can be overrided by defining -`getResourceFromBindlessHandle` in the user code. Below is a possible user-space implementation of `getResourceFromBindlessHandle` -for Vulkan: - -```slang - -// All texture and buffer handles are defined in descriptor set 100. -[vk::binding(0, 100)] -__DynamicResource<__DynamicResourceKind.General> resourceHandles[]; - -// All sampler handles are defined in descriptor set 101. -[vk::binding(0, 101)] -__DynamicResource<__DynamicResourceKind.Sampler> samplerHandles[]; - -export getResourceFromBindlessHandle<T>(uint64_t handle) where T : IOpaqueHandle -{ - if (T.kind == ResourceKind.Sampler) - return (T)samplerHandles[handle]; - else - return (T)resourceHandles[handle]; -} -``` - -### Invalid Handle - -We reserve `uint64_t.maxValue` as a special handle value of `Bindless<T>` types to mean an invalid/null resource. -This will allow us to optimize `Optional<Bindless<Texture2D>>` to use the reserved value to mean no-value. - -The user should also be able to use `Bindless<T>.invalid` to refer to such an invalid value: - -```slang -struct Bindless<T> where T:IOpaqueHandle -{ - static const Bindless<T> invalid = Bindless<T>(uint64_t.maxValue); -} -``` - -## Alternatives Considered - -We initially considered to support a more general `Bindless<T>` where `T` can be any composite type, for example, allowing the following: - -```slang -struct Foo -{ - Texture2D t; - SamplerState s; - float ordinaryData; -} - -uniform Bindless<Foo> foo; -``` - -which is equivalent to: - -```slang -struct Bindless_Foo -{ - Bindless<Texture2D> t; - Bindless<SamplerState> s; - float s; -} -uniform Bindless_Foo foo; -``` - -While relaxing `T` this way adds an extra layer of convenience, it introduces complicated -semantic rules to the type system, and there is increased chance of exposing tricky corner -cases that are hard to get right. - -An argument for allowing `T` to be general composite types is that it enables sharing the same -code for both bindless systems and bindful systems. But this argument can also be countered by -allowing the compiler to treat `Bindless<T>` as `T` in a special mode if this feature is found to be useful. - -For now we think that restricting `T` to be an `IOpaqueHandle` type will result in a much simpler implementation, and is likely sufficient for current needs. Given that the trend of modern GPU architecture is moving towards bindless idioms and the whole idea of opaque handles may disappear in the future, we should be cautious at inventing too many heavy weight mechanisms around opaque handles. Nevertheless, this proposal still allows us to relax this requirement in the future if it becomes clear that such feature is valuable to our users. - -## Conclusion - -This proposal introduces a standard way to achieve bindless parameter passing idom on current graphics platforms. -Standardizing the way of writing bindless parameter binding code is essential for creating reusable shader code -libraries. The convenience language features around `Bindless<T>` type should also make shader code easier to write -and to maintain. Finally, by using Slang's link time specialization feature, -this proposal allows Slang to not get into the way of dicatating one specific way of passing -the actual resource handles to the shader code, and allows the user to customize how the conversion from integer handle -to resource handle is done in a way that best suites the application's design.
\ No newline at end of file diff --git a/docs/proposals/015-descriptor-handle.md b/docs/proposals/015-descriptor-handle.md new file mode 100644 index 000000000..b123a3980 --- /dev/null +++ b/docs/proposals/015-descriptor-handle.md @@ -0,0 +1,260 @@ +SP #015 - `DescriptorHandle<T>` type +============== + +## Status + +Author: Yong He + +Status: In Experiment. + +Implementation: [PR 6028](https://github.com/shader-slang/slang/pull/6028) + +Reviewed by: Theresa Foley, Jay Kwak + +## Background + +Textures, sampler states and buffers are typically passed to shader as opaque handles whose size and storage address is undefined. These handles are communicated to the GPU via "bind states" that are modified with host-side APIs. Because the handle has unknown size, it is not possible to read, copy or construct such a handle from the shader code, and it is not possible to store the handle in buffer memory. This makes both host code and shader code difficult to write and prevents more flexible encapsulation or clean object-oriented designs. + +With the recent advancement in hardware capabilities, a lot of modern graphics systems are adopting a "bindless" parameter passing idiom, where all resource handles are passed to the shader in a single global array, and all remaining references to texture, buffers or sampler states are represented as a single integer index into the array. This allows the shader code to workaround the restrictions around the opaque handle types. + +Direct3D Shader Model 6.6 introduces the "Dynamic Resources" capability, which further simplifies the way to write bindless shader code by removing the need to even declare the global array. + +We believe that graphics developers will greatly benefit from a system defined programming model for the bindless parameter passing idom that is versatile and cross-platform, which will provide a consistent interface so that different shader libraries using the bindless pattern can interop with each other without barriers. + +## Proposed Approach + +We introduce a `DescriptorHandle<T>` type that is defined as: +``` +struct DescriptorHandle<T> : IComparable + where T : IOpaqueDescriptor +{ + [require(hlsl_glsl_spirv)] + __init(uint2 value); // For HLSL, GLSL and SPIRV targets only. +} +``` +Where `IOpaqueDescriptor` is an interface that is implemented by all texture, buffer and sampler state types: + +```slang +enum DescriptorKind +{ + Unknown, + Texture, + CombinedTextureSampler, + Buffer, + Sampler, + AccelerationStructure, +} +interface IOpaqueDescriptor +{ + static const DescriptorKind kind; +} +``` + +All builtin types that implements `IOpaqueDescriptor` interface provide a convenience typealias for `DescriptorHandle<T>`. For example, `Texture2D.Handle` is an alias for `DescriptorHandle<Texture2D>`. + +### Basic Usage + +`DescriptorHandle<T>` should provide the following features: + +- `operator *` to deference the pointer and obatin the actual descriptor handle `T`. +- Implicit conversion to `T` when used in a location that expects `T`. +- When targeting HLSL, GLSL and SPIRV, `DescriptorHandle<T>` can be explicitly casted to and from a `uint2` value. +- Equality comparison. + +For example: + +```slang +uniform DescriptorHandle<Texture2D> texture; + +// `SamplerState.Handle` is equivalent to `DescriptorHandle<SamplerState>`. +uniform SamplerState.Handle sampler; + +void test() +{ + // Explicit cast from bindless handle to an uint2 value. + // (Available on HLSL, GLSL and SPIRV targets only) + let idx = (uint2)texture; + + // Constructing bindless handle from uint2 value. + // (Available on HLSL, GLSL and SPIRV targets only) + let t = DescriptorHandle<Texture2D>(idx); + + // Comparison. + ASSERT(t == texture); + + // OK, `t` is first implicitly dereferenced to producee `Texture2D`, and + // then `Texture2D::Sample` is called. + // The `sampler` argument is implicitly converted from `DescriptorHandle<SamplerState>` + // to `SamplerState`. + t.Sample(sampler, float2(0,0)); + + // Alternatively, the following syntax is also allowed, to + // make `DescriptorHandle` appear more like a pointer: + t->Sample(*sampler, float2(0, 0)); +} +``` + +A `DescriptorHandle<T>` type has target-dependent size, but it is always a concrete/physical data type and valid in all memory locations. For HLSL and SPIRV targets, it is represented by a two-component vector of 32-bit unsigned integer (`uint2`), and laid out as such. On these targets, builtin conversion functions are provided to construct +a `DescriptorHandle<T>` from a `uint2` value. + +On targets where descriptor handles are already concrete and sized types, `DescriptorHandle<T>` simply translates to `T`, and has size and alignment that matches the corresponding native type, which is queryable with Slang's reflection API. + +This means that on all targets where `DescriptorHandle<T>` is supported, you can use a `DescriptorHandle<T>` type in any context where an ordinary data type, e.g. `int` type is allowed, such as in buffer elements. + +### Obtaining Descriptor from `DescriptorHandle<T>` + +Depending on the target platform and the design choices of the user's application, the way to obtain the actual +descriptor handle from a `DescriptorHandle<T>` integer handle can vary. Slang does not dictate how this conversion is done, +and instead, this is left to the user via Slang's link-time specialization ability. + +Slang defines the following core module declarations: + +```slang +extern T getDescriptorFromHandle(DescriptorHandle<T> handle) where T : IOpaqueDescriptor +{ + // Default Implementation + return defaultGetDescriptorFromHandle(handle); +} +``` + +The `getDescriptorFromHandle` is used to convert from a bindless handle to actual opaque resource handle. +If this function is not provided by the user, the default implementation defined in the core module will be used. + +By default, the core module implementation of `getDescriptorFromHandle` should use the `ResourceDescriptorHeap` and +`SamplerDescriptorHeap` builtin object when generating HLSL code. When generating code on other targets, `getDescriptorFromHandle` +will fetch the descriptor handle from a system defined global array of the corresponding descriptor type. + +If/when SPIRV is extended to expose similar capabilities as D3D's `ResourceDescriptorHeap` feature, we should change the default implementation +to use that instead. Until we know the default implementation of `getDescriptorFromHandle` is stable, we should advise users +to provide their own implementation of `getDescriptorFromHandle` to prevent breakages. + +If the user application requires a different bindless implementation, this default behavior can be overrided by defining +`getDescriptorFromHandle` in the user code. Below is a possible user-space implementation of `getDescriptorFromHandle` +for Vulkan: + +```slang + +// All texture and buffer handles are defined in descriptor set 100. +[vk::binding(0, 100)] +__DynamicResource<__DynamicResourceKind.General> resourceHandles[]; + +// All sampler handles are defined in descriptor set 101. +[vk::binding(0, 101)] +__DynamicResource<__DynamicResourceKind.Sampler> samplerHandles[]; + +export T getDescriptorFromHandle<T>(DescriptorHandle<T> handle) where T : IOpaqueDescriptor +{ + if (T.kind == ResourceKind.Sampler) + return samplerHandles[((uint2)handle).x].asOpaqueDescriptor<T>(); + else + return resourceHandles[((uint2)handle).x].asOpaqueDescriptor<T>(); +} +``` + +The user can call `defaultGetDescriptorFromHandle` function from their implementation of `getDescriptorFromBindlessHandle` to dispatch to the default behavior. + +### Uniformity + +By default, the value of a `DescriptorHandle<T>` object is assumed to be dynamically uniform across all +execution threads. If this is not the case, the user is required to mark the `DescriptorHandle` as `nonuniform` +*immediately* before dereferencing it: +```slang +void test(DescriptorHandle<Texture2D> t) +{ + nonuniform(t)->Sample(...); +} +``` + +If the descriptor handle value is not uniform and `nonuniform` is not called, the result may be +undefined. + +### Combined Texture Samplers + +On platforms without native support for combined texture samplers, we will use both components of the +underlying `uint2` value: the `x` component stores the bindless handle for the texture, and the `y` component stores the bindless handle for the sampler. + +For example, given: + +```slang +uniform DescriptorHandle<Sampler2D> s; +void main() +{ + float2 uv = ...; + s.SampleLevel(uv, 0.0); +} +``` + +The Slang compiler should emit HLSL as follows: + +```hlsl +uniform uint2 s; +void main() +{ + float2 uv = ...; + Texture2D(ResourceDescriptorHeap[s.x]).SampleLevel( + SamplerState(SamplerDescriptorHeap[s.y]), + uv, + 0.0); +} +``` + +## Alternatives Considered + +We initially considered to support a more general `DescriptorHandle<T>` where `T` can be any composite type, for example, allowing the following: + +```slang +struct Foo +{ + Texture2D t; + SamplerState s; + float ordinaryData; +} + +uniform DescriptorHandle<Foo> foo; +``` + +which is equivalent to: + +```slang +struct Bindless_Foo +{ + DescriptorHandle<Texture2D> t; + DescriptorHandle<SamplerState> s; + float s; +} +uniform Bindless_Foo foo; +``` + +While relaxing `T` this way adds an extra layer of convenience, it introduces complicated +semantic rules to the type system, and there is increased chance of exposing tricky corner +cases that are hard to get right. + +An argument for allowing `T` to be general composite types is that it enables sharing the same +code for both bindless systems and bindful systems. But this argument can also be countered by +allowing the compiler to treat `DescriptorHandle<T>` as `T` in a special mode if this feature is found to be useful. + +For now we think that restricting `T` to be an `IOpaqueDescriptor` type will result in a much simpler implementation, and is likely sufficient for current needs. Given that the trend of modern GPU architecture is moving towards bindless idioms and the whole idea of opaque handles may disappear in the future, we should be cautious at inventing too many heavy weight mechanisms around opaque handles. Nevertheless, this proposal still allows us to relax this requirement in the future if it becomes clear that such feature is valuable to our users. + +In the initial version of this propsoal, `DescriptorHandle<T>` is named `Bindless<T>`. During discussion, we determined that this naming can be confusing to users who are coming from general GPU compute community and haven't heard of the term "bindless resources". We believe `DescriptorHandle<T>` is a better name because it reflects the essense of the type more accurately, and is consistent with D3D12 terminology in that `DescriptorHandle<T>` is the shader side representation of the `D3D12_GPU_DESCRIPTOR_HANDLE` structure. + +The initial version of the proposal defines `DescriptorHandle<T>` to be backed by an 8-byte integer value independent of the target. This is changed +so that Slang only guarantees `DescriptorHandle<T>` to be a phyiscal data type, and will have target-dependent size. Slang guarantees that `DescriptorHandle<T>` +will be lowered to a `uint2` value when targeting HLSL, GLSL and SPIRV, but not on other targets. This is because on targets where `T` is already a +phyisical type, their size can vary and may not fit in an 8-byte structure. For example, `StructuredBuffer<T>` maps to a `{T*, size_t}` structure when +targeting CUDA, which takes 16 bytes. In the meanwhile, forcing `DescriptorHandle<T>` to be `uint64_t` makes the feature unusable for lower-tier hardware +where 64-bit integers are not supported. Representing the handle with `uint2` allows the feature to be used without requiring this additional +capability. + +The initial proposal also reserves a value for invalid/null handle. This is removed because we cannot find +a safe value that won't be used across all targets we support. In particular, this is not possible on CUDA +and Metal because it is not possible to interpret these handles as plain integers. + +## Conclusion + +This proposal introduces a standard way to achieve bindless parameter passing idom on current graphics platforms. +Standardizing the way of writing bindless parameter binding code is essential for creating reusable shader code +libraries. The convenience language features around `DescriptorHandle<T>` type should also make shader code easier to write +and to maintain. Finally, by using Slang's link time specialization feature, +this proposal allows Slang to not get into the way of dicatating one specific way of passing +the actual descriptor handles to the shader code, and allows the user to customize how the conversion from integer handle +to descriptor handle is done in a way that best suites the application's design.
\ No newline at end of file diff --git a/docs/user-guide/03-convenience-features.md b/docs/user-guide/03-convenience-features.md index aeec8eb28..e6b337eed 100644 --- a/docs/user-guide/03-convenience-features.md +++ b/docs/user-guide/03-convenience-features.md @@ -531,12 +531,126 @@ Pointer types can also be specified using the generic syntax: `Ptr<MyType>` is e - Slang can produce pointers using the & operator from data in global memory. +- Slang doesn't support forming pointers to opaque handle types, e.g. `Texture2D`. For handle pointers, use `DescriptorHandle<T>` instead. + - Slang doesn't support coherent load/stores. - Slang doesn't support custom alignment specification. - Slang currently does not support pointers to immutable values, i.e. `const T*`. +## `DescriptorHandle` for Bindless Descriptor Access + +Slang supports the `DescriptorHandle<T>` type that represents a bindless handle to a resource. This feature provides a portable way of implementing +the bindless resource idiom. When targeting HLSL, GLSL and SPIRV where descriptor types (e.g. textures, samplers and buffers) are opaque handles, +`DescriptorHandle<T>` will translate into a `uint2` so it can be defined in any memory location. The underlying `uint2` value is treated as an index +to access the global descriptor heap or resource array in order to obtain the actual resource handle. On targets with where resource handles +are not opaque handles, `DescriptorHandle<T>` maps to `T` and will have the same size and alignment defined by the target. + +`DescriptorHandle<T>` is declared as: +```slang +struct DescriptorHandle<T> where T:IOpaqueDescriptor {} +``` +where `IOpaqueDescriptor` is an interface implemented by all resource types, including textures, +`ConstantBuffer`, `RaytracingAccelerationStructure`, `SamplerState`, `SamplerComparisonState` and all types of `StructuredBuffer`. + +You may also write `Texture2D.Handle` as a short-hand of `DescriptorHandle<Texture2D>`. + +`DescriptorHandle<T>` supports `operator *`, `operator ->`, and can implicitly convert to `T`, for example: + +```slang +uniform StructuredBuffer<DescriptorHandle<Texture2D>> textures; +uniform int textureIndex; + +// define a descriptor handle using builtin convenience typealias: +uniform StructuredBuffer<float4>.Handle output; + +[numthreads(1,1,1)] +void main() +{ + output[0] = textures[textureIndex].Load(int3(0)); + + // Alternatively, this syntax is also valid: + (*output)[0] = textures[textureIndex]->Load(int3(0)); +} +``` + +By default, when targeting HLSL, `DescriptorHandle<T>` translates to uses of `ResourceDescriptorHeap[index]` and `SamplerDescriptorHeap[index]`. +In particular, when combined with combined texture sampler types (e.g. `Sampler2D`), Slang will fetch the texture using the first +component of the handle, and the sampler state from the second component of the handle. For example: + +``` +uniform DescriptorHandle<Sampler2D> s; +void test() +{ + s.Sample(uv); +} +``` + +translates to: + +```hlsl +uniform uint2 s; +void test() +{ + Texture2D(ResourceDescriptorHeap[s.x]).Sample( + SamplerState(SamplerDescriptorHeap[s.y]), + uv + ); +} +``` + +When targeting SPIRV, Slang will introduce a global array of descriptors and fetch from the global array. +The descriptor set ID of the global descriptor array can be configured with the `-bindless-space-index` +(or `CompilerOptionName::BindlessSpaceIndex` when using the API) option. + +> #### Note +> The default implementation for SPIRV may change in the future if SPIRV is extended to provide what is +> equivalent to D3D's `ResourceDescriptorHeap` construct. + +Users can override the default behavior of convering from bindless handle to resource handle, by providing a +`getDescriptorFromHandle` in user code. For example: + +```slang +// All texture and buffer handles are defined in descriptor set 100. +[vk::binding(0, 100)] +__DynamicResource<__DynamicResourceKind.General> resourceHandles[]; + +// All sampler handles are defined in descriptor set 101. +[vk::binding(0, 101)] +__DynamicResource<__DynamicResourceKind.Sampler> samplerHandles[]; + +export T getDescriptorFromHandle<T>(DescriptorHandle<T> handle) where T : IOpaqueDescriptor +{ + __target_switch + { + case spirv: + if (T.kind == ResourceKind.Sampler) + return samplerHandles[((uint2)handle).x].asOpaqueDescriptor<T>(); + else + return resourceHandles[((uint2)handle).x].asOpaqueDescriptor<T>(); + default: + return defaultGetDescriptorFromHandle(handle); + } +} +``` + +The user can call `defaultGetDescriptorFromHandle` function from their implementation of +`getDescriptorFromHandle` to dispatch to the default behavior. + +By default, the value of a `DescriptorHandle<T>` object is assumed to be dynamically uniform across all +execution threads. If this is not the case, the user is required to mark the `DescriptorHandle` as `nonuniform` +*immediately* before dereferencing it: +```slang +void test(DescriptorHandle<Texture2D> t) +{ + nonuniform(t)->Sample(...); +} +``` + +If the resource pointer value is not uniform and `nonuniform` is not called, the result may be +undefined. + Extensions -------------------- Slang allows defining additional methods for a type outside its initial definition. For example, suppose we already have a type defined: diff --git a/docs/user-guide/toc.html b/docs/user-guide/toc.html index b5a6cccbb..9f9085cdd 100644 --- a/docs/user-guide/toc.html +++ b/docs/user-guide/toc.html @@ -45,6 +45,7 @@ <li data-link="convenience-features#if_let-syntax"><span>`if_let` syntax</span></li> <li data-link="convenience-features#reinterprett-operation"><span>`reinterpret<T>` operation</span></li> <li data-link="convenience-features#pointers-limited"><span>Pointers (limited)</span></li> +<li data-link="convenience-features#descriptorhandle-for-bindless-descriptor-access"><span>`DescriptorHandle` for Bindless Descriptor Access</span></li> <li data-link="convenience-features#extensions"><span>Extensions</span></li> <li data-link="convenience-features#multi-level-break"><span>Multi-level break</span></li> <li data-link="convenience-features#force-inlining"><span>Force inlining</span></li> diff --git a/include/slang.h b/include/slang.h index 05fb78775..356a2f496 100644 --- a/include/slang.h +++ b/include/slang.h @@ -973,6 +973,7 @@ typedef uint32_t SlangSizeT; ValidateUniformity, AllowGLSL, EnableExperimentalPasses, + BindlessSpaceIndex, // int // Internal diff --git a/source/slang/hlsl.meta.slang b/source/slang/hlsl.meta.slang index 5fb82d875..7964e26d8 100644 --- a/source/slang/hlsl.meta.slang +++ b/source/slang/hlsl.meta.slang @@ -20932,14 +20932,38 @@ struct ConstBufferPointer // new aliased bindings for each distinct cast type. // +/// Represent the kind of a descriptor type. +enum DescriptorKind +{ + Unknown, /// Unknown descriptor kind. + Texture, /// A texture descriptor. + CombinedTextureSampler, /// A combined texture and sampler state descriptor. + Buffer, /// A buffer descriptor. + Sampler, /// A sampler state descriptor. + AccelerationStructure, /// A ray tracing acceleration structure descriptor. +} + +/// Represents an opaque descriptor type, such as textures, samplers, and buffers etc, +/// whose size may be undefined and can't be directly accessed as ordinary data. +[sealed] +[builtin] +interface IOpaqueDescriptor +{ + /// The kind of the descriptor. + static const DescriptorKind kind; +} + __magic_type(DynamicResourceType) __intrinsic_type($(kIROp_DynamicResourceType)) struct __DynamicResource<let kind = __DynamicResourceKind.General> { __intrinsic_op($(kIROp_CastDynamicResource)) T as<T : __IDynamicResourceCastable<kind>>(); + + __intrinsic_op($(kIROp_CastDynamicResource)) + T asOpaqueDescriptor<T : IOpaqueDescriptor>(); } -interface __IDynamicResourceCastable<let kind = __DynamicResourceKind.General> +interface __IDynamicResourceCastable<let kind = __DynamicResourceKind.General> : IOpaqueDescriptor { } @@ -20955,41 +20979,166 @@ extension _Texture<T, Shape, isArray, isMS, sampleCount, access, isShadow, isCom __intrinsic_op($(kIROp_CastDynamicResource)) __implicit_conversion($(kConversionCost_GenericParamUpcast)) __init(__DynamicResource res); -} -${{{{ -const char* kDynamicResourceCastableTypes[] = { - "StructuredBuffer<T, L>", "RWStructuredBuffer<T, L>", - "AppendStructuredBuffer<T, L>", "ConsumeStructuredBuffer<T, L>", "RasterizerOrderedStructuredBuffer<T, L>", - "ByteAddressBuffer", "RWByteAddressBuffer", "RasterizerOrderedByteAddressBuffer", + typealias Handle = DescriptorHandle<This>; + + static const DescriptorKind kind = isCombined!=0 ? DescriptorKind.CombinedTextureSampler : DescriptorKind.Texture; - "SamplerState", "SamplerComparisonState", + __implicit_conversion($(kConversionCost_ImplicitDereference)) + [ForceInline] + __init(DescriptorHandle<This> bindless) + { + return getDescriptorFromHandle(bindless); + } +} - "ConstantBuffer<T, L>", "TextureBuffer<T>", +${{{{ +struct DynamicResourceTypeInfo +{ + const char* name; + const char* kind; + const char* dynamicKind; }; -for (auto typeName : kDynamicResourceCastableTypes) { - auto kind = strstr(typeName, "Sampler") ? "Sampler" : "General"; +const DynamicResourceTypeInfo kDynamicResourceCastableTypes[] = { + {"StructuredBuffer<T, L>", "Buffer", "General"}, + {"RWStructuredBuffer<T, L>", "Buffer", "General"}, + {"AppendStructuredBuffer<T, L>", "Buffer", "General"}, + {"ConsumeStructuredBuffer<T, L>", "Buffer", "General"}, + {"RasterizerOrderedStructuredBuffer<T, L>", "Buffer", "General"}, + {"ByteAddressBuffer", "Buffer", "General"}, + {"RWByteAddressBuffer", "Buffer", "General"}, + {"RasterizerOrderedByteAddressBuffer", "Buffer", "General"}, + {"SamplerState", "Sampler", "Sampler"}, + {"SamplerComparisonState", "Sampler", "Sampler"}, + {"ConstantBuffer<T, L>", "Buffer", "General"}, + {"TextureBuffer<T>", "Buffer", "General"}, + {"RaytracingAccelerationStructure", "AccelerationStructure", "General"}, +}; - if (strstr(typeName, "StructuredBuffer<T, L>")) - sb << "__generic<T, L : IBufferDataLayout = DefaultDataLayout>\n"; - else if (strstr(typeName, "ConstantBuffer<T, L>")) - sb << "__generic<T, L : IBufferDataLayout = DefaultDataLayout>\n"; - else if (strstr(typeName, "Buffer<T>")) +for (auto type : kDynamicResourceCastableTypes) { + auto dynamicKind = type.dynamicKind; + auto kind = type.kind; + auto typeName = type.name; + if (strstr(typeName, "<T, L>")) + sb << "__generic<T, L : IBufferDataLayout>\n"; + else if (strstr(typeName, "<T>")) sb << "__generic<T>\n"; }}}} -extension $(typeName) : __IDynamicResourceCastable<__DynamicResourceKind.$(kind)> +extension $(typeName) : __IDynamicResourceCastable<__DynamicResourceKind.$(dynamicKind)> { __intrinsic_op($(kIROp_CastDynamicResource)) __implicit_conversion($(kConversionCost_GenericParamUpcast)) __init(__DynamicResource res); + + static const DescriptorKind kind = DescriptorKind.$(kind); + + typealias Handle = DescriptorHandle<$(typeName)>; + + __implicit_conversion($(kConversionCost_ImplicitDereference)) + [ForceInline] + __init(DescriptorHandle<$(typeName)> bindless) + { + return getDescriptorFromHandle(bindless); + } } ${{{{ } }}}} +/// Represents a bindless resource handle. A bindless resource handle is always a concrete type and can be +/// declared in any memory location. +__magic_type(DescriptorHandleType) +__intrinsic_type($(kIROp_DescriptorHandleType)) +struct DescriptorHandle<T:IOpaqueDescriptor> : IComparable +{ + [require(glsl_spirv)] + [require(hlsl, sm_6_6)] + [require(wgsl)] + __intrinsic_op($(kIROp_CastUInt2ToDescriptorHandle)) + __init(uint2 handleValue); + + [ForceInline] + bool equals(DescriptorHandle<T> other) { return all(__vectorEql((uint2)this, (uint2)other)); } + + [ForceInline] + bool lessThan(DescriptorHandle<T> other) { let vthis = ((uint2)this); let vother = (uint2)other; return vthis.x < vother.x || (vthis.x == vother.x && vthis.y < vother.y); } + + [ForceInline] + bool lessThanOrEquals(DescriptorHandle<T> other) { let vthis = ((uint2)this); let vother = (uint2)other; return vthis.x < vother.x || (vthis.x == vother.x && vthis.y <= vother.y); } +} + +extension uint2 +{ + __intrinsic_op($(kIROp_CastDescriptorHandleToUInt2)) + [require(glsl_spirv)] + [require(hlsl, sm_6_6)] + [require(wgsl)] + __init<T:IOpaqueDescriptor>(DescriptorHandle<T> bindless); +} + +__generic<T:IOpaqueDescriptor> +[ForceInline] +__prefix T operator*(DescriptorHandle<T> value) +{ + return getDescriptorFromHandle(value); +} + +__intrinsic_op($(kIROp_GetDynamicResourceHeap)) +T[] __getDynamicResourceHeap<T:IOpaqueDescriptor>(); + + +// Used by the HLSL backend only, load a sampler state handle from SamplerDescriptorHeap. +__intrinsic_op($(kIROp_LoadSamplerDescriptorFromHeap)) +T __loadSamplerDescriptorFromHeap<T>(uint index); + +// Used by the HLSL backend only, load a resource handle from ResourceDescriptorHeap. +__intrinsic_op($(kIROp_LoadResourceDescriptorFromHeap)) +T __loadResourceDescriptorFromHeap<T>(uint index); + +// Used by the HLSL and Metal backends only, +// create a combined texture sampler object from a bindless handle. +__intrinsic_op($(kIROp_MakeCombinedTextureSamplerFromHandle)) +T __makeCombinedTextureSamplerFromHandle<T, U>(U handle); + +__intrinsic_op($(kIROp_CastDescriptorHandleToResource)) +T __castDescriptorHandleToResource<T:IOpaqueDescriptor>(DescriptorHandle<T> ptr); + +/// The default implementation of `getDescriptorFromHandle`, which converts from a descriptor handle +/// to a descriptor object. +[ForceInline] +T defaultGetDescriptorFromHandle<T:IOpaqueDescriptor>(DescriptorHandle<T> handleValue) +{ + __target_switch + { + case hlsl: + if (T.kind == DescriptorKind.Sampler) + return __loadSamplerDescriptorFromHeap<T>(((uint2)handleValue).x); + else if (T.kind == DescriptorKind.CombinedTextureSampler) + return __makeCombinedTextureSamplerFromHandle<T>((uint2)handleValue); + else + return __loadResourceDescriptorFromHeap<T>(((uint2)handleValue).x); + case spirv: + case glsl: + case wgsl: + return __getDynamicResourceHeap<T>()[((uint2)handleValue).x]; + default: + return __castDescriptorHandleToResource<T>(handleValue); + } +} + +/// Declaration of the `getDescriptorFromHandle` that the user code can provide to customize +/// how a descriptor handle is converted into a actual descriptor. +[ForceInline] +extern T getDescriptorFromHandle<T:IOpaqueDescriptor>(DescriptorHandle<T> handleValue) +{ + return defaultGetDescriptorFromHandle(handleValue); +} + +__intrinsic_op($(kIROp_NonUniformResourceIndex)) +DescriptorHandle<T> nonuniform<T:IOpaqueDescriptor>(DescriptorHandle<T> ptr); __glsl_version(450) __glsl_extension(GL_ARB_shader_clock) diff --git a/source/slang/slang-ast-type.h b/source/slang/slang-ast-type.h index c5c5de5d2..9d4297919 100644 --- a/source/slang/slang-ast-type.h +++ b/source/slang/slang-ast-type.h @@ -394,6 +394,11 @@ class GLSLInputAttachmentType : public BuiltinType }; +class DescriptorHandleType : public PointerLikeType +{ + SLANG_AST_CLASS(DescriptorHandleType) +}; + // Base class for types used when desugaring parameter block // declarations, includeing HLSL `cbuffer` or GLSL `uniform` blocks. class ParameterGroupType : public PointerLikeType diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp index 95d5a2a7c..ce6206973 100644 --- a/source/slang/slang-check-expr.cpp +++ b/source/slang/slang-check-expr.cpp @@ -511,24 +511,41 @@ DeclRefExpr* SemanticsVisitor::ConstructDeclRefExpr( } } -Expr* SemanticsVisitor::ConstructDerefExpr(Expr* base, SourceLoc loc) +Expr* SemanticsVisitor::constructDerefExpr(Expr* base, QualType elementType, SourceLoc loc) { - auto elementType = getPointedToTypeIfCanImplicitDeref(base->type); - SLANG_ASSERT(elementType); + if (auto resPtrType = as<DescriptorHandleType>(base->type)) + { + return coerce(CoercionSite::ExplicitCoercion, resPtrType->getElementType(), base); + } auto derefExpr = m_astBuilder->create<DerefExpr>(); derefExpr->loc = loc; derefExpr->base = base; derefExpr->type = QualType(elementType); - if (as<PtrType>(base->type)) + if (as<PtrType>(base->type) || as<RefType>(base->type)) + { derefExpr->type.isLeftValue = true; + } else + { + derefExpr->type.isLeftValue = base->type.isLeftValue; derefExpr->type.isLeftValue = base->type.isLeftValue; + derefExpr->type.hasReadOnlyOnTarget = base->type.hasReadOnlyOnTarget; + derefExpr->type.isWriteOnly = base->type.isWriteOnly; + } return derefExpr; } +Expr* SemanticsVisitor::ConstructDerefExpr(Expr* base, SourceLoc loc) +{ + auto elementType = getPointedToTypeIfCanImplicitDeref(base->type); + SLANG_ASSERT(elementType); + + return constructDerefExpr(base, elementType, loc); +} + InvokeExpr* SemanticsVisitor::constructUncheckedInvokeExpr( Expr* callee, const List<Expr*>& arguments) @@ -2301,8 +2318,7 @@ Expr* SemanticsVisitor::CheckSimpleSubscriptExpr(IndexExpr* subscriptExpr, Type* auto indexExpr = subscriptExpr->indexExprs[0]; - if (!indexExpr->type->equals(m_astBuilder->getIntType()) && - !indexExpr->type->equals(m_astBuilder->getUIntType())) + if (!isScalarIntegerType(indexExpr->type.type)) { getSink()->diagnose(indexExpr, Diagnostics::subscriptIndexNonInteger); return CreateErrorExpr(subscriptExpr); @@ -4084,43 +4100,16 @@ Expr* SemanticsVisitor::maybeDereference(Expr* inExpr, CheckBaseContext checkBas for (;;) { auto baseType = expr->type; - QualType elementType; - if (auto pointerLikeType = as<PointerLikeType>(baseType)) - { - elementType = QualType(pointerLikeType->getElementType()); - elementType.isLeftValue = baseType.isLeftValue; - elementType.hasReadOnlyOnTarget = baseType.hasReadOnlyOnTarget; - elementType.isWriteOnly = baseType.isWriteOnly; - } - else if (auto ptrType = as<PtrType>(baseType)) + if (as<PtrType>(baseType)) { if (checkBaseContext == CheckBaseContext::Subscript) return expr; - elementType = QualType(ptrType->getValueType()); - elementType.isLeftValue = true; } - else - { - auto newExpr = maybeOpenRef(expr); - if (newExpr != expr) - { - expr = newExpr; - continue; - } - } - if (elementType.type) - { - auto derefExpr = m_astBuilder->create<DerefExpr>(); - derefExpr->base = expr; - derefExpr->type = elementType; - - expr = derefExpr; - continue; - } - break; + auto elementType = getPointedToTypeIfCanImplicitDeref(baseType); + if (!elementType) + return expr; + expr = constructDerefExpr(expr, elementType, inExpr->loc); } - // Default case: just use the expression as-is - return expr; } Expr* SemanticsVisitor::CheckMatrixSwizzleExpr( @@ -4740,8 +4729,12 @@ Expr* SemanticsVisitor::_lookupStaticMember(DeclRefExpr* expr, Expr* baseExpress handleLeafCase(nsType->getDeclRef(), nsType); else if (auto aggType = as<DeclRefType>(e->type)) handleLeafCase(aggType->getDeclRef(), aggType); - else if (auto typetype = as<TypeType>(e->type)) - handleLeafCase(DeclRef<Decl>(), typetype->getType()); + else if (as<TypeType>(e->type)) + { + auto properType = CoerceToProperType(TypeExp(e)); + if (properType.type) + handleLeafCase(DeclRef<Decl>(), properType.type); + } }; auto& baseType = baseExpression->type; diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h index 300596caa..b3e30dbc2 100644 --- a/source/slang/slang-check-impl.h +++ b/source/slang/slang-check-impl.h @@ -1268,6 +1268,7 @@ public: Expr* originalExpr); Expr* ConstructDerefExpr(Expr* base, SourceLoc loc); + Expr* constructDerefExpr(Expr* base, QualType elementType, SourceLoc loc); InvokeExpr* constructUncheckedInvokeExpr(Expr* callee, const List<Expr*>& arguments); diff --git a/source/slang/slang-emit-c-like.cpp b/source/slang/slang-emit-c-like.cpp index b63b9118e..7b51495e2 100644 --- a/source/slang/slang-emit-c-like.cpp +++ b/source/slang/slang-emit-c-like.cpp @@ -391,6 +391,23 @@ void CLikeSourceEmitter::_emitType(IRType* type, DeclaratorInfo* declarator) _emitType(rateQualifiedType->getValueType(), declarator); } break; + case kIROp_DescriptorHandleType: + { + // If the T is already bindless for target, emit it directly. + auto resPtrType = cast<IRDescriptorHandleType>(type); + if (isResourceTypeBindless(resPtrType->getResourceType())) + _emitType(resPtrType->getResourceType(), declarator); + else + { + // Otherwise, emit the DescriptorHandle<T> as uint2. + IRBuilder builder(resPtrType); + builder.setInsertBefore(resPtrType); + emitSimpleTypeAndDeclarator( + builder.getVectorType(builder.getUIntType(), 2), + declarator); + } + } + break; case kIROp_ArrayType: { @@ -2568,7 +2585,11 @@ void CLikeSourceEmitter::defaultEmitInstExpr(IRInst* inst, const EmitOpInfo& inO emitOperand(inst->getOperand(1), rightSide(outerPrec, prec)); break; } - + case kIROp_CastDescriptorHandleToUInt2: + case kIROp_CastUInt2ToDescriptorHandle: + case kIROp_CastDescriptorHandleToResource: + emitOperand(inst->getOperand(0), outerPrec); + break; // Binary ops case kIROp_Add: case kIROp_Sub: diff --git a/source/slang/slang-emit-c-like.h b/source/slang/slang-emit-c-like.h index dd8e27674..e5080f731 100644 --- a/source/slang/slang-emit-c-like.h +++ b/source/slang/slang-emit-c-like.h @@ -512,6 +512,11 @@ protected: virtual void emitGlobalParamDefaultVal(IRGlobalParam* inst) { SLANG_UNUSED(inst); } virtual void emitPostDeclarationAttributesForType(IRInst* type) { SLANG_UNUSED(type); } virtual bool doesTargetSupportPtrTypes() { return false; } + virtual bool isResourceTypeBindless(IRType* type) + { + SLANG_UNUSED(type); + return false; + } virtual void emitLayoutSemanticsImpl( IRInst* inst, char const* uniformSemanticSpelling, diff --git a/source/slang/slang-emit-cpp.h b/source/slang/slang-emit-cpp.h index 2a1114afc..8d4f0d49e 100644 --- a/source/slang/slang-emit-cpp.h +++ b/source/slang/slang-emit-cpp.h @@ -44,6 +44,11 @@ public: protected: // Implement CLikeSourceEmitter interface + virtual bool isResourceTypeBindless(IRType* type) SLANG_OVERRIDE + { + SLANG_UNUSED(type); + return true; + } virtual bool doesTargetSupportPtrTypes() SLANG_OVERRIDE { return true; } virtual void emitParameterGroupImpl(IRGlobalParam* varDecl, IRUniformParameterGroupType* type) SLANG_OVERRIDE; diff --git a/source/slang/slang-emit-hlsl.cpp b/source/slang/slang-emit-hlsl.cpp index 83eec17b4..7bd3bb3db 100644 --- a/source/slang/slang-emit-hlsl.cpp +++ b/source/slang/slang-emit-hlsl.cpp @@ -903,6 +903,24 @@ bool HLSLSourceEmitter::tryEmitInstExprImpl(IRInst* inst, const EmitOpInfo& inOu return true; } + case kIROp_LoadSamplerDescriptorFromHeap: + { + emitType(inst->getDataType()); + m_writer->emit("("); + m_writer->emit("SamplerDescriptorHeap["); + emitOperand(inst->getOperand(0), getInfo(EmitOp::General)); + m_writer->emit("])"); + return true; + } + case kIROp_LoadResourceDescriptorFromHeap: + { + emitType(inst->getDataType()); + m_writer->emit("("); + m_writer->emit("ResourceDescriptorHeap["); + emitOperand(inst->getOperand(0), getInfo(EmitOp::General)); + m_writer->emit("])"); + return true; + } case kIROp_ByteAddressBufferLoad: { // HLSL byte-address buffers have two kinds of `Load` operations. diff --git a/source/slang/slang-emit-metal.h b/source/slang/slang-emit-metal.h index b67a5b801..eb63bd22e 100644 --- a/source/slang/slang-emit-metal.h +++ b/source/slang/slang-emit-metal.h @@ -25,6 +25,12 @@ public: protected: RefPtr<MetalExtensionTracker> m_extensionTracker; + virtual bool isResourceTypeBindless(IRType* type) SLANG_OVERRIDE + { + SLANG_UNUSED(type); + return true; + } + void emitMemoryOrderOperand(IRInst* inst); virtual void emitParameterGroupImpl(IRGlobalParam* varDecl, IRUniformParameterGroupType* type) SLANG_OVERRIDE; diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp index 1407404ad..068e1563c 100644 --- a/source/slang/slang-emit-spirv.cpp +++ b/source/slang/slang-emit-spirv.cpp @@ -1682,6 +1682,12 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex registerInst(inst, result); return result; } + case kIROp_DescriptorHandleType: + { + IRBuilder builder(inst); + builder.setInsertBefore(inst); + return emitOpTypeVector(inst, builder.getUIntType(), SpvLiteralInteger::from32(2)); + } case kIROp_SubpassInputType: return ensureSubpassInputType(inst, cast<IRSubpassInputType>(inst)); case kIROp_TextureType: @@ -3457,6 +3463,8 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex case kIROp_Lsh: result = emitArithmetic(parent, inst); break; + case kIROp_CastDescriptorHandleToUInt2: + case kIROp_CastUInt2ToDescriptorHandle: case kIROp_GlobalValueRef: { auto inner = ensureInst(inst->getOperand(0)); diff --git a/source/slang/slang-emit-wgsl.cpp b/source/slang/slang-emit-wgsl.cpp index f9181a50d..3b2cf12d0 100644 --- a/source/slang/slang-emit-wgsl.cpp +++ b/source/slang/slang-emit-wgsl.cpp @@ -557,6 +557,13 @@ void WGSLSourceEmitter::emitSimpleTypeImpl(IRType* type) m_writer->emit(">"); return; } + case kIROp_UnsizedArrayType: + { + m_writer->emit("array<"); + emitType((IRType*)type->getOperand(0)); + m_writer->emit(">"); + return; + } case kIROp_TextureType: if (auto texType = as<IRTextureType>(type)) { diff --git a/source/slang/slang-emit-wgsl.h b/source/slang/slang-emit-wgsl.h index 390ee876d..4e0c18821 100644 --- a/source/slang/slang-emit-wgsl.h +++ b/source/slang/slang-emit-wgsl.h @@ -12,7 +12,11 @@ public: : CLikeSourceEmitter(desc) { } - + virtual bool isResourceTypeBindless(IRType* type) SLANG_OVERRIDE + { + SLANG_UNUSED(type); + return true; + } virtual void emitParameterGroupImpl(IRGlobalParam* varDecl, IRUniformParameterGroupType* type) SLANG_OVERRIDE; virtual void emitEntryPointAttributesImpl( diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp index cd1b177b2..ee2582267 100644 --- a/source/slang/slang-emit.cpp +++ b/source/slang/slang-emit.cpp @@ -43,6 +43,7 @@ #include "slang-ir-explicit-global-context.h" #include "slang-ir-explicit-global-init.h" #include "slang-ir-fix-entrypoint-callsite.h" +#include "slang-ir-float-non-uniform-resource-index.h" #include "slang-ir-fuse-satcoop.h" #include "slang-ir-glsl-legalize.h" #include "slang-ir-glsl-liveness.h" @@ -64,6 +65,7 @@ #include "slang-ir-lower-bit-cast.h" #include "slang-ir-lower-buffer-element-type.h" #include "slang-ir-lower-combined-texture-sampler.h" +#include "slang-ir-lower-dynamic-resource-heap.h" #include "slang-ir-lower-generics.h" #include "slang-ir-lower-glsl-ssbo-types.h" #include "slang-ir-lower-l-value-cast.h" @@ -316,6 +318,7 @@ struct RequiredLoweringPassSet bool glslSSBO; bool byteAddressBuffer; bool dynamicResource; + bool dynamicResourceHeap; bool resolveVaryingInputRef; }; @@ -426,6 +429,9 @@ void calcRequiredLoweringPassSet( case kIROp_DynamicResourceType: result.dynamicResource = true; break; + case kIROp_GetDynamicResourceHeap: + result.dynamicResourceHeap = true; + break; case kIROp_ResolveVaryingInputRef: result.resolveVaryingInputRef = true; break; @@ -1122,6 +1128,9 @@ Result linkAndOptimizeIR( else simplifyIR(targetProgram, irModule, fastIRSimplificationOptions, sink); + if (requiredLoweringPassSet.dynamicResourceHeap) + lowerDynamicResourceHeap(targetProgram, irModule, sink); + #if 0 dumpIRIfEnabled(codeGenContext, irModule, "AFTER SSA"); #endif @@ -1391,6 +1400,11 @@ Result linkAndOptimizeIR( break; } + if (!isSPIRV(targetRequest->getTarget())) + { + floatNonUniformResourceIndex(irModule, NonUniformResourceIndexFloatMode::Textual); + } + // Legalize non struct parameters that are expected to be structs for HLSL. if (isD3DTarget(targetRequest)) legalizeNonStructParameterToStructForHLSL(irModule); diff --git a/source/slang/slang-ir-float-non-uniform-resource-index.cpp b/source/slang/slang-ir-float-non-uniform-resource-index.cpp new file mode 100644 index 000000000..e37b0445b --- /dev/null +++ b/source/slang/slang-ir-float-non-uniform-resource-index.cpp @@ -0,0 +1,219 @@ +#include "slang-ir-float-non-uniform-resource-index.h" + +#include "slang-ir-util.h" + +namespace Slang +{ +void processNonUniformResourceIndex( + IRInst* nonUniformResourceIndexInst, + NonUniformResourceIndexFloatMode floatMode) +{ + // float `NonUniformResourceIndex()` to right before the access operation + // by walking up the use-def chain + // from nonUniformResource inst of an index to an array of buffer or + // texture def all the way to the leaf operations. To be precise: + // - go through GEP and see if it calls an intrinsic function, + // then decorate the address itself (GetElementPtr) + // - go through GEP to identify the pointer access and the Loads that it + // accesses (GetElementPtr -> Load), then decorate the load instruction. + // - go through IntCasts to deal with u32 -> i32 / vice-versa (IntCast) + List<IRInst*> resWorkList; + + // Handle cases when `nonUniformResourceIndexInst` inst is wrapped around + // an index in a nested fashion, i.e. nonUniform(nonUniform(index)) by + // only adding the inner-most inst in the worklist, and work our way out. + auto insti = nonUniformResourceIndexInst; + while (insti->getOp() == kIROp_NonUniformResourceIndex) + { + if (resWorkList.getCount() != 0) + resWorkList.removeLast(); + resWorkList.add(insti); + insti = insti->getOperand(0); + } + + // For all the users of a `nonUniformResourceIndexInst`, make them directly + // use the underlying base inst that is wrapped by `nonUniformResourceIndex` + // and finally wrap them with a `nonUniformResourceIndex`, and add back to the + // worklist, and keep bubbling them up until it can. + for (Index i = 0; i < resWorkList.getCount(); i++) + { + auto inst = resWorkList[i]; + traverseUses( + inst, + [&](IRUse* use) + { + auto user = use->getUser(); + IRBuilder builder(user); + builder.setInsertBefore(user); + + IRInst* newUser = nullptr; + switch (user->getOp()) + { + case kIROp_IntCast: + // Replace intCast(nonUniformRes(x)), into nonUniformRes(intCast(x)) + newUser = builder.emitCast(user->getFullType(), inst->getOperand(0)); + break; + case kIROp_CastDescriptorHandleToUInt2: + { + // Replace castBindlessToInt(nonUniformRes(x)), into + // nonUniformRes(castBindlessToInt(x)) + auto operand = inst->getOperand(0); + newUser = builder.emitIntrinsicInst( + user->getFullType(), + kIROp_CastDescriptorHandleToUInt2, + 1, + &operand); + } + break; + case kIROp_GetElementPtr: + // Ignore when `NonUniformResourceIndex` is not on the index + if (floatMode != NonUniformResourceIndexFloatMode::SPIRV) + break; + if (user->getOperand(1) == inst) + { + // Replace gep(pArray, nonUniformRes(x)), into + // nonUniformRes(gep(pArray, x)) + newUser = builder.emitElementAddress( + user->getFullType(), + user->getOperand(0), + inst->getOperand(0)); + } + break; + case kIROp_GetElement: + // Ignore when `NonUniformResourceIndex` is not on base + if (user->getOperand(0) == inst) + { + // Replace getElement(nonuniformRes(obj), i), into + // nonUniformRes(getElement(obj, i)) + newUser = builder.emitElementExtract( + user->getFullType(), + inst->getOperand(0), + user->getOperand(1)); + } + break; + case kIROp_swizzle: + // Ignore when `NonUniformResourceIndex` is not on base + if (user->getOperand(0) == inst) + { + // Replace getElement(nonuniformRes(obj), i), into + // nonUniformRes(getElement(obj, i)) + ShortList<IRInst*> operands; + for (UInt i = 0; i < user->getOperandCount(); i++) + operands.add(user->getOperand(i)); + operands[0] = inst->getOperand(0); + newUser = builder.emitIntrinsicInst( + user->getFullType(), + kIROp_swizzle, + operands.getCount(), + operands.getArrayView().getBuffer()); + } + break; + case kIROp_NonUniformResourceIndex: + // Replace nonUniformRes(nonUniformRes(x)), into nonUniformRes(x) + newUser = inst->getOperand(0); + break; + case kIROp_Load: + if (floatMode != NonUniformResourceIndexFloatMode::SPIRV) + break; + // Replace load(nonUniformRes(x)), into nonUniformRes(load(x)) + newUser = builder.emitLoad(user->getFullType(), inst->getOperand(0)); + break; + default: + // Ignore for all other unknown insts. + break; + }; + + // Early exit when we could not process the `NonUniformResourceIndex` inst. + if (!newUser) + return; + + auto nonuniformUser = builder.emitNonUniformResourceIndexInst(newUser); + user->replaceUsesWith(nonuniformUser); + + // Update the worklist with the newly added `NonUniformResourceIndex` inst, + // based on the base inst it was constructed around, in case we need to further + // bubble up the `NonUniformResourceIndex` inst. + switch (user->getOp()) + { + case kIROp_IntCast: + case kIROp_GetElementPtr: + case kIROp_Load: + case kIROp_NonUniformResourceIndex: + case kIROp_CastDescriptorHandleToUInt2: + case kIROp_GetElement: + case kIROp_swizzle: + resWorkList.add(nonuniformUser); + break; + }; + + // Clean up the base inst from the IR module, to avoid duplicate decorations. + user->removeAndDeallocate(); + }); + } + + if (floatMode != NonUniformResourceIndexFloatMode::SPIRV) + return; + // Once all the `NonUniformResourceIndex` insts are visited, and the inst type is bubbled up + // to the parent, a decoration is added to the operands of the insts. + for (int i = 0; i < resWorkList.getCount(); ++i) + { + // It is only required to decorate the base inst, if the `NonUniformResourceIndex` inst + // around it has any active uses. + auto inst = resWorkList[i]; + if (!inst->hasUses()) + { + inst->removeAndDeallocate(); + continue; + } + // For each of the `NonUniformResourceIndex` inst that remain, decorate the base inst + // with a [NonUniformResource] decoration, which is the operand0 of the inst, only + // when the type is a resource type, or a pointer to a resource type, or a pointer + // in the Physical Storage buffer address space. + auto operand = inst->getOperand(0); + auto type = operand->getDataType(); + if (isResourceType(type) || isPointerToResourceType(type)) + { + IRBuilder builder(operand); + builder.addSPIRVNonUniformResourceDecoration(operand); + if (operand->getOp() == kIROp_Load) + { + // If the inst is a load, then the addr inst itself should also be decorated + // with the [NonUniformResource] decoration. + auto addr = operand->getOperand(0); + if (!addr->findDecoration<IRSPIRVNonUniformResourceDecoration>()) + builder.addSPIRVNonUniformResourceDecoration(addr); + } + } + inst->replaceUsesWith(operand); + inst->removeAndDeallocate(); + } +} + +void floatNonUniformResourceIndex(IRModule* module, NonUniformResourceIndexFloatMode floatMode) +{ + // Walk through all the instructions in the module, and float the `NonUniformResourceIndex` + // insts to the right place in the IR module. + + List<IRInst*> workList; + for (auto globalInst : module->getGlobalInsts()) + { + auto func = as<IRGlobalValueWithCode>(getGenericReturnVal(globalInst)); + if (!func) + continue; + workList.clear(); + for (auto block : func->getBlocks()) + { + for (auto inst : block->getChildren()) + { + if (inst->getOp() == kIROp_NonUniformResourceIndex) + workList.add(inst); + } + } + for (auto inst : workList) + { + if (inst->getParent() != nullptr) + processNonUniformResourceIndex(inst, floatMode); + } + } +} +} // namespace Slang diff --git a/source/slang/slang-ir-float-non-uniform-resource-index.h b/source/slang/slang-ir-float-non-uniform-resource-index.h new file mode 100644 index 000000000..1f79b2378 --- /dev/null +++ b/source/slang/slang-ir-float-non-uniform-resource-index.h @@ -0,0 +1,20 @@ +#pragma once + +namespace Slang +{ +struct IRInst; +struct IRModule; + +enum class NonUniformResourceIndexFloatMode +{ + Textual, + SPIRV, +}; + +void processNonUniformResourceIndex( + IRInst* nonUniformResourceIndexInst, + NonUniformResourceIndexFloatMode floatMode); + +void floatNonUniformResourceIndex(IRModule* module, NonUniformResourceIndexFloatMode floatMode); + +} // namespace Slang diff --git a/source/slang/slang-ir-glsl-legalize.cpp b/source/slang/slang-ir-glsl-legalize.cpp index 39f970319..7a46f45b4 100644 --- a/source/slang/slang-ir-glsl-legalize.cpp +++ b/source/slang/slang-ir-glsl-legalize.cpp @@ -4038,7 +4038,24 @@ void legalizeDispatchMeshPayloadForGLSL(IRModule* module) void legalizeDynamicResourcesForGLSL(CodeGenContext* context, IRModule* module) { - List<IRGlobalParam*> toRemove; + List<IRInst*> toRemove; + + // At this stage, we can safely remove the generic `getDescriptorFromHandle` function + // despite it being marked `export`. + for (auto inst : module->getGlobalInsts()) + { + if (auto genFunc = as<IRGeneric>(inst)) + { + if (!genFunc->hasUses()) + { + toRemove.add(genFunc); + } + } + } + for (auto inst : toRemove) + { + inst->removeAndDeallocate(); + } for (auto inst : module->getGlobalInsts()) { diff --git a/source/slang/slang-ir-inst-defs.h b/source/slang/slang-ir-inst-defs.h index 38e5f8869..f5af73dfa 100644 --- a/source/slang/slang-ir-inst-defs.h +++ b/source/slang/slang-ir-inst-defs.h @@ -133,6 +133,10 @@ INST(Nop, nop, 0, 0) INST(ComPtrType, ComPtr, 1, HOISTABLE) // A NativePtr<T> type represents a native pointer to a managed resource. INST(NativePtrType, NativePtr, 1, HOISTABLE) + + // A DescriptorHandle<T> type represents a bindless handle to an opaue resource type. + INST(DescriptorHandleType, DescriptorHandle, 1, HOISTABLE) + // An AtomicUint is a placeholder type for a storage buffer, and will be mangled during compiling. INST(GLSLAtomicUintType, GLSLAtomicUint, 0, HOISTABLE) @@ -365,6 +369,9 @@ INST(MakeTargetTuple, makeTuple, 0, 0) INST(MakeValuePack, makeValuePack, 0, 0) INST(GetTargetTupleElement, getTargetTupleElement, 0, 0) INST(GetTupleElement, getTupleElement, 2, 0) +INST(LoadResourceDescriptorFromHeap, LoadResourceDescriptorFromHeap, 1, 0) +INST(LoadSamplerDescriptorFromHeap, LoadSamplerDescriptorFromHeap, 1, 0) +INST(MakeCombinedTextureSamplerFromHandle, MakeCombinedTextureSamplerFromHandle, 1, 0) INST(MakeWitnessPack, MakeWitnessPack, 0, HOISTABLE) INST(Expand, Expand, 1, 0) INST(Each, Each, 1, HOISTABLE) @@ -1185,6 +1192,12 @@ INST(CastPtrToInt, CastPtrToInt, 1, 0) INST(CastIntToPtr, CastIntToPtr, 1, 0) INST(CastToVoid, castToVoid, 1, 0) INST(PtrCast, PtrCast, 1, 0) +INST(CastUInt2ToDescriptorHandle, CastUInt2ToDescriptorHandle, 1, 0) +INST(CastDescriptorHandleToUInt2, CastDescriptorHandleToUInt2, 1, 0) + +// Represents a no-op cast to convert a resource pointer to a resource on targets where the resource handles are already concrete types. +INST(CastDescriptorHandleToResource, CastDescriptorHandleToResource, 1, 0) + INST(TreatAsDynamicUniform, TreatAsDynamicUniform, 1, 0) INST(SizeOf, sizeOf, 1, 0) @@ -1201,6 +1214,7 @@ INST(IsHalf, IsHalf, 1, 0) INST(IsUnsignedInt, IsUnsignedInt, 1, 0) INST(IsSignedInt, IsSignedInt, 1, 0) INST(IsVector, IsVector, 1, 0) +INST(GetDynamicResourceHeap, GetDynamicResourceHeap, 0, HOISTABLE) INST(ForwardDifferentiate, ForwardDifferentiate, 1, 0) diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h index a288bca97..a58c2e900 100644 --- a/source/slang/slang-ir-insts.h +++ b/source/slang/slang-ir-insts.h @@ -364,7 +364,7 @@ struct IRSPIRVNonUniformResourceDecoration : IRDecoration { kOp = kIROp_SPIRVNonUniformResourceDecoration }; - IR_LEAF_ISA(RequireGLSLVersionDecoration) + IR_LEAF_ISA(SPIRVNonUniformResourceDecoration) IRConstant* getSPIRVNonUniformResourceOperand() { return cast<IRConstant>(getOperand(0)); } IntegerLiteralValue getSPIRVNonUniformResource() diff --git a/source/slang/slang-ir-layout.cpp b/source/slang/slang-ir-layout.cpp index 8180ea6aa..1332c8a25 100644 --- a/source/slang/slang-ir-layout.cpp +++ b/source/slang/slang-ir-layout.cpp @@ -355,6 +355,14 @@ static Result _calcSizeAndAlignment( case kIROp_DefaultBufferLayoutType: *outSizeAndAlignment = IRSizeAndAlignment(0, 4); return SLANG_OK; + case kIROp_DescriptorHandleType: + { + IRBuilder builder(type); + builder.setInsertBefore(type); + auto uintType = builder.getUIntType(); + auto uint2Type = builder.getVectorType(uintType, 2); + return getSizeAndAlignment(optionSet, rules, uint2Type, outSizeAndAlignment); + } case kIROp_AttributedType: { auto attributedType = cast<IRAttributedType>(type); diff --git a/source/slang/slang-ir-legalize-types.cpp b/source/slang/slang-ir-legalize-types.cpp index 962514b08..ae95ca8da 100644 --- a/source/slang/slang-ir-legalize-types.cpp +++ b/source/slang/slang-ir-legalize-types.cpp @@ -2088,7 +2088,9 @@ static LegalVal legalizeInst( case kIROp_Return: result = legalizeRetVal(context, args[0], (IRReturn*)inst); break; - + case kIROp_CastDescriptorHandleToResource: + result = LegalVal::simple(inst); + break; case kIROp_DebugVar: result = legalizeDebugVar(context, type, (IRDebugVar*)inst); break; diff --git a/source/slang/slang-ir-lower-combined-texture-sampler.cpp b/source/slang/slang-ir-lower-combined-texture-sampler.cpp index 20aab1291..7936dadad 100644 --- a/source/slang/slang-ir-lower-combined-texture-sampler.cpp +++ b/source/slang/slang-ir-lower-combined-texture-sampler.cpp @@ -19,6 +19,7 @@ struct LoweredCombinedSamplerStructInfo struct LowerCombinedSamplerContext { Dictionary<IRType*, LoweredCombinedSamplerStructInfo> mapTypeToLoweredInfo; + Dictionary<IRType*, LoweredCombinedSamplerStructInfo> mapLoweredTypeToLoweredInfo; CodeGenTarget codeGenTarget; LoweredCombinedSamplerStructInfo lowerCombinedTextureSamplerType(IRTextureTypeBase* textureType) @@ -96,6 +97,7 @@ struct LowerCombinedSamplerContext builder.addLayoutDecoration(structType, info.typeLayout); mapTypeToLoweredInfo.add(textureType, info); + mapLoweredTypeToLoweredInfo.add(info.type, info); return info; } }; @@ -182,10 +184,8 @@ void lowerCombinedTextureSamplers( continue; for (auto block : func->getBlocks()) { - IRInst* nextInst = nullptr; - for (auto inst = block->getFirstInst(); inst; inst = nextInst) + for (auto inst : block->getModifiableChildren()) { - nextInst = inst->getNextInst(); switch (inst->getOp()) { case kIROp_CombinedTextureSamplerGetTexture: @@ -195,6 +195,9 @@ void lowerCombinedTextureSamplers( auto loweredInfo = context.mapTypeToLoweredInfo.tryGetValue(combinedSamplerType); if (!loweredInfo) + loweredInfo = context.mapLoweredTypeToLoweredInfo.tryGetValue( + combinedSamplerType); + if (!loweredInfo) continue; builder.setInsertBefore(inst); auto fieldExtract = builder.emitFieldExtract( @@ -207,6 +210,65 @@ void lowerCombinedTextureSamplers( inst->removeAndDeallocate(); } break; + case kIROp_CastDescriptorHandleToResource: + { + auto handle = inst->getOperand(0); + if (as<IRDescriptorHandleType>(handle->getDataType())) + { + // If handle is still a DescriptorHandle, we are on a target that + // where native resource handles are already bindless, e.g. metal. + // On these platforms, the handle is a struct containing texture + // and sampler fields, so we just need to insert the extract operations. + auto combinedSamplerType = inst->getDataType(); + auto loweredInfo = + context.mapTypeToLoweredInfo.tryGetValue(combinedSamplerType); + if (!loweredInfo) + continue; + builder.setInsertBefore(inst); + auto textureVal = builder.emitFieldExtract( + loweredInfo->textureType, + handle, + loweredInfo->texture); + auto samplerVal = builder.emitFieldExtract( + loweredInfo->samplerType, + handle, + loweredInfo->sampler); + IRInst* args[] = {textureVal, samplerVal}; + auto combinedSampler = + builder.emitMakeStruct(loweredInfo->type, 2, args); + inst->replaceUsesWith(combinedSampler); + inst->removeAndDeallocate(); + } + } + break; + + case kIROp_MakeCombinedTextureSamplerFromHandle: + { + auto combinedSamplerType = inst->getDataType(); + auto loweredInfo = + context.mapTypeToLoweredInfo.tryGetValue(combinedSamplerType); + if (!loweredInfo) + continue; + auto handle = inst->getOperand(0); + builder.setInsertBefore(inst); + auto textureIndex = builder.emitElementExtract(handle, IRIntegerValue(0)); + auto texture = builder.emitIntrinsicInst( + loweredInfo->textureType, + kIROp_LoadResourceDescriptorFromHeap, + 1, + &textureIndex); + auto samplerIndex = builder.emitElementExtract(handle, IRIntegerValue(1)); + auto sampler = builder.emitIntrinsicInst( + loweredInfo->samplerType, + kIROp_LoadSamplerDescriptorFromHeap, + 1, + &samplerIndex); + IRInst* args[] = {texture, sampler}; + auto combinedSampler = builder.emitMakeStruct(loweredInfo->type, 2, args); + inst->replaceUsesWith(combinedSampler); + inst->removeAndDeallocate(); + } + break; } } } diff --git a/source/slang/slang-ir-lower-dynamic-resource-heap.cpp b/source/slang/slang-ir-lower-dynamic-resource-heap.cpp new file mode 100644 index 000000000..60ec688a0 --- /dev/null +++ b/source/slang/slang-ir-lower-dynamic-resource-heap.cpp @@ -0,0 +1,104 @@ +#include "slang-ir-lower-dynamic-resource-heap.h" + +#include "compiler-core/slang-artifact-associated-impl.h" +#include "slang-ir-util.h" + +namespace Slang +{ +UInt findUnusedSpaceIndex(TargetProgram* targetProgram, IRModule* module) +{ + HashSet<int> usedSpaces; + auto processVarLayout = [&](IRVarLayout* varLayout) + { + UInt spaceOffset = 0; + if (auto spaceAttr = varLayout->findOffsetAttr(LayoutResourceKind::RegisterSpace)) + { + spaceOffset = spaceAttr->getOffset(); + } + for (auto sizeAttr : varLayout->getTypeLayout()->getSizeAttrs()) + { + auto kind = sizeAttr->getResourceKind(); + if (!ShaderBindingRange::isUsageTracked(kind)) + continue; + + if (auto offsetAttr = varLayout->findOffsetAttr(kind)) + { + // Get the binding information from this attribute and insert it into the list + auto spaceIndex = spaceOffset + offsetAttr->getSpace(); + usedSpaces.add((int)spaceIndex); + } + } + }; + + for (auto inst : module->getGlobalInsts()) + { + if (as<IRGlobalParam>(inst)) + { + auto varLayout = findVarLayout(inst); + if (!varLayout) + continue; + processVarLayout(varLayout); + + auto paramGroupTypeLayout = as<IRParameterGroupTypeLayout>(varLayout->getTypeLayout()); + if (!paramGroupTypeLayout) + continue; + auto containerVarLayout = paramGroupTypeLayout->getContainerVarLayout(); + if (!containerVarLayout) + continue; + processVarLayout(containerVarLayout); + } + } + + // Find next unused space index. + int index = targetProgram->getOptionSet().getIntOption(CompilerOptionName::BindlessSpaceIndex); + while (usedSpaces.contains(index)) + { + index++; + } + return index; +} + +IRVarLayout* createResourceHeapVarLayoutWithSpace( + IRBuilder& builder, + IRInst* param, + UInt spaceIndex) +{ + SLANG_UNUSED(param); + IRTypeLayout::Builder typeLayoutBuilder(&builder); + typeLayoutBuilder.addResourceUsage( + LayoutResourceKind::DescriptorTableSlot, + LayoutSize::infinite()); + auto typeLayout = typeLayoutBuilder.build(); + IRVarLayout::Builder varLayoutBuilder(&builder, typeLayout); + varLayoutBuilder.findOrAddResourceInfo(LayoutResourceKind::RegisterSpace)->offset = spaceIndex; + varLayoutBuilder.findOrAddResourceInfo(LayoutResourceKind::DescriptorTableSlot)->offset = 0; + return varLayoutBuilder.build(); +} + +void lowerDynamicResourceHeap(TargetProgram* targetProgram, IRModule* module, DiagnosticSink* sink) +{ + SLANG_UNUSED(sink); + auto unusedSpaceIndex = findUnusedSpaceIndex(targetProgram, module); + List<IRInst*> workList; + for (auto globalInst : module->getGlobalInsts()) + { + if (globalInst->getOp() == kIROp_GetDynamicResourceHeap) + { + workList.add(globalInst); + } + } + for (auto inst : workList) + { + auto arrayType = as<IRArrayTypeBase>(inst->getDataType()); + IRBuilder builder(inst); + builder.setInsertBefore(inst); + + auto param = builder.createGlobalParam(arrayType); + auto varLayout = createResourceHeapVarLayoutWithSpace(builder, param, unusedSpaceIndex); + builder.addLayoutDecoration(param, varLayout); + builder.addNameHintDecoration(param, toSlice("__slang_resource_heap")); + inst->replaceUsesWith(param); + } +} + +} // namespace Slang diff --git a/source/slang/slang-ir-lower-dynamic-resource-heap.h b/source/slang/slang-ir-lower-dynamic-resource-heap.h new file mode 100644 index 000000000..b4d0b320a --- /dev/null +++ b/source/slang/slang-ir-lower-dynamic-resource-heap.h @@ -0,0 +1,13 @@ +#pragma once + +namespace Slang +{ + +struct IRModule; +class TargetProgram; +class DiagnosticSink; + +/// Replace `GetDynamicResourceHeap` insts with an actual array of resources. +void lowerDynamicResourceHeap(TargetProgram* targetProgram, IRModule* module, DiagnosticSink* sink); + +} // namespace Slang diff --git a/source/slang/slang-ir-spirv-legalize.cpp b/source/slang/slang-ir-spirv-legalize.cpp index 4c01e5640..737209207 100644 --- a/source/slang/slang-ir-spirv-legalize.cpp +++ b/source/slang/slang-ir-spirv-legalize.cpp @@ -8,6 +8,7 @@ #include "slang-ir-composite-reg-to-mem.h" #include "slang-ir-dce.h" #include "slang-ir-dominators.h" +#include "slang-ir-float-non-uniform-resource-index.h" #include "slang-ir-glsl-legalize.h" #include "slang-ir-insts.h" #include "slang-ir-layout.h" @@ -1098,130 +1099,6 @@ struct SPIRVLegalizationContext : public SourceEmitterBase addUsersToWorkList(newStore); } - void processNonUniformResourceIndex(IRInst* nonUniformResourceIndexInst) - { - // implement the translation to spirv by walking up the use-def chain - // from nonUniformResource inst of an index to an array of buffer or - // texture def all the way to the leaf operations. To be precise: - // - go through GEP and see if it calls an intrinsic function, - // then decorate the address itself (GetElementPtr) - // - go through GEP to identify the pointer access and the Loads that it - // accesses (GetElementPtr -> Load), then decorate the load instruction. - // - go through IntCasts to deal with u32 -> i32 / vice-versa (IntCast) - List<IRInst*> resWorkList; - - // Handle cases when `nonUniformResourceIndexInst` inst is wrapped around - // an index in a nested fashion, i.e. nonUniform(nonUniform(index)) by - // only adding the inner-most inst in the worklist, and work our way out. - auto insti = nonUniformResourceIndexInst; - while (insti->getOp() == kIROp_NonUniformResourceIndex) - { - if (resWorkList.getCount() != 0) - resWorkList.removeLast(); - resWorkList.add(insti); - insti = insti->getOperand(0); - } - - // For all the users of a `nonUniformResourceIndexInst`, make them directly - // use the underlying base inst that is wrapped by `nonUniformResourceIndex` - // and finally wrap them with a `nonUniformResourceIndex`, and add back to the - // worklist, and keep bubbling them up until it can. - for (Index i = 0; i < resWorkList.getCount(); i++) - { - auto inst = resWorkList[i]; - traverseUses( - inst, - [&](IRUse* use) - { - auto user = use->getUser(); - IRBuilder builder(user); - builder.setInsertBefore(user); - - IRInst* newUser = nullptr; - switch (user->getOp()) - { - case kIROp_IntCast: - // Replace intCast(nonUniformRes(x)), into nonUniformRes(intCast(x)) - newUser = builder.emitCast(user->getFullType(), inst->getOperand(0)); - break; - case kIROp_GetElementPtr: - // Ignore when `NonUniformResourceIndex` is not on the index - if (user->getOperand(1) == inst) - { - // Replace gep(pArray, nonUniformRes(x)), into - // nonUniformRes(gep(pArray, x)) - newUser = builder.emitElementAddress( - user->getFullType(), - user->getOperand(0), - inst->getOperand(0)); - } - break; - case kIROp_NonUniformResourceIndex: - // Replace nonUniformRes(nonUniformRes(x)), into nonUniformRes(x) - newUser = inst->getOperand(0); - break; - case kIROp_Load: - // Replace load(nonUniformRes(x)), into nonUniformRes(load(x)) - newUser = builder.emitLoad(user->getFullType(), inst->getOperand(0)); - break; - default: - // Ignore for all other unknown insts. - break; - }; - - // Early exit when we could not process the `NonUniformResourceIndex` inst. - if (!newUser) - return; - - auto nonuniformUser = builder.emitNonUniformResourceIndexInst(newUser); - user->replaceUsesWith(nonuniformUser); - - // Update the worklist with the newly added `NonUniformResourceIndex` inst, - // based on the base inst it was constructed around, in case we need to further - // bubble up the `NonUniformResourceIndex` inst. - switch (user->getOp()) - { - case kIROp_IntCast: - case kIROp_GetElementPtr: - case kIROp_Load: - case kIROp_NonUniformResourceIndex: - resWorkList.add(nonuniformUser); - break; - }; - - // Clean up the base inst from the IR module, to avoid duplicate decorations. - user->removeAndDeallocate(); - }); - } - - // Once all the `NonUniformResourceIndex` insts are visited, and the inst type is bubbled up - // to the parent, a decoration is added to the operands of the insts. - for (int i = 0; i < resWorkList.getCount(); ++i) - { - // It is only required to decorate the base inst, if the `NonUniformResourceIndex` inst - // around it has any active uses. - auto inst = resWorkList[i]; - if (!inst->hasUses()) - { - inst->removeAndDeallocate(); - continue; - } - // For each of the `NonUniformResourceIndex` inst that remain, decorate the base inst - // with a [NonUniformResource] decoration, which is the operand0 of the inst, only - // when the type is a resource type, or a pointer to a resource type, or a pointer - // in the Physical Storage buffer address space. - auto operand = inst->getOperand(0); - auto type = operand->getDataType(); - if (isResourceType(type) || isPointerToResourceType(type)) - { - IRBuilder builder(operand); - builder.addSPIRVNonUniformResourceDecoration(operand); - } - inst->replaceUsesWith(operand); - inst->removeAndDeallocate(); - } - } - void processImageSubscript(IRImageSubscript* subscript) { if (auto ptrType = as<IRPtrTypeBase>(subscript->getDataType())) @@ -1765,7 +1642,7 @@ struct SPIRVLegalizationContext : public SourceEmitterBase processRWStructuredBufferStore(inst); break; case kIROp_NonUniformResourceIndex: - processNonUniformResourceIndex(inst); + processNonUniformResourceIndex(inst, NonUniformResourceIndexFloatMode::SPIRV); break; case kIROp_loop: processLoop(as<IRLoop>(inst)); diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp index daeaca67b..e15ec6f07 100644 --- a/source/slang/slang-ir.cpp +++ b/source/slang/slang-ir.cpp @@ -8226,6 +8226,10 @@ bool IRInst::mightHaveSideEffects(SideEffectAnalysisOptions options) case kIROp_CastPtrToInt: case kIROp_CastIntToPtr: case kIROp_PtrCast: + case kIROp_CastUInt2ToDescriptorHandle: + case kIROp_CastDescriptorHandleToUInt2: + case kIROp_CastDescriptorHandleToResource: + case kIROp_GetDynamicResourceHeap: case kIROp_CastDynamicResource: case kIROp_AllocObj: case kIROp_BitfieldExtract: diff --git a/source/slang/slang-ir.h b/source/slang/slang-ir.h index b70c4e054..b29b3b815 100644 --- a/source/slang/slang-ir.h +++ b/source/slang/slang-ir.h @@ -1654,6 +1654,11 @@ struct IRRateQualifiedType : IRType IR_LEAF_ISA(RateQualifiedType) }; +struct IRDescriptorHandleType : IRType +{ + IRType* getResourceType() { return (IRType*)getOperand(0); } + IR_LEAF_ISA(DescriptorHandleType) +}; // Unlike the AST-level type system where `TypeType` tracks the // underlying type, the "type of types" in the IR is a simple diff --git a/source/slang/slang-lookup.cpp b/source/slang/slang-lookup.cpp index cd269b185..e4f2b188d 100644 --- a/source/slang/slang-lookup.cpp +++ b/source/slang/slang-lookup.cpp @@ -616,7 +616,8 @@ static void _lookUpMembersInSuperTypeImpl( request, ioResult, &derefBreacrumb); - return; + if (ioResult.isValid()) + return; } } diff --git a/source/slang/slang-mangle.cpp b/source/slang/slang-mangle.cpp index d955b7bd9..dedbb2d48 100644 --- a/source/slang/slang-mangle.cpp +++ b/source/slang/slang-mangle.cpp @@ -416,6 +416,19 @@ void emitQualifiedName(ManglingContext* context, DeclRef<Decl> declRef, bool inc return; } + if (auto genTypeParamDecl = as<GenericTypeParamDeclBase>(declRef.getDecl())) + { + emit(context, "GP"); + emit(context, genTypeParamDecl->parameterIndex); + return; + } + if (auto genValParamDecl = as<GenericValueParamDecl>(declRef.getDecl())) + { + emit(context, "GP"); + emit(context, genValParamDecl->parameterIndex); + return; + } + auto parentDeclRef = declRef.getParent(); if (as<FileDecl>(parentDeclRef)) parentDeclRef = parentDeclRef.getParent(); diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index 681999168..a0e8515eb 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -527,9 +527,7 @@ void initCommandOptions(CommandOptions& options) {OptionKind::EmitReflectionJSON, "-reflection-json", "reflection-json <path>", - "Emit reflection data in JSON format to a file."}, - }; - + "Emit reflection data in JSON format to a file."}}; _addOptions(makeConstArrayView(generalOpts), options); @@ -672,7 +670,10 @@ void initCommandOptions(CommandOptions& options) "-incomplete-library", nullptr, "Allow generating code from incomplete libraries with unresolved external functions"}, - }; + {OptionKind::BindlessSpaceIndex, + "-bindless-space-index", + "-bindless-space-index <index>", + "Specify the space index for the system defined global bindless resource array."}}; _addOptions(makeConstArrayView(targetOpts), options); @@ -2475,6 +2476,10 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv) // value in only allowing a single `-profile` option per target while still allowing // zero or more `-capability` options. + // Don't treat zero args as an error. + if (!m_reader.hasArg()) + break; + CommandLineArg operand; SLANG_RETURN_ON_FAIL(m_reader.expectArg(operand)); @@ -2899,6 +2904,13 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv) linkage->m_optionSet.add(OptionKind::DisableShortCircuit, true); break; } + case OptionKind::BindlessSpaceIndex: + { + Int index = 0; + SLANG_RETURN_ON_FAIL(_expectInt(arg, index)); + linkage->m_optionSet.add(OptionKind::BindlessSpaceIndex, (int)index); + break; + } default: { // Hmmm, we looked up and produced a valid enum, but it wasn't handled in the diff --git a/source/slang/slang-type-layout.cpp b/source/slang/slang-type-layout.cpp index 98a324d96..d7b303535 100644 --- a/source/slang/slang-type-layout.cpp +++ b/source/slang/slang-type-layout.cpp @@ -2523,7 +2523,7 @@ SourceLanguage getIntermediateSourceLanguageForTarget(TargetProgram* targetProgr bool areResourceTypesBindlessOnTarget(TargetRequest* targetReq) { - return isCPUTarget(targetReq) || isCUDATarget(targetReq); + return isCPUTarget(targetReq) || isCUDATarget(targetReq) || isMetalTarget(targetReq); } static bool isD3D11Target(TargetRequest*) @@ -4802,6 +4802,15 @@ static TypeLayoutResult _createTypeLayout(TypeLayoutContext& context, Type* type type, rules); } + else if (auto resPtrType = as<DescriptorHandleType>(type)) + { + if (areResourceTypesBindlessOnTarget(context.targetReq)) + return _createTypeLayout(context, resPtrType->getElementType()); + auto uint2Type = context.astBuilder->getVectorType( + context.astBuilder->getUIntType(), + context.astBuilder->getIntVal(context.astBuilder->getIntType(), 2)); + return _createTypeLayout(context, uint2Type); + } else if (auto optionalType = as<OptionalType>(type)) { // OptionalType should be laid out the same way as Tuple<T, bool>. diff --git a/tests/compute/nonuniformres-array-of-textures.slang b/tests/compute/nonuniformres-array-of-textures.slang index a7f3e05bd..6cab0131a 100644 --- a/tests/compute/nonuniformres-array-of-textures.slang +++ b/tests/compute/nonuniformres-array-of-textures.slang @@ -26,12 +26,12 @@ void main(uint2 pixelIndex : SV_DispatchThreadID) // CHECK2: %[[VAR5]] = OpAccessChain %_ptr_UniformConstant_{{.*}} %{{.*}} %[[VAR4]] // CHECK2: %[[VAR6]] = OpLoad %{{.*}} %[[VAR5]] - // CHECK3: OpDecorate %[[VAR1:[a-zA-Z0-9_]+]] NonUniform - // CHECK3: OpDecorate %[[VAR2:[a-zA-Z0-9_]+]] NonUniform - // CHECK3: %[[VAR1]] = OpLoad %{{.*}} - // CHECK3: %{{.*}} = OpImageFetch %v4float %[[VAR1]] %{{.*}} - // CHECK3: %[[VAR2]] = OpLoad %{{.*}} + // CHECK3: %[[VAR1:[a-zA-Z0-9_]+]] = OpAccessChain{{.*}}NonUniform + // CHECK3: %[[VAR2:[a-zA-Z0-9_]+]] = OpLoad %{{.*}}[[VAR1]]{{.*}}NonUniform // CHECK3: %{{.*}} = OpImageFetch %v4float %[[VAR2]] %{{.*}} + // CHECK3: %[[VAR3:[a-zA-Z0-9_]+]] = OpAccessChain{{.*}}NonUniform + // CHECK3: %[[VAR4:[a-zA-Z0-9_]+]] = OpLoad %{{.*}}[[VAR3]]{{.*}}NonUniform + // CHECK3: %{{.*}} = OpImageFetch %v4float %[[VAR4]] %{{.*}} float2 tmp0 = textures[NonUniformResourceIndex(pixelIndex.x)].Load(int3(0, 0, 0)); outputTexture[0] = tmp0; diff --git a/tests/compute/nonuniformres-nested-rwstructuredbuf.slang b/tests/compute/nonuniformres-nested-rwstructuredbuf.slang index b188a2c52..f935ce799 100644 --- a/tests/compute/nonuniformres-nested-rwstructuredbuf.slang +++ b/tests/compute/nonuniformres-nested-rwstructuredbuf.slang @@ -8,9 +8,9 @@ RWStructuredBuffer<int> buffer[]; [numthreads(8, 1, 1)] void main(uint3 dispatchThreadID: SV_DispatchThreadID) { - // CHECK0: buffer_{{.*}}[nonuniformEXT(nonuniformEXT(nonuniformEXT({{.*}})))] + // CHECK0: buffer_{{.*}}[nonuniformEXT({{.*}})] - // CHECK1: buffer_{{.*}}[NonUniformResourceIndex(NonUniformResourceIndex(NonUniformResourceIndex(_{{.*}})))] + // CHECK1: buffer_{{.*}}[NonUniformResourceIndex(_{{.*}})] // CHECK2-DAG: OpDecorate %[[VAR1:[a-zA-Z0-9_]+]] NonUniform // CHECK2-DAG: OpDecorate %[[VAR2:[a-zA-Z0-9_]+]] NonUniform @@ -21,11 +21,9 @@ void main(uint3 dispatchThreadID: SV_DispatchThreadID) // CHECK2: %[[VAR3]] = OpCopyObject %{{.*}} // CHECK2: %{{.*}} = OpBitcast %int %[[VAR3]] - // CHECK3-DAG: OpDecorate %[[VAR1:[a-zA-Z0-9_]+]] NonUniform - // CHECK3-DAG: OpDecorate %[[VAR2:[a-zA-Z0-9_]+]] NonUniform - // CHECK3: %[[VAR1]] = OpAccessChain %_ptr_StorageBuffer_RWStructuredBuffer %buffer %{{.*}} + // CHECK3: %[[VAR1:[a-zA-Z0-9_]+]] = OpAccessChain %_ptr_StorageBuffer_RWStructuredBuffer %buffer %{{.*}}NonUniform // CHECK3: %{{.*}} = OpAccessChain %_ptr_StorageBuffer_int %[[VAR1]] - // CHECK3: %[[VAR2]] = OpAccessChain %_ptr_StorageBuffer_RWStructuredBuffer %buffer %{{.*}} + // CHECK3: %[[VAR2:[a-zA-Z0-9_]+]] = OpAccessChain %_ptr_StorageBuffer_RWStructuredBuffer %buffer %{{.*}}NonUniform // CHECK3: %{{.*}} = OpAccessChain %_ptr_StorageBuffer_int %[[VAR2]] RWStructuredBuffer<int> buffer1 = buffer[NonUniformResourceIndex(NonUniformResourceIndex(NonUniformResourceIndex(dispatchThreadID.x)))]; buffer1[0] = 1; diff --git a/tests/language-feature/descriptor-handle/desc-handle-0.slang b/tests/language-feature/descriptor-handle/desc-handle-0.slang new file mode 100644 index 000000000..67f452cfc --- /dev/null +++ b/tests/language-feature/descriptor-handle/desc-handle-0.slang @@ -0,0 +1,22 @@ +//TEST:SIMPLE(filecheck=CUDA): -target cuda -entry computeMain -stage compute +//TEST:SIMPLE(filecheck=MTL): -target metallib -entry computeMain -profile cs_6_6 +//TEST:SIMPLE(filecheck=HLSL): -target hlsl -entry computeMain -profile cs_6_6 +//TEST:SIMPLE(filecheck=DXIL): -target dxil -entry computeMain -profile cs_6_6 +//TEST:SIMPLE(filecheck=SPV): -target spirv + +// SPV: OpImageSample +// HLSL: ResourceDescriptorHeap[{{.*}}] +// HLSL: SamplerDescriptorHeap[{{.*}}] +// HLSL: SampleLevel +// DXIL: computeMain +// CUDA: computeMain +// MTL: define void @computeMain + +uniform StructuredBuffer<DescriptorHandle<Sampler2D>> t; +uniform DescriptorHandle<RWStructuredBuffer<float4>> buffer; + +[numthreads(1,1,1)] +void computeMain() +{ + buffer[0] = t[0]->SampleLevel(float2(0.0), 0.0); +}
\ No newline at end of file diff --git a/tests/language-feature/descriptor-handle/desc-handle-1.slang b/tests/language-feature/descriptor-handle/desc-handle-1.slang new file mode 100644 index 000000000..0621f4a84 --- /dev/null +++ b/tests/language-feature/descriptor-handle/desc-handle-1.slang @@ -0,0 +1,32 @@ +//TEST:SIMPLE(filecheck=SPV): -target spirv + +// SPV: OpDecorate %resourceHeap DescriptorSet 10 +// SPV: OpAccessChain {{.*}} %resourceHeap +// SPV: OpImageSample + +uniform StructuredBuffer<DescriptorHandle<Sampler2D>> t; +uniform DescriptorHandle<RWStructuredBuffer<float4>> buffer; + +[vk::binding(0, 10)] +__DynamicResource<__DynamicResourceKind.General> resourceHeap[]; + +// A customized function that overrides the default behavior of fetch texture resources. +export T getDescriptorFromHandle<T:IOpaqueDescriptor>(DescriptorHandle<T> handleValue) +{ + __target_switch + { + case spirv: + if (T.kind != DescriptorKind.Buffer) + return resourceHeap[((uint2)handleValue).x].asOpaqueDescriptor<T>(); + else + return defaultGetDescriptorFromHandle(handleValue); + default: + return defaultGetDescriptorFromHandle(handleValue); + } +} + +[numthreads(1,1,1)] +void computeMain() +{ + buffer[0] = t[0]->SampleLevel(float2(0.0), 0.0); +}
\ No newline at end of file diff --git a/tests/language-feature/descriptor-handle/desc-handle-2.slang b/tests/language-feature/descriptor-handle/desc-handle-2.slang new file mode 100644 index 000000000..fa7f73b31 --- /dev/null +++ b/tests/language-feature/descriptor-handle/desc-handle-2.slang @@ -0,0 +1,14 @@ +//TEST:SIMPLE(filecheck=SPV): -target spirv -bindless-space-index 101 + +// SPV: OpDecorate %__slang_resource_heap{{.*}} Binding 0 +// SPV: OpDecorate %__slang_resource_heap{{.*}} DescriptorSet 101 +// SPV: OpImageSample + +uniform StructuredBuffer<DescriptorHandle<Sampler2D>> t; +uniform DescriptorHandle<RWStructuredBuffer<float4>> buffer; + +[numthreads(1,1,1)] +void computeMain() +{ + (*buffer)[0] = t[0].SampleLevel(float2(0.0), 0.0); +}
\ No newline at end of file diff --git a/tests/language-feature/descriptor-handle/desc-handle-3.slang b/tests/language-feature/descriptor-handle/desc-handle-3.slang new file mode 100644 index 000000000..5b9263026 --- /dev/null +++ b/tests/language-feature/descriptor-handle/desc-handle-3.slang @@ -0,0 +1,35 @@ +//TEST:SIMPLE(filecheck=MTL): -target metal +//TEST:SIMPLE(filecheck=MTLLIB): -target metallib +//TEST:SIMPLE(filecheck=CUDA): -target cuda -entry computeMain -stage compute +//TEST:SIMPLE(filecheck=PTX): -target ptx -entry computeMain -stage compute +//TEST:SIMPLE(filecheck=SPV): -target spirv -entry computeMain -stage compute -emit-spirv-via-glsl +//TEST:SIMPLE(filecheck=GLSL): -target glsl -entry computeMain -stage compute +//TEST:SIMPLE(filecheck=HLSL): -target hlsl -entry computeMain -profile cs_6_6 +//TEST:SIMPLE(filecheck=DXIL): -target dxil -entry computeMain -profile cs_6_6 +//TEST:SIMPLE(filecheck=SPV): -target spirv + +// SPV-DAG: %[[REG:[A-Za-z_0-9]+]] = OpAccessChain{{.*}}NonUniform +// SPV-DAG: %[[TEX:[A-Za-z_0-9]+]] = OpLoad {{.*}} %[[REG]] +// SPV-DAG: OpImageSampleExplicitLod {{.*}} %[[TEX]] + +// HLSL: ResourceDescriptorHeap[NonUniformResourceIndex +// HLSL: SamplerDescriptorHeap[NonUniformResourceIndex( + +// DXIL: define void @computeMain() + +// GLSL: _slang_resource_heap{{[0-9_]+}}[nonuniformEXT + +// MTL: void computeMain +// MTLLIB: @computeMain + +// CUDA: tex2DLod +// PTX: computeMain + +uniform StructuredBuffer<Sampler2D.Handle> t; +uniform RWStructuredBuffer<float4>.Handle buffer; + +[numthreads(4,1,1)] +void computeMain(int3 id : SV_DispatchThreadID) +{ + (*buffer)[0] = nonuniform(t[id.x]).SampleLevel(float2(0.0), 0.0); +}
\ No newline at end of file |
