Edit

kc3-lang/angle/src/compiler/translator/ShaderStorageBlockOutputHLSL.cpp

Branch :

  • Show log

    Commit

  • Author : Peter Kasting
    Date : 2021-06-29 18:13:39
    Hash : 1bffabe8
    Message : Fix -Wunreachable-code-aggressive. Bug: chromium:1066980 Change-Id: I1fa08a40dbf223d60a10681af33ca8a29b12bf8b Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2991094 Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org> Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>

  • src/compiler/translator/ShaderStorageBlockOutputHLSL.cpp
  • //
    // 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
    {
    
    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<unsigned int> arraySizes;
        const TSpan<const unsigned int> &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<InterfaceBlock> &haystack)
    {
        for (const InterfaceBlock &block : haystack)
        {
            if (strcmp(block.name.c_str(), needle->name().data()) == 0)
            {
                ASSERT(block.fields.size() == needle->fields().size());
                return &block;
            }
        }
    
        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<int>(getEncoder(mStorage)->getCurrentOffset());
            int arrayStride = static_cast<int>(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<InterfaceBlock> &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<InterfaceBlock> &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(TInfoSinkBase &out) const
    {
        out << mResourcesHLSL->shaderStorageBlocksHeader(mReferencedShaderStorageBlocks);
        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<const unsigned int> &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