KhronosGroup / KhronosGroup/glslang
Reflection API generates incorrect arrayStride and offset for structs in uniform block (Vulkan)
- Dominant language
- C++
- Stars
- 3.6k
- Forks
- 989
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 31
Description
Here is a complete program to reproduce the issue:
```
#include
#include
#include
#include "glslang/Public/ShaderLang.h"
#include "glslang/SPIRV/GlslangToSpv.h"
#include "glslang/SPIRV/disassemble.h"
const TBuiltInResource DefaultTBuiltInResource = {
/* .MaxLights = */ 32,
/* .MaxClipPlanes = */ 6,
/* .MaxTextureUnits = */ 32,
/* .MaxTextureCoords = */ 32,
/* .MaxVertexAttribs = */ 64,
/* .MaxVertexUniformComponents = */ 4096,
/* .MaxVaryingFloats = */ 64,
/* .MaxVertexTextureImageUnits = */ 32,
/* .MaxCombinedTextureImageUnits = */ 80,
/* .MaxTextureImageUnits = */ 32,
/* .MaxFragmentUniformComponents = */ 4096,
/* .MaxDrawBuffers = */ 32,
/* .MaxVertexUniformVectors = */ 128,
/* .MaxVaryingVectors = */ 8,
/* .MaxFragmentUniformVectors = */ 16,
/* .MaxVertexOutputVectors = */ 16,
/* .MaxFragmentInputVectors = */ 15,
/* .MinProgramTexelOffset = */ -8,
/* .MaxProgramTexelOffset = */ 7,
/* .MaxClipDistances = */ 8,
/* .MaxComputeWorkGroupCountX = */ 65535,
/* .MaxComputeWorkGroupCountY = */ 65535,
/* .MaxComputeWorkGroupCountZ = */ 65535,
/* .MaxComputeWorkGroupSizeX = */ 1024,
/* .MaxComputeWorkGroupSizeY = */ 1024,
/* .MaxComputeWorkGroupSizeZ = */ 64,
/* .MaxComputeUniformComponents = */ 1024,
/* .MaxComputeTextureImageUnits = */ 16,
/* .MaxComputeImageUniforms = */ 8,
/* .MaxComputeAtomicCounters = */ 8,
/* .MaxComputeAtomicCounterBuffers = */ 1,
/* .MaxVaryingComponents = */ 60,
/* .MaxVertexOutputComponents = */ 64,
/* .MaxGeometryInputComponents = */ 64,
/* .MaxGeometryOutputComponents = */ 128,
/* .MaxFragmentInputComponents = */ 128,
/* .MaxImageUnits = */ 8,
/* .MaxCombinedImageUnitsAndFragmentOutputs = */ 8,
/* .MaxCombinedShaderOutputResources = */ 8,
/* .MaxImageSamples = */ 0,
/* .MaxVertexImageUniforms = */ 0,
/* .MaxTessControlImageUniforms = */ 0,
/* .MaxTessEvaluationImageUniforms = */ 0,
/* .MaxGeometryImageUniforms = */ 0,
/* .MaxFragmentImageUniforms = */ 8,
/* .MaxCombinedImageUniforms = */ 8,
/* .MaxGeometryTextureImageUnits = */ 16,
/* .MaxGeometryOutputVertices = */ 256,
/* .MaxGeometryTotalOutputComponents = */ 1024,
/* .MaxGeometryUniformComponents = */ 1024,
/* .MaxGeometryVaryingComponents = */ 64,
/* .MaxTessControlInputComponents = */ 128,
/* .MaxTessControlOutputComponents = */ 128,
/* .MaxTessControlTextureImageUnits = */ 16,
/* .MaxTessControlUniformComponents = */ 1024,
/* .MaxTessControlTotalOutputComponents = */ 4096,
/* .MaxTessEvaluationInputComponents = */ 128,
/* .MaxTessEvaluationOutputComponents = */ 128,
/* .MaxTessEvaluationTextureImageUnits = */ 16,
/* .MaxTessEvaluationUniformComponents = */ 1024,
/* .MaxTessPatchComponents = */ 120,
/* .MaxPatchVertices = */ 32,
/* .MaxTessGenLevel = */ 64,
/* .MaxViewports = */ 16,
/* .MaxVertexAtomicCounters = */ 0,
/* .MaxTessControlAtomicCounters = */ 0,
/* .MaxTessEvaluationAtomicCounters = */ 0,
/* .MaxGeometryAtomicCounters = */ 0,
/* .MaxFragmentAtomicCounters = */ 8,
/* .MaxCombinedAtomicCounters = */ 8,
/* .MaxAtomicCounterBindings = */ 1,
/* .MaxVertexAtomicCounterBuffers = */ 0,
/* .MaxTessControlAtomicCounterBuffers = */ 0,
/* .MaxTessEvaluationAtomicCounterBuffers = */ 0,
/* .MaxGeometryAtomicCounterBuffers = */ 0,
/* .MaxFragmentAtomicCounterBuffers = */ 1,
/* .MaxCombinedAtomicCounterBuffers = */ 1,
/* .MaxAtomicCounterBufferSize = */ 16384,
/* .MaxTransformFeedbackBuffers = */ 4,
/* .MaxTransformFeedbackInterleavedComponents = */ 64,
/* .MaxCullDistances = */ 8,
/* .MaxCombinedClipAndCullDistances = */ 8,
/* .MaxSamples = */ 4,
/* .maxMeshOutputVerticesNV = */ 256,
/* .maxMeshOutputPrimitivesNV = */ 512,
/* .maxMeshWorkGroupSizeX_NV = */ 32,
/* .maxMeshWorkGroupSizeY_NV = */ 1,
/* .maxMeshWorkGroupSizeZ_NV = */ 1,
/* .maxTaskWorkGroupSizeX_NV = */ 32,
/* .maxTaskWorkGroupSizeY_NV = */ 1,
/* .maxTaskWorkGroupSizeZ_NV = */ 1,
/* .maxMeshViewCountNV = */ 4,
/* .maxDualSourceDrawBuffersEXT = */ 1,
/* .limits = */ {
/* .nonInductiveForLoops = */ 1,
/* .whileLoops = */ 1,
/* .doWhileLoops = */ 1,
/* .generalUniformIndexing = */ 1,
/* .generalAttributeMatrixVectorIndexing = */ 1,
/* .generalVaryingIndexing = */ 1,
/* .generalSamplerIndexing = */ 1,
/* .generalVariableIndexing = */ 1,
/* .generalConstantMatrixVectorIndexing = */ 1,
}
};
int main()
{
if (!glslang::InitializeProcess())
return false;
const char* fsCode[] = { R"(
#version 450
struct st
{
vec2 a[2];
float b[2][2];
};
layout(set = 0, binding = 0) uniform ubo {
st s[2];
vec2 a[2];
float b[2][2];
};
layout(location = 0) out vec4 outColor0;
void main()
{
outColor0 = vec4(s[1].b[1][0]);
}
)" };
auto fs = std::make_unique(EShLangFragment);
fs->setStrings(fsCode, 1);
fs->setEnvInput(glslang::EShSourceGlsl, EShLangFragment, glslang::EShClientVulkan, 100);
fs->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1);
fs->setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_5);
auto succeeded = fs->parse(&DefaultTBuiltInResource, 450, ENoProfile, true, true, EShMsgDefault);
std::cout << fs->getInfoLog();
if (!succeeded)
return -1;
auto program = std::make_unique();
program->addShader(fs.get());
succeeded = program->link(EShMsgDefault);
std::cout << program->getInfoLog();
if (!succeeded)
return -1;
std::vector spirv;
glslang::GlslangToSpv(*program->getIntermediate(EShLangFragment), spirv);
std::ostringstream oss;
spv::Disassemble(oss, spirv);
std::cout << "SPIR-V:\n" << oss.str();
int opts = EShReflectionAllBlockVariables | EShReflectionStrictArraySuffix;
succeeded = program->buildReflection(opts);
if (!succeeded)
return -1;
int n = program->getNumUniformVariables();
for (int i = 0; i < n; i++)
{
auto& uniform = program->getUniform(i);
std::cout << uniform.name << ": offset=" << uniform.offset << " stride=" << uniform.arrayStride << "\n";
}
return 0;
}
```
Program output:
```
SPIR-V:
// Module Version 10500
// Generated by (magic number): 8000a
// Id's are bound by 31
Capability Shader
1: ExtInstImport "GLSL.std.450"
MemoryModel Logical GLSL450
EntryPoint Fragment 4 "main" 9 23
ExecutionMode 4 OriginUpperLeft
Source GLSL 450
Name 4 "main"
Name 9 "outColor0"
Name 16 "st"
MemberName 16(st) 0 "a"
MemberName 16(st) 1 "b"
Name 21 "ubo"
MemberName 21(ubo) 0 "s"
MemberName 21(ubo) 1 "a"
MemberName 21(ubo) 2 "b"
Name 23 ""
Decorate 9(outColor0) Location 0
Decorate 13 ArrayStride 16
Decorate 14 ArrayStride 16
Decorate 15 ArrayStride 32
MemberDecorate 16(st) 0 Offset 0
MemberDecorate 16(st) 1 Offset 32
Decorate 17 ArrayStride 96
Decorate 18 ArrayStride 16
Decorate 19 ArrayStride 16
Decorate 20 ArrayStride 32
MemberDecorate 21(ubo) 0 Offset 0
MemberDecorate 21(ubo) 1 Offset 192
MemberDecorate 21(ubo) 2 Offset 224
Decorate 21(ubo) Block
Decorate 23 DescriptorSet 0
Decorate 23 Binding 0
2: TypeVoid
3: TypeFunction 2
6: TypeFloat 32
7: TypeVector 6(float) 4
8: TypePointer Output 7(fvec4)
9(outColor0): 8(ptr) Variable Output
10: TypeVector 6(float) 2
11: TypeInt 32 0
12: 11(int) Constant 2
13: TypeArray 10(fvec2) 12
14: TypeArray 6(float) 12
15: TypeArray 14 12
16(st): TypeStruct 13 15
17: TypeArray 16(st) 12
18: TypeArray 10(fvec2) 12
19: TypeArray 6(float) 12
20: TypeArray 19 12
21(ubo): TypeStruct 17 18 20
22: TypePointer Uniform 21(ubo)
23: 22(ptr) Variable Uniform
24: TypeInt 32 1
25: 24(int) Constant 0
26: 24(int) Constant 1
27: TypePointer Uniform 6(float)
4(main): 2 Function None 3
5: Label
28: 27(ptr) AccessChain 23 25 26 26 26 25
29: 6(float) Load 28
30: 7(fvec4) CompositeConstruct 29 29 29 29
Store 9(outColor0) 30
Return
FunctionEnd
s[0].a: offset=0 stride=8
s[0].b[0]: offset=32 stride=4
s[0].b[1]: offset=40 stride=4
s[1].a: offset=96 stride=8
s[1].b[0]: offset=128 stride=4
s[1].b[1]: offset=136 stride=4
a: offset=192 stride=16
b[0]: offset=224 stride=16
b[1]: offset=256 stride=16
```
The SPIR-V generated by the compiler has 16 array stride in all arrays, which is expected. But the reflection API returns 8 array stride for vec2 and 4 array stride for float for struct members, and offset is also wrong. However the offsets and array strides for non-struct members are correct.
Contributor guide
Research direction
Run the supplied C++ reproducer and inspect program->buildReflection together with getUniform results for the struct members. Compare reflected offsets and arrayStride values with the SPIR-V ArrayStride and MemberDecorate output; done means struct-member reflection reports the matching Vulkan layout values.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100