blob: 963fe2ecf0f4552f10f8c17039acdce33216d7ab [file] [log] [blame]
//
// Copyright 2020 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 ValidateClipCullDistance function:
// * gathers clip/cull distance usages
// * checks if the sum of array sizes for gl_ClipDistance and
// gl_CullDistance exceeds gl_MaxCombinedClipAndCullDistances
// * checks if length() operator is used correctly
// * adds an explicit clip/cull distance declaration
//
#include "ValidateClipCullDistance.h"
#include "compiler/translator/Diagnostics.h"
#include "compiler/translator/SymbolTable.h"
#include "compiler/translator/tree_util/IntermTraverse.h"
#include "compiler/translator/tree_util/ReplaceVariable.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());
}
class ValidateClipCullDistanceTraverser : public TIntermTraverser
{
public:
ValidateClipCullDistanceTraverser();
void validate(TDiagnostics *diagnostics,
const unsigned int maxCombinedClipAndCullDistances,
uint8_t *clipDistanceSizeOut,
uint8_t *cullDistanceSizeOut,
bool *clipDistanceRedeclaredOut,
bool *cullDistanceRedeclaredOut,
bool *clipDistanceUsedOut);
private:
bool visitDeclaration(Visit visit, TIntermDeclaration *node) override;
bool visitBinary(Visit visit, TIntermBinary *node) override;
uint8_t mClipDistanceSize;
uint8_t mCullDistanceSize;
int8_t mMaxClipDistanceIndex;
int8_t mMaxCullDistanceIndex;
bool mHasNonConstClipDistanceIndex;
bool mHasNonConstCullDistanceIndex;
const TIntermSymbol *mClipDistance;
const TIntermSymbol *mCullDistance;
};
ValidateClipCullDistanceTraverser::ValidateClipCullDistanceTraverser()
: TIntermTraverser(true, false, false),
mClipDistanceSize(0),
mCullDistanceSize(0),
mMaxClipDistanceIndex(-1),
mMaxCullDistanceIndex(-1),
mHasNonConstClipDistanceIndex(false),
mHasNonConstCullDistanceIndex(false),
mClipDistance(nullptr),
mCullDistance(nullptr)
{}
bool ValidateClipCullDistanceTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node)
{
const TIntermSequence &sequence = *(node->getSequence());
if (sequence.size() != 1)
{
return true;
}
const TIntermSymbol *symbol = sequence.front()->getAsSymbolNode();
if (symbol == nullptr)
{
return true;
}
if (symbol->getName() == "gl_ClipDistance")
{
mClipDistanceSize = static_cast<uint8_t>(symbol->getOutermostArraySize());
mClipDistance = symbol;
}
else if (symbol->getName() == "gl_CullDistance")
{
mCullDistanceSize = static_cast<uint8_t>(symbol->getOutermostArraySize());
mCullDistance = symbol;
}
return true;
}
bool ValidateClipCullDistanceTraverser::visitBinary(Visit visit, TIntermBinary *node)
{
TOperator op = node->getOp();
if (op != EOpIndexDirect && op != EOpIndexIndirect)
{
return true;
}
TIntermSymbol *left = node->getLeft()->getAsSymbolNode();
if (!left)
{
return true;
}
ImmutableString varName(left->getName());
if (varName != "gl_ClipDistance" && varName != "gl_CullDistance")
{
return true;
}
const TConstantUnion *constIdx = node->getRight()->getConstantValue();
if (constIdx)
{
int idx = 0;
switch (constIdx->getType())
{
case EbtInt:
idx = constIdx->getIConst();
break;
case EbtUInt:
idx = constIdx->getUConst();
break;
default:
UNREACHABLE();
break;
}
if (varName == "gl_ClipDistance")
{
if (idx > mMaxClipDistanceIndex)
{
mMaxClipDistanceIndex = static_cast<int8_t>(idx);
if (!mClipDistance)
{
mClipDistance = left;
}
}
}
else
{
ASSERT(varName == "gl_CullDistance");
if (idx > mMaxCullDistanceIndex)
{
mMaxCullDistanceIndex = static_cast<int8_t>(idx);
if (!mCullDistance)
{
mCullDistance = left;
}
}
}
}
else
{
if (varName == "gl_ClipDistance")
{
mHasNonConstClipDistanceIndex = true;
if (!mClipDistance)
{
mClipDistance = left;
}
}
else
{
ASSERT(varName == "gl_CullDistance");
mHasNonConstCullDistanceIndex = true;
if (!mCullDistance)
{
mCullDistance = left;
}
}
}
return true;
}
void ValidateClipCullDistanceTraverser::validate(TDiagnostics *diagnostics,
const unsigned int maxCombinedClipAndCullDistances,
uint8_t *clipDistanceSizeOut,
uint8_t *cullDistanceSizeOut,
bool *clipDistanceRedeclaredOut,
bool *cullDistanceRedeclaredOut,
bool *clipDistanceUsedOut)
{
ASSERT(diagnostics);
if (mClipDistanceSize == 0 && mHasNonConstClipDistanceIndex)
{
error(*mClipDistance,
"The array must be sized by the shader either redeclaring it with a size or "
"indexing it only with constant integral expressions",
diagnostics);
}
if (mCullDistanceSize == 0 && mHasNonConstCullDistanceIndex)
{
error(*mCullDistance,
"The array must be sized by the shader either redeclaring it with a size or "
"indexing it only with constant integral expressions",
diagnostics);
}
unsigned int enabledClipDistances =
(mClipDistanceSize > 0 ? mClipDistanceSize
: (mClipDistance ? mMaxClipDistanceIndex + 1 : 0));
unsigned int enabledCullDistances =
(mCullDistanceSize > 0 ? mCullDistanceSize
: (mCullDistance ? mMaxCullDistanceIndex + 1 : 0));
unsigned int combinedClipAndCullDistances =
(enabledClipDistances > 0 && enabledCullDistances > 0
? enabledClipDistances + enabledCullDistances
: 0);
// When cull distances are not supported, i.e., when GL_ANGLE_clip_cull_distance is
// exposed but GL_EXT_clip_cull_distance is not exposed, the combined limit is 0.
if (enabledCullDistances > 0 && maxCombinedClipAndCullDistances == 0)
{
error(*mCullDistance, "Cull distance functionality is not available", diagnostics);
}
if (combinedClipAndCullDistances > maxCombinedClipAndCullDistances)
{
const TIntermSymbol *greaterSymbol =
(enabledClipDistances >= enabledCullDistances ? mClipDistance : mCullDistance);
std::stringstream strstr = sh::InitializeStream<std::stringstream>();
strstr << "The sum of 'gl_ClipDistance' and 'gl_CullDistance' size is greater than "
"gl_MaxCombinedClipAndCullDistances ("
<< combinedClipAndCullDistances << " > " << maxCombinedClipAndCullDistances << ")";
error(*greaterSymbol, strstr.str().c_str(), diagnostics);
}
// Update the compiler state
*clipDistanceSizeOut = mClipDistanceSize ? mClipDistanceSize : (mMaxClipDistanceIndex + 1);
*cullDistanceSizeOut = mCullDistanceSize ? mCullDistanceSize : (mMaxCullDistanceIndex + 1);
*clipDistanceRedeclaredOut = mClipDistanceSize != 0;
*cullDistanceRedeclaredOut = mCullDistanceSize != 0;
*clipDistanceUsedOut = (mMaxClipDistanceIndex != -1) || mHasNonConstClipDistanceIndex;
}
class ValidateClipCullDistanceLengthTraverser : public TIntermTraverser
{
public:
ValidateClipCullDistanceLengthTraverser(TDiagnostics *diagnostics,
uint8_t clipDistanceSized,
uint8_t cullDistanceSized);
private:
bool visitUnary(Visit visit, TIntermUnary *node) override;
TDiagnostics *mDiagnostics;
const bool mClipDistanceSized;
const bool mCullDistanceSized;
};
ValidateClipCullDistanceLengthTraverser::ValidateClipCullDistanceLengthTraverser(
TDiagnostics *diagnostics,
uint8_t clipDistanceSize,
uint8_t cullDistanceSize)
: TIntermTraverser(true, false, false),
mDiagnostics(diagnostics),
mClipDistanceSized(clipDistanceSize > 0),
mCullDistanceSized(cullDistanceSize > 0)
{}
bool ValidateClipCullDistanceLengthTraverser::visitUnary(Visit visit, TIntermUnary *node)
{
if (node->getOp() == EOpArrayLength)
{
TIntermTyped *operand = node->getOperand();
if ((operand->getQualifier() == EvqClipDistance && !mClipDistanceSized) ||
(operand->getQualifier() == EvqCullDistance && !mCullDistanceSized))
{
error(*operand->getAsSymbolNode(),
"The length() method cannot be called on an array that is not "
"runtime sized and also has not yet been explicitly sized",
mDiagnostics);
}
}
return true;
}
bool ReplaceAndDeclareVariable(TCompiler *compiler,
TIntermBlock *root,
const ImmutableString &name,
unsigned int size)
{
const TVariable *var = static_cast<const TVariable *>(
compiler->getSymbolTable().findBuiltIn(name, compiler->getShaderVersion()));
ASSERT(var != nullptr);
if (size != var->getType().getOutermostArraySize())
{
TType *resizedType = new TType(var->getType());
resizedType->setArraySize(0, size);
TVariable *resizedVar =
new TVariable(&compiler->getSymbolTable(), name, resizedType, SymbolType::BuiltIn);
if (!ReplaceVariable(compiler, root, var, resizedVar))
{
return false;
}
var = resizedVar;
}
TIntermDeclaration *globalDecl = new TIntermDeclaration();
globalDecl->appendDeclarator(new TIntermSymbol(var));
root->insertStatement(0, globalDecl);
return true;
}
} // anonymous namespace
bool ValidateClipCullDistance(TCompiler *compiler,
TIntermBlock *root,
TDiagnostics *diagnostics,
const unsigned int maxCombinedClipAndCullDistances,
uint8_t *clipDistanceSizeOut,
uint8_t *cullDistanceSizeOut,
bool *clipDistanceUsedOut)
{
ValidateClipCullDistanceTraverser varyingValidator;
root->traverse(&varyingValidator);
int numErrorsBefore = diagnostics->numErrors();
bool clipDistanceRedeclared;
bool cullDistanceRedeclared;
varyingValidator.validate(diagnostics, maxCombinedClipAndCullDistances, clipDistanceSizeOut,
cullDistanceSizeOut, &clipDistanceRedeclared, &cullDistanceRedeclared,
clipDistanceUsedOut);
ValidateClipCullDistanceLengthTraverser lengthValidator(diagnostics, *clipDistanceSizeOut,
*cullDistanceSizeOut);
root->traverse(&lengthValidator);
if (diagnostics->numErrors() != numErrorsBefore)
{
return false;
}
// If the clip/cull distance variables are not explicitly redeclared in the incoming shader,
// redeclare them to ensure that various pruning passes will not cause inconsistent AST state.
if (*clipDistanceSizeOut > 0 && !clipDistanceRedeclared &&
!ReplaceAndDeclareVariable(compiler, root, ImmutableString("gl_ClipDistance"),
*clipDistanceSizeOut))
{
return false;
}
if (*cullDistanceSizeOut > 0 && !cullDistanceRedeclared &&
!ReplaceAndDeclareVariable(compiler, root, ImmutableString("gl_CullDistance"),
*cullDistanceSizeOut))
{
return false;
}
return true;
}
} // namespace sh