| // |
| // 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 |