| // |
| // Copyright 2002 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/SymbolTable.h" |
| #include "compiler/translator/tree_util/IntermTraverse.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; |
| } |
| } |
| |
| BlockType GetBlockType(TQualifier qualifier) |
| { |
| switch (qualifier) |
| { |
| case EvqUniform: |
| return BlockType::BLOCK_UNIFORM; |
| case EvqBuffer: |
| return BlockType::BLOCK_BUFFER; |
| default: |
| UNREACHABLE(); |
| return BlockType::BLOCK_UNIFORM; |
| } |
| } |
| |
| template <class VarT> |
| VarT *FindVariable(const ImmutableString &name, std::vector<VarT> *infoList) |
| { |
| // TODO(zmo): optimize this function. |
| for (size_t ii = 0; ii < infoList->size(); ++ii) |
| { |
| if (name == (*infoList)[ii].name) |
| return &((*infoList)[ii]); |
| } |
| |
| return nullptr; |
| } |
| |
| void MarkActive(ShaderVariable *variable) |
| { |
| if (!variable->active) |
| { |
| if (variable->isStruct()) |
| { |
| // Conservatively assume all fields are statically used as well. |
| for (auto &field : variable->fields) |
| { |
| MarkActive(&field); |
| } |
| } |
| variable->staticUse = true; |
| variable->active = true; |
| } |
| } |
| |
| ShaderVariable *FindVariableInInterfaceBlock(const ImmutableString &name, |
| const TInterfaceBlock *interfaceBlock, |
| std::vector<InterfaceBlock> *infoList) |
| { |
| ASSERT(interfaceBlock); |
| InterfaceBlock *namedBlock = FindVariable(interfaceBlock->name(), infoList); |
| ASSERT(namedBlock); |
| |
| // Set static use on the parent interface block here |
| namedBlock->staticUse = true; |
| namedBlock->active = true; |
| return FindVariable(name, &namedBlock->fields); |
| } |
| |
| ShaderVariable *FindShaderIOBlockVariable(const ImmutableString &blockName, |
| std::vector<ShaderVariable> *infoList) |
| { |
| for (size_t index = 0; index < infoList->size(); ++index) |
| { |
| if (blockName == (*infoList)[index].structOrBlockName) |
| return &(*infoList)[index]; |
| } |
| |
| return nullptr; |
| } |
| |
| // Traverses the intermediate tree to collect all attributes, uniforms, varyings, fragment outputs, |
| // shared data and interface blocks. |
| class CollectVariablesTraverser : public TIntermTraverser |
| { |
| public: |
| CollectVariablesTraverser(std::vector<ShaderVariable> *attribs, |
| std::vector<ShaderVariable> *outputVariables, |
| std::vector<ShaderVariable> *uniforms, |
| std::vector<ShaderVariable> *inputVaryings, |
| std::vector<ShaderVariable> *outputVaryings, |
| std::vector<ShaderVariable> *sharedVariables, |
| std::vector<InterfaceBlock> *uniformBlocks, |
| std::vector<InterfaceBlock> *shaderStorageBlocks, |
| ShHashFunction64 hashFunction, |
| TSymbolTable *symbolTable, |
| GLenum shaderType, |
| const TExtensionBehavior &extensionBehavior, |
| const ShBuiltInResources &resources, |
| int tessControlShaderOutputVertices); |
| |
| bool visitGlobalQualifierDeclaration(Visit visit, |
| TIntermGlobalQualifierDeclaration *node) override; |
| void visitSymbol(TIntermSymbol *symbol) override; |
| bool visitDeclaration(Visit, TIntermDeclaration *node) override; |
| bool visitBinary(Visit visit, TIntermBinary *binaryNode) override; |
| |
| private: |
| std::string getMappedName(const TSymbol *symbol) const; |
| |
| void setFieldOrVariableProperties(const TType &type, |
| bool staticUse, |
| bool isShaderIOBlock, |
| bool isPatch, |
| ShaderVariable *variableOut) const; |
| void setFieldProperties(const TType &type, |
| const ImmutableString &name, |
| bool staticUse, |
| bool isShaderIOBlock, |
| bool isPatch, |
| ShaderVariable *variableOut) const; |
| void setCommonVariableProperties(const TType &type, |
| const TVariable &variable, |
| ShaderVariable *variableOut) const; |
| |
| ShaderVariable recordAttribute(const TIntermSymbol &variable) const; |
| ShaderVariable recordOutputVariable(const TIntermSymbol &variable) const; |
| ShaderVariable recordVarying(const TIntermSymbol &variable) const; |
| void recordInterfaceBlock(const char *instanceName, |
| const TType &interfaceBlockType, |
| InterfaceBlock *interfaceBlock) const; |
| ShaderVariable recordUniform(const TIntermSymbol &variable) const; |
| |
| void setBuiltInInfoFromSymbol(const TVariable &variable, ShaderVariable *info); |
| |
| void recordBuiltInVaryingUsed(const TVariable &variable, |
| bool *addedFlag, |
| std::vector<ShaderVariable> *varyings); |
| void recordBuiltInFragmentOutputUsed(const TVariable &variable, bool *addedFlag); |
| void recordBuiltInAttributeUsed(const TVariable &variable, bool *addedFlag); |
| InterfaceBlock *findNamedInterfaceBlock(const ImmutableString &name) const; |
| |
| std::vector<ShaderVariable> *mAttribs; |
| std::vector<ShaderVariable> *mOutputVariables; |
| std::vector<ShaderVariable> *mUniforms; |
| std::vector<ShaderVariable> *mInputVaryings; |
| std::vector<ShaderVariable> *mOutputVaryings; |
| std::vector<ShaderVariable> *mSharedVariables; |
| std::vector<InterfaceBlock> *mUniformBlocks; |
| std::vector<InterfaceBlock> *mShaderStorageBlocks; |
| |
| std::map<std::string, ShaderVariable *> mInterfaceBlockFields; |
| |
| // Shader uniforms |
| bool mDepthRangeAdded; |
| bool mNumSamplesAdded; |
| |
| // Compute Shader builtins |
| bool mNumWorkGroupsAdded; |
| bool mWorkGroupIDAdded; |
| bool mLocalInvocationIDAdded; |
| bool mGlobalInvocationIDAdded; |
| bool mLocalInvocationIndexAdded; |
| |
| // Vertex Shader builtins |
| bool mInstanceIDAdded; |
| bool mVertexIDAdded; |
| bool mPointSizeAdded; |
| bool mDrawIDAdded; |
| |
| // Vertex Shader and Geometry Shader builtins |
| bool mPositionAdded; |
| bool mClipDistanceAdded; |
| bool mCullDistanceAdded; |
| |
| // Fragment Shader builtins |
| bool mPointCoordAdded; |
| bool mFrontFacingAdded; |
| bool mHelperInvocationAdded; |
| bool mFragCoordAdded; |
| bool mLastFragDataAdded; |
| bool mFragColorAdded; |
| bool mFragDataAdded; |
| bool mFragDepthAdded; |
| bool mSecondaryFragColorEXTAdded; |
| bool mSecondaryFragDataEXTAdded; |
| bool mSampleIDAdded; |
| bool mSamplePositionAdded; |
| bool mSampleMaskAdded; |
| bool mSampleMaskInAdded; |
| |
| // Geometry and Tessellation Shader builtins |
| bool mPerVertexInAdded; |
| bool mPerVertexOutAdded; |
| |
| // Geometry Shader builtins |
| bool mPrimitiveIDInAdded; |
| bool mInvocationIDAdded; |
| |
| // Geometry Shader and Fragment Shader builtins |
| bool mPrimitiveIDAdded; |
| bool mLayerAdded; |
| |
| // Shared memory variables |
| bool mSharedVariableAdded; |
| |
| // Tessellation Shader builtins |
| bool mPatchVerticesInAdded; |
| bool mTessLevelOuterAdded; |
| bool mTessLevelInnerAdded; |
| bool mBoundingBoxEXTAdded; |
| bool mTessCoordAdded; |
| const int mTessControlShaderOutputVertices; |
| |
| ShHashFunction64 mHashFunction; |
| |
| GLenum mShaderType; |
| const TExtensionBehavior &mExtensionBehavior; |
| const ShBuiltInResources &mResources; |
| }; |
| |
| CollectVariablesTraverser::CollectVariablesTraverser( |
| std::vector<sh::ShaderVariable> *attribs, |
| std::vector<sh::ShaderVariable> *outputVariables, |
| std::vector<sh::ShaderVariable> *uniforms, |
| std::vector<sh::ShaderVariable> *inputVaryings, |
| std::vector<sh::ShaderVariable> *outputVaryings, |
| std::vector<sh::ShaderVariable> *sharedVariables, |
| std::vector<sh::InterfaceBlock> *uniformBlocks, |
| std::vector<sh::InterfaceBlock> *shaderStorageBlocks, |
| ShHashFunction64 hashFunction, |
| TSymbolTable *symbolTable, |
| GLenum shaderType, |
| const TExtensionBehavior &extensionBehavior, |
| const ShBuiltInResources &resources, |
| int tessControlShaderOutputVertices) |
| : TIntermTraverser(true, false, false, symbolTable), |
| mAttribs(attribs), |
| mOutputVariables(outputVariables), |
| mUniforms(uniforms), |
| mInputVaryings(inputVaryings), |
| mOutputVaryings(outputVaryings), |
| mSharedVariables(sharedVariables), |
| mUniformBlocks(uniformBlocks), |
| mShaderStorageBlocks(shaderStorageBlocks), |
| mDepthRangeAdded(false), |
| mNumSamplesAdded(false), |
| mNumWorkGroupsAdded(false), |
| mWorkGroupIDAdded(false), |
| mLocalInvocationIDAdded(false), |
| mGlobalInvocationIDAdded(false), |
| mLocalInvocationIndexAdded(false), |
| mInstanceIDAdded(false), |
| mVertexIDAdded(false), |
| mPointSizeAdded(false), |
| mDrawIDAdded(false), |
| mPositionAdded(false), |
| mClipDistanceAdded(false), |
| mCullDistanceAdded(false), |
| mPointCoordAdded(false), |
| mFrontFacingAdded(false), |
| mHelperInvocationAdded(false), |
| mFragCoordAdded(false), |
| mLastFragDataAdded(false), |
| mFragColorAdded(false), |
| mFragDataAdded(false), |
| mFragDepthAdded(false), |
| mSecondaryFragColorEXTAdded(false), |
| mSecondaryFragDataEXTAdded(false), |
| mSampleIDAdded(false), |
| mSamplePositionAdded(false), |
| mSampleMaskAdded(false), |
| mSampleMaskInAdded(false), |
| mPerVertexInAdded(false), |
| mPerVertexOutAdded(false), |
| mPrimitiveIDInAdded(false), |
| mInvocationIDAdded(false), |
| mPrimitiveIDAdded(false), |
| mLayerAdded(false), |
| mSharedVariableAdded(false), |
| mPatchVerticesInAdded(false), |
| mTessLevelOuterAdded(false), |
| mTessLevelInnerAdded(false), |
| mBoundingBoxEXTAdded(false), |
| mTessCoordAdded(false), |
| mTessControlShaderOutputVertices(tessControlShaderOutputVertices), |
| mHashFunction(hashFunction), |
| mShaderType(shaderType), |
| mExtensionBehavior(extensionBehavior), |
| mResources(resources) |
| {} |
| |
| std::string CollectVariablesTraverser::getMappedName(const TSymbol *symbol) const |
| { |
| return HashName(symbol, mHashFunction, nullptr).data(); |
| } |
| |
| void CollectVariablesTraverser::setBuiltInInfoFromSymbol(const TVariable &variable, |
| ShaderVariable *info) |
| { |
| const TType &type = variable.getType(); |
| |
| info->name = variable.name().data(); |
| info->mappedName = variable.name().data(); |
| |
| bool isShaderIOBlock = |
| IsShaderIoBlock(type.getQualifier()) && type.getInterfaceBlock() != nullptr; |
| bool isPatch = type.getQualifier() == EvqTessLevelInner || |
| type.getQualifier() == EvqTessLevelOuter || |
| type.getQualifier() == EvqBoundingBoxEXT; |
| |
| setFieldOrVariableProperties(type, true, isShaderIOBlock, isPatch, info); |
| } |
| |
| void CollectVariablesTraverser::recordBuiltInVaryingUsed(const TVariable &variable, |
| bool *addedFlag, |
| std::vector<ShaderVariable> *varyings) |
| { |
| ASSERT(varyings); |
| if (!(*addedFlag)) |
| { |
| ShaderVariable info; |
| setBuiltInInfoFromSymbol(variable, &info); |
| info.active = true; |
| info.isInvariant = mSymbolTable->isVaryingInvariant(variable); |
| |
| varyings->push_back(info); |
| (*addedFlag) = true; |
| } |
| } |
| |
| void CollectVariablesTraverser::recordBuiltInFragmentOutputUsed(const TVariable &variable, |
| bool *addedFlag) |
| { |
| if (!(*addedFlag)) |
| { |
| ShaderVariable info; |
| setBuiltInInfoFromSymbol(variable, &info); |
| info.active = true; |
| mOutputVariables->push_back(info); |
| (*addedFlag) = true; |
| } |
| } |
| |
| void CollectVariablesTraverser::recordBuiltInAttributeUsed(const TVariable &variable, |
| bool *addedFlag) |
| { |
| if (!(*addedFlag)) |
| { |
| ShaderVariable info; |
| setBuiltInInfoFromSymbol(variable, &info); |
| info.active = true; |
| info.location = -1; |
| mAttribs->push_back(info); |
| (*addedFlag) = true; |
| } |
| } |
| |
| bool CollectVariablesTraverser::visitGlobalQualifierDeclaration( |
| Visit visit, |
| TIntermGlobalQualifierDeclaration *node) |
| { |
| // We should not mark variables as active just based on an invariant/precise declaration, so we |
| // don't traverse the symbols declared invariant. |
| return false; |
| } |
| |
| // We want to check whether a uniform/varying is active because we need to skip updating inactive |
| // ones. We also only count the active ones in packing computing. Also, gl_FragCoord, gl_PointCoord, |
| // and gl_FrontFacing count toward varying counting if they are active in a fragment shader. |
| void CollectVariablesTraverser::visitSymbol(TIntermSymbol *symbol) |
| { |
| ASSERT(symbol != nullptr); |
| |
| if (symbol->variable().symbolType() == SymbolType::AngleInternal || |
| symbol->variable().symbolType() == SymbolType::Empty) |
| { |
| // Internal variables or nameless variables are not collected. |
| return; |
| } |
| |
| ShaderVariable *var = nullptr; |
| |
| const ImmutableString &symbolName = symbol->getName(); |
| |
| // Check the qualifier from the variable, not from the symbol node. The node may have a |
| // different qualifier if it's the result of a folded ternary node. |
| TQualifier qualifier = symbol->variable().getType().getQualifier(); |
| const TInterfaceBlock *interfaceBlock = symbol->getType().getInterfaceBlock(); |
| |
| if (IsVaryingIn(qualifier)) |
| { |
| if (interfaceBlock) |
| { |
| var = FindShaderIOBlockVariable(interfaceBlock->name(), mInputVaryings); |
| } |
| else |
| { |
| var = FindVariable(symbolName, mInputVaryings); |
| } |
| } |
| else if (IsVaryingOut(qualifier)) |
| { |
| if (interfaceBlock) |
| { |
| var = FindShaderIOBlockVariable(interfaceBlock->name(), mOutputVaryings); |
| } |
| else |
| { |
| var = FindVariable(symbolName, mOutputVaryings); |
| } |
| } |
| else if (symbol->getType().getBasicType() == EbtInterfaceBlock) |
| { |
| UNREACHABLE(); |
| } |
| else if (symbolName == "gl_DepthRange") |
| { |
| ASSERT(qualifier == EvqUniform); |
| |
| if (!mDepthRangeAdded) |
| { |
| ShaderVariable info; |
| const char kName[] = "gl_DepthRange"; |
| info.name = kName; |
| info.mappedName = kName; |
| info.type = GL_NONE; |
| info.precision = GL_NONE; |
| info.staticUse = true; |
| info.active = true; |
| |
| ShaderVariable nearInfo(GL_FLOAT); |
| const char kNearName[] = "near"; |
| nearInfo.name = kNearName; |
| nearInfo.mappedName = kNearName; |
| nearInfo.precision = GL_HIGH_FLOAT; |
| nearInfo.staticUse = true; |
| nearInfo.active = true; |
| |
| ShaderVariable farInfo(GL_FLOAT); |
| const char kFarName[] = "far"; |
| farInfo.name = kFarName; |
| farInfo.mappedName = kFarName; |
| farInfo.precision = GL_HIGH_FLOAT; |
| farInfo.staticUse = true; |
| farInfo.active = true; |
| |
| ShaderVariable diffInfo(GL_FLOAT); |
| const char kDiffName[] = "diff"; |
| diffInfo.name = kDiffName; |
| diffInfo.mappedName = kDiffName; |
| diffInfo.precision = GL_HIGH_FLOAT; |
| diffInfo.staticUse = true; |
| diffInfo.active = true; |
| |
| info.fields.push_back(nearInfo); |
| info.fields.push_back(farInfo); |
| info.fields.push_back(diffInfo); |
| |
| mUniforms->push_back(info); |
| mDepthRangeAdded = true; |
| } |
| } |
| else if (symbolName == "gl_NumSamples") |
| { |
| ASSERT(qualifier == EvqUniform); |
| |
| if (!mNumSamplesAdded) |
| { |
| ShaderVariable info; |
| const char kName[] = "gl_NumSamples"; |
| info.name = kName; |
| info.mappedName = kName; |
| info.type = GL_INT; |
| info.precision = GL_LOW_INT; |
| info.staticUse = true; |
| info.active = true; |
| |
| mUniforms->push_back(info); |
| mNumSamplesAdded = true; |
| } |
| } |
| else |
| { |
| switch (qualifier) |
| { |
| case EvqAttribute: |
| case EvqVertexIn: |
| var = FindVariable(symbolName, mAttribs); |
| break; |
| case EvqFragmentOut: |
| case EvqFragmentInOut: |
| var = FindVariable(symbolName, mOutputVariables); |
| break; |
| case EvqUniform: |
| { |
| if (interfaceBlock) |
| { |
| var = FindVariableInInterfaceBlock(symbolName, interfaceBlock, mUniformBlocks); |
| } |
| else |
| { |
| var = FindVariable(symbolName, mUniforms); |
| } |
| |
| // It's an internal error to reference an undefined user uniform |
| ASSERT(!gl::IsBuiltInName(symbolName.data()) || var); |
| } |
| break; |
| case EvqBuffer: |
| { |
| var = |
| FindVariableInInterfaceBlock(symbolName, interfaceBlock, mShaderStorageBlocks); |
| } |
| break; |
| case EvqFragCoord: |
| recordBuiltInVaryingUsed(symbol->variable(), &mFragCoordAdded, mInputVaryings); |
| return; |
| case EvqFrontFacing: |
| recordBuiltInVaryingUsed(symbol->variable(), &mFrontFacingAdded, mInputVaryings); |
| return; |
| case EvqHelperInvocation: |
| recordBuiltInVaryingUsed(symbol->variable(), &mHelperInvocationAdded, |
| mInputVaryings); |
| return; |
| case EvqPointCoord: |
| recordBuiltInVaryingUsed(symbol->variable(), &mPointCoordAdded, mInputVaryings); |
| return; |
| case EvqNumWorkGroups: |
| recordBuiltInAttributeUsed(symbol->variable(), &mNumWorkGroupsAdded); |
| return; |
| case EvqWorkGroupID: |
| recordBuiltInAttributeUsed(symbol->variable(), &mWorkGroupIDAdded); |
| return; |
| case EvqLocalInvocationID: |
| recordBuiltInAttributeUsed(symbol->variable(), &mLocalInvocationIDAdded); |
| return; |
| case EvqGlobalInvocationID: |
| recordBuiltInAttributeUsed(symbol->variable(), &mGlobalInvocationIDAdded); |
| return; |
| case EvqLocalInvocationIndex: |
| recordBuiltInAttributeUsed(symbol->variable(), &mLocalInvocationIndexAdded); |
| 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. Note that gl_InstanceID is not added to the symbol table for ESSL1 |
| // shaders. |
| recordBuiltInAttributeUsed(symbol->variable(), &mInstanceIDAdded); |
| return; |
| case EvqVertexID: |
| recordBuiltInAttributeUsed(symbol->variable(), &mVertexIDAdded); |
| return; |
| case EvqPosition: |
| recordBuiltInVaryingUsed(symbol->variable(), &mPositionAdded, mOutputVaryings); |
| return; |
| case EvqPointSize: |
| recordBuiltInVaryingUsed(symbol->variable(), &mPointSizeAdded, mOutputVaryings); |
| return; |
| case EvqDrawID: |
| recordBuiltInAttributeUsed(symbol->variable(), &mDrawIDAdded); |
| return; |
| case EvqLastFragData: |
| recordBuiltInVaryingUsed(symbol->variable(), &mLastFragDataAdded, mInputVaryings); |
| return; |
| case EvqFragColor: |
| recordBuiltInFragmentOutputUsed(symbol->variable(), &mFragColorAdded); |
| return; |
| case EvqFragData: |
| if (!mFragDataAdded) |
| { |
| ShaderVariable info; |
| setBuiltInInfoFromSymbol(symbol->variable(), &info); |
| if (!IsExtensionEnabled(mExtensionBehavior, TExtension::EXT_draw_buffers)) |
| { |
| ASSERT(info.arraySizes.size() == 1u); |
| info.arraySizes.back() = 1u; |
| } |
| info.active = true; |
| mOutputVariables->push_back(info); |
| mFragDataAdded = true; |
| } |
| return; |
| case EvqFragDepth: |
| recordBuiltInFragmentOutputUsed(symbol->variable(), &mFragDepthAdded); |
| return; |
| case EvqSecondaryFragColorEXT: |
| recordBuiltInFragmentOutputUsed(symbol->variable(), &mSecondaryFragColorEXTAdded); |
| return; |
| case EvqSecondaryFragDataEXT: |
| recordBuiltInFragmentOutputUsed(symbol->variable(), &mSecondaryFragDataEXTAdded); |
| return; |
| case EvqInvocationID: |
| recordBuiltInVaryingUsed(symbol->variable(), &mInvocationIDAdded, mInputVaryings); |
| break; |
| case EvqPrimitiveIDIn: |
| recordBuiltInVaryingUsed(symbol->variable(), &mPrimitiveIDInAdded, mInputVaryings); |
| break; |
| case EvqPrimitiveID: |
| if (mShaderType == GL_GEOMETRY_SHADER_EXT) |
| { |
| recordBuiltInVaryingUsed(symbol->variable(), &mPrimitiveIDAdded, |
| mOutputVaryings); |
| } |
| else |
| { |
| ASSERT(mShaderType == GL_FRAGMENT_SHADER || |
| mShaderType == GL_TESS_CONTROL_SHADER || |
| mShaderType == GL_TESS_EVALUATION_SHADER); |
| recordBuiltInVaryingUsed(symbol->variable(), &mPrimitiveIDAdded, |
| mInputVaryings); |
| } |
| break; |
| case EvqLayer: |
| if (mShaderType == GL_GEOMETRY_SHADER_EXT) |
| { |
| recordBuiltInVaryingUsed(symbol->variable(), &mLayerAdded, mOutputVaryings); |
| } |
| else if (mShaderType == GL_FRAGMENT_SHADER) |
| { |
| recordBuiltInVaryingUsed(symbol->variable(), &mLayerAdded, mInputVaryings); |
| } |
| else |
| { |
| ASSERT(mShaderType == GL_VERTEX_SHADER && |
| (IsExtensionEnabled(mExtensionBehavior, TExtension::OVR_multiview2) || |
| IsExtensionEnabled(mExtensionBehavior, TExtension::OVR_multiview))); |
| } |
| break; |
| case EvqShared: |
| if (mShaderType == GL_COMPUTE_SHADER) |
| { |
| recordBuiltInVaryingUsed(symbol->variable(), &mSharedVariableAdded, |
| mSharedVariables); |
| } |
| break; |
| case EvqClipDistance: |
| recordBuiltInVaryingUsed( |
| symbol->variable(), &mClipDistanceAdded, |
| mShaderType == GL_FRAGMENT_SHADER ? mInputVaryings : mOutputVaryings); |
| return; |
| case EvqCullDistance: |
| recordBuiltInVaryingUsed( |
| symbol->variable(), &mCullDistanceAdded, |
| mShaderType == GL_FRAGMENT_SHADER ? mInputVaryings : mOutputVaryings); |
| return; |
| case EvqSampleID: |
| recordBuiltInVaryingUsed(symbol->variable(), &mSampleIDAdded, mInputVaryings); |
| return; |
| case EvqSamplePosition: |
| recordBuiltInVaryingUsed(symbol->variable(), &mSamplePositionAdded, mInputVaryings); |
| return; |
| case EvqSampleMaskIn: |
| recordBuiltInVaryingUsed(symbol->variable(), &mSampleMaskInAdded, mInputVaryings); |
| return; |
| case EvqSampleMask: |
| recordBuiltInFragmentOutputUsed(symbol->variable(), &mSampleMaskAdded); |
| return; |
| case EvqPatchVerticesIn: |
| recordBuiltInVaryingUsed(symbol->variable(), &mPatchVerticesInAdded, |
| mInputVaryings); |
| break; |
| case EvqTessCoord: |
| recordBuiltInVaryingUsed(symbol->variable(), &mTessCoordAdded, mInputVaryings); |
| break; |
| case EvqTessLevelOuter: |
| if (mShaderType == GL_TESS_CONTROL_SHADER) |
| { |
| recordBuiltInVaryingUsed(symbol->variable(), &mTessLevelOuterAdded, |
| mOutputVaryings); |
| } |
| else |
| { |
| ASSERT(mShaderType == GL_TESS_EVALUATION_SHADER); |
| recordBuiltInVaryingUsed(symbol->variable(), &mTessLevelOuterAdded, |
| mInputVaryings); |
| } |
| break; |
| case EvqTessLevelInner: |
| if (mShaderType == GL_TESS_CONTROL_SHADER) |
| { |
| recordBuiltInVaryingUsed(symbol->variable(), &mTessLevelInnerAdded, |
| mOutputVaryings); |
| } |
| else |
| { |
| ASSERT(mShaderType == GL_TESS_EVALUATION_SHADER); |
| recordBuiltInVaryingUsed(symbol->variable(), &mTessLevelInnerAdded, |
| mInputVaryings); |
| } |
| break; |
| case EvqBoundingBoxEXT: |
| recordBuiltInVaryingUsed(symbol->variable(), &mBoundingBoxEXTAdded, |
| mOutputVaryings); |
| break; |
| default: |
| break; |
| } |
| } |
| if (var) |
| { |
| MarkActive(var); |
| } |
| } |
| |
| void CollectVariablesTraverser::setFieldOrVariableProperties(const TType &type, |
| bool staticUse, |
| bool isShaderIOBlock, |
| bool isPatch, |
| ShaderVariable *variableOut) const |
| { |
| ASSERT(variableOut); |
| |
| variableOut->staticUse = staticUse; |
| variableOut->isShaderIOBlock = isShaderIOBlock; |
| variableOut->isPatch = isPatch; |
| |
| const TStructure *structure = type.getStruct(); |
| const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock(); |
| if (structure) |
| { |
| // Structures use a NONE type that isn't exposed outside ANGLE. |
| variableOut->type = GL_NONE; |
| if (structure->symbolType() != SymbolType::Empty) |
| { |
| variableOut->structOrBlockName = structure->name().data(); |
| } |
| |
| const TFieldList &fields = structure->fields(); |
| |
| for (const TField *field : fields) |
| { |
| // Regardless of the variable type (uniform, in/out etc.) its fields are always plain |
| // ShaderVariable objects. |
| ShaderVariable fieldVariable; |
| setFieldProperties(*field->type(), field->name(), staticUse, isShaderIOBlock, isPatch, |
| &fieldVariable); |
| variableOut->fields.push_back(fieldVariable); |
| } |
| } |
| else if (interfaceBlock && isShaderIOBlock) |
| { |
| variableOut->type = GL_NONE; |
| if (interfaceBlock->symbolType() != SymbolType::Empty) |
| { |
| variableOut->structOrBlockName = interfaceBlock->name().data(); |
| variableOut->mappedStructOrBlockName = |
| HashName(interfaceBlock->name(), mHashFunction, nullptr).data(); |
| } |
| const TFieldList &fields = interfaceBlock->fields(); |
| for (const TField *field : fields) |
| { |
| ShaderVariable fieldVariable; |
| setFieldProperties(*field->type(), field->name(), staticUse, true, isPatch, |
| &fieldVariable); |
| fieldVariable.isShaderIOBlock = true; |
| variableOut->fields.push_back(fieldVariable); |
| } |
| } |
| else |
| { |
| variableOut->type = GLVariableType(type); |
| variableOut->precision = GLVariablePrecision(type); |
| } |
| |
| const TSpan<const unsigned int> &arraySizes = type.getArraySizes(); |
| if (!arraySizes.empty()) |
| { |
| variableOut->arraySizes.assign(arraySizes.begin(), arraySizes.end()); |
| |
| if (arraySizes[0] == 0) |
| { |
| // Tessellation Control & Evaluation shader inputs: |
| // Declaring an array size is optional. If no size is specified, it will be taken from |
| // the implementation-dependent maximum patch size (gl_MaxPatchVertices). |
| if (type.getQualifier() == EvqTessControlIn || |
| type.getQualifier() == EvqTessEvaluationIn) |
| { |
| variableOut->arraySizes[0] = mResources.MaxPatchVertices; |
| } |
| |
| // Tessellation Control shader outputs: |
| // Declaring an array size is optional. If no size is specified, it will be taken from |
| // output patch size declared in the shader. |
| if (type.getQualifier() == EvqTessControlOut) |
| { |
| ASSERT(mTessControlShaderOutputVertices > 0); |
| variableOut->arraySizes[0] = mTessControlShaderOutputVertices; |
| } |
| } |
| } |
| } |
| |
| void CollectVariablesTraverser::setFieldProperties(const TType &type, |
| const ImmutableString &name, |
| bool staticUse, |
| bool isShaderIOBlock, |
| bool isPatch, |
| ShaderVariable *variableOut) const |
| { |
| ASSERT(variableOut); |
| setFieldOrVariableProperties(type, staticUse, isShaderIOBlock, isPatch, variableOut); |
| variableOut->name.assign(name.data(), name.length()); |
| variableOut->mappedName = HashName(name, mHashFunction, nullptr).data(); |
| } |
| |
| void CollectVariablesTraverser::setCommonVariableProperties(const TType &type, |
| const TVariable &variable, |
| ShaderVariable *variableOut) const |
| { |
| ASSERT(variableOut); |
| ASSERT(type.getInterfaceBlock() == nullptr || IsShaderIoBlock(type.getQualifier()) || |
| type.getQualifier() == EvqPatchIn || type.getQualifier() == EvqPatchOut); |
| |
| const bool staticUse = mSymbolTable->isStaticallyUsed(variable); |
| const bool isShaderIOBlock = type.getInterfaceBlock() != nullptr; |
| const bool isPatch = type.getQualifier() == EvqPatchIn || type.getQualifier() == EvqPatchOut; |
| |
| setFieldOrVariableProperties(type, staticUse, isShaderIOBlock, isPatch, variableOut); |
| |
| const bool isNamed = variable.symbolType() != SymbolType::Empty; |
| |
| ASSERT(isNamed || isShaderIOBlock); |
| if (isNamed) |
| { |
| variableOut->name.assign(variable.name().data(), variable.name().length()); |
| variableOut->mappedName = getMappedName(&variable); |
| } |
| |
| // For I/O blocks, additionally store the name of the block as blockName. If the variable is |
| // unnamed, this name will be used instead for the purpose of interface matching. |
| if (isShaderIOBlock) |
| { |
| const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock(); |
| ASSERT(interfaceBlock); |
| |
| variableOut->structOrBlockName.assign(interfaceBlock->name().data(), |
| interfaceBlock->name().length()); |
| variableOut->mappedStructOrBlockName = |
| HashName(interfaceBlock->name(), mHashFunction, nullptr).data(); |
| variableOut->isShaderIOBlock = true; |
| } |
| } |
| |
| ShaderVariable CollectVariablesTraverser::recordAttribute(const TIntermSymbol &variable) const |
| { |
| const TType &type = variable.getType(); |
| ASSERT(!type.getStruct()); |
| |
| ShaderVariable attribute; |
| setCommonVariableProperties(type, variable.variable(), &attribute); |
| |
| attribute.location = type.getLayoutQualifier().location; |
| return attribute; |
| } |
| |
| ShaderVariable CollectVariablesTraverser::recordOutputVariable(const TIntermSymbol &variable) const |
| { |
| const TType &type = variable.getType(); |
| ASSERT(!type.getStruct()); |
| |
| ShaderVariable outputVariable; |
| setCommonVariableProperties(type, variable.variable(), &outputVariable); |
| |
| outputVariable.location = type.getLayoutQualifier().location; |
| outputVariable.index = type.getLayoutQualifier().index; |
| outputVariable.yuv = type.getLayoutQualifier().yuv; |
| return outputVariable; |
| } |
| |
| ShaderVariable CollectVariablesTraverser::recordVarying(const TIntermSymbol &variable) const |
| { |
| const TType &type = variable.getType(); |
| |
| ShaderVariable varying; |
| setCommonVariableProperties(type, variable.variable(), &varying); |
| varying.location = type.getLayoutQualifier().location; |
| |
| switch (type.getQualifier()) |
| { |
| case EvqVaryingIn: |
| case EvqVaryingOut: |
| case EvqVertexOut: |
| case EvqSmoothOut: |
| case EvqFlatOut: |
| case EvqNoPerspectiveOut: |
| case EvqCentroidOut: |
| case EvqGeometryOut: |
| case EvqSampleOut: |
| if (mSymbolTable->isVaryingInvariant(variable.variable()) || type.isInvariant()) |
| { |
| varying.isInvariant = true; |
| } |
| break; |
| case EvqPatchIn: |
| case EvqPatchOut: |
| varying.isPatch = true; |
| break; |
| default: |
| break; |
| } |
| |
| varying.interpolation = GetInterpolationType(type.getQualifier()); |
| |
| // Shader I/O block properties |
| if (type.getBasicType() == EbtInterfaceBlock) |
| { |
| bool isBlockImplicitLocation = false; |
| int location = type.getLayoutQualifier().location; |
| |
| // when a interface has not location in layout, assign to the zero. |
| if (location < 0) |
| { |
| location = 0; |
| isBlockImplicitLocation = true; |
| } |
| |
| const TInterfaceBlock *blockType = type.getInterfaceBlock(); |
| ASSERT(blockType->fields().size() == varying.fields.size()); |
| |
| for (size_t fieldIndex = 0; fieldIndex < varying.fields.size(); ++fieldIndex) |
| { |
| const TField *blockField = blockType->fields()[fieldIndex]; |
| ShaderVariable &fieldVariable = varying.fields[fieldIndex]; |
| const TType &fieldType = *blockField->type(); |
| |
| fieldVariable.hasImplicitLocation = isBlockImplicitLocation; |
| fieldVariable.isPatch = varying.isPatch; |
| |
| int fieldLocation = fieldType.getLayoutQualifier().location; |
| if (fieldLocation >= 0) |
| { |
| fieldVariable.hasImplicitLocation = false; |
| fieldVariable.location = fieldLocation; |
| location = fieldLocation; |
| } |
| else |
| { |
| fieldVariable.location = location; |
| location += fieldType.getLocationCount(); |
| } |
| |
| if (fieldType.getQualifier() != EvqGlobal) |
| { |
| fieldVariable.interpolation = GetFieldInterpolationType(fieldType.getQualifier()); |
| } |
| } |
| } |
| |
| return varying; |
| } |
| |
| void CollectVariablesTraverser::recordInterfaceBlock(const char *instanceName, |
| const TType &interfaceBlockType, |
| InterfaceBlock *interfaceBlock) const |
| { |
| ASSERT(interfaceBlockType.getBasicType() == EbtInterfaceBlock); |
| ASSERT(interfaceBlock); |
| |
| const TInterfaceBlock *blockType = interfaceBlockType.getInterfaceBlock(); |
| ASSERT(blockType); |
| |
| interfaceBlock->name = blockType->name().data(); |
| interfaceBlock->mappedName = getMappedName(blockType); |
| |
| const bool isGLInBuiltin = (instanceName != nullptr) && strncmp(instanceName, "gl_in", 5u) == 0; |
| if (instanceName != nullptr) |
| { |
| interfaceBlock->instanceName = instanceName; |
| const TSymbol *blockSymbol = nullptr; |
| if (isGLInBuiltin) |
| { |
| blockSymbol = mSymbolTable->getGlInVariableWithArraySize(); |
| } |
| else |
| { |
| blockSymbol = mSymbolTable->findGlobal(ImmutableString(instanceName)); |
| } |
| ASSERT(blockSymbol && blockSymbol->isVariable()); |
| interfaceBlock->staticUse = |
| mSymbolTable->isStaticallyUsed(*static_cast<const TVariable *>(blockSymbol)); |
| } |
| |
| 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) |
| { |
| // TODO(oetuaho): Remove setting isRowMajorLayout. |
| interfaceBlock->isRowMajorLayout = false; |
| interfaceBlock->binding = blockType->blockBinding(); |
| interfaceBlock->layout = GetBlockLayoutType(blockType->blockStorage()); |
| } |
| |
| // Gather field information |
| bool anyFieldStaticallyUsed = false; |
| |
| for (const TField *field : blockType->fields()) |
| { |
| const TType &fieldType = *field->type(); |
| |
| bool staticUse = false; |
| if (instanceName == nullptr) |
| { |
| // Static use of individual fields has been recorded, since they are present in the |
| // symbol table as variables. |
| const TSymbol *fieldSymbol = mSymbolTable->findGlobal(field->name()); |
| ASSERT(fieldSymbol && fieldSymbol->isVariable()); |
| staticUse = |
| mSymbolTable->isStaticallyUsed(*static_cast<const TVariable *>(fieldSymbol)); |
| if (staticUse) |
| { |
| anyFieldStaticallyUsed = true; |
| } |
| } |
| |
| ShaderVariable fieldVariable; |
| setFieldProperties(fieldType, field->name(), staticUse, false, false, &fieldVariable); |
| fieldVariable.isRowMajorLayout = |
| (fieldType.getLayoutQualifier().matrixPacking == EmpRowMajor); |
| interfaceBlock->fields.push_back(fieldVariable); |
| } |
| if (anyFieldStaticallyUsed) |
| { |
| interfaceBlock->staticUse = true; |
| } |
| } |
| |
| ShaderVariable CollectVariablesTraverser::recordUniform(const TIntermSymbol &variable) const |
| { |
| ShaderVariable uniform; |
| setCommonVariableProperties(variable.getType(), variable.variable(), &uniform); |
| uniform.binding = variable.getType().getLayoutQualifier().binding; |
| uniform.imageUnitFormat = |
| GetImageInternalFormatType(variable.getType().getLayoutQualifier().imageInternalFormat); |
| uniform.location = variable.getType().getLayoutQualifier().location; |
| uniform.offset = variable.getType().getLayoutQualifier().offset; |
| uniform.readonly = variable.getType().getMemoryQualifier().readonly; |
| uniform.writeonly = variable.getType().getMemoryQualifier().writeonly; |
| 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 == EvqFragmentInOut || |
| 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.variable().symbolType() == SymbolType::AngleInternal) |
| { |
| // Internal variables are not collected. |
| continue; |
| } |
| |
| // SpirvTransformer::transform uses a map of ShaderVariables, it needs member variables and |
| // (named or unnamed) structure as ShaderVariable. at link between two shaders, validation |
| // between of named and unnamed, needs the same structure, its members, and members order |
| // except instance name. |
| if (typedNode.getBasicType() == EbtInterfaceBlock && !IsShaderIoBlock(qualifier) && |
| qualifier != EvqPatchIn && qualifier != EvqPatchOut) |
| { |
| InterfaceBlock interfaceBlock; |
| bool isUnnamed = variable.variable().symbolType() == SymbolType::Empty; |
| const TType &type = variable.getType(); |
| recordInterfaceBlock(isUnnamed ? nullptr : variable.getName().data(), type, |
| &interfaceBlock); |
| |
| // all fields in interface block will be added for updating interface variables because |
| // the temporal structure variable will be ignored. |
| switch (qualifier) |
| { |
| case EvqUniform: |
| mUniformBlocks->push_back(interfaceBlock); |
| break; |
| case EvqBuffer: |
| mShaderStorageBlocks->push_back(interfaceBlock); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| else |
| { |
| ASSERT(variable.variable().symbolType() != SymbolType::Empty || |
| IsShaderIoBlock(qualifier) || qualifier == EvqPatchIn || |
| qualifier == EvqPatchOut); |
| switch (qualifier) |
| { |
| case EvqAttribute: |
| case EvqVertexIn: |
| mAttribs->push_back(recordAttribute(variable)); |
| break; |
| case EvqFragmentOut: |
| case EvqFragmentInOut: |
| 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; |
| } |
| |
| InterfaceBlock *CollectVariablesTraverser::findNamedInterfaceBlock( |
| const ImmutableString &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 / activeness 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) |
| { |
| ASSERT(interfaceIndexingNode->getOp() == EOpIndexDirect || |
| interfaceIndexingNode->getOp() == EOpIndexIndirect); |
| traverseIndexExpression = true; |
| blockNode = interfaceIndexingNode->getLeft(); |
| } |
| |
| const TType &interfaceNodeType = blockNode->getType(); |
| const TInterfaceBlock *interfaceBlock = interfaceNodeType.getInterfaceBlock(); |
| const TQualifier qualifier = interfaceNodeType.getQualifier(); |
| |
| // If it's a shader I/O block, look in varyings |
| ShaderVariable *ioBlockVar = nullptr; |
| if (qualifier == EvqPerVertexIn) |
| { |
| TIntermSymbol *symbolNode = blockNode->getAsSymbolNode(); |
| ASSERT(symbolNode); |
| recordBuiltInVaryingUsed(symbolNode->variable(), &mPerVertexInAdded, mInputVaryings); |
| ioBlockVar = FindShaderIOBlockVariable(interfaceBlock->name(), mInputVaryings); |
| } |
| else if (IsVaryingIn(qualifier)) |
| { |
| ioBlockVar = FindShaderIOBlockVariable(interfaceBlock->name(), mInputVaryings); |
| } |
| else if (qualifier == EvqPerVertexOut) |
| { |
| TIntermSymbol *symbolNode = blockNode->getAsSymbolNode(); |
| ASSERT(symbolNode); |
| recordBuiltInVaryingUsed(symbolNode->variable(), &mPerVertexOutAdded, mOutputVaryings); |
| ioBlockVar = FindShaderIOBlockVariable(interfaceBlock->name(), mOutputVaryings); |
| } |
| else if (IsVaryingOut(qualifier)) |
| { |
| ioBlockVar = FindShaderIOBlockVariable(interfaceBlock->name(), mOutputVaryings); |
| } |
| |
| if (ioBlockVar) |
| { |
| MarkActive(ioBlockVar); |
| } |
| else |
| { |
| if (!namedBlock) |
| { |
| namedBlock = findNamedInterfaceBlock(interfaceBlock->name()); |
| } |
| ASSERT(namedBlock); |
| ASSERT(namedBlock->staticUse); |
| namedBlock->active = true; |
| unsigned int fieldIndex = static_cast<unsigned int>(constantUnion->getIConst(0)); |
| ASSERT(fieldIndex < namedBlock->fields.size()); |
| // TODO(oetuaho): Would be nicer to record static use of fields of named interface |
| // blocks more accurately at parse time - now we only mark the fields statically used if |
| // they are active. http://anglebug.com/2440 We need to mark this field and all of its |
| // sub-fields, as static/active |
| MarkActive(&namedBlock->fields[fieldIndex]); |
| } |
| |
| if (traverseIndexExpression) |
| { |
| ASSERT(interfaceIndexingNode); |
| interfaceIndexingNode->getRight()->traverse(this); |
| } |
| return false; |
| } |
| |
| return true; |
| } |
| |
| } // anonymous namespace |
| |
| void CollectVariables(TIntermBlock *root, |
| std::vector<ShaderVariable> *attributes, |
| std::vector<ShaderVariable> *outputVariables, |
| std::vector<ShaderVariable> *uniforms, |
| std::vector<ShaderVariable> *inputVaryings, |
| std::vector<ShaderVariable> *outputVaryings, |
| std::vector<ShaderVariable> *sharedVariables, |
| std::vector<InterfaceBlock> *uniformBlocks, |
| std::vector<InterfaceBlock> *shaderStorageBlocks, |
| ShHashFunction64 hashFunction, |
| TSymbolTable *symbolTable, |
| GLenum shaderType, |
| const TExtensionBehavior &extensionBehavior, |
| const ShBuiltInResources &resources, |
| int tessControlShaderOutputVertices) |
| { |
| CollectVariablesTraverser collect( |
| attributes, outputVariables, uniforms, inputVaryings, outputVaryings, sharedVariables, |
| uniformBlocks, shaderStorageBlocks, hashFunction, symbolTable, shaderType, |
| extensionBehavior, resources, tessControlShaderOutputVertices); |
| root->traverse(&collect); |
| } |
| |
| } // namespace sh |