Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: [SPIR-V] Add support for vk::buffer_ref attribute #5089

Closed
wants to merge 1 commit into from

Conversation

greg-lunarg
Copy link
Contributor

@greg-lunarg greg-lunarg commented Mar 8, 2023

vk::buffer_ref(X) when prepended to a struct typedef indicates the typedef is actually a reference to the struct, similar to C++ references, that is, syntactically treated like a struct, but implemented with a pointer with byte alignment X. vk::buffer_ref can alternately be prepended to member and local variable declarations of struct type with similar semantics.

@Keenuts @cassiebeckley This is a WIP as I still need to add tests, but the implementation is otherwise complete, so if you could start reviewing the code, that would be great.

I will presently be adding additional high-level description of the feature to this PR.

@greg-lunarg
Copy link
Contributor Author

This following text describes the new Buffer Reference feature in HLSL.

Motivation - GLSL Capability

The following is an example of the GLSL buffer reference feature:

#version 450
#extension GL_EXT_buffer_reference : require

layout(buffer_reference, std430, buffer_reference_align = 16) readonly buffer Globals
{
      vec4 g_vSomeConstantA;
      vec4 g_vTestFloat4;
      vec4 g_vSomeConstantB;
};

layout( push_constant ) uniform constants
{
      Globals g_globals;
} PushConstants;

layout (location = 0) out vec4 vOutColor;

void main()
{
      vOutColor = PushConstants.g_globals.g_vTestFloat4;
}

g_globals is not a struct, but a reference to a struct. This is similar to a C++ reference, where the object is actually a pointer, but the syntax to reference through it is as if it is an object of the pointee type. Each time a reference appears in a "." sequence, it becomes a load of its pointer which is then used as base for subsequent members.

Here is the SPIR-V for this example:

               OpCapability Shader
               OpCapability PhysicalStorageBufferAddresses
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel PhysicalStorageBuffer64 GLSL450
               OpEntryPoint Fragment %main "main" %vOutColor %PushConstants
               OpExecutionMode %main OriginUpperLeft
               OpSource GLSL 450
               OpSourceExtension "GL_EXT_buffer_reference"
               OpName %main "main"
               OpName %vOutColor "vOutColor"
               OpName %constants "constants"
               OpMemberName %constants 0 "g_globals"
               OpName %Globals "Globals"
               OpMemberName %Globals 0 "g_vSomeConstantA"
               OpMemberName %Globals 1 "g_vTestFloat4"
               OpMemberName %Globals 2 "g_vSomeConstantB"
               OpName %PushConstants "PushConstants"
               OpDecorate %vOutColor Location 0
               OpMemberDecorate %constants 0 Offset 0
               OpDecorate %constants Block
               OpMemberDecorate %Globals 0 NonWritable
               OpMemberDecorate %Globals 0 Offset 0
               OpMemberDecorate %Globals 1 NonWritable
               OpMemberDecorate %Globals 1 Offset 16
               OpMemberDecorate %Globals 2 NonWritable
               OpMemberDecorate %Globals 2 Offset 32
               OpDecorate %Globals Block
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
    %v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
  %vOutColor = OpVariable %_ptr_Output_v4float Output
               OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_Globals PhysicalStorageBuffer
  %constants = OpTypeStruct %_ptr_PhysicalStorageBuffer_Globals
    %Globals = OpTypeStruct %v4float %v4float %v4float
%_ptr_PhysicalStorageBuffer_Globals = OpTypePointer PhysicalStorageBuffer %Globals
%_ptr_PushConstant_constants = OpTypePointer PushConstant %constants
%PushConstants = OpVariable %_ptr_PushConstant_constants PushConstant
        %int = OpTypeInt 32 1
      %int_0 = OpConstant %int 0
%_ptr_PushConstant__ptr_PhysicalStorageBuffer_Globals = OpTypePointer PushConstant %_ptr_PhysicalStorageBuffer_Globals
      %int_1 = OpConstant %int 1
