/src/shaderc/third_party/glslang/glslang/MachineIndependent/linkValidate.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // |
2 | | // Copyright (C) 2013 LunarG, Inc. |
3 | | // Copyright (C) 2017 ARM Limited. |
4 | | // Copyright (C) 2015-2018 Google, Inc. |
5 | | // |
6 | | // All rights reserved. |
7 | | // |
8 | | // Redistribution and use in source and binary forms, with or without |
9 | | // modification, are permitted provided that the following conditions |
10 | | // are met: |
11 | | // |
12 | | // Redistributions of source code must retain the above copyright |
13 | | // notice, this list of conditions and the following disclaimer. |
14 | | // |
15 | | // Redistributions in binary form must reproduce the above |
16 | | // copyright notice, this list of conditions and the following |
17 | | // disclaimer in the documentation and/or other materials provided |
18 | | // with the distribution. |
19 | | // |
20 | | // Neither the name of 3Dlabs Inc. Ltd. nor the names of its |
21 | | // contributors may be used to endorse or promote products derived |
22 | | // from this software without specific prior written permission. |
23 | | // |
24 | | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
25 | | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
26 | | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
27 | | // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
28 | | // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
29 | | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
30 | | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
31 | | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
32 | | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
33 | | // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
34 | | // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
35 | | // POSSIBILITY OF SUCH DAMAGE. |
36 | | // |
37 | | |
38 | | // |
39 | | // Do link-time merging and validation of intermediate representations. |
40 | | // |
41 | | // Basic model is that during compilation, each compilation unit (shader) is |
42 | | // compiled into one TIntermediate instance. Then, at link time, multiple |
43 | | // units for the same stage can be merged together, which can generate errors. |
44 | | // Then, after all merging, a single instance of TIntermediate represents |
45 | | // the whole stage. A final error check can be done on the resulting stage, |
46 | | // even if no merging was done (i.e., the stage was only one compilation unit). |
47 | | // |
48 | | |
49 | | #include "glslang/Public/ShaderLang.h" |
50 | | #include "localintermediate.h" |
51 | | #include "../Include/InfoSink.h" |
52 | | #include "SymbolTable.h" |
53 | | #include "LiveTraverser.h" |
54 | | |
55 | | namespace glslang { |
56 | | |
57 | | // |
58 | | // Link-time error emitter. |
59 | | // |
60 | | void TIntermediate::error(TInfoSink& infoSink, const TSourceLoc* loc, EShMessages messages, const char* message, |
61 | | EShLanguage unitStage) |
62 | 1.24k | { |
63 | 1.24k | infoSink.info.prefix(EPrefixError); |
64 | 1.24k | if (loc) |
65 | 0 | infoSink.info.location(*loc, messages & EShMsgAbsolutePath, messages & EShMsgDisplayErrorColumn); |
66 | 1.24k | if (unitStage == EShLangCount) |
67 | 1.24k | infoSink.info << "Linking " << StageName(language) << " stage: " << message << "\n"; |
68 | 0 | else if (language == EShLangCount) |
69 | 0 | infoSink.info << "Linking " << StageName(unitStage) << " stage: " << message << "\n"; |
70 | 0 | else |
71 | 0 | infoSink.info << "Linking " << StageName(language) << " and " << StageName(unitStage) << " stages: " << message << "\n"; |
72 | | |
73 | 1.24k | ++numErrors; |
74 | 1.24k | } |
75 | | |
76 | | // Link-time warning. |
77 | | void TIntermediate::warn(TInfoSink& infoSink, const TSourceLoc* loc, EShMessages messages, const char* message, |
78 | | EShLanguage unitStage) |
79 | 988 | { |
80 | 988 | infoSink.info.prefix(EPrefixWarning); |
81 | 988 | if (loc) |
82 | 0 | infoSink.info.location(*loc, messages & EShMsgAbsolutePath, messages & EShMsgDisplayErrorColumn); |
83 | 988 | if (unitStage == EShLangCount) |
84 | 988 | infoSink.info << "Linking " << StageName(language) << " stage: " << message << "\n"; |
85 | 0 | else if (language == EShLangCount) |
86 | 0 | infoSink.info << "Linking " << StageName(unitStage) << " stage: " << message << "\n"; |
87 | 0 | else |
88 | 0 | infoSink.info << "Linking " << StageName(language) << " and " << StageName(unitStage) << " stages: " << message << "\n"; |
89 | 988 | } |
90 | | |
91 | | // TODO: 4.4 offset/align: "Two blocks linked together in the same program with the same block |
92 | | // name must have the exact same set of members qualified with offset and their integral-constant |
93 | | // expression values must be the same, or a link-time error results." |
94 | | |
95 | | // |
96 | | // Merge the information from 'unit' into 'this' |
97 | | // |
98 | | void TIntermediate::merge(TInfoSink& infoSink, TIntermediate& unit) |
99 | 0 | { |
100 | 0 | mergeCallGraphs(infoSink, unit); |
101 | 0 | mergeModes(infoSink, unit); |
102 | 0 | mergeTrees(infoSink, unit); |
103 | 0 | } |
104 | | |
105 | | // |
106 | | // check that link objects between stages |
107 | | // |
108 | 0 | void TIntermediate::mergeUniformObjects(TInfoSink& infoSink, TIntermediate& unit) { |
109 | 0 | if (unit.treeRoot == nullptr || treeRoot == nullptr) |
110 | 0 | return; |
111 | | |
112 | | // Get the linker-object lists |
113 | 0 | TIntermSequence& linkerObjects = findLinkerObjects()->getSequence(); |
114 | 0 | TIntermSequence unitLinkerObjects = unit.findLinkerObjects()->getSequence(); |
115 | | |
116 | | // filter unitLinkerObjects to only contain uniforms |
117 | 0 | auto end = std::remove_if(unitLinkerObjects.begin(), unitLinkerObjects.end(), |
118 | 0 | [](TIntermNode* node) {return node->getAsSymbolNode()->getQualifier().storage != EvqUniform && |
119 | 0 | node->getAsSymbolNode()->getQualifier().storage != EvqBuffer; }); |
120 | 0 | unitLinkerObjects.resize(end - unitLinkerObjects.begin()); |
121 | | |
122 | | // merge uniforms and do error checking |
123 | 0 | bool mergeExistingOnly = false; |
124 | 0 | mergeGlobalUniformBlocks(infoSink, unit, mergeExistingOnly); |
125 | 0 | mergeLinkerObjects(infoSink, linkerObjects, unitLinkerObjects, unit.getStage()); |
126 | 0 | } |
127 | | |
128 | 0 | static inline bool isSameInterface(TIntermSymbol* symbol, TIntermSymbol* unitSymbol) { |
129 | 0 | EShLanguage stage = symbol->getStage(); |
130 | 0 | EShLanguage unitStage = unitSymbol->getStage(); |
131 | 0 | return // 1) same stage and same shader interface |
132 | 0 | (stage == unitStage && symbol->getType().getShaderInterface() == unitSymbol->getType().getShaderInterface()) || |
133 | | // 2) accross stages and both are uniform or buffer |
134 | 0 | (symbol->getQualifier().storage == EvqUniform && unitSymbol->getQualifier().storage == EvqUniform) || |
135 | 0 | (symbol->getQualifier().storage == EvqBuffer && unitSymbol->getQualifier().storage == EvqBuffer) || |
136 | | // 3) in/out matched across stage boundary |
137 | 0 | (stage < unitStage && symbol->getQualifier().storage == EvqVaryingOut && unitSymbol->getQualifier().storage == EvqVaryingIn) || |
138 | 0 | (unitStage < stage && symbol->getQualifier().storage == EvqVaryingIn && unitSymbol->getQualifier().storage == EvqVaryingOut); |
139 | 0 | } |
140 | | |
141 | 0 | static bool isSameSymbol(TIntermSymbol* symbol1, TIntermSymbol* symbol2) { |
142 | | // If they are both blocks in the same shader interface, |
143 | | // match by the block-name, not the identifier name. |
144 | 0 | if (symbol1->getType().getBasicType() == EbtBlock && symbol2->getType().getBasicType() == EbtBlock) { |
145 | 0 | if (isSameInterface(symbol1, symbol2)) { |
146 | 0 | return symbol1->getType().getTypeName() == symbol2->getType().getTypeName(); |
147 | 0 | } |
148 | 0 | } else if (symbol1->getName() == symbol2->getName()) |
149 | 0 | return true; |
150 | 0 | return false; |
151 | 0 | } |
152 | | |
153 | | // |
154 | | // merge implicit array sizes for uniform/buffer objects |
155 | | // |
156 | 0 | void TIntermediate::mergeImplicitArraySizes(TInfoSink&, TIntermediate& unit) { |
157 | 0 | if (unit.treeRoot == nullptr || treeRoot == nullptr) |
158 | 0 | return; |
159 | | |
160 | | // Get the linker-object lists |
161 | 0 | TIntermSequence& linkerObjects = findLinkerObjects()->getSequence(); |
162 | 0 | TIntermSequence unitLinkerObjects = unit.findLinkerObjects()->getSequence(); |
163 | | |
164 | | // filter unitLinkerObjects to only contain uniforms |
165 | 0 | auto end = std::remove_if(unitLinkerObjects.begin(), unitLinkerObjects.end(), |
166 | 0 | [](TIntermNode* node) {return node->getAsSymbolNode()->getQualifier().storage != EvqUniform && |
167 | 0 | node->getAsSymbolNode()->getQualifier().storage != EvqBuffer; }); |
168 | 0 | unitLinkerObjects.resize(end - unitLinkerObjects.begin()); |
169 | |
|
170 | 0 | std::size_t initialNumLinkerObjects = linkerObjects.size(); |
171 | 0 | for (unsigned int unitLinkObj = 0; unitLinkObj < unitLinkerObjects.size(); ++unitLinkObj) { |
172 | 0 | for (std::size_t linkObj = 0; linkObj < initialNumLinkerObjects; ++linkObj) { |
173 | 0 | TIntermSymbol* symbol = linkerObjects[linkObj]->getAsSymbolNode(); |
174 | 0 | TIntermSymbol* unitSymbol = unitLinkerObjects[unitLinkObj]->getAsSymbolNode(); |
175 | 0 | assert(symbol && unitSymbol); |
176 | |
|
177 | 0 | if (isSameSymbol(symbol, unitSymbol)) { |
178 | | // Update implicit array sizes |
179 | 0 | mergeImplicitArraySizes(symbol->getWritableType(), unitSymbol->getType()); |
180 | 0 | } |
181 | 0 | } |
182 | 0 | } |
183 | 0 | } |
184 | | |
185 | | // |
186 | | // do error checking on the shader boundary in / out vars |
187 | | // |
188 | 0 | void TIntermediate::checkStageIO(TInfoSink& infoSink, TIntermediate& unit, EShMessages messages) { |
189 | 0 | if (unit.treeRoot == nullptr || treeRoot == nullptr) |
190 | 0 | return; |
191 | | |
192 | | // Get copies of the linker-object lists |
193 | 0 | TIntermSequence linkerObjects = findLinkerObjects()->getSequence(); |
194 | 0 | TIntermSequence unitLinkerObjects = unit.findLinkerObjects()->getSequence(); |
195 | | |
196 | | // filter linkerObjects to only contain out variables |
197 | 0 | auto end = std::remove_if(linkerObjects.begin(), linkerObjects.end(), |
198 | 0 | [](TIntermNode* node) {return node->getAsSymbolNode()->getQualifier().storage != EvqVaryingOut; }); |
199 | 0 | linkerObjects.resize(end - linkerObjects.begin()); |
200 | | |
201 | | // filter unitLinkerObjects to only contain in variables |
202 | 0 | auto unitEnd = std::remove_if(unitLinkerObjects.begin(), unitLinkerObjects.end(), |
203 | 0 | [](TIntermNode* node) {return node->getAsSymbolNode()->getQualifier().storage != EvqVaryingIn; }); |
204 | 0 | unitLinkerObjects.resize(unitEnd - unitLinkerObjects.begin()); |
205 | | |
206 | | // do matching and error checking |
207 | 0 | mergeLinkerObjects(infoSink, linkerObjects, unitLinkerObjects, unit.getStage()); |
208 | |
|
209 | 0 | if ((messages & EShMsgValidateCrossStageIO) == 0) |
210 | 0 | return; |
211 | | |
212 | | // The OpenGL Shading Language, Version 4.60.8 (https://registry.khronos.org/OpenGL/specs/gl/GLSLangSpec.4.60.pdf) |
213 | | // 4.3.4 Input Variables |
214 | | // Only the input variables that are statically read need to be written by the previous stage; it is |
215 | | // allowed to have superfluous declarations of input variables. This is shown in the following table. |
216 | | // +------------------------------------------------------------------------------------------------+ |
217 | | // | Treatment of Mismatched Input | Consuming Shader (input variables) | |
218 | | // | Variables |---------------------------------------------------------| |
219 | | // | | No | Declared but no | Declared and Static Use | |
220 | | // | | Declaration | Static Use | | |
221 | | // |--------------------------------------+-------------+-----------------+-------------------------| |
222 | | // | Generating Shader | No Declaration | Allowed | Allowed | Link-Time Error | |
223 | | // | (output variables) |-----------------+-------------+-----------------+-------------------------| |
224 | | // | | Declared but no | Allowed | Allowed | Allowed (values are | |
225 | | // | | Static Use | | | undefined) | |
226 | | // | |-----------------+-------------+-----------------+-------------------------| |
227 | | // | | Declared and | Allowed | Allowed | Allowed (values are | |
228 | | // | | Static Use | | | potentially undefined) | |
229 | | // +------------------------------------------------------------------------------------------------+ |
230 | | // Consumption errors are based on static use only. Compilation may generate a warning, but not an |
231 | | // error, for any dynamic use the compiler can deduce that might cause consumption of undefined values. |
232 | | |
233 | | // TODO: implement support for geometry passthrough |
234 | 0 | if (getGeoPassthroughEXT()) { |
235 | 0 | unit.warn(infoSink, "GL_NV_geometry_shader_passthrough is enabled, skipping cross-stage IO validation", |
236 | 0 | getStage()); |
237 | 0 | return; |
238 | 0 | } |
239 | | |
240 | 0 | class TIOTraverser : public TLiveTraverser { |
241 | 0 | public: |
242 | 0 | TIOTraverser(TIntermediate& i, bool all, TIntermSequence& sequence, TStorageQualifier storage) |
243 | 0 | : TLiveTraverser(i, all, true, false, false), sequence(sequence), storage(storage) |
244 | 0 | { |
245 | 0 | } |
246 | |
|
247 | 0 | virtual void visitSymbol(TIntermSymbol* symbol) |
248 | 0 | { |
249 | 0 | if (symbol->getQualifier().storage == storage) |
250 | 0 | sequence.push_back(symbol); |
251 | 0 | } |
252 | |
|
253 | 0 | private: |
254 | 0 | TIntermSequence& sequence; |
255 | 0 | TStorageQualifier storage; |
256 | 0 | }; |
257 | | |
258 | | // live symbols only |
259 | 0 | TIntermSequence unitLiveInputs; |
260 | |
|
261 | 0 | TIOTraverser unitTraverser(unit, false, unitLiveInputs, EvqVaryingIn); |
262 | 0 | unitTraverser.pushFunction(unit.getEntryPointMangledName().c_str()); |
263 | 0 | while (! unitTraverser.destinations.empty()) { |
264 | 0 | TIntermNode* destination = unitTraverser.destinations.back(); |
265 | 0 | unitTraverser.destinations.pop_back(); |
266 | 0 | destination->traverse(&unitTraverser); |
267 | 0 | } |
268 | | |
269 | | // all symbols |
270 | 0 | TIntermSequence allOutputs; |
271 | |
|
272 | 0 | TIOTraverser traverser(*this, true, allOutputs, EvqVaryingOut); |
273 | 0 | getTreeRoot()->traverse(&traverser); |
274 | |
|
275 | 0 | std::unordered_set<int> outputLocations; |
276 | 0 | for (auto& output : allOutputs) { |
277 | 0 | if (output->getAsSymbolNode()->getBasicType() == EbtBlock) { |
278 | 0 | int lastLocation = -1; |
279 | 0 | if (output->getAsSymbolNode()->getQualifier().hasLocation()) |
280 | 0 | lastLocation = output->getAsSymbolNode()->getQualifier().layoutLocation; |
281 | 0 | const TTypeList* members = output->getAsSymbolNode()->getType().getStruct(); |
282 | 0 | for (auto& member : *members) { |
283 | 0 | int location = lastLocation; |
284 | 0 | if (member.type->getQualifier().hasLocation()) |
285 | 0 | location = member.type->getQualifier().layoutLocation; |
286 | 0 | if (location != -1) { |
287 | 0 | int locationSize = TIntermediate::computeTypeLocationSize(*member.type, getStage()); |
288 | 0 | for (int i = 0; i < locationSize; ++i) |
289 | 0 | outputLocations.insert(location + i); |
290 | 0 | lastLocation = location + locationSize; |
291 | 0 | } |
292 | 0 | } |
293 | 0 | } else { |
294 | 0 | int locationSize = TIntermediate::computeTypeLocationSize(output->getAsSymbolNode()->getType(), getStage()); |
295 | 0 | for (int i = 0; i < locationSize; ++i) |
296 | 0 | outputLocations.insert(output->getAsSymbolNode()->getQualifier().layoutLocation + i); |
297 | 0 | } |
298 | 0 | } |
299 | | |
300 | | // remove unitStage inputs with matching outputs in the current stage |
301 | 0 | auto liveEnd = std::remove_if( |
302 | 0 | unitLiveInputs.begin(), unitLiveInputs.end(), [this, &allOutputs, &outputLocations](TIntermNode* input) { |
303 | | // ignore built-ins |
304 | 0 | if (input->getAsSymbolNode()->getAccessName().compare(0, 3, "gl_") == 0) |
305 | 0 | return true; |
306 | | // try to match by location |
307 | 0 | if (input->getAsSymbolNode()->getQualifier().hasLocation() && |
308 | 0 | outputLocations.find(input->getAsSymbolNode()->getQualifier().layoutLocation) != outputLocations.end()) |
309 | 0 | return true; |
310 | 0 | if (input->getAsSymbolNode()->getBasicType() == EbtBlock) { |
311 | 0 | int lastLocation = -1; |
312 | 0 | if (input->getAsSymbolNode()->getQualifier().hasLocation()) |
313 | 0 | lastLocation = input->getAsSymbolNode()->getQualifier().layoutLocation; |
314 | 0 | const TTypeList* members = input->getAsSymbolNode()->getType().getStruct(); |
315 | 0 | for (auto& member : *members) { |
316 | 0 | int location = lastLocation; |
317 | 0 | if (member.type->getQualifier().hasLocation()) |
318 | 0 | location = member.type->getQualifier().layoutLocation; |
319 | 0 | if (location != -1) { |
320 | 0 | int locationSize = TIntermediate::computeTypeLocationSize(*member.type, getStage()); |
321 | 0 | for (int i = 0; i < locationSize; ++i) |
322 | 0 | if (outputLocations.find(location + i) != outputLocations.end()) |
323 | 0 | return true; |
324 | 0 | lastLocation = location + locationSize; |
325 | 0 | } |
326 | 0 | } |
327 | 0 | } |
328 | | // otherwise, try to match by name |
329 | 0 | return std::any_of(allOutputs.begin(), allOutputs.end(), [input](TIntermNode* output) { |
330 | 0 | return output->getAsSymbolNode()->getAccessName() == input->getAsSymbolNode()->getAccessName(); |
331 | 0 | }); |
332 | 0 | }); |
333 | 0 | unitLiveInputs.resize(liveEnd - unitLiveInputs.begin()); |
334 | | |
335 | | // check remaining loose unitStage inputs for a matching output block member |
336 | 0 | liveEnd = std::remove_if(unitLiveInputs.begin(), unitLiveInputs.end(), [&allOutputs](TIntermNode* input) { |
337 | 0 | return std::any_of(allOutputs.begin(), allOutputs.end(), [input](TIntermNode* output) { |
338 | 0 | if (output->getAsSymbolNode()->getBasicType() != EbtBlock) |
339 | 0 | return false; |
340 | 0 | const TTypeList* members = output->getAsSymbolNode()->getType().getStruct(); |
341 | 0 | return std::any_of(members->begin(), members->end(), [input](TTypeLoc type) { |
342 | 0 | return type.type->getFieldName() == input->getAsSymbolNode()->getName(); |
343 | 0 | }); |
344 | 0 | }); |
345 | 0 | }); |
346 | 0 | unitLiveInputs.resize(liveEnd - unitLiveInputs.begin()); |
347 | | |
348 | | // finally, check remaining unitStage block inputs for a matching loose output |
349 | 0 | liveEnd = std::remove_if( |
350 | 0 | unitLiveInputs.begin(), unitLiveInputs.end(), [&allOutputs](TIntermNode* input) { |
351 | 0 | if (input->getAsSymbolNode()->getBasicType() != EbtBlock) |
352 | 0 | return false; |
353 | | // liveness isn't tracked per member so finding any one live member is the best we can do |
354 | 0 | const TTypeList* members = input->getAsSymbolNode()->getType().getStruct(); |
355 | 0 | return std::any_of(members->begin(), members->end(), [allOutputs](TTypeLoc type) { |
356 | 0 | return std::any_of(allOutputs.begin(), allOutputs.end(), [&type](TIntermNode* output) { |
357 | 0 | return type.type->getFieldName() == output->getAsSymbolNode()->getName(); |
358 | 0 | }); |
359 | 0 | }); |
360 | 0 | }); |
361 | 0 | unitLiveInputs.resize(liveEnd - unitLiveInputs.begin()); |
362 | | |
363 | | // any remaining unitStage inputs have no matching output |
364 | 0 | std::for_each(unitLiveInputs.begin(), unitLiveInputs.end(), [&](TIntermNode* input) { |
365 | 0 | unit.error(infoSink, &input->getLoc(), messages, |
366 | 0 | "Preceding stage has no matching declaration for statically used input:", getStage()); |
367 | 0 | infoSink.info << " " |
368 | 0 | << input->getAsSymbolNode()->getType().getCompleteString( |
369 | 0 | true, true, false, true, input->getAsSymbolNode()->getAccessName()) |
370 | 0 | << "\n"; |
371 | 0 | }); |
372 | | |
373 | | // TODO: warn about statically read inputs with outputs declared but not written to |
374 | 0 | } |
375 | | |
376 | | void TIntermediate::optimizeStageIO(TInfoSink&, TIntermediate& unit) |
377 | 0 | { |
378 | | // don't do any input/output demotion on compute, raytracing, or task/mesh stages |
379 | | // TODO: support task/mesh |
380 | 0 | if (getStage() > EShLangFragment || unit.getStage() > EShLangFragment) { |
381 | 0 | return; |
382 | 0 | } |
383 | | |
384 | 0 | class TIOTraverser : public TLiveTraverser { |
385 | 0 | public: |
386 | 0 | TIOTraverser(TIntermediate& i, bool all, TIntermSequence& sequence, TStorageQualifier storage) |
387 | 0 | : TLiveTraverser(i, all, true, false, false), sequence(sequence), storage(storage) |
388 | 0 | { |
389 | 0 | } |
390 | |
|
391 | 0 | virtual void visitSymbol(TIntermSymbol* symbol) |
392 | 0 | { |
393 | 0 | if (symbol->getQualifier().storage == storage) { |
394 | 0 | sequence.push_back(symbol); |
395 | 0 | } |
396 | 0 | } |
397 | |
|
398 | 0 | private: |
399 | 0 | TIntermSequence& sequence; |
400 | 0 | TStorageQualifier storage; |
401 | 0 | }; |
402 | | |
403 | | // live symbols only |
404 | 0 | TIntermSequence unitLiveInputs; |
405 | |
|
406 | 0 | TIOTraverser unitTraverser(unit, false, unitLiveInputs, EvqVaryingIn); |
407 | 0 | unitTraverser.pushFunction(unit.getEntryPointMangledName().c_str()); |
408 | 0 | while (! unitTraverser.destinations.empty()) { |
409 | 0 | TIntermNode* destination = unitTraverser.destinations.back(); |
410 | 0 | unitTraverser.destinations.pop_back(); |
411 | 0 | destination->traverse(&unitTraverser); |
412 | 0 | } |
413 | |
|
414 | 0 | TIntermSequence allOutputs; |
415 | 0 | TIntermSequence unitAllInputs; |
416 | |
|
417 | 0 | TIOTraverser allTraverser(*this, true, allOutputs, EvqVaryingOut); |
418 | 0 | getTreeRoot()->traverse(&allTraverser); |
419 | |
|
420 | 0 | TIOTraverser unitAllTraverser(unit, true, unitAllInputs, EvqVaryingIn); |
421 | 0 | unit.getTreeRoot()->traverse(&unitAllTraverser); |
422 | | |
423 | | // find outputs not consumed by the next stage |
424 | 0 | std::for_each(allOutputs.begin(), allOutputs.end(), [&unitLiveInputs, &unitAllInputs](TIntermNode* output) { |
425 | | // don't do anything to builtins |
426 | 0 | if (output->getAsSymbolNode()->getAccessName().compare(0, 3, "gl_") == 0) |
427 | 0 | return; |
428 | | |
429 | | // don't demote block outputs (for now) |
430 | 0 | if (output->getAsSymbolNode()->getBasicType() == EbtBlock) |
431 | 0 | return; |
432 | | |
433 | | // check if the (loose) output has a matching loose input |
434 | 0 | auto isMatchingInput = [output](TIntermNode* input) { |
435 | 0 | return output->getAsSymbolNode()->getAccessName() == input->getAsSymbolNode()->getAccessName(); |
436 | 0 | }; |
437 | | |
438 | | // check if the (loose) output has a matching block member input |
439 | 0 | auto isMatchingInputBlockMember = [output](TIntermNode* input) { |
440 | | // ignore loose inputs |
441 | 0 | if (input->getAsSymbolNode()->getBasicType() != EbtBlock) |
442 | 0 | return false; |
443 | | |
444 | | // don't demote loose outputs with matching input block members |
445 | 0 | auto isMatchingBlockMember = [output](TTypeLoc type) { |
446 | 0 | return type.type->getFieldName() == output->getAsSymbolNode()->getName(); |
447 | 0 | }; |
448 | 0 | const TTypeList* members = input->getAsSymbolNode()->getType().getStruct(); |
449 | 0 | return std::any_of(members->begin(), members->end(), isMatchingBlockMember); |
450 | 0 | }; |
451 | | |
452 | | // determine if the input/output pair should be demoted |
453 | | // do the faster (and more likely) loose-loose check first |
454 | 0 | if (std::none_of(unitLiveInputs.begin(), unitLiveInputs.end(), isMatchingInput) && |
455 | 0 | std::none_of(unitAllInputs.begin(), unitAllInputs.end(), isMatchingInputBlockMember)) { |
456 | | // demote any input matching the output |
457 | 0 | auto demoteMatchingInputs = [output](TIntermNode* input) { |
458 | 0 | if (output->getAsSymbolNode()->getAccessName() == input->getAsSymbolNode()->getAccessName()) { |
459 | | // demote input to a plain variable |
460 | 0 | TIntermSymbol* symbol = input->getAsSymbolNode(); |
461 | 0 | symbol->getQualifier().storage = EvqGlobal; |
462 | 0 | symbol->getQualifier().clearInterstage(); |
463 | 0 | symbol->getQualifier().clearLayout(); |
464 | 0 | } |
465 | 0 | }; |
466 | | |
467 | | // demote all matching outputs to a plain variable |
468 | 0 | TIntermSymbol* symbol = output->getAsSymbolNode(); |
469 | 0 | symbol->getQualifier().storage = EvqGlobal; |
470 | 0 | symbol->getQualifier().clearInterstage(); |
471 | 0 | symbol->getQualifier().clearLayout(); |
472 | 0 | std::for_each(unitAllInputs.begin(), unitAllInputs.end(), demoteMatchingInputs); |
473 | 0 | } |
474 | 0 | }); |
475 | 0 | } |
476 | | |
477 | | void TIntermediate::mergeCallGraphs(TInfoSink& infoSink, TIntermediate& unit) |
478 | 0 | { |
479 | 0 | if (unit.getNumEntryPoints() > 0) { |
480 | 0 | if (getNumEntryPoints() > 0) |
481 | 0 | error(infoSink, "can't handle multiple entry points per stage"); |
482 | 0 | else { |
483 | 0 | entryPointName = unit.getEntryPointName(); |
484 | 0 | entryPointMangledName = unit.getEntryPointMangledName(); |
485 | 0 | } |
486 | 0 | } |
487 | 0 | numEntryPoints += unit.getNumEntryPoints(); |
488 | |
|
489 | 0 | callGraph.insert(callGraph.end(), unit.callGraph.begin(), unit.callGraph.end()); |
490 | 0 | } |
491 | | |
492 | 0 | #define MERGE_MAX(member) member = std::max(member, unit.member) |
493 | 0 | #define MERGE_TRUE(member) if (unit.member) member = unit.member; |
494 | | |
495 | | void TIntermediate::mergeModes(TInfoSink& infoSink, TIntermediate& unit) |
496 | 0 | { |
497 | 0 | if (language != unit.language) |
498 | 0 | error(infoSink, "stages must match when linking into a single stage"); |
499 | |
|
500 | 0 | if (getSource() == EShSourceNone) |
501 | 0 | setSource(unit.getSource()); |
502 | 0 | if (getSource() != unit.getSource()) |
503 | 0 | error(infoSink, "can't link compilation units from different source languages"); |
504 | |
|
505 | 0 | if (treeRoot == nullptr) { |
506 | 0 | profile = unit.profile; |
507 | 0 | version = unit.version; |
508 | 0 | requestedExtensions = unit.requestedExtensions; |
509 | 0 | } else { |
510 | 0 | if ((isEsProfile()) != (unit.isEsProfile())) |
511 | 0 | error(infoSink, "Cannot cross link ES and desktop profiles"); |
512 | 0 | else if (unit.profile == ECompatibilityProfile) |
513 | 0 | profile = ECompatibilityProfile; |
514 | 0 | version = std::max(version, unit.version); |
515 | 0 | requestedExtensions.insert(unit.requestedExtensions.begin(), unit.requestedExtensions.end()); |
516 | 0 | } |
517 | |
|
518 | 0 | MERGE_MAX(spvVersion.spv); |
519 | 0 | MERGE_MAX(spvVersion.vulkanGlsl); |
520 | 0 | MERGE_MAX(spvVersion.vulkan); |
521 | 0 | MERGE_MAX(spvVersion.openGl); |
522 | 0 | MERGE_TRUE(spvVersion.vulkanRelaxed); |
523 | |
|
524 | 0 | numErrors += unit.getNumErrors(); |
525 | | // Only one push_constant is allowed, mergeLinkerObjects() will ensure the push_constant |
526 | | // is the same for all units. |
527 | 0 | if (numPushConstants > 1 || unit.numPushConstants > 1) |
528 | 0 | error(infoSink, "Only one push_constant block is allowed per stage"); |
529 | 0 | numPushConstants = std::min(numPushConstants + unit.numPushConstants, 1); |
530 | |
|
531 | 0 | if (unit.invocations != TQualifier::layoutNotSet) { |
532 | 0 | if (invocations == TQualifier::layoutNotSet) |
533 | 0 | invocations = unit.invocations; |
534 | 0 | else if (invocations != unit.invocations) |
535 | 0 | error(infoSink, "number of invocations must match between compilation units"); |
536 | 0 | } |
537 | |
|
538 | 0 | if (vertices == TQualifier::layoutNotSet) |
539 | 0 | vertices = unit.vertices; |
540 | 0 | else if (unit.vertices != TQualifier::layoutNotSet && vertices != unit.vertices) { |
541 | 0 | if (language == EShLangGeometry || language == EShLangMesh) |
542 | 0 | error(infoSink, "Contradictory layout max_vertices values"); |
543 | 0 | else if (language == EShLangTessControl) |
544 | 0 | error(infoSink, "Contradictory layout vertices values"); |
545 | 0 | else |
546 | 0 | assert(0); |
547 | 0 | } |
548 | 0 | if (primitives == TQualifier::layoutNotSet) |
549 | 0 | primitives = unit.primitives; |
550 | 0 | else if (primitives != unit.primitives) { |
551 | 0 | if (language == EShLangMesh) |
552 | 0 | error(infoSink, "Contradictory layout max_primitives values"); |
553 | 0 | else |
554 | 0 | assert(0); |
555 | 0 | } |
556 | |
|
557 | 0 | if (inputPrimitive == ElgNone) |
558 | 0 | inputPrimitive = unit.inputPrimitive; |
559 | 0 | else if (unit.inputPrimitive != ElgNone && inputPrimitive != unit.inputPrimitive) |
560 | 0 | error(infoSink, "Contradictory input layout primitives"); |
561 | |
|
562 | 0 | if (outputPrimitive == ElgNone) |
563 | 0 | outputPrimitive = unit.outputPrimitive; |
564 | 0 | else if (unit.outputPrimitive != ElgNone && outputPrimitive != unit.outputPrimitive) |
565 | 0 | error(infoSink, "Contradictory output layout primitives"); |
566 | |
|
567 | 0 | if (originUpperLeft != unit.originUpperLeft || pixelCenterInteger != unit.pixelCenterInteger) |
568 | 0 | error(infoSink, "gl_FragCoord redeclarations must match across shaders"); |
569 | |
|
570 | 0 | if (vertexSpacing == EvsNone) |
571 | 0 | vertexSpacing = unit.vertexSpacing; |
572 | 0 | else if (vertexSpacing != unit.vertexSpacing) |
573 | 0 | error(infoSink, "Contradictory input vertex spacing"); |
574 | |
|
575 | 0 | if (vertexOrder == EvoNone) |
576 | 0 | vertexOrder = unit.vertexOrder; |
577 | 0 | else if (vertexOrder != unit.vertexOrder) |
578 | 0 | error(infoSink, "Contradictory triangle ordering"); |
579 | |
|
580 | 0 | MERGE_TRUE(pointMode); |
581 | |
|
582 | 0 | for (int i = 0; i < 3; ++i) { |
583 | 0 | if (unit.localSizeNotDefault[i]) { |
584 | 0 | if (!localSizeNotDefault[i]) { |
585 | 0 | localSize[i] = unit.localSize[i]; |
586 | 0 | localSizeNotDefault[i] = true; |
587 | 0 | } |
588 | 0 | else if (localSize[i] != unit.localSize[i]) |
589 | 0 | error(infoSink, "Contradictory local size"); |
590 | 0 | } |
591 | |
|
592 | 0 | if (localSizeSpecId[i] == TQualifier::layoutNotSet) |
593 | 0 | localSizeSpecId[i] = unit.localSizeSpecId[i]; |
594 | 0 | else if (localSizeSpecId[i] != unit.localSizeSpecId[i]) |
595 | 0 | error(infoSink, "Contradictory local size specialization ids"); |
596 | 0 | } |
597 | |
|
598 | 0 | MERGE_TRUE(earlyFragmentTests); |
599 | 0 | MERGE_TRUE(postDepthCoverage); |
600 | 0 | MERGE_TRUE(nonCoherentColorAttachmentReadEXT); |
601 | 0 | MERGE_TRUE(nonCoherentDepthAttachmentReadEXT); |
602 | 0 | MERGE_TRUE(nonCoherentStencilAttachmentReadEXT); |
603 | 0 | MERGE_TRUE(nonCoherentTileAttachmentReadQCOM); |
604 | |
|
605 | 0 | if (depthLayout == EldNone) |
606 | 0 | depthLayout = unit.depthLayout; |
607 | 0 | else if (depthLayout != unit.depthLayout) |
608 | 0 | error(infoSink, "Contradictory depth layouts"); |
609 | |
|
610 | 0 | MERGE_TRUE(depthReplacing); |
611 | 0 | MERGE_TRUE(hlslFunctionality1); |
612 | |
|
613 | 0 | blendEquations |= unit.blendEquations; |
614 | |
|
615 | 0 | MERGE_TRUE(xfbMode); |
616 | |
|
617 | 0 | for (size_t b = 0; b < xfbBuffers.size(); ++b) { |
618 | 0 | if (xfbBuffers[b].stride == TQualifier::layoutXfbStrideEnd) |
619 | 0 | xfbBuffers[b].stride = unit.xfbBuffers[b].stride; |
620 | 0 | else if (xfbBuffers[b].stride != unit.xfbBuffers[b].stride) |
621 | 0 | error(infoSink, "Contradictory xfb_stride"); |
622 | 0 | xfbBuffers[b].implicitStride = std::max(xfbBuffers[b].implicitStride, unit.xfbBuffers[b].implicitStride); |
623 | 0 | if (unit.xfbBuffers[b].contains64BitType) |
624 | 0 | xfbBuffers[b].contains64BitType = true; |
625 | 0 | if (unit.xfbBuffers[b].contains32BitType) |
626 | 0 | xfbBuffers[b].contains32BitType = true; |
627 | 0 | if (unit.xfbBuffers[b].contains16BitType) |
628 | 0 | xfbBuffers[b].contains16BitType = true; |
629 | | // TODO: 4.4 link: enhanced layouts: compare ranges |
630 | 0 | } |
631 | |
|
632 | 0 | MERGE_TRUE(multiStream); |
633 | 0 | MERGE_TRUE(layoutOverrideCoverage); |
634 | 0 | MERGE_TRUE(geoPassthroughEXT); |
635 | |
|
636 | 0 | for (unsigned int i = 0; i < unit.shiftBinding.size(); ++i) { |
637 | 0 | if (unit.shiftBinding[i] > 0) |
638 | 0 | setShiftBinding((TResourceType)i, unit.shiftBinding[i]); |
639 | 0 | } |
640 | |
|
641 | 0 | for (unsigned int i = 0; i < unit.shiftBindingForSet.size(); ++i) { |
642 | 0 | for (auto it = unit.shiftBindingForSet[i].begin(); it != unit.shiftBindingForSet[i].end(); ++it) |
643 | 0 | setShiftBindingForSet((TResourceType)i, it->second, it->first); |
644 | 0 | } |
645 | |
|
646 | 0 | resourceSetBinding.insert(resourceSetBinding.end(), unit.resourceSetBinding.begin(), unit.resourceSetBinding.end()); |
647 | |
|
648 | 0 | MERGE_TRUE(autoMapBindings); |
649 | 0 | MERGE_TRUE(autoMapLocations); |
650 | 0 | MERGE_TRUE(invertY); |
651 | 0 | MERGE_TRUE(dxPositionW); |
652 | 0 | MERGE_TRUE(debugInfo); |
653 | 0 | MERGE_TRUE(flattenUniformArrays); |
654 | 0 | MERGE_TRUE(useUnknownFormat); |
655 | 0 | MERGE_TRUE(hlslOffsets); |
656 | 0 | MERGE_TRUE(useStorageBuffer); |
657 | 0 | MERGE_TRUE(invariantAll); |
658 | 0 | MERGE_TRUE(hlslIoMapping); |
659 | | |
660 | | // TODO: sourceFile |
661 | | // TODO: sourceText |
662 | | // TODO: processes |
663 | |
|
664 | 0 | MERGE_TRUE(needToLegalize); |
665 | 0 | MERGE_TRUE(binaryDoubleOutput); |
666 | 0 | MERGE_TRUE(usePhysicalStorageBuffer); |
667 | 0 | } |
668 | | |
669 | | // |
670 | | // Merge the 'unit' AST into 'this' AST. |
671 | | // That includes rationalizing the unique IDs, which were set up independently, |
672 | | // and might have overlaps that are not the same symbol, or might have different |
673 | | // IDs for what should be the same shared symbol. |
674 | | // |
675 | | void TIntermediate::mergeTrees(TInfoSink& infoSink, TIntermediate& unit) |
676 | 0 | { |
677 | 0 | if (unit.treeRoot == nullptr) |
678 | 0 | return; |
679 | | |
680 | 0 | if (treeRoot == nullptr) { |
681 | 0 | treeRoot = unit.treeRoot; |
682 | 0 | return; |
683 | 0 | } |
684 | | |
685 | | // Getting this far means we have two existing trees to merge... |
686 | 0 | numShaderRecordBlocks += unit.numShaderRecordBlocks; |
687 | 0 | numTaskNVBlocks += unit.numTaskNVBlocks; |
688 | | |
689 | | // Get the top-level globals of each unit |
690 | 0 | TIntermSequence& globals = treeRoot->getAsAggregate()->getSequence(); |
691 | 0 | TIntermSequence& unitGlobals = unit.treeRoot->getAsAggregate()->getSequence(); |
692 | | |
693 | | // Get the linker-object lists |
694 | 0 | TIntermSequence& linkerObjects = findLinkerObjects()->getSequence(); |
695 | 0 | const TIntermSequence& unitLinkerObjects = unit.findLinkerObjects()->getSequence(); |
696 | | |
697 | | // Map by global name to unique ID to rationalize the same object having |
698 | | // differing IDs in different trees. |
699 | 0 | TIdMaps idMaps; |
700 | 0 | long long idShift; |
701 | 0 | seedIdMap(idMaps, idShift); |
702 | 0 | remapIds(idMaps, idShift + 1, unit); |
703 | |
|
704 | 0 | mergeBodies(infoSink, globals, unitGlobals); |
705 | 0 | bool mergeExistingOnly = false; |
706 | 0 | mergeGlobalUniformBlocks(infoSink, unit, mergeExistingOnly); |
707 | 0 | mergeLinkerObjects(infoSink, linkerObjects, unitLinkerObjects, unit.getStage()); |
708 | 0 | ioAccessed.insert(unit.ioAccessed.begin(), unit.ioAccessed.end()); |
709 | 0 | } |
710 | | |
711 | | static const TString& getNameForIdMap(TIntermSymbol* symbol) |
712 | 0 | { |
713 | 0 | TShaderInterface si = symbol->getType().getShaderInterface(); |
714 | 0 | if (si == EsiNone) |
715 | 0 | return symbol->getName(); |
716 | 0 | else |
717 | 0 | return symbol->getType().getTypeName(); |
718 | 0 | } |
719 | | |
720 | | |
721 | | |
722 | | // Traverser that seeds an ID map with all built-ins, and tracks the |
723 | | // maximum ID used, currently using (maximum ID + 1) as new symbol id shift seed. |
724 | | // Level id will keep same after shifting. |
725 | | // (It would be nice to put this in a function, but that causes warnings |
726 | | // on having no bodies for the copy-constructor/operator=.) |
727 | | class TBuiltInIdTraverser : public TIntermTraverser { |
728 | | public: |
729 | 0 | TBuiltInIdTraverser(TIdMaps& idMaps) : idMaps(idMaps), idShift(0) { } |
730 | | // If it's a built in, add it to the map. |
731 | | virtual void visitSymbol(TIntermSymbol* symbol) |
732 | 0 | { |
733 | 0 | const TQualifier& qualifier = symbol->getType().getQualifier(); |
734 | 0 | if (qualifier.builtIn != EbvNone) { |
735 | 0 | TShaderInterface si = symbol->getType().getShaderInterface(); |
736 | 0 | idMaps[si][getNameForIdMap(symbol)] = symbol->getId(); |
737 | 0 | } |
738 | 0 | idShift = (symbol->getId() & ~TSymbolTable::uniqueIdMask) | |
739 | 0 | std::max(idShift & TSymbolTable::uniqueIdMask, |
740 | 0 | symbol->getId() & TSymbolTable::uniqueIdMask); |
741 | 0 | } |
742 | 0 | long long getIdShift() const { return idShift; } |
743 | | protected: |
744 | | TBuiltInIdTraverser(TBuiltInIdTraverser&); |
745 | | TBuiltInIdTraverser& operator=(TBuiltInIdTraverser&); |
746 | | TIdMaps& idMaps; |
747 | | long long idShift; |
748 | | }; |
749 | | |
750 | | // Traverser that seeds an ID map with non-builtins. |
751 | | // (It would be nice to put this in a function, but that causes warnings |
752 | | // on having no bodies for the copy-constructor/operator=.) |
753 | | class TUserIdTraverser : public TIntermTraverser { |
754 | | public: |
755 | 0 | TUserIdTraverser(TIdMaps& idMaps) : idMaps(idMaps) { } |
756 | | // If its a non-built-in global, add it to the map. |
757 | | virtual void visitSymbol(TIntermSymbol* symbol) |
758 | 0 | { |
759 | 0 | const TQualifier& qualifier = symbol->getType().getQualifier(); |
760 | 0 | if (qualifier.builtIn == EbvNone) { |
761 | 0 | TShaderInterface si = symbol->getType().getShaderInterface(); |
762 | 0 | idMaps[si][getNameForIdMap(symbol)] = symbol->getId(); |
763 | 0 | } |
764 | 0 | } |
765 | | |
766 | | protected: |
767 | | TUserIdTraverser(TUserIdTraverser&); |
768 | | TUserIdTraverser& operator=(TUserIdTraverser&); |
769 | | TIdMaps& idMaps; // over biggest id |
770 | | }; |
771 | | |
772 | | // Initialize the the ID map with what we know of 'this' AST. |
773 | | void TIntermediate::seedIdMap(TIdMaps& idMaps, long long& idShift) |
774 | 0 | { |
775 | | // all built-ins everywhere need to align on IDs and contribute to the max ID |
776 | 0 | TBuiltInIdTraverser builtInIdTraverser(idMaps); |
777 | 0 | treeRoot->traverse(&builtInIdTraverser); |
778 | 0 | idShift = builtInIdTraverser.getIdShift() & TSymbolTable::uniqueIdMask; |
779 | | |
780 | | // user variables in the linker object list need to align on ids |
781 | 0 | TUserIdTraverser userIdTraverser(idMaps); |
782 | 0 | findLinkerObjects()->traverse(&userIdTraverser); |
783 | 0 | } |
784 | | |
785 | | // Traverser to map an AST ID to what was known from the seeding AST. |
786 | | // (It would be nice to put this in a function, but that causes warnings |
787 | | // on having no bodies for the copy-constructor/operator=.) |
788 | | class TRemapIdTraverser : public TIntermTraverser { |
789 | | public: |
790 | 0 | TRemapIdTraverser(const TIdMaps& idMaps, long long idShift) : idMaps(idMaps), idShift(idShift) { } |
791 | | // Do the mapping: |
792 | | // - if the same symbol, adopt the 'this' ID |
793 | | // - otherwise, ensure a unique ID by shifting to a new space |
794 | | virtual void visitSymbol(TIntermSymbol* symbol) |
795 | 0 | { |
796 | 0 | const TQualifier& qualifier = symbol->getType().getQualifier(); |
797 | 0 | bool remapped = false; |
798 | 0 | if (qualifier.isLinkable() || qualifier.builtIn != EbvNone) { |
799 | 0 | TShaderInterface si = symbol->getType().getShaderInterface(); |
800 | 0 | auto it = idMaps[si].find(getNameForIdMap(symbol)); |
801 | 0 | if (it != idMaps[si].end()) { |
802 | 0 | uint64_t id = (symbol->getId() & ~TSymbolTable::uniqueIdMask) | |
803 | 0 | (it->second & TSymbolTable::uniqueIdMask); |
804 | 0 | symbol->changeId(id); |
805 | 0 | remapped = true; |
806 | 0 | } |
807 | 0 | } |
808 | 0 | if (!remapped) |
809 | 0 | symbol->changeId(symbol->getId() + idShift); |
810 | 0 | } |
811 | | protected: |
812 | | TRemapIdTraverser(TRemapIdTraverser&); |
813 | | TRemapIdTraverser& operator=(TRemapIdTraverser&); |
814 | | const TIdMaps& idMaps; |
815 | | long long idShift; |
816 | | }; |
817 | | |
818 | | void TIntermediate::remapIds(const TIdMaps& idMaps, long long idShift, TIntermediate& unit) |
819 | 0 | { |
820 | | // Remap all IDs to either share or be unique, as dictated by the idMap and idShift. |
821 | 0 | TRemapIdTraverser idTraverser(idMaps, idShift); |
822 | 0 | unit.getTreeRoot()->traverse(&idTraverser); |
823 | 0 | } |
824 | | |
825 | | // |
826 | | // Merge the function bodies and global-level initializers from unitGlobals into globals. |
827 | | // Will error check duplication of function bodies for the same signature. |
828 | | // |
829 | | void TIntermediate::mergeBodies(TInfoSink& infoSink, TIntermSequence& globals, const TIntermSequence& unitGlobals) |
830 | 0 | { |
831 | | // TODO: link-time performance: Processing in alphabetical order will be faster |
832 | | |
833 | | // Error check the global objects, not including the linker objects |
834 | 0 | for (unsigned int child = 0; child < globals.size() - 1; ++child) { |
835 | 0 | for (unsigned int unitChild = 0; unitChild < unitGlobals.size() - 1; ++unitChild) { |
836 | 0 | TIntermAggregate* body = globals[child]->getAsAggregate(); |
837 | 0 | TIntermAggregate* unitBody = unitGlobals[unitChild]->getAsAggregate(); |
838 | 0 | if (body && unitBody && body->getOp() == EOpFunction && unitBody->getOp() == EOpFunction && body->getName() == unitBody->getName()) { |
839 | 0 | error(infoSink, "Multiple function bodies in multiple compilation units for the same signature in the same stage:"); |
840 | 0 | infoSink.info << " " << globals[child]->getAsAggregate()->getName() << "\n"; |
841 | 0 | } |
842 | 0 | } |
843 | 0 | } |
844 | | |
845 | | // Merge the global objects, just in front of the linker objects |
846 | 0 | globals.insert(globals.end() - 1, unitGlobals.begin(), unitGlobals.end() - 1); |
847 | 0 | } |
848 | | |
849 | | // |
850 | | // Global Unfiform block stores any default uniforms (i.e. uniforms without a block) |
851 | | // If two linked stages declare the same member, they are meant to be the same uniform |
852 | | // and need to be in the same block |
853 | | // merge the members of different stages to allow them to be linked properly |
854 | | // as a single block |
855 | | // |
856 | | void TIntermediate::mergeGlobalUniformBlocks(TInfoSink& infoSink, TIntermediate& unit, bool mergeExistingOnly) |
857 | 0 | { |
858 | 0 | TIntermSequence& linkerObjects = findLinkerObjects()->getSequence(); |
859 | 0 | TIntermSequence& unitLinkerObjects = unit.findLinkerObjects()->getSequence(); |
860 | | |
861 | | // build lists of default blocks from the intermediates |
862 | 0 | TIntermSequence defaultBlocks; |
863 | 0 | TIntermSequence unitDefaultBlocks; |
864 | |
|
865 | 0 | auto filter = [](TIntermSequence& list, TIntermNode* node) { |
866 | 0 | if (node->getAsSymbolNode()->getQualifier().defaultBlock) { |
867 | 0 | list.push_back(node); |
868 | 0 | } |
869 | 0 | }; |
870 | |
|
871 | 0 | std::for_each(linkerObjects.begin(), linkerObjects.end(), |
872 | 0 | [&defaultBlocks, &filter](TIntermNode* node) { |
873 | 0 | filter(defaultBlocks, node); |
874 | 0 | }); |
875 | 0 | std::for_each(unitLinkerObjects.begin(), unitLinkerObjects.end(), |
876 | 0 | [&unitDefaultBlocks, &filter](TIntermNode* node) { |
877 | 0 | filter(unitDefaultBlocks, node); |
878 | 0 | }); |
879 | |
|
880 | 0 | auto itUnitBlock = unitDefaultBlocks.begin(); |
881 | 0 | for (; itUnitBlock != unitDefaultBlocks.end(); itUnitBlock++) { |
882 | |
|
883 | 0 | bool add = !mergeExistingOnly; |
884 | 0 | auto itBlock = defaultBlocks.begin(); |
885 | |
|
886 | 0 | for (; itBlock != defaultBlocks.end(); itBlock++) { |
887 | 0 | TIntermSymbol* block = (*itBlock)->getAsSymbolNode(); |
888 | 0 | TIntermSymbol* unitBlock = (*itUnitBlock)->getAsSymbolNode(); |
889 | |
|
890 | 0 | assert(block && unitBlock); |
891 | | |
892 | | // if the two default blocks match, then merge their definitions |
893 | 0 | if (block->getType().getTypeName() == unitBlock->getType().getTypeName() && |
894 | 0 | block->getQualifier().storage == unitBlock->getQualifier().storage) { |
895 | 0 | add = false; |
896 | 0 | mergeBlockDefinitions(infoSink, block, unitBlock, &unit); |
897 | 0 | } |
898 | 0 | } |
899 | 0 | if (add) { |
900 | | // push back on original list; won't change the size of the list we're iterating over |
901 | 0 | linkerObjects.push_back(*itUnitBlock); |
902 | 0 | } |
903 | 0 | } |
904 | 0 | } |
905 | | |
906 | 0 | void TIntermediate::mergeBlockDefinitions(TInfoSink& infoSink, TIntermSymbol* block, TIntermSymbol* unitBlock, TIntermediate* unit) { |
907 | |
|
908 | 0 | if (block->getType().getTypeName() != unitBlock->getType().getTypeName() || |
909 | 0 | block->getType().getBasicType() != unitBlock->getType().getBasicType() || |
910 | 0 | block->getQualifier().storage != unitBlock->getQualifier().storage || |
911 | 0 | block->getQualifier().layoutSet != unitBlock->getQualifier().layoutSet) { |
912 | | // different block names likely means different blocks |
913 | 0 | return; |
914 | 0 | } |
915 | | |
916 | | // merge the struct |
917 | | // order of declarations doesn't matter and they matched based on member name |
918 | 0 | TTypeList* memberList = block->getType().getWritableStruct(); |
919 | 0 | TTypeList* unitMemberList = unitBlock->getType().getWritableStruct(); |
920 | | |
921 | | // keep track of which members have changed position |
922 | | // so we don't have to search the array again |
923 | 0 | std::map<unsigned int, unsigned int> memberIndexUpdates; |
924 | |
|
925 | 0 | size_t memberListStartSize = memberList->size(); |
926 | 0 | for (unsigned int i = 0; i < unitMemberList->size(); ++i) { |
927 | 0 | bool merge = true; |
928 | 0 | for (unsigned int j = 0; j < memberListStartSize; ++j) { |
929 | 0 | if ((*memberList)[j].type->getFieldName() == (*unitMemberList)[i].type->getFieldName()) { |
930 | 0 | merge = false; |
931 | 0 | const TType* memberType = (*memberList)[j].type; |
932 | 0 | const TType* unitMemberType = (*unitMemberList)[i].type; |
933 | | |
934 | | // compare types |
935 | | // don't need as many checks as when merging symbols, since |
936 | | // initializers and most qualifiers are stripped when the member is moved into the block |
937 | 0 | if ((*memberType) != (*unitMemberType)) { |
938 | 0 | error(infoSink, "Types must match:", unitBlock->getStage()); |
939 | 0 | infoSink.info << " " << memberType->getFieldName() << ": "; |
940 | 0 | infoSink.info << "\"" << memberType->getCompleteString() << "\" in stage " << StageName(block->getStage()) << " versus "; |
941 | 0 | infoSink.info << "\"" << unitMemberType->getCompleteString() << "\" in stage " << StageName(unitBlock->getStage()) << "\n"; |
942 | 0 | } |
943 | |
|
944 | 0 | memberIndexUpdates[i] = j; |
945 | 0 | } |
946 | 0 | } |
947 | 0 | if (merge) { |
948 | 0 | memberList->push_back((*unitMemberList)[i]); |
949 | 0 | memberIndexUpdates[i] = (unsigned int)memberList->size() - 1; |
950 | 0 | } |
951 | 0 | } |
952 | | |
953 | | // update symbol node in unit tree, |
954 | | // and other nodes that may reference it |
955 | 0 | class TMergeBlockTraverser : public TIntermTraverser { |
956 | 0 | public: |
957 | 0 | TMergeBlockTraverser(const TIntermSymbol* newSym) |
958 | 0 | : newSymbol(newSym), newType(nullptr), unit(nullptr), memberIndexUpdates(nullptr) |
959 | 0 | { |
960 | 0 | } |
961 | 0 | TMergeBlockTraverser(const TIntermSymbol* newSym, const glslang::TType* unitType, glslang::TIntermediate* unit, |
962 | 0 | const std::map<unsigned int, unsigned int>* memberIdxUpdates) |
963 | 0 | : TIntermTraverser(false, true), newSymbol(newSym), newType(unitType), unit(unit), memberIndexUpdates(memberIdxUpdates) |
964 | 0 | { |
965 | 0 | } |
966 | 0 | virtual ~TMergeBlockTraverser() {} |
967 | |
|
968 | 0 | const TIntermSymbol* newSymbol; |
969 | 0 | const glslang::TType* newType; // shallow copy of the new type |
970 | 0 | glslang::TIntermediate* unit; // intermediate that is being updated |
971 | 0 | const std::map<unsigned int, unsigned int>* memberIndexUpdates; |
972 | |
|
973 | 0 | virtual void visitSymbol(TIntermSymbol* symbol) |
974 | 0 | { |
975 | 0 | if (newSymbol->getAccessName() == symbol->getAccessName() && |
976 | 0 | newSymbol->getQualifier().getBlockStorage() == symbol->getQualifier().getBlockStorage()) { |
977 | | // Each symbol node may have a local copy of the block structure. |
978 | | // Update those structures to match the new one post-merge |
979 | 0 | *(symbol->getWritableType().getWritableStruct()) = *(newSymbol->getType().getStruct()); |
980 | 0 | } |
981 | 0 | } |
982 | |
|
983 | 0 | virtual bool visitBinary(TVisit, glslang::TIntermBinary* node) |
984 | 0 | { |
985 | 0 | if (!unit || !newType || !memberIndexUpdates || memberIndexUpdates->empty()) |
986 | 0 | return true; |
987 | | |
988 | 0 | if (node->getOp() == EOpIndexDirectStruct && node->getLeft()->getType() == *newType) { |
989 | | // this is a dereference to a member of the block since the |
990 | | // member list changed, need to update this to point to the |
991 | | // right index |
992 | 0 | assert(node->getRight()->getAsConstantUnion()); |
993 | |
|
994 | 0 | glslang::TIntermConstantUnion* constNode = node->getRight()->getAsConstantUnion(); |
995 | 0 | unsigned int memberIdx = constNode->getConstArray()[0].getUConst(); |
996 | 0 | unsigned int newIdx = memberIndexUpdates->at(memberIdx); |
997 | 0 | TIntermTyped* newConstNode = unit->addConstantUnion(newIdx, node->getRight()->getLoc()); |
998 | |
|
999 | 0 | node->setRight(newConstNode); |
1000 | 0 | delete constNode; |
1001 | |
|
1002 | 0 | return true; |
1003 | 0 | } |
1004 | 0 | return true; |
1005 | 0 | } |
1006 | 0 | }; |
1007 | | |
1008 | | // 'this' may have symbols that are using the old block structure, so traverse the tree to update those |
1009 | | // in 'visitSymbol' |
1010 | 0 | TMergeBlockTraverser finalLinkTraverser(block); |
1011 | 0 | getTreeRoot()->traverse(&finalLinkTraverser); |
1012 | | |
1013 | | // The 'unit' intermediate needs the block structures update, but also structure entry indices |
1014 | | // may have changed from the old block to the new one that it was merged into, so update those |
1015 | | // in 'visitBinary' |
1016 | 0 | TType newType; |
1017 | 0 | newType.shallowCopy(block->getType()); |
1018 | 0 | TMergeBlockTraverser unitFinalLinkTraverser(block, &newType, unit, &memberIndexUpdates); |
1019 | 0 | unit->getTreeRoot()->traverse(&unitFinalLinkTraverser); |
1020 | | |
1021 | | // update the member list |
1022 | 0 | (*unitMemberList) = (*memberList); |
1023 | 0 | } |
1024 | | |
1025 | | // |
1026 | | // Merge the linker objects from unitLinkerObjects into linkerObjects. |
1027 | | // Duplication is expected and filtered out, but contradictions are an error. |
1028 | | // |
1029 | | void TIntermediate::mergeLinkerObjects(TInfoSink& infoSink, TIntermSequence& linkerObjects, const TIntermSequence& unitLinkerObjects, EShLanguage unitStage) |
1030 | 0 | { |
1031 | | // Error check and merge the linker objects (duplicates should not be created) |
1032 | 0 | std::size_t initialNumLinkerObjects = linkerObjects.size(); |
1033 | 0 | for (unsigned int unitLinkObj = 0; unitLinkObj < unitLinkerObjects.size(); ++unitLinkObj) { |
1034 | 0 | bool merge = true; |
1035 | 0 | for (std::size_t linkObj = 0; linkObj < initialNumLinkerObjects; ++linkObj) { |
1036 | 0 | TIntermSymbol* symbol = linkerObjects[linkObj]->getAsSymbolNode(); |
1037 | 0 | TIntermSymbol* unitSymbol = unitLinkerObjects[unitLinkObj]->getAsSymbolNode(); |
1038 | 0 | assert(symbol && unitSymbol); |
1039 | |
|
1040 | 0 | if (isSameSymbol(symbol, unitSymbol)) { |
1041 | | // filter out copy |
1042 | 0 | merge = false; |
1043 | | |
1044 | | // but if one has an initializer and the other does not, update |
1045 | | // the initializer |
1046 | 0 | if (symbol->getConstArray().empty() && ! unitSymbol->getConstArray().empty()) |
1047 | 0 | symbol->setConstArray(unitSymbol->getConstArray()); |
1048 | | |
1049 | | // Similarly for binding |
1050 | 0 | if (! symbol->getQualifier().hasBinding() && unitSymbol->getQualifier().hasBinding()) |
1051 | 0 | symbol->getQualifier().layoutBinding = unitSymbol->getQualifier().layoutBinding; |
1052 | | |
1053 | | // Similarly for location |
1054 | 0 | if (!symbol->getQualifier().hasLocation() && unitSymbol->getQualifier().hasLocation()) { |
1055 | 0 | symbol->getQualifier().layoutLocation = unitSymbol->getQualifier().layoutLocation; |
1056 | 0 | } |
1057 | | |
1058 | | // Update implicit array sizes |
1059 | 0 | if (symbol->getWritableType().isImplicitlySizedArray() && unitSymbol->getType().isImplicitlySizedArray()) { |
1060 | 0 | if (unitSymbol->getType().getImplicitArraySize() > symbol->getType().getImplicitArraySize()){ |
1061 | 0 | symbol->getWritableType().updateImplicitArraySize(unitSymbol->getType().getImplicitArraySize()); |
1062 | 0 | } |
1063 | 0 | } |
1064 | 0 | else if (symbol->getWritableType().isImplicitlySizedArray() && unitSymbol->getType().isSizedArray()) { |
1065 | 0 | if (symbol->getWritableType().getImplicitArraySize() > unitSymbol->getType().getOuterArraySize()) |
1066 | 0 | error(infoSink, "Implicit size of unsized array doesn't match same symbol among multiple shaders.", unitStage); |
1067 | 0 | } |
1068 | 0 | else if (unitSymbol->getType().isImplicitlySizedArray() && symbol->getWritableType().isSizedArray()) { |
1069 | 0 | if (unitSymbol->getType().getImplicitArraySize() > symbol->getWritableType().getOuterArraySize()) |
1070 | 0 | error(infoSink, "Implicit size of unsized array doesn't match same symbol among multiple shaders.", unitStage); |
1071 | 0 | } |
1072 | |
|
1073 | 0 | if (symbol->getType().isStruct() && unitSymbol->getType().isStruct() && |
1074 | 0 | symbol->getType().getStruct()->size() == unitSymbol->getType().getStruct()->size()) { |
1075 | 0 | for (int i = 0; i < (int)symbol->getType().getStruct()->size(); ++i) { |
1076 | 0 | auto& type = (*symbol->getWritableType().getStruct())[i]; |
1077 | 0 | auto& unitType = (*unitSymbol->getWritableType().getStruct())[i]; |
1078 | |
|
1079 | 0 | if (type.type->isImplicitlySizedArray() && unitType.type->isImplicitlySizedArray()) { |
1080 | 0 | if (unitType.type->getImplicitArraySize() > type.type->getImplicitArraySize()) |
1081 | 0 | type.type->updateImplicitArraySize(unitType.type->getImplicitArraySize()); |
1082 | 0 | } |
1083 | 0 | else if (type.type->isImplicitlySizedArray() && unitType.type->isSizedArray()) { |
1084 | 0 | if (type.type->getImplicitArraySize() > unitType.type->getOuterArraySize()) |
1085 | 0 | error(infoSink, "Implicit size of unsized array doesn't match same symbol among multiple shaders.", unitStage); |
1086 | 0 | } |
1087 | 0 | else if (type.type->isSizedArray() && unitType.type->isImplicitlySizedArray()) { |
1088 | 0 | if (type.type->getOuterArraySize() < unitType.type->getImplicitArraySize()) |
1089 | 0 | error(infoSink, "Implicit size of unsized array doesn't match same symbol among multiple shaders.", unitStage); |
1090 | 0 | } |
1091 | 0 | } |
1092 | 0 | } |
1093 | | |
1094 | | // Update implicit array sizes |
1095 | 0 | mergeImplicitArraySizes(symbol->getWritableType(), unitSymbol->getType()); |
1096 | | |
1097 | | // Check for consistent types/qualification/initializers etc. |
1098 | 0 | mergeErrorCheck(infoSink, *symbol, *unitSymbol); |
1099 | 0 | } |
1100 | | // If different symbols, verify they arn't push_constant since there can only be one per stage |
1101 | 0 | else if (symbol->getQualifier().isPushConstant() && unitSymbol->getQualifier().isPushConstant() && getStage() == unitStage) |
1102 | 0 | error(infoSink, "Only one push_constant block is allowed per stage"); |
1103 | 0 | } |
1104 | | |
1105 | | // Check conflicts between preset primitives and sizes of I/O variables among multiple geometry shaders |
1106 | 0 | if (language == EShLangGeometry && unitStage == EShLangGeometry) |
1107 | 0 | { |
1108 | 0 | TIntermSymbol* unitSymbol = unitLinkerObjects[unitLinkObj]->getAsSymbolNode(); |
1109 | 0 | if (unitSymbol->isArray() && unitSymbol->getQualifier().storage == EvqVaryingIn && unitSymbol->getQualifier().builtIn == EbvNone) |
1110 | 0 | if ((unitSymbol->getArraySizes()->isImplicitlySized() && |
1111 | 0 | unitSymbol->getArraySizes()->getImplicitSize() != TQualifier::mapGeometryToSize(getInputPrimitive())) || |
1112 | 0 | (! unitSymbol->getArraySizes()->isImplicitlySized() && |
1113 | 0 | unitSymbol->getArraySizes()->getDimSize(0) != TQualifier::mapGeometryToSize(getInputPrimitive()))) |
1114 | 0 | error(infoSink, "Not all array sizes match across all geometry shaders in the program"); |
1115 | 0 | } |
1116 | |
|
1117 | 0 | if (merge) { |
1118 | 0 | linkerObjects.push_back(unitLinkerObjects[unitLinkObj]); |
1119 | | |
1120 | | // for anonymous blocks, check that their members don't conflict with other names |
1121 | 0 | if (unitLinkerObjects[unitLinkObj]->getAsSymbolNode()->getBasicType() == EbtBlock && |
1122 | 0 | IsAnonymous(unitLinkerObjects[unitLinkObj]->getAsSymbolNode()->getName())) { |
1123 | 0 | for (std::size_t linkObj = 0; linkObj < initialNumLinkerObjects; ++linkObj) { |
1124 | 0 | TIntermSymbol* symbol = linkerObjects[linkObj]->getAsSymbolNode(); |
1125 | 0 | TIntermSymbol* unitSymbol = unitLinkerObjects[unitLinkObj]->getAsSymbolNode(); |
1126 | 0 | assert(symbol && unitSymbol); |
1127 | |
|
1128 | 0 | auto checkName = [this, unitSymbol, &infoSink](const TString& name) { |
1129 | 0 | for (unsigned int i = 0; i < unitSymbol->getType().getStruct()->size(); ++i) { |
1130 | 0 | if (name == (*unitSymbol->getType().getStruct())[i].type->getFieldName() |
1131 | 0 | && !((*unitSymbol->getType().getStruct())[i].type->getQualifier().hasLocation() |
1132 | 0 | || unitSymbol->getType().getQualifier().hasLocation()) |
1133 | 0 | ) { |
1134 | 0 | error(infoSink, "Anonymous member name used for global variable or other anonymous member: "); |
1135 | 0 | infoSink.info << (*unitSymbol->getType().getStruct())[i].type->getCompleteString() << "\n"; |
1136 | 0 | } |
1137 | 0 | } |
1138 | 0 | }; |
1139 | |
|
1140 | 0 | if (isSameInterface(symbol, unitSymbol)) { |
1141 | 0 | checkName(symbol->getName()); |
1142 | | |
1143 | | // check members of other anonymous blocks |
1144 | 0 | if (symbol->getBasicType() == EbtBlock && IsAnonymous(symbol->getName())) { |
1145 | 0 | for (unsigned int i = 0; i < symbol->getType().getStruct()->size(); ++i) { |
1146 | 0 | checkName((*symbol->getType().getStruct())[i].type->getFieldName()); |
1147 | 0 | } |
1148 | 0 | } |
1149 | 0 | } |
1150 | 0 | } |
1151 | 0 | } |
1152 | 0 | } |
1153 | 0 | } |
1154 | 0 | } |
1155 | | |
1156 | | // TODO 4.5 link functionality: cull distance array size checking |
1157 | | |
1158 | | // Recursively merge the implicit array sizes through the objects' respective type trees. |
1159 | | void TIntermediate::mergeImplicitArraySizes(TType& type, const TType& unitType) |
1160 | 0 | { |
1161 | 0 | if (type.isUnsizedArray()) { |
1162 | 0 | if (unitType.isUnsizedArray()) { |
1163 | 0 | type.updateImplicitArraySize(unitType.getImplicitArraySize()); |
1164 | 0 | if (unitType.isArrayVariablyIndexed()) |
1165 | 0 | type.setArrayVariablyIndexed(); |
1166 | 0 | } else if (unitType.isSizedArray()) |
1167 | 0 | type.changeOuterArraySize(unitType.getOuterArraySize()); |
1168 | 0 | } |
1169 | | |
1170 | | // Type mismatches are caught and reported after this, just be careful for now. |
1171 | 0 | if (! type.isStruct() || ! unitType.isStruct() || type.getStruct()->size() != unitType.getStruct()->size()) |
1172 | 0 | return; |
1173 | | |
1174 | 0 | for (int i = 0; i < (int)type.getStruct()->size(); ++i) |
1175 | 0 | mergeImplicitArraySizes(*(*type.getStruct())[i].type, *(*unitType.getStruct())[i].type); |
1176 | 0 | } |
1177 | | |
1178 | | // |
1179 | | // Compare two global objects from two compilation units and see if they match |
1180 | | // well enough. Rules can be different for intra- vs. cross-stage matching. |
1181 | | // |
1182 | | // This function only does one of intra- or cross-stage matching per call. |
1183 | | // |
1184 | | void TIntermediate::mergeErrorCheck(TInfoSink& infoSink, const TIntermSymbol& symbol, const TIntermSymbol& unitSymbol) |
1185 | 0 | { |
1186 | 0 | EShLanguage stage = symbol.getStage(); |
1187 | 0 | EShLanguage unitStage = unitSymbol.getStage(); |
1188 | 0 | bool crossStage = stage != unitStage; |
1189 | 0 | bool writeTypeComparison = false; |
1190 | 0 | bool errorReported = false; |
1191 | 0 | bool printQualifiers = false; |
1192 | 0 | bool printPrecision = false; |
1193 | 0 | bool printType = false; |
1194 | | |
1195 | | // Types have to match |
1196 | 0 | { |
1197 | | // but, we make an exception if one is an implicit array and the other is sized |
1198 | | // or if the array sizes differ because of the extra array dimension on some in/out boundaries |
1199 | 0 | bool arraysMatch = false; |
1200 | 0 | if (isIoResizeArray(symbol.getType(), stage) || isIoResizeArray(unitSymbol.getType(), unitStage)) { |
1201 | | // if the arrays have an extra dimension because of the stage. |
1202 | | // compare dimensions while ignoring the outer dimension |
1203 | 0 | unsigned int firstDim = isIoResizeArray(symbol.getType(), stage) ? 1 : 0; |
1204 | 0 | unsigned int numDim = symbol.getArraySizes() |
1205 | 0 | ? symbol.getArraySizes()->getNumDims() : 0; |
1206 | 0 | unsigned int unitFirstDim = isIoResizeArray(unitSymbol.getType(), unitStage) ? 1 : 0; |
1207 | 0 | unsigned int unitNumDim = unitSymbol.getArraySizes() |
1208 | 0 | ? unitSymbol.getArraySizes()->getNumDims() : 0; |
1209 | 0 | arraysMatch = (numDim - firstDim) == (unitNumDim - unitFirstDim); |
1210 | | // check that array sizes match as well |
1211 | 0 | for (unsigned int i = 0; i < (numDim - firstDim) && arraysMatch; i++) { |
1212 | 0 | if (symbol.getArraySizes()->getDimSize(firstDim + i) != |
1213 | 0 | unitSymbol.getArraySizes()->getDimSize(unitFirstDim + i)) { |
1214 | 0 | arraysMatch = false; |
1215 | 0 | break; |
1216 | 0 | } |
1217 | 0 | } |
1218 | 0 | } |
1219 | 0 | else { |
1220 | 0 | arraysMatch = symbol.getType().sameArrayness(unitSymbol.getType()) || |
1221 | 0 | (symbol.getType().isArray() && unitSymbol.getType().isArray() && |
1222 | 0 | (symbol.getType().isImplicitlySizedArray() || unitSymbol.getType().isImplicitlySizedArray() || |
1223 | 0 | symbol.getType().isUnsizedArray() || unitSymbol.getType().isUnsizedArray())); |
1224 | 0 | } |
1225 | |
|
1226 | 0 | int lpidx = -1; |
1227 | 0 | int rpidx = -1; |
1228 | 0 | if (!symbol.getType().sameElementType(unitSymbol.getType(), &lpidx, &rpidx)) { |
1229 | 0 | if (lpidx >= 0 && rpidx >= 0) { |
1230 | 0 | error(infoSink, "Member names and types must match:", unitStage); |
1231 | 0 | infoSink.info << " Block: " << symbol.getType().getTypeName() << "\n"; |
1232 | 0 | infoSink.info << " " << StageName(stage) << " stage: \"" |
1233 | 0 | << (*symbol.getType().getStruct())[lpidx].type->getCompleteString(true, false, false, true, |
1234 | 0 | (*symbol.getType().getStruct())[lpidx].type->getFieldName()) << "\"\n"; |
1235 | 0 | infoSink.info << " " << StageName(unitStage) << " stage: \"" |
1236 | 0 | << (*unitSymbol.getType().getStruct())[rpidx].type->getCompleteString(true, false, false, true, |
1237 | 0 | (*unitSymbol.getType().getStruct())[rpidx].type->getFieldName()) << "\"\n"; |
1238 | 0 | errorReported = true; |
1239 | 0 | } else if (lpidx >= 0 && rpidx == -1) { |
1240 | 0 | TString errmsg = StageName(stage); |
1241 | 0 | errmsg.append(" block member has no corresponding member in ").append(StageName(unitStage)).append(" block:"); |
1242 | 0 | error(infoSink, errmsg.c_str(), unitStage); |
1243 | 0 | infoSink.info << " " << StageName(stage) << " stage: Block: " << symbol.getType().getTypeName() << ", Member: " |
1244 | 0 | << (*symbol.getType().getStruct())[lpidx].type->getFieldName() << "\n"; |
1245 | 0 | infoSink.info << " " << StageName(unitStage) << " stage: Block: " << unitSymbol.getType().getTypeName() << ", Member: n/a \n"; |
1246 | 0 | errorReported = true; |
1247 | 0 | } else if (lpidx == -1 && rpidx >= 0) { |
1248 | 0 | TString errmsg = StageName(unitStage); |
1249 | 0 | errmsg.append(" block member has no corresponding member in ").append(StageName(stage)).append(" block:"); |
1250 | 0 | error(infoSink, errmsg.c_str(), unitStage); |
1251 | 0 | infoSink.info << " " << StageName(unitStage) << " stage: Block: " << unitSymbol.getType().getTypeName() << ", Member: " |
1252 | 0 | << (*unitSymbol.getType().getStruct())[rpidx].type->getFieldName() << "\n"; |
1253 | 0 | infoSink.info << " " << StageName(stage) << " stage: Block: " << symbol.getType().getTypeName() << ", Member: n/a \n"; |
1254 | 0 | errorReported = true; |
1255 | 0 | } else { |
1256 | 0 | error(infoSink, "Types must match:", unitStage); |
1257 | 0 | writeTypeComparison = true; |
1258 | 0 | printType = true; |
1259 | 0 | } |
1260 | 0 | } else if (!arraysMatch) { |
1261 | 0 | error(infoSink, "Array sizes must be compatible:", unitStage); |
1262 | 0 | writeTypeComparison = true; |
1263 | 0 | printType = true; |
1264 | 0 | } else if (!symbol.getType().sameTypeParameters(unitSymbol.getType())) { |
1265 | 0 | error(infoSink, "Type parameters must match:", unitStage); |
1266 | 0 | writeTypeComparison = true; |
1267 | 0 | printType = true; |
1268 | 0 | } |
1269 | 0 | } |
1270 | | |
1271 | | // Interface block member-wise layout qualifiers have to match |
1272 | 0 | if (symbol.getType().getBasicType() == EbtBlock && unitSymbol.getType().getBasicType() == EbtBlock && |
1273 | 0 | symbol.getType().getStruct() && unitSymbol.getType().getStruct() && |
1274 | 0 | symbol.getType().sameStructType(unitSymbol.getType())) { |
1275 | 0 | unsigned int li = 0; |
1276 | 0 | unsigned int ri = 0; |
1277 | 0 | while (li < symbol.getType().getStruct()->size() && ri < unitSymbol.getType().getStruct()->size()) { |
1278 | 0 | if ((*symbol.getType().getStruct())[li].type->hiddenMember()) { |
1279 | 0 | ++li; |
1280 | 0 | continue; |
1281 | 0 | } |
1282 | 0 | if ((*unitSymbol.getType().getStruct())[ri].type->hiddenMember()) { |
1283 | 0 | ++ri; |
1284 | 0 | continue; |
1285 | 0 | } |
1286 | 0 | const TQualifier& qualifier = (*symbol.getType().getStruct())[li].type->getQualifier(); |
1287 | 0 | const TQualifier & unitQualifier = (*unitSymbol.getType().getStruct())[ri].type->getQualifier(); |
1288 | 0 | bool layoutQualifierError = false; |
1289 | 0 | if (qualifier.layoutMatrix != unitQualifier.layoutMatrix) { |
1290 | 0 | error(infoSink, "Interface block member layout matrix qualifier must match:", unitStage); |
1291 | 0 | layoutQualifierError = true; |
1292 | 0 | } |
1293 | 0 | if (qualifier.layoutOffset != unitQualifier.layoutOffset) { |
1294 | 0 | error(infoSink, "Interface block member layout offset qualifier must match:", unitStage); |
1295 | 0 | layoutQualifierError = true; |
1296 | 0 | } |
1297 | 0 | if (qualifier.layoutAlign != unitQualifier.layoutAlign) { |
1298 | 0 | error(infoSink, "Interface block member layout align qualifier must match:", unitStage); |
1299 | 0 | layoutQualifierError = true; |
1300 | 0 | } |
1301 | 0 | if (qualifier.layoutLocation != unitQualifier.layoutLocation) { |
1302 | 0 | error(infoSink, "Interface block member layout location qualifier must match:", unitStage); |
1303 | 0 | layoutQualifierError = true; |
1304 | 0 | } |
1305 | 0 | if (qualifier.layoutComponent != unitQualifier.layoutComponent) { |
1306 | 0 | error(infoSink, "Interface block member layout component qualifier must match:", unitStage); |
1307 | 0 | layoutQualifierError = true; |
1308 | 0 | } |
1309 | 0 | if (layoutQualifierError) { |
1310 | 0 | infoSink.info << " " << StageName(stage) << " stage: Block: " << symbol.getType().getTypeName() << ", Member: " |
1311 | 0 | << (*symbol.getType().getStruct())[li].type->getFieldName() << " \"" |
1312 | 0 | << (*symbol.getType().getStruct())[li].type->getCompleteString(true, true, false, false) << "\"\n"; |
1313 | 0 | infoSink.info << " " << StageName(unitStage) << " stage: Block: " << unitSymbol.getType().getTypeName() << ", Member: " |
1314 | 0 | << (*unitSymbol.getType().getStruct())[ri].type->getFieldName() << " \"" |
1315 | 0 | << (*unitSymbol.getType().getStruct())[ri].type->getCompleteString(true, true, false, false) << "\"\n"; |
1316 | 0 | errorReported = true; |
1317 | 0 | } |
1318 | 0 | ++li; |
1319 | 0 | ++ri; |
1320 | 0 | } |
1321 | 0 | } |
1322 | |
|
1323 | 0 | bool isInOut = crossStage && |
1324 | 0 | ((symbol.getQualifier().storage == EvqVaryingIn && unitSymbol.getQualifier().storage == EvqVaryingOut) || |
1325 | 0 | (symbol.getQualifier().storage == EvqVaryingOut && unitSymbol.getQualifier().storage == EvqVaryingIn)); |
1326 | | |
1327 | | // Qualifiers have to (almost) match |
1328 | | // Storage... |
1329 | 0 | if (!isInOut && symbol.getQualifier().storage != unitSymbol.getQualifier().storage) { |
1330 | 0 | error(infoSink, "Storage qualifiers must match:", unitStage); |
1331 | 0 | writeTypeComparison = true; |
1332 | 0 | printQualifiers = true; |
1333 | 0 | } |
1334 | | |
1335 | | // Uniform and buffer blocks must either both have an instance name, or |
1336 | | // must both be anonymous. The names don't need to match though. |
1337 | 0 | if (symbol.getQualifier().isUniformOrBuffer() && |
1338 | 0 | (IsAnonymous(symbol.getName()) != IsAnonymous(unitSymbol.getName()))) { |
1339 | 0 | error(infoSink, "Matched Uniform or Storage blocks must all be anonymous," |
1340 | 0 | " or all be named:", unitStage); |
1341 | 0 | writeTypeComparison = true; |
1342 | 0 | } |
1343 | |
|
1344 | 0 | if (symbol.getQualifier().storage == unitSymbol.getQualifier().storage && |
1345 | 0 | (IsAnonymous(symbol.getName()) != IsAnonymous(unitSymbol.getName()) || |
1346 | 0 | (!IsAnonymous(symbol.getName()) && symbol.getName() != unitSymbol.getName()))) { |
1347 | 0 | warn(infoSink, "Matched shader interfaces are using different instance names.", unitStage); |
1348 | 0 | writeTypeComparison = true; |
1349 | 0 | } |
1350 | | |
1351 | | // Precision... |
1352 | 0 | if (!isInOut && symbol.getQualifier().precision != unitSymbol.getQualifier().precision) { |
1353 | 0 | error(infoSink, "Precision qualifiers must match:", unitStage); |
1354 | 0 | writeTypeComparison = true; |
1355 | 0 | printPrecision = true; |
1356 | 0 | } |
1357 | | |
1358 | | // Invariance... |
1359 | 0 | if (! crossStage && symbol.getQualifier().invariant != unitSymbol.getQualifier().invariant) { |
1360 | 0 | error(infoSink, "Presence of invariant qualifier must match:", unitStage); |
1361 | 0 | writeTypeComparison = true; |
1362 | 0 | printQualifiers = true; |
1363 | 0 | } |
1364 | | |
1365 | | // Precise... |
1366 | 0 | if (! crossStage && symbol.getQualifier().isNoContraction() != unitSymbol.getQualifier().isNoContraction()) { |
1367 | 0 | error(infoSink, "Presence of precise qualifier must match:", unitStage); |
1368 | 0 | writeTypeComparison = true; |
1369 | 0 | printPrecision = true; |
1370 | 0 | } |
1371 | | |
1372 | | // Auxiliary and interpolation... |
1373 | | // "interpolation qualification (e.g., flat) and auxiliary qualification (e.g. centroid) may differ. |
1374 | | // These mismatches are allowed between any pair of stages ... |
1375 | | // those provided in the fragment shader supersede those provided in previous stages." |
1376 | 0 | if (!crossStage && |
1377 | 0 | (symbol.getQualifier().centroid != unitSymbol.getQualifier().centroid || |
1378 | 0 | symbol.getQualifier().smooth != unitSymbol.getQualifier().smooth || |
1379 | 0 | symbol.getQualifier().flat != unitSymbol.getQualifier().flat || |
1380 | 0 | symbol.getQualifier().isSample()!= unitSymbol.getQualifier().isSample() || |
1381 | 0 | symbol.getQualifier().isPatch() != unitSymbol.getQualifier().isPatch() || |
1382 | 0 | symbol.getQualifier().isNonPerspective() != unitSymbol.getQualifier().isNonPerspective())) { |
1383 | 0 | error(infoSink, "Interpolation and auxiliary storage qualifiers must match:", unitStage); |
1384 | 0 | writeTypeComparison = true; |
1385 | 0 | printQualifiers = true; |
1386 | 0 | } |
1387 | | |
1388 | | // Memory... |
1389 | 0 | bool memoryQualifierError = false; |
1390 | 0 | if (symbol.getQualifier().coherent != unitSymbol.getQualifier().coherent) { |
1391 | 0 | error(infoSink, "Memory coherent qualifier must match:", unitStage); |
1392 | 0 | memoryQualifierError = true; |
1393 | 0 | } |
1394 | 0 | if (symbol.getQualifier().devicecoherent != unitSymbol.getQualifier().devicecoherent) { |
1395 | 0 | error(infoSink, "Memory devicecoherent qualifier must match:", unitStage); |
1396 | 0 | memoryQualifierError = true; |
1397 | 0 | } |
1398 | 0 | if (symbol.getQualifier().queuefamilycoherent != unitSymbol.getQualifier().queuefamilycoherent) { |
1399 | 0 | error(infoSink, "Memory queuefamilycoherent qualifier must match:", unitStage); |
1400 | 0 | memoryQualifierError = true; |
1401 | 0 | } |
1402 | 0 | if (symbol.getQualifier().workgroupcoherent != unitSymbol.getQualifier().workgroupcoherent) { |
1403 | 0 | error(infoSink, "Memory workgroupcoherent qualifier must match:", unitStage); |
1404 | 0 | memoryQualifierError = true; |
1405 | 0 | } |
1406 | 0 | if (symbol.getQualifier().subgroupcoherent != unitSymbol.getQualifier().subgroupcoherent) { |
1407 | 0 | error(infoSink, "Memory subgroupcoherent qualifier must match:", unitStage); |
1408 | 0 | memoryQualifierError = true; |
1409 | 0 | } |
1410 | 0 | if (symbol.getQualifier().shadercallcoherent != unitSymbol.getQualifier().shadercallcoherent) { |
1411 | 0 | error(infoSink, "Memory shadercallcoherent qualifier must match:", unitStage); |
1412 | 0 | memoryQualifierError = true; |
1413 | 0 | } |
1414 | 0 | if (symbol.getQualifier().nonprivate != unitSymbol.getQualifier().nonprivate) { |
1415 | 0 | error(infoSink, "Memory nonprivate qualifier must match:", unitStage); |
1416 | 0 | memoryQualifierError = true; |
1417 | 0 | } |
1418 | 0 | if (symbol.getQualifier().volatil != unitSymbol.getQualifier().volatil) { |
1419 | 0 | error(infoSink, "Memory volatil qualifier must match:", unitStage); |
1420 | 0 | memoryQualifierError = true; |
1421 | 0 | } |
1422 | 0 | if (symbol.getQualifier().nontemporal != unitSymbol.getQualifier().nontemporal) { |
1423 | 0 | error(infoSink, "Memory nontemporal qualifier must match:", unitStage); |
1424 | 0 | memoryQualifierError = true; |
1425 | 0 | } |
1426 | 0 | if (symbol.getQualifier().restrict != unitSymbol.getQualifier().restrict) { |
1427 | 0 | error(infoSink, "Memory restrict qualifier must match:", unitStage); |
1428 | 0 | memoryQualifierError = true; |
1429 | 0 | } |
1430 | 0 | if (symbol.getQualifier().readonly != unitSymbol.getQualifier().readonly) { |
1431 | 0 | error(infoSink, "Memory readonly qualifier must match:", unitStage); |
1432 | 0 | memoryQualifierError = true; |
1433 | 0 | } |
1434 | 0 | if (symbol.getQualifier().writeonly != unitSymbol.getQualifier().writeonly) { |
1435 | 0 | error(infoSink, "Memory writeonly qualifier must match:", unitStage); |
1436 | 0 | memoryQualifierError = true; |
1437 | 0 | } |
1438 | 0 | if (memoryQualifierError) { |
1439 | 0 | writeTypeComparison = true; |
1440 | 0 | printQualifiers = true; |
1441 | 0 | } |
1442 | | |
1443 | | // Layouts... |
1444 | | // TODO: 4.4 enhanced layouts: Generalize to include offset/align: current spec |
1445 | | // requires separate user-supplied offset from actual computed offset, but |
1446 | | // current implementation only has one offset. |
1447 | 0 | bool layoutQualifierError = false; |
1448 | 0 | if (symbol.getQualifier().layoutMatrix != unitSymbol.getQualifier().layoutMatrix) { |
1449 | 0 | error(infoSink, "Layout matrix qualifier must match:", unitStage); |
1450 | 0 | layoutQualifierError = true; |
1451 | 0 | } |
1452 | 0 | if (symbol.getQualifier().layoutPacking != unitSymbol.getQualifier().layoutPacking) { |
1453 | 0 | error(infoSink, "Layout packing qualifier must match:", unitStage); |
1454 | 0 | layoutQualifierError = true; |
1455 | 0 | } |
1456 | 0 | if (symbol.getQualifier().hasLocation() && unitSymbol.getQualifier().hasLocation() && symbol.getQualifier().layoutLocation != unitSymbol.getQualifier().layoutLocation) { |
1457 | 0 | error(infoSink, "Layout location qualifier must match:", unitStage); |
1458 | 0 | layoutQualifierError = true; |
1459 | 0 | } |
1460 | 0 | if (symbol.getQualifier().layoutComponent != unitSymbol.getQualifier().layoutComponent) { |
1461 | 0 | error(infoSink, "Layout component qualifier must match:", unitStage); |
1462 | 0 | layoutQualifierError = true; |
1463 | 0 | } |
1464 | 0 | if (symbol.getQualifier().layoutIndex != unitSymbol.getQualifier().layoutIndex) { |
1465 | 0 | error(infoSink, "Layout index qualifier must match:", unitStage); |
1466 | 0 | layoutQualifierError = true; |
1467 | 0 | } |
1468 | 0 | if (symbol.getQualifier().hasBinding() && unitSymbol.getQualifier().hasBinding() && symbol.getQualifier().layoutBinding != unitSymbol.getQualifier().layoutBinding) { |
1469 | 0 | error(infoSink, "Layout binding qualifier must match:", unitStage); |
1470 | 0 | layoutQualifierError = true; |
1471 | 0 | } |
1472 | 0 | if (symbol.getQualifier().hasBinding() && (symbol.getQualifier().layoutOffset != unitSymbol.getQualifier().layoutOffset)) { |
1473 | 0 | error(infoSink, "Layout offset qualifier must match:", unitStage); |
1474 | 0 | layoutQualifierError = true; |
1475 | 0 | } |
1476 | 0 | if (layoutQualifierError) { |
1477 | 0 | writeTypeComparison = true; |
1478 | 0 | printQualifiers = true; |
1479 | 0 | } |
1480 | | |
1481 | | // Initializers have to match, if both are present, and if we don't already know the types don't match |
1482 | 0 | if (! writeTypeComparison && ! errorReported) { |
1483 | 0 | if (! symbol.getConstArray().empty() && ! unitSymbol.getConstArray().empty()) { |
1484 | 0 | if (symbol.getConstArray() != unitSymbol.getConstArray()) { |
1485 | 0 | error(infoSink, "Initializers must match:", unitStage); |
1486 | 0 | infoSink.info << " " << symbol.getName() << "\n"; |
1487 | 0 | } |
1488 | 0 | } |
1489 | 0 | } |
1490 | |
|
1491 | 0 | if (writeTypeComparison) { |
1492 | 0 | if (symbol.getType().getBasicType() == EbtBlock && unitSymbol.getType().getBasicType() == EbtBlock && |
1493 | 0 | symbol.getType().getStruct() && unitSymbol.getType().getStruct()) { |
1494 | 0 | if (printType) { |
1495 | 0 | infoSink.info << " " << StageName(stage) << " stage: \"" << symbol.getType().getCompleteString(true, printQualifiers, printPrecision, |
1496 | 0 | printType, symbol.getName(), symbol.getType().getTypeName()) << "\"\n"; |
1497 | 0 | infoSink.info << " " << StageName(unitStage) << " stage: \"" << unitSymbol.getType().getCompleteString(true, printQualifiers, printPrecision, |
1498 | 0 | printType, unitSymbol.getName(), unitSymbol.getType().getTypeName()) << "\"\n"; |
1499 | 0 | } else { |
1500 | 0 | infoSink.info << " " << StageName(stage) << " stage: Block: " << symbol.getType().getTypeName() << " Instance: " << symbol.getName() |
1501 | 0 | << ": \"" << symbol.getType().getCompleteString(true, printQualifiers, printPrecision, printType) << "\"\n"; |
1502 | 0 | infoSink.info << " " << StageName(unitStage) << " stage: Block: " << unitSymbol.getType().getTypeName() << " Instance: " << unitSymbol.getName() |
1503 | 0 | << ": \"" << unitSymbol.getType().getCompleteString(true, printQualifiers, printPrecision, printType) << "\"\n"; |
1504 | 0 | } |
1505 | 0 | } else { |
1506 | 0 | if (printType) { |
1507 | 0 | infoSink.info << " " << StageName(stage) << " stage: \"" |
1508 | 0 | << symbol.getType().getCompleteString(true, printQualifiers, printPrecision, printType, symbol.getName()) << "\"\n"; |
1509 | 0 | infoSink.info << " " << StageName(unitStage) << " stage: \"" |
1510 | 0 | << unitSymbol.getType().getCompleteString(true, printQualifiers, printPrecision, printType, unitSymbol.getName()) << "\"\n"; |
1511 | 0 | } else { |
1512 | 0 | infoSink.info << " " << StageName(stage) << " stage: " << symbol.getName() << " \"" |
1513 | 0 | << symbol.getType().getCompleteString(true, printQualifiers, printPrecision, printType) << "\"\n"; |
1514 | 0 | infoSink.info << " " << StageName(unitStage) << " stage: " << unitSymbol.getName() << " \"" |
1515 | 0 | << unitSymbol.getType().getCompleteString(true, printQualifiers, printPrecision, printType) << "\"\n"; |
1516 | 0 | } |
1517 | 0 | } |
1518 | 0 | } |
1519 | 0 | } |
1520 | | |
1521 | | void TIntermediate::sharedBlockCheck(TInfoSink& infoSink) |
1522 | 165 | { |
1523 | 165 | bool has_shared_block = false; |
1524 | 165 | bool has_shared_non_block = false; |
1525 | 165 | TIntermSequence& linkObjects = findLinkerObjects()->getSequence(); |
1526 | 387 | for (size_t i = 0; i < linkObjects.size(); ++i) { |
1527 | 222 | const TType& type = linkObjects[i]->getAsTyped()->getType(); |
1528 | 222 | const TQualifier& qualifier = type.getQualifier(); |
1529 | 222 | if (qualifier.storage == glslang::EvqShared) { |
1530 | 24 | if (type.getBasicType() == glslang::EbtBlock) |
1531 | 0 | has_shared_block = true; |
1532 | 24 | else |
1533 | 24 | has_shared_non_block = true; |
1534 | 24 | } |
1535 | 222 | } |
1536 | 165 | if (has_shared_block && has_shared_non_block) |
1537 | 0 | error(infoSink, "cannot mix use of shared variables inside and outside blocks"); |
1538 | 165 | } |
1539 | | |
1540 | | // |
1541 | | // Do final link-time error checking of a complete (merged) intermediate representation. |
1542 | | // (Much error checking was done during merging). |
1543 | | // |
1544 | | // Also, lock in defaults of things not set. |
1545 | | // Defer adopting implicit array sizes to later, after all stages are merged. |
1546 | | // |
1547 | | void TIntermediate::finalCheck(TInfoSink& infoSink, bool keepUncalled) |
1548 | 4.86k | { |
1549 | 4.86k | if (getTreeRoot() == nullptr) |
1550 | 0 | return; |
1551 | | |
1552 | 4.86k | if (numEntryPoints < 1) { |
1553 | 2.03k | if (getSource() == EShSourceGlsl) |
1554 | 1.04k | error(infoSink, "Missing entry point: Each stage requires one entry point"); |
1555 | 988 | else |
1556 | 988 | warn(infoSink, "Entry point not found"); |
1557 | 2.03k | } |
1558 | | |
1559 | | // recursion and missing body checking |
1560 | 4.86k | checkCallGraphCycles(infoSink); |
1561 | 4.86k | checkCallGraphBodies(infoSink, keepUncalled); |
1562 | | |
1563 | | // overlap/alias/missing I/O, etc. |
1564 | 4.86k | inOutLocationCheck(infoSink); |
1565 | | |
1566 | 4.86k | if (getNumPushConstants() > 1) |
1567 | 0 | error(infoSink, "Only one push_constant block is allowed per stage"); |
1568 | | |
1569 | | // invocations |
1570 | 4.86k | if (invocations == TQualifier::layoutNotSet) |
1571 | 4.86k | invocations = 1; |
1572 | | |
1573 | 4.86k | if (inIoAccessed("gl_ClipDistance") && inIoAccessed("gl_ClipVertex")) |
1574 | 0 | error(infoSink, "Can only use one of gl_ClipDistance or gl_ClipVertex (gl_ClipDistance is preferred)"); |
1575 | 4.86k | if (inIoAccessed("gl_CullDistance") && inIoAccessed("gl_ClipVertex")) |
1576 | 0 | error(infoSink, "Can only use one of gl_CullDistance or gl_ClipVertex (gl_ClipDistance is preferred)"); |
1577 | | |
1578 | 4.86k | if (userOutputUsed() && (inIoAccessed("gl_FragColor") || inIoAccessed("gl_FragData"))) |
1579 | 0 | error(infoSink, "Cannot use gl_FragColor or gl_FragData when using user-defined outputs"); |
1580 | 4.86k | if (inIoAccessed("gl_FragColor") && inIoAccessed("gl_FragData")) |
1581 | 0 | error(infoSink, "Cannot use both gl_FragColor and gl_FragData"); |
1582 | | |
1583 | 77.7k | for (size_t b = 0; b < xfbBuffers.size(); ++b) { |
1584 | 72.9k | if (xfbBuffers[b].contains64BitType) |
1585 | 0 | RoundToPow2(xfbBuffers[b].implicitStride, 8); |
1586 | 72.9k | else if (xfbBuffers[b].contains32BitType) |
1587 | 0 | RoundToPow2(xfbBuffers[b].implicitStride, 4); |
1588 | 72.9k | else if (xfbBuffers[b].contains16BitType) |
1589 | 0 | RoundToPow2(xfbBuffers[b].implicitStride, 2); |
1590 | | |
1591 | | // "It is a compile-time or link-time error to have |
1592 | | // any xfb_offset that overflows xfb_stride, whether stated on declarations before or after the xfb_stride, or |
1593 | | // in different compilation units. While xfb_stride can be declared multiple times for the same buffer, it is a |
1594 | | // compile-time or link-time error to have different values specified for the stride for the same buffer." |
1595 | 72.9k | if (xfbBuffers[b].stride != TQualifier::layoutXfbStrideEnd && xfbBuffers[b].implicitStride > xfbBuffers[b].stride) { |
1596 | 0 | error(infoSink, "xfb_stride is too small to hold all buffer entries:"); |
1597 | 0 | infoSink.info.prefix(EPrefixError); |
1598 | 0 | infoSink.info << " xfb_buffer " << (unsigned int)b << ", xfb_stride " << xfbBuffers[b].stride << ", minimum stride needed: " << xfbBuffers[b].implicitStride << "\n"; |
1599 | 0 | } |
1600 | 72.9k | if (xfbBuffers[b].stride == TQualifier::layoutXfbStrideEnd) |
1601 | 72.9k | xfbBuffers[b].stride = xfbBuffers[b].implicitStride; |
1602 | | |
1603 | | // "If the buffer is capturing any |
1604 | | // outputs with double-precision or 64-bit integer components, the stride must be a multiple of 8, otherwise it must be a |
1605 | | // multiple of 4, or a compile-time or link-time error results." |
1606 | 72.9k | if (xfbBuffers[b].contains64BitType && ! IsMultipleOfPow2(xfbBuffers[b].stride, 8)) { |
1607 | 0 | error(infoSink, "xfb_stride must be multiple of 8 for buffer holding a double or 64-bit integer:"); |
1608 | 0 | infoSink.info.prefix(EPrefixError); |
1609 | 0 | infoSink.info << " xfb_buffer " << (unsigned int)b << ", xfb_stride " << xfbBuffers[b].stride << "\n"; |
1610 | 72.9k | } else if (xfbBuffers[b].contains32BitType && ! IsMultipleOfPow2(xfbBuffers[b].stride, 4)) { |
1611 | 0 | error(infoSink, "xfb_stride must be multiple of 4:"); |
1612 | 0 | infoSink.info.prefix(EPrefixError); |
1613 | 0 | infoSink.info << " xfb_buffer " << (unsigned int)b << ", xfb_stride " << xfbBuffers[b].stride << "\n"; |
1614 | 0 | } |
1615 | | // "If the buffer is capturing any |
1616 | | // outputs with half-precision or 16-bit integer components, the stride must be a multiple of 2" |
1617 | 72.9k | else if (xfbBuffers[b].contains16BitType && ! IsMultipleOfPow2(xfbBuffers[b].stride, 2)) { |
1618 | 0 | error(infoSink, "xfb_stride must be multiple of 2 for buffer holding a half float or 16-bit integer:"); |
1619 | 0 | infoSink.info.prefix(EPrefixError); |
1620 | 0 | infoSink.info << " xfb_buffer " << (unsigned int)b << ", xfb_stride " << xfbBuffers[b].stride << "\n"; |
1621 | 0 | } |
1622 | | |
1623 | | // "The resulting stride (implicit or explicit), when divided by 4, must be less than or equal to the |
1624 | | // implementation-dependent constant gl_MaxTransformFeedbackInterleavedComponents." |
1625 | 72.9k | if (xfbBuffers[b].stride > (unsigned int)(4 * resources->maxTransformFeedbackInterleavedComponents)) { |
1626 | 0 | error(infoSink, "xfb_stride is too large:"); |
1627 | 0 | infoSink.info.prefix(EPrefixError); |
1628 | 0 | infoSink.info << " xfb_buffer " << (unsigned int)b << ", components (1/4 stride) needed are " << xfbBuffers[b].stride/4 << ", gl_MaxTransformFeedbackInterleavedComponents is " << resources->maxTransformFeedbackInterleavedComponents << "\n"; |
1629 | 0 | } |
1630 | 72.9k | } |
1631 | | |
1632 | 4.86k | switch (language) { |
1633 | 3.55k | case EShLangVertex: |
1634 | 3.55k | break; |
1635 | 51 | case EShLangTessControl: |
1636 | 51 | if (vertices == TQualifier::layoutNotSet) |
1637 | 51 | error(infoSink, "At least one shader must specify an output layout(vertices=...)"); |
1638 | 51 | break; |
1639 | 35 | case EShLangTessEvaluation: |
1640 | 35 | if (getSource() == EShSourceGlsl) { |
1641 | 3 | if (inputPrimitive == ElgNone) |
1642 | 3 | error(infoSink, "At least one shader must specify an input layout primitive"); |
1643 | 3 | if (vertexSpacing == EvsNone) |
1644 | 3 | vertexSpacing = EvsEqual; |
1645 | 3 | if (vertexOrder == EvoNone) |
1646 | 3 | vertexOrder = EvoCcw; |
1647 | 3 | } |
1648 | 35 | break; |
1649 | 19 | case EShLangGeometry: |
1650 | 19 | if (inputPrimitive == ElgNone) |
1651 | 19 | error(infoSink, "At least one shader must specify an input layout primitive"); |
1652 | 19 | if (outputPrimitive == ElgNone) |
1653 | 19 | error(infoSink, "At least one shader must specify an output layout primitive"); |
1654 | 19 | if (vertices == TQualifier::layoutNotSet) |
1655 | 19 | error(infoSink, "At least one shader must specify a layout(max_vertices = value)"); |
1656 | 19 | break; |
1657 | 410 | case EShLangFragment: |
1658 | | // for GL_ARB_post_depth_coverage, EarlyFragmentTest is set automatically in |
1659 | | // ParseHelper.cpp. So if we reach here, this must be GL_EXT_post_depth_coverage |
1660 | | // requiring explicit early_fragment_tests |
1661 | 410 | if (getPostDepthCoverage() && !getEarlyFragmentTests()) |
1662 | 2 | error(infoSink, "post_depth_coverage requires early_fragment_tests"); |
1663 | 410 | break; |
1664 | 95 | case EShLangCompute: |
1665 | 95 | sharedBlockCheck(infoSink); |
1666 | 95 | break; |
1667 | 255 | case EShLangRayGen: |
1668 | 311 | case EShLangIntersect: |
1669 | 376 | case EShLangAnyHit: |
1670 | 517 | case EShLangClosestHit: |
1671 | 605 | case EShLangMiss: |
1672 | 625 | case EShLangCallable: |
1673 | 625 | if (numShaderRecordBlocks > 1) |
1674 | 0 | error(infoSink, "Only one shaderRecordNV buffer block is allowed per stage"); |
1675 | 625 | break; |
1676 | 12 | case EShLangMesh: |
1677 | | // NV_mesh_shader doesn't allow use of both single-view and per-view builtins. |
1678 | 12 | if (inIoAccessed("gl_Position") && inIoAccessed("gl_PositionPerViewNV")) |
1679 | 0 | error(infoSink, "Can only use one of gl_Position or gl_PositionPerViewNV"); |
1680 | 12 | if (inIoAccessed("gl_ClipDistance") && inIoAccessed("gl_ClipDistancePerViewNV")) |
1681 | 0 | error(infoSink, "Can only use one of gl_ClipDistance or gl_ClipDistancePerViewNV"); |
1682 | 12 | if (inIoAccessed("gl_CullDistance") && inIoAccessed("gl_CullDistancePerViewNV")) |
1683 | 0 | error(infoSink, "Can only use one of gl_CullDistance or gl_CullDistancePerViewNV"); |
1684 | 12 | if (inIoAccessed("gl_Layer") && inIoAccessed("gl_LayerPerViewNV")) |
1685 | 0 | error(infoSink, "Can only use one of gl_Layer or gl_LayerPerViewNV"); |
1686 | 12 | if (inIoAccessed("gl_ViewportMask") && inIoAccessed("gl_ViewportMaskPerViewNV")) |
1687 | 0 | error(infoSink, "Can only use one of gl_ViewportMask or gl_ViewportMaskPerViewNV"); |
1688 | 12 | if (outputPrimitive == ElgNone) |
1689 | 12 | error(infoSink, "At least one shader must specify an output layout primitive"); |
1690 | 12 | if (vertices == TQualifier::layoutNotSet) |
1691 | 12 | error(infoSink, "At least one shader must specify a layout(max_vertices = value)"); |
1692 | 12 | if (primitives == TQualifier::layoutNotSet) |
1693 | 12 | error(infoSink, "At least one shader must specify a layout(max_primitives = value)"); |
1694 | 12 | [[fallthrough]]; |
1695 | 70 | case EShLangTask: |
1696 | 70 | if (numTaskNVBlocks > 1) |
1697 | 0 | error(infoSink, "Only one taskNV interface block is allowed per shader"); |
1698 | 70 | if (numTaskEXTPayloads > 1) |
1699 | 0 | error(infoSink, "Only single variable of type taskPayloadSharedEXT is allowed per shader"); |
1700 | 70 | sharedBlockCheck(infoSink); |
1701 | 70 | break; |
1702 | 0 | default: |
1703 | 0 | error(infoSink, "Unknown Stage."); |
1704 | 0 | break; |
1705 | 4.86k | } |
1706 | 4.86k | } |
1707 | | |
1708 | | // |
1709 | | // See if the call graph contains any static recursion, which is disallowed |
1710 | | // by the specification. |
1711 | | // |
1712 | | void TIntermediate::checkCallGraphCycles(TInfoSink& infoSink) |
1713 | 4.86k | { |
1714 | | // Clear fields we'll use for this. |
1715 | 8.36k | for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { |
1716 | 3.50k | call->visited = false; |
1717 | 3.50k | call->currentPath = false; |
1718 | 3.50k | call->errorGiven = false; |
1719 | 3.50k | } |
1720 | | |
1721 | | // |
1722 | | // Loop, looking for a new connected subgraph. One subgraph is handled per loop iteration. |
1723 | | // |
1724 | | |
1725 | 4.86k | TCall* newRoot; |
1726 | 7.34k | do { |
1727 | | // See if we have unvisited parts of the graph. |
1728 | 7.34k | newRoot = nullptr; |
1729 | 26.8k | for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { |
1730 | 21.9k | if (! call->visited) { |
1731 | 2.48k | newRoot = &(*call); |
1732 | 2.48k | break; |
1733 | 2.48k | } |
1734 | 21.9k | } |
1735 | | |
1736 | | // If not, we are done. |
1737 | 7.34k | if (! newRoot) |
1738 | 4.86k | break; |
1739 | | |
1740 | | // Otherwise, we found a new subgraph, process it: |
1741 | | // See what all can be reached by this new root, and if any of |
1742 | | // that is recursive. This is done by depth-first traversals, seeing |
1743 | | // if a new call is found that was already in the currentPath (a back edge), |
1744 | | // thereby detecting recursion. |
1745 | 2.48k | std::list<TCall*> stack; |
1746 | 2.48k | newRoot->currentPath = true; // currentPath will be true iff it is on the stack |
1747 | 2.48k | stack.push_back(newRoot); |
1748 | 7.01k | while (! stack.empty()) { |
1749 | | // get a caller |
1750 | 4.52k | TCall* call = stack.back(); |
1751 | | |
1752 | | // Add to the stack just one callee. |
1753 | | // This algorithm always terminates, because only !visited and !currentPath causes a push |
1754 | | // and all pushes change currentPath to true, and all pops change visited to true. |
1755 | 4.52k | TGraph::iterator child = callGraph.begin(); |
1756 | 49.8k | for (; child != callGraph.end(); ++child) { |
1757 | | |
1758 | | // If we already visited this node, its whole subgraph has already been processed, so skip it. |
1759 | 46.3k | if (child->visited) |
1760 | 20.2k | continue; |
1761 | | |
1762 | 26.0k | if (call->callee == child->caller) { |
1763 | 1.04k | if (child->currentPath) { |
1764 | | // Then, we found a back edge |
1765 | 28 | if (! child->errorGiven) { |
1766 | 19 | error(infoSink, "Recursion detected:"); |
1767 | 19 | infoSink.info << " " << call->callee << " calling " << child->callee << "\n"; |
1768 | 19 | child->errorGiven = true; |
1769 | 19 | recursive = true; |
1770 | 19 | } |
1771 | 1.01k | } else { |
1772 | 1.01k | child->currentPath = true; |
1773 | 1.01k | stack.push_back(&(*child)); |
1774 | 1.01k | break; |
1775 | 1.01k | } |
1776 | 1.04k | } |
1777 | 26.0k | } |
1778 | 4.52k | if (child == callGraph.end()) { |
1779 | | // no more callees, we bottomed out, never look at this node again |
1780 | 3.50k | stack.back()->currentPath = false; |
1781 | 3.50k | stack.back()->visited = true; |
1782 | 3.50k | stack.pop_back(); |
1783 | 3.50k | } |
1784 | 4.52k | } // end while, meaning nothing left to process in this subtree |
1785 | | |
1786 | 2.48k | } while (newRoot); // redundant loop check; should always exit via the 'break' above |
1787 | 4.86k | } |
1788 | | |
1789 | | // |
1790 | | // See which functions are reachable from the entry point and which have bodies. |
1791 | | // Reachable ones with missing bodies are errors. |
1792 | | // Unreachable bodies are dead code. |
1793 | | // |
1794 | | void TIntermediate::checkCallGraphBodies(TInfoSink& infoSink, bool keepUncalled) |
1795 | 4.86k | { |
1796 | | // Clear fields we'll use for this. |
1797 | 8.36k | for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { |
1798 | 3.50k | call->visited = false; |
1799 | 3.50k | call->calleeBodyPosition = -1; |
1800 | 3.50k | } |
1801 | | |
1802 | | // The top level of the AST includes function definitions (bodies). |
1803 | | // Compare these to function calls in the call graph. |
1804 | | // We'll end up knowing which have bodies, and if so, |
1805 | | // how to map the call-graph node to the location in the AST. |
1806 | 4.86k | TIntermSequence &functionSequence = getTreeRoot()->getAsAggregate()->getSequence(); |
1807 | 4.86k | std::vector<bool> reachable(functionSequence.size(), true); // so that non-functions are reachable |
1808 | 17.8k | for (int f = 0; f < (int)functionSequence.size(); ++f) { |
1809 | 13.0k | glslang::TIntermAggregate* node = functionSequence[f]->getAsAggregate(); |
1810 | 13.0k | if (node && (node->getOp() == glslang::EOpFunction)) { |
1811 | 7.29k | if (node->getName().compare(getEntryPointMangledName().c_str()) != 0) |
1812 | 4.46k | reachable[f] = false; // so that function bodies are unreachable, until proven otherwise |
1813 | 53.3k | for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { |
1814 | 46.0k | if (call->callee == node->getName()) |
1815 | 2.73k | call->calleeBodyPosition = f; |
1816 | 46.0k | } |
1817 | 7.29k | } |
1818 | 13.0k | } |
1819 | | |
1820 | | // Start call-graph traversal by visiting the entry point nodes. |
1821 | 8.36k | for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { |
1822 | 3.50k | if (call->caller.compare(getEntryPointMangledName().c_str()) == 0) |
1823 | 1.12k | call->visited = true; |
1824 | 3.50k | } |
1825 | | |
1826 | | // Propagate 'visited' through the call-graph to every part of the graph it |
1827 | | // can reach (seeded with the entry-point setting above). |
1828 | 4.86k | bool changed; |
1829 | 5.13k | do { |
1830 | 5.13k | changed = false; |
1831 | 9.43k | for (auto call1 = callGraph.begin(); call1 != callGraph.end(); ++call1) { |
1832 | 4.30k | if (call1->visited) { |
1833 | 16.2k | for (TGraph::iterator call2 = callGraph.begin(); call2 != callGraph.end(); ++call2) { |
1834 | 13.8k | if (! call2->visited) { |
1835 | 877 | if (call1->callee == call2->caller) { |
1836 | 510 | changed = true; |
1837 | 510 | call2->visited = true; |
1838 | 510 | } |
1839 | 877 | } |
1840 | 13.8k | } |
1841 | 2.41k | } |
1842 | 4.30k | } |
1843 | 5.13k | } while (changed); |
1844 | | |
1845 | | // Any call-graph node set to visited but without a callee body is an error. |
1846 | 8.36k | for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { |
1847 | 3.50k | if (call->visited) { |
1848 | 1.63k | if (call->calleeBodyPosition == -1) { |
1849 | 30 | error(infoSink, "No function definition (body) found: "); |
1850 | 30 | infoSink.info << " " << call->callee << "\n"; |
1851 | 30 | } else |
1852 | 1.60k | reachable[call->calleeBodyPosition] = true; |
1853 | 1.63k | } |
1854 | 3.50k | } |
1855 | | |
1856 | | // Bodies in the AST not reached by the call graph are dead; |
1857 | | // clear them out, since they can't be reached and also can't |
1858 | | // be translated further due to possibility of being ill defined. |
1859 | 4.86k | if (! keepUncalled) { |
1860 | 17.8k | for (int f = 0; f < (int)functionSequence.size(); ++f) { |
1861 | 13.0k | if (! reachable[f]) |
1862 | 2.86k | { |
1863 | 2.86k | resetTopLevelUncalledStatus(functionSequence[f]->getAsAggregate()->getName()); |
1864 | 2.86k | functionSequence[f] = nullptr; |
1865 | 2.86k | } |
1866 | 13.0k | } |
1867 | 4.86k | functionSequence.erase(std::remove(functionSequence.begin(), functionSequence.end(), nullptr), functionSequence.end()); |
1868 | 4.86k | } |
1869 | 4.86k | } |
1870 | | |
1871 | | // |
1872 | | // Satisfy rules for location qualifiers on inputs and outputs |
1873 | | // |
1874 | | void TIntermediate::inOutLocationCheck(TInfoSink& infoSink) |
1875 | 4.86k | { |
1876 | | // ES 3.0 requires all outputs to have location qualifiers if there is more than one output |
1877 | 4.86k | bool fragOutWithNoLocation = false; |
1878 | 4.86k | int numFragOut = 0; |
1879 | | |
1880 | | // TODO: linker functionality: location collision checking |
1881 | | |
1882 | 4.86k | TIntermSequence& linkObjects = findLinkerObjects()->getSequence(); |
1883 | 19.8k | for (size_t i = 0; i < linkObjects.size(); ++i) { |
1884 | 14.9k | const TType& type = linkObjects[i]->getAsTyped()->getType(); |
1885 | 14.9k | const TQualifier& qualifier = type.getQualifier(); |
1886 | 14.9k | if (language == EShLangFragment) { |
1887 | 1.55k | if (qualifier.storage == EvqVaryingOut && qualifier.builtIn == EbvNone) { |
1888 | 122 | ++numFragOut; |
1889 | 122 | if (!qualifier.hasAnyLocation()) |
1890 | 1 | fragOutWithNoLocation = true; |
1891 | 122 | } |
1892 | 1.55k | } |
1893 | 14.9k | } |
1894 | | |
1895 | 4.86k | if (isEsProfile()) { |
1896 | 751 | if (numFragOut > 1 && fragOutWithNoLocation) |
1897 | 0 | error(infoSink, "when more than one fragment shader output, all must have location qualifiers"); |
1898 | 751 | } |
1899 | 4.86k | } |
1900 | | |
1901 | | TIntermAggregate* TIntermediate::findLinkerObjects() const |
1902 | 10.4k | { |
1903 | | // Get the top-level globals |
1904 | 10.4k | TIntermSequence& globals = treeRoot->getAsAggregate()->getSequence(); |
1905 | | |
1906 | | // Get the last member of the sequences, expected to be the linker-object lists |
1907 | 10.4k | assert(globals.back()->getAsAggregate()->getOp() == EOpLinkerObjects); |
1908 | | |
1909 | 10.4k | return globals.back()->getAsAggregate(); |
1910 | 10.4k | } |
1911 | | |
1912 | | // See if a variable was both a user-declared output and used. |
1913 | | // Note: the spec discusses writing to one, but this looks at read or write, which |
1914 | | // is more useful, and perhaps the spec should be changed to reflect that. |
1915 | | bool TIntermediate::userOutputUsed() const |
1916 | 4.86k | { |
1917 | 4.86k | const TIntermSequence& linkerObjects = findLinkerObjects()->getSequence(); |
1918 | | |
1919 | 4.86k | bool found = false; |
1920 | 17.1k | for (size_t i = 0; i < linkerObjects.size(); ++i) { |
1921 | 13.3k | const TIntermSymbol& symbolNode = *linkerObjects[i]->getAsSymbolNode(); |
1922 | 13.3k | if (symbolNode.getQualifier().storage == EvqVaryingOut && |
1923 | 13.3k | symbolNode.getName().compare(0, 3, "gl_") != 0 && |
1924 | 13.3k | inIoAccessed(symbolNode.getName())) { |
1925 | 1.10k | found = true; |
1926 | 1.10k | break; |
1927 | 1.10k | } |
1928 | 13.3k | } |
1929 | | |
1930 | 4.86k | return found; |
1931 | 4.86k | } |
1932 | | |
1933 | | // Accumulate locations used for inputs, outputs, and uniforms, payload, callable data, and tileImageEXT |
1934 | | // and check for collisions as the accumulation is done. |
1935 | | // |
1936 | | // Returns < 0 if no collision, >= 0 if collision and the value returned is a colliding value. |
1937 | | // |
1938 | | // typeCollision is set to true if there is no direct collision, but the types in the same location |
1939 | | // are different. |
1940 | | // |
1941 | | int TIntermediate::addUsedLocation(const TQualifier& qualifier, const TType& type, bool& typeCollision) |
1942 | 45.9k | { |
1943 | 45.9k | typeCollision = false; |
1944 | | |
1945 | 45.9k | int set; |
1946 | 45.9k | if (qualifier.isPipeInput()) |
1947 | 36.5k | set = 0; |
1948 | 9.42k | else if (qualifier.isPipeOutput()) |
1949 | 8.22k | set = 1; |
1950 | 1.20k | else if (qualifier.storage == EvqUniform) |
1951 | 982 | set = 2; |
1952 | 221 | else if (qualifier.storage == EvqBuffer) |
1953 | 0 | set = 3; |
1954 | 221 | else if (qualifier.storage == EvqTileImageEXT) |
1955 | 125 | set = 4; |
1956 | 96 | else if (qualifier.isAnyPayload()) |
1957 | 0 | set = 0; |
1958 | 96 | else if (qualifier.isAnyCallable()) |
1959 | 0 | set = 1; |
1960 | 96 | else if (qualifier.isHitObjectAttrNV()) |
1961 | 0 | set = 2; |
1962 | 96 | else |
1963 | 96 | return -1; |
1964 | | |
1965 | 45.8k | int size; |
1966 | 45.8k | if (qualifier.isAnyPayload() || qualifier.isAnyCallable()) { |
1967 | 0 | size = 1; |
1968 | 45.8k | } else if (qualifier.isUniformOrBuffer() || qualifier.isTaskMemory()) { |
1969 | 982 | if (type.isSizedArray()) |
1970 | 29 | size = type.getCumulativeArraySize(); |
1971 | 953 | else |
1972 | 953 | size = 1; |
1973 | 44.8k | } else { |
1974 | | // Strip off the outer array dimension for those having an extra one. |
1975 | 44.8k | if (type.isArray() && qualifier.isArrayedIo(language)) { |
1976 | 163 | TType elementType(type, 0); |
1977 | 163 | size = computeTypeLocationSize(elementType, language); |
1978 | 163 | } else |
1979 | 44.6k | size = computeTypeLocationSize(type, language); |
1980 | 44.8k | } |
1981 | | |
1982 | | // Locations, and components within locations. |
1983 | | // |
1984 | | // Almost always, dealing with components means a single location is involved. |
1985 | | // The exception is a dvec3. From the spec: |
1986 | | // |
1987 | | // "A dvec3 will consume all four components of the first location and components 0 and 1 of |
1988 | | // the second location. This leaves components 2 and 3 available for other component-qualified |
1989 | | // declarations." |
1990 | | // |
1991 | | // That means, without ever mentioning a component, a component range |
1992 | | // for a different location gets specified, if it's not a vertex shader input. (!) |
1993 | | // (A vertex shader input will show using only one location, even for a dvec3/4.) |
1994 | | // |
1995 | | // So, for the case of dvec3, we need two independent ioRanges. |
1996 | | // |
1997 | | // For raytracing IO (payloads and callabledata) each declaration occupies a single |
1998 | | // slot irrespective of type. |
1999 | 45.8k | int collision = -1; // no collision |
2000 | 45.8k | if (qualifier.isAnyPayload() || qualifier.isAnyCallable() || qualifier.isHitObjectAttrNV()) { |
2001 | 0 | TRange range(qualifier.layoutLocation, qualifier.layoutLocation); |
2002 | 0 | collision = checkLocationRT(set, qualifier.layoutLocation); |
2003 | 0 | if (collision < 0) |
2004 | 0 | usedIoRT[set].push_back(range); |
2005 | 0 | return collision; |
2006 | 0 | } |
2007 | 45.8k | if (size == 2 && type.getBasicType() == EbtDouble && type.getVectorSize() == 3 && |
2008 | 45.8k | (qualifier.isPipeInput() || qualifier.isPipeOutput())) { |
2009 | | // Dealing with dvec3 in/out split across two locations. |
2010 | | // Need two io-ranges. |
2011 | | // The case where the dvec3 doesn't start at component 0 was previously caught as overflow. |
2012 | | |
2013 | | // First range: |
2014 | 0 | TRange locationRange(qualifier.layoutLocation, qualifier.layoutLocation); |
2015 | 0 | TRange componentRange(0, 3); |
2016 | 0 | TIoRange range(locationRange, componentRange, type.getBasicType(), 0, qualifier.centroid, qualifier.smooth, qualifier.flat, qualifier.sample, qualifier.patch); |
2017 | | |
2018 | | // check for collisions |
2019 | 0 | collision = checkLocationRange(set, range, type, typeCollision); |
2020 | 0 | if (collision < 0) { |
2021 | 0 | usedIo[set].push_back(range); |
2022 | | |
2023 | | // Second range: |
2024 | 0 | TRange locationRange2(qualifier.layoutLocation + 1, qualifier.layoutLocation + 1); |
2025 | 0 | TRange componentRange2(0, 1); |
2026 | 0 | TIoRange range2(locationRange2, componentRange2, type.getBasicType(), 0, qualifier.centroid, qualifier.smooth, qualifier.flat, qualifier.sample, qualifier.patch); |
2027 | | |
2028 | | // check for collisions |
2029 | 0 | collision = checkLocationRange(set, range2, type, typeCollision); |
2030 | 0 | if (collision < 0) |
2031 | 0 | usedIo[set].push_back(range2); |
2032 | 0 | } |
2033 | 0 | return collision; |
2034 | 0 | } |
2035 | | |
2036 | | // Not a dvec3 in/out split across two locations, generic path. |
2037 | | // Need a single IO-range block. |
2038 | | |
2039 | 45.8k | TRange locationRange(qualifier.layoutLocation, qualifier.layoutLocation + size - 1); |
2040 | 45.8k | TRange componentRange(0, 3); |
2041 | 45.8k | if (qualifier.hasComponent() || type.getVectorSize() > 0) { |
2042 | 44.8k | int consumedComponents = type.getVectorSize() * (type.getBasicType() == EbtDouble ? 2 : 1); |
2043 | 44.8k | if (qualifier.hasComponent()) |
2044 | 5.61k | componentRange.start = qualifier.layoutComponent; |
2045 | 44.8k | componentRange.last = componentRange.start + consumedComponents - 1; |
2046 | 44.8k | } |
2047 | | |
2048 | | // combine location and component ranges |
2049 | 45.8k | TBasicType basicTy = type.getBasicType(); |
2050 | 45.8k | if (basicTy == EbtSampler && type.getSampler().isAttachmentEXT()) |
2051 | 125 | basicTy = type.getSampler().type; |
2052 | 45.8k | TIoRange range(locationRange, componentRange, basicTy, qualifier.hasIndex() ? qualifier.getIndex() : 0, qualifier.centroid, qualifier.smooth, qualifier.flat, qualifier.sample, qualifier.patch); |
2053 | | |
2054 | | // check for collisions, except for vertex inputs on desktop targeting OpenGL |
2055 | 45.8k | if (! (!isEsProfile() && language == EShLangVertex && qualifier.isPipeInput()) || spvVersion.vulkan > 0) |
2056 | 45.8k | collision = checkLocationRange(set, range, type, typeCollision); |
2057 | | |
2058 | 45.8k | if (collision < 0) |
2059 | 41.8k | usedIo[set].push_back(range); |
2060 | | |
2061 | 45.8k | return collision; |
2062 | 45.8k | } |
2063 | | |
2064 | | // Check that two types can be stored in different components in the same location. |
2065 | | // They must be the same type, except signed/unsigned integers are considered compatible. |
2066 | 2.04k | static bool checkCompatibleTypes(TBasicType t1, TBasicType t2) { |
2067 | 2.04k | if (t1 != t2) { |
2068 | 579 | if ((t1 == EbtInt8 && t2 == EbtUint8) || |
2069 | 579 | (t2 == EbtInt8 && t1 == EbtUint8) || |
2070 | 579 | (t1 == EbtInt16 && t2 == EbtUint16) || |
2071 | 579 | (t2 == EbtInt16 && t1 == EbtUint16)|| |
2072 | 579 | (t1 == EbtInt && t2 == EbtUint) || |
2073 | 579 | (t2 == EbtInt && t1 == EbtUint)|| |
2074 | 579 | (t1 == EbtInt64 && t2 == EbtUint64) || |
2075 | 579 | (t2 == EbtInt64 && t1 == EbtUint64)) { |
2076 | 10 | return true; |
2077 | 10 | } |
2078 | 579 | } |
2079 | 2.03k | return t1 == t2; |
2080 | 2.04k | } |
2081 | | |
2082 | | // Compare a new (the passed in) 'range' against the existing set, and see |
2083 | | // if there are any collisions. |
2084 | | // |
2085 | | // Returns < 0 if no collision, >= 0 if collision and the value returned is a colliding value. |
2086 | | // |
2087 | | int TIntermediate::checkLocationRange(int set, const TIoRange& range, const TType& type, bool& typeCollision) |
2088 | 45.8k | { |
2089 | 197k | for (size_t r = 0; r < usedIo[set].size(); ++r) { |
2090 | 155k | if (range.overlap(usedIo[set][r])) { |
2091 | | // there is a collision; pick one |
2092 | 3.33k | return std::max(range.location.start, usedIo[set][r].location.start); |
2093 | 151k | } else if (range.location.overlap(usedIo[set][r].location) && |
2094 | 151k | (!checkCompatibleTypes(type.getBasicType(), usedIo[set][r].basicType) || |
2095 | 2.04k | type.getQualifier().centroid != usedIo[set][r].centroid || |
2096 | 2.04k | type.getQualifier().smooth != usedIo[set][r].smooth || |
2097 | 2.04k | type.getQualifier().flat != usedIo[set][r].flat || |
2098 | 2.04k | type.getQualifier().sample != usedIo[set][r].sample || |
2099 | 2.04k | type.getQualifier().patch != usedIo[set][r].patch)) { |
2100 | | // aliased-type mismatch |
2101 | 589 | typeCollision = true; |
2102 | 589 | return std::max(range.location.start, usedIo[set][r].location.start); |
2103 | 589 | } |
2104 | 155k | } |
2105 | | |
2106 | | // check typeCollision between tileImageEXT and out |
2107 | 41.9k | if (set == 4 || set == 1) { |
2108 | | // if the set is "tileImageEXT", check against "out" and vice versa |
2109 | 7.20k | int againstSet = (set == 4) ? 1 : 4; |
2110 | 7.22k | for (size_t r = 0; r < usedIo[againstSet].size(); ++r) { |
2111 | 66 | if (range.location.overlap(usedIo[againstSet][r].location) && type.getBasicType() != usedIo[againstSet][r].basicType) { |
2112 | | // aliased-type mismatch |
2113 | 40 | typeCollision = true; |
2114 | 40 | return std::max(range.location.start, usedIo[againstSet][r].location.start); |
2115 | 40 | } |
2116 | 66 | } |
2117 | 7.20k | } |
2118 | | |
2119 | 41.8k | return -1; // no collision |
2120 | 41.9k | } |
2121 | | |
2122 | 0 | int TIntermediate::checkLocationRT(int set, int location) { |
2123 | 0 | TRange range(location, location); |
2124 | 0 | for (size_t r = 0; r < usedIoRT[set].size(); ++r) { |
2125 | 0 | if (range.overlap(usedIoRT[set][r])) { |
2126 | 0 | return range.start; |
2127 | 0 | } |
2128 | 0 | } |
2129 | 0 | return -1; // no collision |
2130 | 0 | } |
2131 | | |
2132 | | // Accumulate bindings and offsets, and check for collisions |
2133 | | // as the accumulation is done. |
2134 | | // |
2135 | | // Returns < 0 if no collision, >= 0 if collision and the value returned is a colliding value. |
2136 | | // |
2137 | | int TIntermediate::addUsedOffsets(int binding, int offset, int numOffsets) |
2138 | 1.63k | { |
2139 | 1.63k | TRange bindingRange(binding, binding); |
2140 | 1.63k | TRange offsetRange(offset, offset + numOffsets - 1); |
2141 | 1.63k | TOffsetRange range(bindingRange, offsetRange); |
2142 | | |
2143 | | // check for collisions, except for vertex inputs on desktop |
2144 | 2.61k | for (size_t r = 0; r < usedAtomics.size(); ++r) { |
2145 | 1.06k | if (range.overlap(usedAtomics[r])) { |
2146 | | // there is a collision; pick one |
2147 | 83 | return std::max(offset, usedAtomics[r].offset.start); |
2148 | 83 | } |
2149 | 1.06k | } |
2150 | | |
2151 | 1.54k | usedAtomics.push_back(range); |
2152 | | |
2153 | 1.54k | return -1; // no collision |
2154 | 1.63k | } |
2155 | | |
2156 | | // Accumulate used constant_id values. |
2157 | | // |
2158 | | // Return false is one was already used. |
2159 | | bool TIntermediate::addUsedConstantId(int id) |
2160 | 3.73k | { |
2161 | 3.73k | if (usedConstantId.find(id) != usedConstantId.end()) |
2162 | 102 | return false; |
2163 | | |
2164 | 3.62k | usedConstantId.insert(id); |
2165 | | |
2166 | 3.62k | return true; |
2167 | 3.73k | } |
2168 | | |
2169 | | // Recursively figure out how many locations are used up by an input or output type. |
2170 | | // Return the size of type, as measured by "locations". |
2171 | | int TIntermediate::computeTypeLocationSize(const TType& type, EShLanguage stage) |
2172 | 57.1k | { |
2173 | | // "If the declared input is an array of size n and each element takes m locations, it will be assigned m * n |
2174 | | // consecutive locations..." |
2175 | 57.1k | if (type.isArray()) { |
2176 | | // TODO: perf: this can be flattened by using getCumulativeArraySize(), and a deref that discards all arrayness |
2177 | | // TODO: are there valid cases of having an unsized array with a location? If so, running this code too early. |
2178 | 2.83k | TType elementType(type, 0); |
2179 | 2.83k | if (type.isSizedArray() && !type.getQualifier().isPerView()) |
2180 | 2.64k | return type.getOuterArraySize() * computeTypeLocationSize(elementType, stage); |
2181 | 190 | else { |
2182 | | // unset perViewNV attributes for arrayed per-view outputs: "perviewNV vec4 v[MAX_VIEWS][3];" |
2183 | 190 | elementType.getQualifier().perViewNV = false; |
2184 | 190 | return computeTypeLocationSize(elementType, stage); |
2185 | 190 | } |
2186 | 2.83k | } |
2187 | | |
2188 | | // "The locations consumed by block and structure members are determined by applying the rules above |
2189 | | // recursively..." |
2190 | 54.3k | if (type.isStruct()) { |
2191 | 2.02k | int size = 0; |
2192 | 5.52k | for (int member = 0; member < (int)type.getStruct()->size(); ++member) { |
2193 | 3.50k | TType memberType(type, member); |
2194 | 3.50k | size += computeTypeLocationSize(memberType, stage); |
2195 | 3.50k | } |
2196 | 2.02k | return size; |
2197 | 2.02k | } |
2198 | | |
2199 | | // ES: "If a shader input is any scalar or vector type, it will consume a single location." |
2200 | | |
2201 | | // Desktop: "If a vertex shader input is any scalar or vector type, it will consume a single location. If a non-vertex |
2202 | | // shader input is a scalar or vector type other than dvec3 or dvec4, it will consume a single location, while |
2203 | | // types dvec3 or dvec4 will consume two consecutive locations. Inputs of type double and dvec2 will |
2204 | | // consume only a single location, in all stages." |
2205 | 52.3k | if (type.isScalar()) |
2206 | 17.4k | return 1; |
2207 | 34.8k | if (type.isVector()) { |
2208 | 33.5k | if (stage == EShLangVertex && type.getQualifier().isPipeInput()) |
2209 | 18.9k | return 1; |
2210 | 14.6k | if (type.getBasicType() == EbtDouble && type.getVectorSize() > 2) |
2211 | 0 | return 2; |
2212 | 14.6k | else |
2213 | 14.6k | return 1; |
2214 | 14.6k | } |
2215 | | |
2216 | | // "If the declared input is an n x m single- or double-precision matrix, ... |
2217 | | // The number of locations assigned for each matrix will be the same as |
2218 | | // for an n-element array of m-component vectors..." |
2219 | 1.29k | if (type.isMatrix()) { |
2220 | 1.29k | TType columnType(type, 0); |
2221 | 1.29k | return type.getMatrixCols() * computeTypeLocationSize(columnType, stage); |
2222 | 1.29k | } |
2223 | | |
2224 | 0 | assert(0); |
2225 | 0 | return 1; |
2226 | 1.29k | } |
2227 | | |
2228 | | // Same as computeTypeLocationSize but for uniforms |
2229 | | int TIntermediate::computeTypeUniformLocationSize(const TType& type) |
2230 | 0 | { |
2231 | | // "Individual elements of a uniform array are assigned |
2232 | | // consecutive locations with the first element taking location |
2233 | | // location." |
2234 | 0 | if (type.isArray()) { |
2235 | | // TODO: perf: this can be flattened by using getCumulativeArraySize(), and a deref that discards all arrayness |
2236 | 0 | TType elementType(type, 0); |
2237 | 0 | if (type.isSizedArray()) { |
2238 | 0 | return type.getOuterArraySize() * computeTypeUniformLocationSize(elementType); |
2239 | 0 | } else { |
2240 | | // TODO: are there valid cases of having an implicitly-sized array with a location? If so, running this code too early. |
2241 | 0 | return computeTypeUniformLocationSize(elementType); |
2242 | 0 | } |
2243 | 0 | } |
2244 | | |
2245 | | // "Each subsequent inner-most member or element gets incremental |
2246 | | // locations for the entire structure or array." |
2247 | 0 | if (type.isStruct()) { |
2248 | 0 | int size = 0; |
2249 | 0 | for (int member = 0; member < (int)type.getStruct()->size(); ++member) { |
2250 | 0 | TType memberType(type, member); |
2251 | 0 | size += computeTypeUniformLocationSize(memberType); |
2252 | 0 | } |
2253 | 0 | return size; |
2254 | 0 | } |
2255 | | |
2256 | 0 | return 1; |
2257 | 0 | } |
2258 | | |
2259 | | // Accumulate xfb buffer ranges and check for collisions as the accumulation is done. |
2260 | | // |
2261 | | // Returns < 0 if no collision, >= 0 if collision and the value returned is a colliding value. |
2262 | | // |
2263 | | int TIntermediate::addXfbBufferOffset(const TType& type) |
2264 | 0 | { |
2265 | 0 | const TQualifier& qualifier = type.getQualifier(); |
2266 | |
|
2267 | 0 | assert(qualifier.hasXfbOffset() && qualifier.hasXfbBuffer()); |
2268 | 0 | TXfbBuffer& buffer = xfbBuffers[qualifier.layoutXfbBuffer]; |
2269 | | |
2270 | | // compute the range |
2271 | 0 | unsigned int size = computeTypeXfbSize(type, buffer.contains64BitType, buffer.contains32BitType, buffer.contains16BitType); |
2272 | 0 | buffer.implicitStride = std::max(buffer.implicitStride, qualifier.layoutXfbOffset + size); |
2273 | 0 | TRange range(qualifier.layoutXfbOffset, qualifier.layoutXfbOffset + size - 1); |
2274 | | |
2275 | | // check for collisions |
2276 | 0 | for (size_t r = 0; r < buffer.ranges.size(); ++r) { |
2277 | 0 | if (range.overlap(buffer.ranges[r])) { |
2278 | | // there is a collision; pick an example to return |
2279 | 0 | return std::max(range.start, buffer.ranges[r].start); |
2280 | 0 | } |
2281 | 0 | } |
2282 | | |
2283 | 0 | buffer.ranges.push_back(range); |
2284 | |
|
2285 | 0 | return -1; // no collision |
2286 | 0 | } |
2287 | | |
2288 | | // Recursively figure out how many bytes of xfb buffer are used by the given type. |
2289 | | // Return the size of type, in bytes. |
2290 | | // Sets contains64BitType to true if the type contains a 64-bit data type. |
2291 | | // Sets contains32BitType to true if the type contains a 32-bit data type. |
2292 | | // Sets contains16BitType to true if the type contains a 16-bit data type. |
2293 | | // N.B. Caller must set contains64BitType, contains32BitType, and contains16BitType to false before calling. |
2294 | | unsigned int TIntermediate::computeTypeXfbSize(const TType& type, bool& contains64BitType, bool& contains32BitType, bool& contains16BitType) const |
2295 | 0 | { |
2296 | | // "...if applied to an aggregate containing a double or 64-bit integer, the offset must also be a multiple of 8, |
2297 | | // and the space taken in the buffer will be a multiple of 8. |
2298 | | // ...within the qualified entity, subsequent components are each |
2299 | | // assigned, in order, to the next available offset aligned to a multiple of |
2300 | | // that component's size. Aggregate types are flattened down to the component |
2301 | | // level to get this sequence of components." |
2302 | |
|
2303 | 0 | if (type.isSizedArray()) { |
2304 | | // TODO: perf: this can be flattened by using getCumulativeArraySize(), and a deref that discards all arrayness |
2305 | | // Unsized array use to xfb should be a compile error. |
2306 | 0 | TType elementType(type, 0); |
2307 | 0 | return type.getOuterArraySize() * computeTypeXfbSize(elementType, contains64BitType, contains16BitType, contains16BitType); |
2308 | 0 | } |
2309 | | |
2310 | 0 | if (type.isStruct()) { |
2311 | 0 | unsigned int size = 0; |
2312 | 0 | bool structContains64BitType = false; |
2313 | 0 | bool structContains32BitType = false; |
2314 | 0 | bool structContains16BitType = false; |
2315 | 0 | for (int member = 0; member < (int)type.getStruct()->size(); ++member) { |
2316 | 0 | TType memberType(type, member); |
2317 | | // "... if applied to |
2318 | | // an aggregate containing a double or 64-bit integer, the offset must also be a multiple of 8, |
2319 | | // and the space taken in the buffer will be a multiple of 8." |
2320 | 0 | bool memberContains64BitType = false; |
2321 | 0 | bool memberContains32BitType = false; |
2322 | 0 | bool memberContains16BitType = false; |
2323 | 0 | int memberSize = computeTypeXfbSize(memberType, memberContains64BitType, memberContains32BitType, memberContains16BitType); |
2324 | 0 | if (memberContains64BitType) { |
2325 | 0 | structContains64BitType = true; |
2326 | 0 | RoundToPow2(size, 8); |
2327 | 0 | } else if (memberContains32BitType) { |
2328 | 0 | structContains32BitType = true; |
2329 | 0 | RoundToPow2(size, 4); |
2330 | 0 | } else if (memberContains16BitType) { |
2331 | 0 | structContains16BitType = true; |
2332 | 0 | RoundToPow2(size, 2); |
2333 | 0 | } |
2334 | 0 | size += memberSize; |
2335 | 0 | } |
2336 | |
|
2337 | 0 | if (structContains64BitType) { |
2338 | 0 | contains64BitType = true; |
2339 | 0 | RoundToPow2(size, 8); |
2340 | 0 | } else if (structContains32BitType) { |
2341 | 0 | contains32BitType = true; |
2342 | 0 | RoundToPow2(size, 4); |
2343 | 0 | } else if (structContains16BitType) { |
2344 | 0 | contains16BitType = true; |
2345 | 0 | RoundToPow2(size, 2); |
2346 | 0 | } |
2347 | 0 | return size; |
2348 | 0 | } |
2349 | | |
2350 | 0 | int numComponents {0}; |
2351 | 0 | if (type.isScalar()) |
2352 | 0 | numComponents = 1; |
2353 | 0 | else if (type.isVector()) |
2354 | 0 | numComponents = type.getVectorSize(); |
2355 | 0 | else if (type.isMatrix()) |
2356 | 0 | numComponents = type.getMatrixCols() * type.getMatrixRows(); |
2357 | 0 | else { |
2358 | 0 | assert(0); |
2359 | 0 | numComponents = 1; |
2360 | 0 | } |
2361 | |
|
2362 | 0 | if (type.getBasicType() == EbtDouble || type.getBasicType() == EbtInt64 || type.getBasicType() == EbtUint64) { |
2363 | 0 | contains64BitType = true; |
2364 | 0 | return 8 * numComponents; |
2365 | 0 | } else if (type.getBasicType() == EbtFloat16 || type.getBasicType() == EbtInt16 || type.getBasicType() == EbtUint16) { |
2366 | 0 | contains16BitType = true; |
2367 | 0 | return 2 * numComponents; |
2368 | 0 | } else if (type.getBasicType() == EbtInt8 || type.getBasicType() == EbtUint8) |
2369 | 0 | return numComponents; |
2370 | 0 | else { |
2371 | 0 | contains32BitType = true; |
2372 | 0 | return 4 * numComponents; |
2373 | 0 | } |
2374 | 0 | } |
2375 | | |
2376 | | const int baseAlignmentVec4Std140 = 16; |
2377 | | |
2378 | | // Return the size and alignment of a component of the given type. |
2379 | | // The size is returned in the 'size' parameter |
2380 | | // Return value is the alignment.. |
2381 | | int TIntermediate::getBaseAlignmentScalar(const TType& type, int& size) |
2382 | 106k | { |
2383 | 106k | switch (type.getBasicType()) { |
2384 | 535 | case EbtInt64: |
2385 | 1.07k | case EbtUint64: |
2386 | 5.16k | case EbtDouble: size = 8; return 8; |
2387 | 9.77k | case EbtFloat16: size = 2; return 2; |
2388 | 0 | case EbtBFloat16: size = 2; return 2; |
2389 | 0 | case EbtFloatE5M2: |
2390 | 0 | case EbtFloatE4M3: |
2391 | 5.05k | case EbtInt8: |
2392 | 12.3k | case EbtUint8: size = 1; return 1; |
2393 | 9.13k | case EbtInt16: |
2394 | 18.9k | case EbtUint16: size = 2; return 2; |
2395 | 1.30k | case EbtReference: size = 8; return 8; |
2396 | 1 | case EbtSampler: |
2397 | 1 | { |
2398 | 1 | if (type.isBindlessImage() || type.isBindlessTexture()) { |
2399 | 0 | size = 8; return 8; |
2400 | 0 | } |
2401 | 1 | else { |
2402 | 1 | size = 4; return 4; |
2403 | 1 | } |
2404 | 1 | } |
2405 | 58.4k | default: size = 4; return 4; |
2406 | 106k | } |
2407 | 106k | } |
2408 | | |
2409 | | // Implement base-alignment and size rules from section 7.6.2.2 Standard Uniform Block Layout |
2410 | | // Operates recursively. |
2411 | | // |
2412 | | // If std140 is true, it does the rounding up to vec4 size required by std140, |
2413 | | // otherwise it does not, yielding std430 rules. |
2414 | | // |
2415 | | // The size is returned in the 'size' parameter |
2416 | | // |
2417 | | // The stride is only non-0 for arrays or matrices, and is the stride of the |
2418 | | // top-level object nested within the type. E.g., for an array of matrices, |
2419 | | // it is the distances needed between matrices, despite the rules saying the |
2420 | | // stride comes from the flattening down to vectors. |
2421 | | // |
2422 | | // Return value is the alignment of the type. |
2423 | | int TIntermediate::getBaseAlignment(const TType& type, int& size, int& stride, TLayoutPacking layoutPacking, bool rowMajor) |
2424 | 125k | { |
2425 | 125k | int alignment; |
2426 | | |
2427 | 125k | bool std140 = layoutPacking == glslang::ElpStd140; |
2428 | | // When using the std140 storage layout, structures will be laid out in buffer |
2429 | | // storage with its members stored in monotonically increasing order based on their |
2430 | | // location in the declaration. A structure and each structure member have a base |
2431 | | // offset and a base alignment, from which an aligned offset is computed by rounding |
2432 | | // the base offset up to a multiple of the base alignment. The base offset of the first |
2433 | | // member of a structure is taken from the aligned offset of the structure itself. The |
2434 | | // base offset of all other structure members is derived by taking the offset of the |
2435 | | // last basic machine unit consumed by the previous member and adding one. Each |
2436 | | // structure member is stored in memory at its aligned offset. The members of a top- |
2437 | | // level uniform block are laid out in buffer storage by treating the uniform block as |
2438 | | // a structure with a base offset of zero. |
2439 | | // |
2440 | | // 1. If the member is a scalar consuming N basic machine units, the base alignment is N. |
2441 | | // |
2442 | | // 2. If the member is a two- or four-component vector with components consuming N basic |
2443 | | // machine units, the base alignment is 2N or 4N, respectively. |
2444 | | // |
2445 | | // 3. If the member is a three-component vector with components consuming N |
2446 | | // basic machine units, the base alignment is 4N. |
2447 | | // |
2448 | | // 4. If the member is an array of scalars or vectors, the base alignment and array |
2449 | | // stride are set to match the base alignment of a single array element, according |
2450 | | // to rules (1), (2), and (3), and rounded up to the base alignment of a vec4. The |
2451 | | // array may have padding at the end; the base offset of the member following |
2452 | | // the array is rounded up to the next multiple of the base alignment. |
2453 | | // |
2454 | | // 5. If the member is a column-major matrix with C columns and R rows, the |
2455 | | // matrix is stored identically to an array of C column vectors with R |
2456 | | // components each, according to rule (4). |
2457 | | // |
2458 | | // 6. If the member is an array of S column-major matrices with C columns and |
2459 | | // R rows, the matrix is stored identically to a row of S X C column vectors |
2460 | | // with R components each, according to rule (4). |
2461 | | // |
2462 | | // 7. If the member is a row-major matrix with C columns and R rows, the matrix |
2463 | | // is stored identically to an array of R row vectors with C components each, |
2464 | | // according to rule (4). |
2465 | | // |
2466 | | // 8. If the member is an array of S row-major matrices with C columns and R |
2467 | | // rows, the matrix is stored identically to a row of S X R row vectors with C |
2468 | | // components each, according to rule (4). |
2469 | | // |
2470 | | // 9. If the member is a structure, the base alignment of the structure is N , where |
2471 | | // N is the largest base alignment value of any of its members, and rounded |
2472 | | // up to the base alignment of a vec4. The individual members of this substructure |
2473 | | // are then assigned offsets by applying this set of rules recursively, |
2474 | | // where the base offset of the first member of the sub-structure is equal to the |
2475 | | // aligned offset of the structure. The structure may have padding at the end; |
2476 | | // the base offset of the member following the sub-structure is rounded up to |
2477 | | // the next multiple of the base alignment of the structure. |
2478 | | // |
2479 | | // 10. If the member is an array of S structures, the S elements of the array are laid |
2480 | | // out in order, according to rule (9). |
2481 | | // |
2482 | | // Assuming, for rule 10: The stride is the same as the size of an element. |
2483 | | |
2484 | 125k | stride = 0; |
2485 | 125k | int dummyStride; |
2486 | | |
2487 | | // rules 4, 6, 8, and 10 |
2488 | 125k | if (type.isArray()) { |
2489 | | // TODO: perf: this might be flattened by using getCumulativeArraySize(), and a deref that discards all arrayness |
2490 | 12.3k | TType derefType(type, 0); |
2491 | 12.3k | alignment = getBaseAlignment(derefType, size, dummyStride, layoutPacking, rowMajor); |
2492 | 12.3k | if (std140) |
2493 | 7.30k | alignment = std::max(baseAlignmentVec4Std140, alignment); |
2494 | 12.3k | RoundToPow2(size, alignment); |
2495 | 12.3k | stride = size; // uses full matrix size for stride of an array of matrices (not quite what rule 6/8, but what's expected) |
2496 | | // uses the assumption for rule 10 in the comment above |
2497 | | // use one element to represent the last member of SSBO which is unsized array |
2498 | 12.3k | int arraySize = (type.isUnsizedArray() && (type.getOuterArraySize() == 0)) ? 1 : type.getOuterArraySize(); |
2499 | 12.3k | size = stride * arraySize; |
2500 | 12.3k | return alignment; |
2501 | 12.3k | } |
2502 | | |
2503 | | // rule 9 |
2504 | 112k | if (type.getBasicType() == EbtStruct || type.getBasicType() == EbtBlock) { |
2505 | 10.9k | const TTypeList& memberList = *type.getStruct(); |
2506 | | |
2507 | 10.9k | size = 0; |
2508 | 10.9k | int maxAlignment = std140 ? baseAlignmentVec4Std140 : 0; |
2509 | 50.6k | for (size_t m = 0; m < memberList.size(); ++m) { |
2510 | 39.7k | int memberSize; |
2511 | | // modify just the children's view of matrix layout, if there is one for this member |
2512 | 39.7k | TLayoutMatrix subMatrixLayout = memberList[m].type->getQualifier().layoutMatrix; |
2513 | 39.7k | int memberAlignment = getBaseAlignment(*memberList[m].type, memberSize, dummyStride, layoutPacking, |
2514 | 39.7k | (subMatrixLayout != ElmNone) ? (subMatrixLayout == ElmRowMajor) : rowMajor); |
2515 | 39.7k | maxAlignment = std::max(maxAlignment, memberAlignment); |
2516 | 39.7k | RoundToPow2(size, memberAlignment); |
2517 | 39.7k | size += memberSize; |
2518 | 39.7k | } |
2519 | | |
2520 | | // The structure may have padding at the end; the base offset of |
2521 | | // the member following the sub-structure is rounded up to the next |
2522 | | // multiple of the base alignment of the structure. |
2523 | 10.9k | RoundToPow2(size, maxAlignment); |
2524 | | |
2525 | 10.9k | return maxAlignment; |
2526 | 10.9k | } |
2527 | | |
2528 | | // rule 1 |
2529 | 101k | if (type.isScalar()) |
2530 | 39.8k | return getBaseAlignmentScalar(type, size); |
2531 | | |
2532 | | // rules 2 and 3 |
2533 | 62.0k | if (type.isVector()) { |
2534 | 51.5k | int scalarAlign = getBaseAlignmentScalar(type, size); |
2535 | 51.5k | switch (type.getVectorSize()) { |
2536 | 0 | case 1: // HLSL has this, GLSL does not |
2537 | 0 | return scalarAlign; |
2538 | 13.9k | case 2: |
2539 | 13.9k | size *= 2; |
2540 | 13.9k | return 2 * scalarAlign; |
2541 | 37.5k | default: |
2542 | 37.5k | size *= type.getVectorSize(); |
2543 | 37.5k | return 4 * scalarAlign; |
2544 | 51.5k | } |
2545 | 51.5k | } |
2546 | | |
2547 | | // rules 5 and 7 |
2548 | 10.5k | if (type.isMatrix()) { |
2549 | | // rule 5: deref to row, not to column, meaning the size of vector is num columns instead of num rows |
2550 | 10.5k | TType derefType(type, 0, rowMajor); |
2551 | | |
2552 | 10.5k | alignment = getBaseAlignment(derefType, size, dummyStride, layoutPacking, rowMajor); |
2553 | 10.5k | if (std140) |
2554 | 8.25k | alignment = std::max(baseAlignmentVec4Std140, alignment); |
2555 | 10.5k | RoundToPow2(size, alignment); |
2556 | 10.5k | stride = size; // use intra-matrix stride for stride of a just a matrix |
2557 | 10.5k | if (rowMajor) |
2558 | 1.13k | size = stride * type.getMatrixRows(); |
2559 | 9.39k | else |
2560 | 9.39k | size = stride * type.getMatrixCols(); |
2561 | | |
2562 | 10.5k | return alignment; |
2563 | 10.5k | } |
2564 | | |
2565 | 0 | assert(0); // all cases should be covered above |
2566 | 0 | size = baseAlignmentVec4Std140; |
2567 | 0 | return baseAlignmentVec4Std140; |
2568 | 10.5k | } |
2569 | | |
2570 | | // To aid the basic HLSL rule about crossing vec4 boundaries. |
2571 | | bool TIntermediate::improperStraddle(const TType& type, int size, int offset, bool vectorLike) |
2572 | 6.95k | { |
2573 | 6.95k | if (! vectorLike || type.isArray()) |
2574 | 4.36k | return false; |
2575 | | |
2576 | 2.58k | return size <= 16 ? offset / 16 != (offset + size - 1) / 16 |
2577 | 2.58k | : offset % 16 != 0; |
2578 | 6.95k | } |
2579 | | |
2580 | | int TIntermediate::getScalarAlignment(const TType& type, int& size, int& stride, bool rowMajor) |
2581 | 2.22k | { |
2582 | 2.22k | int alignment; |
2583 | | |
2584 | 2.22k | stride = 0; |
2585 | 2.22k | int dummyStride; |
2586 | | |
2587 | 2.22k | if (type.isArray()) { |
2588 | 513 | TType derefType(type, 0); |
2589 | 513 | alignment = getScalarAlignment(derefType, size, dummyStride, rowMajor); |
2590 | | |
2591 | 513 | stride = size; |
2592 | 513 | RoundToPow2(stride, alignment); |
2593 | | |
2594 | 513 | size = stride * (type.getOuterArraySize() - 1) + size; |
2595 | 513 | return alignment; |
2596 | 513 | } |
2597 | | |
2598 | 1.71k | if (type.getBasicType() == EbtStruct) { |
2599 | 160 | const TTypeList& memberList = *type.getStruct(); |
2600 | | |
2601 | 160 | size = 0; |
2602 | 160 | int maxAlignment = 0; |
2603 | 956 | for (size_t m = 0; m < memberList.size(); ++m) { |
2604 | 796 | int memberSize; |
2605 | | // modify just the children's view of matrix layout, if there is one for this member |
2606 | 796 | TLayoutMatrix subMatrixLayout = memberList[m].type->getQualifier().layoutMatrix; |
2607 | 796 | int memberAlignment = getScalarAlignment(*memberList[m].type, memberSize, dummyStride, |
2608 | 796 | (subMatrixLayout != ElmNone) ? (subMatrixLayout == ElmRowMajor) : rowMajor); |
2609 | 796 | maxAlignment = std::max(maxAlignment, memberAlignment); |
2610 | 796 | RoundToPow2(size, memberAlignment); |
2611 | 796 | size += memberSize; |
2612 | 796 | } |
2613 | | |
2614 | 160 | return maxAlignment; |
2615 | 160 | } |
2616 | | |
2617 | 1.55k | if (type.isScalar()) |
2618 | 667 | return getBaseAlignmentScalar(type, size); |
2619 | | |
2620 | 889 | if (type.isVector()) { |
2621 | 763 | int scalarAlign = getBaseAlignmentScalar(type, size); |
2622 | | |
2623 | 763 | size *= type.getVectorSize(); |
2624 | 763 | return scalarAlign; |
2625 | 763 | } |
2626 | | |
2627 | 126 | if (type.isMatrix()) { |
2628 | 126 | TType derefType(type, 0, rowMajor); |
2629 | | |
2630 | 126 | alignment = getScalarAlignment(derefType, size, dummyStride, rowMajor); |
2631 | | |
2632 | 126 | stride = size; // use intra-matrix stride for stride of a just a matrix |
2633 | 126 | if (rowMajor) |
2634 | 0 | size = stride * type.getMatrixRows(); |
2635 | 126 | else |
2636 | 126 | size = stride * type.getMatrixCols(); |
2637 | | |
2638 | 126 | return alignment; |
2639 | 126 | } |
2640 | | |
2641 | 0 | assert(0); // all cases should be covered above |
2642 | 0 | size = 1; |
2643 | 0 | return 1; |
2644 | 126 | } |
2645 | | |
2646 | | int TIntermediate::getMemberAlignment(const TType& type, int& size, int& stride, TLayoutPacking layoutPacking, bool rowMajor) |
2647 | 63.4k | { |
2648 | 63.4k | if (layoutPacking == glslang::ElpScalar) { |
2649 | 794 | return getScalarAlignment(type, size, stride, rowMajor); |
2650 | 62.6k | } else { |
2651 | 62.6k | return getBaseAlignment(type, size, stride, layoutPacking, rowMajor); |
2652 | 62.6k | } |
2653 | 63.4k | } |
2654 | | |
2655 | | // shared calculation by getOffset and getOffsets |
2656 | | void TIntermediate::updateOffset(const TType& parentType, const TType& memberType, int& offset, int& memberSize) |
2657 | 2.22k | { |
2658 | 2.22k | int dummyStride; |
2659 | | |
2660 | | // modify just the children's view of matrix layout, if there is one for this member |
2661 | 2.22k | TLayoutMatrix subMatrixLayout = memberType.getQualifier().layoutMatrix; |
2662 | 2.22k | int memberAlignment = getMemberAlignment(memberType, memberSize, dummyStride, |
2663 | 2.22k | parentType.getQualifier().layoutPacking, |
2664 | 2.22k | subMatrixLayout != ElmNone |
2665 | 2.22k | ? subMatrixLayout == ElmRowMajor |
2666 | 2.22k | : parentType.getQualifier().layoutMatrix == ElmRowMajor); |
2667 | 2.22k | RoundToPow2(offset, memberAlignment); |
2668 | 2.22k | } |
2669 | | |
2670 | | // Lookup or calculate the offset of a block member, using the recursively |
2671 | | // defined block offset rules. |
2672 | | int TIntermediate::getOffset(const TType& type, int index) |
2673 | 1.16k | { |
2674 | 1.16k | const TTypeList& memberList = *type.getStruct(); |
2675 | | |
2676 | | // Don't calculate offset if one is present, it could be user supplied |
2677 | | // and different than what would be calculated. That is, this is faster, |
2678 | | // but not just an optimization. |
2679 | 1.16k | if (memberList[index].type->getQualifier().hasOffset()) |
2680 | 23 | return memberList[index].type->getQualifier().layoutOffset; |
2681 | | |
2682 | 1.14k | int memberSize = 0; |
2683 | 1.14k | int offset = 0; |
2684 | 3.36k | for (int m = 0; m <= index; ++m) { |
2685 | 2.22k | updateOffset(type, *memberList[m].type, offset, memberSize); |
2686 | | |
2687 | 2.22k | if (m < index) |
2688 | 1.08k | offset += memberSize; |
2689 | 2.22k | } |
2690 | | |
2691 | 1.14k | return offset; |
2692 | 1.16k | } |
2693 | | |
2694 | | // Calculate the block data size. |
2695 | | // Block arrayness is not taken into account, each element is backed by a separate buffer. |
2696 | | int TIntermediate::getBlockSize(const TType& blockType) |
2697 | 1.16k | { |
2698 | 1.16k | const TTypeList& memberList = *blockType.getStruct(); |
2699 | 1.16k | int lastIndex = (int)memberList.size() - 1; |
2700 | 1.16k | int lastOffset = getOffset(blockType, lastIndex); |
2701 | | |
2702 | 1.16k | int lastMemberSize; |
2703 | 1.16k | int dummyStride; |
2704 | 1.16k | getMemberAlignment(*memberList[lastIndex].type, lastMemberSize, dummyStride, |
2705 | 1.16k | blockType.getQualifier().layoutPacking, |
2706 | 1.16k | blockType.getQualifier().layoutMatrix == ElmRowMajor); |
2707 | | |
2708 | 1.16k | return lastOffset + lastMemberSize; |
2709 | 1.16k | } |
2710 | | |
2711 | | int TIntermediate::computeBufferReferenceTypeSize(const TType& type) |
2712 | 1.16k | { |
2713 | 1.16k | assert(type.isReference()); |
2714 | 1.16k | int size = getBlockSize(*type.getReferentType()); |
2715 | | |
2716 | 1.16k | int align = type.getBufferReferenceAlignment(); |
2717 | | |
2718 | 1.16k | if (align) { |
2719 | 1.16k | size = (size + align - 1) & ~(align-1); |
2720 | 1.16k | } |
2721 | | |
2722 | 1.16k | return size; |
2723 | 1.16k | } |
2724 | | |
2725 | 0 | bool TIntermediate::isIoResizeArray(const TType& type, EShLanguage language) { |
2726 | 0 | return type.isArray() && |
2727 | 0 | ((language == EShLangGeometry && type.getQualifier().storage == EvqVaryingIn) || |
2728 | 0 | (language == EShLangTessControl && (type.getQualifier().storage == EvqVaryingIn || type.getQualifier().storage == EvqVaryingOut) && |
2729 | 0 | ! type.getQualifier().patch) || |
2730 | 0 | (language == EShLangTessEvaluation && type.getQualifier().storage == EvqVaryingIn) || |
2731 | 0 | (language == EShLangFragment && type.getQualifier().storage == EvqVaryingIn && |
2732 | 0 | (type.getQualifier().pervertexNV || type.getQualifier().pervertexEXT)) || |
2733 | 0 | (language == EShLangMesh && type.getQualifier().storage == EvqVaryingOut && |
2734 | 0 | !type.getQualifier().perTaskNV)); |
2735 | 0 | } |
2736 | | |
2737 | | } // end namespace glslang |