1//
2// Copyright (c) 2013 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// ValidateOutputs validates fragment shader outputs. It checks for conflicting locations,
7// out-of-range locations, that locations are specified when using multiple outputs, and YUV output
8// validity.
9
10#include "compiler/translator/ValidateOutputs.h"
11
12#include <set>
13
14#include "compiler/translator/InfoSink.h"
15#include "compiler/translator/ParseContext.h"
16#include "compiler/translator/tree_util/IntermTraverse.h"
17
18namespace sh
19{
20
21namespace
22{
23
24void error(const TIntermSymbol &symbol, const char *reason, TDiagnostics *diagnostics)
25{
26 diagnostics->error(symbol.getLine(), reason, symbol.getName().data());
27}
28
29class ValidateOutputsTraverser : public TIntermTraverser
30{
31 public:
32 ValidateOutputsTraverser(const TExtensionBehavior &extBehavior, int maxDrawBuffers);
33
34 void validate(TDiagnostics *diagnostics) const;
35
36 void visitSymbol(TIntermSymbol *) override;
37
38 private:
39 int mMaxDrawBuffers;
40 bool mAllowUnspecifiedOutputLocationResolution;
41 bool mUsesFragDepth;
42
43 typedef std::vector<TIntermSymbol *> OutputVector;
44 OutputVector mOutputs;
45 OutputVector mUnspecifiedLocationOutputs;
46 OutputVector mYuvOutputs;
47 std::set<int> mVisitedSymbols; // Visited symbol ids.
48};
49
50ValidateOutputsTraverser::ValidateOutputsTraverser(const TExtensionBehavior &extBehavior,
51 int maxDrawBuffers)
52 : TIntermTraverser(true, false, false),
53 mMaxDrawBuffers(maxDrawBuffers),
54 mAllowUnspecifiedOutputLocationResolution(
55 IsExtensionEnabled(extBehavior, TExtension::EXT_blend_func_extended)),
56 mUsesFragDepth(false)
57{}
58
59void ValidateOutputsTraverser::visitSymbol(TIntermSymbol *symbol)
60{
61 if (symbol->variable().symbolType() == SymbolType::Empty)
62 return;
63
64 if (mVisitedSymbols.count(symbol->uniqueId().get()) == 1)
65 return;
66
67 mVisitedSymbols.insert(symbol->uniqueId().get());
68
69 TQualifier qualifier = symbol->getQualifier();
70 if (qualifier == EvqFragmentOut)
71 {
72 if (symbol->getType().getLayoutQualifier().location != -1)
73 {
74 mOutputs.push_back(symbol);
75 }
76 else if (symbol->getType().getLayoutQualifier().yuv == true)
77 {
78 mYuvOutputs.push_back(symbol);
79 }
80 else
81 {
82 mUnspecifiedLocationOutputs.push_back(symbol);
83 }
84 }
85 else if (qualifier == EvqFragDepth || qualifier == EvqFragDepthEXT)
86 {
87 mUsesFragDepth = true;
88 }
89}
90
91void ValidateOutputsTraverser::validate(TDiagnostics *diagnostics) const
92{
93 ASSERT(diagnostics);
94 OutputVector validOutputs(mMaxDrawBuffers, nullptr);
95 OutputVector validSecondaryOutputs(mMaxDrawBuffers, nullptr);
96
97 for (const auto &symbol : mOutputs)
98 {
99 const TType &type = symbol->getType();
100 ASSERT(!type.isArrayOfArrays()); // Disallowed in GLSL ES 3.10 section 4.3.6.
101 const size_t elementCount =
102 static_cast<size_t>(type.isArray() ? type.getOutermostArraySize() : 1u);
103 const size_t location = static_cast<size_t>(type.getLayoutQualifier().location);
104
105 ASSERT(type.getLayoutQualifier().location != -1);
106
107 OutputVector *validOutputsToUse = &validOutputs;
108 // The default index is 0, so we only assign the output to secondary outputs in case the
109 // index is explicitly set to 1.
110 if (type.getLayoutQualifier().index == 1)
111 {
112 validOutputsToUse = &validSecondaryOutputs;
113 }
114
115 if (location + elementCount <= validOutputsToUse->size())
116 {
117 for (size_t elementIndex = 0; elementIndex < elementCount; elementIndex++)
118 {
119 const size_t offsetLocation = location + elementIndex;
120 if ((*validOutputsToUse)[offsetLocation])
121 {
122 std::stringstream strstr = sh::InitializeStream<std::stringstream>();
123 strstr << "conflicting output locations with previously defined output '"
124 << (*validOutputsToUse)[offsetLocation]->getName() << "'";
125 error(*symbol, strstr.str().c_str(), diagnostics);
126 }
127 else
128 {
129 (*validOutputsToUse)[offsetLocation] = symbol;
130 }
131 }
132 }
133 else
134 {
135 if (elementCount > 0)
136 {
137 error(*symbol,
138 elementCount > 1 ? "output array locations would exceed MAX_DRAW_BUFFERS"
139 : "output location must be < MAX_DRAW_BUFFERS",
140 diagnostics);
141 }
142 }
143 }
144
145 if (!mAllowUnspecifiedOutputLocationResolution &&
146 ((!mOutputs.empty() && !mUnspecifiedLocationOutputs.empty()) ||
147 mUnspecifiedLocationOutputs.size() > 1))
148 {
149 for (const auto &symbol : mUnspecifiedLocationOutputs)
150 {
151 error(*symbol,
152 "must explicitly specify all locations when using multiple fragment outputs",
153 diagnostics);
154 }
155 }
156
157 if (!mYuvOutputs.empty() && (mYuvOutputs.size() > 1 || mUsesFragDepth || !mOutputs.empty() ||
158 !mUnspecifiedLocationOutputs.empty()))
159 {
160 for (const auto &symbol : mYuvOutputs)
161 {
162 error(*symbol,
163 "not allowed to specify yuv qualifier when using depth or multiple color "
164 "fragment outputs",
165 diagnostics);
166 }
167 }
168}
169
170} // anonymous namespace
171
172bool ValidateOutputs(TIntermBlock *root,
173 const TExtensionBehavior &extBehavior,
174 int maxDrawBuffers,
175 TDiagnostics *diagnostics)
176{
177 ValidateOutputsTraverser validateOutputs(extBehavior, maxDrawBuffers);
178 root->traverse(&validateOutputs);
179 int numErrorsBefore = diagnostics->numErrors();
180 validateOutputs.validate(diagnostics);
181 return (diagnostics->numErrors() == numErrorsBefore);
182}
183
184} // namespace sh
185