%_ptr_PhysicalStorageBuffer_v4float = OpTypePointer PhysicalStorageBuffer %v4float
       %main = OpFunction %void None %3
          %5 = OpLabel
         %18 = OpAccessChain %_ptr_PushConstant__ptr_PhysicalStorageBuffer_Globals %PushConstants %int_0
         %19 = OpLoad %_ptr_PhysicalStorageBuffer_Globals %18
         %22 = OpAccessChain %_ptr_PhysicalStorageBuffer_v4float %19 %int_1
         %23 = OpLoad %v4float %22 Aligned 16
               OpStore %vOutColor %23
               OpReturn
               OpFunctionEnd

Motivation - Previous HLSL Capability

The previous attempt to support a similar capability in HLSL was the offering of the function vk::RawBufferLoad. Here is an example of its use to implement the same functionality as the GLSL shader above:

// struct GlobalsTest_t
// {
//       float4 g_vSomeConstantA;
//       float4 g_vTestFloat4;
//       float4 g_vSomeConstantB;
// };

struct TestPushConstant_t
{
      uint64_t m_nBufferDeviceAddress;  // GlobalsTest_t
};

[[vk::push_constant]] TestPushConstant_t g_PushConstants;

float4 MainPs(void) : SV_Target0
{
      float4 vTest = vk::RawBufferLoad<float4>(g_PushConstants.m_nBufferDeviceAddress + 16);
      return vTest;
}

Here is the SPIR-V for this example:

               OpCapability Shader
               OpCapability Int64
               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
               OpName %type_PushConstant_TestPushConstant_t "type.PushConstant.TestPushConstant_t"
               OpMemberName %type_PushConstant_TestPushConstant_t 0 "m_nBufferDeviceAddress"
               OpName %g_PushConstants "g_PushConstants"
               OpName %out_var_SV_Target0 "out.var.SV_Target0"
               OpName %MainPs "MainPs"
               OpDecorate %out_var_SV_Target0 Location 0
               OpMemberDecorate %type_PushConstant_TestPushConstant_t 0 Offset 0
               OpDecorate %type_PushConstant_TestPushConstant_t Block
        %int = OpTypeInt 32 1
      %int_0 = OpConstant %int 0
      %ulong = OpTypeInt 64 0
   %ulong_16 = OpConstant %ulong 16
%type_PushConstant_TestPushConstant_t = OpTypeStruct %ulong
%_ptr_PushConstant_type_PushConstant_TestPushConstant_t = OpTypePointer PushConstant %type_PushConstant_TestPushConstant_t
      %float = OpTypeFloat 32
    %v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
       %void = OpTypeVoid
         %14 = OpTypeFunction %void
         %15 = OpTypeFunction %v4float
%_ptr_Function_v4float = OpTypePointer Function %v4float
%_ptr_PushConstant_ulong = OpTypePointer PushConstant %ulong
%_ptr_PhysicalStorageBuffer_v4float = OpTypePointer PhysicalStorageBuffer %v4float
%g_PushConstants = OpVariable %_ptr_PushConstant_type_PushConstant_TestPushConstant_t PushConstant
%out_var_SV_Target0 = OpVariable %_ptr_Output_v4float Output
     %MainPs = OpFunction %void None %14
         %19 = OpLabel
         %20 = OpVariable %_ptr_Function_v4float Function
         %21 = OpVariable %_ptr_Function_v4float Function
         %22 = OpAccessChain %_ptr_PushConstant_ulong %g_PushConstants %int_0
         %23 = OpLoad %ulong %22
         %24 = OpIAdd %ulong %23 %ulong_16
         %25 = OpBitcast %_ptr_PhysicalStorageBuffer_v4float %24
         %26 = OpLoad %v4float %25 Aligned 4
               OpStore %20 %26
               OpStore %21 %26
               OpStore %out_var_SV_Target0 %26

               OpReturn
               OpFunctionEnd

There are several problems with this low-level approach. One is that tools such as spirv-reflect do not have the context to report on which members of GlobalsTest_t are used and not used in a logical manner. A bigger problem is that the programmer must compute the physical offsets of the members of GlobalsTest_t which is error prone and difficult to maintain.

The New Functionality

A new attribute vk:BufferRef has been introduced into HLSL. Here is an example of how this new attribute can be used to implement the original GLSL shader:

