add cross-stage check for missing outputs

If an 'in' is present in a shader stage, make sure a matching 'out'
is present in the previous stage. Only enabled when doing Vulkan.

This commit also fixes a bug where previous stage's linkerObjects
got polluted with 'in' variables from the next stage when merging
linker objects.
This commit is contained in:
Malcolm Bechard 2024-07-12 00:51:48 -04:00 committed by arcady-lunarg
parent 6a28e226c7
commit 69249e46b6
6 changed files with 439 additions and 25 deletions

View File

@ -0,0 +1,313 @@
iomap.crossStage.vk.2.vert
Shader version: 460
0:? Sequence
0:8 Function Definition: main( ( global void)
0:8 Function Parameters:
0:10 Sequence
0:10 move second child to first child ( temp highp 4-component vector of float)
0:10 val: direct index for structure ( out highp 4-component vector of float)
0:10 'anon@0' ( out block{ out highp 4-component vector of float val})
0:10 Constant:
0:10 0 (const uint)
0:10 Constant:
0:10 0.500000
0:10 0.500000
0:10 0.500000
0:10 0.500000
0:11 move second child to first child ( temp highp 4-component vector of float)
0:11 'color' ( smooth out highp 4-component vector of float)
0:11 Constant:
0:11 1.000000
0:11 1.000000
0:11 1.000000
0:11 1.000000
0:12 move second child to first child ( temp 4-component vector of float)
0:12 gl_Position: direct index for structure ( gl_Position 4-component vector of float Position)
0:12 'anon@1' ( out block{ gl_Position 4-component vector of float Position gl_Position, gl_PointSize float PointSize gl_PointSize, out unsized 1-element array of float ClipDistance gl_ClipDistance, out unsized 1-element array of float CullDistance gl_CullDistance})
0:12 Constant:
0:12 0 (const uint)
0:12 Constant:
0:12 1.000000
0:12 1.000000
0:12 1.000000
0:12 1.000000
0:? Linker Objects
0:? 'anon@0' ( out block{ out highp 4-component vector of float val})
0:? 'color' ( smooth out highp 4-component vector of float)
0:? 'anon@1' ( out block{ gl_Position 4-component vector of float Position gl_Position, gl_PointSize float PointSize gl_PointSize, out unsized 1-element array of float ClipDistance gl_ClipDistance, out unsized 1-element array of float CullDistance gl_CullDistance})
iomap.crossStage.vk.2.geom
Shader version: 460
invocations = -1
max_vertices = 3
input primitive = points
output primitive = triangle_strip
0:? Sequence
0:23 Function Definition: main( ( global void)
0:23 Function Parameters:
0:25 Sequence
0:25 Sequence
0:25 Sequence
0:25 move second child to first child ( temp highp int)
0:25 'i' ( temp highp int)
0:25 Constant:
0:25 0 (const int)
0:25 Loop with condition tested first
0:25 Loop Condition
0:25 Compare Less Than ( temp bool)
0:25 'i' ( temp highp int)
0:25 Constant:
0:25 3 (const int)
0:25 Loop Body
0:26 Sequence
0:26 move second child to first child ( temp highp 4-component vector of float)
0:26 'colorOut' (layout( stream=0) out highp 4-component vector of float)
0:26 component-wise multiply ( temp highp 4-component vector of float)
0:26 indirect index ( temp highp 4-component vector of float)
0:26 'color' ( in 1-element array of highp 4-component vector of float)
0:26 'i' ( temp highp int)
0:26 val: direct index for structure ( in highp 4-component vector of float)
0:26 indirect index ( temp block{ in highp 4-component vector of float val})
0:26 'vv' ( in 1-element array of block{ in highp 4-component vector of float val})
0:26 'i' ( temp highp int)
0:26 Constant:
0:26 0 (const int)
0:27 move second child to first child ( temp highp 4-component vector of float)
0:27 vv2Val: direct index for structure (layout( stream=0) out highp 4-component vector of float)
0:27 'anon@0' (layout( stream=0) out block{layout( stream=0) out highp 4-component vector of float vv2Val})
0:27 Constant:
0:27 0 (const uint)
0:27 Constant:
0:27 1.000000
0:27 1.000000
0:27 1.000000
0:27 1.000000
0:28 EmitVertex ( global void)
0:25 Loop Terminal Expression
0:25 Post-Increment ( temp highp int)
0:25 'i' ( temp highp int)
0:30 EndPrimitive ( global void)
0:? Linker Objects
0:? 'vgo1' ( in 1-element array of highp 4-component vector of float)
0:? 'color' ( in 1-element array of highp 4-component vector of float)
0:? 'colorOut' (layout( stream=0) out highp 4-component vector of float)
0:? 'vv' ( in 1-element array of block{ in highp 4-component vector of float val})
0:? 'anon@0' (layout( stream=0) out block{layout( stream=0) out highp 4-component vector of float vv2Val})
iomap.crossStage.vk.2.frag
Shader version: 460
gl_FragCoord origin is upper left
0:? Sequence
0:19 Function Definition: main( ( global void)
0:19 Function Parameters:
0:21 Sequence
0:21 move second child to first child ( temp highp 4-component vector of float)
0:21 'fragColor' ( out highp 4-component vector of float)
0:21 add ( temp highp 4-component vector of float)
0:21 'colorOut' ( smooth in highp 4-component vector of float)
0:21 component-wise multiply ( temp highp 4-component vector of float)
0:21 component-wise multiply ( temp highp 4-component vector of float)
0:21 component-wise multiply ( temp highp 4-component vector of float)
0:21 'unsetColor' ( smooth in highp 4-component vector of float)
0:21 Construct vec4 ( temp highp 4-component vector of float)
0:21 vector swizzle ( temp highp 4-component vector of float)
0:21 val: direct index for structure ( in highp 2-component vector of float)
0:21 'iVert' ( in block{ in highp 2-component vector of float val})
0:21 Constant:
0:21 0 (const int)
0:21 Sequence
0:21 Constant:
0:21 0 (const int)
0:21 Constant:
0:21 0 (const int)
0:21 Constant:
0:21 1 (const int)
0:21 Constant:
0:21 1 (const int)
0:21 Construct vec4 ( temp highp 4-component vector of float)
0:21 vector swizzle ( temp highp 4-component vector of float)
0:21 val2: direct index for structure ( in highp 2-component vector of float)
0:21 'anon@0' ( in block{ in highp 2-component vector of float val2})
0:21 Constant:
0:21 0 (const uint)
0:21 Sequence
0:21 Constant:
0:21 0 (const int)
0:21 Constant:
0:21 0 (const int)
0:21 Constant:
0:21 1 (const int)
0:21 Constant:
0:21 1 (const int)
0:22 'vv2Val' ( smooth in highp 4-component vector of float)
0:? Linker Objects
0:? 'unsetColor' ( smooth in highp 4-component vector of float)
0:? 'colorOut' ( smooth in highp 4-component vector of float)
0:? 'fragColor' ( out highp 4-component vector of float)
0:? 'iVert' ( in block{ in highp 2-component vector of float val})
0:? 'anon@0' ( in block{ in highp 2-component vector of float val2})
0:? 'vv2Val' ( smooth in highp 4-component vector of float)
Linked vertex stage:
Linked geometry stage:
Linked fragment stage:
ERROR: Linking vertex and geometry stages: Input 'vgo1' in geometry shader has no corresponding output in vertex shader.
ERROR: Linking geometry and fragment stages: Input 'unsetColor' in fragment shader has no corresponding output in geometry shader.
ERROR: Linking geometry and fragment stages: Input 'Vertex' in fragment shader has no corresponding output in geometry shader.
ERROR: Linking geometry and fragment stages: Input 'Vertex2' in fragment shader has no corresponding output in geometry shader.
ERROR: Linking geometry and fragment stages: Input 'vv2Val' in fragment shader has no corresponding output in geometry shader.
Shader version: 460
0:? Sequence
0:8 Function Definition: main( ( global void)
0:8 Function Parameters:
0:10 Sequence
0:10 move second child to first child ( temp highp 4-component vector of float)
0:10 val: direct index for structure ( out highp 4-component vector of float)
0:10 'anon@0' ( out block{ out highp 4-component vector of float val})
0:10 Constant:
0:10 0 (const uint)
0:10 Constant:
0:10 0.500000
0:10 0.500000
0:10 0.500000
0:10 0.500000
0:11 move second child to first child ( temp highp 4-component vector of float)
0:11 'color' ( smooth out highp 4-component vector of float)
0:11 Constant:
0:11 1.000000
0:11 1.000000
0:11 1.000000
0:11 1.000000
0:12 move second child to first child ( temp 4-component vector of float)
0:12 gl_Position: direct index for structure ( gl_Position 4-component vector of float Position)
0:12 'anon@1' ( out block{ gl_Position 4-component vector of float Position gl_Position, gl_PointSize float PointSize gl_PointSize, out 1-element array of float ClipDistance gl_ClipDistance, out 1-element array of float CullDistance gl_CullDistance})
0:12 Constant:
0:12 0 (const uint)
0:12 Constant:
0:12 1.000000
0:12 1.000000
0:12 1.000000
0:12 1.000000
0:? Linker Objects
0:? 'anon@0' ( out block{ out highp 4-component vector of float val})
0:? 'color' ( smooth out highp 4-component vector of float)
0:? 'anon@1' ( out block{ gl_Position 4-component vector of float Position gl_Position, gl_PointSize float PointSize gl_PointSize, out 1-element array of float ClipDistance gl_ClipDistance, out 1-element array of float CullDistance gl_CullDistance})
Shader version: 460
invocations = 1
max_vertices = 3
input primitive = points
output primitive = triangle_strip
0:? Sequence
0:23 Function Definition: main( ( global void)
0:23 Function Parameters:
0:25 Sequence
0:25 Sequence
0:25 Sequence
0:25 move second child to first child ( temp highp int)
0:25 'i' ( temp highp int)
0:25 Constant:
0:25 0 (const int)
0:25 Loop with condition tested first
0:25 Loop Condition
0:25 Compare Less Than ( temp bool)
0:25 'i' ( temp highp int)
0:25 Constant:
0:25 3 (const int)
0:25 Loop Body
0:26 Sequence
0:26 move second child to first child ( temp highp 4-component vector of float)
0:26 'colorOut' (layout( stream=0) out highp 4-component vector of float)
0:26 component-wise multiply ( temp highp 4-component vector of float)
0:26 indirect index ( temp highp 4-component vector of float)
0:26 'color' ( in 1-element array of highp 4-component vector of float)
0:26 'i' ( temp highp int)
0:26 val: direct index for structure ( in highp 4-component vector of float)
0:26 indirect index ( temp block{ in highp 4-component vector of float val})
0:26 'vv' ( in 1-element array of block{ in highp 4-component vector of float val})
0:26 'i' ( temp highp int)
0:26 Constant:
0:26 0 (const int)
0:27 move second child to first child ( temp highp 4-component vector of float)
0:27 vv2Val: direct index for structure (layout( stream=0) out highp 4-component vector of float)
0:27 'anon@0' (layout( stream=0) out block{layout( stream=0) out highp 4-component vector of float vv2Val})
0:27 Constant:
0:27 0 (const uint)
0:27 Constant:
0:27 1.000000
0:27 1.000000
0:27 1.000000
0:27 1.000000
0:28 EmitVertex ( global void)
0:25 Loop Terminal Expression
0:25 Post-Increment ( temp highp int)
0:25 'i' ( temp highp int)
0:30 EndPrimitive ( global void)
0:? Linker Objects
0:? 'vgo1' ( in 1-element array of highp 4-component vector of float)
0:? 'color' ( in 1-element array of highp 4-component vector of float)
0:? 'colorOut' (layout( stream=0) out highp 4-component vector of float)
0:? 'vv' ( in 1-element array of block{ in highp 4-component vector of float val})
0:? 'anon@0' (layout( stream=0) out block{layout( stream=0) out highp 4-component vector of float vv2Val})
Shader version: 460
gl_FragCoord origin is upper left
0:? Sequence
0:19 Function Definition: main( ( global void)
0:19 Function Parameters:
0:21 Sequence
0:21 move second child to first child ( temp highp 4-component vector of float)
0:21 'fragColor' ( out highp 4-component vector of float)
0:21 add ( temp highp 4-component vector of float)
0:21 'colorOut' ( smooth in highp 4-component vector of float)
0:21 component-wise multiply ( temp highp 4-component vector of float)
0:21 component-wise multiply ( temp highp 4-component vector of float)
0:21 component-wise multiply ( temp highp 4-component vector of float)
0:21 'unsetColor' ( smooth in highp 4-component vector of float)
0:21 Construct vec4 ( temp highp 4-component vector of float)
0:21 vector swizzle ( temp highp 4-component vector of float)
0:21 val: direct index for structure ( in highp 2-component vector of float)
0:21 'iVert' ( in block{ in highp 2-component vector of float val})
0:21 Constant:
0:21 0 (const int)
0:21 Sequence
0:21 Constant:
0:21 0 (const int)
0:21 Constant:
0:21 0 (const int)
0:21 Constant:
0:21 1 (const int)
0:21 Constant:
0:21 1 (const int)
0:21 Construct vec4 ( temp highp 4-component vector of float)
0:21 vector swizzle ( temp highp 4-component vector of float)
0:21 val2: direct index for structure ( in highp 2-component vector of float)
0:21 'anon@0' ( in block{ in highp 2-component vector of float val2})
0:21 Constant:
0:21 0 (const uint)
0:21 Sequence
0:21 Constant:
0:21 0 (const int)
0:21 Constant:
0:21 0 (const int)
0:21 Constant:
0:21 1 (const int)
0:21 Constant:
0:21 1 (const int)
0:22 'vv2Val' ( smooth in highp 4-component vector of float)
0:? Linker Objects
0:? 'unsetColor' ( smooth in highp 4-component vector of float)
0:? 'colorOut' ( smooth in highp 4-component vector of float)
0:? 'fragColor' ( out highp 4-component vector of float)
0:? 'iVert' ( in block{ in highp 2-component vector of float val})
0:? 'anon@0' ( in block{ in highp 2-component vector of float val2})
0:? 'vv2Val' ( smooth in highp 4-component vector of float)
Mismatched cross-stage IO
Validation failed
SPIR-V is not generated for failed compile or link

24
Test/iomap.crossStage.vk.2.frag Executable file
View File

@ -0,0 +1,24 @@
#version 460
in vec4 unsetColor;
in vec4 colorOut;
out vec4 fragColor;
in Vertex
{
vec2 val;
} iVert;
in Vertex2
{
vec2 val2;
};
in vec4 vv2Val;
void main()
{
fragColor = colorOut + unsetColor * vec4(iVert.val.xxyy) * vec4(val2.xxyy) *
vv2Val;
}

32
Test/iomap.crossStage.vk.2.geom Executable file
View File

@ -0,0 +1,32 @@
#version 460
layout(points) in;
layout(triangle_strip, max_vertices=3) out;
// Not written by vertex shader
in vec4 vgo1[];
in vec4 color[];
out vec4 colorOut;
in VV
{
vec4 val;
} vv[];
out VV2
{
vec4 vv2Val;
};
void main()
{
for (int i = 0; i < 3; i++) {
colorOut = color[i] * vv[i].val;
vv2Val = vec4(1.0);
EmitVertex();
}
EndPrimitive();
}

14
Test/iomap.crossStage.vk.2.vert Executable file
View File

@ -0,0 +1,14 @@
#version 460
out VV
{
vec4 val;
};
out vec4 color;
void main()
{
val = vec4(0.5);
color = vec4(1.0);
gl_Position = vec4(1.0);
}

View File

@ -113,6 +113,28 @@ void TIntermediate::mergeUniformObjects(TInfoSink& infoSink, TIntermediate& unit
mergeLinkerObjects(infoSink, linkerObjects, unitLinkerObjects, unit.getStage());
}
static inline bool isSameInterface(TIntermSymbol* symbol, EShLanguage stage, TIntermSymbol* unitSymbol, EShLanguage unitStage) {
return // 1) same stage and same shader interface
(stage == unitStage && symbol->getType().getShaderInterface() == unitSymbol->getType().getShaderInterface()) ||
// 2) accross stages and both are uniform or buffer
(symbol->getQualifier().storage == EvqUniform && unitSymbol->getQualifier().storage == EvqUniform) ||
(symbol->getQualifier().storage == EvqBuffer && unitSymbol->getQualifier().storage == EvqBuffer) ||
// 3) in/out matched across stage boundary
(stage < unitStage && symbol->getQualifier().storage == EvqVaryingOut && unitSymbol->getQualifier().storage == EvqVaryingIn) ||
(unitStage < stage && symbol->getQualifier().storage == EvqVaryingIn && unitSymbol->getQualifier().storage == EvqVaryingOut);
}
static bool isSameSymbol(TIntermSymbol* symbol1, EShLanguage stage1, TIntermSymbol* symbol2, EShLanguage stage2) {
// If they are both blocks in the same shader interface,
// match by the block-name, not the identifier name.
if (symbol1->getType().getBasicType() == EbtBlock && symbol2->getType().getBasicType() == EbtBlock) {
if (isSameInterface(symbol1, stage1, symbol2, stage2)) {
return symbol1->getType().getTypeName() == symbol2->getType().getTypeName();
}
} else if (symbol1->getName() == symbol2->getName())
return true;
return false;
}
//
// do error checking on the shader boundary in / out vars
//
@ -137,7 +159,32 @@ void TIntermediate::checkStageIO(TInfoSink& infoSink, TIntermediate& unit) {
// do matching and error checking
mergeLinkerObjects(infoSink, linkerObjects, unitLinkerObjects, unit.getStage());
// TODO: final check; make sure that any statically used `in` have matching `out` written to
// Check that all of our inputs have matching outputs from the previous stage.
// Only do this for Vulkan, since GL_ARB_separate_shader_objects allows for
// the in/out to not match
if (spvVersion.vulkan > 0) {
for (auto& nextStageInterm : unitLinkerObjects) {
auto* nextStageSymbol = nextStageInterm->getAsSymbolNode();
bool found = false;
for (auto& curStageInterm : linkerObjects) {
if (isSameSymbol(curStageInterm->getAsSymbolNode(), getStage(), nextStageSymbol, unit.getStage())) {
found = true;
break;
}
}
if (!found) {
TString errmsg;
errmsg.append("Input '");
if (nextStageSymbol->getType().getBasicType() == EbtBlock)
errmsg.append(nextStageSymbol->getType().getTypeName());
else
errmsg.append(nextStageSymbol->getName());
errmsg.append("' in ").append(StageName(unit.getStage()));
errmsg.append(" shader has no corresponding output in ").append(StageName(getStage())).append(" shader.");
error(infoSink, errmsg.c_str(), unit.getStage());
}
}
}
}
void TIntermediate::mergeCallGraphs(TInfoSink& infoSink, TIntermediate& unit)
@ -511,17 +558,6 @@ void TIntermediate::mergeBodies(TInfoSink& infoSink, TIntermSequence& globals, c
globals.insert(globals.end() - 1, unitGlobals.begin(), unitGlobals.end() - 1);
}
static inline bool isSameInterface(TIntermSymbol* symbol, EShLanguage stage, TIntermSymbol* unitSymbol, EShLanguage unitStage) {
return // 1) same stage and same shader interface
(stage == unitStage && symbol->getType().getShaderInterface() == unitSymbol->getType().getShaderInterface()) ||
// 2) accross stages and both are uniform or buffer
(symbol->getQualifier().storage == EvqUniform && unitSymbol->getQualifier().storage == EvqUniform) ||
(symbol->getQualifier().storage == EvqBuffer && unitSymbol->getQualifier().storage == EvqBuffer) ||
// 3) in/out matched across stage boundary
(stage < unitStage && symbol->getQualifier().storage == EvqVaryingOut && unitSymbol->getQualifier().storage == EvqVaryingIn) ||
(unitStage < stage && symbol->getQualifier().storage == EvqVaryingIn && unitSymbol->getQualifier().storage == EvqVaryingOut);
}
//
// Global Unfiform block stores any default uniforms (i.e. uniforms without a block)
// If two linked stages declare the same member, they are meant to be the same uniform
@ -707,24 +743,18 @@ void TIntermediate::mergeLinkerObjects(TInfoSink& infoSink, TIntermSequence& lin
// Error check and merge the linker objects (duplicates should not be created)
std::size_t initialNumLinkerObjects = linkerObjects.size();
for (unsigned int unitLinkObj = 0; unitLinkObj < unitLinkerObjects.size(); ++unitLinkObj) {
TIntermSymbol* unitSymbol = unitLinkerObjects[unitLinkObj]->getAsSymbolNode();
bool merge = true;
// Don't merge inputs backwards into previous stages
if (getStage() != unitStage && unitSymbol->getQualifier().storage == EvqVaryingIn)
merge = false;
for (std::size_t linkObj = 0; linkObj < initialNumLinkerObjects; ++linkObj) {
TIntermSymbol* symbol = linkerObjects[linkObj]->getAsSymbolNode();
TIntermSymbol* unitSymbol = unitLinkerObjects[unitLinkObj]->getAsSymbolNode();
assert(symbol && unitSymbol);
bool isSameSymbol = false;
// If they are both blocks in the same shader interface,
// match by the block-name, not the identifier name.
if (symbol->getType().getBasicType() == EbtBlock && unitSymbol->getType().getBasicType() == EbtBlock) {
if (isSameInterface(symbol, getStage(), unitSymbol, unitStage)) {
isSameSymbol = symbol->getType().getTypeName() == unitSymbol->getType().getTypeName();
}
}
else if (symbol->getName() == unitSymbol->getName())
isSameSymbol = true;
if (isSameSymbol) {
if (isSameSymbol(symbol, getStage(), unitSymbol, unitStage)) {
// filter out copy
merge = false;

View File

@ -345,6 +345,7 @@ INSTANTIATE_TEST_SUITE_P(
{{"iomap.variableOutBlockIn.2.vert", "iomap.variableOutBlockIn.geom"}, Semantics::OpenGL},
// vulkan semantics
{{"iomap.crossStage.vk.vert", "iomap.crossStage.vk.geom", "iomap.crossStage.vk.frag" }, Semantics::Vulkan},
{{"iomap.crossStage.vk.2.vert", "iomap.crossStage.vk.2.geom", "iomap.crossStage.vk.2.frag" }, Semantics::Vulkan},
}))
);
// clang-format on