| // |
| // 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<int, SymbolAndField>; |
| |
| 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<std::stringstream>(); |
| 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<const TIntermSymbol *>; |
| |
| 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 |