1 | // |
2 | // Copyright (c) 2013-2014 The ANGLE Project Authors. All rights reserved. |
3 | // Use of this source code is governed by a BSD-style license that can be |
4 | // found in the LICENSE file. |
5 | // |
6 | // blocklayout.cpp: |
7 | // Implementation for block layout classes and methods. |
8 | // |
9 | |
10 | #include "compiler/translator/blocklayout.h" |
11 | |
12 | #include "common/mathutil.h" |
13 | #include "common/utilities.h" |
14 | #include "compiler/translator/Common.h" |
15 | |
16 | namespace sh |
17 | { |
18 | |
19 | namespace |
20 | { |
21 | class BlockLayoutMapVisitor : public BlockEncoderVisitor |
22 | { |
23 | public: |
24 | BlockLayoutMapVisitor(BlockLayoutMap *blockInfoOut, |
25 | const std::string &instanceName, |
26 | BlockLayoutEncoder *encoder) |
27 | : BlockEncoderVisitor(instanceName, instanceName, encoder), mInfoOut(blockInfoOut) |
28 | {} |
29 | |
30 | void encodeVariable(const ShaderVariable &variable, |
31 | const BlockMemberInfo &variableInfo, |
32 | const std::string &name, |
33 | const std::string &mappedName) override |
34 | { |
35 | ASSERT(!gl::IsSamplerType(variable.type)); |
36 | (*mInfoOut)[name] = variableInfo; |
37 | } |
38 | |
39 | private: |
40 | BlockLayoutMap *mInfoOut; |
41 | }; |
42 | |
43 | template <typename VarT> |
44 | void GetInterfaceBlockInfo(const std::vector<VarT> &fields, |
45 | const std::string &prefix, |
46 | BlockLayoutEncoder *encoder, |
47 | bool inRowMajorLayout, |
48 | BlockLayoutMap *blockInfoOut) |
49 | { |
50 | BlockLayoutMapVisitor visitor(blockInfoOut, prefix, encoder); |
51 | TraverseShaderVariables(fields, inRowMajorLayout, &visitor); |
52 | } |
53 | |
54 | void TraverseStructVariable(const ShaderVariable &variable, |
55 | bool isRowMajorLayout, |
56 | ShaderVariableVisitor *visitor) |
57 | { |
58 | const std::vector<ShaderVariable> &fields = variable.fields; |
59 | |
60 | visitor->enterStructAccess(variable, isRowMajorLayout); |
61 | TraverseShaderVariables(fields, isRowMajorLayout, visitor); |
62 | visitor->exitStructAccess(variable, isRowMajorLayout); |
63 | } |
64 | |
65 | void TraverseStructArrayVariable(const ShaderVariable &variable, |
66 | bool inRowMajorLayout, |
67 | ShaderVariableVisitor *visitor) |
68 | { |
69 | visitor->enterArray(variable); |
70 | |
71 | // Nested arrays are processed starting from outermost (arrayNestingIndex 0u) and ending at the |
72 | // innermost. We make a special case for unsized arrays. |
73 | const unsigned int currentArraySize = variable.getNestedArraySize(0); |
74 | unsigned int count = std::max(currentArraySize, 1u); |
75 | for (unsigned int arrayElement = 0u; arrayElement < count; ++arrayElement) |
76 | { |
77 | visitor->enterArrayElement(variable, arrayElement); |
78 | ShaderVariable elementVar = variable; |
79 | elementVar.indexIntoArray(arrayElement); |
80 | |
81 | if (variable.arraySizes.size() > 1u) |
82 | { |
83 | TraverseStructArrayVariable(elementVar, inRowMajorLayout, visitor); |
84 | } |
85 | else |
86 | { |
87 | TraverseStructVariable(elementVar, inRowMajorLayout, visitor); |
88 | } |
89 | |
90 | visitor->exitArrayElement(variable, arrayElement); |
91 | } |
92 | |
93 | visitor->exitArray(variable); |
94 | } |
95 | |
96 | void TraverseArrayOfArraysVariable(const ShaderVariable &variable, |
97 | unsigned int arrayNestingIndex, |
98 | bool isRowMajorMatrix, |
99 | ShaderVariableVisitor *visitor) |
100 | { |
101 | visitor->enterArray(variable); |
102 | |
103 | const unsigned int currentArraySize = variable.getNestedArraySize(arrayNestingIndex); |
104 | unsigned int count = std::max(currentArraySize, 1u); |
105 | for (unsigned int arrayElement = 0u; arrayElement < count; ++arrayElement) |
106 | { |
107 | visitor->enterArrayElement(variable, arrayElement); |
108 | |
109 | ShaderVariable elementVar = variable; |
110 | elementVar.indexIntoArray(arrayElement); |
111 | |
112 | if (arrayNestingIndex + 2u < variable.arraySizes.size()) |
113 | { |
114 | TraverseArrayOfArraysVariable(elementVar, arrayNestingIndex, isRowMajorMatrix, visitor); |
115 | } |
116 | else |
117 | { |
118 | if (gl::IsSamplerType(variable.type)) |
119 | { |
120 | visitor->visitSampler(elementVar); |
121 | } |
122 | else |
123 | { |
124 | visitor->visitVariable(elementVar, isRowMajorMatrix); |
125 | } |
126 | } |
127 | |
128 | visitor->exitArrayElement(variable, arrayElement); |
129 | } |
130 | |
131 | visitor->exitArray(variable); |
132 | } |
133 | |
134 | std::string CollapseNameStack(const std::vector<std::string> &nameStack) |
135 | { |
136 | std::stringstream strstr = sh::InitializeStream<std::stringstream>(); |
137 | for (const std::string &part : nameStack) |
138 | { |
139 | strstr << part; |
140 | } |
141 | return strstr.str(); |
142 | } |
143 | |
144 | size_t GetStd430BaseAlignment(GLenum variableType, bool isRowMajor) |
145 | { |
146 | GLenum flippedType = isRowMajor ? variableType : gl::TransposeMatrixType(variableType); |
147 | size_t numComponents = static_cast<size_t>(gl::VariableColumnCount(flippedType)); |
148 | return ComponentAlignment(numComponents); |
149 | } |
150 | |
151 | class BaseAlignmentVisitor : public ShaderVariableVisitor |
152 | { |
153 | public: |
154 | BaseAlignmentVisitor() = default; |
155 | void visitVariable(const ShaderVariable &variable, bool isRowMajor) override |
156 | { |
157 | size_t baseAlignment = GetStd430BaseAlignment(variable.type, isRowMajor); |
158 | mCurrentAlignment = std::max(mCurrentAlignment, baseAlignment); |
159 | } |
160 | |
161 | // This is in components rather than bytes. |
162 | size_t getBaseAlignment() const { return mCurrentAlignment; } |
163 | |
164 | private: |
165 | size_t mCurrentAlignment = 0; |
166 | }; |
167 | } // anonymous namespace |
168 | |
169 | // BlockLayoutEncoder implementation. |
170 | BlockLayoutEncoder::BlockLayoutEncoder() : mCurrentOffset(0) {} |
171 | |
172 | BlockMemberInfo BlockLayoutEncoder::encodeType(GLenum type, |
173 | const std::vector<unsigned int> &arraySizes, |
174 | bool isRowMajorMatrix) |
175 | { |
176 | int arrayStride; |
177 | int matrixStride; |
178 | |
179 | getBlockLayoutInfo(type, arraySizes, isRowMajorMatrix, &arrayStride, &matrixStride); |
180 | |
181 | const BlockMemberInfo memberInfo(static_cast<int>(mCurrentOffset * kBytesPerComponent), |
182 | static_cast<int>(arrayStride * kBytesPerComponent), |
183 | static_cast<int>(matrixStride * kBytesPerComponent), |
184 | isRowMajorMatrix); |
185 | |
186 | advanceOffset(type, arraySizes, isRowMajorMatrix, arrayStride, matrixStride); |
187 | |
188 | return memberInfo; |
189 | } |
190 | |
191 | size_t BlockLayoutEncoder::getShaderVariableSize(const ShaderVariable &structVar, bool isRowMajor) |
192 | { |
193 | size_t currentOffset = mCurrentOffset; |
194 | mCurrentOffset = 0; |
195 | BlockEncoderVisitor visitor("" , "" , this); |
196 | enterAggregateType(structVar); |
197 | TraverseShaderVariables(structVar.fields, isRowMajor, &visitor); |
198 | exitAggregateType(structVar); |
199 | size_t structVarSize = getCurrentOffset(); |
200 | mCurrentOffset = currentOffset; |
201 | return structVarSize; |
202 | } |
203 | |
204 | // static |
205 | size_t BlockLayoutEncoder::GetBlockRegister(const BlockMemberInfo &info) |
206 | { |
207 | return (info.offset / kBytesPerComponent) / kComponentsPerRegister; |
208 | } |
209 | |
210 | // static |
211 | size_t BlockLayoutEncoder::GetBlockRegisterElement(const BlockMemberInfo &info) |
212 | { |
213 | return (info.offset / kBytesPerComponent) % kComponentsPerRegister; |
214 | } |
215 | |
216 | void BlockLayoutEncoder::align(size_t baseAlignment) |
217 | { |
218 | mCurrentOffset = rx::roundUp<size_t>(mCurrentOffset, baseAlignment); |
219 | } |
220 | |
221 | // DummyBlockEncoder implementation. |
222 | void DummyBlockEncoder::getBlockLayoutInfo(GLenum type, |
223 | const std::vector<unsigned int> &arraySizes, |
224 | bool isRowMajorMatrix, |
225 | int *arrayStrideOut, |
226 | int *matrixStrideOut) |
227 | { |
228 | *arrayStrideOut = 0; |
229 | *matrixStrideOut = 0; |
230 | } |
231 | |
232 | // Std140BlockEncoder implementation. |
233 | Std140BlockEncoder::Std140BlockEncoder() {} |
234 | |
235 | void Std140BlockEncoder::enterAggregateType(const ShaderVariable &structVar) |
236 | { |
237 | align(getBaseAlignment(structVar)); |
238 | } |
239 | |
240 | void Std140BlockEncoder::exitAggregateType(const ShaderVariable &structVar) |
241 | { |
242 | align(getBaseAlignment(structVar)); |
243 | } |
244 | |
245 | void Std140BlockEncoder::getBlockLayoutInfo(GLenum type, |
246 | const std::vector<unsigned int> &arraySizes, |
247 | bool isRowMajorMatrix, |
248 | int *arrayStrideOut, |
249 | int *matrixStrideOut) |
250 | { |
251 | // We assume we are only dealing with 4 byte components (no doubles or half-words currently) |
252 | ASSERT(gl::VariableComponentSize(gl::VariableComponentType(type)) == kBytesPerComponent); |
253 | |
254 | size_t baseAlignment = 0; |
255 | int matrixStride = 0; |
256 | int arrayStride = 0; |
257 | |
258 | if (gl::IsMatrixType(type)) |
259 | { |
260 | baseAlignment = getTypeBaseAlignment(type, isRowMajorMatrix); |
261 | matrixStride = static_cast<int>(getTypeBaseAlignment(type, isRowMajorMatrix)); |
262 | |
263 | if (!arraySizes.empty()) |
264 | { |
265 | const int numRegisters = gl::MatrixRegisterCount(type, isRowMajorMatrix); |
266 | arrayStride = static_cast<int>(getTypeBaseAlignment(type, isRowMajorMatrix) * numRegisters); |
267 | } |
268 | } |
269 | else if (!arraySizes.empty()) |
270 | { |
271 | baseAlignment = static_cast<int>(getTypeBaseAlignment(type, false)); |
272 | arrayStride = static_cast<int>(getTypeBaseAlignment(type, false)); |
273 | } |
274 | else |
275 | { |
276 | const size_t numComponents = static_cast<size_t>(gl::VariableComponentCount(type)); |
277 | baseAlignment = ComponentAlignment(numComponents); |
278 | } |
279 | |
280 | mCurrentOffset = rx::roundUp(mCurrentOffset, baseAlignment); |
281 | |
282 | *matrixStrideOut = matrixStride; |
283 | *arrayStrideOut = arrayStride; |
284 | } |
285 | |
286 | void Std140BlockEncoder::advanceOffset(GLenum type, |
287 | const std::vector<unsigned int> &arraySizes, |
288 | bool isRowMajorMatrix, |
289 | int arrayStride, |
290 | int matrixStride) |
291 | { |
292 | if (!arraySizes.empty()) |
293 | { |
294 | mCurrentOffset += arrayStride * gl::ArraySizeProduct(arraySizes); |
295 | } |
296 | else if (gl::IsMatrixType(type)) |
297 | { |
298 | const int numRegisters = gl::MatrixRegisterCount(type, isRowMajorMatrix); |
299 | mCurrentOffset += matrixStride * numRegisters; |
300 | } |
301 | else |
302 | { |
303 | mCurrentOffset += gl::VariableComponentCount(type); |
304 | } |
305 | } |
306 | |
307 | size_t Std140BlockEncoder::getBaseAlignment(const ShaderVariable &variable) const |
308 | { |
309 | return kComponentsPerRegister; |
310 | } |
311 | |
312 | size_t Std140BlockEncoder::getTypeBaseAlignment(GLenum type, bool isRowMajorMatrix) const |
313 | { |
314 | return kComponentsPerRegister; |
315 | } |
316 | |
317 | // Std430BlockEncoder implementation. |
318 | Std430BlockEncoder::Std430BlockEncoder() {} |
319 | |
320 | size_t Std430BlockEncoder::getBaseAlignment(const ShaderVariable &shaderVar) const |
321 | { |
322 | if (shaderVar.isStruct()) |
323 | { |
324 | BaseAlignmentVisitor visitor; |
325 | TraverseShaderVariables(shaderVar.fields, false, &visitor); |
326 | return visitor.getBaseAlignment(); |
327 | } |
328 | |
329 | return GetStd430BaseAlignment(shaderVar.type, shaderVar.isRowMajorLayout); |
330 | } |
331 | |
332 | size_t Std430BlockEncoder::getTypeBaseAlignment(GLenum type, bool isRowMajorMatrix) const |
333 | { |
334 | return GetStd430BaseAlignment(type, isRowMajorMatrix); |
335 | } |
336 | |
337 | void GetInterfaceBlockInfo(const std::vector<InterfaceBlockField> &fields, |
338 | const std::string &prefix, |
339 | BlockLayoutEncoder *encoder, |
340 | BlockLayoutMap *blockInfoOut) |
341 | { |
342 | // Matrix packing is always recorded in individual fields, so they'll set the row major layout |
343 | // flag to true if needed. |
344 | GetInterfaceBlockInfo(fields, prefix, encoder, false, blockInfoOut); |
345 | } |
346 | |
347 | void GetUniformBlockInfo(const std::vector<Uniform> &uniforms, |
348 | const std::string &prefix, |
349 | BlockLayoutEncoder *encoder, |
350 | BlockLayoutMap *blockInfoOut) |
351 | { |
352 | // Matrix packing is always recorded in individual fields, so they'll set the row major layout |
353 | // flag to true if needed. |
354 | GetInterfaceBlockInfo(uniforms, prefix, encoder, false, blockInfoOut); |
355 | } |
356 | |
357 | // VariableNameVisitor implementation. |
358 | VariableNameVisitor::VariableNameVisitor(const std::string &namePrefix, |
359 | const std::string &mappedNamePrefix) |
360 | { |
361 | if (!namePrefix.empty()) |
362 | { |
363 | mNameStack.push_back(namePrefix + "." ); |
364 | } |
365 | |
366 | if (!mappedNamePrefix.empty()) |
367 | { |
368 | mMappedNameStack.push_back(mappedNamePrefix + "." ); |
369 | } |
370 | } |
371 | |
372 | VariableNameVisitor::~VariableNameVisitor() = default; |
373 | |
374 | void VariableNameVisitor::enterStruct(const ShaderVariable &structVar) |
375 | { |
376 | mNameStack.push_back(structVar.name); |
377 | mMappedNameStack.push_back(structVar.mappedName); |
378 | } |
379 | |
380 | void VariableNameVisitor::exitStruct(const ShaderVariable &structVar) |
381 | { |
382 | mNameStack.pop_back(); |
383 | mMappedNameStack.pop_back(); |
384 | } |
385 | |
386 | void VariableNameVisitor::enterStructAccess(const ShaderVariable &structVar, bool isRowMajor) |
387 | { |
388 | mNameStack.push_back("." ); |
389 | mMappedNameStack.push_back("." ); |
390 | } |
391 | |
392 | void VariableNameVisitor::exitStructAccess(const ShaderVariable &structVar, bool isRowMajor) |
393 | { |
394 | mNameStack.pop_back(); |
395 | mMappedNameStack.pop_back(); |
396 | } |
397 | |
398 | void VariableNameVisitor::enterArray(const ShaderVariable &arrayVar) |
399 | { |
400 | if (!arrayVar.hasParentArrayIndex() && !arrayVar.isStruct()) |
401 | { |
402 | mNameStack.push_back(arrayVar.name); |
403 | mMappedNameStack.push_back(arrayVar.mappedName); |
404 | } |
405 | } |
406 | |
407 | void VariableNameVisitor::exitArray(const ShaderVariable &arrayVar) |
408 | { |
409 | if (!arrayVar.hasParentArrayIndex() && !arrayVar.isStruct()) |
410 | { |
411 | mNameStack.pop_back(); |
412 | mMappedNameStack.pop_back(); |
413 | } |
414 | } |
415 | |
416 | void VariableNameVisitor::enterArrayElement(const ShaderVariable &arrayVar, |
417 | unsigned int arrayElement) |
418 | { |
419 | std::stringstream strstr = sh::InitializeStream<std::stringstream>(); |
420 | strstr << "[" << arrayElement << "]" ; |
421 | std::string elementString = strstr.str(); |
422 | mNameStack.push_back(elementString); |
423 | mMappedNameStack.push_back(elementString); |
424 | } |
425 | |
426 | void VariableNameVisitor::exitArrayElement(const ShaderVariable &arrayVar, |
427 | unsigned int arrayElement) |
428 | { |
429 | mNameStack.pop_back(); |
430 | mMappedNameStack.pop_back(); |
431 | } |
432 | |
433 | std::string VariableNameVisitor::collapseNameStack() const |
434 | { |
435 | return CollapseNameStack(mNameStack); |
436 | } |
437 | |
438 | std::string VariableNameVisitor::collapseMappedNameStack() const |
439 | { |
440 | return CollapseNameStack(mMappedNameStack); |
441 | } |
442 | |
443 | void VariableNameVisitor::visitSampler(const sh::ShaderVariable &sampler) |
444 | { |
445 | if (!sampler.hasParentArrayIndex()) |
446 | { |
447 | mNameStack.push_back(sampler.name); |
448 | mMappedNameStack.push_back(sampler.mappedName); |
449 | } |
450 | |
451 | std::string name = collapseNameStack(); |
452 | std::string mappedName = collapseMappedNameStack(); |
453 | |
454 | if (!sampler.hasParentArrayIndex()) |
455 | { |
456 | mNameStack.pop_back(); |
457 | mMappedNameStack.pop_back(); |
458 | } |
459 | |
460 | visitNamedSampler(sampler, name, mappedName); |
461 | } |
462 | |
463 | void VariableNameVisitor::visitVariable(const ShaderVariable &variable, bool isRowMajor) |
464 | { |
465 | if (!variable.hasParentArrayIndex()) |
466 | { |
467 | mNameStack.push_back(variable.name); |
468 | mMappedNameStack.push_back(variable.mappedName); |
469 | } |
470 | |
471 | std::string name = collapseNameStack(); |
472 | std::string mappedName = collapseMappedNameStack(); |
473 | |
474 | if (!variable.hasParentArrayIndex()) |
475 | { |
476 | mNameStack.pop_back(); |
477 | mMappedNameStack.pop_back(); |
478 | } |
479 | |
480 | visitNamedVariable(variable, isRowMajor, name, mappedName); |
481 | } |
482 | |
483 | // BlockEncoderVisitor implementation. |
484 | BlockEncoderVisitor::BlockEncoderVisitor(const std::string &namePrefix, |
485 | const std::string &mappedNamePrefix, |
486 | BlockLayoutEncoder *encoder) |
487 | : VariableNameVisitor(namePrefix, mappedNamePrefix), mEncoder(encoder) |
488 | {} |
489 | |
490 | BlockEncoderVisitor::~BlockEncoderVisitor() = default; |
491 | |
492 | void BlockEncoderVisitor::enterStructAccess(const ShaderVariable &structVar, bool isRowMajor) |
493 | { |
494 | mStructStackSize++; |
495 | if (!mIsTopLevelArrayStrideReady) |
496 | { |
497 | size_t structSize = mEncoder->getShaderVariableSize(structVar, isRowMajor); |
498 | mTopLevelArrayStride *= structSize; |
499 | mIsTopLevelArrayStrideReady = true; |
500 | } |
501 | |
502 | VariableNameVisitor::enterStructAccess(structVar, isRowMajor); |
503 | mEncoder->enterAggregateType(structVar); |
504 | } |
505 | |
506 | void BlockEncoderVisitor::exitStructAccess(const ShaderVariable &structVar, bool isRowMajor) |
507 | { |
508 | mStructStackSize--; |
509 | mEncoder->exitAggregateType(structVar); |
510 | VariableNameVisitor::exitStructAccess(structVar, isRowMajor); |
511 | } |
512 | |
513 | void BlockEncoderVisitor::enterArrayElement(const sh::ShaderVariable &arrayVar, |
514 | unsigned int arrayElement) |
515 | { |
516 | if (mStructStackSize == 0 && !arrayVar.hasParentArrayIndex()) |
517 | { |
518 | // From the ES 3.1 spec "7.3.1.1 Naming Active Resources": |
519 | // For an active shader storage block member declared as an array of an aggregate type, |
520 | // an entry will be generated only for the first array element, regardless of its type. |
521 | // Such block members are referred to as top-level arrays. If the block member is an |
522 | // aggregate type, the enumeration rules are then applied recursively. |
523 | if (arrayElement == 0) |
524 | { |
525 | mTopLevelArraySize = arrayVar.getOutermostArraySize(); |
526 | mTopLevelArrayStride = arrayVar.getInnerArraySizeProduct(); |
527 | mIsTopLevelArrayStrideReady = false; |
528 | } |
529 | else |
530 | { |
531 | mSkipEnabled = true; |
532 | } |
533 | } |
534 | VariableNameVisitor::enterArrayElement(arrayVar, arrayElement); |
535 | } |
536 | |
537 | void BlockEncoderVisitor::exitArrayElement(const sh::ShaderVariable &arrayVar, |
538 | unsigned int arrayElement) |
539 | { |
540 | if (mStructStackSize == 0 && !arrayVar.hasParentArrayIndex()) |
541 | { |
542 | mTopLevelArraySize = 1; |
543 | mTopLevelArrayStride = 0; |
544 | mIsTopLevelArrayStrideReady = true; |
545 | mSkipEnabled = false; |
546 | } |
547 | VariableNameVisitor::exitArrayElement(arrayVar, arrayElement); |
548 | } |
549 | |
550 | void BlockEncoderVisitor::visitNamedVariable(const ShaderVariable &variable, |
551 | bool isRowMajor, |
552 | const std::string &name, |
553 | const std::string &mappedName) |
554 | { |
555 | std::vector<unsigned int> innermostArraySize; |
556 | |
557 | if (variable.isArray()) |
558 | { |
559 | innermostArraySize.push_back(variable.getNestedArraySize(0)); |
560 | } |
561 | BlockMemberInfo variableInfo = |
562 | mEncoder->encodeType(variable.type, innermostArraySize, isRowMajor); |
563 | if (!mIsTopLevelArrayStrideReady) |
564 | { |
565 | ASSERT(mTopLevelArrayStride); |
566 | mTopLevelArrayStride *= variableInfo.arrayStride; |
567 | mIsTopLevelArrayStrideReady = true; |
568 | } |
569 | variableInfo.topLevelArrayStride = mTopLevelArrayStride; |
570 | encodeVariable(variable, variableInfo, name, mappedName); |
571 | } |
572 | |
573 | void TraverseShaderVariable(const ShaderVariable &variable, |
574 | bool isRowMajorLayout, |
575 | ShaderVariableVisitor *visitor) |
576 | { |
577 | bool rowMajorLayout = (isRowMajorLayout || variable.isRowMajorLayout); |
578 | bool isRowMajor = rowMajorLayout && gl::IsMatrixType(variable.type); |
579 | |
580 | if (variable.isStruct()) |
581 | { |
582 | visitor->enterStruct(variable); |
583 | if (variable.isArray()) |
584 | { |
585 | TraverseStructArrayVariable(variable, rowMajorLayout, visitor); |
586 | } |
587 | else |
588 | { |
589 | TraverseStructVariable(variable, rowMajorLayout, visitor); |
590 | } |
591 | visitor->exitStruct(variable); |
592 | } |
593 | else if (variable.isArrayOfArrays()) |
594 | { |
595 | TraverseArrayOfArraysVariable(variable, 0u, isRowMajor, visitor); |
596 | } |
597 | else if (gl::IsSamplerType(variable.type)) |
598 | { |
599 | visitor->visitSampler(variable); |
600 | } |
601 | else |
602 | { |
603 | visitor->visitVariable(variable, isRowMajor); |
604 | } |
605 | } |
606 | } // namespace sh |
607 | |