diff --git a/proposals/0010-vk-buffer-ref.md b/proposals/0010-vk-buffer-ref.md index 89e1cd2b..cf1536b7 100644 --- a/proposals/0010-vk-buffer-ref.md +++ b/proposals/0010-vk-buffer-ref.md @@ -1,20 +1,32 @@ + # Buffer Pointers in HLSL With vk::BufferPointer -* Author(s): [Greg Fischer](https://github.com/greg-lunarg) -* Sponsor(s): [Chris Bieneman](https://github.com/llvm-beanz), [Steven Perron](https://github.com/s-perron), [Diego Novillo](https://github.com/dnovillo) -* Status: **Under Review** -* Planned Version: Retroactive addition to Vulkan 1.2 (requires SPIR-V 1.3. Some language details require HLSL 202x +* Author(s): [Greg Fischer](https://github.com/greg-lunarg) +* Sponsor(s): [Chris Bieneman](https://github.com/llvm-beanz), + [Steven Perron](https://github.com/s-perron), + [Diego Novillo](https://github.com/dnovillo) +* Status: **Under Review** +* Planned Version: Retroactive addition to Vulkan 1.2 (requires SPIR-V 1.3. + Some language details require HLSL 202x ## Introduction -This proposal seeks to improve tool support for Vulkan shaders doing buffer device addressing by adding the vk::BufferPointer type to HLSL. +This proposal seeks to improve tool support for Vulkan shaders doing buffer +device addressing by adding the vk::BufferPointer type to HLSL. ## Motivation -vk::RawBufferLoad() and vk::RawBufferStore are currently used to reference physical storage buffer space. Unfortunately, use of these functions has a number of shortcomings. One is that they generate low-level SPIR-V so that tools such as spirv-reflect, spirv-opt and renderdoc do not have the context to analyze and report on which members of a buffer are used in a logical manner. A bigger problem is that the HLSL programmer must compute the physical offsets of the members of a buffer which is error prone and difficult to maintain. +vk::RawBufferLoad() and vk::RawBufferStore are currently used to reference +physical storage buffer space. Unfortunately, use of these functions has a +number of shortcomings. One is that they generate low-level SPIR-V so that tools +such as spirv-reflect, spirv-opt and renderdoc do not have the context to +analyze and report on which members of a buffer are used in a logical manner. A +bigger problem is that the HLSL programmer must compute the physical offsets of +the members of a buffer which is error prone and difficult to maintain. -For example, here is a shader using vk::RawBufferLoad(). Note the physical offset 16 hard-coded into the shader: +For example, here is a shader using vk::RawBufferLoad(). Note the physical +offset 16 hard-coded into the shader: ```c++ // struct GlobalsTest_t @@ -34,24 +46,38 @@ struct TestPushConstant_t float4 MainPs(void) : SV_Target0 { float4 vTest = vk::RawBufferLoad(g_PushConstants.m_nBufferDeviceAddress + 16); - + return vTest; } ``` -The SPIR-V for this shader can be seen in Appendix A. Note the lack of logical context for the accessed buffer i.e. no declaration for the underlying structure GlobalsTest_t as is generated for other buffers. - -There is another way to use RawBufferLoad which does allow logical selection of the buffer fields, but it inefficiently loads the entire buffer to do it. See https://github.com/microsoft/DirectXShaderCompiler/issues/4986. - -The goal of this proposal is to have a solution that meets the following requirements: - -* Removes the need for having to manually or automatically generate offsets to load structured data with BufferDeviceAddress. -* Enables equivalent tooling functionality as is provided by the buffer reference feature in GLSL. Namely, tools like RenderDoc are able to introspect the type information such that its buffer inspection and shader debugger are able to properly understand and represent the type of the data. -* Make it possible through SPIR-V reflection to determine which members of a struct accessed by BufferDeviceAddress are statically referenced and at what offset. This is already possible for other data like cbuffers in order for shader tooling to be able to identify which elements are used and where to put them. +The SPIR-V for this shader can be seen in Appendix A. Note the lack of logical +context for the accessed buffer i.e. no declaration for the underlying structure +GlobalsTest_t as is generated for other buffers. + +There is another way to use RawBufferLoad which does allow logical selection of +the buffer fields, but it inefficiently loads the entire buffer to do it. See +https://github.com/microsoft/DirectXShaderCompiler/issues/4986. + +The goal of this proposal is to have a solution that meets the following +requirements: + +* Removes the need for having to manually or automatically generate offsets to + load structured data with BufferDeviceAddress. +* Enables equivalent tooling functionality as is provided by the buffer + reference feature in GLSL. Namely, tools like RenderDoc are able to + introspect the type information such that its buffer inspection and shader + debugger are able to properly understand and represent the type of the data. +* Make it possible through SPIR-V reflection to determine which members of a + struct accessed by BufferDeviceAddress are statically referenced and at what + offset. This is already possible for other data like cbuffers in order for + shader tooling to be able to identify which elements are used and where to + put them. ## Proposed solution -Our solution is to add a new builtin type in the vk namespace that is a pointer to a buffer of a given type: +Our solution is to add a new builtin type in the vk namespace that is a pointer +to a buffer of a given type: ```c++ template @@ -63,27 +89,48 @@ class vk::BufferPointer { } ``` -This class represents a pointer to a buffer of type struct `S`. `align` is the alignment in bytes of the pointer. If `align` is not specified, the alignment is assumed to be alignof(S). +This class represents a pointer to a buffer of type struct `S`. `align` is the +alignment in bytes of the pointer. If `align` is not specified, the alignment is +assumed to be alignof(S). This new type will have the following operations -* Copy assignment and copy construction - These copy the value of the pointer from one variable to another. -* Dereference Method - The Get() method represents the struct lvalue reference of the pointer to which it is applied. The selection . operator can be applied to the Get() to further select a member from the referenced struct. -* Two new cast operators are introduced. vk::static_pointer_cast allows casting any vk::BufferPointer to vk::BufferPointer only if SrcType is a type derived from DstType. vk::reinterpret_pointer_cast allows casting for all other BufferPointer types. For both casts, DstAlign <= SrcAlign must be true. -* A buffer pointer can be constructed from a uint64_t u using the constructor syntax vk::BufferPointer(u). -* A buffer pointer can be cast to a bool. If so, it returns FALSE if the pointer is null, TRUE otherwise. +* Copy assignment and copy construction - These copy the value of the pointer + from one variable to another. +* Dereference Method - The Get() method represents the struct lvalue reference + of the pointer to which it is applied. The selection . operator can be + applied to the Get() to further select a member from the referenced struct. +* Two new cast operators are introduced. vk::static_pointer_cast allows + casting any vk::BufferPointer to + vk::BufferPointer only if SrcType is a type derived from + DstType. vk::reinterpret_pointer_cast allows casting for all other + BufferPointer types. For both casts, DstAlign <= SrcAlign must be true. +* A buffer pointer can be constructed from a uint64_t u using the constructor + syntax vk::BufferPointer(u). +* A buffer pointer can be cast to a bool. If so, it returns FALSE if the + pointer is null, TRUE otherwise. Note the operations that are not allowed: -* There is no default construction. Every vk::BufferPointer is either contained in a global resource (like a cbuffer, ubo, or ssbo), or it must be constructed using the copy constructor. -* There is no explicit pointer arithmetic. All addressing is implicitly done using the `.` operator, or indexing an array in the struct T. -* The comparison operators == and != are not supported for buffer pointers. +* There is no default construction. Every vk::BufferPointer is either + contained in a global resource (like a cbuffer, ubo, or ssbo), or it must be + constructed using the copy constructor. +* There is no explicit pointer arithmetic. All addressing is implicitly done + using the `.` operator, or indexing an array in the struct T. +* The comparison operators == and != are not supported for buffer pointers. -Most of these restrictions are there for safety. They minimize the possibility of getting an invalid pointer. If the Get() method is used on a null or invalid pointer, the behaviour is undefined. +Most of these restrictions are there for safety. They minimize the possibility +of getting an invalid pointer. If the Get() method is used on a null or invalid +pointer, the behaviour is undefined. -When used as a member in a buffer, vk::BufferPointer can be used to pass physical buffer addresses into a shader, and address and access buffer space with logical addressing, which allows tools such as spirv-opt, spirv-reflect and renderdoc to be able to better work with these shaders. +When used as a member in a buffer, vk::BufferPointer can be used to pass +physical buffer addresses into a shader, and address and access buffer space +with logical addressing, which allows tools such as spirv-opt, spirv-reflect and +renderdoc to be able to better work with these shaders. -For example, here is a shader using vk::BufferPointer to do the same thing as the shader above using vk::RawBufferLoad. Note the natural, logical syntax of the reference: +For example, here is a shader using vk::BufferPointer to do the same thing as +the shader above using vk::RawBufferLoad. Note the natural, logical syntax of +the reference: ```c++ @@ -111,7 +158,11 @@ float4 MainPs(void) : SV_Target0 ``` -In SPIR-V, Globals_p would be a pointer to the physical buffer storage class. The struct type of the push constant would contain one of those pointers. The SPIR-V for this shader can be seen in Appendix B. Note the logical context of the declaration and addressing of underlying struct Globals_s including Offset decorations all Globals_s members. +In SPIR-V, Globals_p would be a pointer to the physical buffer storage class. +The struct type of the push constant would contain one of those pointers. The +SPIR-V for this shader can be seen in Appendix B. Note the logical context of +the declaration and addressing of underlying struct Globals_s including Offset +decorations all Globals_s members. ## Linked Lists and Local Variables @@ -146,69 +197,101 @@ float4 MainPs(void) : SV_Target0 ``` -Note also the ability to create local variables of type vk::BufferPointer such as g_p which can be read, written and dereferenced. +Note also the ability to create local variables of type vk::BufferPointer such +as g_p which can be read, written and dereferenced. ## Design Details ### Writing Buffer Pointer Pointees -The pointees of vk::BufferPointer objects can be written as well as read. See Appendix C for example HLSL. See Appendix D for the SPIR-V. +The pointees of vk::BufferPointer objects can be written as well as read. See +Appendix C for example HLSL. See Appendix D for the SPIR-V. ### Differences from C++ Pointers -vk::BufferPointer is different from a C++ pointer in that the method Get() can and must be applied to de-reference it. +vk::BufferPointer is different from a C++ pointer in that the method Get() can +and must be applied to de-reference it. ### Buffer Pointer Target Alignment -The target alignment `A` of `vk::BufferPointer` must be at least as large as the largest component type in the buffer pointer's pointee struct type `T` or the compiler may issue an error. +The target alignment `A` of `vk::BufferPointer` must be at least as large +as the largest component type in the buffer pointer's pointee struct type `T` or +the compiler may issue an error. ### Buffer Pointer Data Size and Alignment -For the purpose of laying out a buffer containing a vk::BufferPointer, the data size and alignment is that of a uint64_t. +For the purpose of laying out a buffer containing a vk::BufferPointer, the data +size and alignment is that of a uint64_t. ### Buffer Pointer Pointee Buffer Layout -The pointee of a vk::BufferPointer is considered to be a buffer and will be laid out as the user directs all buffers to be laid out through the dxc compiler. All layouts that are supported by dxc are supported for vk::BufferPointer pointee buffers. +The pointee of a vk::BufferPointer is considered to be a buffer and will be laid +out as the user directs all buffers to be laid out through the dxc compiler. All +layouts that are supported by dxc are supported for vk::BufferPointer pointee +buffers. ### Buffer Pointer Usage vk::BufferPointer cannot be used in Input and Output variables. -A vk::BufferPointer can otherwise be used whereever the HLSL spec does not otherwise disallow it through listing of allowed types. Specifically, buffer members, local and static variables, function argument and return types can be vk::BufferPointer. Ray tracing payloads and shader buffer table records may also contain vk::BufferPointer. +A vk::BufferPointer can otherwise be used whereever the HLSL spec does not +otherwise disallow it through listing of allowed types. Specifically, buffer +members, local and static variables, function argument and return types can be +vk::BufferPointer. Ray tracing payloads and shader buffer table records may also +contain vk::BufferPointer. ### Buffer Pointer and Semantic Annotations -Applying HLSL semantic annotations to objects of type vk::BufferPointer is disallowed. +Applying HLSL semantic annotations to objects of type vk::BufferPointer is +disallowed. ### Buffer Pointers and Aliasing -By default, buffer pointers are assumed to be restrict pointers as defined by the C99 standard. +By default, buffer pointers are assumed to be restrict pointers as defined by +the C99 standard. -An attribute vk::aliased_pointer can be attached to a variable, function parameter or a struct member of BufferPointer type. It is assumed that the pointee of a BufferPointer with this attribute can overlap with the pointee of any other BufferPointer with this attribute if they have the same pointee type and their scopes intersect. This also means that the pointee of a BufferPointer with this attribute does not overlap with the pointee of a default (restrict) BufferPointer. +An attribute vk::aliased_pointer can be attached to a variable, function +parameter or a struct member of BufferPointer type. It is assumed that the +pointee of a BufferPointer with this attribute can overlap with the pointee of +any other BufferPointer with this attribute if they have the same pointee type +and their scopes intersect. This also means that the pointee of a BufferPointer +with this attribute does not overlap with the pointee of a default (restrict) +BufferPointer. -The result of vk::static_pointer_cast and vk::reinterpret_pointer_cast as well as all constructors is restrict. +The result of vk::static_pointer_cast and vk::reinterpret_pointer_cast as well +as all constructors is restrict. -A pointer value can be assigned to a variable, function parameter or struct member entity, even if the aliasing disagrees. Such an assignment is an implicit cast of this property. +A pointer value can be assigned to a variable, function parameter or struct +member entity, even if the aliasing disagrees. Such an assignment is an implicit +cast of this property. See Appendix E for example of aliasing casting. ### Buffer Pointers and Address Space -All buffer pointers are presumed to point into the buffer device address space as defined by the Vulkan type VkDeviceAddress. See the following link for additional detail: https://registry.khronos.org/vulkan/specs/1.3-khr-extensions/html/vkspec.html#VkDeviceAddress. +All buffer pointers are presumed to point into the buffer device address space +as defined by the Vulkan type VkDeviceAddress. See the following link for +additional detail: +https://registry.khronos.org/vulkan/specs/1.3-khr-extensions/html/vkspec.html#VkDeviceAddress. ### Buffer Pointer Availability -The following can be used at pre-processor time to determine if the current compiler supports vk::BufferPointer: __has_feature(hlsl_vk_buffer_pointer). +The following can be used at pre-processor time to determine if the current +compiler supports vk::BufferPointer: __has_feature(hlsl_vk_buffer_pointer). ### Buffer Pointers and Type Punning Through Unions -While buffer pointer types are allowed in unions, type punning with buffer pointer types is disallowed as it is with all other types in HLSL. Specifically, when a member of a union is defined, all other members become undefined, no matter the types. +While buffer pointer types are allowed in unions, type punning with buffer +pointer types is disallowed as it is with all other types in HLSL. Specifically, +when a member of a union is defined, all other members become undefined, no +matter the types. ## SPIR-V Appendices ### Appendix A: SPIR-V for RawBufferLoad -Note the lack of logical context for the accessed buffer i.e. no declaration for the underlying structure GlobalsTest_t as is generated for other buffers. +Note the lack of logical context for the accessed buffer i.e. no declaration for +the underlying structure GlobalsTest_t as is generated for other buffers. ``` @@ -264,7 +347,9 @@ Note the lack of logical context for the accessed buffer i.e. no declaration for ### Appendix B: SPIR-V for vk::buffer_ref -Here is the SPIR-V for this shader. Note the logical context of the declaration and addressing of underlying struct Globals_s including Offset decorations all Globals_s members: +Here is the SPIR-V for this shader. Note the logical context of the declaration +and addressing of underlying struct Globals_s including Offset decorations all +Globals_s members: ``` OpCapability Shader @@ -321,7 +406,6 @@ Here is the SPIR-V for this shader. Note the logical context of the declaration ### Appendix C: HLSL for Write through vk::BufferPointer - ```c++ struct Globals_s @@ -351,15 +435,14 @@ float4 MainPs(void) : SV_Target0 ### Appendix D: SPIR-V for Write through vk::BufferPointer - ``` OpCapability Shader OpCapability PhysicalStorageBufferAddresses OpExtension "SPV_KHR_physical_storage_buffer" OpMemoryModel PhysicalStorageBuffer64 GLSL450 OpEntryPoint Fragment %MainPs "MainPs" %out_var_SV_Target0 %g_PushConstants - OpExecutionMode %MainPs OriginUpperLeft - OpSource HLSL 600 + OpExecutionMode %MainPs OriginUpperLeft + OpSource HLSL 600 OpName %type_PushConstant_TestPushConstant_t "type.PushConstant.TestPushConstant_t" OpMemberName %type_PushConstant_TestPushConstant_t 0 "m_nBufferDeviceAddress" OpName %Globals_s "Globals_s" @@ -410,7 +493,6 @@ float4 MainPs(void) : SV_Target0 ### Appendix E: HLSL for Implicit Cast of Restrict to Aliased - ```c++ struct Globals_s