// // 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()); } unsigned int GetLocationCount(const TIntermSymbol *varying, bool ignoreVaryingArraySize) { const auto &varyingType = varying->getType(); if (varyingType.getStruct() != nullptr) { ASSERT(!varyingType.isArray()); unsigned int totalLocation = 0; for (const auto *field : varyingType.getStruct()->fields()) { const auto *fieldType = field->type(); ASSERT(fieldType->getStruct() == nullptr && !fieldType->isArray()); totalLocation += fieldType->isMatrix() ? fieldType->getNominalSize() : 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 if (varyingType.isMatrix()) { return varyingType.getNominalSize() * varyingType.getArraySizeProduct(); } else { return varyingType.getArraySizeProduct(); } } using VaryingVector = std::vector; 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 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(); 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 unsigned int CalculateVaryingLocationCount(TIntermSymbol *varying, GLenum shaderType) { return GetLocationCount(varying, shaderType == GL_GEOMETRY_SHADER_EXT); } 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