[[vk::buffer_ref(16)]] typedef struct Globals_s
{
      float4 g_vSomeConstantA;
      float4 g_vTestFloat4;
      float4 g_vSomeConstantB;
} Globals_t;

struct TestPushConstant_t
{
      Globals_t m_nBufferDeviceAddress;
};

[[vk::push_constant]] TestPushConstant_t g_PushConstants;

float4 MainPs(void) : SV_Target0
{
      float4 vTest = g_PushConstants.m_nBufferDeviceAddress.g_vTestFloat4;
      return vTest;
}

The SPIR-V for this shader is virtually identical to that of the GLSL. It therefore avoids the problems of vk::RawBufferLoad.

Note that the shader source is also nearly identical to the GLSL. Just as in the GLSL, the reference is created by annotating the referenced struct. The buffer alignment (here 16) is also similarly expressed and can be used by the compiler to automatically account for it in the struct's SPIR-V offsets. The typedef is required here since dxc does not currently support applying attributes to types. However it does allow attributes to be attached to declarations such as typedefs.

Linked List Example

The spec for the GLSL extension GL_EXT_buffer_reference gives the following example of using buffer reference to create a linked list of identical buffers:

#version 450
#extension GL_EXT_buffer_reference : require

// forward declaration
layout(buffer_reference) buffer blockType;

// complete reference type definition
layout(buffer_reference, std430, buffer_reference_align = 16) buffer blockType {
    int x;
    blockType next;
};

// A normal block declaration that includes a reference to blockType
layout(std430) buffer rootBlock {
    blockType root;
} r;

void main()
{
    blockType b = r.root;
    // "pointer chasing" through a linked list
    b = b.next.next.next.next.next;
    // ...
    // use b.x;
}

vk::BufferRef can be used to program a similar shader in HLSL:

// Forward declaration
[[vk::buffer_ref(16)]] typedef struct block_s block_t;

struct block_s
{
      int x;
      block_t next;
};

struct TestPushConstant_t
{
      block_t root;
};

[[vk::push_constant]] TestPushConstant_t g_PushConstants;

float4 MainPs(void) : SV_Target0
{
      block_t g_t = g_PushConstants.root;
      g_t = g_t.next.next.next.next.next;
      // ...

      // use g_t.x
}

Notice the capability to create, set and use local variables of the reference type such as b in the GLSL example and g_t in the HLSL example.

An Additional vk::BufferRef Form

Currently dxc supports an additional, slightly different way to use vk::BufferRef. This is where the attribute is applied directly to struct members and local variables of struct type. Here is the first HLSL example using the alternate form:

typedef struct Globals_s
{
      float4 g_vSomeConstantA;
      float4 g_vTestFloat4;
      float4 g_vSomeConstantB;
} Globals_t;

struct TestPushConstant_t
{
      [[vk::buffer_ref(16)]] Globals_t m_nBufferDeviceAddress;

};

[[vk::push_constant]] TestPushConstant_t g_PushConstants;

float4 MainPs(void) : SV_Target0
{
      float4 vTest = g_PushConstants.m_nBufferDeviceAddress.g_vTestFloat4;
      return vTest;
}

And here is the linked list example:

// Forward declaration
typedef struct block_s block_t;

struct block_s
{
      int x;
      [[vk::buffer_ref(16)]] block_t next;

};

struct TestPushConstant_t
{
      [[vk::buffer_ref(16)]] block_t root;

};

[[vk::push_constant]] TestPushConstant_t g_PushConstants;

float4 MainPs(void) : SV_Target0
{
      [[vk::buffer_ref(16)]] block_t g_t = g_PushConstants.root;

      g_t = g_t.next.next.next.next.next;
      // ...

      // use g_t.x
}

The alternate form supports exactly the same functionality as that supported by the typedef form. The reason the alternate form is supported is that it is the first way that I implemented this feature. During that work I realized how to do the typedef form, which at the time seemed to me superior. The reason I haven't ripped out the alternate form is that I became unsure which form was most advantageous, so I left them both in, at least for the moment.

I don't think it is necessarily bad to have two ways to express this. Some may prefer one way, some the other. Even so, the way I have it coded at the moment, I could rip out either form by just editing a few lines.

@greg-lunarg
Copy link
Contributor Author

