Specialization constant for input_attachment_index

Hello community,

Is there a specific reason not to introduce input_attachment_index_id for input_attachment_index just as there is local_size_x_id etc for compute shaders local sizes?

The reason is I am writing a VK backend for an engine, which tries to merge what the engine exposes as a graphics pass into what vulkan knows as renderpass with subpasses and when the algorithm finds that it can merge many passes, sometimes there are multiple resources part of that single renderpass that in the shaders both are referred to by the same id. If I could just set the input_attachment_index via a specialization constant, that would be handy.

Currently when i create materials preruntime which use InputAttachments my generator produces 4 versions, 1 for each of sampler2D / sampler2DMS / subpassInput and subpassInputMS and for the subpassInput(MS) versions analyses the spirv for the offset of the InputAttachmentIndex so at runtime I can pick the appropriate spirv (depending on whether it was merged or not I pick sampler2D or subpassInput version and in case the resource is multisampled I use the MS versions thereof), then modify the spirv at the offset found preruntime to the index that is needed at runtime.
It works but with an input_attachment_index_id I could get rid of modifying the spirv at runtime.

Since it works one can argue that it is not VKs job to expose such functionality, but then is there a reason to expose local_size_x_id etc when I could just modify the spirv at runtime in that case, too?
I assume there is an advantage in having only a single module and producing different versions using specialization constants versus modifying the spirv at runtime and producing multiple modules?

Thanks

Hm, wouldn’t it work to index the input attachment array by specialization constant?

1 Like

Thanks for the reply.
I didn’t find anything in the SPIRV spec regarding InputAttachmentIndex and specialization constants so I just tried:

#version 450
layout (constant_id = 0) const int IAIDX = 0;
layout(input_attachment_index = IAIDX, set = 0, binding = 0) uniform subpassInput g_Scene;
...

Which results in:

main:2: error: 'input_attachment_index' : needs a literal integer

Hence my question, but if I understand you correctly I can instead use:

#version 450
layout (constant_id = 0) const int IA_IDX = 0;
layout (constant_id = 1) const int MAX_IAS = 1;
layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput[MAX_IAS] g_Scenes;
...

and it increments the input_attachment_index for each element in the array? So if I adjusted the snippet to read input_attachment_index = 1 specifying MAX_IAS to 2 and IA_IDX to 0 or 1 refer to the InputAttachments at indices 1 or 2 respectively? Just to make sure I understand, because I failed to find that in the spec and that would indeed solve the problem.

That was my first thought when I read this question earlier. But I also realized that it didn’t make sense why input_attachment_index_id would need to even exist if you could just use a user-defined specialization constant.

So I found this: constant expressions in a layout block are explicitly forbidden from being specialization constants:

integer-constant-expression is defined in “Constant Expressions” as constant integral expression, with it being a compile-time error for integer-constant-expression to be a specialization constant.

Hence my question, but if I understand you correctly I can instead use:

Yes, that’s the idea.

I don’t know if you can use it and haven’t tested it.

I failed to find that in the spec and that would indeed solve the problem.

As I recall, there’s a part about input attachment index assignment, and so the array declaration should work that way.

I haven’t used much of specialization constants. I assume one can use them to index certain arrays. Not sure if it will allow you to declare that array size, but 4 seems to be always allowed I think.

Yes, you are right. Not sure how I missed it but it clearly states

If the subpass input variable is declared as an array of size N, it consumes N consecutive input attachments, starting with the index specified.

Vulkan® 1.2.211 - A Specification 3rd paragraph, 2nd sentence

Problem solved, thank you both for idea and references.

I just realized that this requires the DescriptorIndexing feature, due to

shaderInputAttachmentArrayDynamicIndexing indicates whether arrays of input attachments can be indexed by dynamically uniform integer expressions in shader code. If this feature is not enabled, resources with a descriptor type of VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT must be indexed only by constant integral expressions when aggregated into arrays in shader code. This also indicates whether shader modules can declare the InputAttachmentArrayDynamicIndexing capability.

(emphasis mine)
and the aforementioned

integer-constant-expression is defined in “Constant Expressions ” as constant integral expression , with it being a compile-time error for integer-constant-expression to be a specialization constant.

Not neccessarily a problem on desktop, but more on mobile
And thus it is unfortunately not a full replacement for the original way of modifying the SPIRV at runtime
In case there are any other thoughts or ideas, i would love to hear them

Doesn’t your link say that specialization constants are constant integral expressions?

first I thought so too, which is why I implemented it
once I got to testing it i was confused that upon usage at runtime the validation layers complain

The SPIR-V Capability (InputAttachmentArrayDynamicIndexing) was declared, but none of the requirements were met to use it. The Vulkan spec states: If pCode declares any of the capabilities listed in the SPIR-V Environment appendix, one of the corresponding requirements must be satisfied

which lead me to the quote from my previous post regarding shaderInputAttachmentArrayDynamicIndexing which then lead me back to the quote and link from Alfonse_Reinheart.

But I probably misunderstood the integer-constant-expression definition and it instead seems to be a subtype of constant integral expressions, with the only difference that you cannot use SpecializationConstants, meaning for constant integral expressions this part of the specification should matter

  • A variable declared with the const qualifier and an initializer, where the initializer is a constant expression. This includes both const declared with a specialization-constant layout qualifier, e.g. layout(constant_id = …​), and those declared without a specialization-constant layout qualifier.

(emphasis mine)
So that means it is an error in the validation layers?

I am still confused because when I set the VkInstance and VkDevice versions to 1.0 (from 1.3, assuming 1.0 without extensions would not know about DescriptorIndexing) I got the same validation error

To make sure, the relevant part of the shader look like

#version 450
layout (constant_id = 0) const int SCENE_IA_IDX = 0;
layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput[4] g_InputAttachments;
void main() {
    vec4 col = subpassLoad(g_InputAttachments[SCENE_IA_IDX]);

Could be a false positive. Why not ask at at Layers repo?

SPIR-V spec specifically mentions that specialization constants are useful for sizing arrays:

Specialization enables offline creation of a portable SPIR-V module based on constant values that won’t be known until a later point in time. For example, to size a fixed array with a constant not known during creation of a module, but known when the module will be lowered to the target architecture.

One would assume indexing is less constrained than sizing.