diff --git a/README.adoc b/README.adoc index 7159490..72d6f4d 100644 --- a/README.adoc +++ b/README.adoc @@ -142,6 +142,8 @@ The Vulkan Guide content is also viewable from https://docs.vulkan.org/guide/lat === xref:{chapters}ways_to_provide_spirv.adoc[Ways to Provide SPIR-V] +=== xref:{chapters}buffer_array_length.adoc[Buffer Array Length (OpArrayLength)] + == xref:{chapters}robustness.adoc[Robustness] * `VK_EXT_image_robustness`, `VK_KHR_robustness2`, `VK_EXT_pipeline_robustness` diff --git a/antora/modules/ROOT/nav.adoc b/antora/modules/ROOT/nav.adoc index 00b878a..6fd940a 100644 --- a/antora/modules/ROOT/nav.adoc +++ b/antora/modules/ROOT/nav.adoc @@ -56,6 +56,7 @@ *** xref:{chapters}location_component_interface.adoc[] *** xref:{chapters}push_constants.adoc[] *** xref:{chapters}ways_to_provide_spirv.adoc[] +*** xref:{chapters}buffer_array_length.adoc[] ** xref:{chapters}robustness.adoc[] ** xref:{chapters}dynamic_state.adoc[] *** xref:{chapters}dynamic_state_map.adoc[] diff --git a/chapters/buffer_array_length.adoc b/chapters/buffer_array_length.adoc new file mode 100644 index 0000000..77e7152 --- /dev/null +++ b/chapters/buffer_array_length.adoc @@ -0,0 +1,115 @@ +// Copyright 2026 The Khronos Group, Inc. +// SPDX-License-Identifier: CC-BY-4.0 + +ifndef::chapters[:chapters:] +ifndef::images[:images: images/] + +[[buffer-array-length]] += Buffer Array Length (OpArrayLength) + +Sometimes when writing your shader you might not know the length of the array in your buffer. To solve this there is a `OpArrayLength` operation in SPIR-V that can be used to query the size at runtime. + +The following link:https://godbolt.org/z/jbqq65cK5[GLSL], +link:https://godbolt.org/z/7jKr5Pdqz[HLSL], and +link:https://godbolt.org/z/x7YW4rbsq[Slang] all demonstrate how you can get the length of a `VK_DESCRIPTOR_TYPE_STORAGE_BUFFER`. + +[source,glsl] +---- +layout(set = 0, binding = 0) buffer StorageBuffer { + uint header; + uint payload[]; // Run-time sized array +}; + +uint count = payload.length(); +---- + +== When is OpArrayLength allowed + +There are few restrictions to `OpArrayLength`. + +First, it must be used with a runtime array (`OpTypeRuntimeArray`). Shading languages can detect length of a static array already. + +Second, it must be in a struct, this link:https://godbolt.org/z/4h4bn6oW3[prevents you] from getting the length of a runtime descriptor array. + +**The short answer** is `OpArrayLength` is only allowed for a **Storage Buffer**, and it must be the **last element** of the struct. + +[NOTE] +==== +While `VK_EXT_shader_uniform_buffer_unsized_array` allows runtime arrays in a Uniform Buffer, `OpArrayLength` is banned on it. +==== + +== How is the length calculated + +The answer is really simple, whatever you bound! + +So imagine we have a single `VkBuffer` that is 1024 bytes and we use it for all 3 SSBO in your shader like + +[source,glsl] +---- +layout(binding = 0) buffer StorageBuffer_A { + uint a[]; +}; +layout(binding = 1) buffer StorageBuffer_B { + uint b[]; +}; +layout(binding = 2) buffer StorageBuffer_C { + uint c[]; +}; +---- + +and when we update our descriptors we go + +[source,c++] +---- +VkWriteDescriptorSet writes[3]; +writes[0].binding = 0; +writes[0].pBufferInfo->buffer = my_buffer; +writes[0].pBufferInfo->range = 64; // bytes bound + +writes[1].binding = 1; +writes[1].pBufferInfo->buffer = my_buffer; +writes[1].pBufferInfo->range = 257; // not a full uint + +writes[2].binding = 2; +writes[2].pBufferInfo->buffer = my_buffer; +writes[2].pBufferInfo->range = VK_WHOLE_SIZE; + +vkUpdateDescriptorSets(3, writes); +---- + +The shader code can now at runtime detect the length of the array. + +[source,glsl] +---- +a.length() == 64 / sizeof(uint); +b.length() == 256 / sizeof(uint); // The driver will round down as it is invalid to access a partially bound item +c.length() == 1024 / sizeof(uint); +---- + +== Using with Descriptor Buffers + +For those using xref:{chapters}descriptor_buffer.adoc[VK_EXT_descriptor_buffer] the size bound is not decided with `vkUpdateDescriptorSets`, but instead with `vkGetDescriptorEXT`. + +[source,c++] +---- +VkDescriptorGetInfoEXT info; +info.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; +info.data.pStorageBuffer->range = 256; // bytes bound +vkGetDescriptorEXT(info) +---- + +== Using with Descriptor Heaps + +For those using xref:{chapters}descriptor_heap.adoc[VK_EXT_descriptor_heap] the size bound is not decided with `vkUpdateDescriptorSets`, but instead with `vkWriteResourceDescriptorsEXT`. + +[source,c++] +---- +VkResourceDescriptorInfoEXT resources; +resources.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; +resources.data.pAddressRange->size = 256; // bytes bound +vkWriteResourceDescriptorsEXT(&resources) +---- + +When using `VK_EXT_descriptor_heap` it is also important to realize you are not allowed to use `OpArrayLength` with `VK_DESCRIPTOR_MAPPING_SOURCE_PUSH_ADDRESS_EXT`, `VK_DESCRIPTOR_MAPPING_SOURCE_SHADER_RECORD_ADDRESS_EXT`, or `VK_DESCRIPTOR_MAPPING_SOURCE_INDIRECT_ADDRESS_EXT` mappings. + +Something like `VK_DESCRIPTOR_MAPPING_SOURCE_INDIRECT_ADDRESS_EXT` allows you to basically use an inlined Storage Buffer. In these scenarios, because the range is now just a raw GPU address, the driver cannot reliably calculate the length of the resource.