/src/glslang/glslang/MachineIndependent/linkValidate.cpp
Line | Count | Source |
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 | 0 | { |
63 | 0 | infoSink.info.prefix(EPrefixError); |
64 | 0 | if (loc) |
65 | 0 | infoSink.info.location(*loc, messages & EShMsgAbsolutePath, messages & EShMsgDisplayErrorColumn); |
66 | 0 | if (unitStage == EShLangCount) |
67 | 0 | 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 | 0 | ++numErrors; |
74 | 0 | } |
75 | | |
76 | | // Link-time warning. |
77 | | void TIntermediate::warn(TInfoSink& infoSink, const TSourceLoc* loc, EShMessages messages, const char* message, |
78 | | EShLanguage unitStage) |
79 | 0 | { |
80 | 0 | infoSink.info.prefix(EPrefixWarning); |
81 | 0 | if (loc) |
82 | 0 | infoSink.info.location(*loc, messages & EShMsgAbsolutePath, messages & EShMsgDisplayErrorColumn); |
83 | 0 | if (unitStage == EShLangCount) |
84 | 0 | 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 | 0 | } |
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 (!IsRequestedExtension(glslang::E_GL_NV_push_constant_bank) && |
528 | 0 | (numPushConstants > 1 || unit.numPushConstants > 1)) |
529 | 0 | error(infoSink, "Only one push_constant block is allowed per stage"); |
530 | 0 | numPushConstants = std::min(numPushConstants + unit.numPushConstants, 1); |
531 | |
|
532 | 0 | if (unit.invocations != TQualifier::layoutNotSet) { |
533 | 0 | if (invocations == TQualifier::layoutNotSet) |
534 | 0 | invocations = unit.invocations; |
535 | 0 | else if (invocations != unit.invocations) |
536 | 0 | error(infoSink, "number of invocations must match between compilation units"); |
537 | 0 | } |
538 | | |
539 | | // The GLSL specification requires that at least one compilation unit |
540 | | // must declare the vertices layout, but not all units need to do so. |
541 | | // However, all declarations must match. |
542 | 0 | if (vertices == TQualifier::layoutNotSet) |
543 | 0 | vertices = unit.vertices; |
544 | 0 | else if (unit.vertices != TQualifier::layoutNotSet && vertices != unit.vertices) { |
545 | 0 | if (language == EShLangGeometry || language == EShLangMesh) |
546 | 0 | error(infoSink, "Contradictory layout max_vertices values"); |
547 | 0 | else if (language == EShLangTessControl) |
548 | 0 | error(infoSink, "Contradictory layout vertices values"); |
549 | 0 | else |
550 | 0 | assert(0); |
551 | 0 | } |
552 | | |
553 | | // The mesh shader extension requires that at least one compilation unit |
554 | | // must declare the max_primitives layout, but not all units need to do so. |
555 | | // However, all declarations must match. |
556 | 0 | if (primitives == TQualifier::layoutNotSet) |
557 | 0 | primitives = unit.primitives; |
558 | 0 | else if (unit.primitives != TQualifier::layoutNotSet && primitives != unit.primitives) { |
559 | 0 | if (language == EShLangMesh) |
560 | 0 | error(infoSink, "Contradictory layout max_primitives values"); |
561 | 0 | else |
562 | 0 | assert(0); |
563 | 0 | } |
564 | | |
565 | | // The GLSL specification requires that at least one compilation unit |
566 | | // must declare the input primitive layout, but not all units need to do so. |
567 | | // However, all declarations must match. |
568 | 0 | if (inputPrimitive == ElgNone) |
569 | 0 | inputPrimitive = unit.inputPrimitive; |
570 | 0 | else if (unit.inputPrimitive != ElgNone && inputPrimitive != unit.inputPrimitive) |
571 | 0 | error(infoSink, "Contradictory input layout primitives"); |
572 | | |
573 | | // The GLSL specification requires that at least one compilation unit |
574 | | // must declare the output primitive layout, but not all units need to do so. |
575 | | // However, all declarations must match. |
576 | 0 | if (outputPrimitive == ElgNone) |
577 | 0 | outputPrimitive = unit.outputPrimitive; |
578 | 0 | else if (unit.outputPrimitive != ElgNone && outputPrimitive != unit.outputPrimitive) |
579 | 0 | error(infoSink, "Contradictory output layout primitives"); |
580 | |
|
581 | 0 | if (originUpperLeft != unit.originUpperLeft || pixelCenterInteger != unit.pixelCenterInteger) |
582 | 0 | error(infoSink, "gl_FragCoord redeclarations must match across shaders"); |
583 | | |
584 | | // The GLSL specification requires that at least one compilation unit |
585 | | // must declare the vertex spacing layout, but not all units need to do so. |
586 | | // However, all declarations must match. |
587 | 0 | if (vertexSpacing == EvsNone) |
588 | 0 | vertexSpacing = unit.vertexSpacing; |
589 | 0 | else if (unit.vertexSpacing != EvsNone && vertexSpacing != unit.vertexSpacing) |
590 | 0 | error(infoSink, "Contradictory input vertex spacing"); |
591 | | |
592 | | // The GLSL specification requires that at least one compilation unit |
593 | | // must declare the triangle ordering layout, but not all units need to do so. |
594 | | // However, all declarations must match. |
595 | 0 | if (vertexOrder == EvoNone) |
596 | 0 | vertexOrder = unit.vertexOrder; |
597 | 0 | else if (unit.vertexOrder != EvoNone && vertexOrder != unit.vertexOrder) |
598 | 0 | error(infoSink, "Contradictory triangle ordering"); |
599 | |
|
600 | 0 | MERGE_TRUE(pointMode); |
601 | |
|
602 | 0 | for (int i = 0; i < 3; ++i) { |
603 | | // The GLSL specification requires that all workgroup size declarations must match |
604 | | // but not all units have to declare the layout. |
605 | 0 | if (unit.localSizeNotDefault[i]) { |
606 | 0 | if (!localSizeNotDefault[i]) { |
607 | 0 | localSize[i] = unit.localSize[i]; |
608 | 0 | localSizeNotDefault[i] = true; |
609 | 0 | } |
610 | 0 | else if (localSize[i] != unit.localSize[i]) |
611 | 0 | error(infoSink, "Contradictory local size"); |
612 | 0 | } |
613 | | |
614 | | // The GLSL specification requires that all workgroup size specialization |
615 | | // ids declarations must match, but not all units have to declare the layout. |
616 | 0 | if (localSizeSpecId[i] == TQualifier::layoutNotSet) |
617 | 0 | localSizeSpecId[i] = unit.localSizeSpecId[i]; |
618 | 0 | else if (unit.localSizeSpecId[i] != TQualifier::layoutNotSet && localSizeSpecId[i] != unit.localSizeSpecId[i]) |
619 | 0 | error(infoSink, "Contradictory local size specialization ids"); |
620 | 0 | } |
621 | |
|
622 | 0 | MERGE_TRUE(earlyFragmentTests); |
623 | 0 | MERGE_TRUE(postDepthCoverage); |
624 | 0 | MERGE_TRUE(nonCoherentColorAttachmentReadEXT); |
625 | 0 | MERGE_TRUE(nonCoherentDepthAttachmentReadEXT); |
626 | 0 | MERGE_TRUE(nonCoherentStencilAttachmentReadEXT); |
627 | 0 | MERGE_TRUE(nonCoherentTileAttachmentReadQCOM); |
628 | | |
629 | | // The GLSL specification requires that all depth layout redeclarations must match, |
630 | | // but not all units have to declare the layout. |
631 | 0 | if (depthLayout == EldNone) |
632 | 0 | depthLayout = unit.depthLayout; |
633 | 0 | else if (unit.depthLayout != EldNone && depthLayout != unit.depthLayout) |
634 | 0 | error(infoSink, "Contradictory depth layouts"); |
635 | |
|
636 | 0 | MERGE_TRUE(depthReplacing); |
637 | 0 | MERGE_TRUE(hlslFunctionality1); |
638 | |
|
639 | 0 | blendEquations |= unit.blendEquations; |
640 | |
|
641 | 0 | MERGE_TRUE(xfbMode); |
642 | |
|
643 | 0 | for (size_t b = 0; b < xfbBuffers.size(); ++b) { |
644 | | // The GLSL specification requires that all xfb_stride declarations for |
645 | | // the same buffer must match, but not all units have to declare the layout. |
646 | 0 | if (xfbBuffers[b].stride == TQualifier::layoutXfbStrideEnd) |
647 | 0 | xfbBuffers[b].stride = unit.xfbBuffers[b].stride; |
648 | 0 | else if (unit.xfbBuffers[b].stride != TQualifier::layoutXfbStrideEnd && xfbBuffers[b].stride != unit.xfbBuffers[b].stride) |
649 | 0 | error(infoSink, "Contradictory xfb_stride"); |
650 | 0 | xfbBuffers[b].implicitStride = std::max(xfbBuffers[b].implicitStride, unit.xfbBuffers[b].implicitStride); |
651 | 0 | if (unit.xfbBuffers[b].contains64BitType) |
652 | 0 | xfbBuffers[b].contains64BitType = true; |
653 | 0 | if (unit.xfbBuffers[b].contains32BitType) |
654 | 0 | xfbBuffers[b].contains32BitType = true; |
655 | 0 | if (unit.xfbBuffers[b].contains16BitType) |
656 | 0 | xfbBuffers[b].contains16BitType = true; |
657 | | // TODO: 4.4 link: enhanced layouts: compare ranges |
658 | 0 | } |
659 | |
|
660 | 0 | MERGE_TRUE(multiStream); |
661 | 0 | MERGE_TRUE(layoutOverrideCoverage); |
662 | 0 | MERGE_TRUE(geoPassthroughEXT); |
663 | |
|
664 | 0 | for (unsigned int i = 0; i < unit.shiftBinding.size(); ++i) { |
665 | 0 | if (unit.shiftBinding[i] > 0) |
666 | 0 | setShiftBinding((TResourceType)i, unit.shiftBinding[i]); |
667 | 0 | } |
668 | |
|
669 | 0 | for (unsigned int i = 0; i < unit.shiftBindingForSet.size(); ++i) { |
670 | 0 | for (auto it = unit.shiftBindingForSet[i].begin(); it != unit.shiftBindingForSet[i].end(); ++it) |
671 | 0 | setShiftBindingForSet((TResourceType)i, it->second, it->first); |
672 | 0 | } |
673 | |
|
674 | 0 | resourceSetBinding.insert(resourceSetBinding.end(), unit.resourceSetBinding.begin(), unit.resourceSetBinding.end()); |
675 | |
|
676 | 0 | MERGE_TRUE(autoMapBindings); |
677 | 0 | MERGE_TRUE(autoMapLocations); |
678 | 0 | MERGE_TRUE(invertY); |
679 | 0 | MERGE_TRUE(dxPositionW); |
680 | 0 | MERGE_TRUE(debugInfo); |
681 | 0 | MERGE_TRUE(flattenUniformArrays); |
682 | 0 | MERGE_TRUE(useUnknownFormat); |
683 | 0 | MERGE_TRUE(hlslOffsets); |
684 | 0 | MERGE_TRUE(useStorageBuffer); |
685 | 0 | MERGE_TRUE(invariantAll); |
686 | 0 | MERGE_TRUE(hlslIoMapping); |
687 | | |
688 | | // TODO: sourceFile |
689 | | // TODO: sourceText |
690 | | // TODO: processes |
691 | |
|
692 | 0 | MERGE_TRUE(needToLegalize); |
693 | 0 | MERGE_TRUE(binaryDoubleOutput); |
694 | 0 | MERGE_TRUE(usePhysicalStorageBuffer); |
695 | 0 | } |
696 | | |
697 | | // |
698 | | // Merge the 'unit' AST into 'this' AST. |
699 | | // That includes rationalizing the unique IDs, which were set up independently, |
700 | | // and might have overlaps that are not the same symbol, or might have different |
701 | | // IDs for what should be the same shared symbol. |
702 | | // |
703 | | void TIntermediate::mergeTrees(TInfoSink& infoSink, TIntermediate& unit) |
704 | 0 | { |
705 | 0 | if (unit.treeRoot == nullptr) |
706 | 0 | return; |
707 | | |
708 | 0 | if (treeRoot == nullptr) { |
709 | 0 | treeRoot = unit.treeRoot; |
710 | 0 | return; |
711 | 0 | } |
712 | | |
713 | | // Getting this far means we have two existing trees to merge... |
714 | 0 | numShaderRecordBlocks += unit.numShaderRecordBlocks; |
715 | 0 | numTaskNVBlocks += unit.numTaskNVBlocks; |
716 | | |
717 | | // Get the top-level globals of each unit |
718 | 0 | TIntermSequence& globals = treeRoot->getAsAggregate()->getSequence(); |
719 | 0 | TIntermSequence& unitGlobals = unit.treeRoot->getAsAggregate()->getSequence(); |
720 | | |
721 | | // Get the linker-object lists |
722 | 0 | TIntermSequence& linkerObjects = findLinkerObjects()->getSequence(); |
723 | 0 | const TIntermSequence& unitLinkerObjects = unit.findLinkerObjects()->getSequence(); |
724 | | |
725 | | // Map by global name to unique ID to rationalize the same object having |
726 | | // differing IDs in different trees. |
727 | 0 | TIdMaps idMaps; |
728 | 0 | long long idShift; |
729 | 0 | seedIdMap(idMaps, idShift); |
730 | 0 | remapIds(idMaps, idShift + 1, unit); |
731 | |
|
732 | 0 | mergeBodies(infoSink, globals, unitGlobals); |
733 | 0 | bool mergeExistingOnly = false; |
734 | 0 | mergeGlobalUniformBlocks(infoSink, unit, mergeExistingOnly); |
735 | 0 | mergeLinkerObjects(infoSink, linkerObjects, unitLinkerObjects, unit.getStage()); |
736 | 0 | ioAccessed.insert(unit.ioAccessed.begin(), unit.ioAccessed.end()); |
737 | 0 | } |
738 | | |
739 | | static const TString& getNameForIdMap(TIntermSymbol* symbol) |
740 | 0 | { |
741 | 0 | TShaderInterface si = symbol->getType().getShaderInterface(); |
742 | 0 | if (si == EsiNone) |
743 | 0 | return symbol->getName(); |
744 | 0 | else |
745 | 0 | return symbol->getType().getTypeName(); |
746 | 0 | } |
747 | | |
748 | | |
749 | | |
750 | | // Traverser that seeds an ID map with all built-ins, and tracks the |
751 | | // maximum ID used, currently using (maximum ID + 1) as new symbol id shift seed. |
752 | | // Level id will keep same after shifting. |
753 | | // (It would be nice to put this in a function, but that causes warnings |
754 | | // on having no bodies for the copy-constructor/operator=.) |
755 | | class TBuiltInIdTraverser : public TIntermTraverser { |
756 | | public: |
757 | 0 | TBuiltInIdTraverser(TIdMaps& idMaps) : idMaps(idMaps), idShift(0) { } |
758 | | // If it's a built in, add it to the map. |
759 | | virtual void visitSymbol(TIntermSymbol* symbol) |
760 | 0 | { |
761 | 0 | const TQualifier& qualifier = symbol->getType().getQualifier(); |
762 | 0 | if (qualifier.builtIn != EbvNone) { |
763 | 0 | TShaderInterface si = symbol->getType().getShaderInterface(); |
764 | 0 | idMaps[si][getNameForIdMap(symbol)] = symbol->getId(); |
765 | 0 | } |
766 | 0 | idShift = (symbol->getId() & ~TSymbolTable::uniqueIdMask) | |
767 | 0 | std::max(idShift & TSymbolTable::uniqueIdMask, |
768 | 0 | symbol->getId() & TSymbolTable::uniqueIdMask); |
769 | 0 | } |
770 | 0 | long long getIdShift() const { return idShift; } |
771 | | protected: |
772 | | TBuiltInIdTraverser(TBuiltInIdTraverser&); |
773 | | TBuiltInIdTraverser& operator=(TBuiltInIdTraverser&); |
774 | | TIdMaps& idMaps; |
775 | | long long idShift; |
776 | | }; |
777 | | |
778 | | // Traverser that seeds an ID map with non-builtins. |
779 | | // (It would be nice to put this in a function, but that causes warnings |
780 | | // on having no bodies for the copy-constructor/operator=.) |
781 | | class TUserIdTraverser : public TIntermTraverser { |
782 | | public: |
783 | 0 | TUserIdTraverser(TIdMaps& idMaps) : idMaps(idMaps) { } |
784 | | // If its a non-built-in global, add it to the map. |
785 | | virtual void visitSymbol(TIntermSymbol* symbol) |
786 | 0 | { |
787 | 0 | const TQualifier& qualifier = symbol->getType().getQualifier(); |
788 | 0 | if (qualifier.builtIn == EbvNone) { |
789 | 0 | TShaderInterface si = symbol->getType().getShaderInterface(); |
790 | 0 | idMaps[si][getNameForIdMap(symbol)] = symbol->getId(); |
791 | 0 | } |
792 | 0 | } |
793 | | |
794 | | protected: |
795 | | TUserIdTraverser(TUserIdTraverser&); |
796 | | TUserIdTraverser& operator=(TUserIdTraverser&); |
797 | | TIdMaps& idMaps; // over biggest id |
798 | | }; |
799 | | |
800 | | // Initialize the the ID map with what we know of 'this' AST. |
801 | | void TIntermediate::seedIdMap(TIdMaps& idMaps, long long& idShift) |
802 | 0 | { |
803 | | // all built-ins everywhere need to align on IDs and contribute to the max ID |
804 | 0 | TBuiltInIdTraverser builtInIdTraverser(idMaps); |
805 | 0 | treeRoot->traverse(&builtInIdTraverser); |
806 | 0 | idShift = builtInIdTraverser.getIdShift() & TSymbolTable::uniqueIdMask; |
807 | | |
808 | | // user variables in the linker object list need to align on ids |
809 | 0 | TUserIdTraverser userIdTraverser(idMaps); |
810 | 0 | findLinkerObjects()->traverse(&userIdTraverser); |
811 | 0 | } |
812 | | |
813 | | // Traverser to map an AST ID to what was known from the seeding AST. |
814 | | // (It would be nice to put this in a function, but that causes warnings |
815 | | // on having no bodies for the copy-constructor/operator=.) |
816 | | class TRemapIdTraverser : public TIntermTraverser { |
817 | | public: |
818 | 0 | TRemapIdTraverser(const TIdMaps& idMaps, long long idShift) : idMaps(idMaps), idShift(idShift) { } |
819 | | // Do the mapping: |
820 | | // - if the same symbol, adopt the 'this' ID |
821 | | // - otherwise, ensure a unique ID by shifting to a new space |
822 | | virtual void visitSymbol(TIntermSymbol* symbol) |
823 | 0 | { |
824 | 0 | const TQualifier& qualifier = symbol->getType().getQualifier(); |
825 | 0 | bool remapped = false; |
826 | 0 | if (qualifier.isLinkable() || qualifier.builtIn != EbvNone) { |
827 | 0 | TShaderInterface si = symbol->getType().getShaderInterface(); |
828 | 0 | auto it = idMaps[si].find(getNameForIdMap(symbol)); |
829 | 0 | if (it != idMaps[si].end()) { |
830 | 0 | uint64_t id = (symbol->getId() & ~TSymbolTable::uniqueIdMask) | |
831 | 0 | (it->second & TSymbolTable::uniqueIdMask); |
832 | 0 | symbol->changeId(id); |
833 | 0 | remapped = true; |
834 | 0 | } |
835 | 0 | } |
836 | 0 | if (!remapped) |
837 | 0 | symbol->changeId(symbol->getId() + idShift); |
838 | 0 | } |
839 | | protected: |
840 | | TRemapIdTraverser(TRemapIdTraverser&); |
841 | | TRemapIdTraverser& operator=(TRemapIdTraverser&); |
842 | | const TIdMaps& idMaps; |
843 | | long long idShift; |
844 | | }; |
845 | | |
846 | | void TIntermediate::remapIds(const TIdMaps& idMaps, long long idShift, TIntermediate& unit) |
847 | 0 | { |
848 | | // Remap all IDs to either share or be unique, as dictated by the idMap and idShift. |
849 | 0 | TRemapIdTraverser idTraverser(idMaps, idShift); |
850 | 0 | unit.getTreeRoot()->traverse(&idTraverser); |
851 | 0 | } |
852 | | |
853 | | // |
854 | | // Merge the function bodies and global-level initializers from unitGlobals into globals. |
855 | | // Will error check duplication of function bodies for the same signature. |
856 | | // |
857 | | void TIntermediate::mergeBodies(TInfoSink& infoSink, TIntermSequence& globals, const TIntermSequence& unitGlobals) |
858 | 0 | { |
859 | | // TODO: link-time performance: Processing in alphabetical order will be faster |
860 | | |
861 | | // Error check the global objects, not including the linker objects |
862 | 0 | for (unsigned int child = 0; child < globals.size() - 1; ++child) { |
863 | 0 | for (unsigned int unitChild = 0; unitChild < unitGlobals.size() - 1; ++unitChild) { |
864 | 0 | TIntermAggregate* body = globals[child]->getAsAggregate(); |
865 | 0 | TIntermAggregate* unitBody = unitGlobals[unitChild]->getAsAggregate(); |
866 | 0 | if (body && unitBody && body->getOp() == EOpFunction && unitBody->getOp() == EOpFunction && body->getName() == unitBody->getName()) { |
867 | 0 | error(infoSink, "Multiple function bodies in multiple compilation units for the same signature in the same stage:"); |
868 | 0 | infoSink.info << " " << globals[child]->getAsAggregate()->getName() << "\n"; |
869 | 0 | } |
870 | 0 | } |
871 | 0 | } |
872 | | |
873 | | // Merge the global objects, just in front of the linker objects |
874 | 0 | globals.insert(globals.end() - 1, unitGlobals.begin(), unitGlobals.end() - 1); |
875 | 0 | } |
876 | | |
877 | | // |
878 | | // Global Unfiform block stores any default uniforms (i.e. uniforms without a block) |
879 | | // If two linked stages declare the same member, they are meant to be the same uniform |
880 | | // and need to be in the same block |
881 | | // merge the members of different stages to allow them to be linked properly |
882 | | // as a single block |
883 | | // |
884 | | void TIntermediate::mergeGlobalUniformBlocks(TInfoSink& infoSink, TIntermediate& unit, bool mergeExistingOnly) |
885 | 0 | { |
886 | 0 | TIntermSequence& linkerObjects = findLinkerObjects()->getSequence(); |
887 | 0 | TIntermSequence& unitLinkerObjects = unit.findLinkerObjects()->getSequence(); |
888 | | |
889 | | // build lists of default blocks from the intermediates |
890 | 0 | TIntermSequence defaultBlocks; |
891 | 0 | TIntermSequence unitDefaultBlocks; |
892 | |
|
893 | 0 | auto filter = [](TIntermSequence& list, TIntermNode* node) { |
894 | 0 | if (node->getAsSymbolNode()->getQualifier().defaultBlock) { |
895 | 0 | list.push_back(node); |
896 | 0 | } |
897 | 0 | }; |
898 | |
|
899 | 0 | std::for_each(linkerObjects.begin(), linkerObjects.end(), |
900 | 0 | [&defaultBlocks, &filter](TIntermNode* node) { |
901 | 0 | filter(defaultBlocks, node); |
902 | 0 | }); |
903 | 0 | std::for_each(unitLinkerObjects.begin(), unitLinkerObjects.end(), |
904 | 0 | [&unitDefaultBlocks, &filter](TIntermNode* node) { |
905 | 0 | filter(unitDefaultBlocks, node); |
906 | 0 | }); |
907 | |
|
908 | 0 | auto itUnitBlock = unitDefaultBlocks.begin(); |
909 | 0 | for (; itUnitBlock != unitDefaultBlocks.end(); itUnitBlock++) { |
910 | |
|
911 | 0 | bool add = !mergeExistingOnly; |
912 | 0 | auto itBlock = defaultBlocks.begin(); |
913 | |
|
914 | 0 | for (; itBlock != defaultBlocks.end(); itBlock++) { |
915 | 0 | TIntermSymbol* block = (*itBlock)->getAsSymbolNode(); |
916 | 0 | TIntermSymbol* unitBlock = (*itUnitBlock)->getAsSymbolNode(); |
917 | |
|
918 | 0 | assert(block && unitBlock); |
919 | | |
920 | | // if the two default blocks match, then merge their definitions |
921 | 0 | if (block->getType().getTypeName() == unitBlock->getType().getTypeName() && |
922 | 0 | block->getQualifier().storage == unitBlock->getQualifier().storage) { |
923 | 0 | add = false; |
924 | 0 | mergeBlockDefinitions(infoSink, block, unitBlock, &unit); |
925 | 0 | } |
926 | 0 | } |
927 | 0 | if (add) { |
928 | | // push back on original list; won't change the size of the list we're iterating over |
929 | 0 | linkerObjects.push_back(*itUnitBlock); |
930 | 0 | } |
931 | 0 | } |
932 | 0 | } |
933 | | |
934 | 0 | void TIntermediate::mergeBlockDefinitions(TInfoSink& infoSink, TIntermSymbol* block, TIntermSymbol* unitBlock, TIntermediate* unit) { |
935 | |
|
936 | 0 | if (block->getType().getTypeName() != unitBlock->getType().getTypeName() || |
937 | 0 | block->getType().getBasicType() != unitBlock->getType().getBasicType() || |
938 | 0 | block->getQualifier().storage != unitBlock->getQualifier().storage || |
939 | 0 | block->getQualifier().layoutSet != unitBlock->getQualifier().layoutSet) { |
940 | | // different block names likely means different blocks |
941 | 0 | return; |
942 | 0 | } |
943 | | |
944 | | // merge the struct |
945 | | // order of declarations doesn't matter and they matched based on member name |
946 | 0 | TTypeList* memberList = block->getType().getWritableStruct(); |
947 | 0 | TTypeList* unitMemberList = unitBlock->getType().getWritableStruct(); |
948 | | |
949 | | // keep track of which members have changed position |
950 | | // so we don't have to search the array again |
951 | 0 | std::map<unsigned int, unsigned int> memberIndexUpdates; |
952 | |
|
953 | 0 | size_t memberListStartSize = memberList->size(); |
954 | 0 | for (unsigned int i = 0; i < unitMemberList->size(); ++i) { |
955 | 0 | bool merge = true; |
956 | 0 | for (unsigned int j = 0; j < memberListStartSize; ++j) { |
957 | 0 | if ((*memberList)[j].type->getFieldName() == (*unitMemberList)[i].type->getFieldName()) { |
958 | 0 | merge = false; |
959 | 0 | const TType* memberType = (*memberList)[j].type; |
960 | 0 | const TType* unitMemberType = (*unitMemberList)[i].type; |
961 | | |
962 | | // compare types |
963 | | // don't need as many checks as when merging symbols, since |
964 | | // initializers and most qualifiers are stripped when the member is moved into the block |
965 | 0 | if ((*memberType) != (*unitMemberType)) { |
966 | 0 | error(infoSink, "Types must match:", unitBlock->getStage()); |
967 | 0 | infoSink.info << " " << memberType->getFieldName() << ": "; |
968 | 0 | infoSink.info << "\"" << memberType->getCompleteString() << "\" in stage " << StageName(block->getStage()) << " versus "; |
969 | 0 | infoSink.info << "\"" << unitMemberType->getCompleteString() << "\" in stage " << StageName(unitBlock->getStage()) << "\n"; |
970 | 0 | } |
971 | |
|
972 | 0 | memberIndexUpdates[i] = j; |
973 | 0 | } |
974 | 0 | } |
975 | 0 | if (merge) { |
976 | 0 | memberList->push_back((*unitMemberList)[i]); |
977 | 0 | memberIndexUpdates[i] = (unsigned int)memberList->size() - 1; |
978 | 0 | } |
979 | 0 | } |
980 | | |
981 | | // update symbol node in unit tree, |
982 | | // and other nodes that may reference it |
983 | 0 | class TMergeBlockTraverser : public TIntermTraverser { |
984 | 0 | public: |
985 | 0 | TMergeBlockTraverser(const TIntermSymbol* newSym) |
986 | 0 | : newSymbol(newSym), newType(nullptr), unit(nullptr), memberIndexUpdates(nullptr) |
987 | 0 | { |
988 | 0 | } |
989 | 0 | TMergeBlockTraverser(const TIntermSymbol* newSym, const glslang::TType* unitType, glslang::TIntermediate* unit, |
990 | 0 | const std::map<unsigned int, unsigned int>* memberIdxUpdates) |
991 | 0 | : TIntermTraverser(false, true), newSymbol(newSym), newType(unitType), unit(unit), memberIndexUpdates(memberIdxUpdates) |
992 | 0 | { |
993 | 0 | } |
994 | 0 | virtual ~TMergeBlockTraverser() {} |
995 | |
|
996 | 0 | const TIntermSymbol* newSymbol; |
997 | 0 | const glslang::TType* newType; // shallow copy of the new type |
998 | 0 | glslang::TIntermediate* unit; // intermediate that is being updated |
999 | 0 | const std::map<unsigned int, unsigned int>* memberIndexUpdates; |
1000 | |
|
1001 | 0 | virtual void visitSymbol(TIntermSymbol* symbol) |
1002 | 0 | { |
1003 | 0 | if (newSymbol->getAccessName() == symbol->getAccessName() && |
1004 | 0 | newSymbol->getQualifier().getBlockStorage() == symbol->getQualifier().getBlockStorage()) { |
1005 | | // Each symbol node may have a local copy of the block structure. |
1006 | | // Update those structures to match the new one post-merge |
1007 | 0 | *(symbol->getWritableType().getWritableStruct()) = *(newSymbol->getType().getStruct()); |
1008 | 0 | } |
1009 | 0 | } |
1010 | |
|
1011 | 0 | virtual bool visitBinary(TVisit, glslang::TIntermBinary* node) |
1012 | 0 | { |
1013 | 0 | if (!unit || !newType || !memberIndexUpdates || memberIndexUpdates->empty()) |
1014 | 0 | return true; |
1015 | | |
1016 | 0 | if (node->getOp() == EOpIndexDirectStruct && node->getLeft()->getType() == *newType) { |
1017 | | // this is a dereference to a member of the block since the |
1018 | | // member list changed, need to update this to point to the |
1019 | | // right index |
1020 | 0 | assert(node->getRight()->getAsConstantUnion()); |
1021 | |
|
1022 | 0 | glslang::TIntermConstantUnion* constNode = node->getRight()->getAsConstantUnion(); |
1023 | 0 | unsigned int memberIdx = constNode->getConstArray()[0].getUConst(); |
1024 | 0 | unsigned int newIdx = memberIndexUpdates->at(memberIdx); |
1025 | 0 | TIntermTyped* newConstNode = unit->addConstantUnion(newIdx, node->getRight()->getLoc()); |
1026 | |
|
1027 | 0 | node->setRight(newConstNode); |
1028 | 0 | delete constNode; |
1029 | |
|
1030 | 0 | return true; |
1031 | 0 | } |
1032 | 0 | return true; |
1033 | 0 | } |
1034 | 0 | }; |
1035 | | |
1036 | | // 'this' may have symbols that are using the old block structure, so traverse the tree to update those |
1037 | | // in 'visitSymbol' |
1038 | 0 | TMergeBlockTraverser finalLinkTraverser(block); |
1039 | 0 | getTreeRoot()->traverse(&finalLinkTraverser); |
1040 | | |
1041 | | // The 'unit' intermediate needs the block structures update, but also structure entry indices |
1042 | | // may have changed from the old block to the new one that it was merged into, so update those |
1043 | | // in 'visitBinary' |
1044 | 0 | TType newType; |
1045 | 0 | newType.shallowCopy(block->getType()); |
1046 | 0 | TMergeBlockTraverser unitFinalLinkTraverser(block, &newType, unit, &memberIndexUpdates); |
1047 | 0 | unit->getTreeRoot()->traverse(&unitFinalLinkTraverser); |
1048 | | |
1049 | | // update the member list |
1050 | 0 | (*unitMemberList) = (*memberList); |
1051 | 0 | } |
1052 | | |
1053 | | // |
1054 | | // Merge the linker objects from unitLinkerObjects into linkerObjects. |
1055 | | // Duplication is expected and filtered out, but contradictions are an error. |
1056 | | // |
1057 | | void TIntermediate::mergeLinkerObjects(TInfoSink& infoSink, TIntermSequence& linkerObjects, const TIntermSequence& unitLinkerObjects, EShLanguage unitStage) |
1058 | 0 | { |
1059 | | // Error check and merge the linker objects (duplicates should not be created) |
1060 | 0 | std::size_t initialNumLinkerObjects = linkerObjects.size(); |
1061 | 0 | for (unsigned int unitLinkObj = 0; unitLinkObj < unitLinkerObjects.size(); ++unitLinkObj) { |
1062 | 0 | bool merge = true; |
1063 | 0 | for (std::size_t linkObj = 0; linkObj < initialNumLinkerObjects; ++linkObj) { |
1064 | 0 | TIntermSymbol* symbol = linkerObjects[linkObj]->getAsSymbolNode(); |
1065 | 0 | TIntermSymbol* unitSymbol = unitLinkerObjects[unitLinkObj]->getAsSymbolNode(); |
1066 | 0 | assert(symbol && unitSymbol); |
1067 | |
|
1068 | 0 | if (isSameSymbol(symbol, unitSymbol)) { |
1069 | | // filter out copy |
1070 | 0 | merge = false; |
1071 | | |
1072 | | // but if one has an initializer and the other does not, update |
1073 | | // the initializer |
1074 | 0 | if (symbol->getConstArray().empty() && ! unitSymbol->getConstArray().empty()) |
1075 | 0 | symbol->setConstArray(unitSymbol->getConstArray()); |
1076 | | |
1077 | | // Similarly for binding |
1078 | 0 | if (! symbol->getQualifier().hasBinding() && unitSymbol->getQualifier().hasBinding()) |
1079 | 0 | symbol->getQualifier().layoutBinding = unitSymbol->getQualifier().layoutBinding; |
1080 | | |
1081 | | // Similarly for location |
1082 | 0 | if (!symbol->getQualifier().hasLocation() && unitSymbol->getQualifier().hasLocation()) { |
1083 | 0 | symbol->getQualifier().layoutLocation = unitSymbol->getQualifier().layoutLocation; |
1084 | 0 | } |
1085 | | |
1086 | | // Update implicit array sizes |
1087 | 0 | if (symbol->getWritableType().isImplicitlySizedArray() && unitSymbol->getType().isImplicitlySizedArray()) { |
1088 | 0 | if (unitSymbol->getType().getImplicitArraySize() > symbol->getType().getImplicitArraySize()){ |
1089 | 0 | symbol->getWritableType().updateImplicitArraySize(unitSymbol->getType().getImplicitArraySize()); |
1090 | 0 | } |
1091 | 0 | } |
1092 | 0 | else if (symbol->getWritableType().isImplicitlySizedArray() && unitSymbol->getType().isSizedArray()) { |
1093 | 0 | if (symbol->getWritableType().getImplicitArraySize() > unitSymbol->getType().getOuterArraySize()) |
1094 | 0 | error(infoSink, "Implicit size of unsized array doesn't match same symbol among multiple shaders.", unitStage); |
1095 | 0 | } |
1096 | 0 | else if (unitSymbol->getType().isImplicitlySizedArray() && symbol->getWritableType().isSizedArray()) { |
1097 | 0 | if (unitSymbol->getType().getImplicitArraySize() > symbol->getWritableType().getOuterArraySize()) |
1098 | 0 | error(infoSink, "Implicit size of unsized array doesn't match same symbol among multiple shaders.", unitStage); |
1099 | 0 | } |
1100 | |
|
1101 | 0 | if (symbol->getType().isStruct() && unitSymbol->getType().isStruct() && |
1102 | 0 | symbol->getType().getStruct()->size() == unitSymbol->getType().getStruct()->size()) { |
1103 | 0 | for (int i = 0; i < (int)symbol->getType().getStruct()->size(); ++i) { |
1104 | 0 | auto& type = (*symbol->getWritableType().getStruct())[i]; |
1105 | 0 | auto& unitType = (*unitSymbol->getWritableType().getStruct())[i]; |
1106 | |
|
1107 | 0 | if (type.type->isImplicitlySizedArray() && unitType.type->isImplicitlySizedArray()) { |
1108 | 0 | if (unitType.type->getImplicitArraySize() > type.type->getImplicitArraySize()) |
1109 | 0 | type.type->updateImplicitArraySize(unitType.type->getImplicitArraySize()); |
1110 | 0 | } |
1111 | 0 | else if (type.type->isImplicitlySizedArray() && unitType.type->isSizedArray()) { |
1112 | 0 | if (type.type->getImplicitArraySize() > unitType.type->getOuterArraySize()) |
1113 | 0 | error(infoSink, "Implicit size of unsized array doesn't match same symbol among multiple shaders.", unitStage); |
1114 | 0 | } |
1115 | 0 | else if (type.type->isSizedArray() && unitType.type->isImplicitlySizedArray()) { |
1116 | 0 | if (type.type->getOuterArraySize() < unitType.type->getImplicitArraySize()) |
1117 | 0 | error(infoSink, "Implicit size of unsized array doesn't match same symbol among multiple shaders.", unitStage); |
1118 | 0 | } |
1119 | 0 | } |
1120 | 0 | } |
1121 | | |
1122 | | // Update implicit array sizes |
1123 | 0 | mergeImplicitArraySizes(symbol->getWritableType(), unitSymbol->getType()); |
1124 | | |
1125 | | // Check for consistent types/qualification/initializers etc. |
1126 | 0 | mergeErrorCheck(infoSink, *symbol, *unitSymbol); |
1127 | 0 | } |
1128 | | // If different symbols, verify they arn't push_constant since there can only be one per stage |
1129 | 0 | else if (!IsRequestedExtension(glslang::E_GL_NV_push_constant_bank) && |
1130 | 0 | (symbol->getQualifier().isPushConstant() && unitSymbol->getQualifier().isPushConstant() && getStage() == unitStage)) |
1131 | 0 | error(infoSink, "Only one push_constant block is allowed per stage"); |
1132 | 0 | } |
1133 | | |
1134 | | // Check conflicts between preset primitives and sizes of I/O variables among multiple geometry shaders |
1135 | 0 | if (language == EShLangGeometry && unitStage == EShLangGeometry) |
1136 | 0 | { |
1137 | 0 | TIntermSymbol* unitSymbol = unitLinkerObjects[unitLinkObj]->getAsSymbolNode(); |
1138 | 0 | if (unitSymbol->isArray() && unitSymbol->getQualifier().storage == EvqVaryingIn && unitSymbol->getQualifier().builtIn == EbvNone) |
1139 | 0 | if ((unitSymbol->getArraySizes()->isImplicitlySized() && |
1140 | 0 | unitSymbol->getArraySizes()->getImplicitSize() != TQualifier::mapGeometryToSize(getInputPrimitive())) || |
1141 | 0 | (! unitSymbol->getArraySizes()->isImplicitlySized() && |
1142 | 0 | unitSymbol->getArraySizes()->getDimSize(0) != TQualifier::mapGeometryToSize(getInputPrimitive()))) |
1143 | 0 | error(infoSink, "Not all array sizes match across all geometry shaders in the program"); |
1144 | 0 | } |
1145 | |
|
1146 | 0 | if (merge) { |
1147 | 0 | linkerObjects.push_back(unitLinkerObjects[unitLinkObj]); |
1148 | | |
1149 | | // for anonymous blocks, check that their members don't conflict with other names |
1150 | 0 | if (unitLinkerObjects[unitLinkObj]->getAsSymbolNode()->getBasicType() == EbtBlock && |
1151 | 0 | IsAnonymous(unitLinkerObjects[unitLinkObj]->getAsSymbolNode()->getName())) { |
1152 | 0 | for (std::size_t linkObj = 0; linkObj < initialNumLinkerObjects; ++linkObj) { |
1153 | 0 | TIntermSymbol* symbol = linkerObjects[linkObj]->getAsSymbolNode(); |
1154 | 0 | TIntermSymbol* unitSymbol = unitLinkerObjects[unitLinkObj]->getAsSymbolNode(); |
1155 | 0 | assert(symbol && unitSymbol); |
1156 | |
|
1157 | 0 | auto checkName = [this, unitSymbol, &infoSink](const TString& name) { |
1158 | 0 | for (unsigned int i = 0; i < unitSymbol->getType().getStruct()->size(); ++i) { |
1159 | 0 | if (name == (*unitSymbol->getType().getStruct())[i].type->getFieldName() |
1160 | 0 | && !((*unitSymbol->getType().getStruct())[i].type->getQualifier().hasLocation() |
1161 | 0 | || unitSymbol->getType().getQualifier().hasLocation()) |
1162 | 0 | ) { |
1163 | 0 | error(infoSink, "Anonymous member name used for global variable or other anonymous member: "); |
1164 | 0 | infoSink.info << (*unitSymbol->getType().getStruct())[i].type->getCompleteString() << "\n"; |
1165 | 0 | } |
1166 | 0 | } |
1167 | 0 | }; |
1168 | |
|
1169 | 0 | if (isSameInterface(symbol, unitSymbol)) { |
1170 | 0 | checkName(symbol->getName()); |
1171 | | |
1172 | | // check members of other anonymous blocks |
1173 | 0 | if (symbol->getBasicType() == EbtBlock && IsAnonymous(symbol->getName())) { |
1174 | 0 | for (unsigned int i = 0; i < symbol->getType().getStruct()->size(); ++i) { |
1175 | 0 | checkName((*symbol->getType().getStruct())[i].type->getFieldName()); |
1176 | 0 | } |
1177 | 0 | } |
1178 | 0 | } |
1179 | 0 | } |
1180 | 0 | } |
1181 | 0 | } |
1182 | 0 | } |
1183 | 0 | } |
1184 | | |
1185 | | // TODO 4.5 link functionality: cull distance array size checking |
1186 | | |
1187 | | // Recursively merge the implicit array sizes through the objects' respective type trees. |
1188 | | void TIntermediate::mergeImplicitArraySizes(TType& type, const TType& unitType) |
1189 | 0 | { |
1190 | 0 | if (type.isUnsizedArray()) { |
1191 | 0 | if (unitType.isUnsizedArray()) { |
1192 | 0 | type.updateImplicitArraySize(unitType.getImplicitArraySize()); |
1193 | 0 | if (unitType.isArrayVariablyIndexed()) |
1194 | 0 | type.setArrayVariablyIndexed(); |
1195 | 0 | } else if (unitType.isSizedArray()) |
1196 | 0 | type.changeOuterArraySize(unitType.getOuterArraySize()); |
1197 | 0 | } |
1198 | | |
1199 | | // Type mismatches are caught and reported after this, just be careful for now. |
1200 | 0 | if (! type.isStruct() || ! unitType.isStruct() || type.getStruct()->size() != unitType.getStruct()->size()) |
1201 | 0 | return; |
1202 | | |
1203 | 0 | for (int i = 0; i < (int)type.getStruct()->size(); ++i) |
1204 | 0 | mergeImplicitArraySizes(*(*type.getStruct())[i].type, *(*unitType.getStruct())[i].type); |
1205 | 0 | } |
1206 | | |
1207 | | // |
1208 | | // Compare two global objects from two compilation units and see if they match |
1209 | | // well enough. Rules can be different for intra- vs. cross-stage matching. |
1210 | | // |
1211 | | // This function only does one of intra- or cross-stage matching per call. |
1212 | | // |
1213 | | void TIntermediate::mergeErrorCheck(TInfoSink& infoSink, const TIntermSymbol& symbol, const TIntermSymbol& unitSymbol) |
1214 | 0 | { |
1215 | 0 | EShLanguage stage = symbol.getStage(); |
1216 | 0 | EShLanguage unitStage = unitSymbol.getStage(); |
1217 | 0 | bool crossStage = stage != unitStage; |
1218 | 0 | bool writeTypeComparison = false; |
1219 | 0 | bool errorReported = false; |
1220 | 0 | bool printQualifiers = false; |
1221 | 0 | bool printPrecision = false; |
1222 | 0 | bool printType = false; |
1223 | | |
1224 | | // Types have to match |
1225 | 0 | { |
1226 | | // but, we make an exception if one is an implicit array and the other is sized |
1227 | | // or if the array sizes differ because of the extra array dimension on some in/out boundaries |
1228 | 0 | bool arraysMatch = false; |
1229 | 0 | if (isIoResizeArray(symbol.getType(), stage) || isIoResizeArray(unitSymbol.getType(), unitStage)) { |
1230 | | // if the arrays have an extra dimension because of the stage. |
1231 | | // compare dimensions while ignoring the outer dimension |
1232 | 0 | unsigned int firstDim = isIoResizeArray(symbol.getType(), stage) ? 1 : 0; |
1233 | 0 | unsigned int numDim = symbol.getArraySizes() |
1234 | 0 | ? symbol.getArraySizes()->getNumDims() : 0; |
1235 | 0 | unsigned int unitFirstDim = isIoResizeArray(unitSymbol.getType(), unitStage) ? 1 : 0; |
1236 | 0 | unsigned int unitNumDim = unitSymbol.getArraySizes() |
1237 | 0 | ? unitSymbol.getArraySizes()->getNumDims() : 0; |
1238 | 0 | arraysMatch = (numDim - firstDim) == (unitNumDim - unitFirstDim); |
1239 | | // check that array sizes match as well |
1240 | 0 | for (unsigned int i = 0; i < (numDim - firstDim) && arraysMatch; i++) { |
1241 | 0 | if (symbol.getArraySizes()->getDimSize(firstDim + i) != |
1242 | 0 | unitSymbol.getArraySizes()->getDimSize(unitFirstDim + i)) { |
1243 | 0 | arraysMatch = false; |
1244 | 0 | break; |
1245 | 0 | } |
1246 | 0 | } |
1247 | 0 | } |
1248 | 0 | else { |
1249 | 0 | arraysMatch = symbol.getType().sameArrayness(unitSymbol.getType()) || |
1250 | 0 | (symbol.getType().isArray() && unitSymbol.getType().isArray() && |
1251 | 0 | (symbol.getType().isImplicitlySizedArray() || unitSymbol.getType().isImplicitlySizedArray() || |
1252 | 0 | symbol.getType().isUnsizedArray() || unitSymbol.getType().isUnsizedArray())); |
1253 | 0 | } |
1254 | |
|
1255 | 0 | int lpidx = -1; |
1256 | 0 | int rpidx = -1; |
1257 | 0 | if (!symbol.getType().sameElementType(unitSymbol.getType(), &lpidx, &rpidx)) { |
1258 | 0 | if (lpidx >= 0 && rpidx >= 0) { |
1259 | 0 | error(infoSink, "Member names and types must match:", unitStage); |
1260 | 0 | infoSink.info << " Block: " << symbol.getType().getTypeName() << "\n"; |
1261 | 0 | infoSink.info << " " << StageName(stage) << " stage: \"" |
1262 | 0 | << (*symbol.getType().getStruct())[lpidx].type->getCompleteString(true, false, false, true, |
1263 | 0 | (*symbol.getType().getStruct())[lpidx].type->getFieldName()) << "\"\n"; |
1264 | 0 | infoSink.info << " " << StageName(unitStage) << " stage: \"" |
1265 | 0 | << (*unitSymbol.getType().getStruct())[rpidx].type->getCompleteString(true, false, false, true, |
1266 | 0 | (*unitSymbol.getType().getStruct())[rpidx].type->getFieldName()) << "\"\n"; |
1267 | 0 | errorReported = true; |
1268 | 0 | } else if (lpidx >= 0 && rpidx == -1) { |
1269 | 0 | TString errmsg = StageName(stage); |
1270 | 0 | errmsg.append(" block member has no corresponding member in ").append(StageName(unitStage)).append(" block:"); |
1271 | 0 | error(infoSink, errmsg.c_str(), unitStage); |
1272 | 0 | infoSink.info << " " << StageName(stage) << " stage: Block: " << symbol.getType().getTypeName() << ", Member: " |
1273 | 0 | << (*symbol.getType().getStruct())[lpidx].type->getFieldName() << "\n"; |
1274 | 0 | infoSink.info << " " << StageName(unitStage) << " stage: Block: " << unitSymbol.getType().getTypeName() << ", Member: n/a \n"; |
1275 | 0 | errorReported = true; |
1276 | 0 | } else if (lpidx == -1 && rpidx >= 0) { |
1277 | 0 | TString errmsg = StageName(unitStage); |
1278 | 0 | errmsg.append(" block member has no corresponding member in ").append(StageName(stage)).append(" block:"); |
1279 | 0 | error(infoSink, errmsg.c_str(), unitStage); |
1280 | 0 | infoSink.info << " " << StageName(unitStage) << " stage: Block: " << unitSymbol.getType().getTypeName() << ", Member: " |
1281 | 0 | << (*unitSymbol.getType().getStruct())[rpidx].type->getFieldName() << "\n"; |
1282 | 0 | infoSink.info << " " << StageName(stage) << " stage: Block: " << symbol.getType().getTypeName() << ", Member: n/a \n"; |
1283 | 0 | errorReported = true; |
1284 | 0 | } else { |
1285 | 0 | error(infoSink, "Types must match:", unitStage); |
1286 | 0 | writeTypeComparison = true; |
1287 | 0 | printType = true; |
1288 | 0 | } |
1289 | 0 | } else if (!arraysMatch) { |
1290 | 0 | error(infoSink, "Array sizes must be compatible:", unitStage); |
1291 | 0 | writeTypeComparison = true; |
1292 | 0 | printType = true; |
1293 | 0 | } else if (!symbol.getType().sameTypeParameters(unitSymbol.getType())) { |
1294 | 0 | error(infoSink, "Type parameters must match:", unitStage); |
1295 | 0 | writeTypeComparison = true; |
1296 | 0 | printType = true; |
1297 | 0 | } |
1298 | 0 | } |
1299 | | |
1300 | | // Interface block member-wise layout qualifiers have to match |
1301 | 0 | if (symbol.getType().getBasicType() == EbtBlock && unitSymbol.getType().getBasicType() == EbtBlock && |
1302 | 0 | symbol.getType().getStruct() && unitSymbol.getType().getStruct() && |
1303 | 0 | symbol.getType().sameStructType(unitSymbol.getType())) { |
1304 | 0 | unsigned int li = 0; |
1305 | 0 | unsigned int ri = 0; |
1306 | 0 | while (li < symbol.getType().getStruct()->size() && ri < unitSymbol.getType().getStruct()->size()) { |
1307 | 0 | if ((*symbol.getType().getStruct())[li].type->hiddenMember()) { |
1308 | 0 | ++li; |
1309 | 0 | continue; |
1310 | 0 | } |
1311 | 0 | if ((*unitSymbol.getType().getStruct())[ri].type->hiddenMember()) { |
1312 | 0 | ++ri; |
1313 | 0 | continue; |
1314 | 0 | } |
1315 | 0 | const TQualifier& qualifier = (*symbol.getType().getStruct())[li].type->getQualifier(); |
1316 | 0 | const TQualifier & unitQualifier = (*unitSymbol.getType().getStruct())[ri].type->getQualifier(); |
1317 | 0 | bool layoutQualifierError = false; |
1318 | 0 | if (qualifier.layoutMatrix != unitQualifier.layoutMatrix) { |
1319 | 0 | error(infoSink, "Interface block member layout matrix qualifier must match:", unitStage); |
1320 | 0 | layoutQualifierError = true; |
1321 | 0 | } |
1322 | 0 | if (qualifier.layoutOffset != unitQualifier.layoutOffset) { |
1323 | 0 | error(infoSink, "Interface block member layout offset qualifier must match:", unitStage); |
1324 | 0 | layoutQualifierError = true; |
1325 | 0 | } |
1326 | 0 | if (qualifier.layoutAlign != unitQualifier.layoutAlign) { |
1327 | 0 | error(infoSink, "Interface block member layout align qualifier must match:", unitStage); |
1328 | 0 | layoutQualifierError = true; |
1329 | 0 | } |
1330 | 0 | if (qualifier.layoutLocation != unitQualifier.layoutLocation) { |
1331 | 0 | error(infoSink, "Interface block member layout location qualifier must match:", unitStage); |
1332 | 0 | layoutQualifierError = true; |
1333 | 0 | } |
1334 | 0 | if (qualifier.layoutComponent != unitQualifier.layoutComponent) { |
1335 | 0 | error(infoSink, "Interface block member layout component qualifier must match:", unitStage); |
1336 | 0 | layoutQualifierError = true; |
1337 | 0 | } |
1338 | 0 | if (layoutQualifierError) { |
1339 | 0 | infoSink.info << " " << StageName(stage) << " stage: Block: " << symbol.getType().getTypeName() << ", Member: " |
1340 | 0 | << (*symbol.getType().getStruct())[li].type->getFieldName() << " \"" |
1341 | 0 | << (*symbol.getType().getStruct())[li].type->getCompleteString(true, true, false, false) << "\"\n"; |
1342 | 0 | infoSink.info << " " << StageName(unitStage) << " stage: Block: " << unitSymbol.getType().getTypeName() << ", Member: " |
1343 | 0 | << (*unitSymbol.getType().getStruct())[ri].type->getFieldName() << " \"" |
1344 | 0 | << (*unitSymbol.getType().getStruct())[ri].type->getCompleteString(true, true, false, false) << "\"\n"; |
1345 | 0 | errorReported = true; |
1346 | 0 | } |
1347 | 0 | ++li; |
1348 | 0 | ++ri; |
1349 | 0 | } |
1350 | 0 | } |
1351 | |
|
1352 | 0 | bool isInOut = crossStage && |
1353 | 0 | ((symbol.getQualifier().storage == EvqVaryingIn && unitSymbol.getQualifier().storage == EvqVaryingOut) || |
1354 | 0 | (symbol.getQualifier().storage == EvqVaryingOut && unitSymbol.getQualifier().storage == EvqVaryingIn)); |
1355 | | |
1356 | | // Qualifiers have to (almost) match |
1357 | | // Storage... |
1358 | 0 | if (!isInOut && symbol.getQualifier().storage != unitSymbol.getQualifier().storage) { |
1359 | 0 | error(infoSink, "Storage qualifiers must match:", unitStage); |
1360 | 0 | writeTypeComparison = true; |
1361 | 0 | printQualifiers = true; |
1362 | 0 | } |
1363 | | |
1364 | | // Uniform and buffer blocks must either both have an instance name, or |
1365 | | // must both be anonymous. The names don't need to match though. |
1366 | 0 | if (symbol.getQualifier().isUniformOrBuffer() && |
1367 | 0 | (IsAnonymous(symbol.getName()) != IsAnonymous(unitSymbol.getName()))) { |
1368 | 0 | error(infoSink, "Matched Uniform or Storage blocks must all be anonymous," |
1369 | 0 | " or all be named:", unitStage); |
1370 | 0 | writeTypeComparison = true; |
1371 | 0 | } |
1372 | |
|
1373 | 0 | if (symbol.getQualifier().storage == unitSymbol.getQualifier().storage && |
1374 | 0 | (IsAnonymous(symbol.getName()) != IsAnonymous(unitSymbol.getName()) || |
1375 | 0 | (!IsAnonymous(symbol.getName()) && symbol.getName() != unitSymbol.getName()))) { |
1376 | 0 | warn(infoSink, "Matched shader interfaces are using different instance names.", unitStage); |
1377 | 0 | writeTypeComparison = true; |
1378 | 0 | } |
1379 | | |
1380 | | // Precision... |
1381 | 0 | if (!isInOut && symbol.getQualifier().precision != unitSymbol.getQualifier().precision) { |
1382 | 0 | error(infoSink, "Precision qualifiers must match:", unitStage); |
1383 | 0 | writeTypeComparison = true; |
1384 | 0 | printPrecision = true; |
1385 | 0 | } |
1386 | | |
1387 | | // Invariance... |
1388 | 0 | if (! crossStage && symbol.getQualifier().invariant != unitSymbol.getQualifier().invariant) { |
1389 | 0 | error(infoSink, "Presence of invariant qualifier must match:", unitStage); |
1390 | 0 | writeTypeComparison = true; |
1391 | 0 | printQualifiers = true; |
1392 | 0 | } |
1393 | | |
1394 | | // Precise... |
1395 | 0 | if (! crossStage && symbol.getQualifier().isNoContraction() != unitSymbol.getQualifier().isNoContraction()) { |
1396 | 0 | error(infoSink, "Presence of precise qualifier must match:", unitStage); |
1397 | 0 | writeTypeComparison = true; |
1398 | 0 | printPrecision = true; |
1399 | 0 | } |
1400 | | |
1401 | | // Auxiliary and interpolation... |
1402 | | // "interpolation qualification (e.g., flat) and auxiliary qualification (e.g. centroid) may differ. |
1403 | | // These mismatches are allowed between any pair of stages ... |
1404 | | // those provided in the fragment shader supersede those provided in previous stages." |
1405 | 0 | if (!crossStage && |
1406 | 0 | (symbol.getQualifier().centroid != unitSymbol.getQualifier().centroid || |
1407 | 0 | symbol.getQualifier().smooth != unitSymbol.getQualifier().smooth || |
1408 | 0 | symbol.getQualifier().flat != unitSymbol.getQualifier().flat || |
1409 | 0 | symbol.getQualifier().isSample()!= unitSymbol.getQualifier().isSample() || |
1410 | 0 | symbol.getQualifier().isPatch() != unitSymbol.getQualifier().isPatch() || |
1411 | 0 | symbol.getQualifier().isNonPerspective() != unitSymbol.getQualifier().isNonPerspective())) { |
1412 | 0 | error(infoSink, "Interpolation and auxiliary storage qualifiers must match:", unitStage); |
1413 | 0 | writeTypeComparison = true; |
1414 | 0 | printQualifiers = true; |
1415 | 0 | } |
1416 | | |
1417 | | // Memory... |
1418 | 0 | bool memoryQualifierError = false; |
1419 | 0 | if (symbol.getQualifier().coherent != unitSymbol.getQualifier().coherent) { |
1420 | 0 | error(infoSink, "Memory coherent qualifier must match:", unitStage); |
1421 | 0 | memoryQualifierError = true; |
1422 | 0 | } |
1423 | 0 | if (symbol.getQualifier().devicecoherent != unitSymbol.getQualifier().devicecoherent) { |
1424 | 0 | error(infoSink, "Memory devicecoherent qualifier must match:", unitStage); |
1425 | 0 | memoryQualifierError = true; |
1426 | 0 | } |
1427 | 0 | if (symbol.getQualifier().queuefamilycoherent != unitSymbol.getQualifier().queuefamilycoherent) { |
1428 | 0 | error(infoSink, "Memory queuefamilycoherent qualifier must match:", unitStage); |
1429 | 0 | memoryQualifierError = true; |
1430 | 0 | } |
1431 | 0 | if (symbol.getQualifier().workgroupcoherent != unitSymbol.getQualifier().workgroupcoherent) { |
1432 | 0 | error(infoSink, "Memory workgroupcoherent qualifier must match:", unitStage); |
1433 | 0 | memoryQualifierError = true; |
1434 | 0 | } |
1435 | 0 | if (symbol.getQualifier().subgroupcoherent != unitSymbol.getQualifier().subgroupcoherent) { |
1436 | 0 | error(infoSink, "Memory subgroupcoherent qualifier must match:", unitStage); |
1437 | 0 | memoryQualifierError = true; |
1438 | 0 | } |
1439 | 0 | if (symbol.getQualifier().shadercallcoherent != unitSymbol.getQualifier().shadercallcoherent) { |
1440 | 0 | error(infoSink, "Memory shadercallcoherent qualifier must match:", unitStage); |
1441 | 0 | memoryQualifierError = true; |
1442 | 0 | } |
1443 | 0 | if (symbol.getQualifier().nonprivate != unitSymbol.getQualifier().nonprivate) { |
1444 | 0 | error(infoSink, "Memory nonprivate qualifier must match:", unitStage); |
1445 | 0 | memoryQualifierError = true; |
1446 | 0 | } |
1447 | 0 | if (symbol.getQualifier().volatil != unitSymbol.getQualifier().volatil) { |
1448 | 0 | error(infoSink, "Memory volatil qualifier must match:", unitStage); |
1449 | 0 | memoryQualifierError = true; |
1450 | 0 | } |
1451 | 0 | if (symbol.getQualifier().nontemporal != unitSymbol.getQualifier().nontemporal) { |
1452 | 0 | error(infoSink, "Memory nontemporal qualifier must match:", unitStage); |
1453 | 0 | memoryQualifierError = true; |
1454 | 0 | } |
1455 | 0 | if (symbol.getQualifier().restrict != unitSymbol.getQualifier().restrict) { |
1456 | 0 | error(infoSink, "Memory restrict qualifier must match:", unitStage); |
1457 | 0 | memoryQualifierError = true; |
1458 | 0 | } |
1459 | 0 | if (symbol.getQualifier().readonly != unitSymbol.getQualifier().readonly) { |
1460 | 0 | error(infoSink, "Memory readonly qualifier must match:", unitStage); |
1461 | 0 | memoryQualifierError = true; |
1462 | 0 | } |
1463 | 0 | if (symbol.getQualifier().writeonly != unitSymbol.getQualifier().writeonly) { |
1464 | 0 | error(infoSink, "Memory writeonly qualifier must match:", unitStage); |
1465 | 0 | memoryQualifierError = true; |
1466 | 0 | } |
1467 | 0 | if (memoryQualifierError) { |
1468 | 0 | writeTypeComparison = true; |
1469 | 0 | printQualifiers = true; |
1470 | 0 | } |
1471 | | |
1472 | | // Layouts... |
1473 | | // TODO: 4.4 enhanced layouts: Generalize to include offset/align: current spec |
1474 | | // requires separate user-supplied offset from actual computed offset, but |
1475 | | // current implementation only has one offset. |
1476 | 0 | bool layoutQualifierError = false; |
1477 | 0 | if (symbol.getQualifier().layoutMatrix != unitSymbol.getQualifier().layoutMatrix) { |
1478 | 0 | error(infoSink, "Layout matrix qualifier must match:", unitStage); |
1479 | 0 | layoutQualifierError = true; |
1480 | 0 | } |
1481 | 0 | if (symbol.getQualifier().layoutPacking != unitSymbol.getQualifier().layoutPacking) { |
1482 | 0 | error(infoSink, "Layout packing qualifier must match:", unitStage); |
1483 | 0 | layoutQualifierError = true; |
1484 | 0 | } |
1485 | 0 | if (symbol.getQualifier().hasLocation() && unitSymbol.getQualifier().hasLocation() && symbol.getQualifier().layoutLocation != unitSymbol.getQualifier().layoutLocation) { |
1486 | 0 | error(infoSink, "Layout location qualifier must match:", unitStage); |
1487 | 0 | layoutQualifierError = true; |
1488 | 0 | } |
1489 | 0 | if (symbol.getQualifier().layoutComponent != unitSymbol.getQualifier().layoutComponent) { |
1490 | 0 | error(infoSink, "Layout component qualifier must match:", unitStage); |
1491 | 0 | layoutQualifierError = true; |
1492 | 0 | } |
1493 | 0 | if (symbol.getQualifier().layoutIndex != unitSymbol.getQualifier().layoutIndex) { |
1494 | 0 | error(infoSink, "Layout index qualifier must match:", unitStage); |
1495 | 0 | layoutQualifierError = true; |
1496 | 0 | } |
1497 | 0 | if (symbol.getQualifier().hasBinding() && unitSymbol.getQualifier().hasBinding() && symbol.getQualifier().layoutBinding != unitSymbol.getQualifier().layoutBinding) { |
1498 | 0 | error(infoSink, "Layout binding qualifier must match:", unitStage); |
1499 | 0 | layoutQualifierError = true; |
1500 | 0 | } |
1501 | 0 | if (symbol.getQualifier().hasBinding() && (symbol.getQualifier().layoutOffset != unitSymbol.getQualifier().layoutOffset)) { |
1502 | 0 | error(infoSink, "Layout offset qualifier must match:", unitStage); |
1503 | 0 | layoutQualifierError = true; |
1504 | 0 | } |
1505 | 0 | if (layoutQualifierError) { |
1506 | 0 | writeTypeComparison = true; |
1507 | 0 | printQualifiers = true; |
1508 | 0 | } |
1509 | | |
1510 | | // Initializers have to match, if both are present, and if we don't already know the types don't match |
1511 | 0 | if (! writeTypeComparison && ! errorReported) { |
1512 | 0 | if (! symbol.getConstArray().empty() && ! unitSymbol.getConstArray().empty()) { |
1513 | 0 | if (symbol.getConstArray() != unitSymbol.getConstArray()) { |
1514 | 0 | error(infoSink, "Initializers must match:", unitStage); |
1515 | 0 | infoSink.info << " " << symbol.getName() << "\n"; |
1516 | 0 | } |
1517 | 0 | } |
1518 | 0 | } |
1519 | |
|
1520 | 0 | if (writeTypeComparison) { |
1521 | 0 | if (symbol.getType().getBasicType() == EbtBlock && unitSymbol.getType().getBasicType() == EbtBlock && |
1522 | 0 | symbol.getType().getStruct() && unitSymbol.getType().getStruct()) { |
1523 | 0 | if (printType) { |
1524 | 0 | infoSink.info << " " << StageName(stage) << " stage: \"" << symbol.getType().getCompleteString(true, printQualifiers, printPrecision, |
1525 | 0 | printType, symbol.getName(), symbol.getType().getTypeName()) << "\"\n"; |
1526 | 0 | infoSink.info << " " << StageName(unitStage) << " stage: \"" << unitSymbol.getType().getCompleteString(true, printQualifiers, printPrecision, |
1527 | 0 | printType, unitSymbol.getName(), unitSymbol.getType().getTypeName()) << "\"\n"; |
1528 | 0 | } else { |
1529 | 0 | infoSink.info << " " << StageName(stage) << " stage: Block: " << symbol.getType().getTypeName() << " Instance: " << symbol.getName() |
1530 | 0 | << ": \"" << symbol.getType().getCompleteString(true, printQualifiers, printPrecision, printType) << "\"\n"; |
1531 | 0 | infoSink.info << " " << StageName(unitStage) << " stage: Block: " << unitSymbol.getType().getTypeName() << " Instance: " << unitSymbol.getName() |
1532 | 0 | << ": \"" << unitSymbol.getType().getCompleteString(true, printQualifiers, printPrecision, printType) << "\"\n"; |
1533 | 0 | } |
1534 | 0 | } else { |
1535 | 0 | if (printType) { |
1536 | 0 | infoSink.info << " " << StageName(stage) << " stage: \"" |
1537 | 0 | << symbol.getType().getCompleteString(true, printQualifiers, printPrecision, printType, symbol.getName()) << "\"\n"; |
1538 | 0 | infoSink.info << " " << StageName(unitStage) << " stage: \"" |
1539 | 0 | << unitSymbol.getType().getCompleteString(true, printQualifiers, printPrecision, printType, unitSymbol.getName()) << "\"\n"; |
1540 | 0 | } else { |
1541 | 0 | infoSink.info << " " << StageName(stage) << " stage: " << symbol.getName() << " \"" |
1542 | 0 | << symbol.getType().getCompleteString(true, printQualifiers, printPrecision, printType) << "\"\n"; |
1543 | 0 | infoSink.info << " " << StageName(unitStage) << " stage: " << unitSymbol.getName() << " \"" |
1544 | 0 | << unitSymbol.getType().getCompleteString(true, printQualifiers, printPrecision, printType) << "\"\n"; |
1545 | 0 | } |
1546 | 0 | } |
1547 | 0 | } |
1548 | 0 | } |
1549 | | |
1550 | | void TIntermediate::sharedBlockCheck(TInfoSink& infoSink) |
1551 | 0 | { |
1552 | 0 | bool has_shared_block = false; |
1553 | 0 | bool has_shared_non_block = false; |
1554 | 0 | TIntermSequence& linkObjects = findLinkerObjects()->getSequence(); |
1555 | 0 | for (size_t i = 0; i < linkObjects.size(); ++i) { |
1556 | 0 | const TType& type = linkObjects[i]->getAsTyped()->getType(); |
1557 | 0 | const TQualifier& qualifier = type.getQualifier(); |
1558 | 0 | if (qualifier.storage == glslang::EvqShared) { |
1559 | 0 | if (type.getBasicType() == glslang::EbtBlock) |
1560 | 0 | has_shared_block = true; |
1561 | 0 | else |
1562 | 0 | has_shared_non_block = true; |
1563 | 0 | } |
1564 | 0 | } |
1565 | 0 | if (has_shared_block && has_shared_non_block) |
1566 | 0 | error(infoSink, "cannot mix use of shared variables inside and outside blocks"); |
1567 | 0 | } |
1568 | | |
1569 | | // |
1570 | | // Do final link-time error checking of a complete (merged) intermediate representation. |
1571 | | // (Much error checking was done during merging). |
1572 | | // |
1573 | | // Also, lock in defaults of things not set. |
1574 | | // Defer adopting implicit array sizes to later, after all stages are merged. |
1575 | | // |
1576 | | void TIntermediate::finalCheck(TInfoSink& infoSink, bool keepUncalled) |
1577 | 0 | { |
1578 | 0 | if (getTreeRoot() == nullptr) |
1579 | 0 | return; |
1580 | | |
1581 | 0 | if (numEntryPoints < 1) { |
1582 | 0 | if (getSource() == EShSourceGlsl) |
1583 | 0 | error(infoSink, "Missing entry point: Each stage requires one entry point"); |
1584 | 0 | else |
1585 | 0 | warn(infoSink, "Entry point not found"); |
1586 | 0 | } |
1587 | | |
1588 | | // recursion and missing body checking |
1589 | 0 | checkCallGraphCycles(infoSink); |
1590 | 0 | checkCallGraphBodies(infoSink, keepUncalled); |
1591 | | |
1592 | | // overlap/alias/missing I/O, etc. |
1593 | 0 | inOutLocationCheck(infoSink); |
1594 | |
|
1595 | 0 | if (!IsRequestedExtension(glslang::E_GL_NV_push_constant_bank) && (getNumPushConstants() > 1)) |
1596 | 0 | error(infoSink, "Only one push_constant block is allowed per stage"); |
1597 | | |
1598 | | // invocations |
1599 | 0 | if (invocations == TQualifier::layoutNotSet) |
1600 | 0 | invocations = 1; |
1601 | |
|
1602 | 0 | if (inIoAccessed("gl_ClipDistance") && inIoAccessed("gl_ClipVertex")) |
1603 | 0 | error(infoSink, "Can only use one of gl_ClipDistance or gl_ClipVertex (gl_ClipDistance is preferred)"); |
1604 | 0 | if (inIoAccessed("gl_CullDistance") && inIoAccessed("gl_ClipVertex")) |
1605 | 0 | error(infoSink, "Can only use one of gl_CullDistance or gl_ClipVertex (gl_ClipDistance is preferred)"); |
1606 | |
|
1607 | 0 | if (userOutputUsed() && (inIoAccessed("gl_FragColor") || inIoAccessed("gl_FragData"))) |
1608 | 0 | error(infoSink, "Cannot use gl_FragColor or gl_FragData when using user-defined outputs"); |
1609 | 0 | if (inIoAccessed("gl_FragColor") && inIoAccessed("gl_FragData")) |
1610 | 0 | error(infoSink, "Cannot use both gl_FragColor and gl_FragData"); |
1611 | |
|
1612 | 0 | for (size_t b = 0; b < xfbBuffers.size(); ++b) { |
1613 | 0 | if (xfbBuffers[b].contains64BitType) |
1614 | 0 | RoundToPow2(xfbBuffers[b].implicitStride, 8); |
1615 | 0 | else if (xfbBuffers[b].contains32BitType) |
1616 | 0 | RoundToPow2(xfbBuffers[b].implicitStride, 4); |
1617 | 0 | else if (xfbBuffers[b].contains16BitType) |
1618 | 0 | RoundToPow2(xfbBuffers[b].implicitStride, 2); |
1619 | | |
1620 | | // "It is a compile-time or link-time error to have |
1621 | | // any xfb_offset that overflows xfb_stride, whether stated on declarations before or after the xfb_stride, or |
1622 | | // in different compilation units. While xfb_stride can be declared multiple times for the same buffer, it is a |
1623 | | // compile-time or link-time error to have different values specified for the stride for the same buffer." |
1624 | 0 | if (xfbBuffers[b].stride != TQualifier::layoutXfbStrideEnd && xfbBuffers[b].implicitStride > xfbBuffers[b].stride) { |
1625 | 0 | error(infoSink, "xfb_stride is too small to hold all buffer entries:"); |
1626 | 0 | infoSink.info.prefix(EPrefixError); |
1627 | 0 | infoSink.info << " xfb_buffer " << (unsigned int)b << ", xfb_stride " << xfbBuffers[b].stride << ", minimum stride needed: " << xfbBuffers[b].implicitStride << "\n"; |
1628 | 0 | } |
1629 | 0 | if (xfbBuffers[b].stride == TQualifier::layoutXfbStrideEnd) |
1630 | 0 | xfbBuffers[b].stride = xfbBuffers[b].implicitStride; |
1631 | | |
1632 | | // "If the buffer is capturing any |
1633 | | // outputs with double-precision or 64-bit integer components, the stride must be a multiple of 8, otherwise it must be a |
1634 | | // multiple of 4, or a compile-time or link-time error results." |
1635 | 0 | if (xfbBuffers[b].contains64BitType && ! IsMultipleOfPow2(xfbBuffers[b].stride, 8)) { |
1636 | 0 | error(infoSink, "xfb_stride must be multiple of 8 for buffer holding a double or 64-bit integer:"); |
1637 | 0 | infoSink.info.prefix(EPrefixError); |
1638 | 0 | infoSink.info << " xfb_buffer " << (unsigned int)b << ", xfb_stride " << xfbBuffers[b].stride << "\n"; |
1639 | 0 | } else if (xfbBuffers[b].contains32BitType && ! IsMultipleOfPow2(xfbBuffers[b].stride, 4)) { |
1640 | 0 | error(infoSink, "xfb_stride must be multiple of 4:"); |
1641 | 0 | infoSink.info.prefix(EPrefixError); |
1642 | 0 | infoSink.info << " xfb_buffer " << (unsigned int)b << ", xfb_stride " << xfbBuffers[b].stride << "\n"; |
1643 | 0 | } |
1644 | | // "If the buffer is capturing any |
1645 | | // outputs with half-precision or 16-bit integer components, the stride must be a multiple of 2" |
1646 | 0 | else if (xfbBuffers[b].contains16BitType && ! IsMultipleOfPow2(xfbBuffers[b].stride, 2)) { |
1647 | 0 | error(infoSink, "xfb_stride must be multiple of 2 for buffer holding a half float or 16-bit integer:"); |
1648 | 0 | infoSink.info.prefix(EPrefixError); |
1649 | 0 | infoSink.info << " xfb_buffer " << (unsigned int)b << ", xfb_stride " << xfbBuffers[b].stride << "\n"; |
1650 | 0 | } |
1651 | | |
1652 | | // "The resulting stride (implicit or explicit), when divided by 4, must be less than or equal to the |
1653 | | // implementation-dependent constant gl_MaxTransformFeedbackInterleavedComponents." |
1654 | 0 | if (xfbBuffers[b].stride > (unsigned int)(4 * resources->maxTransformFeedbackInterleavedComponents)) { |
1655 | 0 | error(infoSink, "xfb_stride is too large:"); |
1656 | 0 | infoSink.info.prefix(EPrefixError); |
1657 | 0 | infoSink.info << " xfb_buffer " << (unsigned int)b << ", components (1/4 stride) needed are " << xfbBuffers[b].stride/4 << ", gl_MaxTransformFeedbackInterleavedComponents is " << resources->maxTransformFeedbackInterleavedComponents << "\n"; |
1658 | 0 | } |
1659 | 0 | } |
1660 | |
|
1661 | 0 | switch (language) { |
1662 | 0 | case EShLangVertex: |
1663 | 0 | break; |
1664 | 0 | case EShLangTessControl: |
1665 | 0 | if (vertices == TQualifier::layoutNotSet) |
1666 | 0 | error(infoSink, "At least one shader must specify an output layout(vertices=...)"); |
1667 | 0 | break; |
1668 | 0 | case EShLangTessEvaluation: |
1669 | 0 | if (getSource() == EShSourceGlsl) { |
1670 | 0 | if (inputPrimitive == ElgNone) |
1671 | 0 | error(infoSink, "At least one shader must specify an input layout primitive"); |
1672 | 0 | if (vertexSpacing == EvsNone) |
1673 | 0 | vertexSpacing = EvsEqual; |
1674 | 0 | if (vertexOrder == EvoNone) |
1675 | 0 | vertexOrder = EvoCcw; |
1676 | 0 | } |
1677 | 0 | break; |
1678 | 0 | case EShLangGeometry: |
1679 | 0 | if (inputPrimitive == ElgNone) |
1680 | 0 | error(infoSink, "At least one shader must specify an input layout primitive"); |
1681 | 0 | if (outputPrimitive == ElgNone) |
1682 | 0 | error(infoSink, "At least one shader must specify an output layout primitive"); |
1683 | 0 | if (vertices == TQualifier::layoutNotSet) |
1684 | 0 | error(infoSink, "At least one shader must specify a layout(max_vertices = value)"); |
1685 | 0 | break; |
1686 | 0 | case EShLangFragment: |
1687 | | // for GL_ARB_post_depth_coverage, EarlyFragmentTest is set automatically in |
1688 | | // ParseHelper.cpp. So if we reach here, this must be GL_EXT_post_depth_coverage |
1689 | | // requiring explicit early_fragment_tests |
1690 | 0 | if (getPostDepthCoverage() && !getEarlyFragmentTests()) |
1691 | 0 | error(infoSink, "post_depth_coverage requires early_fragment_tests"); |
1692 | 0 | break; |
1693 | 0 | case EShLangCompute: |
1694 | 0 | sharedBlockCheck(infoSink); |
1695 | 0 | break; |
1696 | 0 | case EShLangRayGen: |
1697 | 0 | case EShLangIntersect: |
1698 | 0 | case EShLangAnyHit: |
1699 | 0 | case EShLangClosestHit: |
1700 | 0 | case EShLangMiss: |
1701 | 0 | case EShLangCallable: |
1702 | 0 | if (numShaderRecordBlocks > 1) |
1703 | 0 | error(infoSink, "Only one shaderRecordNV buffer block is allowed per stage"); |
1704 | 0 | break; |
1705 | 0 | case EShLangMesh: |
1706 | | // NV_mesh_shader doesn't allow use of both single-view and per-view builtins. |
1707 | 0 | if (inIoAccessed("gl_Position") && inIoAccessed("gl_PositionPerViewNV")) |
1708 | 0 | error(infoSink, "Can only use one of gl_Position or gl_PositionPerViewNV"); |
1709 | 0 | if (inIoAccessed("gl_ClipDistance") && inIoAccessed("gl_ClipDistancePerViewNV")) |
1710 | 0 | error(infoSink, "Can only use one of gl_ClipDistance or gl_ClipDistancePerViewNV"); |
1711 | 0 | if (inIoAccessed("gl_CullDistance") && inIoAccessed("gl_CullDistancePerViewNV")) |
1712 | 0 | error(infoSink, "Can only use one of gl_CullDistance or gl_CullDistancePerViewNV"); |
1713 | 0 | if (inIoAccessed("gl_Layer") && inIoAccessed("gl_LayerPerViewNV")) |
1714 | 0 | error(infoSink, "Can only use one of gl_Layer or gl_LayerPerViewNV"); |
1715 | 0 | if (inIoAccessed("gl_ViewportMask") && inIoAccessed("gl_ViewportMaskPerViewNV")) |
1716 | 0 | error(infoSink, "Can only use one of gl_ViewportMask or gl_ViewportMaskPerViewNV"); |
1717 | 0 | if (outputPrimitive == ElgNone) |
1718 | 0 | error(infoSink, "At least one shader must specify an output layout primitive"); |
1719 | 0 | if (vertices == TQualifier::layoutNotSet) |
1720 | 0 | error(infoSink, "At least one shader must specify a layout(max_vertices = value)"); |
1721 | 0 | if (primitives == TQualifier::layoutNotSet) |
1722 | 0 | error(infoSink, "At least one shader must specify a layout(max_primitives = value)"); |
1723 | 0 | [[fallthrough]]; |
1724 | 0 | case EShLangTask: |
1725 | 0 | if (numTaskNVBlocks > 1) |
1726 | 0 | error(infoSink, "Only one taskNV interface block is allowed per shader"); |
1727 | 0 | if (numTaskEXTPayloads > 1) |
1728 | 0 | error(infoSink, "Only single variable of type taskPayloadSharedEXT is allowed per shader"); |
1729 | 0 | sharedBlockCheck(infoSink); |
1730 | 0 | break; |
1731 | 0 | default: |
1732 | 0 | error(infoSink, "Unknown Stage."); |
1733 | 0 | break; |
1734 | 0 | } |
1735 | 0 | } |
1736 | | |
1737 | | // |
1738 | | // See if the call graph contains any static recursion, which is disallowed |
1739 | | // by the specification. |
1740 | | // |
1741 | | void TIntermediate::checkCallGraphCycles(TInfoSink& infoSink) |
1742 | 0 | { |
1743 | | // Clear fields we'll use for this. |
1744 | 0 | for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { |
1745 | 0 | call->visited = false; |
1746 | 0 | call->currentPath = false; |
1747 | 0 | call->errorGiven = false; |
1748 | 0 | } |
1749 | | |
1750 | | // |
1751 | | // Loop, looking for a new connected subgraph. One subgraph is handled per loop iteration. |
1752 | | // |
1753 | |
|
1754 | 0 | TCall* newRoot; |
1755 | 0 | do { |
1756 | | // See if we have unvisited parts of the graph. |
1757 | 0 | newRoot = nullptr; |
1758 | 0 | for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { |
1759 | 0 | if (! call->visited) { |
1760 | 0 | newRoot = &(*call); |
1761 | 0 | break; |
1762 | 0 | } |
1763 | 0 | } |
1764 | | |
1765 | | // If not, we are done. |
1766 | 0 | if (! newRoot) |
1767 | 0 | break; |
1768 | | |
1769 | | // Otherwise, we found a new subgraph, process it: |
1770 | | // See what all can be reached by this new root, and if any of |
1771 | | // that is recursive. This is done by depth-first traversals, seeing |
1772 | | // if a new call is found that was already in the currentPath (a back edge), |
1773 | | // thereby detecting recursion. |
1774 | 0 | std::list<TCall*> stack; |
1775 | 0 | newRoot->currentPath = true; // currentPath will be true iff it is on the stack |
1776 | 0 | stack.push_back(newRoot); |
1777 | 0 | while (! stack.empty()) { |
1778 | | // get a caller |
1779 | 0 | TCall* call = stack.back(); |
1780 | | |
1781 | | // Add to the stack just one callee. |
1782 | | // This algorithm always terminates, because only !visited and !currentPath causes a push |
1783 | | // and all pushes change currentPath to true, and all pops change visited to true. |
1784 | 0 | TGraph::iterator child = callGraph.begin(); |
1785 | 0 | for (; child != callGraph.end(); ++child) { |
1786 | | |
1787 | | // If we already visited this node, its whole subgraph has already been processed, so skip it. |
1788 | 0 | if (child->visited) |
1789 | 0 | continue; |
1790 | | |
1791 | 0 | if (call->callee == child->caller) { |
1792 | 0 | if (child->currentPath) { |
1793 | | // Then, we found a back edge |
1794 | 0 | if (! child->errorGiven) { |
1795 | 0 | error(infoSink, "Recursion detected:"); |
1796 | 0 | infoSink.info << " " << call->callee << " calling " << child->callee << "\n"; |
1797 | 0 | child->errorGiven = true; |
1798 | 0 | recursive = true; |
1799 | 0 | } |
1800 | 0 | } else { |
1801 | 0 | child->currentPath = true; |
1802 | 0 | stack.push_back(&(*child)); |
1803 | 0 | break; |
1804 | 0 | } |
1805 | 0 | } |
1806 | 0 | } |
1807 | 0 | if (child == callGraph.end()) { |
1808 | | // no more callees, we bottomed out, never look at this node again |
1809 | 0 | stack.back()->currentPath = false; |
1810 | 0 | stack.back()->visited = true; |
1811 | 0 | stack.pop_back(); |
1812 | 0 | } |
1813 | 0 | } // end while, meaning nothing left to process in this subtree |
1814 | |
|
1815 | 0 | } while (newRoot); // redundant loop check; should always exit via the 'break' above |
1816 | 0 | } |
1817 | | |
1818 | | // |
1819 | | // See which functions are reachable from the entry point and which have bodies. |
1820 | | // Reachable ones with missing bodies are errors. |
1821 | | // Unreachable bodies are dead code. |
1822 | | // |
1823 | | void TIntermediate::checkCallGraphBodies(TInfoSink& infoSink, bool keepUncalled) |
1824 | 0 | { |
1825 | | // Clear fields we'll use for this. |
1826 | 0 | for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { |
1827 | 0 | call->visited = false; |
1828 | 0 | call->calleeBodyPosition = -1; |
1829 | 0 | } |
1830 | | |
1831 | | // The top level of the AST includes function definitions (bodies). |
1832 | | // Compare these to function calls in the call graph. |
1833 | | // We'll end up knowing which have bodies, and if so, |
1834 | | // how to map the call-graph node to the location in the AST. |
1835 | 0 | TIntermSequence &functionSequence = getTreeRoot()->getAsAggregate()->getSequence(); |
1836 | 0 | std::vector<bool> reachable(functionSequence.size(), true); // so that non-functions are reachable |
1837 | 0 | for (int f = 0; f < (int)functionSequence.size(); ++f) { |
1838 | 0 | glslang::TIntermAggregate* node = functionSequence[f]->getAsAggregate(); |
1839 | 0 | if (node && (node->getOp() == glslang::EOpFunction)) { |
1840 | 0 | if (node->getName().compare(getEntryPointMangledName().c_str()) != 0) |
1841 | 0 | reachable[f] = false; // so that function bodies are unreachable, until proven otherwise |
1842 | 0 | for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { |
1843 | 0 | if (call->callee == node->getName()) |
1844 | 0 | call->calleeBodyPosition = f; |
1845 | 0 | } |
1846 | 0 | } |
1847 | 0 | } |
1848 | | |
1849 | | // Start call-graph traversal by visiting the entry point nodes. |
1850 | 0 | for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { |
1851 | 0 | if (call->caller.compare(getEntryPointMangledName().c_str()) == 0) |
1852 | 0 | call->visited = true; |
1853 | 0 | } |
1854 | | |
1855 | | // Propagate 'visited' through the call-graph to every part of the graph it |
1856 | | // can reach (seeded with the entry-point setting above). |
1857 | 0 | bool changed; |
1858 | 0 | do { |
1859 | 0 | changed = false; |
1860 | 0 | for (auto call1 = callGraph.begin(); call1 != callGraph.end(); ++call1) { |
1861 | 0 | if (call1->visited) { |
1862 | 0 | for (TGraph::iterator call2 = callGraph.begin(); call2 != callGraph.end(); ++call2) { |
1863 | 0 | if (! call2->visited) { |
1864 | 0 | if (call1->callee == call2->caller) { |
1865 | 0 | changed = true; |
1866 | 0 | call2->visited = true; |
1867 | 0 | } |
1868 | 0 | } |
1869 | 0 | } |
1870 | 0 | } |
1871 | 0 | } |
1872 | 0 | } while (changed); |
1873 | | |
1874 | | // Any call-graph node set to visited but without a callee body is an error. |
1875 | 0 | for (TGraph::iterator call = callGraph.begin(); call != callGraph.end(); ++call) { |
1876 | 0 | if (call->visited) { |
1877 | 0 | if (call->calleeBodyPosition == -1) { |
1878 | 0 | error(infoSink, "No function definition (body) found: "); |
1879 | 0 | infoSink.info << " " << call->callee << "\n"; |
1880 | 0 | } else |
1881 | 0 | reachable[call->calleeBodyPosition] = true; |
1882 | 0 | } |
1883 | 0 | } |
1884 | | |
1885 | | // Bodies in the AST not reached by the call graph are dead; |
1886 | | // clear them out, since they can't be reached and also can't |
1887 | | // be translated further due to possibility of being ill defined. |
1888 | 0 | if (! keepUncalled) { |
1889 | 0 | for (int f = 0; f < (int)functionSequence.size(); ++f) { |
1890 | 0 | if (! reachable[f]) |
1891 | 0 | { |
1892 | 0 | resetTopLevelUncalledStatus(functionSequence[f]->getAsAggregate()->getName()); |
1893 | 0 | functionSequence[f] = nullptr; |
1894 | 0 | } |
1895 | 0 | } |
1896 | 0 | functionSequence.erase(std::remove(functionSequence.begin(), functionSequence.end(), nullptr), functionSequence.end()); |
1897 | 0 | } |
1898 | 0 | } |
1899 | | |
1900 | | // |
1901 | | // Satisfy rules for location qualifiers on inputs and outputs |
1902 | | // |
1903 | | void TIntermediate::inOutLocationCheck(TInfoSink& infoSink) |
1904 | 0 | { |
1905 | | // ES 3.0 requires all outputs to have location qualifiers if there is more than one output |
1906 | 0 | bool fragOutWithNoLocation = false; |
1907 | 0 | int numFragOut = 0; |
1908 | | |
1909 | | // TODO: linker functionality: location collision checking |
1910 | |
|
1911 | 0 | TIntermSequence& linkObjects = findLinkerObjects()->getSequence(); |
1912 | 0 | for (size_t i = 0; i < linkObjects.size(); ++i) { |
1913 | 0 | const TType& type = linkObjects[i]->getAsTyped()->getType(); |
1914 | 0 | const TQualifier& qualifier = type.getQualifier(); |
1915 | 0 | if (language == EShLangFragment) { |
1916 | 0 | if (qualifier.storage == EvqVaryingOut && qualifier.builtIn == EbvNone) { |
1917 | 0 | ++numFragOut; |
1918 | 0 | if (!qualifier.hasAnyLocation()) |
1919 | 0 | fragOutWithNoLocation = true; |
1920 | 0 | } |
1921 | 0 | } |
1922 | 0 | } |
1923 | |
|
1924 | 0 | if (isEsProfile()) { |
1925 | 0 | if (numFragOut > 1 && fragOutWithNoLocation) |
1926 | 0 | error(infoSink, "when more than one fragment shader output, all must have location qualifiers"); |
1927 | 0 | } |
1928 | 0 | } |
1929 | | |
1930 | | TIntermAggregate* TIntermediate::findLinkerObjects() const |
1931 | 0 | { |
1932 | | // Get the top-level globals |
1933 | 0 | TIntermSequence& globals = treeRoot->getAsAggregate()->getSequence(); |
1934 | | |
1935 | | // Get the last member of the sequences, expected to be the linker-object lists |
1936 | 0 | assert(globals.back()->getAsAggregate()->getOp() == EOpLinkerObjects); |
1937 | |
|
1938 | 0 | return globals.back()->getAsAggregate(); |
1939 | 0 | } |
1940 | | |
1941 | | // See if a variable was both a user-declared output and used. |
1942 | | // Note: the spec discusses writing to one, but this looks at read or write, which |
1943 | | // is more useful, and perhaps the spec should be changed to reflect that. |
1944 | | bool TIntermediate::userOutputUsed() const |
1945 | 0 | { |
1946 | 0 | const TIntermSequence& linkerObjects = findLinkerObjects()->getSequence(); |
1947 | |
|
1948 | 0 | bool found = false; |
1949 | 0 | for (size_t i = 0; i < linkerObjects.size(); ++i) { |
1950 | 0 | const TIntermSymbol& symbolNode = *linkerObjects[i]->getAsSymbolNode(); |
1951 | 0 | if (symbolNode.getQualifier().storage == EvqVaryingOut && |
1952 | 0 | symbolNode.getName().compare(0, 3, "gl_") != 0 && |
1953 | 0 | inIoAccessed(symbolNode.getName())) { |
1954 | 0 | found = true; |
1955 | 0 | break; |
1956 | 0 | } |
1957 | 0 | } |
1958 | |
|
1959 | 0 | return found; |
1960 | 0 | } |
1961 | | |
1962 | | // Accumulate locations used for inputs, outputs, and uniforms, payload, callable data, and tileImageEXT |
1963 | | // and check for collisions as the accumulation is done. |
1964 | | // |
1965 | | // Returns < 0 if no collision, >= 0 if collision and the value returned is a colliding value. |
1966 | | // |
1967 | | // typeCollision is set to true if there is no direct collision, but the types in the same location |
1968 | | // are different. |
1969 | | // |
1970 | | int TIntermediate::addUsedLocation(const TQualifier& qualifier, const TType& type, bool& typeCollision) |
1971 | 0 | { |
1972 | 0 | typeCollision = false; |
1973 | |
|
1974 | 0 | int set; |
1975 | 0 | if (qualifier.isPipeInput()) |
1976 | 0 | set = 0; |
1977 | 0 | else if (qualifier.isPipeOutput()) |
1978 | 0 | set = 1; |
1979 | 0 | else if (qualifier.storage == EvqUniform) |
1980 | 0 | set = 2; |
1981 | 0 | else if (qualifier.storage == EvqBuffer) |
1982 | 0 | set = 3; |
1983 | 0 | else if (qualifier.storage == EvqTileImageEXT) |
1984 | 0 | set = 4; |
1985 | 0 | else if (qualifier.isAnyPayload()) |
1986 | 0 | set = 0; |
1987 | 0 | else if (qualifier.isAnyCallable()) |
1988 | 0 | set = 1; |
1989 | 0 | else if (qualifier.isHitObjectAttrNV()) |
1990 | 0 | set = 2; |
1991 | 0 | else if (qualifier.isHitObjectAttrEXT()) |
1992 | 0 | set = 2; |
1993 | 0 | else |
1994 | 0 | return -1; |
1995 | | |
1996 | 0 | int size; |
1997 | 0 | if (qualifier.isAnyPayload() || qualifier.isAnyCallable()) { |
1998 | 0 | size = 1; |
1999 | 0 | } else if (qualifier.isUniformOrBuffer() || qualifier.isTaskMemory()) { |
2000 | 0 | if (type.isSizedArray()) |
2001 | 0 | size = type.getCumulativeArraySize(); |
2002 | 0 | else |
2003 | 0 | size = 1; |
2004 | 0 | } else { |
2005 | | // Strip off the outer array dimension for those having an extra one. |
2006 | 0 | if (type.isArray() && qualifier.isArrayedIo(language)) { |
2007 | 0 | TType elementType(type, 0); |
2008 | 0 | size = computeTypeLocationSize(elementType, language); |
2009 | 0 | } else |
2010 | 0 | size = computeTypeLocationSize(type, language); |
2011 | 0 | } |
2012 | | |
2013 | | // Locations, and components within locations. |
2014 | | // |
2015 | | // Almost always, dealing with components means a single location is involved. |
2016 | | // The exception is a dvec3. From the spec: |
2017 | | // |
2018 | | // "A dvec3 will consume all four components of the first location and components 0 and 1 of |
2019 | | // the second location. This leaves components 2 and 3 available for other component-qualified |
2020 | | // declarations." |
2021 | | // |
2022 | | // That means, without ever mentioning a component, a component range |
2023 | | // for a different location gets specified, if it's not a vertex shader input. (!) |
2024 | | // (A vertex shader input will show using only one location, even for a dvec3/4.) |
2025 | | // |
2026 | | // So, for the case of dvec3, we need two independent ioRanges. |
2027 | | // |
2028 | | // For raytracing IO (payloads and callabledata) each declaration occupies a single |
2029 | | // slot irrespective of type. |
2030 | 0 | int collision = -1; // no collision |
2031 | 0 | if (qualifier.isAnyPayload() || qualifier.isAnyCallable() || qualifier.isHitObjectAttrNV() || qualifier.isHitObjectAttrEXT()) { |
2032 | 0 | TRange range(qualifier.layoutLocation, qualifier.layoutLocation); |
2033 | 0 | collision = checkLocationRT(set, qualifier.layoutLocation); |
2034 | 0 | if (collision < 0) |
2035 | 0 | usedIoRT[set].push_back(range); |
2036 | 0 | return collision; |
2037 | 0 | } |
2038 | 0 | if (size == 2 && type.getBasicType() == EbtDouble && type.getVectorSize() == 3 && |
2039 | 0 | (qualifier.isPipeInput() || qualifier.isPipeOutput())) { |
2040 | | // Dealing with dvec3 in/out split across two locations. |
2041 | | // Need two io-ranges. |
2042 | | // The case where the dvec3 doesn't start at component 0 was previously caught as overflow. |
2043 | | |
2044 | | // First range: |
2045 | 0 | TRange locationRange(qualifier.layoutLocation, qualifier.layoutLocation); |
2046 | 0 | TRange componentRange(0, 3); |
2047 | 0 | TIoRange range(locationRange, componentRange, type.getBasicType(), 0, qualifier.centroid, qualifier.smooth, qualifier.flat, qualifier.sample, qualifier.patch); |
2048 | | |
2049 | | // check for collisions |
2050 | 0 | collision = checkLocationRange(set, range, type, typeCollision); |
2051 | 0 | if (collision < 0) { |
2052 | 0 | usedIo[set].push_back(range); |
2053 | | |
2054 | | // Second range: |
2055 | 0 | TRange locationRange2(qualifier.layoutLocation + 1, qualifier.layoutLocation + 1); |
2056 | 0 | TRange componentRange2(0, 1); |
2057 | 0 | TIoRange range2(locationRange2, componentRange2, type.getBasicType(), 0, qualifier.centroid, qualifier.smooth, qualifier.flat, qualifier.sample, qualifier.patch); |
2058 | | |
2059 | | // check for collisions |
2060 | 0 | collision = checkLocationRange(set, range2, type, typeCollision); |
2061 | 0 | if (collision < 0) |
2062 | 0 | usedIo[set].push_back(range2); |
2063 | 0 | } |
2064 | 0 | return collision; |
2065 | 0 | } |
2066 | | |
2067 | | // Not a dvec3 in/out split across two locations, generic path. |
2068 | | // Need a single IO-range block. |
2069 | | |
2070 | 0 | TRange locationRange(qualifier.layoutLocation, qualifier.layoutLocation + size - 1); |
2071 | 0 | TRange componentRange(0, 3); |
2072 | 0 | if (qualifier.hasComponent() || type.getVectorSize() > 0) { |
2073 | 0 | int consumedComponents = type.getVectorSize() * (type.getBasicType() == EbtDouble ? 2 : 1); |
2074 | 0 | if (qualifier.hasComponent()) |
2075 | 0 | componentRange.start = qualifier.layoutComponent; |
2076 | 0 | componentRange.last = componentRange.start + consumedComponents - 1; |
2077 | 0 | } |
2078 | | |
2079 | | // combine location and component ranges |
2080 | 0 | TBasicType basicTy = type.getBasicType(); |
2081 | 0 | if (basicTy == EbtSampler && type.getSampler().isAttachmentEXT()) |
2082 | 0 | basicTy = type.getSampler().type; |
2083 | 0 | TIoRange range(locationRange, componentRange, basicTy, qualifier.hasIndex() ? qualifier.getIndex() : 0, qualifier.centroid, qualifier.smooth, qualifier.flat, qualifier.sample, qualifier.patch); |
2084 | | |
2085 | | // check for collisions, except for vertex inputs on desktop targeting OpenGL |
2086 | 0 | if (! (!isEsProfile() && language == EShLangVertex && qualifier.isPipeInput()) || spvVersion.vulkan > 0) |
2087 | 0 | collision = checkLocationRange(set, range, type, typeCollision); |
2088 | |
|
2089 | 0 | if (collision < 0) |
2090 | 0 | usedIo[set].push_back(range); |
2091 | |
|
2092 | 0 | return collision; |
2093 | 0 | } |
2094 | | |
2095 | | // Check that two types can be stored in different components in the same location. |
2096 | | // They must be the same type, except signed/unsigned integers are considered compatible. |
2097 | 0 | static bool checkCompatibleTypes(TBasicType t1, TBasicType t2) { |
2098 | 0 | if (t1 != t2) { |
2099 | 0 | if ((t1 == EbtInt8 && t2 == EbtUint8) || |
2100 | 0 | (t2 == EbtInt8 && t1 == EbtUint8) || |
2101 | 0 | (t1 == EbtInt16 && t2 == EbtUint16) || |
2102 | 0 | (t2 == EbtInt16 && t1 == EbtUint16)|| |
2103 | 0 | (t1 == EbtInt && t2 == EbtUint) || |
2104 | 0 | (t2 == EbtInt && t1 == EbtUint)|| |
2105 | 0 | (t1 == EbtInt64 && t2 == EbtUint64) || |
2106 | 0 | (t2 == EbtInt64 && t1 == EbtUint64)) { |
2107 | 0 | return true; |
2108 | 0 | } |
2109 | 0 | } |
2110 | 0 | return t1 == t2; |
2111 | 0 | } |
2112 | | |
2113 | | // Compare a new (the passed in) 'range' against the existing set, and see |
2114 | | // if there are any collisions. |
2115 | | // |
2116 | | // Returns < 0 if no collision, >= 0 if collision and the value returned is a colliding value. |
2117 | | // |
2118 | | int TIntermediate::checkLocationRange(int set, const TIoRange& range, const TType& type, bool& typeCollision) |
2119 | 0 | { |
2120 | 0 | for (size_t r = 0; r < usedIo[set].size(); ++r) { |
2121 | 0 | if (range.overlap(usedIo[set][r])) { |
2122 | | // there is a collision; pick one |
2123 | 0 | return std::max(range.location.start, usedIo[set][r].location.start); |
2124 | 0 | } else if (range.location.overlap(usedIo[set][r].location) && |
2125 | 0 | (!checkCompatibleTypes(type.getBasicType(), usedIo[set][r].basicType) || |
2126 | 0 | type.getQualifier().centroid != usedIo[set][r].centroid || |
2127 | 0 | type.getQualifier().smooth != usedIo[set][r].smooth || |
2128 | 0 | type.getQualifier().flat != usedIo[set][r].flat || |
2129 | 0 | type.getQualifier().sample != usedIo[set][r].sample || |
2130 | 0 | type.getQualifier().patch != usedIo[set][r].patch)) { |
2131 | | // aliased-type mismatch |
2132 | 0 | typeCollision = true; |
2133 | 0 | return std::max(range.location.start, usedIo[set][r].location.start); |
2134 | 0 | } |
2135 | 0 | } |
2136 | | |
2137 | | // check typeCollision between tileImageEXT and out |
2138 | 0 | if (set == 4 || set == 1) { |
2139 | | // if the set is "tileImageEXT", check against "out" and vice versa |
2140 | 0 | int againstSet = (set == 4) ? 1 : 4; |
2141 | 0 | for (size_t r = 0; r < usedIo[againstSet].size(); ++r) { |
2142 | 0 | if (range.location.overlap(usedIo[againstSet][r].location) && type.getBasicType() != usedIo[againstSet][r].basicType) { |
2143 | | // aliased-type mismatch |
2144 | 0 | typeCollision = true; |
2145 | 0 | return std::max(range.location.start, usedIo[againstSet][r].location.start); |
2146 | 0 | } |
2147 | 0 | } |
2148 | 0 | } |
2149 | | |
2150 | 0 | return -1; // no collision |
2151 | 0 | } |
2152 | | |
2153 | 0 | int TIntermediate::checkLocationRT(int set, int location) { |
2154 | 0 | TRange range(location, location); |
2155 | 0 | for (size_t r = 0; r < usedIoRT[set].size(); ++r) { |
2156 | 0 | if (range.overlap(usedIoRT[set][r])) { |
2157 | 0 | return range.start; |
2158 | 0 | } |
2159 | 0 | } |
2160 | 0 | return -1; // no collision |
2161 | 0 | } |
2162 | | |
2163 | | // Accumulate bindings and offsets, and check for collisions |
2164 | | // as the accumulation is done. |
2165 | | // |
2166 | | // Returns < 0 if no collision, >= 0 if collision and the value returned is a colliding value. |
2167 | | // |
2168 | | int TIntermediate::addUsedOffsets(int binding, int offset, int numOffsets) |
2169 | 0 | { |
2170 | 0 | TRange bindingRange(binding, binding); |
2171 | 0 | TRange offsetRange(offset, offset + numOffsets - 1); |
2172 | 0 | TOffsetRange range(bindingRange, offsetRange); |
2173 | | |
2174 | | // check for collisions, except for vertex inputs on desktop |
2175 | 0 | for (size_t r = 0; r < usedAtomics.size(); ++r) { |
2176 | 0 | if (range.overlap(usedAtomics[r])) { |
2177 | | // there is a collision; pick one |
2178 | 0 | return std::max(offset, usedAtomics[r].offset.start); |
2179 | 0 | } |
2180 | 0 | } |
2181 | | |
2182 | 0 | usedAtomics.push_back(range); |
2183 | |
|
2184 | 0 | return -1; // no collision |
2185 | 0 | } |
2186 | | |
2187 | | // Accumulate used constant_id values. |
2188 | | // |
2189 | | // Return false is one was already used. |
2190 | | bool TIntermediate::addUsedConstantId(int id) |
2191 | 0 | { |
2192 | 0 | if (usedConstantId.find(id) != usedConstantId.end()) |
2193 | 0 | return false; |
2194 | | |
2195 | 0 | usedConstantId.insert(id); |
2196 | |
|
2197 | 0 | return true; |
2198 | 0 | } |
2199 | | |
2200 | | // Recursively figure out how many locations are used up by an input or output type. |
2201 | | // Return the size of type, as measured by "locations". |
2202 | | int TIntermediate::computeTypeLocationSize(const TType& type, EShLanguage stage) |
2203 | 0 | { |
2204 | | // "If the declared input is an array of size n and each element takes m locations, it will be assigned m * n |
2205 | | // consecutive locations..." |
2206 | 0 | if (type.isArray()) { |
2207 | | // TODO: perf: this can be flattened by using getCumulativeArraySize(), and a deref that discards all arrayness |
2208 | | // TODO: are there valid cases of having an unsized array with a location? If so, running this code too early. |
2209 | 0 | TType elementType(type, 0); |
2210 | 0 | if (type.isSizedArray() && !type.getQualifier().isPerView()) |
2211 | 0 | return type.getOuterArraySize() * computeTypeLocationSize(elementType, stage); |
2212 | 0 | else { |
2213 | | // unset perViewNV attributes for arrayed per-view outputs: "perviewNV vec4 v[MAX_VIEWS][3];" |
2214 | 0 | elementType.getQualifier().perViewNV = false; |
2215 | 0 | return computeTypeLocationSize(elementType, stage); |
2216 | 0 | } |
2217 | 0 | } |
2218 | | |
2219 | | // "The locations consumed by block and structure members are determined by applying the rules above |
2220 | | // recursively..." |
2221 | 0 | if (type.isStruct()) { |
2222 | 0 | int size = 0; |
2223 | 0 | for (int member = 0; member < (int)type.getStruct()->size(); ++member) { |
2224 | 0 | TType memberType(type, member); |
2225 | 0 | size += computeTypeLocationSize(memberType, stage); |
2226 | 0 | } |
2227 | 0 | return size; |
2228 | 0 | } |
2229 | | |
2230 | | // ES: "If a shader input is any scalar or vector type, it will consume a single location." |
2231 | | |
2232 | | // Desktop: "If a vertex shader input is any scalar or vector type, it will consume a single location. If a non-vertex |
2233 | | // shader input is a scalar or vector type other than dvec3 or dvec4, it will consume a single location, while |
2234 | | // types dvec3 or dvec4 will consume two consecutive locations. Inputs of type double and dvec2 will |
2235 | | // consume only a single location, in all stages." |
2236 | 0 | if (type.isScalar()) |
2237 | 0 | return 1; |
2238 | 0 | if (type.isVector()) { |
2239 | 0 | if (stage == EShLangVertex && type.getQualifier().isPipeInput()) |
2240 | 0 | return 1; |
2241 | 0 | if (type.getBasicType() == EbtDouble && type.getVectorSize() > 2) |
2242 | 0 | return 2; |
2243 | 0 | else |
2244 | 0 | return 1; |
2245 | 0 | } |
2246 | | |
2247 | | // "If the declared input is an n x m single- or double-precision matrix, ... |
2248 | | // The number of locations assigned for each matrix will be the same as |
2249 | | // for an n-element array of m-component vectors..." |
2250 | 0 | if (type.isMatrix()) { |
2251 | 0 | TType columnType(type, 0); |
2252 | 0 | return type.getMatrixCols() * computeTypeLocationSize(columnType, stage); |
2253 | 0 | } |
2254 | | |
2255 | 0 | assert(0); |
2256 | 0 | return 1; |
2257 | 0 | } |
2258 | | |
2259 | | // Same as computeTypeLocationSize but for uniforms |
2260 | | int TIntermediate::computeTypeUniformLocationSize(const TType& type) |
2261 | 0 | { |
2262 | | // "Individual elements of a uniform array are assigned |
2263 | | // consecutive locations with the first element taking location |
2264 | | // location." |
2265 | 0 | if (type.isArray()) { |
2266 | | // TODO: perf: this can be flattened by using getCumulativeArraySize(), and a deref that discards all arrayness |
2267 | 0 | TType elementType(type, 0); |
2268 | 0 | if (type.isSizedArray()) { |
2269 | 0 | return type.getOuterArraySize() * computeTypeUniformLocationSize(elementType); |
2270 | 0 | } else { |
2271 | | // TODO: are there valid cases of having an implicitly-sized array with a location? If so, running this code too early. |
2272 | 0 | return computeTypeUniformLocationSize(elementType); |
2273 | 0 | } |
2274 | 0 | } |
2275 | | |
2276 | | // "Each subsequent inner-most member or element gets incremental |
2277 | | // locations for the entire structure or array." |
2278 | 0 | if (type.isStruct()) { |
2279 | 0 | int size = 0; |
2280 | 0 | for (int member = 0; member < (int)type.getStruct()->size(); ++member) { |
2281 | 0 | TType memberType(type, member); |
2282 | 0 | size += computeTypeUniformLocationSize(memberType); |
2283 | 0 | } |
2284 | 0 | return size; |
2285 | 0 | } |
2286 | | |
2287 | 0 | return 1; |
2288 | 0 | } |
2289 | | |
2290 | | // Accumulate xfb buffer ranges and check for collisions as the accumulation is done. |
2291 | | // |
2292 | | // Returns < 0 if no collision, >= 0 if collision and the value returned is a colliding value. |
2293 | | // |
2294 | | int TIntermediate::addXfbBufferOffset(const TType& type) |
2295 | 0 | { |
2296 | 0 | const TQualifier& qualifier = type.getQualifier(); |
2297 | |
|
2298 | 0 | assert(qualifier.hasXfbOffset() && qualifier.hasXfbBuffer()); |
2299 | 0 | TXfbBuffer& buffer = xfbBuffers[qualifier.layoutXfbBuffer]; |
2300 | | |
2301 | | // compute the range |
2302 | 0 | unsigned int size = computeTypeXfbSize(type, buffer.contains64BitType, buffer.contains32BitType, buffer.contains16BitType); |
2303 | 0 | buffer.implicitStride = std::max(buffer.implicitStride, qualifier.layoutXfbOffset + size); |
2304 | 0 | TRange range(qualifier.layoutXfbOffset, qualifier.layoutXfbOffset + size - 1); |
2305 | | |
2306 | | // check for collisions |
2307 | 0 | for (size_t r = 0; r < buffer.ranges.size(); ++r) { |
2308 | 0 | if (range.overlap(buffer.ranges[r])) { |
2309 | | // there is a collision; pick an example to return |
2310 | 0 | return std::max(range.start, buffer.ranges[r].start); |
2311 | 0 | } |
2312 | 0 | } |
2313 | | |
2314 | 0 | buffer.ranges.push_back(range); |
2315 | |
|
2316 | 0 | return -1; // no collision |
2317 | 0 | } |
2318 | | |
2319 | | // Recursively figure out how many bytes of xfb buffer are used by the given type. |
2320 | | // Return the size of type, in bytes. |
2321 | | // Sets contains64BitType to true if the type contains a 64-bit data type. |
2322 | | // Sets contains32BitType to true if the type contains a 32-bit data type. |
2323 | | // Sets contains16BitType to true if the type contains a 16-bit data type. |
2324 | | // N.B. Caller must set contains64BitType, contains32BitType, and contains16BitType to false before calling. |
2325 | | unsigned int TIntermediate::computeTypeXfbSize(const TType& type, bool& contains64BitType, bool& contains32BitType, bool& contains16BitType) const |
2326 | 0 | { |
2327 | | // "...if applied to an aggregate containing a double or 64-bit integer, the offset must also be a multiple of 8, |
2328 | | // and the space taken in the buffer will be a multiple of 8. |
2329 | | // ...within the qualified entity, subsequent components are each |
2330 | | // assigned, in order, to the next available offset aligned to a multiple of |
2331 | | // that component's size. Aggregate types are flattened down to the component |
2332 | | // level to get this sequence of components." |
2333 | |
|
2334 | 0 | if (type.isSizedArray()) { |
2335 | | // TODO: perf: this can be flattened by using getCumulativeArraySize(), and a deref that discards all arrayness |
2336 | | // Unsized array use to xfb should be a compile error. |
2337 | 0 | TType elementType(type, 0); |
2338 | 0 | return type.getOuterArraySize() * computeTypeXfbSize(elementType, contains64BitType, contains16BitType, contains16BitType); |
2339 | 0 | } |
2340 | | |
2341 | 0 | if (type.isStruct()) { |
2342 | 0 | unsigned int size = 0; |
2343 | 0 | bool structContains64BitType = false; |
2344 | 0 | bool structContains32BitType = false; |
2345 | 0 | bool structContains16BitType = false; |
2346 | 0 | for (int member = 0; member < (int)type.getStruct()->size(); ++member) { |
2347 | 0 | TType memberType(type, member); |
2348 | | // "... if applied to |
2349 | | // an aggregate containing a double or 64-bit integer, the offset must also be a multiple of 8, |
2350 | | // and the space taken in the buffer will be a multiple of 8." |
2351 | 0 | bool memberContains64BitType = false; |
2352 | 0 | bool memberContains32BitType = false; |
2353 | 0 | bool memberContains16BitType = false; |
2354 | 0 | int memberSize = computeTypeXfbSize(memberType, memberContains64BitType, memberContains32BitType, memberContains16BitType); |
2355 | 0 | if (memberContains64BitType) { |
2356 | 0 | structContains64BitType = true; |
2357 | 0 | RoundToPow2(size, 8); |
2358 | 0 | } else if (memberContains32BitType) { |
2359 | 0 | structContains32BitType = true; |
2360 | 0 | RoundToPow2(size, 4); |
2361 | 0 | } else if (memberContains16BitType) { |
2362 | 0 | structContains16BitType = true; |
2363 | 0 | RoundToPow2(size, 2); |
2364 | 0 | } |
2365 | 0 | size += memberSize; |
2366 | 0 | } |
2367 | |
|
2368 | 0 | if (structContains64BitType) { |
2369 | 0 | contains64BitType = true; |
2370 | 0 | RoundToPow2(size, 8); |
2371 | 0 | } else if (structContains32BitType) { |
2372 | 0 | contains32BitType = true; |
2373 | 0 | RoundToPow2(size, 4); |
2374 | 0 | } else if (structContains16BitType) { |
2375 | 0 | contains16BitType = true; |
2376 | 0 | RoundToPow2(size, 2); |
2377 | 0 | } |
2378 | 0 | return size; |
2379 | 0 | } |
2380 | | |
2381 | 0 | int numComponents {0}; |
2382 | 0 | if (type.isScalar()) |
2383 | 0 | numComponents = 1; |
2384 | 0 | else if (type.isVector()) |
2385 | 0 | numComponents = type.getVectorSize(); |
2386 | 0 | else if (type.isMatrix()) |
2387 | 0 | numComponents = type.getMatrixCols() * type.getMatrixRows(); |
2388 | 0 | else { |
2389 | 0 | assert(0); |
2390 | 0 | numComponents = 1; |
2391 | 0 | } |
2392 | |
|
2393 | 0 | if (type.getBasicType() == EbtDouble || type.getBasicType() == EbtInt64 || type.getBasicType() == EbtUint64) { |
2394 | 0 | contains64BitType = true; |
2395 | 0 | return 8 * numComponents; |
2396 | 0 | } else if (type.getBasicType() == EbtFloat16 || type.getBasicType() == EbtInt16 || type.getBasicType() == EbtUint16) { |
2397 | 0 | contains16BitType = true; |
2398 | 0 | return 2 * numComponents; |
2399 | 0 | } else if (type.getBasicType() == EbtInt8 || type.getBasicType() == EbtUint8) |
2400 | 0 | return numComponents; |
2401 | 0 | else { |
2402 | 0 | contains32BitType = true; |
2403 | 0 | return 4 * numComponents; |
2404 | 0 | } |
2405 | 0 | } |
2406 | | |
2407 | | const int baseAlignmentVec4Std140 = 16; |
2408 | | |
2409 | | // Return the size and alignment of a component of the given type. |
2410 | | // The size is returned in the 'size' parameter |
2411 | | // Return value is the alignment.. |
2412 | | int TIntermediate::getBaseAlignmentScalar(const TType& type, int& size) |
2413 | 0 | { |
2414 | 0 | switch (type.getBasicType()) { |
2415 | 0 | case EbtInt64: |
2416 | 0 | case EbtUint64: |
2417 | 0 | case EbtDouble: size = 8; return 8; |
2418 | 0 | case EbtFloat16: size = 2; return 2; |
2419 | 0 | case EbtBFloat16: size = 2; return 2; |
2420 | 0 | case EbtFloatE5M2: |
2421 | 0 | case EbtFloatE4M3: |
2422 | 0 | case EbtInt8: |
2423 | 0 | case EbtUint8: size = 1; return 1; |
2424 | 0 | case EbtInt16: |
2425 | 0 | case EbtUint16: size = 2; return 2; |
2426 | 0 | case EbtReference: size = 8; return 8; |
2427 | 0 | case EbtSampler: |
2428 | 0 | { |
2429 | 0 | if (type.isBindlessImage() || type.isBindlessTexture()) { |
2430 | 0 | size = 8; return 8; |
2431 | 0 | } |
2432 | 0 | else { |
2433 | 0 | size = 4; return 4; |
2434 | 0 | } |
2435 | 0 | } |
2436 | 0 | default: size = 4; return 4; |
2437 | 0 | } |
2438 | 0 | } |
2439 | | |
2440 | | // Implement base-alignment and size rules from section 7.6.2.2 Standard Uniform Block Layout |
2441 | | // Operates recursively. |
2442 | | // |
2443 | | // If std140 is true, it does the rounding up to vec4 size required by std140, |
2444 | | // otherwise it does not, yielding std430 rules. |
2445 | | // |
2446 | | // The size is returned in the 'size' parameter |
2447 | | // |
2448 | | // The stride is only non-0 for arrays or matrices, and is the stride of the |
2449 | | // top-level object nested within the type. E.g., for an array of matrices, |
2450 | | // it is the distances needed between matrices, despite the rules saying the |
2451 | | // stride comes from the flattening down to vectors. |
2452 | | // |
2453 | | // Return value is the alignment of the type. |
2454 | | int TIntermediate::getBaseAlignment(const TType& type, int& size, int& stride, TLayoutPacking layoutPacking, bool rowMajor) |
2455 | 0 | { |
2456 | 0 | int alignment; |
2457 | |
|
2458 | 0 | bool std140 = layoutPacking == glslang::ElpStd140; |
2459 | | // When using the std140 storage layout, structures will be laid out in buffer |
2460 | | // storage with its members stored in monotonically increasing order based on their |
2461 | | // location in the declaration. A structure and each structure member have a base |
2462 | | // offset and a base alignment, from which an aligned offset is computed by rounding |
2463 | | // the base offset up to a multiple of the base alignment. The base offset of the first |
2464 | | // member of a structure is taken from the aligned offset of the structure itself. The |
2465 | | // base offset of all other structure members is derived by taking the offset of the |
2466 | | // last basic machine unit consumed by the previous member and adding one. Each |
2467 | | // structure member is stored in memory at its aligned offset. The members of a top- |
2468 | | // level uniform block are laid out in buffer storage by treating the uniform block as |
2469 | | // a structure with a base offset of zero. |
2470 | | // |
2471 | | // 1. If the member is a scalar consuming N basic machine units, the base alignment is N. |
2472 | | // |
2473 | | // 2. If the member is a two- or four-component vector with components consuming N basic |
2474 | | // machine units, the base alignment is 2N or 4N, respectively. |
2475 | | // |
2476 | | // 3. If the member is a three-component vector with components consuming N |
2477 | | // basic machine units, the base alignment is 4N. |
2478 | | // |
2479 | | // 4. If the member is an array of scalars or vectors, the base alignment and array |
2480 | | // stride are set to match the base alignment of a single array element, according |
2481 | | // to rules (1), (2), and (3), and rounded up to the base alignment of a vec4. The |
2482 | | // array may have padding at the end; the base offset of the member following |
2483 | | // the array is rounded up to the next multiple of the base alignment. |
2484 | | // |
2485 | | // 5. If the member is a column-major matrix with C columns and R rows, the |
2486 | | // matrix is stored identically to an array of C column vectors with R |
2487 | | // components each, according to rule (4). |
2488 | | // |
2489 | | // 6. If the member is an array of S column-major matrices with C columns and |
2490 | | // R rows, the matrix is stored identically to a row of S X C column vectors |
2491 | | // with R components each, according to rule (4). |
2492 | | // |
2493 | | // 7. If the member is a row-major matrix with C columns and R rows, the matrix |
2494 | | // is stored identically to an array of R row vectors with C components each, |
2495 | | // according to rule (4). |
2496 | | // |
2497 | | // 8. If the member is an array of S row-major matrices with C columns and R |
2498 | | // rows, the matrix is stored identically to a row of S X R row vectors with C |
2499 | | // components each, according to rule (4). |
2500 | | // |
2501 | | // 9. If the member is a structure, the base alignment of the structure is N , where |
2502 | | // N is the largest base alignment value of any of its members, and rounded |
2503 | | // up to the base alignment of a vec4. The individual members of this substructure |
2504 | | // are then assigned offsets by applying this set of rules recursively, |
2505 | | // where the base offset of the first member of the sub-structure is equal to the |
2506 | | // aligned offset of the structure. The structure may have padding at the end; |
2507 | | // the base offset of the member following the sub-structure is rounded up to |
2508 | | // the next multiple of the base alignment of the structure. |
2509 | | // |
2510 | | // 10. If the member is an array of S structures, the S elements of the array are laid |
2511 | | // out in order, according to rule (9). |
2512 | | // |
2513 | | // Assuming, for rule 10: The stride is the same as the size of an element. |
2514 | |
|
2515 | 0 | stride = 0; |
2516 | 0 | int dummyStride; |
2517 | | |
2518 | | // rules 4, 6, 8, and 10 |
2519 | 0 | if (type.isArray()) { |
2520 | | // TODO: perf: this might be flattened by using getCumulativeArraySize(), and a deref that discards all arrayness |
2521 | 0 | TType derefType(type, 0); |
2522 | 0 | alignment = getBaseAlignment(derefType, size, dummyStride, layoutPacking, rowMajor); |
2523 | 0 | if (std140) |
2524 | 0 | alignment = std::max(baseAlignmentVec4Std140, alignment); |
2525 | 0 | RoundToPow2(size, alignment); |
2526 | 0 | stride = size; // uses full matrix size for stride of an array of matrices (not quite what rule 6/8, but what's expected) |
2527 | | // uses the assumption for rule 10 in the comment above |
2528 | | // use one element to represent the last member of SSBO which is unsized array |
2529 | 0 | int arraySize = (type.isUnsizedArray() && (type.getOuterArraySize() == 0)) ? 1 : type.getOuterArraySize(); |
2530 | 0 | size = stride * arraySize; |
2531 | 0 | return alignment; |
2532 | 0 | } |
2533 | | |
2534 | | // rule 9 |
2535 | 0 | if (type.getBasicType() == EbtStruct || type.getBasicType() == EbtBlock) { |
2536 | 0 | const TTypeList& memberList = *type.getStruct(); |
2537 | |
|
2538 | 0 | size = 0; |
2539 | 0 | int maxAlignment = std140 ? baseAlignmentVec4Std140 : 0; |
2540 | 0 | for (size_t m = 0; m < memberList.size(); ++m) { |
2541 | 0 | int memberSize; |
2542 | | // modify just the children's view of matrix layout, if there is one for this member |
2543 | 0 | TLayoutMatrix subMatrixLayout = memberList[m].type->getQualifier().layoutMatrix; |
2544 | 0 | int memberAlignment = getBaseAlignment(*memberList[m].type, memberSize, dummyStride, layoutPacking, |
2545 | 0 | (subMatrixLayout != ElmNone) ? (subMatrixLayout == ElmRowMajor) : rowMajor); |
2546 | 0 | maxAlignment = std::max(maxAlignment, memberAlignment); |
2547 | 0 | RoundToPow2(size, memberAlignment); |
2548 | 0 | size += memberSize; |
2549 | 0 | } |
2550 | | |
2551 | | // The structure may have padding at the end; the base offset of |
2552 | | // the member following the sub-structure is rounded up to the next |
2553 | | // multiple of the base alignment of the structure. |
2554 | 0 | RoundToPow2(size, maxAlignment); |
2555 | |
|
2556 | 0 | return maxAlignment; |
2557 | 0 | } |
2558 | | |
2559 | | // rule 1 |
2560 | 0 | if (type.isScalar()) |
2561 | 0 | return getBaseAlignmentScalar(type, size); |
2562 | | |
2563 | | // rules 2 and 3 |
2564 | 0 | if (type.isVector()) { |
2565 | 0 | int scalarAlign = getBaseAlignmentScalar(type, size); |
2566 | 0 | switch (type.getVectorSize()) { |
2567 | 0 | case 1: // HLSL has this, GLSL does not |
2568 | 0 | return scalarAlign; |
2569 | 0 | case 2: |
2570 | 0 | size *= 2; |
2571 | 0 | return 2 * scalarAlign; |
2572 | 0 | default: |
2573 | 0 | size *= type.getVectorSize(); |
2574 | 0 | return 4 * scalarAlign; |
2575 | 0 | } |
2576 | 0 | } |
2577 | | |
2578 | | // rules 2 and 3 |
2579 | 0 | if (type.isLongVector()) { |
2580 | 0 | int scalarAlign = getBaseAlignmentScalar(type, size); |
2581 | 0 | uint32_t vectorSize = type.getTypeParameters()->arraySizes->getDimSize(0); |
2582 | 0 | switch (vectorSize) { |
2583 | 0 | case 1: // HLSL has this, GLSL does not |
2584 | 0 | return scalarAlign; |
2585 | 0 | case 2: |
2586 | 0 | size *= 2; |
2587 | 0 | return 2 * scalarAlign; |
2588 | 0 | default: |
2589 | 0 | size *= vectorSize; |
2590 | 0 | return 4 * scalarAlign; |
2591 | 0 | } |
2592 | 0 | } |
2593 | | |
2594 | | // rules 5 and 7 |
2595 | 0 | if (type.isMatrix()) { |
2596 | | // rule 5: deref to row, not to column, meaning the size of vector is num columns instead of num rows |
2597 | 0 | TType derefType(type, 0, rowMajor); |
2598 | |
|
2599 | 0 | alignment = getBaseAlignment(derefType, size, dummyStride, layoutPacking, rowMajor); |
2600 | 0 | if (std140) |
2601 | 0 | alignment = std::max(baseAlignmentVec4Std140, alignment); |
2602 | 0 | RoundToPow2(size, alignment); |
2603 | 0 | stride = size; // use intra-matrix stride for stride of a just a matrix |
2604 | 0 | if (rowMajor) |
2605 | 0 | size = stride * type.getMatrixRows(); |
2606 | 0 | else |
2607 | 0 | size = stride * type.getMatrixCols(); |
2608 | |
|
2609 | 0 | return alignment; |
2610 | 0 | } |
2611 | | |
2612 | 0 | assert(0); // all cases should be covered above |
2613 | 0 | size = baseAlignmentVec4Std140; |
2614 | 0 | return baseAlignmentVec4Std140; |
2615 | 0 | } |
2616 | | |
2617 | | // To aid the basic HLSL rule about crossing vec4 boundaries. |
2618 | | bool TIntermediate::improperStraddle(const TType& type, int size, int offset, bool vectorLike) |
2619 | 0 | { |
2620 | 0 | if (! vectorLike || type.isArray()) |
2621 | 0 | return false; |
2622 | | |
2623 | 0 | return size <= 16 ? offset / 16 != (offset + size - 1) / 16 |
2624 | 0 | : offset % 16 != 0; |
2625 | 0 | } |
2626 | | |
2627 | | int TIntermediate::getScalarAlignment(const TType& type, int& size, int& stride, bool rowMajor) |
2628 | 0 | { |
2629 | 0 | int alignment; |
2630 | |
|
2631 | 0 | stride = 0; |
2632 | 0 | int dummyStride; |
2633 | |
|
2634 | 0 | if (type.isArray()) { |
2635 | 0 | TType derefType(type, 0); |
2636 | 0 | alignment = getScalarAlignment(derefType, size, dummyStride, rowMajor); |
2637 | |
|
2638 | 0 | stride = size; |
2639 | 0 | RoundToPow2(stride, alignment); |
2640 | |
|
2641 | 0 | size = stride * (type.getOuterArraySize() - 1) + size; |
2642 | 0 | return alignment; |
2643 | 0 | } |
2644 | | |
2645 | 0 | if (type.getBasicType() == EbtStruct) { |
2646 | 0 | const TTypeList& memberList = *type.getStruct(); |
2647 | |
|
2648 | 0 | size = 0; |
2649 | 0 | int maxAlignment = 0; |
2650 | 0 | for (size_t m = 0; m < memberList.size(); ++m) { |
2651 | 0 | int memberSize; |
2652 | | // modify just the children's view of matrix layout, if there is one for this member |
2653 | 0 | TLayoutMatrix subMatrixLayout = memberList[m].type->getQualifier().layoutMatrix; |
2654 | 0 | int memberAlignment = getScalarAlignment(*memberList[m].type, memberSize, dummyStride, |
2655 | 0 | (subMatrixLayout != ElmNone) ? (subMatrixLayout == ElmRowMajor) : rowMajor); |
2656 | 0 | maxAlignment = std::max(maxAlignment, memberAlignment); |
2657 | 0 | RoundToPow2(size, memberAlignment); |
2658 | 0 | size += memberSize; |
2659 | 0 | } |
2660 | |
|
2661 | 0 | return maxAlignment; |
2662 | 0 | } |
2663 | | |
2664 | 0 | if (type.isScalar()) |
2665 | 0 | return getBaseAlignmentScalar(type, size); |
2666 | | |
2667 | 0 | if (type.isVector()) { |
2668 | 0 | int scalarAlign = getBaseAlignmentScalar(type, size); |
2669 | |
|
2670 | 0 | size *= type.getVectorSize(); |
2671 | 0 | return scalarAlign; |
2672 | 0 | } |
2673 | | |
2674 | 0 | if (type.isLongVector()) { |
2675 | 0 | int scalarAlign = getBaseAlignmentScalar(type, size); |
2676 | |
|
2677 | 0 | uint32_t vectorSize = type.getTypeParameters()->arraySizes->getDimSize(0); |
2678 | 0 | size *= vectorSize; |
2679 | 0 | return scalarAlign; |
2680 | 0 | } |
2681 | | |
2682 | 0 | if (type.isMatrix()) { |
2683 | 0 | TType derefType(type, 0, rowMajor); |
2684 | |
|
2685 | 0 | alignment = getScalarAlignment(derefType, size, dummyStride, rowMajor); |
2686 | |
|
2687 | 0 | stride = size; // use intra-matrix stride for stride of a just a matrix |
2688 | 0 | if (rowMajor) |
2689 | 0 | size = stride * type.getMatrixRows(); |
2690 | 0 | else |
2691 | 0 | size = stride * type.getMatrixCols(); |
2692 | |
|
2693 | 0 | return alignment; |
2694 | 0 | } |
2695 | | |
2696 | 0 | assert(0); // all cases should be covered above |
2697 | 0 | size = 1; |
2698 | 0 | return 1; |
2699 | 0 | } |
2700 | | |
2701 | | int TIntermediate::getMemberAlignment(const TType& type, int& size, int& stride, TLayoutPacking layoutPacking, bool rowMajor) |
2702 | 0 | { |
2703 | 0 | if (layoutPacking == glslang::ElpScalar) { |
2704 | 0 | return getScalarAlignment(type, size, stride, rowMajor); |
2705 | 0 | } else { |
2706 | 0 | return getBaseAlignment(type, size, stride, layoutPacking, rowMajor); |
2707 | 0 | } |
2708 | 0 | } |
2709 | | |
2710 | | // shared calculation by getOffset and getOffsets |
2711 | | void TIntermediate::updateOffset(const TType& parentType, const TType& memberType, int& offset, int& memberSize) |
2712 | 0 | { |
2713 | 0 | int dummyStride; |
2714 | | |
2715 | | // modify just the children's view of matrix layout, if there is one for this member |
2716 | 0 | TLayoutMatrix subMatrixLayout = memberType.getQualifier().layoutMatrix; |
2717 | 0 | int memberAlignment = getMemberAlignment(memberType, memberSize, dummyStride, |
2718 | 0 | parentType.getQualifier().layoutPacking, |
2719 | 0 | subMatrixLayout != ElmNone |
2720 | 0 | ? subMatrixLayout == ElmRowMajor |
2721 | 0 | : parentType.getQualifier().layoutMatrix == ElmRowMajor); |
2722 | 0 | RoundToPow2(offset, memberAlignment); |
2723 | 0 | } |
2724 | | |
2725 | | // Lookup or calculate the offset of a block member, using the recursively |
2726 | | // defined block offset rules. |
2727 | | int TIntermediate::getOffset(const TType& type, int index) |
2728 | 0 | { |
2729 | 0 | const TTypeList& memberList = *type.getStruct(); |
2730 | | |
2731 | | // Don't calculate offset if one is present, it could be user supplied |
2732 | | // and different than what would be calculated. That is, this is faster, |
2733 | | // but not just an optimization. |
2734 | 0 | if (memberList[index].type->getQualifier().hasOffset()) |
2735 | 0 | return memberList[index].type->getQualifier().layoutOffset; |
2736 | | |
2737 | 0 | int memberSize = 0; |
2738 | 0 | int offset = 0; |
2739 | 0 | for (int m = 0; m <= index; ++m) { |
2740 | 0 | updateOffset(type, *memberList[m].type, offset, memberSize); |
2741 | |
|
2742 | 0 | if (m < index) |
2743 | 0 | offset += memberSize; |
2744 | 0 | } |
2745 | |
|
2746 | 0 | return offset; |
2747 | 0 | } |
2748 | | |
2749 | | // Calculate the block data size. |
2750 | | // Block arrayness is not taken into account, each element is backed by a separate buffer. |
2751 | | int TIntermediate::getBlockSize(const TType& blockType) |
2752 | 0 | { |
2753 | 0 | const TTypeList& memberList = *blockType.getStruct(); |
2754 | 0 | int lastIndex = (int)memberList.size() - 1; |
2755 | 0 | int lastOffset = getOffset(blockType, lastIndex); |
2756 | |
|
2757 | 0 | int lastMemberSize; |
2758 | 0 | int dummyStride; |
2759 | 0 | getMemberAlignment(*memberList[lastIndex].type, lastMemberSize, dummyStride, |
2760 | 0 | blockType.getQualifier().layoutPacking, |
2761 | 0 | blockType.getQualifier().layoutMatrix == ElmRowMajor); |
2762 | |
|
2763 | 0 | return lastOffset + lastMemberSize; |
2764 | 0 | } |
2765 | | |
2766 | | int TIntermediate::computeBufferReferenceTypeSize(const TType& type) |
2767 | 0 | { |
2768 | 0 | assert(type.isReference()); |
2769 | 0 | int size = getBlockSize(*type.getReferentType()); |
2770 | |
|
2771 | 0 | int align = type.getBufferReferenceAlignment(); |
2772 | |
|
2773 | 0 | if (align) { |
2774 | 0 | size = (size + align - 1) & ~(align-1); |
2775 | 0 | } |
2776 | |
|
2777 | 0 | return size; |
2778 | 0 | } |
2779 | | |
2780 | 0 | bool TIntermediate::isIoResizeArray(const TType& type, EShLanguage language) { |
2781 | 0 | return type.isArray() && |
2782 | 0 | ((language == EShLangGeometry && type.getQualifier().storage == EvqVaryingIn) || |
2783 | 0 | (language == EShLangTessControl && (type.getQualifier().storage == EvqVaryingIn || type.getQualifier().storage == EvqVaryingOut) && |
2784 | 0 | ! type.getQualifier().patch) || |
2785 | 0 | (language == EShLangTessEvaluation && type.getQualifier().storage == EvqVaryingIn) || |
2786 | 0 | (language == EShLangFragment && type.getQualifier().storage == EvqVaryingIn && |
2787 | 0 | (type.getQualifier().pervertexNV || type.getQualifier().pervertexEXT)) || |
2788 | 0 | (language == EShLangMesh && type.getQualifier().storage == EvqVaryingOut && |
2789 | 0 | !type.getQualifier().perTaskNV)); |
2790 | 0 | } |
2791 | | |
2792 | | } // end namespace glslang |