Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
1 change: 1 addition & 0 deletions antora/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down
115 changes: 115 additions & 0 deletions chapters/buffer_array_length.adoc
Original file line number Diff line number Diff line change
@@ -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.