// // 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 GetStructLocationCount(const TStructure *structure); int GetFieldLocationCount(const TField *field) { int field_size = 0; const TType *fieldType = field->type(); if (fieldType->getStruct() != nullptr) { field_size = GetStructLocationCount(fieldType->getStruct()); } else if (fieldType->isMatrix()) { field_size = fieldType->getNominalSize(); } else { ASSERT(fieldType->getSecondarySize() == 1); field_size = 1; } if (fieldType->isArray()) { field_size *= fieldType->getArraySizeProduct(); } return field_size; } int GetStructLocationCount(const TStructure *structure) { int totalLocation = 0; for (const TField *field : structure->fields()) { totalLocation += GetFieldLocationCount(field); } return totalLocation; } int GetInterfaceBlockLocationCount(const TType &varyingType, bool ignoreVaryingArraySize) { int totalLocation = 0; for (const TField *field : varyingType.getInterfaceBlock()->fields()) { totalLocation += GetFieldLocationCount(field); } if (!ignoreVaryingArraySize && varyingType.isArray()) { totalLocation *= varyingType.getArraySizeProduct(); } return totalLocation; } int GetLocationCount(const TType &varyingType, bool ignoreVaryingArraySize) { ASSERT(!varyingType.isInterfaceBlock()); if (varyingType.getStruct() != nullptr) { int totalLocation = 0; for (const TField *field : varyingType.getStruct()->fields()) { const TType *fieldType = field->type(); ASSERT(fieldType->getStruct() == nullptr && !fieldType->isArray()); totalLocation += GetFieldLocationCount(field); } return totalLocation; } ASSERT(varyingType.isMatrix() || varyingType.getSecondarySize() == 1); int elementLocationCount = varyingType.isMatrix() ? varyingType.getNominalSize() : 1; // [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. 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 elementLocationCount; } return elementLocationCount * varyingType.getArraySizeProduct(); } bool ShouldIgnoreVaryingArraySize(TQualifier qualifier, GLenum shaderType) { bool isVaryingIn = IsShaderIn(qualifier) && qualifier != EvqPatchIn; switch (shaderType) { case GL_GEOMETRY_SHADER: case GL_TESS_EVALUATION_SHADER: return isVaryingIn; case GL_TESS_CONTROL_SHADER: return (IsShaderOut(qualifier) && qualifier != EvqPatchOut) || isVaryingIn; default: return false; } } struct SymbolAndField { const TIntermSymbol *symbol; const TField *field; }; using LocationMap = std::map; void MarkVaryingLocations(TDiagnostics *diagnostics, const TIntermSymbol *varying, const TField *field, int location, int elementCount, LocationMap *locationMap) { for (int elementIndex = 0; elementIndex < elementCount; ++elementIndex) { const int offsetLocation = location + elementIndex; auto conflict = locationMap->find(offsetLocation); if (conflict != locationMap->end()) { std::stringstream strstr = sh::InitializeStream(); strstr << "'" << varying->getName(); if (field) { strstr << "." << field->name(); } strstr << "' conflicting location with '" << conflict->second.symbol->getName(); if (conflict->second.field) { strstr << "." << conflict->second.field->name(); } strstr << "'"; error(*varying, strstr.str().c_str(), diagnostics); } else { (*locationMap)[offsetLocation] = {varying, field}; } } } using VaryingVector = std::vector; void ValidateShaderInterfaceAndAssignLocations(TDiagnostics *diagnostics, const VaryingVector &varyingVector, GLenum shaderType) { // Location conflicts can only happen when there are two or more varyings in varyingVector. if (varyingVector.size() <= 1) { return; } LocationMap locationMap; for (const TIntermSymbol *varying : varyingVector) { const TType &varyingType = varying->getType(); const int location = varyingType.getLayoutQualifier().location; ASSERT(location >= 0); bool ignoreVaryingArraySize = ShouldIgnoreVaryingArraySize(varying->getQualifier(), shaderType); // A varying is either: // // - A vector or matrix, which can take a number of contiguous locations // - A struct, which also takes a number of contiguous locations // - An interface block. // // Interface blocks can assign arbitrary locations to their fields, for example: // // layout(location = 4) in block { // vec4 a; // gets location 4 // vec4 b; // gets location 5 // layout(location = 7) vec4 c; // gets location 7 // vec4 d; // gets location 8 // layout (location = 1) vec4 e; // gets location 1 // vec4 f; // gets location 2 // }; // // The following code therefore takes two paths. For non-interface-block types, the number // of locations for the varying is calculated (elementCount), and all locations in // [location, location + elementCount) are marked as occupied. // // For interface blocks, a similar algorithm is implemented except each field is // individually marked with the location either advancing automatically or taking its value // from the field's layout qualifier. if (varyingType.isInterfaceBlock()) { int currentLocation = location; bool anyFieldWithLocation = false; for (const TField *field : varyingType.getInterfaceBlock()->fields()) { const int fieldLocation = field->type()->getLayoutQualifier().location; if (fieldLocation >= 0) { currentLocation = fieldLocation; anyFieldWithLocation = true; } const int fieldLocationCount = GetFieldLocationCount(field); MarkVaryingLocations(diagnostics, varying, field, currentLocation, fieldLocationCount, &locationMap); currentLocation += fieldLocationCount; } // Array interface blocks can't have location qualifiers on fields. ASSERT(ignoreVaryingArraySize || !anyFieldWithLocation || !varyingType.isArray()); if (!ignoreVaryingArraySize && varyingType.isArray()) { // This is only reached if the varying is an array of interface blocks, with only a // layout qualifier on the block itself, for example: // // layout(location = 4) in block { // vec4 a; // vec4 b; // vec4 c; // vec4 d; // } instance[N]; // // The locations for instance[0] are already marked by the above code, so we need to // further mark locations occupied by instances [1, N). |currentLocation| is // already just past the end of instance[0], which is the beginning of instance[1]. // int remainingLocations = currentLocation * (varyingType.getArraySizeProduct() - 1); MarkVaryingLocations(diagnostics, varying, nullptr, currentLocation, remainingLocations, &locationMap); } } else { const int elementCount = GetLocationCount(varying->getType(), ignoreVaryingArraySize); MarkVaryingLocations(diagnostics, varying, nullptr, location, elementCount, &locationMap); } } } 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); ValidateShaderInterfaceAndAssignLocations(diagnostics, mInputVaryingsWithLocation, mShaderType); ValidateShaderInterfaceAndAssignLocations(diagnostics, mOutputVaryingsWithLocation, mShaderType); } } // anonymous namespace unsigned int CalculateVaryingLocationCount(const TType &varyingType, GLenum shaderType) { const TQualifier qualifier = varyingType.getQualifier(); const bool ignoreVaryingArraySize = ShouldIgnoreVaryingArraySize(qualifier, shaderType); if (varyingType.isInterfaceBlock()) { return GetInterfaceBlockLocationCount(varyingType, ignoreVaryingArraySize); } return GetLocationCount(varyingType, ignoreVaryingArraySize); } 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