| // | |
| // 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/StaticType.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 kAtomicCounterTypeName = ImmutableString("ANGLE_atomic_uint"); | |
| constexpr ImmutableString kAtomicCounterBlockName = ImmutableString("ANGLEAtomicCounters"); | |
| constexpr ImmutableString kAtomicCounterVarName = 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); | |
| 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". | |
| return DeclareInterfaceBlock(root, symbolTable, fieldList, EvqBuffer, coherentMemory, | |
| kMaxAtomicCounterBuffers, kAtomicCounterBlockName, | |
| kAtomicCounterVarName); | |
| } | |
| TIntermConstantUnion *CreateUIntConstant(uint32_t value) | |
| { | |
| TType *constantType = new TType(*StaticType::GetBasic<EbtUInt, 1>()); | |
| constantType->setQualifier(EvqConst); | |
| TConstantUnion *constantValue = new TConstantUnion; | |
| constantValue->setUConst(value); | |
| return new TIntermConstantUnion(constantValue, *constantType); | |
| } | |
| TIntermTyped *CreateAtomicCounterConstant(TType *atomicCounterType, | |
| uint32_t binding, | |
| uint32_t offset) | |
| { | |
| ASSERT(atomicCounterType->getBasicType() == EbtStruct); | |
| TIntermSequence *arguments = new TIntermSequence(); | |
| arguments->push_back(CreateUIntConstant(binding)); | |
| arguments->push_back(CreateUIntConstant(offset)); | |
| return TIntermAggregate::CreateConstructor(*atomicCounterType, arguments); | |
| } | |
| TIntermBinary *CreateAtomicCounterRef(const TVariable *atomicCounters, | |
| const TIntermTyped *bindingOffset, | |
| const TIntermTyped *bufferOffsets) | |
| { | |
| // 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. | |
| // | |
| // Given an ANGLEAtomicCounter variable (which is a struct of {binding, offset}), we need to | |
| // return: | |
| // | |
| // atomicCounters[binding].counters[offset] | |
| // | |
| // The offset itself is the provided one plus an offset given through uniforms. | |
| TIntermSymbol *atomicCountersRef = new TIntermSymbol(atomicCounters); | |
| TIntermConstantUnion *bindingFieldRef = CreateIndexNode(0); | |
| TIntermConstantUnion *offsetFieldRef = CreateIndexNode(1); | |
| TIntermConstantUnion *countersFieldRef = CreateIndexNode(0); | |
| // Create references to bindingOffset.binding and bindingOffset.offset. | |
| TIntermBinary *binding = | |
| new TIntermBinary(EOpIndexDirectStruct, bindingOffset->deepCopy(), bindingFieldRef); | |
| TIntermBinary *offset = | |
| new TIntermBinary(EOpIndexDirectStruct, bindingOffset->deepCopy(), offsetFieldRef); | |
| // Create reference to atomicCounters[bindingOffset.binding] | |
| TIntermBinary *countersBlock = new TIntermBinary(EOpIndexDirect, atomicCountersRef, binding); | |
| // Create reference to atomicCounters[bindingOffset.binding].counters | |
| TIntermBinary *counters = | |
| new TIntermBinary(EOpIndexDirectInterfaceBlock, countersBlock, countersFieldRef); | |
| // Create bufferOffsets[binding / 4]. Each uint in bufferOffsets contains offsets for 4 | |
| // bindings. | |
| TIntermBinary *bindingDivFour = | |
| new TIntermBinary(EOpDiv, binding->deepCopy(), CreateUIntConstant(4)); | |
| TIntermBinary *bufferOffsetUint = | |
| new TIntermBinary(EOpIndexDirect, bufferOffsets->deepCopy(), bindingDivFour); | |
| // Create (binding % 4) * 8 | |
| TIntermBinary *bindingModFour = | |
| new TIntermBinary(EOpIMod, binding->deepCopy(), CreateUIntConstant(4)); | |
| TIntermBinary *bufferOffsetShift = | |
| new TIntermBinary(EOpMul, bindingModFour, CreateUIntConstant(8)); | |
| // Create bufferOffsets[binding / 4] >> ((binding % 4) * 8) & 0xFF | |
| TIntermBinary *bufferOffsetShifted = | |
| new TIntermBinary(EOpBitShiftRight, bufferOffsetUint, bufferOffsetShift); | |
| TIntermBinary *bufferOffset = | |
| new TIntermBinary(EOpBitwiseAnd, bufferOffsetShifted, CreateUIntConstant(0xFF)); | |
| // return atomicCounters[bindingOffset.binding].counters[bindingOffset.offset + bufferOffset] | |
| offset = new TIntermBinary(EOpAdd, offset, bufferOffset); | |
| return new TIntermBinary(EOpIndexDirect, counters, offset); | |
| } | |
| // Traverser that: | |
| // | |
| // 1. Converts the |atomic_uint| types to |{uint,uint}| for binding and offset. | |
| // 2. Substitutes the |uniform atomic_uint| declarations with a global declaration that holds the | |
| // binding and offset. | |
| // 3. 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, true, true, symbolTable), | |
| mAtomicCounters(atomicCounters), | |
| mAcbBufferOffsets(acbBufferOffsets), | |
| mAtomicCounterType(nullptr), | |
| mAtomicCounterTypeConst(nullptr), | |
| mAtomicCounterTypeDeclaration(nullptr) | |
| {} | |
| bool visitDeclaration(Visit visit, TIntermDeclaration *node) override | |
| { | |
| if (visit != PreVisit) | |
| { | |
| return true; | |
| } | |
| const TIntermSequence &sequence = *(node->getSequence()); | |
| TIntermTyped *variable = sequence.front()->getAsTyped(); | |
| const TType &type = variable->getType(); | |
| bool isAtomicCounter = type.getQualifier() == EvqUniform && type.isAtomicCounter(); | |
| if (isAtomicCounter) | |
| { | |
| // Atomic counters cannot have initializers, so the declaration must necessarily be a | |
| // symbol. | |
| TIntermSymbol *samplerVariable = variable->getAsSymbolNode(); | |
| ASSERT(samplerVariable != nullptr); | |
| declareAtomicCounter(&samplerVariable->variable(), node); | |
| return false; | |
| } | |
| return true; | |
| } | |
| void visitFunctionPrototype(TIntermFunctionPrototype *node) override | |
| { | |
| const TFunction *function = node->getFunction(); | |
| // Go over the parameters and replace the atomic arguments with a uint type. | |
| mRetyper.visitFunctionPrototype(); | |
| for (size_t paramIndex = 0; paramIndex < function->getParamCount(); ++paramIndex) | |
| { | |
| const TVariable *param = function->getParam(paramIndex); | |
| TVariable *replacement = convertFunctionParameter(node, param); | |
| if (replacement) | |
| { | |
| mRetyper.replaceFunctionParam(param, replacement); | |
| } | |
| } | |
| TIntermFunctionPrototype *replacementPrototype = | |
| mRetyper.convertFunctionPrototype(mSymbolTable, function); | |
| if (replacementPrototype) | |
| { | |
| queueReplacement(replacementPrototype, OriginalNode::IS_DROPPED); | |
| } | |
| } | |
| bool visitAggregate(Visit visit, TIntermAggregate *node) override | |
| { | |
| if (visit == PreVisit) | |
| { | |
| mRetyper.preVisitAggregate(); | |
| } | |
| if (visit != PostVisit) | |
| { | |
| return true; | |
| } | |
| if (node->getOp() == EOpCallBuiltInFunction) | |
| { | |
| convertBuiltinFunction(node); | |
| } | |
| else if (node->getOp() == EOpCallFunctionInAST) | |
| { | |
| TIntermAggregate *substituteCall = mRetyper.convertASTFunction(node); | |
| if (substituteCall) | |
| { | |
| queueReplacement(substituteCall, OriginalNode::IS_DROPPED); | |
| } | |
| } | |
| mRetyper.postVisitAggregate(); | |
| return true; | |
| } | |
| void visitSymbol(TIntermSymbol *symbol) override | |
| { | |
| const TVariable *symbolVariable = &symbol->variable(); | |
| if (!symbol->getType().isAtomicCounter()) | |
| { | |
| return; | |
| } | |
| // The symbol is either referencing a global atomic counter, or is a function parameter. In | |
| // either case, it could be an array. The are the following possibilities: | |
| // | |
| // layout(..) uniform atomic_uint ac; | |
| // layout(..) uniform atomic_uint acArray[N]; | |
| // | |
| // void func(inout atomic_uint c) | |
| // { | |
| // otherFunc(c); | |
| // } | |
| // | |
| // void funcArray(inout atomic_uint cArray[N]) | |
| // { | |
| // otherFuncArray(cArray); | |
| // otherFunc(cArray[n]); | |
| // } | |
| // | |
| // void funcGlobal() | |
| // { | |
| // func(ac); | |
| // func(acArray[n]); | |
| // funcArray(acArray); | |
| // atomicIncrement(ac); | |
| // atomicIncrement(acArray[n]); | |
| // } | |
| // | |
| // This should translate to: | |
| // | |
| // buffer ANGLEAtomicCounters | |
| // { | |
| // uint counters[]; | |
| // } atomicCounters; | |
| // | |
| // struct ANGLEAtomicCounter | |
| // { | |
| // uint binding; | |
| // uint offset; | |
| // }; | |
| // const ANGLEAtomicCounter ac = {<binding>, <offset>}; | |
| // const ANGLEAtomicCounter acArray = {<binding>, <offset>}; | |
| // | |
| // void func(inout ANGLEAtomicCounter c) | |
| // { | |
| // otherFunc(c); | |
| // } | |
| // | |
| // void funcArray(inout uint cArray) | |
| // { | |
| // otherFuncArray(cArray); | |
| // otherFunc({cArray.binding, cArray.offset + n}); | |
| // } | |
| // | |
| // void funcGlobal() | |
| // { | |
| // func(ac); | |
| // func(acArray+n); | |
| // funcArray(acArray); | |
| // atomicAdd(atomicCounters[ac.binding]counters[ac.offset]); | |
| // atomicAdd(atomicCounters[ac.binding]counters[ac.offset+n]); | |
| // } | |
| // | |
| // In all cases, the argument transformation is stored in mRetyper. In the function call's | |
| // PostVisit, if it's a builtin, the look up in |atomicCounters.counters| is done as well as | |
| // the builtin function change. Otherwise, the transformed argument is passed on as is. | |
| // | |
| TIntermTyped *bindingOffset = | |
| new TIntermSymbol(mRetyper.getVariableReplacement(symbolVariable)); | |
| ASSERT(bindingOffset != nullptr); | |
| TIntermNode *argument = convertFunctionArgument(symbol, &bindingOffset); | |
| if (mRetyper.isInAggregate()) | |
| { | |
| mRetyper.replaceFunctionCallArg(argument, bindingOffset); | |
| } | |
| else | |
| { | |
| // If there's a stray ac[i] lying around, just delete it. This can happen if the shader | |
| // uses ac[i].length(), which in RemoveArrayLengthMethod() will result in an ineffective | |
| // statement that's just ac[i]; (similarly for a stray ac;, it doesn't have to be | |
| // subscripted). Note that the subscript could have side effects, but the | |
| // convertFunctionArgument above has already generated code that includes the subscript | |
| // (and therefore its side-effect). | |
| TIntermBlock *block = nullptr; | |
| for (uint32_t ancestorIndex = 0; block == nullptr; ++ancestorIndex) | |
| { | |
| block = getAncestorNode(ancestorIndex)->getAsBlock(); | |
| } | |
| TIntermSequence emptySequence; | |
| mMultiReplacements.emplace_back(block, argument, emptySequence); | |
| } | |
| } | |
| TIntermDeclaration *getAtomicCounterTypeDeclaration() { return mAtomicCounterTypeDeclaration; } | |
| private: | |
| void declareAtomicCounter(const TVariable *atomicCounterVar, TIntermDeclaration *node) | |
| { | |
| // Create a global variable that contains the binding and offset of this atomic counter | |
| // declaration. | |
| if (mAtomicCounterType == nullptr) | |
| { | |
| declareAtomicCounterType(); | |
| } | |
| ASSERT(mAtomicCounterTypeConst); | |
| TVariable *bindingOffset = new TVariable(mSymbolTable, atomicCounterVar->name(), | |
| mAtomicCounterTypeConst, SymbolType::UserDefined); | |
| const TType &atomicCounterType = atomicCounterVar->getType(); | |
| uint32_t offset = atomicCounterType.getLayoutQualifier().offset; | |
| uint32_t binding = atomicCounterType.getLayoutQualifier().binding; | |
| ASSERT(offset % 4 == 0); | |
| TIntermTyped *bindingOffsetInitValue = | |
| CreateAtomicCounterConstant(mAtomicCounterTypeConst, binding, offset / 4); | |
| TIntermSymbol *bindingOffsetSymbol = new TIntermSymbol(bindingOffset); | |
| TIntermBinary *bindingOffsetInit = | |
| new TIntermBinary(EOpInitialize, bindingOffsetSymbol, bindingOffsetInitValue); | |
| TIntermDeclaration *bindingOffsetDeclaration = new TIntermDeclaration(); | |
| bindingOffsetDeclaration->appendDeclarator(bindingOffsetInit); | |
| // Replace the atomic_uint declaration with the binding/offset declaration. | |
| TIntermSequence replacement; | |
| replacement.push_back(bindingOffsetDeclaration); | |
| mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, replacement); | |
| // Remember the binding/offset variable. | |
| mRetyper.replaceGlobalVariable(atomicCounterVar, bindingOffset); | |
| } | |
| void declareAtomicCounterType() | |
| { | |
| ASSERT(mAtomicCounterType == nullptr); | |
| TFieldList *fields = new TFieldList(); | |
| fields->push_back(new TField(new TType(EbtUInt, EbpUndefined, EvqGlobal, 1, 1), | |
| ImmutableString("binding"), TSourceLoc(), | |
| SymbolType::AngleInternal)); | |
| fields->push_back(new TField(new TType(EbtUInt, EbpUndefined, EvqGlobal, 1, 1), | |
| ImmutableString("arrayIndex"), TSourceLoc(), | |
| SymbolType::AngleInternal)); | |
| TStructure *atomicCounterTypeStruct = | |
| new TStructure(mSymbolTable, kAtomicCounterTypeName, fields, SymbolType::AngleInternal); | |
| mAtomicCounterType = new TType(atomicCounterTypeStruct, false); | |
| mAtomicCounterTypeDeclaration = new TIntermDeclaration; | |
| TVariable *emptyVariable = new TVariable(mSymbolTable, kEmptyImmutableString, | |
| mAtomicCounterType, SymbolType::Empty); | |
| mAtomicCounterTypeDeclaration->appendDeclarator(new TIntermSymbol(emptyVariable)); | |
| // Keep a const variant around as well. | |
| mAtomicCounterTypeConst = new TType(*mAtomicCounterType); | |
| mAtomicCounterTypeConst->setQualifier(EvqConst); | |
| } | |
| TVariable *convertFunctionParameter(TIntermNode *parent, const TVariable *param) | |
| { | |
| if (!param->getType().isAtomicCounter()) | |
| { | |
| return nullptr; | |
| } | |
| if (mAtomicCounterType == nullptr) | |
| { | |
| declareAtomicCounterType(); | |
| } | |
| const TType *paramType = ¶m->getType(); | |
| TType *newType = | |
| paramType->getQualifier() == EvqConst ? mAtomicCounterTypeConst : mAtomicCounterType; | |
| TVariable *replacementVar = | |
| new TVariable(mSymbolTable, param->name(), newType, SymbolType::UserDefined); | |
| return replacementVar; | |
| } | |
| TIntermTyped *convertFunctionArgumentHelper( | |
| const TVector<unsigned int> &runningArraySizeProducts, | |
| TIntermTyped *flattenedSubscript, | |
| uint32_t depth, | |
| uint32_t *subscriptCountOut) | |
| { | |
| std::string prefix(depth, ' '); | |
| TIntermNode *parent = getAncestorNode(depth); | |
| ASSERT(parent); | |
| TIntermBinary *arrayExpression = parent->getAsBinaryNode(); | |
| if (!arrayExpression) | |
| { | |
| // If the parent is not an array subscript operation, we have reached the end of the | |
| // subscript chain. Note the depth that's traversed so the corresponding node can be | |
| // taken as the function argument. | |
| *subscriptCountOut = depth; | |
| return flattenedSubscript; | |
| } | |
| ASSERT(arrayExpression->getOp() == EOpIndexDirect || | |
| arrayExpression->getOp() == EOpIndexIndirect); | |
| // Assume i = n - depth. Get Pi. See comment in convertFunctionArgument. | |
| ASSERT(depth < runningArraySizeProducts.size()); | |
| uint32_t thisDimensionSize = | |
| runningArraySizeProducts[runningArraySizeProducts.size() - 1 - depth]; | |
| // Get Ii. | |
| TIntermTyped *thisDimensionOffset = arrayExpression->getRight(); | |
| TIntermConstantUnion *subscriptAsConstant = thisDimensionOffset->getAsConstantUnion(); | |
| const bool subscriptIsZero = subscriptAsConstant && subscriptAsConstant->isZero(0); | |
| // If Ii is zero, don't need to add Ii*Pi; that's zero. | |
| if (!subscriptIsZero) | |
| { | |
| thisDimensionOffset = thisDimensionOffset->deepCopy(); | |
| // If Pi is 1, don't multiply. Just accumulate Ii. | |
| if (thisDimensionSize != 1) | |
| { | |
| thisDimensionOffset = new TIntermBinary(EOpMul, thisDimensionOffset, | |
| CreateUIntConstant(thisDimensionSize)); | |
| } | |
| // Accumulate with the previous running offset, if any. | |
| if (flattenedSubscript) | |
| { | |
| flattenedSubscript = | |
| new TIntermBinary(EOpAdd, flattenedSubscript, thisDimensionOffset); | |
| } | |
| else | |
| { | |
| flattenedSubscript = thisDimensionOffset; | |
| } | |
| } | |
| // Note: GLSL only allows 2 nested levels of arrays, so this recursion is bounded. | |
| return convertFunctionArgumentHelper(runningArraySizeProducts, flattenedSubscript, | |
| depth + 1, subscriptCountOut); | |
| } | |
| TIntermNode *convertFunctionArgument(TIntermNode *symbol, TIntermTyped **bindingOffset) | |
| { | |
| // Assume a general case of array declaration with N dimensions: | |
| // | |
| // atomic_uint ac[Dn]..[D2][D1]; | |
| // | |
| // Let's define | |
| // | |
| // Pn = D(n-1)*...*D2*D1 | |
| // | |
| // In that case, we have: | |
| // | |
| // ac[In] = ac + In*Pn | |
| // ac[In][I(n-1)] = ac + In*Pn + I(n-1)*P(n-1) | |
| // ac[In]...[Ii] = ac + In*Pn + ... + Ii*Pi | |
| // | |
| // We have just visited a symbol; ac. Walking the parent chain, we will visit the | |
| // expressions in the above order (ac, ac[In], ac[In][I(n-1)], ...). We therefore can | |
| // simply walk the parent chain and accumulate Ii*Pi to obtain the offset from the base of | |
| // ac. | |
| TIntermSymbol *argumentAsSymbol = symbol->getAsSymbolNode(); | |
| ASSERT(argumentAsSymbol); | |
| const TVector<unsigned int> *arraySizes = argumentAsSymbol->getType().getArraySizes(); | |
| // Calculate Pi | |
| TVector<unsigned int> runningArraySizeProducts; | |
| if (arraySizes && arraySizes->size() > 0) | |
| { | |
| runningArraySizeProducts.resize(arraySizes->size()); | |
| uint32_t runningProduct = 1; | |
| for (size_t dimension = 0; dimension < arraySizes->size(); ++dimension) | |
| { | |
| runningArraySizeProducts[dimension] = runningProduct; | |
| runningProduct *= (*arraySizes)[dimension]; | |
| } | |
| } | |
| // Walk the parent chain and accumulate Ii*Pi | |
| uint32_t subscriptCount = 0; | |
| TIntermTyped *flattenedSubscript = | |
| convertFunctionArgumentHelper(runningArraySizeProducts, nullptr, 0, &subscriptCount); | |
| // Find the function argument, which is either in the form of ac (i.e. there are no | |
| // subscripts, in which case that's the function argument), or ac[In]...[Ii] (in which case | |
| // the function argument is the (n-i)th ancestor of ac. | |
| // | |
| // Note that this is the case because no other operation is allowed on ac other than | |
| // subscript. | |
| TIntermNode *argument = subscriptCount == 0 ? symbol : getAncestorNode(subscriptCount - 1); | |
| ASSERT(argument != nullptr); | |
| // If not subscripted, keep the argument as-is. | |
| if (flattenedSubscript == nullptr) | |
| { | |
| return argument; | |
| } | |
| // Copy the atomic counter binding/offset constant and modify it by adding the array | |
| // subscript to its offset field. | |
| TVariable *modified = CreateTempVariable(mSymbolTable, mAtomicCounterType); | |
| TIntermDeclaration *modifiedDecl = CreateTempInitDeclarationNode(modified, *bindingOffset); | |
| TIntermSymbol *modifiedSymbol = new TIntermSymbol(modified); | |
| TConstantUnion *offsetFieldIndex = new TConstantUnion; | |
| offsetFieldIndex->setIConst(1); | |
| TIntermConstantUnion *offsetFieldRef = | |
| new TIntermConstantUnion(offsetFieldIndex, *StaticType::GetBasic<EbtUInt>()); | |
| TIntermBinary *offsetField = | |
| new TIntermBinary(EOpIndexDirectStruct, modifiedSymbol, offsetFieldRef); | |
| TIntermBinary *modifiedOffset = | |
| new TIntermBinary(EOpAddAssign, offsetField, flattenedSubscript); | |
| TIntermSequence *modifySequence = new TIntermSequence({modifiedDecl, modifiedOffset}); | |
| insertStatementsInParentBlock(*modifySequence); | |
| *bindingOffset = modifiedSymbol->deepCopy(); | |
| return argument; | |
| } | |
| void convertBuiltinFunction(TIntermAggregate *node) | |
| { | |
| // If the function is |memoryBarrierAtomicCounter|, simply replace it with | |
| // |memoryBarrierBuffer|. | |
| if (node->getFunction()->name() == "memoryBarrierAtomicCounter") | |
| { | |
| TIntermTyped *substituteCall = CreateBuiltInFunctionCallNode( | |
| "memoryBarrierBuffer", new TIntermSequence, *mSymbolTable, 310); | |
| queueReplacement(substituteCall, OriginalNode::IS_DROPPED); | |
| return; | |
| } | |
| // If it's an |atomicCounter*| function, replace the function with an |atomic*| equivalent. | |
| if (!node->getFunction()->isAtomicCounterFunction()) | |
| { | |
| return; | |
| } | |
| const ImmutableString &functionName = node->getFunction()->name(); | |
| TIntermSequence *arguments = node->getSequence(); | |
| // Note: atomicAdd(0) is used for atomic reads. | |
| uint32_t valueChange = 0; | |
| constexpr char kAtomicAddFunction[] = "atomicAdd"; | |
| bool isDecrement = false; | |
| if (functionName == "atomicCounterIncrement") | |
| { | |
| valueChange = 1; | |
| } | |
| else if (functionName == "atomicCounterDecrement") | |
| { | |
| // 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(functionName == "atomicCounter"); | |
| } | |
| const TIntermNode *param = (*arguments)[0]; | |
| TIntermTyped *bindingOffset = mRetyper.getFunctionCallArgReplacement(param); | |
| TIntermSequence *substituteArguments = new TIntermSequence; | |
| substituteArguments->push_back( | |
| CreateAtomicCounterRef(mAtomicCounters, bindingOffset, mAcbBufferOffsets)); | |
| substituteArguments->push_back(CreateUIntConstant(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, CreateUIntConstant(1)); | |
| } | |
| queueReplacement(substituteCall, OriginalNode::IS_DROPPED); | |
| } | |
| const TVariable *mAtomicCounters; | |
| const TIntermTyped *mAcbBufferOffsets; | |
| RetypeOpaqueVariablesHelper mRetyper; | |
| TType *mAtomicCounterType; | |
| TType *mAtomicCounterTypeConst; | |
| // Stored to be put at the top of the shader after the pass. | |
| TIntermDeclaration *mAtomicCounterTypeDeclaration; | |
| }; | |
| } // 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); | |
| if (!traverser.updateTree(compiler, root)) | |
| { | |
| return false; | |
| } | |
| TIntermDeclaration *atomicCounterTypeDeclaration = traverser.getAtomicCounterTypeDeclaration(); | |
| if (atomicCounterTypeDeclaration) | |
| { | |
| root->getSequence()->insert(root->getSequence()->begin(), atomicCounterTypeDeclaration); | |
| } | |
| return compiler->validateAST(root); | |
| } | |
| } // namespace sh |