Source code

Revision control

Copy as Markdown

Other Tools

//
// Copyright 2021 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.
//
// ClampIndirectIndices.h: Add clamp to the indirect indices used on arrays.
//
#include "compiler/translator/tree_ops/ClampIndirectIndices.h"
#include "compiler/translator/Compiler.h"
#include "compiler/translator/StaticType.h"
#include "compiler/translator/SymbolTable.h"
#include "compiler/translator/tree_util/BuiltIn.h"
#include "compiler/translator/tree_util/IntermNode_util.h"
#include "compiler/translator/tree_util/IntermTraverse.h"
namespace sh
{
namespace
{
enum class ExtDrawBuffers
{
Disabled,
Enabled,
};
bool ClampIndirectIndicesImpl(TCompiler *compiler,
TIntermNode *root,
TSymbolTable *symbolTable,
ExtDrawBuffers extDrawBuffers);
// Traverser that finds EOpIndexIndirect nodes and applies a clamp to their right-hand side
// expression.
class ClampIndirectIndicesTraverser : public TIntermTraverser
{
public:
ClampIndirectIndicesTraverser(TCompiler *compiler,
TSymbolTable *symbolTable,
ExtDrawBuffers extDrawBuffers)
: TIntermTraverser(true, false, false, symbolTable),
mCompiler(compiler),
mExtDrawBuffers(extDrawBuffers)
{
mIsSecondaryFragDataUsed = symbolTable->isSecondaryFragDataUsed();
}
bool visitBinary(Visit visit, TIntermBinary *node) override
{
ASSERT(visit == PreVisit);
// Only interested in EOpIndexIndirect nodes.
if (node->getOp() != EOpIndexIndirect)
{
return true;
}
// Apply the transformation to the left and right nodes
bool valid =
ClampIndirectIndicesImpl(mCompiler, node->getLeft(), mSymbolTable, mExtDrawBuffers);
ASSERT(valid);
valid =
ClampIndirectIndicesImpl(mCompiler, node->getRight(), mSymbolTable, mExtDrawBuffers);
ASSERT(valid);
// Generate clamp(right, 0, N), where N is the size of the array being indexed minus 1. If
// the array is runtime-sized, the length() method is called on it.
const TType &leftType = node->getLeft()->getType();
const TType &rightType = node->getRight()->getType();
// Don't clamp indirect indices on unsized arrays in buffer blocks. They are covered by the
// relevant robust access behavior of the backend.
if (leftType.isUnsizedArray())
{
return true;
}
// On GLSL es 100, clamp is only defined for float, so float arguments are used.
//
// However, float clamp is unconditionally emitted to workaround driver bugs with integer
// clamp on Qualcomm. http://crbug.com/1217167
//
// const bool useFloatClamp = mCompiler->getShaderVersion() == 100;
const bool useFloatClamp = true;
TIntermConstantUnion *zero = createClampValue(0, useFloatClamp);
TIntermTyped *max;
if (leftType.isArray())
{
int arraySize = static_cast<int>(leftType.getOutermostArraySize());
if (leftType.getQualifier() == EvqFragData &&
mExtDrawBuffers == ExtDrawBuffers::Disabled)
{
// When EXT_draw_buffers is disabled, only element 0 of gl_FragData may be accessed.
arraySize = 1;
}
else if (leftType.getQualifier() == EvqFragData && mIsSecondaryFragDataUsed)
{
// When gl_SecondaryFragDataEXT is used, only indices up to MaxDualSourceDrawBuffers
// of gl_FragData may be accessed.
arraySize =
std::min(arraySize, mCompiler->getBuiltInResources().MaxDualSourceDrawBuffers);
}
max = createClampValue(arraySize - 1, useFloatClamp);
}
else
{
ASSERT(leftType.isVector() || leftType.isMatrix());
max = createClampValue(leftType.getNominalSize() - 1, useFloatClamp);
}
TIntermTyped *index = node->getRight();
// If the index node is not an int (i.e. it's a uint), or a float (if using float clamp),
// cast it.
const TBasicType requiredBasicType = useFloatClamp ? EbtFloat : EbtInt;
if (rightType.getBasicType() != requiredBasicType)
{
const TType *clampType = useFloatClamp ? StaticType::GetBasic<EbtFloat, EbpHigh>()
: StaticType::GetBasic<EbtInt, EbpHigh>();
TIntermSequence constructorArgs = {index};
index = TIntermAggregate::CreateConstructor(*clampType, &constructorArgs);
}
// clamp(index, zero, max)
TIntermSequence args;
args.push_back(index);
args.push_back(zero);
args.push_back(max);
TIntermTyped *clamped =
CreateBuiltInFunctionCallNode("clamp", &args, *mSymbolTable, useFloatClamp ? 100 : 300);
// Cast back to int if float clamp was used.
if (useFloatClamp)
{
TIntermSequence constructorArgs = {clamped};
clamped = TIntermAggregate::CreateConstructor(*StaticType::GetBasic<EbtInt, EbpHigh>(),
&constructorArgs);
}
// Replace the right node (the index) with the clamped result.
queueReplacementWithParent(node, node->getRight(), clamped, OriginalNode::IS_DROPPED);
// Don't recurse as left and right nodes are already processed.
return false;
}
private:
TIntermConstantUnion *createClampValue(int value, bool useFloat)
{
if (useFloat)
{
return CreateFloatNode(static_cast<float>(value), EbpHigh);
}
return CreateIndexNode(value);
}
TCompiler *mCompiler;
const ExtDrawBuffers mExtDrawBuffers;
bool mIsSecondaryFragDataUsed = false;
};
bool ClampIndirectIndicesImpl(TCompiler *compiler,
TIntermNode *root,
TSymbolTable *symbolTable,
ExtDrawBuffers extDrawBuffers)
{
ClampIndirectIndicesTraverser traverser(compiler, symbolTable, extDrawBuffers);
root->traverse(&traverser);
return traverser.updateTree(compiler, root);
}
} // anonymous namespace
bool ClampIndirectIndices(TCompiler *compiler,
TIntermNode *root,
TSymbolTable *symbolTable,
const TExtensionBehavior &extensionBehavior)
{
const ExtDrawBuffers extDrawBuffers =
IsExtensionEnabled(extensionBehavior, TExtension::EXT_draw_buffers)
? ExtDrawBuffers::Enabled
: ExtDrawBuffers::Disabled;
return ClampIndirectIndicesImpl(compiler, root, symbolTable, extDrawBuffers);
}
} // namespace sh