Edit

kc3-lang/angle/src/compiler/translator/tree_ops/RewriteStructSamplers.cpp

Branch :

  • Show log

    Commit

  • Author : Kenneth Russell
    Date : 2021-07-28 00:36:12
    Hash : aec5e65c
    Message : Get direct-to-Metal backend to run angle_end2end_tests. Cherry-pick nameless struct fix from Apple in https://bugs.webkit.org/show_bug.cgi?id=227482 . Fix SeparateCompoundStructDeclarations pass to stop generating multiple declarations; thanks syoussefi@ for advice. Incorporate additional passes from TranslatorVulkan (MonomorphizeUnsupportedFunctionsInVulkanGLSL, RewriteArrayOfArrayOfOpaqueUniforms, SeparateStructFromUniformDeclarations) needed by RewriteStructSamplers pass in TranslatorMetalDirect. Fixes many assertion failures in GLSL tests. Moved these passes out of tree_ops/vulkan. Thanks again to syoussefi@ for advice and help. Disable a validation check related to the RewritePipelines pass. Skip two tests that were failing for other reasons. With these changes, angle_end2end_tests runs to completion when the direct-to-Metal backend is turned on. There are still ~1300 failures of the ~4000 tests which will be investigated next. Bug: angleproject:5505 Change-Id: Ibca77822543e8e8e8d2a8c862e92cdf74bfa3545 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3058524 Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org> Commit-Queue: Kenneth Russell <kbr@chromium.org>

  • src/compiler/translator/tree_ops/RewriteStructSamplers.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.
    //
    // RewriteStructSamplers: Extract samplers from structs.
    //
    
    #include "compiler/translator/tree_ops/RewriteStructSamplers.h"
    
    #include "compiler/translator/ImmutableStringBuilder.h"
    #include "compiler/translator/SymbolTable.h"
    #include "compiler/translator/tree_util/IntermNode_util.h"
    #include "compiler/translator/tree_util/IntermTraverse.h"
    
    namespace sh
    {
    namespace
    {
    
    // Used to map one structure type to another (one where the samplers are removed).
    struct StructureData
    {
        // The structure this was replaced with.  If nullptr, it means the structure is removed (because
        // it had all samplers).
        const TStructure *modified;
        // Indexed by the field index of original structure, to get the field index of the modified
        // structure.  For example:
        //
        //     struct Original
        //     {
        //         sampler2D s1;
        //         vec4 f1;
        //         sampler2D s2;
        //         sampler2D s3;
        //         vec4 f2;
        //     };
        //
        //     struct Modified
        //     {
        //         vec4 f1;
        //         vec4 f2;
        //     };
        //
        //     fieldMap:
        //         0 -> Invalid
        //         1 -> 0
        //         2 -> Invalid
        //         3 -> Invalid
        //         4 -> 1
        //
        TVector<int> fieldMap;
    };
    
    using StructureMap        = angle::HashMap<const TStructure *, StructureData>;
    using StructureUniformMap = angle::HashMap<const TVariable *, const TVariable *>;
    using ExtractedSamplerMap = angle::HashMap<std::string, const TVariable *>;
    
    TIntermTyped *RewriteModifiedStructFieldSelectionExpression(
        TCompiler *compiler,
        TIntermBinary *node,
        const StructureMap &structureMap,
        const StructureUniformMap &structureUniformMap,
        const ExtractedSamplerMap &extractedSamplers);
    
    TIntermTyped *RewriteExpressionVisitBinaryHelper(TCompiler *compiler,
                                                     TIntermBinary *node,
                                                     const StructureMap &structureMap,
                                                     const StructureUniformMap &structureUniformMap,
                                                     const ExtractedSamplerMap &extractedSamplers)
    {
        // Only interested in EOpIndexDirectStruct binary nodes.
        if (node->getOp() != EOpIndexDirectStruct)
        {
            return nullptr;
        }
    
        const TStructure *structure = node->getLeft()->getType().getStruct();
        ASSERT(structure);
    
        // If the result of the index is not a sampler and the struct is not replaced, there's nothing
        // to do.
        if (!node->getType().isSampler() && structureMap.find(structure) == structureMap.end())
        {
            return nullptr;
        }
    
        // Otherwise, replace the whole expression such that:
        //
        // - if sampler, it's indexed with whatever indices the parent structs were indexed with,
        // - otherwise, the chain of field selections is rewritten by modifying the base uniform so all
        //   the intermediate nodes would have the correct type (and therefore fields).
        ASSERT(structureMap.find(structure) != structureMap.end());
    
        return RewriteModifiedStructFieldSelectionExpression(compiler, node, structureMap,
                                                             structureUniformMap, extractedSamplers);
    }
    
    // Given an expression, this traverser calculates a new expression where sampler-in-structs are
    // replaced with their extracted ones, and field indices are adjusted for the rest of the fields.
    // In particular, this is run on the right node of EOpIndexIndirect binary nodes, so that the
    // expression in the index gets a chance to go through this transformation.
    class RewriteExpressionTraverser final : public TIntermTraverser
    {
      public:
        explicit RewriteExpressionTraverser(TCompiler *compiler,
                                            const StructureMap &structureMap,
                                            const StructureUniformMap &structureUniformMap,
                                            const ExtractedSamplerMap &extractedSamplers)
            : TIntermTraverser(true, false, false),
              mCompiler(compiler),
              mStructureMap(structureMap),
              mStructureUniformMap(structureUniformMap),
              mExtractedSamplers(extractedSamplers)
        {}
    
        bool visitBinary(Visit visit, TIntermBinary *node) override
        {
            TIntermTyped *rewritten = RewriteExpressionVisitBinaryHelper(
                mCompiler, node, mStructureMap, mStructureUniformMap, mExtractedSamplers);
    
            if (rewritten == nullptr)
            {
                return true;
            }
    
            queueReplacement(rewritten, OriginalNode::IS_DROPPED);
    
            // Don't iterate as the expression is rewritten.
            return false;
        }
    
        void visitSymbol(TIntermSymbol *node) override
        {
            // It's impossible to reach here with a symbol that needs replacement.
            // MonomorphizeUnsupportedFunctions makes sure that whole structs containing
            // samplers are not passed to functions, so any instance of the struct uniform is
            // necessarily indexed right away.  visitBinary should have already taken care of it.
            ASSERT(mStructureUniformMap.find(&node->variable()) == mStructureUniformMap.end());
        }
    
      private:
        TCompiler *mCompiler;
    
        // See RewriteStructSamplersTraverser.
        const StructureMap &mStructureMap;
        const StructureUniformMap &mStructureUniformMap;
        const ExtractedSamplerMap &mExtractedSamplers;
    };
    
    // Rewrite the index of an EOpIndexIndirect expression.  The root can never need replacing, because
    // it cannot be a sampler itself or of a struct type.
    void RewriteIndexExpression(TCompiler *compiler,
                                TIntermTyped *expression,
                                const StructureMap &structureMap,
                                const StructureUniformMap &structureUniformMap,
                                const ExtractedSamplerMap &extractedSamplers)
    {
        RewriteExpressionTraverser traverser(compiler, structureMap, structureUniformMap,
                                             extractedSamplers);
        expression->traverse(&traverser);
        bool valid = traverser.updateTree(compiler, expression);
        ASSERT(valid);
    }
    
    // Given an expression such as the following:
    //
    //                                                    EOpIndexDirectStruct (sampler)
    //                                                    /                  \
    //                                               EOpIndex*           field index
    //                                              /        \
    //                                EOpIndexDirectStruct   index 2
    //                                /                  \
    //                           EOpIndex*           field index
    //                          /        \
    //            EOpIndexDirectStruct   index 1
    //            /                  \
    //     Uniform Struct           field index
    //
    // produces:
    //
    //                                EOpIndex*
    //                                /      \
    //                           EOpIndex*  index 2
    //                          /        \
    //                      sampler    index 1
    //
    // Alternatively, if the expression is as such:
    //
    //                                                    EOpIndexDirectStruct
    //                                                    /                  \
    //                        (modified struct type) EOpIndex*           field index
    //                                              /        \
    //                                EOpIndexDirectStruct   index 2
    //                                /                  \
    //                           EOpIndex*           field index
    //                          /        \
    //            EOpIndexDirectStruct   index 1
    //            /                  \
    //     Uniform Struct           field index
    //
    // produces:
    //
    //                                                    EOpIndexDirectStruct
    //                                                    /                  \
    //                                               EOpIndex*     mapped field index
    //                                              /        \
    //                                EOpIndexDirectStruct   index 2
    //                                /                  \
    //                           EOpIndex*      mapped field index
    //                          /        \
    //            EOpIndexDirectStruct   index 1
    //            /                  \
    //     Uniform Struct     mapped field index
    //
    TIntermTyped *RewriteModifiedStructFieldSelectionExpression(
        TCompiler *compiler,
        TIntermBinary *node,
        const StructureMap &structureMap,
        const StructureUniformMap &structureUniformMap,
        const ExtractedSamplerMap &extractedSamplers)
    {
        ASSERT(node->getOp() == EOpIndexDirectStruct);
    
        const bool isSampler = node->getType().isSampler();
    
        TIntermSymbol *baseUniform = nullptr;
        std::string samplerName;
    
        TVector<TIntermBinary *> indexNodeStack;
    
        // Iterate once and build the name of the sampler.
        TIntermBinary *iter = node;
        while (baseUniform == nullptr)
        {
            indexNodeStack.push_back(iter);
            baseUniform = iter->getLeft()->getAsSymbolNode();
    
            if (isSampler)
            {
                if (iter->getOp() == EOpIndexDirectStruct)
                {
                    // When indexed into a struct, get the field name instead and construct the sampler
                    // name.
                    samplerName.insert(0, iter->getIndexStructFieldName().data());
                    samplerName.insert(0, "_");
                }
    
                if (baseUniform)
                {
                    // If left is a symbol, we have reached the end of the chain.  Use the struct name
                    // to finish building the name of the sampler.
                    samplerName.insert(0, baseUniform->variable().name().data());
                }
            }
    
            iter = iter->getLeft()->getAsBinaryNode();
        }
    
        TIntermTyped *rewritten = nullptr;
    
        if (isSampler)
        {
            ASSERT(extractedSamplers.find(samplerName) != extractedSamplers.end());
            rewritten = new TIntermSymbol(extractedSamplers.at(samplerName));
        }
        else
        {
            const TVariable *baseUniformVar = &baseUniform->variable();
            ASSERT(structureUniformMap.find(baseUniformVar) != structureUniformMap.end());
            rewritten = new TIntermSymbol(structureUniformMap.at(baseUniformVar));
        }
    
        // Iterate again and build the expression from bottom up.
        for (auto it = indexNodeStack.rbegin(); it != indexNodeStack.rend(); ++it)
        {
            TIntermBinary *indexNode = *it;
    
            switch (indexNode->getOp())
            {
                case EOpIndexDirectStruct:
                    if (!isSampler)
                    {
                        // Remap the field.
                        const TStructure *structure = indexNode->getLeft()->getType().getStruct();
                        ASSERT(structureMap.find(structure) != structureMap.end());
    
                        TIntermConstantUnion *asConstantUnion =
                            indexNode->getRight()->getAsConstantUnion();
                        ASSERT(asConstantUnion);
    
                        const int fieldIndex = asConstantUnion->getIConst(0);
                        ASSERT(fieldIndex <
                               static_cast<int>(structureMap.at(structure).fieldMap.size()));
    
                        const int mappedFieldIndex = structureMap.at(structure).fieldMap[fieldIndex];
    
                        rewritten = new TIntermBinary(EOpIndexDirectStruct, rewritten,
                                                      CreateIndexNode(mappedFieldIndex));
                    }
                    break;
    
                case EOpIndexDirect:
                    rewritten = new TIntermBinary(EOpIndexDirect, rewritten, indexNode->getRight());
                    break;
    
                case EOpIndexIndirect:
                {
                    // Run RewriteExpressionTraverser on the right node.  It may itself be an expression
                    // with a sampler inside that needs to be rewritten, or simply use a field of a
                    // struct that's remapped.
                    TIntermTyped *indexExpression = indexNode->getRight();
                    RewriteIndexExpression(compiler, indexExpression, structureMap, structureUniformMap,
                                           extractedSamplers);
                    rewritten = new TIntermBinary(EOpIndexIndirect, rewritten, indexExpression);
                    break;
                }
    
                default:
                    UNREACHABLE();
                    break;
            }
        }
    
        return rewritten;
    }
    
    class RewriteStructSamplersTraverser final : public TIntermTraverser
    {
      public:
        explicit RewriteStructSamplersTraverser(TCompiler *compiler, TSymbolTable *symbolTable)
            : TIntermTraverser(true, false, false, symbolTable),
              mCompiler(compiler),
              mRemovedUniformsCount(0)
        {}
    
        int removedUniformsCount() const { return mRemovedUniformsCount; }
    
        // Each struct sampler declaration is stripped of its samplers. New uniforms are added for each
        // stripped struct sampler.
        bool visitDeclaration(Visit visit, TIntermDeclaration *decl) override
        {
            if (!mInGlobalScope)
            {
                return true;
            }
    
            const TIntermSequence &sequence = *(decl->getSequence());
            TIntermTyped *declarator        = sequence.front()->getAsTyped();
            const TType &type               = declarator->getType();
    
            if (!type.isStructureContainingSamplers())
            {
                return false;
            }
    
            TIntermSequence newSequence;
    
            if (type.isStructSpecifier())
            {
                // If this is just a struct definition (not a uniform variable declaration of a
                // struct type), just remove the samplers.  They are not instantiated yet.
                const TStructure *structure = type.getStruct();
                ASSERT(structure && mStructureMap.find(structure) == mStructureMap.end());
    
                stripStructSpecifierSamplers(structure, &newSequence);
            }
            else
            {
                const TStructure *structure = type.getStruct();
    
                // If the structure is defined at the same time, create the mapping to the stripped
                // version first.
                if (mStructureMap.find(structure) == mStructureMap.end())
                {
                    stripStructSpecifierSamplers(structure, &newSequence);
                }
    
                // Then, extract the samplers from the struct and create global-scope variables instead.
                TIntermSymbol *asSymbol = declarator->getAsSymbolNode();
                ASSERT(asSymbol);
                const TVariable &variable = asSymbol->variable();
                ASSERT(variable.symbolType() != SymbolType::Empty);
    
                extractStructSamplerUniforms(variable, structure, &newSequence);
            }
    
            mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), decl,
                                            std::move(newSequence));
    
            return false;
        }
    
        // Same implementation as in RewriteExpressionTraverser.  That traverser cannot replace root.
        bool visitBinary(Visit visit, TIntermBinary *node) override
        {
            TIntermTyped *rewritten = RewriteExpressionVisitBinaryHelper(
                mCompiler, node, mStructureMap, mStructureUniformMap, mExtractedSamplers);
    
            if (rewritten == nullptr)
            {
                return true;
            }
    
            queueReplacement(rewritten, OriginalNode::IS_DROPPED);
    
            // Don't iterate as the expression is rewritten.
            return false;
        }
    
        // Same implementation as in RewriteExpressionTraverser.  That traverser cannot replace root.
        void visitSymbol(TIntermSymbol *node) override
        {
            ASSERT(mStructureUniformMap.find(&node->variable()) == mStructureUniformMap.end());
        }
    
      private:
        // Removes all samplers from a struct specifier.
        void stripStructSpecifierSamplers(const TStructure *structure, TIntermSequence *newSequence)
        {
            TFieldList *newFieldList = new TFieldList;
            ASSERT(structure->containsSamplers());
    
            // Add this struct to the struct map
            ASSERT(mStructureMap.find(structure) == mStructureMap.end());
            StructureData *modifiedData = &mStructureMap[structure];
    
            modifiedData->modified = nullptr;
            modifiedData->fieldMap.resize(structure->fields().size(), std::numeric_limits<int>::max());
    
            for (size_t fieldIndex = 0; fieldIndex < structure->fields().size(); ++fieldIndex)
            {
                const TField *field    = structure->fields()[fieldIndex];
                const TType &fieldType = *field->type();
    
                // If the field is a sampler, or a struct that's entirely removed, skip it.
                if (!fieldType.isSampler() && !isRemovedStructType(fieldType))
                {
                    TType *newType = nullptr;
    
                    // Otherwise, if it's a struct that's replaced, create a new field of the replaced
                    // type.
                    if (fieldType.isStructureContainingSamplers())
                    {
                        const TStructure *fieldStruct = fieldType.getStruct();
                        ASSERT(mStructureMap.find(fieldStruct) != mStructureMap.end());
    
                        const TStructure *modifiedStruct = mStructureMap[fieldStruct].modified;
                        ASSERT(modifiedStruct);
    
                        newType = new TType(modifiedStruct, true);
                        if (fieldType.isArray())
                        {
                            newType->makeArrays(fieldType.getArraySizes());
                        }
                    }
                    else
                    {
                        // If not, duplicate the field as is.
                        newType = new TType(fieldType);
                    }
    
                    // Record the mapping of the field indices, so future EOpIndexDirectStruct's into
                    // this struct can be fixed up.
                    modifiedData->fieldMap[fieldIndex] = static_cast<int>(newFieldList->size());
    
                    TField *newField =
                        new TField(newType, field->name(), field->line(), field->symbolType());
                    newFieldList->push_back(newField);
                }
            }
    
            // Prune empty structs.
            if (newFieldList->empty())
            {
                return;
            }
    
            // Declare a new struct with the same name and the new fields.
            modifiedData->modified =
                new TStructure(mSymbolTable, structure->name(), newFieldList, structure->symbolType());
            TType *newStructType = new TType(modifiedData->modified, true);
            TVariable *newStructVar =
                new TVariable(mSymbolTable, kEmptyImmutableString, newStructType, SymbolType::Empty);
            TIntermSymbol *newStructRef = new TIntermSymbol(newStructVar);
    
            TIntermDeclaration *structDecl = new TIntermDeclaration;
            structDecl->appendDeclarator(newStructRef);
    
            newSequence->push_back(structDecl);
        }
    
        // Returns true if the type is a struct that was removed because we extracted all the members.
        bool isRemovedStructType(const TType &type) const
        {
            const TStructure *structure = type.getStruct();
            if (structure == nullptr)
            {
                // Not a struct
                return false;
            }
    
            // A struct is removed if it is in the map, but doesn't have a replacement struct.
            auto iter = mStructureMap.find(structure);
            return iter != mStructureMap.end() && iter->second.modified == nullptr;
        }
    
        // Removes samplers from struct uniforms. For each sampler removed also adds a new globally
        // defined sampler uniform.
        void extractStructSamplerUniforms(const TVariable &variable,
                                          const TStructure *structure,
                                          TIntermSequence *newSequence)
        {
            ASSERT(structure->containsSamplers());
            ASSERT(mStructureMap.find(structure) != mStructureMap.end());
    
            const TType &type = variable.getType();
            enterArray(type);
    
            for (const TField *field : structure->fields())
            {
                extractFieldSamplers(variable.name().data(), field, newSequence);
            }
    
            // If there's a replacement structure (because there are non-sampler fields in the struct),
            // add a declaration with that type.
            const TStructure *modified = mStructureMap[structure].modified;
            if (modified != nullptr)
            {
                TType *newType = new TType(modified, false);
                if (type.isArray())
                {
                    newType->makeArrays(type.getArraySizes());
                }
                newType->setQualifier(EvqUniform);
                const TVariable *newVariable =
                    new TVariable(mSymbolTable, variable.name(), newType, variable.symbolType());
    
                TIntermDeclaration *newDecl = new TIntermDeclaration();
                newDecl->appendDeclarator(new TIntermSymbol(newVariable));
    
                newSequence->push_back(newDecl);
    
                ASSERT(mStructureUniformMap.find(&variable) == mStructureUniformMap.end());
                mStructureUniformMap[&variable] = newVariable;
            }
            else
            {
                mRemovedUniformsCount++;
            }
    
            exitArray(type);
        }
    
        // Extracts samplers from a field of a struct. Works with nested structs and arrays.
        void extractFieldSamplers(const std::string &prefix,
                                  const TField *field,
                                  TIntermSequence *newSequence)
        {
            const TType &fieldType = *field->type();
            if (fieldType.isSampler() || fieldType.isStructureContainingSamplers())
            {
                std::string newPrefix = prefix + "_" + field->name().data();
    
                if (fieldType.isSampler())
                {
                    extractSampler(newPrefix, fieldType, newSequence);
                }
                else
                {
                    enterArray(fieldType);
                    const TStructure *structure = fieldType.getStruct();
                    for (const TField *nestedField : structure->fields())
                    {
                        extractFieldSamplers(newPrefix, nestedField, newSequence);
                    }
                    exitArray(fieldType);
                }
            }
        }
    
        void GenerateArraySizesFromStack(TVector<unsigned int> *sizesOut)
        {
            sizesOut->reserve(mArraySizeStack.size());
    
            for (auto it = mArraySizeStack.rbegin(); it != mArraySizeStack.rend(); ++it)
            {
                sizesOut->push_back(*it);
            }
        }
    
        // Extracts a sampler from a struct. Declares the new extracted sampler.
        void extractSampler(const std::string &newName,
                            const TType &fieldType,
                            TIntermSequence *newSequence)
        {
            ASSERT(fieldType.isSampler());
    
            TType *newType = new TType(fieldType);
    
            // Add array dimensions accumulated so far due to struct arrays.  Note that to support
            // nested arrays, mArraySizeStack has the outermost size in the front.  |makeArrays| thus
            // expects this in reverse order.
            TVector<unsigned int> parentArraySizes;
            GenerateArraySizesFromStack(&parentArraySizes);
            newType->makeArrays(parentArraySizes);
    
            ImmutableStringBuilder nameBuilder(newName.size() + 1);
            nameBuilder << newName;
    
            newType->setQualifier(EvqUniform);
            TVariable *newVariable =
                new TVariable(mSymbolTable, nameBuilder, newType, SymbolType::AngleInternal);
            TIntermSymbol *newSymbol = new TIntermSymbol(newVariable);
    
            TIntermDeclaration *samplerDecl = new TIntermDeclaration;
            samplerDecl->appendDeclarator(newSymbol);
    
            newSequence->push_back(samplerDecl);
    
            // TODO: Use a temp name instead of generating a name as currently done.  There is no
            // guarantee that these generated names cannot clash.  Create a mapping from the previous
            // name to the name assigned to the temp variable so ShaderVariable::mappedName can be
            // updated post-transformation.  http://anglebug.com/4301
            ASSERT(mExtractedSamplers.find(newName) == mExtractedSamplers.end());
            mExtractedSamplers[newName] = newVariable;
        }
    
        void enterArray(const TType &arrayType)
        {
            const TSpan<const unsigned int> &arraySizes = arrayType.getArraySizes();
            for (auto it = arraySizes.rbegin(); it != arraySizes.rend(); ++it)
            {
                unsigned int arraySize = *it;
                mArraySizeStack.push_back(arraySize);
            }
        }
    
        void exitArray(const TType &arrayType)
        {
            mArraySizeStack.resize(mArraySizeStack.size() - arrayType.getNumArraySizes());
        }
    
        TCompiler *mCompiler;
        int mRemovedUniformsCount;
    
        // Map structures with samplers to ones that have their samplers removed.
        StructureMap mStructureMap;
    
        // Map uniform variables of structure type that are replaced with another variable.
        StructureUniformMap mStructureUniformMap;
    
        // Map a constructed sampler name to its variable.  Used to replace an expression that uses this
        // sampler with the extracted one.
        ExtractedSamplerMap mExtractedSamplers;
    
        // A stack of array sizes.  Used to figure out the array dimensions of the extracted sampler,
        // for example when it's nested in an array of structs in an array of structs.
        TVector<unsigned int> mArraySizeStack;
    };
    }  // anonymous namespace
    
    bool RewriteStructSamplers(TCompiler *compiler,
                               TIntermBlock *root,
                               TSymbolTable *symbolTable,
                               int *removedUniformsCountOut)
    {
        RewriteStructSamplersTraverser traverser(compiler, symbolTable);
        root->traverse(&traverser);
        *removedUniformsCountOut = traverser.removedUniformsCount();
        return traverser.updateTree(compiler, root);
    }
    }  // namespace sh