diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.cpp')
-rw-r--r-- | gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.cpp | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.cpp new file mode 100644 index 0000000000..defc59bcc0 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.cpp @@ -0,0 +1,328 @@ +// +// Copyright 2019 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. +// +// RewriteAtomicCounters: Emulate atomic counter buffers with storage buffers. +// + +#include "compiler/translator/tree_ops/RewriteAtomicCounters.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/ImmutableStringBuilder.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/tree_util/ReplaceVariable.h" + +namespace sh +{ +namespace +{ +constexpr ImmutableString kAtomicCountersVarName = ImmutableString("atomicCounters"); +constexpr ImmutableString kAtomicCounterFieldName = ImmutableString("counters"); + +// DeclareAtomicCountersBuffer adds a storage buffer array that's used with atomic counters. +const TVariable *DeclareAtomicCountersBuffers(TIntermBlock *root, TSymbolTable *symbolTable) +{ + // Define `uint counters[];` as the only field in the interface block. + TFieldList *fieldList = new TFieldList; + TType *counterType = new TType(EbtUInt, EbpHigh, EvqGlobal); + counterType->makeArray(0); + + TField *countersField = + new TField(counterType, kAtomicCounterFieldName, TSourceLoc(), SymbolType::AngleInternal); + + fieldList->push_back(countersField); + + TMemoryQualifier coherentMemory = TMemoryQualifier::Create(); + coherentMemory.coherent = true; + + // There are a maximum of 8 atomic counter buffers per IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFERS + // in libANGLE/Constants.h. + constexpr uint32_t kMaxAtomicCounterBuffers = 8; + + // Define a storage block "ANGLEAtomicCounters" with instance name "atomicCounters". + TLayoutQualifier layoutQualifier = TLayoutQualifier::Create(); + layoutQualifier.blockStorage = EbsStd430; + + return DeclareInterfaceBlock(root, symbolTable, fieldList, EvqBuffer, layoutQualifier, + coherentMemory, kMaxAtomicCounterBuffers, + ImmutableString(vk::kAtomicCountersBlockName), + kAtomicCountersVarName); +} + +TIntermTyped *CreateUniformBufferOffset(const TIntermTyped *uniformBufferOffsets, int binding) +{ + // Each uint in the |acbBufferOffsets| uniform contains offsets for 4 bindings. Therefore, the + // expression to get the uniform offset for the binding is: + // + // acbBufferOffsets[binding / 4] >> ((binding % 4) * 8) & 0xFF + + // acbBufferOffsets[binding / 4] + TIntermBinary *uniformBufferOffsetUint = new TIntermBinary( + EOpIndexDirect, uniformBufferOffsets->deepCopy(), CreateIndexNode(binding / 4)); + + // acbBufferOffsets[binding / 4] >> ((binding % 4) * 8) + TIntermBinary *uniformBufferOffsetShifted = uniformBufferOffsetUint; + if (binding % 4 != 0) + { + uniformBufferOffsetShifted = new TIntermBinary(EOpBitShiftRight, uniformBufferOffsetUint, + CreateUIntNode((binding % 4) * 8)); + } + + // acbBufferOffsets[binding / 4] >> ((binding % 4) * 8) & 0xFF + return new TIntermBinary(EOpBitwiseAnd, uniformBufferOffsetShifted, CreateUIntNode(0xFF)); +} + +TIntermBinary *CreateAtomicCounterRef(TIntermTyped *atomicCounterExpression, + const TVariable *atomicCounters, + const TIntermTyped *uniformBufferOffsets) +{ + // The atomic counters storage buffer declaration looks as such: + // + // layout(...) buffer ANGLEAtomicCounters + // { + // uint counters[]; + // } atomicCounters[N]; + // + // Where N is large enough to accommodate atomic counter buffer bindings used in the shader. + // + // This function takes an expression that uses an atomic counter, which can either be: + // + // - ac + // - acArray[index] + // + // Note that RewriteArrayOfArrayOfOpaqueUniforms has already flattened array of array of atomic + // counters. + // + // For the first case (ac), the following code is generated: + // + // atomicCounters[binding].counters[offset] + // + // For the second case (acArray[index]), the following code is generated: + // + // atomicCounters[binding].counters[offset + index] + // + // In either case, an offset given through uniforms is also added to |offset|. The binding is + // necessarily a constant thanks to MonomorphizeUnsupportedFunctions. + + // First determine if there's an index, and extract the atomic counter symbol out of the + // expression. + TIntermSymbol *atomicCounterSymbol = atomicCounterExpression->getAsSymbolNode(); + TIntermTyped *atomicCounterIndex = nullptr; + int atomicCounterConstIndex = 0; + TIntermBinary *asBinary = atomicCounterExpression->getAsBinaryNode(); + if (asBinary != nullptr) + { + atomicCounterSymbol = asBinary->getLeft()->getAsSymbolNode(); + + switch (asBinary->getOp()) + { + case EOpIndexDirect: + atomicCounterConstIndex = asBinary->getRight()->getAsConstantUnion()->getIConst(0); + break; + case EOpIndexIndirect: + atomicCounterIndex = asBinary->getRight(); + break; + default: + UNREACHABLE(); + } + } + + // Extract binding and offset information out of the atomic counter symbol. + ASSERT(atomicCounterSymbol); + const TVariable *atomicCounterVar = &atomicCounterSymbol->variable(); + const TType &atomicCounterType = atomicCounterVar->getType(); + + const int binding = atomicCounterType.getLayoutQualifier().binding; + int offset = atomicCounterType.getLayoutQualifier().offset / 4; + + // Create the expression: + // + // offset + arrayIndex + uniformOffset + // + // If arrayIndex is a constant, it's added with offset right here. + + offset += atomicCounterConstIndex; + + TIntermTyped *index = CreateUniformBufferOffset(uniformBufferOffsets, binding); + if (atomicCounterIndex != nullptr) + { + index = new TIntermBinary(EOpAdd, index, atomicCounterIndex); + } + if (offset != 0) + { + index = new TIntermBinary(EOpAdd, index, CreateIndexNode(offset)); + } + + // Finally, create the complete expression: + // + // atomicCounters[binding].counters[index] + + TIntermSymbol *atomicCountersRef = new TIntermSymbol(atomicCounters); + + // atomicCounters[binding] + TIntermBinary *countersBlock = + new TIntermBinary(EOpIndexDirect, atomicCountersRef, CreateIndexNode(binding)); + + // atomicCounters[binding].counters + TIntermBinary *counters = + new TIntermBinary(EOpIndexDirectInterfaceBlock, countersBlock, CreateIndexNode(0)); + + return new TIntermBinary(EOpIndexIndirect, counters, index); +} + +// Traverser that: +// +// 1. Removes the |uniform atomic_uint| declarations and remembers the binding and offset. +// 2. Substitutes |atomicVar[n]| with |buffer[binding].counters[offset + n]|. +class RewriteAtomicCountersTraverser : public TIntermTraverser +{ + public: + RewriteAtomicCountersTraverser(TSymbolTable *symbolTable, + const TVariable *atomicCounters, + const TIntermTyped *acbBufferOffsets) + : TIntermTraverser(true, false, false, symbolTable), + mAtomicCounters(atomicCounters), + mAcbBufferOffsets(acbBufferOffsets) + {} + + bool visitDeclaration(Visit visit, TIntermDeclaration *node) override + { + if (!mInGlobalScope) + { + return true; + } + + const TIntermSequence &sequence = *(node->getSequence()); + + TIntermTyped *variable = sequence.front()->getAsTyped(); + const TType &type = variable->getType(); + bool isAtomicCounter = type.isAtomicCounter(); + + if (isAtomicCounter) + { + ASSERT(type.getQualifier() == EvqUniform); + TIntermSequence emptySequence; + mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, + std::move(emptySequence)); + + return false; + } + + return true; + } + + bool visitAggregate(Visit visit, TIntermAggregate *node) override + { + if (BuiltInGroup::IsBuiltIn(node->getOp())) + { + bool converted = convertBuiltinFunction(node); + return !converted; + } + + // AST functions don't require modification as atomic counter function parameters are + // removed by MonomorphizeUnsupportedFunctions. + return true; + } + + void visitSymbol(TIntermSymbol *symbol) override + { + // Cannot encounter the atomic counter symbol directly. It can only be used with functions, + // and therefore it's handled by visitAggregate. + ASSERT(!symbol->getType().isAtomicCounter()); + } + + bool visitBinary(Visit visit, TIntermBinary *node) override + { + // Cannot encounter an atomic counter expression directly. It can only be used with + // functions, and therefore it's handled by visitAggregate. + ASSERT(!node->getType().isAtomicCounter()); + return true; + } + + private: + bool convertBuiltinFunction(TIntermAggregate *node) + { + const TOperator op = node->getOp(); + + // If the function is |memoryBarrierAtomicCounter|, simply replace it with + // |memoryBarrierBuffer|. + if (op == EOpMemoryBarrierAtomicCounter) + { + TIntermSequence emptySequence; + TIntermTyped *substituteCall = CreateBuiltInFunctionCallNode( + "memoryBarrierBuffer", &emptySequence, *mSymbolTable, 310); + queueReplacement(substituteCall, OriginalNode::IS_DROPPED); + return true; + } + + // If it's an |atomicCounter*| function, replace the function with an |atomic*| equivalent. + if (!node->getFunction()->isAtomicCounterFunction()) + { + return false; + } + + // Note: atomicAdd(0) is used for atomic reads. + uint32_t valueChange = 0; + constexpr char kAtomicAddFunction[] = "atomicAdd"; + bool isDecrement = false; + + if (op == EOpAtomicCounterIncrement) + { + valueChange = 1; + } + else if (op == EOpAtomicCounterDecrement) + { + // uint values are required to wrap around, so 0xFFFFFFFFu is used as -1. + valueChange = std::numeric_limits<uint32_t>::max(); + static_assert(static_cast<uint32_t>(-1) == std::numeric_limits<uint32_t>::max(), + "uint32_t max is not -1"); + + isDecrement = true; + } + else + { + ASSERT(op == EOpAtomicCounter); + } + + TIntermTyped *param = (*node->getSequence())[0]->getAsTyped(); + + TIntermSequence substituteArguments; + substituteArguments.push_back( + CreateAtomicCounterRef(param, mAtomicCounters, mAcbBufferOffsets)); + substituteArguments.push_back(CreateUIntNode(valueChange)); + + TIntermTyped *substituteCall = CreateBuiltInFunctionCallNode( + kAtomicAddFunction, &substituteArguments, *mSymbolTable, 310); + + // Note that atomicCounterDecrement returns the *new* value instead of the prior value, + // unlike atomicAdd. So we need to do a -1 on the result as well. + if (isDecrement) + { + substituteCall = new TIntermBinary(EOpSub, substituteCall, CreateUIntNode(1)); + } + + queueReplacement(substituteCall, OriginalNode::IS_DROPPED); + return true; + } + + const TVariable *mAtomicCounters; + const TIntermTyped *mAcbBufferOffsets; +}; + +} // anonymous namespace + +bool RewriteAtomicCounters(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + const TIntermTyped *acbBufferOffsets) +{ + const TVariable *atomicCounters = DeclareAtomicCountersBuffers(root, symbolTable); + + RewriteAtomicCountersTraverser traverser(symbolTable, atomicCounters, acbBufferOffsets); + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} +} // namespace sh |