HLSL: Allow stream output Append() method after entry point.

Append() method is special: unlike most outputs, it does not copy
some temporary data to a symbol in the entry point epilogue, but
rather uses an emit builtin after each write to the output stream.

This had been handled by remembering the special output symbol for
the stream as it was declared in the shader entry point before
symbol sanitization.  However the prior code was too simple and
only handled cases where the Append() method happened after the
entry point, so that the output symbol had been seen.

This PR adds a patching step so that the Append()s may appear in
any order WRT the entry point.  They are patched in an epilogue,
whereupon it is guaranteed in a well formed shader that we have
seen the appropriate declaration.

Fixes #1217.
This commit is contained in:
LoopDawg 2018-05-15 13:36:13 -06:00
parent 1831087e48
commit 1326b8c754
5 changed files with 275 additions and 11 deletions

View File

@ -0,0 +1,212 @@
hlsl.tristream-append.geom
Shader version: 500
invocations = -1
max_vertices = 3
input primitive = triangles
output primitive = triangle_strip
0:? Sequence
0:8 Function Definition: EmitVertex(struct-GSPS_INPUT1;struct-GSPS_INPUT1; ( temp void)
0:8 Function Parameters:
0:8 'output' ( in structure{})
0:8 'TriStream' ( out structure{})
0:? Sequence
0:9 Sequence
0:9 Sequence
0:9 move second child to first child ( temp structure{})
0:9 'TriStream' ( out structure{})
0:9 'output' ( in structure{})
0:9 EmitVertex ( temp void)
0:14 Function Definition: @main(struct-GSPS_INPUT1[3];struct-GSPS_INPUT1; ( temp void)
0:14 Function Parameters:
0:14 'input' ( in 3-element array of structure{})
0:14 'TriStream' ( out structure{})
0:? Sequence
0:15 Function Call: EmitVertex(struct-GSPS_INPUT1;struct-GSPS_INPUT1; ( temp void)
0:15 direct index ( temp structure{})
0:15 'input' ( in 3-element array of structure{})
0:15 Constant:
0:15 0 (const int)
0:15 'TriStream' ( out structure{})
0:16 Function Call: EmitVertex(struct-GSPS_INPUT1;struct-GSPS_INPUT1; ( temp void)
0:16 direct index ( temp structure{})
0:16 'input' ( in 3-element array of structure{})
0:16 Constant:
0:16 1 (const int)
0:16 'TriStream' ( out structure{})
0:17 Function Call: EmitVertex(struct-GSPS_INPUT1;struct-GSPS_INPUT1; ( temp void)
0:17 direct index ( temp structure{})
0:17 'input' ( in 3-element array of structure{})
0:17 Constant:
0:17 2 (const int)
0:17 'TriStream' ( out structure{})
0:14 Function Definition: main( ( temp void)
0:14 Function Parameters:
0:? Sequence
0:14 move second child to first child ( temp 3-element array of structure{})
0:? 'input' ( temp 3-element array of structure{})
0:? 'input' ( in 3-element array of structure{})
0:14 Function Call: @main(struct-GSPS_INPUT1[3];struct-GSPS_INPUT1; ( temp void)
0:? 'input' ( temp 3-element array of structure{})
0:? 'TriStream' ( temp structure{})
0:? Linker Objects
Linked geometry stage:
Shader version: 500
invocations = 1
max_vertices = 3
input primitive = triangles
output primitive = triangle_strip
0:? Sequence
0:8 Function Definition: EmitVertex(struct-GSPS_INPUT1;struct-GSPS_INPUT1; ( temp void)
0:8 Function Parameters:
0:8 'output' ( in structure{})
0:8 'TriStream' ( out structure{})
0:? Sequence
0:9 Sequence
0:9 Sequence
0:9 move second child to first child ( temp structure{})
0:9 'TriStream' ( out structure{})
0:9 'output' ( in structure{})
0:9 EmitVertex ( temp void)
0:14 Function Definition: @main(struct-GSPS_INPUT1[3];struct-GSPS_INPUT1; ( temp void)
0:14 Function Parameters:
0:14 'input' ( in 3-element array of structure{})
0:14 'TriStream' ( out structure{})
0:? Sequence
0:15 Function Call: EmitVertex(struct-GSPS_INPUT1;struct-GSPS_INPUT1; ( temp void)
0:15 direct index ( temp structure{})
0:15 'input' ( in 3-element array of structure{})
0:15 Constant:
0:15 0 (const int)
0:15 'TriStream' ( out structure{})
0:16 Function Call: EmitVertex(struct-GSPS_INPUT1;struct-GSPS_INPUT1; ( temp void)
0:16 direct index ( temp structure{})
0:16 'input' ( in 3-element array of structure{})
0:16 Constant:
0:16 1 (const int)
0:16 'TriStream' ( out structure{})
0:17 Function Call: EmitVertex(struct-GSPS_INPUT1;struct-GSPS_INPUT1; ( temp void)
0:17 direct index ( temp structure{})
0:17 'input' ( in 3-element array of structure{})
0:17 Constant:
0:17 2 (const int)
0:17 'TriStream' ( out structure{})
0:14 Function Definition: main( ( temp void)
0:14 Function Parameters:
0:? Sequence
0:14 move second child to first child ( temp 3-element array of structure{})
0:? 'input' ( temp 3-element array of structure{})
0:? 'input' ( in 3-element array of structure{})
0:14 Function Call: @main(struct-GSPS_INPUT1[3];struct-GSPS_INPUT1; ( temp void)
0:? 'input' ( temp 3-element array of structure{})
0:? 'TriStream' ( temp structure{})
0:? Linker Objects
// Module Version 10000
// Generated by (magic number): 80006
// Id's are bound by 57
Capability Geometry
1: ExtInstImport "GLSL.std.450"
MemoryModel Logical GLSL450
EntryPoint Geometry 4 "main"
ExecutionMode 4 Triangles
ExecutionMode 4 Invocations 1
ExecutionMode 4 OutputTriangleStrip
ExecutionMode 4 OutputVertices 3
Source HLSL 500
Name 4 "main"
Name 6 "GSPS_INPUT"
Name 11 "EmitVertex(struct-GSPS_INPUT1;struct-GSPS_INPUT1;"
Name 9 "output"
Name 10 "TriStream"
Name 20 "@main(struct-GSPS_INPUT1[3];struct-GSPS_INPUT1;"
Name 18 "input"
Name 19 "TriStream"
Name 23 "TriStream"
Name 27 "param"
Name 30 "param"
Name 34 "param"
Name 37 "param"
Name 41 "param"
Name 44 "param"
Name 47 "input"
Name 49 "input"
Name 51 "TriStream"
Name 52 "param"
Name 54 "param"
2: TypeVoid
3: TypeFunction 2
6(GSPS_INPUT): TypeStruct
7: TypePointer Function 6(GSPS_INPUT)
8: TypeFunction 2 7(ptr) 7(ptr)
13: TypeInt 32 0
14: 13(int) Constant 3
15: TypeArray 6(GSPS_INPUT) 14
16: TypePointer Function 15
17: TypeFunction 2 16(ptr) 7(ptr)
22: TypePointer Output 6(GSPS_INPUT)
23(TriStream): 22(ptr) Variable Output
25: TypeInt 32 1
26: 25(int) Constant 0
33: 25(int) Constant 1
40: 25(int) Constant 2
48: TypePointer Input 15
49(input): 48(ptr) Variable Input
4(main): 2 Function None 3
5: Label
47(input): 16(ptr) Variable Function
51(TriStream): 7(ptr) Variable Function
52(param): 16(ptr) Variable Function
54(param): 7(ptr) Variable Function
50: 15 Load 49(input)
Store 47(input) 50
53: 15 Load 47(input)
Store 52(param) 53
55: 2 FunctionCall 20(@main(struct-GSPS_INPUT1[3];struct-GSPS_INPUT1;) 52(param) 54(param)
56:6(GSPS_INPUT) Load 54(param)
Store 51(TriStream) 56
Return
FunctionEnd
11(EmitVertex(struct-GSPS_INPUT1;struct-GSPS_INPUT1;): 2 Function None 8
9(output): 7(ptr) FunctionParameter
10(TriStream): 7(ptr) FunctionParameter
12: Label
24:6(GSPS_INPUT) Load 9(output)
Store 23(TriStream) 24
EmitVertex
Return
FunctionEnd
20(@main(struct-GSPS_INPUT1[3];struct-GSPS_INPUT1;): 2 Function None 17
18(input): 16(ptr) FunctionParameter
19(TriStream): 7(ptr) FunctionParameter
21: Label
27(param): 7(ptr) Variable Function
30(param): 7(ptr) Variable Function
34(param): 7(ptr) Variable Function
37(param): 7(ptr) Variable Function
41(param): 7(ptr) Variable Function
44(param): 7(ptr) Variable Function
28: 7(ptr) AccessChain 18(input) 26
29:6(GSPS_INPUT) Load 28
Store 27(param) 29
31: 2 FunctionCall 11(EmitVertex(struct-GSPS_INPUT1;struct-GSPS_INPUT1;) 27(param) 30(param)
32:6(GSPS_INPUT) Load 30(param)
Store 19(TriStream) 32
35: 7(ptr) AccessChain 18(input) 33
36:6(GSPS_INPUT) Load 35
Store 34(param) 36
38: 2 FunctionCall 11(EmitVertex(struct-GSPS_INPUT1;struct-GSPS_INPUT1;) 34(param) 37(param)
39:6(GSPS_INPUT) Load 37(param)
Store 19(TriStream) 39
42: 7(ptr) AccessChain 18(input) 40
43:6(GSPS_INPUT) Load 42
Store 41(param) 43
45: 2 FunctionCall 11(EmitVertex(struct-GSPS_INPUT1;struct-GSPS_INPUT1;) 41(param) 44(param)
46:6(GSPS_INPUT) Load 44(param)
Store 19(TriStream) 46
Return
FunctionEnd

View File

@ -0,0 +1,18 @@
struct GSPS_INPUT
{
};
// Test Append() method appearing before declaration of entry point's stream output.
void EmitVertex(in GSPS_INPUT output, inout TriangleStream<GSPS_INPUT> TriStream)
{
TriStream.Append( output );
}
[maxvertexcount(3)]
void main( triangle GSPS_INPUT input[3], inout TriangleStream<GSPS_INPUT> TriStream )
{
EmitVertex(input[0], TriStream);
EmitVertex(input[1], TriStream);
EmitVertex(input[2], TriStream);
}

View File

@ -370,6 +370,7 @@ INSTANTIATE_TEST_CASE_P(
{"hlsl.targetStruct1.frag", "main"},
{"hlsl.targetStruct2.frag", "main"},
{"hlsl.templatetypes.frag", "PixelShaderFunction"},
{"hlsl.tristream-append.geom", "main"},
{"hlsl.tx.bracket.frag", "main"},
{"hlsl.tx.overload.frag", "main"},
{"hlsl.type.half.frag", "main"},

View File

@ -4487,23 +4487,18 @@ void HlslParseContext::decomposeGeometryMethods(const TSourceLoc& loc, TIntermTy
emit->setLoc(loc);
emit->setType(TType(EbtVoid));
// find the matching output
if (gsStreamOutput == nullptr) {
error(loc, "unable to find output symbol for Append()", "", "");
return;
}
sequence = intermediate.growAggregate(sequence,
handleAssign(loc, EOpAssign,
intermediate.addSymbol(*gsStreamOutput, loc),
argAggregate->getSequence()[1]->getAsTyped()),
loc);
TIntermTyped* data = argAggregate->getSequence()[1]->getAsTyped();
// This will be patched in finalization during finalizeAppendMethods()
sequence = intermediate.growAggregate(sequence, data, loc);
sequence = intermediate.growAggregate(sequence, emit);
sequence->setOperator(EOpSequence);
sequence->setLoc(loc);
sequence->setType(TType(EbtVoid));
gsAppends.push_back({sequence, loc});
node = sequence;
}
break;
@ -9919,6 +9914,31 @@ void HlslParseContext::fixTextureShadowModes()
}
}
// Finalization step: patch append methods to use proper stream output, which isn't known until
// main is parsed, which could happen after the append method is parsed.
void HlslParseContext::finalizeAppendMethods()
{
TSourceLoc loc;
loc.init();
// Nothing to do: bypass test for valid stream output.
if (gsAppends.empty())
return;
if (gsStreamOutput == nullptr) {
error(loc, "unable to find output symbol for Append()", "", "");
return;
}
// Patch append sequences, now that we know the stream output symbol.
for (auto append = gsAppends.begin(); append != gsAppends.end(); ++append) {
append->node->getSequence()[0] =
handleAssign(append->loc, EOpAssign,
intermediate.addSymbol(*gsStreamOutput, append->loc),
append->node->getSequence()[0]->getAsTyped());
}
}
// post-processing
void HlslParseContext::finish()
{
@ -9931,6 +9951,7 @@ void HlslParseContext::finish()
removeUnusedStructBufferCounters();
addPatchConstantInvocation();
fixTextureShadowModes();
finalizeAppendMethods();
// Communicate out (esp. for command line) that we formed AST that will make
// illegal AST SPIR-V and it needs transforms to legalize it.

View File

@ -266,6 +266,7 @@ protected:
TVariable* getSplitNonIoVar(int id) const;
void addPatchConstantInvocation();
void fixTextureShadowModes();
void finalizeAppendMethods();
TIntermTyped* makeIntegerIndex(TIntermTyped*);
void fixBuiltInIoType(TType&);
@ -460,6 +461,17 @@ protected:
TVector<tMipsOperatorData> mipsOperatorMipArg;
// The geometry output stream is not copied out from the entry point as a typical output variable
// is. It's written via EmitVertex (hlsl=Append), which may happen in arbitrary control flow.
// For this we need the real output symbol. Since it may not be known at the time and Append()
// method is parsed, the sequence will be patched during finalization.
struct tGsAppendData {
TIntermAggregate* node;
TSourceLoc loc;
};
TVector<tGsAppendData> gsAppends;
// A texture object may be used with shadow and non-shadow samplers, but both may not be
// alive post-DCE in the same shader. We do not know at compilation time which are alive: that's
// only known post-DCE. If a texture is used both ways, we create two textures, and