blob: c92f054f875f9ed204ebb9145eef8b32c19d58ae [file] [log] [blame]
//
// 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