Acknowledgement: This feature has been designed, implemented and tested under review and assistance of @danginsburg

@AppVeyorBot
Copy link

@AppVeyorBot
Copy link

@godlikepanos
Copy link
Contributor

godlikepanos commented Mar 9, 2023

There are several problems with this low-level approach. One is that tools such as spirv-reflect do not have the context to report on which members of GlobalsTest_t are used and not used in a logical manner. A bigger problem is that the programmer must compute the physical offsets of the members of GlobalsTest_t which is error prone and difficult to maintain.

Hi Greg. The 1st argument I can understand but for the 2nd I'm pretty sure that RawBufferLoad can accept a struct directly. The following compiles:

struct Foo
{
    float4 a;
    float4 b;
};

float4 PSMain(uint64_t addr : ADD) : SV_TARGET
{
	return vk::RawBufferLoad<Foo>(addr).b;
}

IMHO RawBufferLoad (with the addition of alignment RawBufferLoad<typename Type, uint Alignment = 4>) is a cleaner approach. You just play with 64bit addresses and that's it.

Buffer reference introduces complexity because you have to cast to and from uint64_t in order to calculate addresses of complex data. At the same time types between HLSL and C++ will not look the same. C++ types will have a uint64_t (?) and HLSL will have a buffer ref.

At the same time what is the underlying type of the buffer ref? Is it uint64_t or uint2? Packing rules are different depending on the type.

@danginsburg
Copy link
Contributor

RawBufferLoad can accept a struct directly

Please see #4986 and #4289 for some discussion of the issues with why that doesn't currently work.

Also, in case it wasn't clear from the original description, a goal here beyond spirv-reflect was about introspection and debugability in RenderDoc. If you have a GLSL shader using buffer_reference RenderDoc is able to perform introspection on the type to provide the ability to see the data in the buffer viewer and to be able to get the data type in the shader debugger. Part of the goal of this change from a usability point-of-view was to enable that same ability from shaders generated with HLSL via DXC.

@greg-lunarg
Copy link
Contributor Author

There are several problems with this low-level approach.

One other possible advantage of vk::buffer_ref is its syntactic similarity to the GLSL functionality, which aids in understanding and porting to the HLSL.

@greg-lunarg
Copy link
Contributor Author

Buffer reference introduces complexity because you have to cast to and from uint64_t in order to calculate addresses of complex data. At the same time types between HLSL and C++ will not look the same. C++ types will have a uint64_t (?) and HLSL will have a buffer ref.

At the same time what is the underlying type of the buffer ref? Is it uint64_t or uint2? Packing rules are different depending on the type.

@godlikepanos The SPIR-V for both approaches is above. To my eyes, it is the RawBufferLoad approach that is doing the BitCasting with uint64_t or whatever. You can see in the SPIR-V that the vk::buffer_ref approach is able to keep to a strictly logical addressing scheme, using pointers to types. The underlying type is whatever is specified by the API.

Another apparent advantage to vk::buffer_ref is that "." sequences with multiple/linked/nested references are able to stick with the original syntax, aiding in compactness and readability.

@AppVeyorBot
Copy link

@godlikepanos
Copy link
Contributor

Thanks for the explanation to both. It sounds good. I guess you will also allow casting from uint64_t to a buffer ref and the opposite. Is that correct?

vk::buffer_ref(X) when prepended to a struct typedef indicates the
typedef is actually a reference to the struct, similar to C++
references, that is, syntactically treated like a struct, but
implemented with a pointer with byte alignment X. vk::buffer_ref can
alternately be prepended to member and local variable declarations of
struct type creating similar semantics.
@AppVeyorBot
Copy link

@greg-lunarg
Copy link
Contributor Author

greg-lunarg commented Mar 10, 2023

I guess you will also allow casting from uint64_t to a buffer ref and the opposite. Is that correct?

I don't think there is a way to do the casting within a shader. Certainly with Vulkan a uint64_t address in a buffer can be used as a buffer reference by a shader which declares the buffer with a buffer reference aliasing the uint64_t.

@Keenuts
Copy link
Collaborator

Keenuts commented Mar 10, 2023

Hi Greg!

I can see the benefit of having this kind of feature vs the vk::RawBufferLoad.
Just unsure about the first syntax suggested (which is supposed to match GLSL).

