Issues with spirv-opt when using -Os flag

I am trying to use spirv-opt tool after compiling my shaders to reduce and optimize them, since they are generated and there are a lot of unused and ifdef-ed code. However when using -Os flag the generated spirv code seems incompatible with the source.

As an example here’s a very simple vertex shader:

#version 430

struct CommonData
	mat4 WVP;
	mat4 WorldMatrix;
layout(location=0) uniform CommonData g_CommonData;

struct FrameData
	mat4 Variables;
layout(location=1) uniform FrameData g_FrameData;

layout(location=0) in vec4 vs_Position;

void main( )
	gl_Position = g_CommonData.WorldMatrix * vs_Position;

After compiling it and then running spirv-opt, with -Os flag and then decomposing it to glsl again, the result is:

#version 430

struct CommonData
    mat4 WorldMatrix;

layout(location = 0) uniform CommonData g_CommonData;

layout(location = 0) in vec4 vs_Position;

void main()
    gl_Position = g_CommonData.WorldMatrix * vs_Position;

As you can see, the g_FrameData is removed, as expected, but the g_CommonData uniform has its type changed. I am guessing its because of the “eliminate-dead-members” flag that gets enabled by -Os.

Is that really a valid optimization? Maybe removing elements after the last element that is actually used in the code is okay, but isn’t removing the first structure element shifting the memory location of the element actually being used, which will cause it to be loaded with incorrect value?

And more generally - is it safe to process my glsl code in this way:

glslangValidator.exe <original_code>
spirv-opt.exe <generated_spirv> <optimized_spirv>
spirv-cross.exe <optimized_spirv>

and expect that the newly generated glsl code will be fully compatible with the original one?

Is this intended to be a Vulkan shader or an OpenGL shader? Because default-block uniforms for types other than opaque types isn’t allowed in Vulkan.

This is only intended for OpenGL shaders, maybe it’s not the correct approach at all though. I figured I can use the spirv tools to compile/optimize/decompile the generated shaders so they will get distributed stripped of all useless stuff in them (and there are A LOT of code to be removed, since the shaders are semi-auto generated).

I actually thought my implementation is already working, but then tried adding -Os flag (in addition to -O) to spirv-opt and seeing the way it changed my structure declaration got me really worried.

The approach is fine; it’s just that you posted in a category called “Vulkan: Tools”. So I just wanted that cleared up.

Now that that’s settled, it’s clear that… the transformation is almost correct.

When you expose a struct as a uniform, it appears as a series of uniform values, which represent the decomposition of the aggregate into non-aggregate types. There is no uniform named g_CommonData; there are only g_CommonData.WorldMatrix and g_CommonData.MVP. And since your code never uses the CommonData struct itself outside of accessing WorldMatrix from that uniform, it is 100% OK for the compiler to strip the unused member.

Well, almost.

The incorrect part is that you gave the uniform an explicit location. That means that g_CommonData.WVP should have location 0 and g_CommonData.WorldMatrix should have location 1. But the generated code that optimized the matrix out doesn’t reflect this. It simply removed the text, ignoring the location designation, which means it assigned g_CommonData.WorldMatrix to location 0. That’s no longer equivalent to the original.

If the optimizer insisted on removing that member, it should have explicitly assigned the struct a location of 1.

That’s a bug in their optimizer/generator, and you should report it. The easiest way for them to fix it is probably to just turn off struct member removal optimizations if the struct is used in a uniform that has an explicit location. Otherwise, the rules of when you get to remove members from them can get really complicated.

Thank you for taking the time to respond!

I wasn’t sure where to post the question since it’s opengl related, but the spirv-opt tool is part of the vulkan sdk.

I guess my understanding of uniform locations is incorrect. I was under the impression these are more or less equal to hlsl registers. In retrospect I should’ve probably read some more about these locations before posing the question, but now that I have, it seems that glslangValidator should’ve reported an error trying to compile the sample code since location 1 is used in both structures (second element of the first one and first element of the second one).

Interestingly, if I modify the declaration to this:

layout(location=0) uniform mat4 g_CommonData[2];
mat4 g_WVP = g_CommonData[0];
mat4 g_WorldMatrix = g_CommonData[1];

layout(location=1) uniform vec4 g_FrameData[3];

glslangValidator correctly identifies the error and results in

ERROR: vs.glsl:5: 'location' : overlapping use of location 1
ERROR: 1 compilation errors.  No code generated.

Any hint where should I report the problem would be really appreciated.

Thanks again

Khronos maintains a number of GitHub repositories, including the one for glslang.