1 //
2 // Copyright 2002 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // The ValidateVaryingLocations function checks if there exists location conflicts on shader
7 // varyings.
8 //
9
10 #include "ValidateVaryingLocations.h"
11
12 #include "compiler/translator/Diagnostics.h"
13 #include "compiler/translator/SymbolTable.h"
14 #include "compiler/translator/tree_util/IntermTraverse.h"
15 #include "compiler/translator/util.h"
16
17 namespace sh
18 {
19
20 namespace
21 {
22
error(const TIntermSymbol & symbol,const char * reason,TDiagnostics * diagnostics)23 void error(const TIntermSymbol &symbol, const char *reason, TDiagnostics *diagnostics)
24 {
25 diagnostics->error(symbol.getLine(), reason, symbol.getName().data());
26 }
27
28 int GetStructLocationCount(const TStructure *structure);
29
GetFieldLocationCount(const TField * field)30 int GetFieldLocationCount(const TField *field)
31 {
32 int field_size = 0;
33 const TType *fieldType = field->type();
34
35 if (fieldType->getStruct() != nullptr)
36 {
37 field_size = GetStructLocationCount(fieldType->getStruct());
38 }
39 else if (fieldType->isMatrix())
40 {
41 field_size = fieldType->getNominalSize();
42 }
43 else
44 {
45 ASSERT(fieldType->getSecondarySize() == 1);
46 field_size = 1;
47 }
48
49 if (fieldType->isArray())
50 {
51 field_size *= fieldType->getArraySizeProduct();
52 }
53
54 return field_size;
55 }
56
GetStructLocationCount(const TStructure * structure)57 int GetStructLocationCount(const TStructure *structure)
58 {
59 int totalLocation = 0;
60 for (const TField *field : structure->fields())
61 {
62 totalLocation += GetFieldLocationCount(field);
63 }
64 return totalLocation;
65 }
66
GetInterfaceBlockLocationCount(const TType & varyingType,bool ignoreVaryingArraySize)67 int GetInterfaceBlockLocationCount(const TType &varyingType, bool ignoreVaryingArraySize)
68 {
69 int totalLocation = 0;
70 for (const TField *field : varyingType.getInterfaceBlock()->fields())
71 {
72 totalLocation += GetFieldLocationCount(field);
73 }
74
75 if (!ignoreVaryingArraySize && varyingType.isArray())
76 {
77 totalLocation *= varyingType.getArraySizeProduct();
78 }
79 return totalLocation;
80 }
81
GetLocationCount(const TType & varyingType,bool ignoreVaryingArraySize)82 int GetLocationCount(const TType &varyingType, bool ignoreVaryingArraySize)
83 {
84 ASSERT(!varyingType.isInterfaceBlock());
85
86 if (varyingType.getStruct() != nullptr)
87 {
88 int totalLocation = 0;
89 for (const TField *field : varyingType.getStruct()->fields())
90 {
91 const TType *fieldType = field->type();
92 ASSERT(fieldType->getStruct() == nullptr && !fieldType->isArray());
93
94 totalLocation += GetFieldLocationCount(field);
95 }
96 return totalLocation;
97 }
98
99 ASSERT(varyingType.isMatrix() || varyingType.getSecondarySize() == 1);
100 int elementLocationCount = varyingType.isMatrix() ? varyingType.getNominalSize() : 1;
101
102 // [GL_EXT_shader_io_blocks SPEC Chapter 4.4.1]
103 // Geometry shader inputs, tessellation control shader inputs and outputs, and tessellation
104 // evaluation inputs all have an additional level of arrayness relative to other shader inputs
105 // and outputs. This outer array level is removed from the type before considering how many
106 // locations the type consumes.
107 if (ignoreVaryingArraySize)
108 {
109 // Array-of-arrays cannot be inputs or outputs of a geometry shader.
110 // (GL_EXT_geometry_shader SPEC issues(5))
111 ASSERT(!varyingType.isArrayOfArrays());
112 return elementLocationCount;
113 }
114
115 return elementLocationCount * varyingType.getArraySizeProduct();
116 }
117
ShouldIgnoreVaryingArraySize(TQualifier qualifier,GLenum shaderType)118 bool ShouldIgnoreVaryingArraySize(TQualifier qualifier, GLenum shaderType)
119 {
120 bool isVaryingIn = IsShaderIn(qualifier) && qualifier != EvqPatchIn;
121
122 switch (shaderType)
123 {
124 case GL_GEOMETRY_SHADER:
125 case GL_TESS_EVALUATION_SHADER:
126 return isVaryingIn;
127 case GL_TESS_CONTROL_SHADER:
128 return (IsShaderOut(qualifier) && qualifier != EvqPatchOut) || isVaryingIn;
129 default:
130 return false;
131 }
132 }
133
134 struct SymbolAndField
135 {
136 const TIntermSymbol *symbol;
137 const TField *field;
138 };
139 using LocationMap = std::map<int, SymbolAndField>;
140
MarkVaryingLocations(TDiagnostics * diagnostics,const TIntermSymbol * varying,const TField * field,int location,int elementCount,LocationMap * locationMap)141 void MarkVaryingLocations(TDiagnostics *diagnostics,
142 const TIntermSymbol *varying,
143 const TField *field,
144 int location,
145 int elementCount,
146 LocationMap *locationMap)
147 {
148 for (int elementIndex = 0; elementIndex < elementCount; ++elementIndex)
149 {
150 const int offsetLocation = location + elementIndex;
151 auto conflict = locationMap->find(offsetLocation);
152 if (conflict != locationMap->end())
153 {
154 std::stringstream strstr = sh::InitializeStream<std::stringstream>();
155 strstr << "'" << varying->getName();
156 if (field)
157 {
158 strstr << "." << field->name();
159 }
160 strstr << "' conflicting location with '" << conflict->second.symbol->getName();
161 if (conflict->second.field)
162 {
163 strstr << "." << conflict->second.field->name();
164 }
165 strstr << "'";
166 error(*varying, strstr.str().c_str(), diagnostics);
167 }
168 else
169 {
170 (*locationMap)[offsetLocation] = {varying, field};
171 }
172 }
173 }
174
175 using VaryingVector = std::vector<const TIntermSymbol *>;
176
ValidateShaderInterfaceAndAssignLocations(TDiagnostics * diagnostics,const VaryingVector & varyingVector,GLenum shaderType)177 void ValidateShaderInterfaceAndAssignLocations(TDiagnostics *diagnostics,
178 const VaryingVector &varyingVector,
179 GLenum shaderType)
180 {
181 // Location conflicts can only happen when there are two or more varyings in varyingVector.
182 if (varyingVector.size() <= 1)
183 {
184 return;
185 }
186
187 LocationMap locationMap;
188 for (const TIntermSymbol *varying : varyingVector)
189 {
190 const TType &varyingType = varying->getType();
191 const int location = varyingType.getLayoutQualifier().location;
192 ASSERT(location >= 0);
193
194 bool ignoreVaryingArraySize =
195 ShouldIgnoreVaryingArraySize(varying->getQualifier(), shaderType);
196
197 // A varying is either:
198 //
199 // - A vector or matrix, which can take a number of contiguous locations
200 // - A struct, which also takes a number of contiguous locations
201 // - An interface block.
202 //
203 // Interface blocks can assign arbitrary locations to their fields, for example:
204 //
205 // layout(location = 4) in block {
206 // vec4 a; // gets location 4
207 // vec4 b; // gets location 5
208 // layout(location = 7) vec4 c; // gets location 7
209 // vec4 d; // gets location 8
210 // layout (location = 1) vec4 e; // gets location 1
211 // vec4 f; // gets location 2
212 // };
213 //
214 // The following code therefore takes two paths. For non-interface-block types, the number
215 // of locations for the varying is calculated (elementCount), and all locations in
216 // [location, location + elementCount) are marked as occupied.
217 //
218 // For interface blocks, a similar algorithm is implemented except each field is
219 // individually marked with the location either advancing automatically or taking its value
220 // from the field's layout qualifier.
221
222 if (varyingType.isInterfaceBlock())
223 {
224 int currentLocation = location;
225 bool anyFieldWithLocation = false;
226
227 for (const TField *field : varyingType.getInterfaceBlock()->fields())
228 {
229 const int fieldLocation = field->type()->getLayoutQualifier().location;
230 if (fieldLocation >= 0)
231 {
232 currentLocation = fieldLocation;
233 anyFieldWithLocation = true;
234 }
235
236 const int fieldLocationCount = GetFieldLocationCount(field);
237 MarkVaryingLocations(diagnostics, varying, field, currentLocation,
238 fieldLocationCount, &locationMap);
239
240 currentLocation += fieldLocationCount;
241 }
242
243 // Array interface blocks can't have location qualifiers on fields.
244 ASSERT(ignoreVaryingArraySize || !anyFieldWithLocation || !varyingType.isArray());
245
246 if (!ignoreVaryingArraySize && varyingType.isArray())
247 {
248 // This is only reached if the varying is an array of interface blocks, with only a
249 // layout qualifier on the block itself, for example:
250 //
251 // layout(location = 4) in block {
252 // vec4 a;
253 // vec4 b;
254 // vec4 c;
255 // vec4 d;
256 // } instance[N];
257 //
258 // The locations for instance[0] are already marked by the above code, so we need to
259 // further mark locations occupied by instances [1, N). |currentLocation| is
260 // already just past the end of instance[0], which is the beginning of instance[1].
261 //
262 int remainingLocations = currentLocation * (varyingType.getArraySizeProduct() - 1);
263 MarkVaryingLocations(diagnostics, varying, nullptr, currentLocation,
264 remainingLocations, &locationMap);
265 }
266 }
267 else
268 {
269 const int elementCount = GetLocationCount(varying->getType(), ignoreVaryingArraySize);
270 MarkVaryingLocations(diagnostics, varying, nullptr, location, elementCount,
271 &locationMap);
272 }
273 }
274 }
275
276 class ValidateVaryingLocationsTraverser : public TIntermTraverser
277 {
278 public:
279 ValidateVaryingLocationsTraverser(GLenum shaderType);
280 void validate(TDiagnostics *diagnostics);
281
282 private:
283 bool visitDeclaration(Visit visit, TIntermDeclaration *node) override;
284 bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override;
285
286 VaryingVector mInputVaryingsWithLocation;
287 VaryingVector mOutputVaryingsWithLocation;
288 GLenum mShaderType;
289 };
290
ValidateVaryingLocationsTraverser(GLenum shaderType)291 ValidateVaryingLocationsTraverser::ValidateVaryingLocationsTraverser(GLenum shaderType)
292 : TIntermTraverser(true, false, false), mShaderType(shaderType)
293 {}
294
visitDeclaration(Visit visit,TIntermDeclaration * node)295 bool ValidateVaryingLocationsTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node)
296 {
297 const TIntermSequence &sequence = *(node->getSequence());
298 ASSERT(!sequence.empty());
299
300 const TIntermSymbol *symbol = sequence.front()->getAsSymbolNode();
301 if (symbol == nullptr)
302 {
303 return false;
304 }
305
306 if (symbol->variable().symbolType() == SymbolType::Empty)
307 {
308 return false;
309 }
310
311 // Collect varyings that have explicit 'location' qualifiers.
312 const TQualifier qualifier = symbol->getQualifier();
313 if (symbol->getType().getLayoutQualifier().location != -1)
314 {
315 if (IsVaryingIn(qualifier))
316 {
317 mInputVaryingsWithLocation.push_back(symbol);
318 }
319 else if (IsVaryingOut(qualifier))
320 {
321 mOutputVaryingsWithLocation.push_back(symbol);
322 }
323 }
324
325 return false;
326 }
327
visitFunctionDefinition(Visit visit,TIntermFunctionDefinition * node)328 bool ValidateVaryingLocationsTraverser::visitFunctionDefinition(Visit visit,
329 TIntermFunctionDefinition *node)
330 {
331 // We stop traversing function definitions because varyings cannot be defined in a function.
332 return false;
333 }
334
validate(TDiagnostics * diagnostics)335 void ValidateVaryingLocationsTraverser::validate(TDiagnostics *diagnostics)
336 {
337 ASSERT(diagnostics);
338
339 ValidateShaderInterfaceAndAssignLocations(diagnostics, mInputVaryingsWithLocation, mShaderType);
340 ValidateShaderInterfaceAndAssignLocations(diagnostics, mOutputVaryingsWithLocation,
341 mShaderType);
342 }
343
344 } // anonymous namespace
345
CalculateVaryingLocationCount(const TType & varyingType,GLenum shaderType)346 unsigned int CalculateVaryingLocationCount(const TType &varyingType, GLenum shaderType)
347 {
348 const TQualifier qualifier = varyingType.getQualifier();
349 const bool ignoreVaryingArraySize = ShouldIgnoreVaryingArraySize(qualifier, shaderType);
350
351 if (varyingType.isInterfaceBlock())
352 {
353 return GetInterfaceBlockLocationCount(varyingType, ignoreVaryingArraySize);
354 }
355
356 return GetLocationCount(varyingType, ignoreVaryingArraySize);
357 }
358
ValidateVaryingLocations(TIntermBlock * root,TDiagnostics * diagnostics,GLenum shaderType)359 bool ValidateVaryingLocations(TIntermBlock *root, TDiagnostics *diagnostics, GLenum shaderType)
360 {
361 ValidateVaryingLocationsTraverser varyingValidator(shaderType);
362 root->traverse(&varyingValidator);
363 int numErrorsBefore = diagnostics->numErrors();
364 varyingValidator.validate(diagnostics);
365 return (diagnostics->numErrors() == numErrorsBefore);
366 }
367
368 } // namespace sh
369