[[vk::buffer_ref(16)]] typedef struct Globals_s
{
      float4 g_vSomeConstantA;
      float4 g_vTestFloat4;
      float4 g_vSomeConstantB;
} Globals_t;

This looks weird to me, as the attribute seems to apply to the whole struct, a bit like __attribute__((packed)), and not just to the Globals_t typedef.

I would be in favor of your second variant, when the field/variable gets this reference attribute.

struct MyStruct
{
      [[vk::buffer_ref(16)]] MyStruct next;
};

Now this means declaring a variable means typing [[vk::buffer_ref(16)]] again. Thing is the Typedef feels awkard. Maybe something like:

typedef MyStruct MyStructValue, [[vk::buffer_ref(16)]] MyStructRef;

Or the attribute after the type, like clang attributes

typedef unsigned *aligned_1k __attribute__((align_value(1024))), *aligned_16 __attribute__((align_value(16)));

(Would be cool to have the using keyword..)

@greg-lunarg
Copy link
Contributor Author

Hey Nathan! Thanks for looking at this.

Yes, the syntax for the typedef form can be visually a little awkward. A couple things about that:

  1. One of my goals of this project was to change the non-SPIRV parts of the compiler as little as possible. One reason was I wasn't sure how much would be tolerated by Microsoft. Another was to avoid inadvertent breakage in unrelated areas or to increase the scope of the work I would have to do. Given this, I was restricted to where attributes could appear. For typedef, it could only appear before the typedef keyword. Appearance anywhere else causes syntax errors.

  2. Given these restrictions, I think the behavior is still fairly intuitive and alternate approaches can be used to reduce ambiguity.

For example, from the first example:

[[vk::buffer_ref(16)]] typedef struct Globals_s
{
      float4 g_vSomeConstantA;
      float4 g_vTestFloat4;
      float4 g_vSomeConstantB;
} Globals_t;

The attribute only modifies the typedef and Globals_t; Globals_s is not affected by the attribute and can continue to be used in the remainder of the program without any buffer_ref semantics affecting it. Visually this might not be intuitive, but I think logically this is intuitive as well as desirable.

If the user is concerned about the appearance that Globals_s is affected by the attribute, they can use this syntax instead:

struct Globals_s
{
      float4 g_vSomeConstantA;
      float4 g_vTestFloat4;
      float4 g_vSomeConstantB;
};

[[vk::buffer_ref(16)]] typedef Globals_s Globals_t;

Visually, it seems fairly intuitive here that Globals_s is not affected by the attribute.

@Keenuts
Copy link
Collaborator

Keenuts commented Mar 13, 2023

Hey Nathan! Thanks for looking at this.

  1. One of my goals of this project was to change the non-SPIRV parts of the compiler as little as possible.

I'm not sure, as I haven't tried, but is it that disruptive to the codebase to add a compiler attribute?
(maybe vk:: stuff is not handled as one?)

If the user is concerned about the appearance that Globals_s is affected by the attribute, they can use this syntax instead:

Sure, but if the language can enforce something readable, that's better 😊

Given:

struct A;

struct A
{
      int                             int_value;
      [[vk::buffer_ref(16)]] struct A ptr_to_a;
      [[vk::buffer_ref(16)]] int      ptr_to_int;
};

struct TestPushConstant_t
{
      [[vk::buffer_ref(16)]] struct A root;

};

[[vk::push_constant]] TestPushConstant_t push_constants;

