1 //
2 // Copyright 2020 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 ValidateClipCullDistance function:
7 // * gathers clip/cull distance usages
8 // * checks if the sum of array sizes for gl_ClipDistance and
9 // gl_CullDistance exceeds gl_MaxCombinedClipAndCullDistances
10 // * checks if length() operator is used correctly
11 // * adds an explicit clip/cull distance declaration
12 //
13
14 #include "ValidateClipCullDistance.h"
15
16 #include "compiler/translator/Diagnostics.h"
17 #include "compiler/translator/SymbolTable.h"
18 #include "compiler/translator/tree_util/IntermTraverse.h"
19 #include "compiler/translator/tree_util/ReplaceVariable.h"
20 #include "compiler/translator/util.h"
21
22 namespace sh
23 {
24
25 namespace
26 {
27
error(const TIntermSymbol & symbol,const char * reason,TDiagnostics * diagnostics)28 void error(const TIntermSymbol &symbol, const char *reason, TDiagnostics *diagnostics)
29 {
30 diagnostics->error(symbol.getLine(), reason, symbol.getName().data());
31 }
32
33 class ValidateClipCullDistanceTraverser : public TIntermTraverser
34 {
35 public:
36 ValidateClipCullDistanceTraverser();
37 void validate(TDiagnostics *diagnostics,
38 const unsigned int maxCombinedClipAndCullDistances,
39 uint8_t *clipDistanceSizeOut,
40 uint8_t *cullDistanceSizeOut,
41 bool *clipDistanceRedeclaredOut,
42 bool *cullDistanceRedeclaredOut,
43 bool *clipDistanceUsedOut);
44
45 private:
46 bool visitDeclaration(Visit visit, TIntermDeclaration *node) override;
47 bool visitBinary(Visit visit, TIntermBinary *node) override;
48
49 uint8_t mClipDistanceSize;
50 uint8_t mCullDistanceSize;
51
52 int8_t mMaxClipDistanceIndex;
53 int8_t mMaxCullDistanceIndex;
54
55 bool mHasNonConstClipDistanceIndex;
56 bool mHasNonConstCullDistanceIndex;
57
58 const TIntermSymbol *mClipDistance;
59 const TIntermSymbol *mCullDistance;
60 };
61
ValidateClipCullDistanceTraverser()62 ValidateClipCullDistanceTraverser::ValidateClipCullDistanceTraverser()
63 : TIntermTraverser(true, false, false),
64 mClipDistanceSize(0),
65 mCullDistanceSize(0),
66 mMaxClipDistanceIndex(-1),
67 mMaxCullDistanceIndex(-1),
68 mHasNonConstClipDistanceIndex(false),
69 mHasNonConstCullDistanceIndex(false),
70 mClipDistance(nullptr),
71 mCullDistance(nullptr)
72 {}
73
visitDeclaration(Visit visit,TIntermDeclaration * node)74 bool ValidateClipCullDistanceTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node)
75 {
76 const TIntermSequence &sequence = *(node->getSequence());
77
78 if (sequence.size() != 1)
79 {
80 return true;
81 }
82
83 const TIntermSymbol *symbol = sequence.front()->getAsSymbolNode();
84 if (symbol == nullptr)
85 {
86 return true;
87 }
88
89 if (symbol->getName() == "gl_ClipDistance")
90 {
91 mClipDistanceSize = static_cast<uint8_t>(symbol->getOutermostArraySize());
92 mClipDistance = symbol;
93 }
94 else if (symbol->getName() == "gl_CullDistance")
95 {
96 mCullDistanceSize = static_cast<uint8_t>(symbol->getOutermostArraySize());
97 mCullDistance = symbol;
98 }
99
100 return true;
101 }
102
visitBinary(Visit visit,TIntermBinary * node)103 bool ValidateClipCullDistanceTraverser::visitBinary(Visit visit, TIntermBinary *node)
104 {
105 TOperator op = node->getOp();
106 if (op != EOpIndexDirect && op != EOpIndexIndirect)
107 {
108 return true;
109 }
110
111 TIntermSymbol *left = node->getLeft()->getAsSymbolNode();
112 if (!left)
113 {
114 return true;
115 }
116
117 ImmutableString varName(left->getName());
118 if (varName != "gl_ClipDistance" && varName != "gl_CullDistance")
119 {
120 return true;
121 }
122
123 const TConstantUnion *constIdx = node->getRight()->getConstantValue();
124 if (constIdx)
125 {
126 int idx = 0;
127 switch (constIdx->getType())
128 {
129 case EbtInt:
130 idx = constIdx->getIConst();
131 break;
132 case EbtUInt:
133 idx = constIdx->getUConst();
134 break;
135 default:
136 UNREACHABLE();
137 break;
138 }
139
140 if (varName == "gl_ClipDistance")
141 {
142 if (idx > mMaxClipDistanceIndex)
143 {
144 mMaxClipDistanceIndex = static_cast<int8_t>(idx);
145 if (!mClipDistance)
146 {
147 mClipDistance = left;
148 }
149 }
150 }
151 else
152 {
153 ASSERT(varName == "gl_CullDistance");
154 if (idx > mMaxCullDistanceIndex)
155 {
156 mMaxCullDistanceIndex = static_cast<int8_t>(idx);
157 if (!mCullDistance)
158 {
159 mCullDistance = left;
160 }
161 }
162 }
163 }
164 else
165 {
166 if (varName == "gl_ClipDistance")
167 {
168 mHasNonConstClipDistanceIndex = true;
169 if (!mClipDistance)
170 {
171 mClipDistance = left;
172 }
173 }
174 else
175 {
176 ASSERT(varName == "gl_CullDistance");
177 mHasNonConstCullDistanceIndex = true;
178 if (!mCullDistance)
179 {
180 mCullDistance = left;
181 }
182 }
183 }
184
185 return true;
186 }
187
validate(TDiagnostics * diagnostics,const unsigned int maxCombinedClipAndCullDistances,uint8_t * clipDistanceSizeOut,uint8_t * cullDistanceSizeOut,bool * clipDistanceRedeclaredOut,bool * cullDistanceRedeclaredOut,bool * clipDistanceUsedOut)188 void ValidateClipCullDistanceTraverser::validate(TDiagnostics *diagnostics,
189 const unsigned int maxCombinedClipAndCullDistances,
190 uint8_t *clipDistanceSizeOut,
191 uint8_t *cullDistanceSizeOut,
192 bool *clipDistanceRedeclaredOut,
193 bool *cullDistanceRedeclaredOut,
194 bool *clipDistanceUsedOut)
195 {
196 ASSERT(diagnostics);
197
198 if (mClipDistanceSize == 0 && mHasNonConstClipDistanceIndex)
199 {
200 error(*mClipDistance,
201 "The array must be sized by the shader either redeclaring it with a size or "
202 "indexing it only with constant integral expressions",
203 diagnostics);
204 }
205
206 if (mCullDistanceSize == 0 && mHasNonConstCullDistanceIndex)
207 {
208 error(*mCullDistance,
209 "The array must be sized by the shader either redeclaring it with a size or "
210 "indexing it only with constant integral expressions",
211 diagnostics);
212 }
213
214 unsigned int enabledClipDistances =
215 (mClipDistanceSize > 0 ? mClipDistanceSize
216 : (mClipDistance ? mMaxClipDistanceIndex + 1 : 0));
217 unsigned int enabledCullDistances =
218 (mCullDistanceSize > 0 ? mCullDistanceSize
219 : (mCullDistance ? mMaxCullDistanceIndex + 1 : 0));
220 unsigned int combinedClipAndCullDistances =
221 (enabledClipDistances > 0 && enabledCullDistances > 0
222 ? enabledClipDistances + enabledCullDistances
223 : 0);
224
225 // When cull distances are not supported, i.e., when GL_ANGLE_clip_cull_distance is
226 // exposed but GL_EXT_clip_cull_distance is not exposed, the combined limit is 0.
227 if (enabledCullDistances > 0 && maxCombinedClipAndCullDistances == 0)
228 {
229 error(*mCullDistance, "Cull distance functionality is not available", diagnostics);
230 }
231
232 if (combinedClipAndCullDistances > maxCombinedClipAndCullDistances)
233 {
234 const TIntermSymbol *greaterSymbol =
235 (enabledClipDistances >= enabledCullDistances ? mClipDistance : mCullDistance);
236
237 std::stringstream strstr = sh::InitializeStream<std::stringstream>();
238 strstr << "The sum of 'gl_ClipDistance' and 'gl_CullDistance' size is greater than "
239 "gl_MaxCombinedClipAndCullDistances ("
240 << combinedClipAndCullDistances << " > " << maxCombinedClipAndCullDistances << ")";
241 error(*greaterSymbol, strstr.str().c_str(), diagnostics);
242 }
243
244 // Update the compiler state
245 *clipDistanceSizeOut = mClipDistanceSize ? mClipDistanceSize : (mMaxClipDistanceIndex + 1);
246 *cullDistanceSizeOut = mCullDistanceSize ? mCullDistanceSize : (mMaxCullDistanceIndex + 1);
247 *clipDistanceRedeclaredOut = mClipDistanceSize != 0;
248 *cullDistanceRedeclaredOut = mCullDistanceSize != 0;
249 *clipDistanceUsedOut = (mMaxClipDistanceIndex != -1) || mHasNonConstClipDistanceIndex;
250 }
251
252 class ValidateClipCullDistanceLengthTraverser : public TIntermTraverser
253 {
254 public:
255 ValidateClipCullDistanceLengthTraverser(TDiagnostics *diagnostics,
256 uint8_t clipDistanceSized,
257 uint8_t cullDistanceSized);
258
259 private:
260 bool visitUnary(Visit visit, TIntermUnary *node) override;
261
262 TDiagnostics *mDiagnostics;
263 const bool mClipDistanceSized;
264 const bool mCullDistanceSized;
265 };
266
ValidateClipCullDistanceLengthTraverser(TDiagnostics * diagnostics,uint8_t clipDistanceSize,uint8_t cullDistanceSize)267 ValidateClipCullDistanceLengthTraverser::ValidateClipCullDistanceLengthTraverser(
268 TDiagnostics *diagnostics,
269 uint8_t clipDistanceSize,
270 uint8_t cullDistanceSize)
271 : TIntermTraverser(true, false, false),
272 mDiagnostics(diagnostics),
273 mClipDistanceSized(clipDistanceSize > 0),
274 mCullDistanceSized(cullDistanceSize > 0)
275 {}
276
visitUnary(Visit visit,TIntermUnary * node)277 bool ValidateClipCullDistanceLengthTraverser::visitUnary(Visit visit, TIntermUnary *node)
278 {
279 if (node->getOp() == EOpArrayLength)
280 {
281 TIntermTyped *operand = node->getOperand();
282 if ((operand->getQualifier() == EvqClipDistance && !mClipDistanceSized) ||
283 (operand->getQualifier() == EvqCullDistance && !mCullDistanceSized))
284 {
285 error(*operand->getAsSymbolNode(),
286 "The length() method cannot be called on an array that is not "
287 "runtime sized and also has not yet been explicitly sized",
288 mDiagnostics);
289 }
290 }
291 return true;
292 }
293
ReplaceAndDeclareVariable(TCompiler * compiler,TIntermBlock * root,const ImmutableString & name,unsigned int size)294 bool ReplaceAndDeclareVariable(TCompiler *compiler,
295 TIntermBlock *root,
296 const ImmutableString &name,
297 unsigned int size)
298 {
299 const TVariable *var = static_cast<const TVariable *>(
300 compiler->getSymbolTable().findBuiltIn(name, compiler->getShaderVersion()));
301 ASSERT(var != nullptr);
302
303 if (size != var->getType().getOutermostArraySize())
304 {
305 TType *resizedType = new TType(var->getType());
306 resizedType->setArraySize(0, size);
307 TVariable *resizedVar =
308 new TVariable(&compiler->getSymbolTable(), name, resizedType, SymbolType::BuiltIn);
309 if (!ReplaceVariable(compiler, root, var, resizedVar))
310 {
311 return false;
312 }
313 var = resizedVar;
314 }
315
316 TIntermDeclaration *globalDecl = new TIntermDeclaration();
317 globalDecl->appendDeclarator(new TIntermSymbol(var));
318 root->insertStatement(0, globalDecl);
319
320 return true;
321 }
322
323 } // anonymous namespace
324
ValidateClipCullDistance(TCompiler * compiler,TIntermBlock * root,TDiagnostics * diagnostics,const unsigned int maxCombinedClipAndCullDistances,uint8_t * clipDistanceSizeOut,uint8_t * cullDistanceSizeOut,bool * clipDistanceUsedOut)325 bool ValidateClipCullDistance(TCompiler *compiler,
326 TIntermBlock *root,
327 TDiagnostics *diagnostics,
328 const unsigned int maxCombinedClipAndCullDistances,
329 uint8_t *clipDistanceSizeOut,
330 uint8_t *cullDistanceSizeOut,
331 bool *clipDistanceUsedOut)
332 {
333 ValidateClipCullDistanceTraverser varyingValidator;
334 root->traverse(&varyingValidator);
335 int numErrorsBefore = diagnostics->numErrors();
336 bool clipDistanceRedeclared;
337 bool cullDistanceRedeclared;
338 varyingValidator.validate(diagnostics, maxCombinedClipAndCullDistances, clipDistanceSizeOut,
339 cullDistanceSizeOut, &clipDistanceRedeclared, &cullDistanceRedeclared,
340 clipDistanceUsedOut);
341
342 ValidateClipCullDistanceLengthTraverser lengthValidator(diagnostics, *clipDistanceSizeOut,
343 *cullDistanceSizeOut);
344 root->traverse(&lengthValidator);
345 if (diagnostics->numErrors() != numErrorsBefore)
346 {
347 return false;
348 }
349
350 // If the clip/cull distance variables are not explicitly redeclared in the incoming shader,
351 // redeclare them to ensure that various pruning passes will not cause inconsistent AST state.
352 if (*clipDistanceSizeOut > 0 && !clipDistanceRedeclared &&
353 !ReplaceAndDeclareVariable(compiler, root, ImmutableString("gl_ClipDistance"),
354 *clipDistanceSizeOut))
355 {
356
357 return false;
358 }
359 if (*cullDistanceSizeOut > 0 && !cullDistanceRedeclared &&
360 !ReplaceAndDeclareVariable(compiler, root, ImmutableString("gl_CullDistance"),
361 *cullDistanceSizeOut))
362 {
363 return false;
364 }
365
366 return true;
367 }
368
369 } // namespace sh
370