// // Copyright 2018 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. // // ShaderStorageBlockOutputHLSL: A traverser to translate a ssbo_access_chain to an offset of // RWByteAddressBuffer. // //EOpIndexDirectInterfaceBlock // ssbo_variable := // | the name of the SSBO // | the name of a variable in an SSBO backed interface block // // EOpIndexInDirect // // EOpIndexDirect // ssbo_array_indexing := ssbo_access_chain[expr_no_ssbo] // // EOpIndexDirectStruct // ssbo_structure_access := ssbo_access_chain.identifier // ssbo_access_chain := // | ssbo_variable // | ssbo_array_indexing // | ssbo_structure_access // #include "compiler/translator/ShaderStorageBlockOutputHLSL.h" #include "compiler/translator/ResourcesHLSL.h" #include "compiler/translator/blocklayoutHLSL.h" #include "compiler/translator/tree_util/IntermNode_util.h" #include "compiler/translator/util.h" namespace sh { namespace { constexpr const char kShaderStorageDeclarationString[] = "// @@ SHADER STORAGE DECLARATION STRING @@"; void GetBlockLayoutInfo(TIntermTyped *node, bool rowMajorAlreadyAssigned, TLayoutBlockStorage *storage, bool *rowMajor) { TIntermSwizzle *swizzleNode = node->getAsSwizzleNode(); if (swizzleNode) { return GetBlockLayoutInfo(swizzleNode->getOperand(), rowMajorAlreadyAssigned, storage, rowMajor); } TIntermBinary *binaryNode = node->getAsBinaryNode(); if (binaryNode) { switch (binaryNode->getOp()) { case EOpIndexDirectInterfaceBlock: { // The column_major/row_major qualifier of a field member overrides the interface // block's row_major/column_major. So we can assign rowMajor here and don't need to // assign it again. But we still need to call recursively to get the storage's // value. const TType &type = node->getType(); *rowMajor = type.getLayoutQualifier().matrixPacking == EmpRowMajor; return GetBlockLayoutInfo(binaryNode->getLeft(), true, storage, rowMajor); } case EOpIndexIndirect: case EOpIndexDirect: case EOpIndexDirectStruct: return GetBlockLayoutInfo(binaryNode->getLeft(), rowMajorAlreadyAssigned, storage, rowMajor); default: UNREACHABLE(); return; } } const TType &type = node->getType(); ASSERT(type.getQualifier() == EvqBuffer); const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock(); ASSERT(interfaceBlock); *storage = interfaceBlock->blockStorage(); // If the block doesn't have an instance name, rowMajorAlreadyAssigned will be false. In // this situation, we still need to set rowMajor's value. if (!rowMajorAlreadyAssigned) { *rowMajor = type.getLayoutQualifier().matrixPacking == EmpRowMajor; } } // It's possible that the current type has lost the original layout information. So we should pass // the right layout information to GetBlockMemberInfoByType. const BlockMemberInfo GetBlockMemberInfoByType(const TType &type, TLayoutBlockStorage storage, bool rowMajor) { sh::Std140BlockEncoder std140Encoder; sh::Std430BlockEncoder std430Encoder; sh::HLSLBlockEncoder hlslEncoder(sh::HLSLBlockEncoder::ENCODE_PACKED, false); sh::BlockLayoutEncoder *encoder = nullptr; if (storage == EbsStd140) { encoder = &std140Encoder; } else if (storage == EbsStd430) { encoder = &std430Encoder; } else { encoder = &hlslEncoder; } std::vector arraySizes; const TSpan &typeArraySizes = type.getArraySizes(); if (!typeArraySizes.empty()) { arraySizes.assign(typeArraySizes.begin(), typeArraySizes.end()); } return encoder->encodeType(GLVariableType(type), arraySizes, rowMajor); } const TField *GetFieldMemberInShaderStorageBlock(const TInterfaceBlock *interfaceBlock, const ImmutableString &variableName) { for (const TField *field : interfaceBlock->fields()) { if (field->name() == variableName) { return field; } } return nullptr; } const InterfaceBlock *FindInterfaceBlock(const TInterfaceBlock *needle, const std::vector &haystack) { for (const InterfaceBlock &block : haystack) { if (strcmp(block.name.c_str(), needle->name().data()) == 0) { ASSERT(block.fields.size() == needle->fields().size()); return █ } } UNREACHABLE(); return nullptr; } std::string StripArrayIndices(const std::string &nameIn) { std::string name = nameIn; size_t pos = name.find('['); while (pos != std::string::npos) { size_t closePos = name.find(']', pos); ASSERT(closePos != std::string::npos); name.erase(pos, closePos - pos + 1); pos = name.find('[', pos); } ASSERT(name.find(']') == std::string::npos); return name; } // Does not include any array indices. void MapVariableToField(const ShaderVariable &variable, const TField *field, std::string currentName, ShaderVarToFieldMap *shaderVarToFieldMap) { ASSERT((field->type()->getStruct() == nullptr) == variable.fields.empty()); (*shaderVarToFieldMap)[currentName] = field; if (!variable.fields.empty()) { const TStructure *subStruct = field->type()->getStruct(); ASSERT(variable.fields.size() == subStruct->fields().size()); for (size_t index = 0; index < variable.fields.size(); ++index) { const TField *subField = subStruct->fields()[index]; const ShaderVariable &subVariable = variable.fields[index]; std::string subName = currentName + "." + subVariable.name; MapVariableToField(subVariable, subField, subName, shaderVarToFieldMap); } } } class BlockInfoVisitor final : public BlockEncoderVisitor { public: BlockInfoVisitor(const std::string &prefix, TLayoutBlockStorage storage, const ShaderVarToFieldMap &shaderVarToFieldMap, BlockMemberInfoMap *blockInfoOut) : BlockEncoderVisitor(prefix, "", getEncoder(storage)), mShaderVarToFieldMap(shaderVarToFieldMap), mBlockInfoOut(blockInfoOut), mHLSLEncoder(HLSLBlockEncoder::ENCODE_PACKED, false), mStorage(storage) {} BlockLayoutEncoder *getEncoder(TLayoutBlockStorage storage) { switch (storage) { case EbsStd140: return &mStd140Encoder; case EbsStd430: return &mStd430Encoder; default: return &mHLSLEncoder; } } void enterStructAccess(const ShaderVariable &structVar, bool isRowMajor) override { BlockEncoderVisitor::enterStructAccess(structVar, isRowMajor); std::string variableName = StripArrayIndices(collapseNameStack()); // Remove the trailing "." variableName.pop_back(); BlockInfoVisitor childVisitor(variableName, mStorage, mShaderVarToFieldMap, mBlockInfoOut); childVisitor.getEncoder(mStorage)->enterAggregateType(structVar); TraverseShaderVariables(structVar.fields, isRowMajor, &childVisitor); childVisitor.getEncoder(mStorage)->exitAggregateType(structVar); int offset = static_cast(getEncoder(mStorage)->getCurrentOffset()); int arrayStride = static_cast(childVisitor.getEncoder(mStorage)->getCurrentOffset()); auto iter = mShaderVarToFieldMap.find(variableName); if (iter == mShaderVarToFieldMap.end()) return; const TField *structField = iter->second; if (mBlockInfoOut->count(structField) == 0) { mBlockInfoOut->emplace(structField, BlockMemberInfo(offset, arrayStride, -1, false)); } } void encodeVariable(const ShaderVariable &variable, const BlockMemberInfo &variableInfo, const std::string &name, const std::string &mappedName) override { auto iter = mShaderVarToFieldMap.find(StripArrayIndices(name)); if (iter == mShaderVarToFieldMap.end()) return; const TField *field = iter->second; if (mBlockInfoOut->count(field) == 0) { mBlockInfoOut->emplace(field, variableInfo); } } private: const ShaderVarToFieldMap &mShaderVarToFieldMap; BlockMemberInfoMap *mBlockInfoOut; Std140BlockEncoder mStd140Encoder; Std430BlockEncoder mStd430Encoder; HLSLBlockEncoder mHLSLEncoder; TLayoutBlockStorage mStorage; }; void GetShaderStorageBlockMembersInfo(const TInterfaceBlock *interfaceBlock, const std::vector &shaderStorageBlocks, BlockMemberInfoMap *blockInfoOut) { // Find the sh::InterfaceBlock. const InterfaceBlock *block = FindInterfaceBlock(interfaceBlock, shaderStorageBlocks); ASSERT(block); // Map ShaderVariable to TField. ShaderVarToFieldMap shaderVarToFieldMap; for (size_t index = 0; index < block->fields.size(); ++index) { const TField *field = interfaceBlock->fields()[index]; const ShaderVariable &variable = block->fields[index]; MapVariableToField(variable, field, variable.name, &shaderVarToFieldMap); } BlockInfoVisitor visitor("", interfaceBlock->blockStorage(), shaderVarToFieldMap, blockInfoOut); TraverseShaderVariables(block->fields, false, &visitor); } TIntermTyped *Mul(TIntermTyped *left, TIntermTyped *right) { return left && right ? new TIntermBinary(EOpMul, left, right) : nullptr; } TIntermTyped *Add(TIntermTyped *left, TIntermTyped *right) { return left ? right ? new TIntermBinary(EOpAdd, left, right) : left : right; } } // anonymous namespace ShaderStorageBlockOutputHLSL::ShaderStorageBlockOutputHLSL( OutputHLSL *outputHLSL, ResourcesHLSL *resourcesHLSL, const std::vector &shaderStorageBlocks) : mOutputHLSL(outputHLSL), mResourcesHLSL(resourcesHLSL), mShaderStorageBlocks(shaderStorageBlocks) { mSSBOFunctionHLSL = new ShaderStorageBlockFunctionHLSL; } ShaderStorageBlockOutputHLSL::~ShaderStorageBlockOutputHLSL() { SafeDelete(mSSBOFunctionHLSL); } void ShaderStorageBlockOutputHLSL::outputStoreFunctionCallPrefix(TIntermTyped *node) { traverseSSBOAccess(node, SSBOMethod::STORE); } void ShaderStorageBlockOutputHLSL::outputLoadFunctionCall(TIntermTyped *node) { traverseSSBOAccess(node, SSBOMethod::LOAD); mOutputHLSL->getInfoSink() << ")"; } void ShaderStorageBlockOutputHLSL::outputLengthFunctionCall(TIntermTyped *node) { traverseSSBOAccess(node, SSBOMethod::LENGTH); mOutputHLSL->getInfoSink() << ")"; } void ShaderStorageBlockOutputHLSL::outputAtomicMemoryFunctionCallPrefix(TIntermTyped *node, TOperator op) { switch (op) { case EOpAtomicAdd: traverseSSBOAccess(node, SSBOMethod::ATOMIC_ADD); break; case EOpAtomicMin: traverseSSBOAccess(node, SSBOMethod::ATOMIC_MIN); break; case EOpAtomicMax: traverseSSBOAccess(node, SSBOMethod::ATOMIC_MAX); break; case EOpAtomicAnd: traverseSSBOAccess(node, SSBOMethod::ATOMIC_AND); break; case EOpAtomicOr: traverseSSBOAccess(node, SSBOMethod::ATOMIC_OR); break; case EOpAtomicXor: traverseSSBOAccess(node, SSBOMethod::ATOMIC_XOR); break; case EOpAtomicExchange: traverseSSBOAccess(node, SSBOMethod::ATOMIC_EXCHANGE); break; case EOpAtomicCompSwap: traverseSSBOAccess(node, SSBOMethod::ATOMIC_COMPSWAP); break; default: UNREACHABLE(); break; } } // Note that we must calculate the matrix stride here instead of ShaderStorageBlockFunctionHLSL. // It's because that if the current node's type is a vector which comes from a matrix, we will // lose the matrix type info once we enter ShaderStorageBlockFunctionHLSL. int ShaderStorageBlockOutputHLSL::getMatrixStride(TIntermTyped *node, TLayoutBlockStorage storage, bool rowMajor, bool *isRowMajorMatrix) const { if (node->getType().isMatrix()) { *isRowMajorMatrix = rowMajor; return GetBlockMemberInfoByType(node->getType(), storage, rowMajor).matrixStride; } if (node->getType().isVector()) { TIntermBinary *binaryNode = node->getAsBinaryNode(); if (binaryNode) { return getMatrixStride(binaryNode->getLeft(), storage, rowMajor, isRowMajorMatrix); } else { TIntermSwizzle *swizzleNode = node->getAsSwizzleNode(); if (swizzleNode) { return getMatrixStride(swizzleNode->getOperand(), storage, rowMajor, isRowMajorMatrix); } } } return 0; } void ShaderStorageBlockOutputHLSL::collectShaderStorageBlocks(TIntermTyped *node) { TIntermSwizzle *swizzleNode = node->getAsSwizzleNode(); if (swizzleNode) { return collectShaderStorageBlocks(swizzleNode->getOperand()); } TIntermBinary *binaryNode = node->getAsBinaryNode(); if (binaryNode) { switch (binaryNode->getOp()) { case EOpIndexDirectInterfaceBlock: case EOpIndexIndirect: case EOpIndexDirect: case EOpIndexDirectStruct: return collectShaderStorageBlocks(binaryNode->getLeft()); default: UNREACHABLE(); return; } } const TIntermSymbol *symbolNode = node->getAsSymbolNode(); const TType &type = symbolNode->getType(); ASSERT(type.getQualifier() == EvqBuffer); const TVariable &variable = symbolNode->variable(); const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock(); ASSERT(interfaceBlock); if (mReferencedShaderStorageBlocks.count(interfaceBlock->uniqueId().get()) == 0) { const TVariable *instanceVariable = nullptr; if (type.isInterfaceBlock()) { instanceVariable = &variable; } mReferencedShaderStorageBlocks[interfaceBlock->uniqueId().get()] = new TReferencedBlock(interfaceBlock, instanceVariable); GetShaderStorageBlockMembersInfo(interfaceBlock, mShaderStorageBlocks, &mBlockMemberInfoMap); } } void ShaderStorageBlockOutputHLSL::traverseSSBOAccess(TIntermTyped *node, SSBOMethod method) { // TODO: Merge collectShaderStorageBlocks and GetBlockLayoutInfo to simplify the code. collectShaderStorageBlocks(node); // Note that we don't have correct BlockMemberInfo from mBlockMemberInfoMap at the current // point. But we must use those information to generate the right function name. So here we have // to calculate them again. TLayoutBlockStorage storage; bool rowMajor; GetBlockLayoutInfo(node, false, &storage, &rowMajor); int unsizedArrayStride = 0; if (node->getType().isUnsizedArray()) { // The unsized array member must be the last member of a shader storage block. TIntermBinary *binaryNode = node->getAsBinaryNode(); if (binaryNode) { const TInterfaceBlock *interfaceBlock = binaryNode->getLeft()->getType().getInterfaceBlock(); ASSERT(interfaceBlock); const TIntermConstantUnion *index = binaryNode->getRight()->getAsConstantUnion(); const TField *field = interfaceBlock->fields()[index->getIConst(0)]; auto fieldInfoIter = mBlockMemberInfoMap.find(field); ASSERT(fieldInfoIter != mBlockMemberInfoMap.end()); unsizedArrayStride = fieldInfoIter->second.arrayStride; } else { const TIntermSymbol *symbolNode = node->getAsSymbolNode(); const TVariable &variable = symbolNode->variable(); const TInterfaceBlock *interfaceBlock = symbolNode->getType().getInterfaceBlock(); ASSERT(interfaceBlock); const TField *field = GetFieldMemberInShaderStorageBlock(interfaceBlock, variable.name()); auto fieldInfoIter = mBlockMemberInfoMap.find(field); ASSERT(fieldInfoIter != mBlockMemberInfoMap.end()); unsizedArrayStride = fieldInfoIter->second.arrayStride; } } bool isRowMajorMatrix = false; int matrixStride = getMatrixStride(node, storage, rowMajor, &isRowMajorMatrix); const TString &functionName = mSSBOFunctionHLSL->registerShaderStorageBlockFunction( node->getType(), method, storage, isRowMajorMatrix, matrixStride, unsizedArrayStride, node->getAsSwizzleNode()); TInfoSinkBase &out = mOutputHLSL->getInfoSink(); out << functionName; out << "("; BlockMemberInfo blockMemberInfo; TIntermNode *loc = traverseNode(out, node, &blockMemberInfo); out << ", "; loc->traverse(mOutputHLSL); } void ShaderStorageBlockOutputHLSL::writeShaderStorageBlocksHeader(GLenum shaderType, TInfoSinkBase &out) const { if (mReferencedShaderStorageBlocks.empty()) { return; } mResourcesHLSL->allocateShaderStorageBlockRegisters(mReferencedShaderStorageBlocks); out << "// Shader Storage Blocks\n\n"; if (shaderType == GL_COMPUTE_SHADER) { out << mResourcesHLSL->shaderStorageBlocksHeader(mReferencedShaderStorageBlocks); } else { out << kShaderStorageDeclarationString << "\n"; } mSSBOFunctionHLSL->shaderStorageBlockFunctionHeader(out); } TIntermTyped *ShaderStorageBlockOutputHLSL::traverseNode(TInfoSinkBase &out, TIntermTyped *node, BlockMemberInfo *blockMemberInfo) { if (TIntermSymbol *symbolNode = node->getAsSymbolNode()) { const TVariable &variable = symbolNode->variable(); const TType &type = variable.getType(); if (type.isInterfaceBlock()) { out << DecorateVariableIfNeeded(variable); } else { const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock(); out << Decorate(interfaceBlock->name()); const TField *field = GetFieldMemberInShaderStorageBlock(interfaceBlock, variable.name()); return createFieldOffset(field, blockMemberInfo); } } else if (TIntermSwizzle *swizzleNode = node->getAsSwizzleNode()) { return traverseNode(out, swizzleNode->getOperand(), blockMemberInfo); } else if (TIntermBinary *binaryNode = node->getAsBinaryNode()) { switch (binaryNode->getOp()) { case EOpIndexDirect: { const TType &leftType = binaryNode->getLeft()->getType(); if (leftType.isInterfaceBlock()) { ASSERT(leftType.getQualifier() == EvqBuffer); TIntermSymbol *instanceArraySymbol = binaryNode->getLeft()->getAsSymbolNode(); const int arrayIndex = binaryNode->getRight()->getAsConstantUnion()->getIConst(0); out << mResourcesHLSL->InterfaceBlockInstanceString( instanceArraySymbol->getName(), arrayIndex); } else { return writeEOpIndexDirectOrIndirectOutput(out, binaryNode, blockMemberInfo); } break; } case EOpIndexIndirect: { // We do not currently support indirect references to interface blocks ASSERT(binaryNode->getLeft()->getBasicType() != EbtInterfaceBlock); return writeEOpIndexDirectOrIndirectOutput(out, binaryNode, blockMemberInfo); } case EOpIndexDirectStruct: { // We do not currently support direct references to interface blocks ASSERT(binaryNode->getLeft()->getBasicType() != EbtInterfaceBlock); TIntermTyped *left = traverseNode(out, binaryNode->getLeft(), blockMemberInfo); const TStructure *structure = binaryNode->getLeft()->getType().getStruct(); const TIntermConstantUnion *index = binaryNode->getRight()->getAsConstantUnion(); const TField *field = structure->fields()[index->getIConst(0)]; return Add(createFieldOffset(field, blockMemberInfo), left); } case EOpIndexDirectInterfaceBlock: { ASSERT(IsInShaderStorageBlock(binaryNode->getLeft())); traverseNode(out, binaryNode->getLeft(), blockMemberInfo); const TInterfaceBlock *interfaceBlock = binaryNode->getLeft()->getType().getInterfaceBlock(); const TIntermConstantUnion *index = binaryNode->getRight()->getAsConstantUnion(); const TField *field = interfaceBlock->fields()[index->getIConst(0)]; return createFieldOffset(field, blockMemberInfo); } default: return nullptr; } } return nullptr; } TIntermTyped *ShaderStorageBlockOutputHLSL::writeEOpIndexDirectOrIndirectOutput( TInfoSinkBase &out, TIntermBinary *node, BlockMemberInfo *blockMemberInfo) { ASSERT(IsInShaderStorageBlock(node->getLeft())); TIntermTyped *left = traverseNode(out, node->getLeft(), blockMemberInfo); TIntermTyped *right = node->getRight()->deepCopy(); const TType &type = node->getLeft()->getType(); TLayoutBlockStorage storage; bool rowMajor; GetBlockLayoutInfo(node, false, &storage, &rowMajor); if (type.isArray()) { const TSpan &arraySizes = type.getArraySizes(); for (unsigned int i = 0; i < arraySizes.size() - 1; i++) { right = Mul(CreateUIntNode(arraySizes[i]), right); } right = Mul(CreateUIntNode(blockMemberInfo->arrayStride), right); } else if (type.isMatrix()) { if (rowMajor) { right = Mul(CreateUIntNode(BlockLayoutEncoder::kBytesPerComponent), right); } else { right = Mul(CreateUIntNode(blockMemberInfo->matrixStride), right); } } else if (type.isVector()) { if (blockMemberInfo->isRowMajorMatrix) { right = Mul(CreateUIntNode(blockMemberInfo->matrixStride), right); } else { right = Mul(CreateUIntNode(BlockLayoutEncoder::kBytesPerComponent), right); } } return Add(left, right); } TIntermTyped *ShaderStorageBlockOutputHLSL::createFieldOffset(const TField *field, BlockMemberInfo *blockMemberInfo) { auto fieldInfoIter = mBlockMemberInfoMap.find(field); ASSERT(fieldInfoIter != mBlockMemberInfoMap.end()); *blockMemberInfo = fieldInfoIter->second; return CreateUIntNode(blockMemberInfo->offset); } } // namespace sh