// // Copyright (c) 2002-2013 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // CollectVariables.cpp: Collect lists of shader interface variables based on the AST. #include "compiler/translator/CollectVariables.h" #include "angle_gl.h" #include "common/utilities.h" #include "compiler/translator/HashNames.h" #include "compiler/translator/IntermTraverse.h" #include "compiler/translator/SymbolTable.h" #include "compiler/translator/util.h" namespace sh { namespace { BlockLayoutType GetBlockLayoutType(TLayoutBlockStorage blockStorage) { switch (blockStorage) { case EbsPacked: return BLOCKLAYOUT_PACKED; case EbsShared: return BLOCKLAYOUT_SHARED; case EbsStd140: return BLOCKLAYOUT_STD140; case EbsStd430: return BLOCKLAYOUT_STD430; default: UNREACHABLE(); return BLOCKLAYOUT_SHARED; } } // TODO(jiawei.shao@intel.com): implement GL_OES_shader_io_blocks. BlockType GetBlockType(TQualifier qualifier) { switch (qualifier) { case EvqUniform: return BlockType::BLOCK_UNIFORM; case EvqBuffer: return BlockType::BLOCK_BUFFER; case EvqPerVertexIn: return BlockType::BLOCK_IN; default: UNREACHABLE(); return BlockType::BLOCK_UNIFORM; } } template VarT *FindVariable(const TString &name, std::vector *infoList) { // TODO(zmo): optimize this function. for (size_t ii = 0; ii < infoList->size(); ++ii) { if ((*infoList)[ii].name.c_str() == name) return &((*infoList)[ii]); } return nullptr; } // Note that this shouldn't be called for interface blocks - static use information is collected for // individual fields in case of interface blocks. void MarkStaticallyUsed(ShaderVariable *variable) { if (!variable->staticUse) { if (variable->isStruct()) { // Conservatively assume all fields are statically used as well. for (auto &field : variable->fields) { MarkStaticallyUsed(&field); } } variable->staticUse = true; } } ShaderVariable *FindVariableInInterfaceBlock(const TString &name, const TInterfaceBlock *interfaceBlock, std::vector *infoList) { ASSERT(interfaceBlock); InterfaceBlock *namedBlock = FindVariable(interfaceBlock->name(), infoList); ASSERT(namedBlock); // Set static use on the parent interface block here namedBlock->staticUse = true; return FindVariable(name, &namedBlock->fields); } // Traverses the intermediate tree to collect all attributes, uniforms, varyings, fragment outputs, // and interface blocks. class CollectVariablesTraverser : public TIntermTraverser { public: CollectVariablesTraverser(std::vector *attribs, std::vector *outputVariables, std::vector *uniforms, std::vector *inputVaryings, std::vector *outputVaryings, std::vector *uniformBlocks, std::vector *shaderStorageBlocks, std::vector *inBlocks, ShHashFunction64 hashFunction, TSymbolTable *symbolTable, int shaderVersion, GLenum shaderType, const TExtensionBehavior &extensionBehavior); void visitSymbol(TIntermSymbol *symbol) override; bool visitDeclaration(Visit, TIntermDeclaration *node) override; bool visitBinary(Visit visit, TIntermBinary *binaryNode) override; private: std::string getMappedName(const TName &name) const; void setCommonVariableProperties(const TType &type, const TName &name, ShaderVariable *variableOut) const; Attribute recordAttribute(const TIntermSymbol &variable) const; OutputVariable recordOutputVariable(const TIntermSymbol &variable) const; Varying recordVarying(const TIntermSymbol &variable) const; void recordInterfaceBlock(const TType &interfaceBlockType, InterfaceBlock *interfaceBlock) const; Uniform recordUniform(const TIntermSymbol &variable) const; void setBuiltInInfoFromSymbolTable(const char *name, ShaderVariable *info); void recordBuiltInVaryingUsed(const char *name, bool *addedFlag, std::vector *varyings); void recordBuiltInFragmentOutputUsed(const char *name, bool *addedFlag); void recordBuiltInAttributeUsed(const char *name, bool *addedFlag); InterfaceBlock *recordGLInUsed(const TType &glInType); InterfaceBlock *findNamedInterfaceBlock(const TString &name) const; std::vector *mAttribs; std::vector *mOutputVariables; std::vector *mUniforms; std::vector *mInputVaryings; std::vector *mOutputVaryings; std::vector *mUniformBlocks; std::vector *mShaderStorageBlocks; std::vector *mInBlocks; std::map mInterfaceBlockFields; // Shader uniforms bool mDepthRangeAdded; // Vertex Shader builtins bool mInstanceIDAdded; bool mVertexIDAdded; bool mPointSizeAdded; // Vertex Shader and Geometry Shader builtins bool mPositionAdded; // Fragment Shader builtins bool mPointCoordAdded; bool mFrontFacingAdded; bool mFragCoordAdded; bool mLastFragDataAdded; bool mFragColorAdded; bool mFragDataAdded; bool mFragDepthEXTAdded; bool mFragDepthAdded; bool mSecondaryFragColorEXTAdded; bool mSecondaryFragDataEXTAdded; // Geometry Shader builtins bool mPerVertexInAdded; bool mPrimitiveIDInAdded; bool mInvocationIDAdded; // Geometry Shader and Fragment Shader builtins bool mPrimitiveIDAdded; bool mLayerAdded; ShHashFunction64 mHashFunction; int mShaderVersion; GLenum mShaderType; const TExtensionBehavior &mExtensionBehavior; }; CollectVariablesTraverser::CollectVariablesTraverser( std::vector *attribs, std::vector *outputVariables, std::vector *uniforms, std::vector *inputVaryings, std::vector *outputVaryings, std::vector *uniformBlocks, std::vector *shaderStorageBlocks, std::vector *inBlocks, ShHashFunction64 hashFunction, TSymbolTable *symbolTable, int shaderVersion, GLenum shaderType, const TExtensionBehavior &extensionBehavior) : TIntermTraverser(true, false, false, symbolTable), mAttribs(attribs), mOutputVariables(outputVariables), mUniforms(uniforms), mInputVaryings(inputVaryings), mOutputVaryings(outputVaryings), mUniformBlocks(uniformBlocks), mShaderStorageBlocks(shaderStorageBlocks), mInBlocks(inBlocks), mDepthRangeAdded(false), mInstanceIDAdded(false), mVertexIDAdded(false), mPointSizeAdded(false), mPositionAdded(false), mPointCoordAdded(false), mFrontFacingAdded(false), mFragCoordAdded(false), mLastFragDataAdded(false), mFragColorAdded(false), mFragDataAdded(false), mFragDepthEXTAdded(false), mFragDepthAdded(false), mSecondaryFragColorEXTAdded(false), mSecondaryFragDataEXTAdded(false), mPerVertexInAdded(false), mPrimitiveIDInAdded(false), mInvocationIDAdded(false), mPrimitiveIDAdded(false), mLayerAdded(false), mHashFunction(hashFunction), mShaderVersion(shaderVersion), mShaderType(shaderType), mExtensionBehavior(extensionBehavior) { } std::string CollectVariablesTraverser::getMappedName(const TName &name) const { return HashName(name, mHashFunction, nullptr).c_str(); } void CollectVariablesTraverser::setBuiltInInfoFromSymbolTable(const char *name, ShaderVariable *info) { TVariable *symbolTableVar = reinterpret_cast(mSymbolTable->findBuiltIn(name, mShaderVersion)); ASSERT(symbolTableVar); const TType &type = symbolTableVar->getType(); info->name = name; info->mappedName = name; info->type = GLVariableType(type); info->precision = GLVariablePrecision(type); if (auto *arraySizes = type.getArraySizes()) { info->arraySizes.assign(arraySizes->begin(), arraySizes->end()); } } void CollectVariablesTraverser::recordBuiltInVaryingUsed(const char *name, bool *addedFlag, std::vector *varyings) { ASSERT(varyings); if (!(*addedFlag)) { Varying info; setBuiltInInfoFromSymbolTable(name, &info); info.staticUse = true; info.isInvariant = mSymbolTable->isVaryingInvariant(name); varyings->push_back(info); (*addedFlag) = true; } } void CollectVariablesTraverser::recordBuiltInFragmentOutputUsed(const char *name, bool *addedFlag) { if (!(*addedFlag)) { OutputVariable info; setBuiltInInfoFromSymbolTable(name, &info); info.staticUse = true; mOutputVariables->push_back(info); (*addedFlag) = true; } } void CollectVariablesTraverser::recordBuiltInAttributeUsed(const char *name, bool *addedFlag) { if (!(*addedFlag)) { Attribute info; setBuiltInInfoFromSymbolTable(name, &info); info.staticUse = true; info.location = -1; mAttribs->push_back(info); (*addedFlag) = true; } } InterfaceBlock *CollectVariablesTraverser::recordGLInUsed(const TType &glInType) { if (!mPerVertexInAdded) { ASSERT(glInType.getQualifier() == EvqPerVertexIn); InterfaceBlock info; recordInterfaceBlock(glInType, &info); info.staticUse = true; mPerVertexInAdded = true; mInBlocks->push_back(info); return &mInBlocks->back(); } else { return FindVariable("gl_PerVertex", mInBlocks); } } // We want to check whether a uniform/varying is statically used // because we only count the used ones in packing computing. // Also, gl_FragCoord, gl_PointCoord, and gl_FrontFacing count // toward varying counting if they are statically used in a fragment // shader. void CollectVariablesTraverser::visitSymbol(TIntermSymbol *symbol) { ASSERT(symbol != nullptr); if (symbol->getName().isInternal()) { // Internal variables are not collected. return; } ShaderVariable *var = nullptr; const TString &symbolName = symbol->getName().getString(); if (IsVaryingIn(symbol->getQualifier())) { var = FindVariable(symbolName, mInputVaryings); } else if (IsVaryingOut(symbol->getQualifier())) { var = FindVariable(symbolName, mOutputVaryings); } else if (symbol->getType().getBasicType() == EbtInterfaceBlock) { UNREACHABLE(); } else if (symbolName == "gl_DepthRange") { ASSERT(symbol->getQualifier() == EvqUniform); if (!mDepthRangeAdded) { Uniform info; const char kName[] = "gl_DepthRange"; info.name = kName; info.mappedName = kName; info.type = GL_NONE; info.precision = GL_NONE; info.staticUse = true; ShaderVariable nearInfo(GL_FLOAT); const char kNearName[] = "near"; nearInfo.name = kNearName; nearInfo.mappedName = kNearName; nearInfo.precision = GL_HIGH_FLOAT; nearInfo.staticUse = true; ShaderVariable farInfo(GL_FLOAT); const char kFarName[] = "far"; farInfo.name = kFarName; farInfo.mappedName = kFarName; farInfo.precision = GL_HIGH_FLOAT; farInfo.staticUse = true; ShaderVariable diffInfo(GL_FLOAT); const char kDiffName[] = "diff"; diffInfo.name = kDiffName; diffInfo.mappedName = kDiffName; diffInfo.precision = GL_HIGH_FLOAT; diffInfo.staticUse = true; info.fields.push_back(nearInfo); info.fields.push_back(farInfo); info.fields.push_back(diffInfo); mUniforms->push_back(info); mDepthRangeAdded = true; } } else { switch (symbol->getQualifier()) { case EvqAttribute: case EvqVertexIn: var = FindVariable(symbolName, mAttribs); break; case EvqFragmentOut: var = FindVariable(symbolName, mOutputVariables); break; case EvqUniform: { const TInterfaceBlock *interfaceBlock = symbol->getType().getInterfaceBlock(); if (interfaceBlock) { var = FindVariableInInterfaceBlock(symbolName, interfaceBlock, mUniformBlocks); } else { var = FindVariable(symbolName, mUniforms); } // It's an internal error to reference an undefined user uniform ASSERT(symbolName.compare(0, 3, "gl_") != 0 || var); } break; case EvqBuffer: { const TInterfaceBlock *interfaceBlock = symbol->getType().getInterfaceBlock(); var = FindVariableInInterfaceBlock(symbolName, interfaceBlock, mShaderStorageBlocks); } break; case EvqFragCoord: recordBuiltInVaryingUsed("gl_FragCoord", &mFragCoordAdded, mInputVaryings); return; case EvqFrontFacing: recordBuiltInVaryingUsed("gl_FrontFacing", &mFrontFacingAdded, mInputVaryings); return; case EvqPointCoord: recordBuiltInVaryingUsed("gl_PointCoord", &mPointCoordAdded, mInputVaryings); return; case EvqInstanceID: // Whenever the SH_INITIALIZE_BUILTINS_FOR_INSTANCED_MULTIVIEW option is set, // gl_InstanceID is added inside expressions to initialize ViewID_OVR and // InstanceID. gl_InstanceID is not added to the symbol table for ESSL1 shaders // which makes it necessary to populate the type information explicitly instead of // extracting it from the symbol table. if (!mInstanceIDAdded) { Attribute info; const char kName[] = "gl_InstanceID"; info.name = kName; info.mappedName = kName; info.type = GL_INT; info.precision = GL_HIGH_INT; // Defined by spec. info.staticUse = true; info.location = -1; mAttribs->push_back(info); mInstanceIDAdded = true; } return; case EvqVertexID: recordBuiltInAttributeUsed("gl_VertexID", &mVertexIDAdded); return; case EvqPosition: recordBuiltInVaryingUsed("gl_Position", &mPositionAdded, mOutputVaryings); return; case EvqPointSize: recordBuiltInVaryingUsed("gl_PointSize", &mPointSizeAdded, mOutputVaryings); return; case EvqLastFragData: recordBuiltInVaryingUsed("gl_LastFragData", &mLastFragDataAdded, mInputVaryings); return; case EvqFragColor: recordBuiltInFragmentOutputUsed("gl_FragColor", &mFragColorAdded); return; case EvqFragData: if (!mFragDataAdded) { OutputVariable info; setBuiltInInfoFromSymbolTable("gl_FragData", &info); if (!IsExtensionEnabled(mExtensionBehavior, TExtension::EXT_draw_buffers)) { ASSERT(info.arraySizes.size() == 1u); info.arraySizes.back() = 1u; } info.staticUse = true; mOutputVariables->push_back(info); mFragDataAdded = true; } return; case EvqFragDepthEXT: recordBuiltInFragmentOutputUsed("gl_FragDepthEXT", &mFragDepthEXTAdded); return; case EvqFragDepth: recordBuiltInFragmentOutputUsed("gl_FragDepth", &mFragDepthAdded); return; case EvqSecondaryFragColorEXT: recordBuiltInFragmentOutputUsed("gl_SecondaryFragColorEXT", &mSecondaryFragColorEXTAdded); return; case EvqSecondaryFragDataEXT: recordBuiltInFragmentOutputUsed("gl_SecondaryFragDataEXT", &mSecondaryFragDataEXTAdded); return; case EvqInvocationID: recordBuiltInVaryingUsed("gl_InvocationID", &mInvocationIDAdded, mInputVaryings); break; case EvqPrimitiveIDIn: recordBuiltInVaryingUsed("gl_PrimitiveIDIn", &mPrimitiveIDInAdded, mInputVaryings); break; case EvqPrimitiveID: if (mShaderType == GL_GEOMETRY_SHADER_OES) { recordBuiltInVaryingUsed("gl_PrimitiveID", &mPrimitiveIDAdded, mOutputVaryings); } else { ASSERT(mShaderType == GL_FRAGMENT_SHADER); recordBuiltInVaryingUsed("gl_PrimitiveID", &mPrimitiveIDAdded, mInputVaryings); } break; case EvqLayer: if (mShaderType == GL_GEOMETRY_SHADER_OES) { recordBuiltInVaryingUsed("gl_Layer", &mLayerAdded, mOutputVaryings); } else if (mShaderType == GL_FRAGMENT_SHADER) { recordBuiltInVaryingUsed("gl_Layer", &mLayerAdded, mInputVaryings); } else { ASSERT(mShaderType == GL_VERTEX_SHADER && IsExtensionEnabled(mExtensionBehavior, TExtension::OVR_multiview)); } break; default: break; } } if (var) { MarkStaticallyUsed(var); } } void CollectVariablesTraverser::setCommonVariableProperties(const TType &type, const TName &name, ShaderVariable *variableOut) const { ASSERT(variableOut); const TStructure *structure = type.getStruct(); if (!structure) { variableOut->type = GLVariableType(type); variableOut->precision = GLVariablePrecision(type); } else { // Structures use a NONE type that isn't exposed outside ANGLE. variableOut->type = GL_NONE; variableOut->structName = structure->name().c_str(); const TFieldList &fields = structure->fields(); for (TField *field : fields) { // Regardless of the variable type (uniform, in/out etc.) its fields are always plain // ShaderVariable objects. ShaderVariable fieldVariable; setCommonVariableProperties(*field->type(), TName(field->name()), &fieldVariable); variableOut->fields.push_back(fieldVariable); } } variableOut->name = name.getString().c_str(); variableOut->mappedName = getMappedName(name); if (auto *arraySizes = type.getArraySizes()) { variableOut->arraySizes.assign(arraySizes->begin(), arraySizes->end()); } } Attribute CollectVariablesTraverser::recordAttribute(const TIntermSymbol &variable) const { const TType &type = variable.getType(); ASSERT(!type.getStruct()); Attribute attribute; setCommonVariableProperties(type, variable.getName(), &attribute); attribute.location = type.getLayoutQualifier().location; return attribute; } OutputVariable CollectVariablesTraverser::recordOutputVariable(const TIntermSymbol &variable) const { const TType &type = variable.getType(); ASSERT(!type.getStruct()); OutputVariable outputVariable; setCommonVariableProperties(type, variable.getName(), &outputVariable); outputVariable.location = type.getLayoutQualifier().location; return outputVariable; } Varying CollectVariablesTraverser::recordVarying(const TIntermSymbol &variable) const { const TType &type = variable.getType(); Varying varying; setCommonVariableProperties(type, variable.getName(), &varying); varying.location = type.getLayoutQualifier().location; switch (type.getQualifier()) { case EvqVaryingIn: case EvqVaryingOut: case EvqVertexOut: case EvqSmoothOut: case EvqFlatOut: case EvqCentroidOut: case EvqGeometryOut: if (mSymbolTable->isVaryingInvariant(std::string(variable.getSymbol().c_str())) || type.isInvariant()) { varying.isInvariant = true; } break; default: break; } varying.interpolation = GetInterpolationType(type.getQualifier()); return varying; } // TODO(jiawei.shao@intel.com): implement GL_OES_shader_io_blocks. void CollectVariablesTraverser::recordInterfaceBlock(const TType &interfaceBlockType, InterfaceBlock *interfaceBlock) const { ASSERT(interfaceBlockType.getBasicType() == EbtInterfaceBlock); ASSERT(interfaceBlock); const TInterfaceBlock *blockType = interfaceBlockType.getInterfaceBlock(); ASSERT(blockType); interfaceBlock->name = blockType->name().c_str(); interfaceBlock->mappedName = getMappedName(TName(blockType->name())); interfaceBlock->instanceName = (blockType->hasInstanceName() ? blockType->instanceName().c_str() : ""); ASSERT(!interfaceBlockType.isArrayOfArrays()); // Disallowed by GLSL ES 3.10 section 4.3.9 interfaceBlock->arraySize = interfaceBlockType.isArray() ? interfaceBlockType.getOutermostArraySize() : 0; interfaceBlock->blockType = GetBlockType(interfaceBlockType.getQualifier()); if (interfaceBlock->blockType == BlockType::BLOCK_UNIFORM || interfaceBlock->blockType == BlockType::BLOCK_BUFFER) { interfaceBlock->isRowMajorLayout = (blockType->matrixPacking() == EmpRowMajor); interfaceBlock->binding = blockType->blockBinding(); interfaceBlock->layout = GetBlockLayoutType(blockType->blockStorage()); } // Gather field information for (const TField *field : blockType->fields()) { const TType &fieldType = *field->type(); InterfaceBlockField fieldVariable; setCommonVariableProperties(fieldType, TName(field->name()), &fieldVariable); fieldVariable.isRowMajorLayout = (fieldType.getLayoutQualifier().matrixPacking == EmpRowMajor); interfaceBlock->fields.push_back(fieldVariable); } } Uniform CollectVariablesTraverser::recordUniform(const TIntermSymbol &variable) const { Uniform uniform; setCommonVariableProperties(variable.getType(), variable.getName(), &uniform); uniform.binding = variable.getType().getLayoutQualifier().binding; uniform.location = variable.getType().getLayoutQualifier().location; uniform.offset = variable.getType().getLayoutQualifier().offset; return uniform; } bool CollectVariablesTraverser::visitDeclaration(Visit, TIntermDeclaration *node) { const TIntermSequence &sequence = *(node->getSequence()); ASSERT(!sequence.empty()); const TIntermTyped &typedNode = *(sequence.front()->getAsTyped()); TQualifier qualifier = typedNode.getQualifier(); bool isShaderVariable = qualifier == EvqAttribute || qualifier == EvqVertexIn || qualifier == EvqFragmentOut || qualifier == EvqUniform || IsVarying(qualifier); if (typedNode.getBasicType() != EbtInterfaceBlock && !isShaderVariable) { return true; } for (TIntermNode *variableNode : sequence) { // The only case in which the sequence will not contain a TIntermSymbol node is // initialization. It will contain a TInterBinary node in that case. Since attributes, // uniforms, varyings, outputs and interface blocks cannot be initialized in a shader, we // must have only TIntermSymbol nodes in the sequence in the cases we are interested in. const TIntermSymbol &variable = *variableNode->getAsSymbolNode(); if (variable.getName().isInternal()) { // Internal variables are not collected. continue; } // TODO(jiawei.shao@intel.com): implement GL_OES_shader_io_blocks. if (typedNode.getBasicType() == EbtInterfaceBlock) { InterfaceBlock interfaceBlock; recordInterfaceBlock(variable.getType(), &interfaceBlock); switch (qualifier) { case EvqUniform: mUniformBlocks->push_back(interfaceBlock); break; case EvqBuffer: mShaderStorageBlocks->push_back(interfaceBlock); break; default: UNREACHABLE(); } } else { switch (qualifier) { case EvqAttribute: case EvqVertexIn: mAttribs->push_back(recordAttribute(variable)); break; case EvqFragmentOut: mOutputVariables->push_back(recordOutputVariable(variable)); break; case EvqUniform: mUniforms->push_back(recordUniform(variable)); break; default: if (IsVaryingIn(qualifier)) { mInputVaryings->push_back(recordVarying(variable)); } else { ASSERT(IsVaryingOut(qualifier)); mOutputVaryings->push_back(recordVarying(variable)); } break; } } } // None of the recorded variables can have initializers, so we don't need to traverse the // declarators. return false; } // TODO(jiawei.shao@intel.com): add search on mInBlocks and mOutBlocks when implementing // GL_OES_shader_io_blocks. InterfaceBlock *CollectVariablesTraverser::findNamedInterfaceBlock(const TString &blockName) const { InterfaceBlock *namedBlock = FindVariable(blockName, mUniformBlocks); if (!namedBlock) { namedBlock = FindVariable(blockName, mShaderStorageBlocks); } return namedBlock; } bool CollectVariablesTraverser::visitBinary(Visit, TIntermBinary *binaryNode) { if (binaryNode->getOp() == EOpIndexDirectInterfaceBlock) { // NOTE: we do not determine static use for individual blocks of an array TIntermTyped *blockNode = binaryNode->getLeft()->getAsTyped(); ASSERT(blockNode); TIntermConstantUnion *constantUnion = binaryNode->getRight()->getAsConstantUnion(); ASSERT(constantUnion); InterfaceBlock *namedBlock = nullptr; bool traverseIndexExpression = false; TIntermBinary *interfaceIndexingNode = blockNode->getAsBinaryNode(); if (interfaceIndexingNode) { TIntermTyped *interfaceNode = interfaceIndexingNode->getLeft()->getAsTyped(); ASSERT(interfaceNode); const TType &interfaceType = interfaceNode->getType(); if (interfaceType.getQualifier() == EvqPerVertexIn) { namedBlock = recordGLInUsed(interfaceType); ASSERT(namedBlock); // We need to continue traversing to collect useful variables in the index // expression of gl_in. traverseIndexExpression = true; } } const TInterfaceBlock *interfaceBlock = blockNode->getType().getInterfaceBlock(); if (!namedBlock) { namedBlock = findNamedInterfaceBlock(interfaceBlock->name()); } ASSERT(namedBlock); namedBlock->staticUse = true; unsigned int fieldIndex = static_cast(constantUnion->getIConst(0)); ASSERT(fieldIndex < namedBlock->fields.size()); namedBlock->fields[fieldIndex].staticUse = true; if (traverseIndexExpression) { ASSERT(interfaceIndexingNode); interfaceIndexingNode->getRight()->traverse(this); } return false; } return true; } } // anonymous namespace void CollectVariables(TIntermBlock *root, std::vector *attributes, std::vector *outputVariables, std::vector *uniforms, std::vector *inputVaryings, std::vector *outputVaryings, std::vector *uniformBlocks, std::vector *shaderStorageBlocks, std::vector *inBlocks, ShHashFunction64 hashFunction, TSymbolTable *symbolTable, int shaderVersion, GLenum shaderType, const TExtensionBehavior &extensionBehavior) { CollectVariablesTraverser collect(attributes, outputVariables, uniforms, inputVaryings, outputVaryings, uniformBlocks, shaderStorageBlocks, inBlocks, hashFunction, symbolTable, shaderVersion, shaderType, extensionBehavior); root->traverse(&collect); } } // namespace sh