Played around with the current implementation (I know it's a "draft" and not robust yet), but what about those case:

[numthreads(1,1,1)]
void main() {
  // ptr_to_a is a ref to a struct A.
  [[vk::buffer_ref(16)]] struct A ptr_to_a = push_constants.root.ptr_to_a;
  // ptr_to_int is a ref to an int? And like in C++, the pointer would be deref of moved depending on the result type?
  [[vk::buffer_ref(16)]] int ptr_to_int = push_constants.root.ptr_to_int;
  int int_value = push_constants.root.ptr_to_int;
}
void foo([[vk::buffer_ref(16)]] struct A ref) { }
[numthreads(1,1,1)]
void main() {
   [[vk::buffer_ref(16)]] struct A ptr_to_a = push_constants.root.ptr_to_a;
   foo(ptr_to_a);
}

Shall we handle this exactly as a C++ ref?

Btw, this compiles (just fails validation because vk::buffer_ref doesn't like ints:

[[vk::buffer_ref(16)]] int ptr_to_int = push_constants.root.ptr_to_int;
ptr_to_int = ptr_to_int + 1;

Generated SPIR-V here don't seem correct:
C++ -> load value pointed by ptr_to_a, add 1 to register, store result at address pointer by ptr_to_a.
SPIR-V -> load value pointed by ptr_to_a, add 1 to register, store result in ptr_to_a (missing deref)

Same for this (non-aligned store)

[[vk::buffer_ref(16)]] struct A ptr_to_a = push_constants.root.ptr_to_a;
ptr_to_a.int_value = ptr_to_a.int_value + 1;

And this blows up my stack (cycle in IsHLSLCopyableAnnotatableRecord/IsHLSLNumericOrAggregateOfNumericType)

[[vk::buffer_ref(16)]] struct A ptr_to_a = push_constants.root.ptr_to_int;
ptr_to_a.int_value = ptr_to_a.int_value + 1;

Same for this (clang::spirv::SpirvEmitter::reconstructValue/

[[vk::buffer_ref(16)]] struct A get_at_depth(int depth) {
  return push_constants.root.ptr_to_a;
}

[numthreads(1,1,1)]
void main() {
  get_at_depth(10);
}

(Overall, a lot of cases causes infinite loops around the codebase, as we don't expect those)

@llvm-beanz
Copy link
Collaborator

I think this isn't the right venue for this discussion. This change has significant language implications, and we should probably discuss this as a more core language change rather than just an attribute. We have a process for designing and developing language changes here.

One big thought in my mind is should this be an attribute, or should this be an actual reference with appropriate address space annotations.

@devshgraphicsprogramming
Copy link

devshgraphicsprogramming commented Mar 24, 2023

RawBufferLoad can accept a struct directly

Please see #4986 and #4289 for some discussion of the issues with why that doesn't currently work.

Also, in case it wasn't clear from the original description, a goal here beyond spirv-reflect was about introspection and debugability in RenderDoc. If you have a GLSL shader using buffer_reference RenderDoc is able to perform introspection on the type to provide the ability to see the data in the buffer viewer and to be able to get the data type in the shader debugger. Part of the goal of this change from a usability point-of-view was to enable that same ability from shaders generated with HLSL via DXC.

This probably also fixes #4983, #4924 #4620 and maybe even #5095.

By the way, current vk::RawBufferLoad requires you to tediously manually write out all the pointer chasing when working with a linked list.

P.S. my two cents, I'd probably want to use this as

using ptr_to_A = [[vk::buffer_ref(16)]] A;

or something similar.

We have a process for designing and developing language changes here.
One big thought in my mind is should this be an attribute, or should this be an actual reference with appropriate address space annotations.

As @ahmadi-erfan might say "We need this today, not in a few months/years".

If keeping this around is an issue I suggest making a forward-compatibility macro

// will change to something like `using REFERENCE_ALIAS = TYPE& global` when HLSL introduces actual pointers and pointer scopes
#define HLSL_DECLARE_REF_TYPE(REFERENCE_ALIAS ,TYPE) [[vk::buffer_ref(alignof(TYPE))]] typedef TYPE REFERENCE_ALIAS

P.S. Made an edit after realizing GLSL BDA is 99% a C++ reference and not a pointer despite the casting with uint64_t.

@devshgraphicsprogramming
Copy link

devshgraphicsprogramming commented Mar 24, 2023

@greg-lunarg a serious question, given this

#version 450
#extension GL_EXT_buffer_reference : require

layout(buffer_reference, std430, buffer_reference_align = 16) readonly buffer Globals
{
      vec4 g_vSomeConstantA;
      vec4 g_vTestFloat4;
      vec4 g_vSomeConstantB;
};

layout( set=0, binding=0) buffer
{
      Globals g_globals;
} SSBO;

How does one resolve the ambiguity between

SSBO.g_globals = 0xdeadbeefBADC0FFEull;

and

SSBO.g_globals = {vec4(1.f),vec4(2.f),vec4(3.f)};

i.e. changing the pointer vs changing the pointee?

Is it transparent like a reference, and I need another "view" where the buffer reference variable is a uint64_t onto whatever is holding it to overwrite the actual pointer?

EDIT: Answered my own question, its a reference not a pointer, being passed around by value, so it can't be changed without a second view.

@devshgraphicsprogramming
Copy link

devshgraphicsprogramming commented Mar 24, 2023

Ok reading https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GLSL_EXT_buffer_reference.txt it would seem that this is more of a *std::assume_aligned<N,T> (a T&) than a std::assume_aligned<N,T> (a T*)

Its super weird that GLSL does syntax abuse that lets you do uint64_t(ref) and ref_t(addr), but these are constructors (copies of the variable after casting) so I still have no clue how I'd modify a ref_t's stored address with this approach?

I guess I really do need a "uint64_t" view onto the place where the ref_t's actual numerical address is stored.

hmm, in C++ I wouldn't be able to change the reference either, because & is transparent so I'd get the address of the referenced variable and not the reference itself. So I guess it checks out.

@Ipotrick
Copy link

This change would be great. Currently RWStructuredBuffer arrays are not supported in hlsl. Thismakes bindless buffers in hlsl with vulkan very painfull as the only good option is to make all buffers ByteAddressBuffers.

@llvm-beanz
Copy link
Collaborator

As @ahmadi-erfan might say "We need this today, not in a few months/years".

We are not rushing fundamental language changes.

Putting in changes that have bypassed language design review has caused an enormous amount of technical debt in HLSL and DXC. Nobody is going to die if we put this through a proper process so that this is designed, reviewed and implemented to our quality standards.

@BeastLe9enD
Copy link

I implemented a vk::BufferPtr<T> which is a structure that holds an uint64_t and has an T at(uint64_t idx) function to load data from the raw buffer. I think something in this direction would be a better fit for HLSL imo. I don't really like the glsl syntax with theese buffer references.

@llvm-beanz
Copy link
Collaborator

I implemented a vk::BufferPtr<T> which is a structure that holds an uint64_t and has an T at(uint64_t idx) function to load data from the raw buffer. I think something in this direction would be a better fit for HLSL imo. I don't really like the glsl syntax with theese buffer references.

This.

We want Vulkan features added to HLSL to be HLSL features, not just ports of GLSL. This is a factor in why we added the requirment that all language changes go through our design and review process.

Language features are also incredibly difficult to change once introduced, and they cause significant disruption when we do change them. As a result, we need to be extra careful about what we introduce.

We very much understand the utility and desire for this feature. There is no question that we want to add something to the language that addresses this use case. Whatever we add needs to be consistent with the design philosophy of HLSL.

@s-perron
Copy link
Collaborator

I implemented a vk::BufferPtr<T> which is a structure that holds an uint64_t and has an T at(uint64_t idx) function to load data from the raw buffer. I think something in this direction would be a better fit for HLSL imo. I don't really like the glsl syntax with theese buffer references.

I had suggested the exact same thing in private conversations with LunarG.

@s-perron
Copy link
Collaborator

We are working on a proposal that will be brought to the hlsl specs.

@greg-lunarg
Copy link
Contributor Author

Sorry everyone for going dark. As @s-perron said, we have been working on a doc to submit to https://github.com/microsoft/hlsl-specs/. We will cross reference on this PR. We will answer all of your questions in that space.

We never meant to go around a language review. We thought it would happen here. We were not aware of the process.

Talk to you all soon on the other side :)

@greg-lunarg
Copy link
Contributor Author

I have submitted a PR proposing an HLSL language change at microsoft/hlsl-specs#37.

At the insistence of MS and Google, the feature has changed from an attribute to a first class pointer type. We have also added the capability for function args and returns to take this type. We have also added the ability to convert a buffer pointer to a uint64_t to compare with zero for linked list traversal.

@litterbug23
Copy link

litterbug23 commented Apr 7, 2023

Vk::RawBufferLoad/RawBufferStore cannot generate compatible code
[vk::buffer_ref attribute] It's a better solution

MultiIndirectDraw , vulkan & DX12 There will be a common implementation method
IndirectBuffer
{
IndirectArgs args;
int64 pushConstant;
}


[[vk::buffer_ref(16)]] typedef struct matbuffer_t
{
      float4 diffuseColor;
      float4 emssiveColor;     
} matbuffer_s;


[[vk::push_constant]] TestPushConstant_t 
{
matbuffer_s  material;
}

float4 MainPs(void) : SV_Target0
{
      float4 vTest = material.diffuseColor;
      return vTest;
}

@natevm
Copy link
Contributor

natevm commented Apr 7, 2023

One thing I don’t understand with this proposed syntax is how pointers to pointers would work…

like, if I have a buffer of addresses (maybe a list of emissive triangle meshes I want to sample in a path tracer), how would that work here?

I can do

buffer2 = vk::RawBufferLoad<uint64_t>(buffer1, idx1);
data = vk::RawBufferLoad(buffer2, idx2);

But with attributes I have no idea how I’d accomplish that.

Also, I agree I tend to dislike pulling GLSL syntax into HLSL. The proposed attribute API seems very un-CUDA-like, which I think is the opposite direction we should be going. However, the proposed API in microsoft/hlsl-specs#37 is much nicer IMO, and I’m much more on board with a first-class pointer type.

@llvm-beanz
Copy link
Collaborator

Since we're iterating on the design for this change over here, and this change as designed and implemented is not going to be accepted I'm going to close this PR.

Please direct further feedback to hlsl-specs on the current review or by filing an issue.

@danielmaciel
Copy link

Hey have you considered using just the pointer syntax? That's what those are, right? Pointers? Just use the uint64_t, but type them instead of a raw uint64_t value (which btw is akin to using a void* and then casting it to whatever is the real type?)

@llvm-beanz
Copy link
Collaborator

@danielmaciel, the proposal for this is evolving over on the specification.

Introducing pointer syntax is a significantly more complicated and invasive change to the language, and requires a new language version.

The proposal as we're evolving it doesn't require changing the language, instead it is a backwards-compatible feature that can be exposed in older language modes since it has no new grammatical elements. This has the benefit of being able to allow users to adopt this feature without forcing them to update their code to a new language version (or making them wait until the new language version is ready).

We are also working to design it in a way that will not introduce complicated syntax that would be removed when HLSL does gain pointers in the future, so that we have a good transition path from this interface to pointers when they become available.

@BeastLe9enD
Copy link

Is there a plan for when exactly the feature should be implemented? It's quite frustrating to still not be able to use the buffer pointers decently with HLSL, and GLSL is no real alternative.

@danginsburg
Copy link
Contributor

Is there a plan for when exactly the feature should be implemented?

It's been mostly implemented in slang (shader-slang/slang#3814). I'm not sure what the plan is for implementing it in DXC, but Valve/LunarG are not planning to implement it. I've moved our engine over to using slang.

@BeastLe9enD
Copy link

BeastLe9enD commented Apr 2, 2024

Valve/LunarG are not planning to implement it. I've moved our engine over to using slang.

Good point, I also move my stuff over to GLSL or slang if this is the case, HLSL just feels slow in adopting spirv support in my opinion.

@Keenuts
Copy link
Collaborator

Keenuts commented Apr 3, 2024

Hi! Owners on the SPIR-V side are either on leave, retired, or not working on compilers anymore. The proposal has been accepted, so seems like we should be able to add this to HLSL/DXC.
I've created an issue to track this. Now we need to find somebody who has time on our end.
Thanks for the reminder! 😊

(#6489)

@natevm
Copy link
Contributor

natevm commented Apr 3, 2024

The engineering time is the main bottleneck IIUC yep.

Slang has a decent number of engineers working hard on the SPIR-V backend. For anyone looking to use slang and SPIR-V, I recommend using the —emit-spirv-directly flag, since that enables more “hot off the press” features like buffer pointers.

For more info on buffer pointers themselves, here's the current docs: https://shader-slang.com/slang/user-guide/convenience-features.html#pointers

As well as the current test we're using, which has some more examples: https://github.com/shader-slang/slang/blob/master/tests/spirv/pointer.slang

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.