| // | |
| // Copyright 2013 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. | |
| // | |
| // ValidateOutputs validates fragment shader outputs. It checks for conflicting locations, | |
| // out-of-range locations, that locations are specified when using multiple outputs, and YUV output | |
| // validity. | |
| #include "compiler/translator/ValidateOutputs.h" | |
| #include <set> | |
| #include "compiler/translator/InfoSink.h" | |
| #include "compiler/translator/ParseContext.h" | |
| #include "compiler/translator/tree_util/IntermTraverse.h" | |
| namespace sh | |
| { | |
| namespace | |
| { | |
| void error(const TIntermSymbol &symbol, const char *reason, TDiagnostics *diagnostics) | |
| { | |
| diagnostics->error(symbol.getLine(), reason, symbol.getName().data()); | |
| } | |
| class ValidateOutputsTraverser : public TIntermTraverser | |
| { | |
| public: | |
| ValidateOutputsTraverser(const TExtensionBehavior &extBehavior, int maxDrawBuffers); | |
| void validate(TDiagnostics *diagnostics) const; | |
| void visitSymbol(TIntermSymbol *) override; | |
| private: | |
| int mMaxDrawBuffers; | |
| bool mAllowUnspecifiedOutputLocationResolution; | |
| bool mUsesFragDepth; | |
| typedef std::vector<TIntermSymbol *> OutputVector; | |
| OutputVector mOutputs; | |
| OutputVector mUnspecifiedLocationOutputs; | |
| OutputVector mYuvOutputs; | |
| std::set<int> mVisitedSymbols; // Visited symbol ids. | |
| }; | |
| ValidateOutputsTraverser::ValidateOutputsTraverser(const TExtensionBehavior &extBehavior, | |
| int maxDrawBuffers) | |
| : TIntermTraverser(true, false, false), | |
| mMaxDrawBuffers(maxDrawBuffers), | |
| mAllowUnspecifiedOutputLocationResolution( | |
| IsExtensionEnabled(extBehavior, TExtension::EXT_blend_func_extended)), | |
| mUsesFragDepth(false) | |
| {} | |
| void ValidateOutputsTraverser::visitSymbol(TIntermSymbol *symbol) | |
| { | |
| if (symbol->variable().symbolType() == SymbolType::Empty) | |
| return; | |
| if (mVisitedSymbols.count(symbol->uniqueId().get()) == 1) | |
| return; | |
| mVisitedSymbols.insert(symbol->uniqueId().get()); | |
| TQualifier qualifier = symbol->getQualifier(); | |
| if (qualifier == EvqFragmentOut) | |
| { | |
| if (symbol->getType().getLayoutQualifier().location != -1) | |
| { | |
| mOutputs.push_back(symbol); | |
| } | |
| else if (symbol->getType().getLayoutQualifier().yuv == true) | |
| { | |
| mYuvOutputs.push_back(symbol); | |
| } | |
| else | |
| { | |
| mUnspecifiedLocationOutputs.push_back(symbol); | |
| } | |
| } | |
| else if (qualifier == EvqFragDepth || qualifier == EvqFragDepthEXT) | |
| { | |
| mUsesFragDepth = true; | |
| } | |
| } | |
| void ValidateOutputsTraverser::validate(TDiagnostics *diagnostics) const | |
| { | |
| ASSERT(diagnostics); | |
| OutputVector validOutputs(mMaxDrawBuffers, nullptr); | |
| OutputVector validSecondaryOutputs(mMaxDrawBuffers, nullptr); | |
| for (const auto &symbol : mOutputs) | |
| { | |
| const TType &type = symbol->getType(); | |
| ASSERT(!type.isArrayOfArrays()); // Disallowed in GLSL ES 3.10 section 4.3.6. | |
| const size_t elementCount = | |
| static_cast<size_t>(type.isArray() ? type.getOutermostArraySize() : 1u); | |
| const size_t location = static_cast<size_t>(type.getLayoutQualifier().location); | |
| ASSERT(type.getLayoutQualifier().location != -1); | |
| OutputVector *validOutputsToUse = &validOutputs; | |
| // The default index is 0, so we only assign the output to secondary outputs in case the | |
| // index is explicitly set to 1. | |
| if (type.getLayoutQualifier().index == 1) | |
| { | |
| validOutputsToUse = &validSecondaryOutputs; | |
| } | |
| if (location + elementCount <= validOutputsToUse->size()) | |
| { | |
| for (size_t elementIndex = 0; elementIndex < elementCount; elementIndex++) | |
| { | |
| const size_t offsetLocation = location + elementIndex; | |
| if ((*validOutputsToUse)[offsetLocation]) | |
| { | |
| std::stringstream strstr = sh::InitializeStream<std::stringstream>(); | |
| strstr << "conflicting output locations with previously defined output '" | |
| << (*validOutputsToUse)[offsetLocation]->getName() << "'"; | |
| error(*symbol, strstr.str().c_str(), diagnostics); | |
| } | |
| else | |
| { | |
| (*validOutputsToUse)[offsetLocation] = symbol; | |
| } | |
| } | |
| } | |
| else | |
| { | |
| if (elementCount > 0) | |
| { | |
| error(*symbol, | |
| elementCount > 1 ? "output array locations would exceed MAX_DRAW_BUFFERS" | |
| : "output location must be < MAX_DRAW_BUFFERS", | |
| diagnostics); | |
| } | |
| } | |
| } | |
| if (!mAllowUnspecifiedOutputLocationResolution && | |
| ((!mOutputs.empty() && !mUnspecifiedLocationOutputs.empty()) || | |
| mUnspecifiedLocationOutputs.size() > 1)) | |
| { | |
| for (const auto &symbol : mUnspecifiedLocationOutputs) | |
| { | |
| error(*symbol, | |
| "must explicitly specify all locations when using multiple fragment outputs", | |
| diagnostics); | |
| } | |
| } | |
| if (!mYuvOutputs.empty() && (mYuvOutputs.size() > 1 || mUsesFragDepth || !mOutputs.empty() || | |
| !mUnspecifiedLocationOutputs.empty())) | |
| { | |
| for (const auto &symbol : mYuvOutputs) | |
| { | |
| error(*symbol, | |
| "not allowed to specify yuv qualifier when using depth or multiple color " | |
| "fragment outputs", | |
| diagnostics); | |
| } | |
| } | |
| } | |
| } // anonymous namespace | |
| bool ValidateOutputs(TIntermBlock *root, | |
| const TExtensionBehavior &extBehavior, | |
| int maxDrawBuffers, | |
| TDiagnostics *diagnostics) | |
| { | |
| ValidateOutputsTraverser validateOutputs(extBehavior, maxDrawBuffers); | |
| root->traverse(&validateOutputs); | |
| int numErrorsBefore = diagnostics->numErrors(); | |
| validateOutputs.validate(diagnostics); | |
| return (diagnostics->numErrors() == numErrorsBefore); | |
| } | |
| } // namespace sh |