Skip to content

Commit

Permalink
Reformat the buffer pointer proposal (microsoft#133)
Browse files Browse the repository at this point in the history
  • Loading branch information
s-perron authored Dec 4, 2023
1 parent 05d21c5 commit 7e3d166
Showing 1 changed file with 135 additions and 53 deletions.
188 changes: 135 additions & 53 deletions proposals/0010-vk-buffer-ref.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
<!-- {% raw %} -->

# 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
Expand All @@ -34,24 +46,38 @@ struct TestPushConstant_t
float4 MainPs(void) : SV_Target0
{
float4 vTest = vk::RawBufferLoad<float4>(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 <struct S, int align>
Expand All @@ -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<T, A> allows casting any vk::BufferPointer<SrcType, SrcAlign> to vk::BufferPointer<DstType, DstAlign> only if SrcType is a type derived from DstType. vk::reinterpret_pointer_cast<T, A> 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<T,A>(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<T, A> allows
casting any vk::BufferPointer<SrcType, SrcAlign> to
vk::BufferPointer<DstType, DstAlign> only if SrcType is a type derived from
DstType. vk::reinterpret_pointer_cast<T, A> 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<T,A>(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<T> 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<T> 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++

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<T,A>` 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<T,A>` 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.

```
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -410,7 +493,6 @@ float4 MainPs(void) : SV_Target0
### Appendix E: HLSL for Implicit Cast of Restrict to Aliased
```c++
struct Globals_s
Expand Down

0 comments on commit 7e3d166

Please sign in to comment.