Edit

kc3-lang/angle/src/compiler/translator/TranslatorMetalDirect/ModifyStruct.cpp

Branch :

  • Show log

    Commit

  • Author : Shahbaz Youssefi
    Date : 2021-08-23 11:05:23
    Hash : 800e82c6
    Message : Translator: Validate precisions When declaring a variable, a struct field, function parameter etc, there's a precision necessarily applied to the entity being declared. AST Validation is added to enforce this. Intermediate nodes derive their precision from these entities automatically. Consistency of intermediate nodes is not validated. This is because AST transformations replace a node with a transformed one, and that may not have the same precision. Take the following code: mediump float x = ...; mediump float y = ...; ... x + y ... and assume is transformed as such: highp float driver_uniform; ... (x * driver_uniform) + y ... The addition was originally done in mediump, but would seemingly need to be done in highp after transformation. There are a number of options here: - Make sure that when nodes are replaced, the precision is unaffected. This can be intrusive, requiring temp variables. - Bubble up the new precision - Accept the discrepancy ANGLE opts for the last option, which actually respects the original shader's intended precision for operations, even if some transformation needs to temporarily evaluate an expression at a higher precision. Bug: angleproject:4889 Bug: angleproject:6132 Change-Id: Ibcde3a230de159157783b1c6d5ef1cd63ceb4d8f Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3114027 Reviewed-by: Tim Van Patten <timvp@google.com> Reviewed-by: Jamie Madill <jmadill@chromium.org> Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>

  • src/compiler/translator/TranslatorMetalDirect/ModifyStruct.cpp
  • //
    // Copyright 2020 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.
    //
    
    #include <algorithm>
    #include <cstring>
    #include <numeric>
    #include <unordered_map>
    #include <unordered_set>
    
    #include "compiler/translator/Compiler.h"
    #include "compiler/translator/TranslatorMetalDirect.h"
    #include "compiler/translator/TranslatorMetalDirect/AstHelpers.h"
    #include "compiler/translator/TranslatorMetalDirect/ModifyStruct.h"
    
    using namespace sh;
    
    ////////////////////////////////////////////////////////////////////////////////
    
    size_t ModifiedStructMachineries::size() const
    {
        return ordering.size();
    }
    
    const ModifiedStructMachinery &ModifiedStructMachineries::at(size_t index) const
    {
        ASSERT(index < size());
        const TStructure *s              = ordering[index];
        const ModifiedStructMachinery *m = find(*s);
        ASSERT(m);
        return *m;
    }
    
    const ModifiedStructMachinery *ModifiedStructMachineries::find(const TStructure &s) const
    {
        auto iter = originalToMachinery.find(&s);
        if (iter == originalToMachinery.end())
        {
            return nullptr;
        }
        return &iter->second;
    }
    
    void ModifiedStructMachineries::insert(const TStructure &s,
                                           const ModifiedStructMachinery &machinery)
    {
        ASSERT(!find(s));
        originalToMachinery[&s] = machinery;
        ordering.push_back(&s);
    }
    
    ////////////////////////////////////////////////////////////////////////////////
    
    namespace
    {
    
    TIntermTyped &Flatten(SymbolEnv &symbolEnv, TIntermTyped &node)
    {
        auto &type = node.getType();
        ASSERT(type.isArray());
    
        auto &retType = InnermostType(type);
        retType.makeArray(1);
    
        return symbolEnv.callFunctionOverload(Name("flatten"), retType, *new TIntermSequence{&node});
    }
    
    struct FlattenArray
    {};
    
    struct PathItem
    {
        enum class Type
        {
            Field,         // Struct field indexing.
            Index,         // Array, vector, or matrix indexing.
            FlattenArray,  // Array of any rank -> pointer of innermost type.
        };
    
        PathItem(const TField &field) : field(&field), type(Type::Field) {}
        PathItem(int index) : index(index), type(Type::Index) {}
        PathItem(unsigned index) : PathItem(static_cast<int>(index)) {}
        PathItem(FlattenArray flatten) : type(Type::FlattenArray) {}
    
        union
        {
            const TField *field;
            int index;
        };
        Type type;
    };
    
    TIntermTyped &BuildPathAccess(SymbolEnv &symbolEnv,
                                  const TVariable &var,
                                  const std::vector<PathItem> &path)
    {
        TIntermTyped *curr = new TIntermSymbol(&var);
        for (const PathItem &item : path)
        {
            switch (item.type)
            {
                case PathItem::Type::Field:
                    curr = &AccessField(*curr, item.field->name());
                    break;
                case PathItem::Type::Index:
                    curr = &AccessIndex(*curr, item.index);
                    break;
                case PathItem::Type::FlattenArray:
                {
                    curr = &Flatten(symbolEnv, *curr);
                }
                break;
            }
        }
        return *curr;
    }
    
    ////////////////////////////////////////////////////////////////////////////////
    
    using OriginalParam = const TVariable &;
    using ModifiedParam = const TVariable &;
    
    using OriginalAccess = TIntermTyped;
    using ModifiedAccess = TIntermTyped;
    
    struct Access
    {
        OriginalAccess &original;
        ModifiedAccess &modified;
    
        struct Env
        {
            const ConvertType type;
        };
    };
    
    using ConversionFunc = std::function<Access(Access::Env &, OriginalAccess &, ModifiedAccess &)>;
    
    class ConvertStructState : angle::NonCopyable
    {
      private:
        struct ConversionInfo
        {
            ConversionFunc stdFunc;
            const TFunction *astFunc;
            std::vector<PathItem> pathItems;
            ImmutableString pathName;
        };
    
      public:
        ConvertStructState(TCompiler &compiler,
                           SymbolEnv &symbolEnv,
                           IdGen &idGen,
                           const ModifyStructConfig &config,
                           ModifiedStructMachineries &outMachineries,
                           const bool isUBO)
            : mCompiler(compiler),
              config(config),
              symbolEnv(symbolEnv),
              modifiedFields(*new TFieldList()),
              symbolTable(symbolEnv.symbolTable()),
              idGen(idGen),
              outMachineries(outMachineries),
              isUBO(isUBO)
        {}
    
        ~ConvertStructState()
        {
            ASSERT(namePath.empty());
            ASSERT(namePathSizes.empty());
        }
    
        void publish(const TStructure &originalStruct, const Name &modifiedStructName)
        {
            const bool isOriginalToModified = config.convertType == ConvertType::OriginalToModified;
    
            auto &modifiedStruct = *new TStructure(&symbolTable, modifiedStructName.rawName(),
                                                   &modifiedFields, modifiedStructName.symbolType());
    
            auto &func = *new TFunction(
                &symbolTable,
                idGen.createNewName(isOriginalToModified ? "originalToModified" : "modifiedToOriginal")
                    .rawName(),
                SymbolType::AngleInternal, new TType(TBasicType::EbtVoid), false);
    
            OriginalParam originalParam =
                CreateInstanceVariable(symbolTable, originalStruct, Name("original"));
            ModifiedParam modifiedParam =
                CreateInstanceVariable(symbolTable, modifiedStruct, Name("modified"));
    
            symbolEnv.markAsReference(originalParam, AddressSpace::Thread);
            symbolEnv.markAsReference(modifiedParam, config.externalAddressSpace);
            if (isOriginalToModified)
            {
                func.addParameter(&originalParam);
                func.addParameter(&modifiedParam);
            }
            else
            {
                func.addParameter(&modifiedParam);
                func.addParameter(&originalParam);
            }
    
            TIntermBlock &body = *new TIntermBlock();
    
            Access::Env env{config.convertType};
    
            for (ConversionInfo &info : conversionInfos)
            {
                auto convert = [&](OriginalAccess &original, ModifiedAccess &modified) {
                    if (info.astFunc)
                    {
                        ASSERT(!info.stdFunc);
                        TIntermTyped &src  = isOriginalToModified ? modified : original;
                        TIntermTyped &dest = isOriginalToModified ? original : modified;
                        body.appendStatement(TIntermAggregate::CreateFunctionCall(
                            *info.astFunc, new TIntermSequence{&dest, &src}));
                    }
                    else
                    {
                        ASSERT(info.stdFunc);
                        Access access      = info.stdFunc(env, original, modified);
                        TIntermTyped &src  = isOriginalToModified ? access.original : access.modified;
                        TIntermTyped &dest = isOriginalToModified ? access.modified : access.original;
                        body.appendStatement(new TIntermBinary(TOperator::EOpAssign, &dest, &src));
                    }
                };
    
                OriginalAccess *original = &BuildPathAccess(symbolEnv, originalParam, info.pathItems);
                ModifiedAccess *modified = &AccessField(modifiedParam, info.pathName);
    
                const TType ot = original->getType();
                const TType mt = modified->getType();
                ASSERT(ot.isArray() == mt.isArray());
    
                if (ot.isArray() && (ot.getLayoutQualifier().matrixPacking == EmpRowMajor || ot != mt))
                {
                    ASSERT(ot.getArraySizes() == mt.getArraySizes());
                    if (ot.isArrayOfArrays())
                    {
                        original = &Flatten(symbolEnv, *original);
                        modified = &Flatten(symbolEnv, *modified);
                    }
                    const int volume = static_cast<int>(ot.getArraySizeProduct());
                    for (int i = 0; i < volume; ++i)
                    {
                        if (i != 0)
                        {
                            original = original->deepCopy();
                            modified = modified->deepCopy();
                        }
                        OriginalAccess &o = AccessIndex(*original, i);
                        OriginalAccess &m = AccessIndex(*modified, i);
                        convert(o, m);
                    }
                }
                else
                {
                    convert(*original, *modified);
                }
            }
    
            auto *funcProto = new TIntermFunctionPrototype(&func);
            auto *funcDef   = new TIntermFunctionDefinition(funcProto, &body);
    
            ModifiedStructMachinery machinery;
            machinery.modifiedStruct                   = &modifiedStruct;
            machinery.getConverter(config.convertType) = funcDef;
    
            outMachineries.insert(originalStruct, machinery);
        }
    
        void pushPath(PathItem const &item)
        {
            pathItems.push_back(item);
    
            switch (item.type)
            {
                case PathItem::Type::Field:
                    pushNamePath(item.field->name().data());
                    break;
    
                case PathItem::Type::Index:
                    pushNamePath(item.index);
                    break;
    
                case PathItem::Type::FlattenArray:
                    namePathSizes.push_back(namePath.size());
                    break;
            }
        }
    
        void popPath()
        {
            ASSERT(!namePath.empty());
            ASSERT(!namePathSizes.empty());
            namePath.resize(namePathSizes.back());
            namePathSizes.pop_back();
    
            ASSERT(!pathItems.empty());
            pathItems.pop_back();
        }
    
        void finalize(const bool allowPadding)
        {
            ASSERT(!finalized);
            finalized = true;
            introducePacking();
            ASSERT(metalLayoutTotal == Layout::Identity());
            // Only pad substructs. We don't want to pad the structure that contains all the UBOs, only
            // individual UBOs.
            if (allowPadding)
                introducePadding();
        }
    
        void addModifiedField(const TField &field,
                              TType &newType,
                              TLayoutBlockStorage storage,
                              TLayoutMatrixPacking packing,
                              const AddressSpace *addressSpace)
        {
            TLayoutQualifier layoutQualifier = newType.getLayoutQualifier();
            layoutQualifier.blockStorage     = storage;
            layoutQualifier.matrixPacking    = packing;
            newType.setLayoutQualifier(layoutQualifier);
    
            const ImmutableString pathName(namePath);
            TField *modifiedField = new TField(&newType, pathName, field.line(), field.symbolType());
            if (addressSpace)
            {
                symbolEnv.markAsPointer(*modifiedField, *addressSpace);
            }
            if (symbolEnv.isUBO(field))
            {
                symbolEnv.markAsUBO(*modifiedField);
            }
            modifiedFields.push_back(modifiedField);
        }
    
        void addConversion(const ConversionFunc &func)
        {
            ASSERT(!modifiedFields.empty());
            conversionInfos.push_back({func, nullptr, pathItems, modifiedFields.back()->name()});
        }
    
        void addConversion(const TFunction &func)
        {
            ASSERT(!modifiedFields.empty());
            conversionInfos.push_back({{}, &func, pathItems, modifiedFields.back()->name()});
        }
    
        bool hasPacking() const { return containsPacked; }
    
        bool hasPadding() const { return padFieldCount > 0; }
    
        bool recurse(const TStructure &structure,
                     ModifiedStructMachinery &outMachinery,
                     const bool isUBORecurse)
        {
            const ModifiedStructMachinery *m = outMachineries.find(structure);
            if (m == nullptr)
            {
                TranslatorMetalReflection *reflection =
                    ((sh::TranslatorMetalDirect *)&mCompiler)->getTranslatorMetalReflection();
                reflection->addOriginalName(structure.uniqueId().get(), structure.name().data());
                const Name name = idGen.createNewName(structure.name().data());
                if (!TryCreateModifiedStruct(mCompiler, symbolEnv, idGen, config, structure, name,
                                             outMachineries, isUBORecurse, true))
                {
                    return false;
                }
                m = outMachineries.find(structure);
                ASSERT(m);
            }
            outMachinery = *m;
            return true;
        }
    
        bool getIsUBO() const { return isUBO; }
    
      private:
        void addPadding(size_t padAmount, bool updateLayout)
        {
            if (padAmount == 0)
            {
                return;
            }
    
            const size_t begin = modifiedFields.size();
    
            // Iteratively adding in scalar or vector padding because some struct types will not
            // allow matrix or array members.
            while (padAmount > 0)
            {
                TType *padType;
                if (padAmount >= 16)
                {
                    padAmount -= 16;
                    padType = new TType(TBasicType::EbtFloat, 4);
                }
                else if (padAmount >= 8)
                {
                    padAmount -= 8;
                    padType = new TType(TBasicType::EbtFloat, 2);
                }
                else if (padAmount >= 4)
                {
                    padAmount -= 4;
                    padType = new TType(TBasicType::EbtFloat);
                }
                else if (padAmount >= 2)
                {
                    padAmount -= 2;
                    padType = new TType(TBasicType::EbtBool, 2);
                }
                else
                {
                    ASSERT(padAmount == 1);
                    padAmount -= 1;
                    padType = new TType(TBasicType::EbtBool);
                }
    
                if (padType->getBasicType() != EbtBool)
                {
                    padType->setPrecision(EbpLow);
                }
    
                if (updateLayout)
                {
                    metalLayoutTotal += MetalLayoutOf(*padType);
                }
    
                const Name name = idGen.createNewName("pad");
                modifiedFields.push_back(
                    new TField(padType, name.rawName(), kNoSourceLoc, name.symbolType()));
                ++padFieldCount;
            }
    
            std::reverse(modifiedFields.begin() + begin, modifiedFields.end());
        }
    
        void introducePacking()
        {
            if (!config.allowPacking)
            {
                return;
            }
    
            auto setUnpackedStorage = [](TType &type) {
                TLayoutBlockStorage storage = type.getLayoutQualifier().blockStorage;
                switch (storage)
                {
                    case TLayoutBlockStorage::EbsShared:
                        storage = TLayoutBlockStorage::EbsStd140;
                        break;
                    case TLayoutBlockStorage::EbsPacked:
                        storage = TLayoutBlockStorage::EbsStd430;
                        break;
                    case TLayoutBlockStorage::EbsStd140:
                    case TLayoutBlockStorage::EbsStd430:
                    case TLayoutBlockStorage::EbsUnspecified:
                        break;
                }
                SetBlockStorage(type, storage);
            };
    
            Layout glslLayoutTotal = Layout::Identity();
            const size_t size      = modifiedFields.size();
    
            for (size_t i = 0; i < size; ++i)
            {
                TField &curr           = *modifiedFields[i];
                TType &currType        = *curr.type();
                const bool canBePacked = CanBePacked(currType);
    
                auto dontPack = [&]() {
                    if (canBePacked)
                    {
                        setUnpackedStorage(currType);
                    }
                    glslLayoutTotal += GlslLayoutOf(currType);
                };
    
                if (!CanBePacked(currType))
                {
                    dontPack();
                    continue;
                }
    
                const Layout packedGlslLayout           = GlslLayoutOf(currType);
                const TLayoutBlockStorage packedStorage = currType.getLayoutQualifier().blockStorage;
                setUnpackedStorage(currType);
                const Layout unpackedGlslLayout = GlslLayoutOf(currType);
                SetBlockStorage(currType, packedStorage);
    
                ASSERT(packedGlslLayout.sizeOf <= unpackedGlslLayout.sizeOf);
                if (packedGlslLayout.sizeOf == unpackedGlslLayout.sizeOf)
                {
                    dontPack();
                    continue;
                }
    
                const size_t j = i + 1;
                if (j == size)
                {
                    dontPack();
                    break;
                }
    
                const size_t pad            = unpackedGlslLayout.sizeOf - packedGlslLayout.sizeOf;
                const TField &next          = *modifiedFields[j];
                const Layout nextGlslLayout = GlslLayoutOf(*next.type());
    
                if (pad < nextGlslLayout.sizeOf)
                {
                    dontPack();
                    continue;
                }
    
                symbolEnv.markAsPacked(curr);
                glslLayoutTotal += packedGlslLayout;
                containsPacked = true;
            }
        }
    
        void introducePadding()
        {
            if (!config.allowPadding)
            {
                return;
            }
    
            MetalLayoutOfConfig layoutConfig;
            layoutConfig.disablePacking             = !config.allowPacking;
            layoutConfig.assumeStructsAreTailPadded = true;
    
            TFieldList fields = std::move(modifiedFields);
            ASSERT(!fields.empty());  // GLSL requires at least one member.
    
            const TField *const first = fields.front();
    
            for (TField *field : fields)
            {
                const TType &type = *field->type();
    
                const Layout glslLayout  = GlslLayoutOf(type);
                const Layout metalLayout = MetalLayoutOf(type, layoutConfig);
    
                size_t prePadAmount = 0;
                if (glslLayout.alignOf > metalLayout.alignOf && field != first)
                {
                    const size_t prePaddedSize = metalLayoutTotal.sizeOf;
                    metalLayoutTotal.requireAlignment(glslLayout.alignOf, true);
                    const size_t paddedSize = metalLayoutTotal.sizeOf;
                    prePadAmount            = paddedSize - prePaddedSize;
                    metalLayoutTotal += metalLayout;
                    addPadding(prePadAmount, false);  // Note: requireAlignment() already updated layout
                }
                else
                {
                    metalLayoutTotal += metalLayout;
                }
    
                modifiedFields.push_back(field);
    
                if (glslLayout.sizeOf > metalLayout.sizeOf && field != fields.back())
                {
                    const bool updateLayout = true;  // XXX: Correct?
                    const size_t padAmount  = glslLayout.sizeOf - metalLayout.sizeOf;
                    addPadding(padAmount, updateLayout);
                }
            }
        }
    
        void pushNamePath(const char *extra)
        {
            ASSERT(extra && *extra != '\0');
            namePathSizes.push_back(namePath.size());
            const char *p = extra;
            if (namePath.empty())
            {
                namePath = p;
                return;
            }
            while (*p == '_')
            {
                ++p;
            }
            if (*p == '\0')
            {
                p = "x";
            }
            if (namePath.back() != '_')
            {
                namePath += '_';
            }
            namePath += p;
        }
    
        void pushNamePath(unsigned extra)
        {
            char buffer[std::numeric_limits<unsigned>::digits10 + 1];
            sprintf(buffer, "%u", extra);
            pushNamePath(buffer);
        }
    
      public:
        TCompiler &mCompiler;
        const ModifyStructConfig &config;
        SymbolEnv &symbolEnv;
    
      private:
        TFieldList &modifiedFields;
        Layout metalLayoutTotal = Layout::Identity();
        size_t padFieldCount    = 0;
        bool containsPacked     = false;
        bool finalized          = false;
    
        std::vector<PathItem> pathItems;
    
        std::vector<size_t> namePathSizes;
        std::string namePath;
    
        std::vector<ConversionInfo> conversionInfos;
        TSymbolTable &symbolTable;
        IdGen &idGen;
        ModifiedStructMachineries &outMachineries;
        const bool isUBO;
    };
    
    ////////////////////////////////////////////////////////////////////////////////
    
    using ModifyFunc = bool (*)(ConvertStructState &state,
                                const TField &field,
                                const TLayoutBlockStorage storage,
                                const TLayoutMatrixPacking packing);
    
    bool ModifyRecursive(ConvertStructState &state,
                         const TField &field,
                         const TLayoutBlockStorage storage,
                         const TLayoutMatrixPacking packing);
    
    bool IdentityModify(ConvertStructState &state,
                        const TField &field,
                        const TLayoutBlockStorage storage,
                        const TLayoutMatrixPacking packing)
    {
        const TType &type = *field.type();
        state.addModifiedField(field, CloneType(type), storage, packing, nullptr);
        state.addConversion([=](Access::Env &, OriginalAccess &o, ModifiedAccess &m) {
            return Access{o, m};
        });
        return false;
    }
    
    bool InlineStruct(ConvertStructState &state,
                      const TField &field,
                      const TLayoutBlockStorage storage,
                      const TLayoutMatrixPacking packing)
    {
        const TType &type              = *field.type();
        const TStructure *substructure = state.symbolEnv.remap(type.getStruct());
        if (!substructure)
        {
            return false;
        }
        if (type.isArray())
        {
            return false;
        }
        if (!state.config.inlineStruct(field))
        {
            return false;
        }
    
        const TFieldList &subfields = substructure->fields();
        for (const TField *subfield : subfields)
        {
            const TType &subtype                  = *subfield->type();
            const TLayoutBlockStorage substorage  = Overlay(storage, subtype);
            const TLayoutMatrixPacking subpacking = Overlay(packing, subtype);
            ModifyRecursive(state, *subfield, substorage, subpacking);
        }
    
        return true;
    }
    
    bool RecurseStruct(ConvertStructState &state,
                       const TField &field,
                       const TLayoutBlockStorage storage,
                       const TLayoutMatrixPacking packing)
    {
        const TType &type              = *field.type();
        const TStructure *substructure = state.symbolEnv.remap(type.getStruct());
        if (!substructure)
        {
            return false;
        }
        if (!state.config.recurseStruct(field))
        {
            return false;
        }
    
        ModifiedStructMachinery machinery;
        if (!state.recurse(*substructure, machinery, state.getIsUBO()))
        {
            return false;
        }
    
        TType &newType = *new TType(machinery.modifiedStruct, false);
        if (type.isArray())
        {
            newType.makeArrays(type.getArraySizes());
        }
    
        TIntermFunctionDefinition *converter = machinery.getConverter(state.config.convertType);
        ASSERT(converter);
    
        state.addModifiedField(field, newType, storage, packing, state.symbolEnv.isPointer(field));
        if (state.symbolEnv.isPointer(field))
        {
            state.symbolEnv.removePointer(field);
        }
        state.addConversion(*converter->getFunction());
    
        return true;
    }
    
    bool SplitMatrixColumns(ConvertStructState &state,
                            const TField &field,
                            const TLayoutBlockStorage storage,
                            const TLayoutMatrixPacking packing)
    {
        const TType &type = *field.type();
        if (!type.isMatrix())
        {
            return false;
        }
    
        if (!state.config.splitMatrixColumns(field))
        {
            return false;
        }
    
        const int cols = type.getCols();
        TType &rowType = DropColumns(type);
    
        for (int c = 0; c < cols; ++c)
        {
            state.pushPath(c);
    
            state.addModifiedField(field, rowType, storage, packing, state.symbolEnv.isPointer(field));
            if (state.symbolEnv.isPointer(field))
            {
                state.symbolEnv.removePointer(field);
            }
            state.addConversion([=](Access::Env &, OriginalAccess &o, ModifiedAccess &m) {
                return Access{o, m};
            });
    
            state.popPath();
        }
    
        return true;
    }
    
    bool SaturateMatrixRows(ConvertStructState &state,
                            const TField &field,
                            const TLayoutBlockStorage storage,
                            const TLayoutMatrixPacking packing)
    {
        const TType &type = *field.type();
        if (!type.isMatrix())
        {
            return false;
        }
        const bool isRowMajor = type.getLayoutQualifier().matrixPacking == EmpRowMajor;
        const int rows        = type.getRows();
        const int saturation  = state.config.saturateMatrixRows(field);
        if (saturation <= rows && !isRowMajor)
        {
            return false;
        }
    
        const int cols = type.getCols();
        TType &satType = SetMatrixRowDim(type, saturation);
        state.addModifiedField(field, satType, storage, packing, state.symbolEnv.isPointer(field));
        if (state.symbolEnv.isPointer(field))
        {
            state.symbolEnv.removePointer(field);
        }
    
        for (int c = 0; c < cols; ++c)
        {
            for (int r = 0; r < rows; ++r)
            {
                state.addConversion([=](Access::Env &, OriginalAccess &o, ModifiedAccess &m) {
                    int firstModifiedIndex  = isRowMajor ? r : c;
                    int secondModifiedIndex = isRowMajor ? c : r;
                    auto &o_                = AccessIndex(AccessIndex(o, c), r);
                    auto &m_ = AccessIndex(AccessIndex(m, firstModifiedIndex), secondModifiedIndex);
                    return Access{o_, m_};
                });
            }
        }
    
        return true;
    }
    
    bool TestBoolToUint(ConvertStructState &state, const TField &field)
    {
        if (field.type()->getBasicType() != TBasicType::EbtBool)
        {
            return false;
        }
        if (!state.config.promoteBoolToUint(field))
        {
            return false;
        }
        return true;
    }
    
    Access ConvertBoolToUint(ConvertType convertType, OriginalAccess &o, ModifiedAccess &m)
    {
        auto coerce = [](TIntermTyped &to, TIntermTyped &from) -> TIntermTyped & {
            return *TIntermAggregate::CreateConstructor(to.getType(), new TIntermSequence{&from});
        };
        switch (convertType)
        {
            case ConvertType::OriginalToModified:
                return Access{coerce(m, o), m};
            case ConvertType::ModifiedToOriginal:
                return Access{o, coerce(o, m)};
        }
    }
    
    bool SaturateScalarOrVectorCommon(ConvertStructState &state,
                                      const TField &field,
                                      const TLayoutBlockStorage storage,
                                      const TLayoutMatrixPacking packing,
                                      const bool array)
    {
        const TType &type = *field.type();
        if (type.isArray() != array)
        {
            return false;
        }
        if (!((type.isRank0() && HasScalarBasicType(type)) || type.isVector()))
        {
            return false;
        }
        const auto saturator =
            array ? state.config.saturateScalarOrVectorArrays : state.config.saturateScalarOrVector;
        const int dim        = type.getNominalSize();
        const int saturation = saturator(field);
        if (saturation <= dim)
        {
            return false;
        }
    
        TType &satType        = SetVectorDim(type, saturation);
        const bool boolToUint = TestBoolToUint(state, field);
        if (boolToUint)
        {
            satType.setBasicType(TBasicType::EbtUInt);
        }
        state.addModifiedField(field, satType, storage, packing, state.symbolEnv.isPointer(field));
        if (state.symbolEnv.isPointer(field))
        {
            state.symbolEnv.removePointer(field);
        }
    
        for (int d = 0; d < dim; ++d)
        {
            state.addConversion([=](Access::Env &env, OriginalAccess &o, ModifiedAccess &m) {
                auto &o_ = dim > 1 ? AccessIndex(o, d) : o;
                auto &m_ = AccessIndex(m, d);
                if (boolToUint)
                {
                    return ConvertBoolToUint(env.type, o_, m_);
                }
                else
                {
                    return Access{o_, m_};
                }
            });
        }
    
        return true;
    }
    
    bool SaturateScalarOrVectorArrays(ConvertStructState &state,
                                      const TField &field,
                                      const TLayoutBlockStorage storage,
                                      const TLayoutMatrixPacking packing)
    {
        return SaturateScalarOrVectorCommon(state, field, storage, packing, true);
    }
    
    bool SaturateScalarOrVector(ConvertStructState &state,
                                const TField &field,
                                const TLayoutBlockStorage storage,
                                const TLayoutMatrixPacking packing)
    {
        return SaturateScalarOrVectorCommon(state, field, storage, packing, false);
    }
    
    bool PromoteBoolToUint(ConvertStructState &state,
                           const TField &field,
                           const TLayoutBlockStorage storage,
                           const TLayoutMatrixPacking packing)
    {
        if (!TestBoolToUint(state, field))
        {
            return false;
        }
    
        auto &promotedType = CloneType(*field.type());
        promotedType.setBasicType(TBasicType::EbtUInt);
        state.addModifiedField(field, promotedType, storage, packing, state.symbolEnv.isPointer(field));
        if (state.symbolEnv.isPointer(field))
        {
            state.symbolEnv.removePointer(field);
        }
    
        state.addConversion([=](Access::Env &env, OriginalAccess &o, ModifiedAccess &m) {
            return ConvertBoolToUint(env.type, o, m);
        });
    
        return true;
    }
    
    bool ModifyCommon(ConvertStructState &state,
                      const TField &field,
                      const TLayoutBlockStorage storage,
                      const TLayoutMatrixPacking packing)
    {
        ModifyFunc funcs[] = {
            InlineStruct,                  //
            RecurseStruct,                 //
            SplitMatrixColumns,            //
            SaturateMatrixRows,            //
            SaturateScalarOrVectorArrays,  //
            SaturateScalarOrVector,        //
            PromoteBoolToUint,             //
        };
    
        for (ModifyFunc func : funcs)
        {
            if (func(state, field, storage, packing))
            {
                return true;
            }
        }
    
        return IdentityModify(state, field, storage, packing);
    }
    
    bool InlineArray(ConvertStructState &state,
                     const TField &field,
                     const TLayoutBlockStorage storage,
                     const TLayoutMatrixPacking packing)
    {
        const TType &type = *field.type();
        if (!type.isArray())
        {
            return false;
        }
        if (!state.config.inlineArray(field))
        {
            return false;
        }
    
        const unsigned volume = type.getArraySizeProduct();
        const bool isMultiDim = type.isArrayOfArrays();
    
        auto &innermostType = InnermostType(type);
        const TField innermostField(&innermostType, field.name(), field.line(), field.symbolType());
    
        if (isMultiDim)
        {
            state.pushPath(FlattenArray());
        }
    
        for (unsigned i = 0; i < volume; ++i)
        {
            state.pushPath(i);
            ModifyCommon(state, innermostField, storage, packing);
            state.popPath();
        }
    
        if (isMultiDim)
        {
            state.popPath();
        }
    
        return true;
    }
    
    bool ModifyRecursive(ConvertStructState &state,
                         const TField &field,
                         const TLayoutBlockStorage storage,
                         const TLayoutMatrixPacking packing)
    {
        state.pushPath(field);
    
        bool modified;
        if (InlineArray(state, field, storage, packing))
        {
            modified = true;
        }
        else
        {
            modified = ModifyCommon(state, field, storage, packing);
        }
    
        state.popPath();
    
        return modified;
    }
    
    }  // anonymous namespace
    
    ////////////////////////////////////////////////////////////////////////////////
    
    bool sh::TryCreateModifiedStruct(TCompiler &compiler,
                                     SymbolEnv &symbolEnv,
                                     IdGen &idGen,
                                     const ModifyStructConfig &config,
                                     const TStructure &originalStruct,
                                     const Name &modifiedStructName,
                                     ModifiedStructMachineries &outMachineries,
                                     const bool isUBO,
                                     const bool allowPadding)
    {
        ConvertStructState state(compiler, symbolEnv, idGen, config, outMachineries, isUBO);
        size_t identicalFieldCount = 0;
    
        const TFieldList &originalFields = originalStruct.fields();
        for (TField *originalField : originalFields)
        {
            const TType &originalType          = *originalField->type();
            const TLayoutBlockStorage storage  = Overlay(config.initialBlockStorage, originalType);
            const TLayoutMatrixPacking packing = Overlay(config.initialMatrixPacking, originalType);
            if (!ModifyRecursive(state, *originalField, storage, packing))
            {
                ++identicalFieldCount;
            }
        }
    
        state.finalize(allowPadding);
    
        if (identicalFieldCount == originalFields.size() && !state.hasPacking() &&
            !state.hasPadding() && !isUBO)
        {
            return false;
        }
    
        state.publish(originalStruct, modifiedStructName);
    
        return true;
    }