1//
2// Copyright (c) 2002-2017 The ANGLE Project Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5//
6// The ValidateVaryingLocations function checks if there exists location conflicts on shader
7// varyings.
8//
9
10#include "ValidateVaryingLocations.h"
11
12#include "compiler/translator/Diagnostics.h"
13#include "compiler/translator/SymbolTable.h"
14#include "compiler/translator/tree_util/IntermTraverse.h"
15#include "compiler/translator/util.h"
16
17namespace sh
18{
19
20namespace
21{
22
23void error(const TIntermSymbol &symbol, const char *reason, TDiagnostics *diagnostics)
24{
25 diagnostics->error(symbol.getLine(), reason, symbol.getName().data());
26}
27
28int GetLocationCount(const TIntermSymbol *varying, bool ignoreVaryingArraySize)
29{
30 const auto &varyingType = varying->getType();
31 if (varyingType.getStruct() != nullptr)
32 {
33 ASSERT(!varyingType.isArray());
34 int totalLocation = 0;
35 for (const auto *field : varyingType.getStruct()->fields())
36 {
37 const auto *fieldType = field->type();
38 ASSERT(fieldType->getStruct() == nullptr && !fieldType->isArray());
39
40 totalLocation += fieldType->getSecondarySize();
41 }
42 return totalLocation;
43 }
44 // [GL_EXT_shader_io_blocks SPEC Chapter 4.4.1]
45 // Geometry shader inputs, tessellation control shader inputs and outputs, and tessellation
46 // evaluation inputs all have an additional level of arrayness relative to other shader inputs
47 // and outputs. This outer array level is removed from the type before considering how many
48 // locations the type consumes.
49 else if (ignoreVaryingArraySize)
50 {
51 // Array-of-arrays cannot be inputs or outputs of a geometry shader.
52 // (GL_EXT_geometry_shader SPEC issues(5))
53 ASSERT(!varyingType.isArrayOfArrays());
54 return varyingType.getSecondarySize();
55 }
56 else
57 {
58 return varyingType.getSecondarySize() * static_cast<int>(varyingType.getArraySizeProduct());
59 }
60}
61
62using VaryingVector = std::vector<const TIntermSymbol *>;
63
64void ValidateShaderInterface(TDiagnostics *diagnostics,
65 VaryingVector &varyingVector,
66 bool ignoreVaryingArraySize)
67{
68 // Location conflicts can only happen when there are two or more varyings in varyingVector.
69 if (varyingVector.size() <= 1)
70 {
71 return;
72 }
73
74 std::map<int, const TIntermSymbol *> locationMap;
75 for (const TIntermSymbol *varying : varyingVector)
76 {
77 const int location = varying->getType().getLayoutQualifier().location;
78 ASSERT(location >= 0);
79
80 const int elementCount = GetLocationCount(varying, ignoreVaryingArraySize);
81 for (int elementIndex = 0; elementIndex < elementCount; ++elementIndex)
82 {
83 const int offsetLocation = location + elementIndex;
84 if (locationMap.find(offsetLocation) != locationMap.end())
85 {
86 std::stringstream strstr = sh::InitializeStream<std::stringstream>();
87 strstr << "'" << varying->getName()
88 << "' conflicting location with previously defined '"
89 << locationMap[offsetLocation]->getName() << "'";
90 error(*varying, strstr.str().c_str(), diagnostics);
91 }
92 else
93 {
94 locationMap[offsetLocation] = varying;
95 }
96 }
97 }
98}
99
100class ValidateVaryingLocationsTraverser : public TIntermTraverser
101{
102 public:
103 ValidateVaryingLocationsTraverser(GLenum shaderType);
104 void validate(TDiagnostics *diagnostics);
105
106 private:
107 bool visitDeclaration(Visit visit, TIntermDeclaration *node) override;
108 bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override;
109
110 VaryingVector mInputVaryingsWithLocation;
111 VaryingVector mOutputVaryingsWithLocation;
112 GLenum mShaderType;
113};
114
115ValidateVaryingLocationsTraverser::ValidateVaryingLocationsTraverser(GLenum shaderType)
116 : TIntermTraverser(true, false, false), mShaderType(shaderType)
117{}
118
119bool ValidateVaryingLocationsTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node)
120{
121 const TIntermSequence &sequence = *(node->getSequence());
122 ASSERT(!sequence.empty());
123
124 const TIntermSymbol *symbol = sequence.front()->getAsSymbolNode();
125 if (symbol == nullptr)
126 {
127 return false;
128 }
129
130 if (symbol->variable().symbolType() == SymbolType::Empty)
131 {
132 return false;
133 }
134
135 // Collect varyings that have explicit 'location' qualifiers.
136 const TQualifier qualifier = symbol->getQualifier();
137 if (symbol->getType().getLayoutQualifier().location != -1)
138 {
139 if (IsVaryingIn(qualifier))
140 {
141 mInputVaryingsWithLocation.push_back(symbol);
142 }
143 else if (IsVaryingOut(qualifier))
144 {
145 mOutputVaryingsWithLocation.push_back(symbol);
146 }
147 }
148
149 return false;
150}
151
152bool ValidateVaryingLocationsTraverser::visitFunctionDefinition(Visit visit,
153 TIntermFunctionDefinition *node)
154{
155 // We stop traversing function definitions because varyings cannot be defined in a function.
156 return false;
157}
158
159void ValidateVaryingLocationsTraverser::validate(TDiagnostics *diagnostics)
160{
161 ASSERT(diagnostics);
162
163 ValidateShaderInterface(diagnostics, mInputVaryingsWithLocation,
164 mShaderType == GL_GEOMETRY_SHADER_EXT);
165 ValidateShaderInterface(diagnostics, mOutputVaryingsWithLocation, false);
166}
167
168} // anonymous namespace
169
170bool ValidateVaryingLocations(TIntermBlock *root, TDiagnostics *diagnostics, GLenum shaderType)
171{
172 ValidateVaryingLocationsTraverser varyingValidator(shaderType);
173 root->traverse(&varyingValidator);
174 int numErrorsBefore = diagnostics->numErrors();
175 varyingValidator.validate(diagnostics);
176 return (diagnostics->numErrors() == numErrorsBefore);
177}
178
179} // namespace sh