| // | |
| // 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. | |
| // | |
| // The ValidateVaryingLocations function checks if there exists location conflicts on shader | |
| // varyings. | |
| // | |
| #include "ValidateVaryingLocations.h" | |
| #include "compiler/translator/Diagnostics.h" | |
| #include "compiler/translator/SymbolTable.h" | |
| #include "compiler/translator/tree_util/IntermTraverse.h" | |
| #include "compiler/translator/util.h" | |
| namespace sh | |
| { | |
| namespace | |
| { | |
| void error(const TIntermSymbol &symbol, const char *reason, TDiagnostics *diagnostics) | |
| { | |
| diagnostics->error(symbol.getLine(), reason, symbol.getName().data()); | |
| } | |
| int GetLocationCount(const TIntermSymbol *varying, bool ignoreVaryingArraySize) | |
| { | |
| const auto &varyingType = varying->getType(); | |
| if (varyingType.getStruct() != nullptr) | |
| { | |
| ASSERT(!varyingType.isArray()); | |
| int totalLocation = 0; | |
| for (const auto *field : varyingType.getStruct()->fields()) | |
| { | |
| const auto *fieldType = field->type(); | |
| ASSERT(fieldType->getStruct() == nullptr && !fieldType->isArray()); | |
| totalLocation += fieldType->getSecondarySize(); | |
| } | |
| return totalLocation; | |
| } | |
| // [GL_EXT_shader_io_blocks SPEC Chapter 4.4.1] | |
| // Geometry shader inputs, tessellation control shader inputs and outputs, and tessellation | |
| // evaluation inputs all have an additional level of arrayness relative to other shader inputs | |
| // and outputs. This outer array level is removed from the type before considering how many | |
| // locations the type consumes. | |
| else if (ignoreVaryingArraySize) | |
| { | |
| // Array-of-arrays cannot be inputs or outputs of a geometry shader. | |
| // (GL_EXT_geometry_shader SPEC issues(5)) | |
| ASSERT(!varyingType.isArrayOfArrays()); | |
| return varyingType.getSecondarySize(); | |
| } | |
| else | |
| { | |
| return varyingType.getSecondarySize() * static_cast<int>(varyingType.getArraySizeProduct()); | |
| } | |
| } | |
| using VaryingVector = std::vector<const TIntermSymbol *>; | |
| void ValidateShaderInterface(TDiagnostics *diagnostics, | |
| VaryingVector &varyingVector, | |
| bool ignoreVaryingArraySize) | |
| { | |
| // Location conflicts can only happen when there are two or more varyings in varyingVector. | |
| if (varyingVector.size() <= 1) | |
| { | |
| return; | |
| } | |
| std::map<int, const TIntermSymbol *> locationMap; | |
| for (const TIntermSymbol *varying : varyingVector) | |
| { | |
| const int location = varying->getType().getLayoutQualifier().location; | |
| ASSERT(location >= 0); | |
| const int elementCount = GetLocationCount(varying, ignoreVaryingArraySize); | |
| for (int elementIndex = 0; elementIndex < elementCount; ++elementIndex) | |
| { | |
| const int offsetLocation = location + elementIndex; | |
| if (locationMap.find(offsetLocation) != locationMap.end()) | |
| { | |
| std::stringstream strstr = sh::InitializeStream<std::stringstream>(); | |
| strstr << "'" << varying->getName() | |
| << "' conflicting location with previously defined '" | |
| << locationMap[offsetLocation]->getName() << "'"; | |
| error(*varying, strstr.str().c_str(), diagnostics); | |
| } | |
| else | |
| { | |
| locationMap[offsetLocation] = varying; | |
| } | |
| } | |
| } | |
| } | |
| class ValidateVaryingLocationsTraverser : public TIntermTraverser | |
| { | |
| public: | |
| ValidateVaryingLocationsTraverser(GLenum shaderType); | |
| void validate(TDiagnostics *diagnostics); | |
| private: | |
| bool visitDeclaration(Visit visit, TIntermDeclaration *node) override; | |
| bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override; | |
| VaryingVector mInputVaryingsWithLocation; | |
| VaryingVector mOutputVaryingsWithLocation; | |
| GLenum mShaderType; | |
| }; | |
| ValidateVaryingLocationsTraverser::ValidateVaryingLocationsTraverser(GLenum shaderType) | |
| : TIntermTraverser(true, false, false), mShaderType(shaderType) | |
| {} | |
| bool ValidateVaryingLocationsTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node) | |
| { | |
| const TIntermSequence &sequence = *(node->getSequence()); | |
| ASSERT(!sequence.empty()); | |
| const TIntermSymbol *symbol = sequence.front()->getAsSymbolNode(); | |
| if (symbol == nullptr) | |
| { | |
| return false; | |
| } | |
| if (symbol->variable().symbolType() == SymbolType::Empty) | |
| { | |
| return false; | |
| } | |
| // Collect varyings that have explicit 'location' qualifiers. | |
| const TQualifier qualifier = symbol->getQualifier(); | |
| if (symbol->getType().getLayoutQualifier().location != -1) | |
| { | |
| if (IsVaryingIn(qualifier)) | |
| { | |
| mInputVaryingsWithLocation.push_back(symbol); | |
| } | |
| else if (IsVaryingOut(qualifier)) | |
| { | |
| mOutputVaryingsWithLocation.push_back(symbol); | |
| } | |
| } | |
| return false; | |
| } | |
| bool ValidateVaryingLocationsTraverser::visitFunctionDefinition(Visit visit, | |
| TIntermFunctionDefinition *node) | |
| { | |
| // We stop traversing function definitions because varyings cannot be defined in a function. | |
| return false; | |
| } | |
| void ValidateVaryingLocationsTraverser::validate(TDiagnostics *diagnostics) | |
| { | |
| ASSERT(diagnostics); | |
| ValidateShaderInterface(diagnostics, mInputVaryingsWithLocation, | |
| mShaderType == GL_GEOMETRY_SHADER_EXT); | |
| ValidateShaderInterface(diagnostics, mOutputVaryingsWithLocation, false); | |
| } | |
| } // anonymous namespace | |
| bool ValidateVaryingLocations(TIntermBlock *root, TDiagnostics *diagnostics, GLenum shaderType) | |
| { | |
| ValidateVaryingLocationsTraverser varyingValidator(shaderType); | |
| root->traverse(&varyingValidator); | |
| int numErrorsBefore = diagnostics->numErrors(); | |
| varyingValidator.validate(diagnostics); | |
| return (diagnostics->numErrors() == numErrorsBefore); | |
| } | |
| } // namespace sh |