Edit

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

Branch :

  • Show log

    Commit

  • Author : Jamie Madill
    Date : 2019-10-24 12:55:11
    Hash : 155947fc
    Message : Enable "-Wconditional-uninitialized". This is a final warning used by Skia. Bug: angleproject:4046 Change-Id: I3970e30e4bd2aef07cddadd7322ef120ac857493 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1877481 Reviewed-by: Jamie Madill <jmadill@chromium.org> Commit-Queue: Jamie Madill <jmadill@chromium.org>

  • src/compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.cpp
  • //
    // Copyright 2019 The ANGLE Project Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    //
    // RewriteCubeMapSamplersAs2DArray: Change samplerCube samplers to sampler2DArray for seamful cube
    // map emulation.
    //
    
    #include "compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.h"
    
    #include "compiler/translator/Compiler.h"
    #include "compiler/translator/ImmutableStringBuilder.h"
    #include "compiler/translator/StaticType.h"
    #include "compiler/translator/SymbolTable.h"
    #include "compiler/translator/tree_util/IntermNode_util.h"
    #include "compiler/translator/tree_util/IntermTraverse.h"
    #include "compiler/translator/tree_util/ReplaceVariable.h"
    
    namespace sh
    {
    namespace
    {
    constexpr ImmutableString kCoordTransformFuncName("ANGLECubeMapCoordTransform");
    constexpr ImmutableString kCoordTransformFuncNameImplicit("ANGLECubeMapCoordTransformImplicit");
    
    TIntermTyped *DerivativeQuotient(TIntermTyped *u,
                                     TIntermTyped *du,
                                     TIntermTyped *v,
                                     TIntermTyped *dv,
                                     TIntermTyped *vRecip)
    {
        // (du v - dv u) / v^2
        return new TIntermBinary(
            EOpMul,
            new TIntermBinary(EOpSub, new TIntermBinary(EOpMul, du->deepCopy(), v->deepCopy()),
                              new TIntermBinary(EOpMul, dv->deepCopy(), u->deepCopy())),
            new TIntermBinary(EOpMul, vRecip->deepCopy(), vRecip->deepCopy()));
    }
    
    TIntermTyped *Swizzle1(TIntermTyped *array, int i)
    {
        return new TIntermSwizzle(array, {i});
    }
    
    TIntermTyped *IndexDirect(TIntermTyped *array, int i)
    {
        return new TIntermBinary(EOpIndexDirect, array, CreateIndexNode(i));
    }
    
    // Generated the common transformation in each coord transformation case.  See comment in
    // declareCoordTranslationFunction().  Called with P, dPdx and dPdy.
    void TransformXMajor(TIntermBlock *block,
                         TIntermTyped *x,
                         TIntermTyped *y,
                         TIntermTyped *z,
                         TIntermTyped *uc,
                         TIntermTyped *vc)
    {
        // uc = -sign(x)*z
        // vc = -y
        TIntermTyped *signX = new TIntermUnary(EOpSign, x->deepCopy(), nullptr);
    
        TIntermTyped *ucValue =
            new TIntermUnary(EOpNegative, new TIntermBinary(EOpMul, signX, z->deepCopy()), nullptr);
        TIntermTyped *vcValue = new TIntermUnary(EOpNegative, y->deepCopy(), nullptr);
    
        block->appendStatement(new TIntermBinary(EOpAssign, uc->deepCopy(), ucValue));
        block->appendStatement(new TIntermBinary(EOpAssign, vc->deepCopy(), vcValue));
    }
    
    void TransformDerivativeXMajor(TIntermBlock *block,
                                   TSymbolTable *symbolTable,
                                   TIntermTyped *x,
                                   TIntermTyped *y,
                                   TIntermTyped *z,
                                   TIntermTyped *dx,
                                   TIntermTyped *dy,
                                   TIntermTyped *dz,
                                   TIntermTyped *du,
                                   TIntermTyped *dv,
                                   TIntermTyped *xRecip)
    {
        // Only the magnitude of the derivative matters, so we ignore the sign(x)
        // and the negations.
        TIntermTyped *duValue = DerivativeQuotient(z, dz, x, dx, xRecip);
        TIntermTyped *dvValue = DerivativeQuotient(y, dy, x, dx, xRecip);
        duValue               = new TIntermBinary(EOpMul, duValue, CreateFloatNode(0.5f));
        dvValue               = new TIntermBinary(EOpMul, dvValue, CreateFloatNode(0.5f));
        block->appendStatement(new TIntermBinary(EOpAssign, du->deepCopy(), duValue));
        block->appendStatement(new TIntermBinary(EOpAssign, dv->deepCopy(), dvValue));
    }
    
    void TransformImplicitDerivativeXMajor(TIntermBlock *block,
                                           TIntermTyped *dOuter,
                                           TIntermTyped *du,
                                           TIntermTyped *dv)
    {
        block->appendStatement(
            new TIntermBinary(EOpAssign, du->deepCopy(), Swizzle1(dOuter->deepCopy(), 2)));
        block->appendStatement(
            new TIntermBinary(EOpAssign, dv->deepCopy(), Swizzle1(dOuter->deepCopy(), 1)));
    }
    
    void TransformYMajor(TIntermBlock *block,
                         TIntermTyped *x,
                         TIntermTyped *y,
                         TIntermTyped *z,
                         TIntermTyped *uc,
                         TIntermTyped *vc)
    {
        // uc = x
        // vc = sign(y)*z
        TIntermTyped *signY = new TIntermUnary(EOpSign, y->deepCopy(), nullptr);
    
        TIntermTyped *ucValue = x->deepCopy();
        TIntermTyped *vcValue = new TIntermBinary(EOpMul, signY, z->deepCopy());
    
        block->appendStatement(new TIntermBinary(EOpAssign, uc->deepCopy(), ucValue));
        block->appendStatement(new TIntermBinary(EOpAssign, vc->deepCopy(), vcValue));
    }
    
    void TransformDerivativeYMajor(TIntermBlock *block,
                                   TSymbolTable *symbolTable,
                                   TIntermTyped *x,
                                   TIntermTyped *y,
                                   TIntermTyped *z,
                                   TIntermTyped *dx,
                                   TIntermTyped *dy,
                                   TIntermTyped *dz,
                                   TIntermTyped *du,
                                   TIntermTyped *dv,
                                   TIntermTyped *yRecip)
    {
        // Only the magnitude of the derivative matters, so we ignore the sign(x)
        // and the negations.
        TIntermTyped *duValue = DerivativeQuotient(x, dx, y, dy, yRecip);
        TIntermTyped *dvValue = DerivativeQuotient(z, dz, y, dy, yRecip);
        duValue               = new TIntermBinary(EOpMul, duValue, CreateFloatNode(0.5f));
        dvValue               = new TIntermBinary(EOpMul, dvValue, CreateFloatNode(0.5f));
        block->appendStatement(new TIntermBinary(EOpAssign, du->deepCopy(), duValue));
        block->appendStatement(new TIntermBinary(EOpAssign, dv->deepCopy(), dvValue));
    }
    
    void TransformImplicitDerivativeYMajor(TIntermBlock *block,
                                           TIntermTyped *dOuter,
                                           TIntermTyped *du,
                                           TIntermTyped *dv)
    {
        block->appendStatement(
            new TIntermBinary(EOpAssign, du->deepCopy(), Swizzle1(dOuter->deepCopy(), 0)));
        block->appendStatement(
            new TIntermBinary(EOpAssign, dv->deepCopy(), Swizzle1(dOuter->deepCopy(), 2)));
    }
    
    void TransformZMajor(TIntermBlock *block,
                         TIntermTyped *x,
                         TIntermTyped *y,
                         TIntermTyped *z,
                         TIntermTyped *uc,
                         TIntermTyped *vc)
    {
        // uc = size(z)*x
        // vc = -y
        TIntermTyped *signZ = new TIntermUnary(EOpSign, z->deepCopy(), nullptr);
    
        TIntermTyped *ucValue = new TIntermBinary(EOpMul, signZ, x->deepCopy());
        TIntermTyped *vcValue = new TIntermUnary(EOpNegative, y->deepCopy(), nullptr);
    
        block->appendStatement(new TIntermBinary(EOpAssign, uc->deepCopy(), ucValue));
        block->appendStatement(new TIntermBinary(EOpAssign, vc->deepCopy(), vcValue));
    }
    
    void TransformDerivativeZMajor(TIntermBlock *block,
                                   TSymbolTable *symbolTable,
                                   TIntermTyped *x,
                                   TIntermTyped *y,
                                   TIntermTyped *z,
                                   TIntermTyped *dx,
                                   TIntermTyped *dy,
                                   TIntermTyped *dz,
                                   TIntermTyped *du,
                                   TIntermTyped *dv,
                                   TIntermTyped *zRecip)
    {
        // Only the magnitude of the derivative matters, so we ignore the sign(x)
        // and the negations.
        TIntermTyped *duValue = DerivativeQuotient(x, dx, z, dz, zRecip);
        TIntermTyped *dvValue = DerivativeQuotient(y, dy, z, dz, zRecip);
        duValue               = new TIntermBinary(EOpMul, duValue, CreateFloatNode(0.5f));
        dvValue               = new TIntermBinary(EOpMul, dvValue, CreateFloatNode(0.5f));
        block->appendStatement(new TIntermBinary(EOpAssign, du->deepCopy(), duValue));
        block->appendStatement(new TIntermBinary(EOpAssign, dv->deepCopy(), dvValue));
    }
    
    void TransformImplicitDerivativeZMajor(TIntermBlock *block,
                                           TIntermTyped *dOuter,
                                           TIntermTyped *du,
                                           TIntermTyped *dv)
    {
        block->appendStatement(
            new TIntermBinary(EOpAssign, du->deepCopy(), Swizzle1(dOuter->deepCopy(), 0)));
        block->appendStatement(
            new TIntermBinary(EOpAssign, dv->deepCopy(), Swizzle1(dOuter->deepCopy(), 1)));
    }
    
    class RewriteCubeMapSamplersAs2DArrayTraverser : public TIntermTraverser
    {
      public:
        RewriteCubeMapSamplersAs2DArrayTraverser(TSymbolTable *symbolTable, bool isFragmentShader)
            : TIntermTraverser(true, true, true, symbolTable),
              mCubeXYZToArrayUVL(nullptr),
              mCubeXYZToArrayUVLImplicit(nullptr),
              mIsFragmentShader(isFragmentShader),
              mCoordTranslationFunctionDecl(nullptr),
              mCoordTranslationFunctionImplicitDecl(nullptr)
        {}
    
        bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
        {
            if (visit != PreVisit)
            {
                return true;
            }
    
            const TIntermSequence &sequence = *(node->getSequence());
    
            TIntermTyped *variable = sequence.front()->getAsTyped();
            const TType &type      = variable->getType();
            bool isSamplerCube     = type.getQualifier() == EvqUniform && type.isSamplerCube();
    
            if (isSamplerCube)
            {
                // Samplers cannot have initializers, so the declaration must necessarily be a symbol.
                TIntermSymbol *samplerVariable = variable->getAsSymbolNode();
                ASSERT(samplerVariable != nullptr);
    
                declareSampler2DArray(&samplerVariable->variable(), node);
                return false;
            }
    
            return true;
        }
    
        void visitFunctionPrototype(TIntermFunctionPrototype *node) override
        {
            const TFunction *function = node->getFunction();
            // Go over the parameters and replace the samplerCube arguments with a sampler2DArray.
            mRetyper.visitFunctionPrototype();
            for (size_t paramIndex = 0; paramIndex < function->getParamCount(); ++paramIndex)
            {
                const TVariable *param = function->getParam(paramIndex);
                TVariable *replacement = convertFunctionParameter(node, param);
                if (replacement)
                {
                    mRetyper.replaceFunctionParam(param, replacement);
                }
            }
    
            TIntermFunctionPrototype *replacementPrototype =
                mRetyper.convertFunctionPrototype(mSymbolTable, function);
            if (replacementPrototype)
            {
                queueReplacement(replacementPrototype, OriginalNode::IS_DROPPED);
            }
        }
    
        bool visitAggregate(Visit visit, TIntermAggregate *node) override
        {
            if (visit == PreVisit)
            {
                mRetyper.preVisitAggregate();
            }
    
            if (visit != PostVisit)
            {
                return true;
            }
    
            if (node->getOp() == EOpCallBuiltInFunction)
            {
                convertBuiltinFunction(node);
            }
            else if (node->getOp() == EOpCallFunctionInAST)
            {
                TIntermAggregate *substituteCall = mRetyper.convertASTFunction(node);
                if (substituteCall)
                {
                    queueReplacement(substituteCall, OriginalNode::IS_DROPPED);
                }
            }
            mRetyper.postVisitAggregate();
    
            return true;
        }
    
        void visitSymbol(TIntermSymbol *symbol) override
        {
            if (!symbol->getType().isSamplerCube())
            {
                return;
            }
    
            const TVariable *samplerCubeVar = &symbol->variable();
    
            TIntermTyped *sampler2DArrayVar =
                new TIntermSymbol(mRetyper.getVariableReplacement(samplerCubeVar));
            ASSERT(sampler2DArrayVar != nullptr);
    
            TIntermNode *argument = symbol;
    
            // We need to replace the whole function call argument with the symbol replaced.  The
            // argument can either be the sampler (array) itself, or a subscript into a sampler array.
            TIntermBinary *arrayExpression = getParentNode()->getAsBinaryNode();
            if (arrayExpression)
            {
                ASSERT(arrayExpression->getOp() == EOpIndexDirect ||
                       arrayExpression->getOp() == EOpIndexIndirect);
    
                argument = arrayExpression;
    
                sampler2DArrayVar = new TIntermBinary(arrayExpression->getOp(), sampler2DArrayVar,
                                                      arrayExpression->getRight()->deepCopy());
            }
    
            mRetyper.replaceFunctionCallArg(argument, sampler2DArrayVar);
        }
    
        TIntermFunctionDefinition *getCoordTranslationFunctionDecl()
        {
            return mCoordTranslationFunctionDecl;
        }
    
        TIntermFunctionDefinition *getCoordTranslationFunctionDeclImplicit()
        {
            return mCoordTranslationFunctionImplicitDecl;
        }
    
      private:
        void declareSampler2DArray(const TVariable *samplerCubeVar, TIntermDeclaration *node)
        {
            if (mCubeXYZToArrayUVL == nullptr)
            {
                // If not done yet, declare the function that transforms cube map texture sampling
                // coordinates to face index and uv coordinates.
                declareCoordTranslationFunction(false, kCoordTransformFuncName, &mCubeXYZToArrayUVL,
                                                &mCoordTranslationFunctionDecl);
            }
            if (mCubeXYZToArrayUVLImplicit == nullptr && mIsFragmentShader)
            {
                declareCoordTranslationFunction(true, kCoordTransformFuncNameImplicit,
                                                &mCubeXYZToArrayUVLImplicit,
                                                &mCoordTranslationFunctionImplicitDecl);
            }
    
            TType *newType = new TType(samplerCubeVar->getType());
            newType->setBasicType(EbtSampler2DArray);
    
            TVariable *sampler2DArrayVar =
                new TVariable(mSymbolTable, samplerCubeVar->name(), newType, SymbolType::UserDefined);
    
            TIntermDeclaration *sampler2DArrayDecl = new TIntermDeclaration();
            sampler2DArrayDecl->appendDeclarator(new TIntermSymbol(sampler2DArrayVar));
    
            TIntermSequence replacement;
            replacement.push_back(sampler2DArrayDecl);
            mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, replacement);
    
            // Remember the sampler2DArray variable.
            mRetyper.replaceGlobalVariable(samplerCubeVar, sampler2DArrayVar);
        }
    
        void declareCoordTranslationFunction(bool implicit,
                                             const ImmutableString &name,
                                             TFunction **functionOut,
                                             TIntermFunctionDefinition **declOut)
        {
            // GLES2.0 (as well as desktop OpenGL 2.0) define the coordination transformation as
            // follows.  Given xyz cube coordinates, where each channel is in [-1, 1], the following
            // table calculates uc, vc and ma as well as the cube map face.
            //
            //    Major    Axis Direction Target     uc  vc  ma
            //     +x   TEXTURE_CUBE_MAP_POSITIVE_X  −z  −y  |x|
            //     −x   TEXTURE_CUBE_MAP_NEGATIVE_X   z  −y  |x|
            //     +y   TEXTURE_CUBE_MAP_POSITIVE_Y   x   z  |y|
            //     −y   TEXTURE_CUBE_MAP_NEGATIVE_Y   x  −z  |y|
            //     +z   TEXTURE_CUBE_MAP_POSITIVE_Z   x  −y  |z|
            //     −z   TEXTURE_CUBE_MAP_NEGATIVE_Z  −x  −y  |z|
            //
            // "Major" is an indication of the axis with the largest value.  The cube map face indicates
            // the layer to sample from.  The uv coordinates to sample from are calculated as,
            // effectively transforming the uv values to [0, 1]:
            //
            //     u = (1 + uc/ma) / 2
            //     v = (1 + vc/ma) / 2
            //
            // The function can be implemented as 6 ifs, though it would be far from efficient.  The
            // following calculations implement the table above in a smaller number of instructions.
            //
            // First, ma can be calculated as the max of the three axes.
            //
            //     ma = max3(|x|, |y|, |z|)
            //
            // We have three cases:
            //
            //     ma == |x|:      uc = -sign(x)*z
            //                     vc = -y
            //                  layer = float(x < 0)
            //
            //     ma == |y|:      uc = x
            //                     vc = sign(y)*z
            //                  layer = 2 + float(y < 0)
            //
            //     ma == |z|:      uc = size(z)*x
            //                     vc = -y
            //                  layer = 4 + float(z < 0)
            //
            // This can be implemented with a number of ?: instructions or 3 ifs. ?: would require all
            // expressions to be evaluated (vector ALU) while if would require exec mask and jumps
            // (scalar operations).  We implement this using ifs as there would otherwise be many vector
            // operations and not much of anything else.
            //
            // If textureCubeGrad is used, we also need to transform the provided dPdx and dPdy (both
            // vec3) to a dUVdx and dUVdy.  Assume P=(r,s,t) and we are investigating dx (note the
            // change from xyz to rst to not confuse with dx and dy):
            //
            //     uv = (f(r,s,t)/ma + 1)/2
            //
            // Where f is one of the transformations above for uc and vc.  Between two neighbors along
            // the x axis, we have P0=(r0,s0,t0) and P1=(r1,s1,t1)
            //
            //     dP = (r1-r0, s1-s0, t1-t0)
            //     dUV = (f(r1,s1,t1)/ma1 - g(r0,s0,t0)/ma0) / 2
            //
            // f and g may not necessarily be the same because the two points may have different major
            // axes.  Even with the same major access, the sign that's used in the formulas may not be
            // the same.  Furthermore, ma0 and ma1 may not be the same.  This makes it impossible to
            // derive dUV from dP exactly.
            //
            // However, gradient transformation is implementation dependant, so we will simplify and
            // assume all the above complications are non-existent.  We therefore have:
            //
            //      dUV = (f(r1,s1,t1)/ma0 - f(r0,s0,t0)/ma0)/2
            //
            // Given that we assumed the sign functions are returning identical results for the two
            // points, f becomes a linear transformation.  Thus:
            //
            //      dUV = f(r1-r0,s1-0,t1-t0)/ma0/2
            //
            // In other words, we use the same formulae that transform XYZ (RST here) to UV to
            // transform the derivatives.
            //
            //     ma == |x|:    dUdx = -sign(x)*dPdx.z / ma / 2
            //                   dVdx = -dPdx.y / ma / 2
            //
            //     ma == |y|:    dUdx = dPdx.x / ma / 2
            //                   dVdx = sign(y)*dPdx.z / ma / 2
            //
            //     ma == |z|:    dUdx = size(z)*dPdx.x / ma / 2
            //                   dVdx = -dPdx.y / ma / 2
            //
            // Similarly for dy.
    
            // Create the function parameters: vec3 P, vec3 dPdx, vec3 dPdy,
            //                                 out vec2 dUVdx, out vec2 dUVdy
            const TType *vec3Type = StaticType::GetBasic<EbtFloat, 3>();
            TVariable *pVar =
                new TVariable(mSymbolTable, ImmutableString("P"), vec3Type, SymbolType::AngleInternal);
            TVariable *dPdxVar = new TVariable(mSymbolTable, ImmutableString("dPdx"), vec3Type,
                                               SymbolType::AngleInternal);
            TVariable *dPdyVar = new TVariable(mSymbolTable, ImmutableString("dPdy"), vec3Type,
                                               SymbolType::AngleInternal);
    
            const TType *vec2Type = StaticType::GetBasic<EbtFloat, 2>();
            TType *outVec2Type    = new TType(*vec2Type);
            outVec2Type->setQualifier(EvqOut);
    
            TVariable *dUVdxVar = new TVariable(mSymbolTable, ImmutableString("dUVdx"), outVec2Type,
                                                SymbolType::AngleInternal);
            TVariable *dUVdyVar = new TVariable(mSymbolTable, ImmutableString("dUVdy"), outVec2Type,
                                                SymbolType::AngleInternal);
    
            TIntermSymbol *p     = new TIntermSymbol(pVar);
            TIntermSymbol *dPdx  = new TIntermSymbol(dPdxVar);
            TIntermSymbol *dPdy  = new TIntermSymbol(dPdyVar);
            TIntermSymbol *dUVdx = new TIntermSymbol(dUVdxVar);
            TIntermSymbol *dUVdy = new TIntermSymbol(dUVdyVar);
    
            // Create the function body as statements are generated.
            TIntermBlock *body = new TIntermBlock;
    
            // Create the swizzle nodes that will be used in multiple expressions:
            TIntermSwizzle *x = new TIntermSwizzle(p->deepCopy(), {0});
            TIntermSwizzle *y = new TIntermSwizzle(p->deepCopy(), {1});
            TIntermSwizzle *z = new TIntermSwizzle(p->deepCopy(), {2});
    
            // Create abs and "< 0" expressions from the channels.
            const TType *floatType = StaticType::GetBasic<EbtFloat>();
    
            TIntermTyped *isNegX = new TIntermBinary(EOpLessThan, x, CreateZeroNode(*floatType));
            TIntermTyped *isNegY = new TIntermBinary(EOpLessThan, y, CreateZeroNode(*floatType));
            TIntermTyped *isNegZ = new TIntermBinary(EOpLessThan, z, CreateZeroNode(*floatType));
    
            TIntermSymbol *absX = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
            TIntermSymbol *absY = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
            TIntermSymbol *absZ = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
    
            TIntermDeclaration *absXDecl = CreateTempInitDeclarationNode(
                &absX->variable(), new TIntermUnary(EOpAbs, x->deepCopy(), nullptr));
            TIntermDeclaration *absYDecl = CreateTempInitDeclarationNode(
                &absY->variable(), new TIntermUnary(EOpAbs, y->deepCopy(), nullptr));
            TIntermDeclaration *absZDecl = CreateTempInitDeclarationNode(
                &absZ->variable(), new TIntermUnary(EOpAbs, z->deepCopy(), nullptr));
    
            body->appendStatement(absXDecl);
            body->appendStatement(absYDecl);
            body->appendStatement(absZDecl);
    
            // Create temporary variable for division outer product matrix and its
            // derivatives.
            // recipOuter[i][j] = 0.5 * P[j] / P[i]
            const TType *mat3Type     = StaticType::GetBasic<EbtFloat, 3, 3>();
            TIntermSymbol *recipOuter = new TIntermSymbol(CreateTempVariable(mSymbolTable, mat3Type));
    
            TIntermTyped *pRecip     = new TIntermBinary(EOpDiv, CreateFloatNode(1.0), p->deepCopy());
            TIntermSymbol *pRecipVar = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type));
    
            body->appendStatement(CreateTempInitDeclarationNode(&pRecipVar->variable(), pRecip));
    
            TIntermDeclaration *recipOuterDecl = CreateTempInitDeclarationNode(
                &recipOuter->variable(),
                CreateBuiltInFunctionCallNode(
                    "outerProduct",
                    new TIntermSequence(
                        {p->deepCopy(), new TIntermBinary(EOpVectorTimesScalar, CreateFloatNode(0.5),
                                                          pRecipVar->deepCopy())}),
                    *mSymbolTable, 300));
            body->appendStatement(recipOuterDecl);
    
            TIntermSymbol *dPDXdx = nullptr;
            TIntermSymbol *dPDYdx = nullptr;
            TIntermSymbol *dPDZdx = nullptr;
            TIntermSymbol *dPDXdy = nullptr;
            TIntermSymbol *dPDYdy = nullptr;
            TIntermSymbol *dPDZdy = nullptr;
            if (implicit)
            {
                dPDXdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type));
                dPDYdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type));
                dPDZdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type));
                dPDXdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type));
                dPDYdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type));
                dPDZdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type));
    
                TIntermDeclaration *dPDXdxDecl = CreateTempInitDeclarationNode(
                    &dPDXdx->variable(),
                    new TIntermUnary(EOpDFdx, IndexDirect(recipOuter, 0)->deepCopy(), nullptr));
                TIntermDeclaration *dPDYdxDecl = CreateTempInitDeclarationNode(
                    &dPDYdx->variable(),
                    new TIntermUnary(EOpDFdx, IndexDirect(recipOuter, 1)->deepCopy(), nullptr));
                TIntermDeclaration *dPDZdxDecl = CreateTempInitDeclarationNode(
                    &dPDZdx->variable(),
                    new TIntermUnary(EOpDFdx, IndexDirect(recipOuter, 2)->deepCopy(), nullptr));
                TIntermDeclaration *dPDXdyDecl = CreateTempInitDeclarationNode(
                    &dPDXdy->variable(),
                    new TIntermUnary(EOpDFdy, IndexDirect(recipOuter, 0)->deepCopy(), nullptr));
                TIntermDeclaration *dPDYdyDecl = CreateTempInitDeclarationNode(
                    &dPDYdy->variable(),
                    new TIntermUnary(EOpDFdy, IndexDirect(recipOuter, 1)->deepCopy(), nullptr));
                TIntermDeclaration *dPDZdyDecl = CreateTempInitDeclarationNode(
                    &dPDZdy->variable(),
                    new TIntermUnary(EOpDFdy, IndexDirect(recipOuter, 2)->deepCopy(), nullptr));
    
                body->appendStatement(dPDXdxDecl);
                body->appendStatement(dPDYdxDecl);
                body->appendStatement(dPDZdxDecl);
                body->appendStatement(dPDXdyDecl);
                body->appendStatement(dPDYdyDecl);
                body->appendStatement(dPDZdyDecl);
            }
    
            // Create temporary variables for ma, uc, vc, and l (layer), as well as dUdx, dVdx, dUdy
            // and dVdy.
            TIntermSymbol *ma   = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
            TIntermSymbol *l    = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
            TIntermSymbol *uc   = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
            TIntermSymbol *vc   = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
            TIntermSymbol *dUdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
            TIntermSymbol *dVdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
            TIntermSymbol *dUdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
            TIntermSymbol *dVdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
    
            body->appendStatement(CreateTempDeclarationNode(&ma->variable()));
            body->appendStatement(CreateTempDeclarationNode(&l->variable()));
            body->appendStatement(CreateTempDeclarationNode(&uc->variable()));
            body->appendStatement(CreateTempDeclarationNode(&vc->variable()));
            body->appendStatement(CreateTempDeclarationNode(&dUdx->variable()));
            body->appendStatement(CreateTempDeclarationNode(&dVdx->variable()));
            body->appendStatement(CreateTempDeclarationNode(&dUdy->variable()));
            body->appendStatement(CreateTempDeclarationNode(&dVdy->variable()));
    
            // ma = max(|x|, max(|y|, |z|))
            TIntermTyped *maxYZ = CreateBuiltInFunctionCallNode(
                "max", new TIntermSequence({absY->deepCopy(), absZ->deepCopy()}), *mSymbolTable, 100);
            TIntermTyped *maValue = CreateBuiltInFunctionCallNode(
                "max", new TIntermSequence({absX->deepCopy(), maxYZ}), *mSymbolTable, 100);
            body->appendStatement(new TIntermBinary(EOpAssign, ma, maValue));
    
            // ma == |x| and ma == |y| expressions
            TIntermTyped *isXMajor = new TIntermBinary(EOpEqual, ma->deepCopy(), absX->deepCopy());
            TIntermTyped *isYMajor = new TIntermBinary(EOpEqual, ma->deepCopy(), absY->deepCopy());
    
            // Determine the cube face:
    
            // The case where x is major:
            //     layer = float(x < 0)
            TIntermTyped *xl =
                TIntermAggregate::CreateConstructor(*floatType, new TIntermSequence({isNegX}));
    
            TIntermBlock *calculateXL = new TIntermBlock;
            calculateXL->appendStatement(new TIntermBinary(EOpAssign, l->deepCopy(), xl));
    
            // The case where y is major:
            //     layer = 2 + float(y < 0)
            TIntermTyped *yl = new TIntermBinary(
                EOpAdd, CreateFloatNode(2.0f),
                TIntermAggregate::CreateConstructor(*floatType, new TIntermSequence({isNegY})));
    
            TIntermBlock *calculateYL = new TIntermBlock;
            calculateYL->appendStatement(new TIntermBinary(EOpAssign, l->deepCopy(), yl));
    
            // The case where z is major:
            //     layer = 4 + float(z < 0)
            TIntermTyped *zl = new TIntermBinary(
                EOpAdd, CreateFloatNode(4.0f),
                TIntermAggregate::CreateConstructor(*floatType, new TIntermSequence({isNegZ})));
    
            TIntermBlock *calculateZL = new TIntermBlock;
            calculateZL->appendStatement(new TIntermBinary(EOpAssign, l->deepCopy(), zl));
    
            // Create the if-else paths:
            TIntermIfElse *calculateYZL     = new TIntermIfElse(isYMajor, calculateYL, calculateZL);
            TIntermBlock *calculateYZLBlock = new TIntermBlock;
            calculateYZLBlock->appendStatement(calculateYZL);
            TIntermIfElse *calculateXYZL = new TIntermIfElse(isXMajor, calculateXL, calculateYZLBlock);
            body->appendStatement(calculateXYZL);
    
            // layer < 1.5 (covering faces 0 and 1, corresponding to major axis being X) and layer < 3.5
            // (covering faces 2 and 3, corresponding to major axis being Y).  Used to determine which
            // of the three transformations to apply.  Previously, ma == |X| and ma == |Y| was used,
            // which is no longer correct for helper invocations.  The value of ma is updated in each
            // case for these invocations.
            isXMajor = new TIntermBinary(EOpLessThan, l->deepCopy(), CreateFloatNode(1.5f));
            isYMajor = new TIntermBinary(EOpLessThan, l->deepCopy(), CreateFloatNode(3.5f));
    
            TIntermSwizzle *dPdxX = new TIntermSwizzle(dPdx->deepCopy(), {0});
            TIntermSwizzle *dPdxY = new TIntermSwizzle(dPdx->deepCopy(), {1});
            TIntermSwizzle *dPdxZ = new TIntermSwizzle(dPdx->deepCopy(), {2});
    
            TIntermSwizzle *dPdyX = new TIntermSwizzle(dPdy->deepCopy(), {0});
            TIntermSwizzle *dPdyY = new TIntermSwizzle(dPdy->deepCopy(), {1});
            TIntermSwizzle *dPdyZ = new TIntermSwizzle(dPdy->deepCopy(), {2});
    
            TIntermBlock *calculateXUcVc = new TIntermBlock;
            calculateXUcVc->appendStatement(
                new TIntermBinary(EOpAssign, ma->deepCopy(), absX->deepCopy()));
            TransformXMajor(calculateXUcVc, x, y, z, uc, vc);
    
            TIntermBlock *calculateYUcVc = new TIntermBlock;
            calculateYUcVc->appendStatement(
                new TIntermBinary(EOpAssign, ma->deepCopy(), absY->deepCopy()));
            TransformYMajor(calculateYUcVc, x, y, z, uc, vc);
    
            TIntermBlock *calculateZUcVc = new TIntermBlock;
            calculateZUcVc->appendStatement(
                new TIntermBinary(EOpAssign, ma->deepCopy(), absZ->deepCopy()));
            TransformZMajor(calculateZUcVc, x, y, z, uc, vc);
    
            // Compute derivatives.
            if (implicit)
            {
                TransformImplicitDerivativeXMajor(calculateXUcVc, dPDXdx, dUdx, dVdx);
                TransformImplicitDerivativeXMajor(calculateXUcVc, dPDXdy, dUdy, dVdy);
                TransformImplicitDerivativeYMajor(calculateYUcVc, dPDYdx, dUdx, dVdx);
                TransformImplicitDerivativeYMajor(calculateYUcVc, dPDYdy, dUdy, dVdy);
                TransformImplicitDerivativeZMajor(calculateZUcVc, dPDZdx, dUdx, dVdx);
                TransformImplicitDerivativeZMajor(calculateZUcVc, dPDZdy, dUdy, dVdy);
            }
            else
            {
                TransformDerivativeXMajor(calculateXUcVc, mSymbolTable, x, y, z, dPdxX, dPdxY, dPdxZ,
                                          dUdx, dVdx, Swizzle1(pRecipVar->deepCopy(), 0));
                TransformDerivativeXMajor(calculateXUcVc, mSymbolTable, x, y, z, dPdyX, dPdyY, dPdyZ,
                                          dUdy, dVdy, Swizzle1(pRecipVar->deepCopy(), 0));
                TransformDerivativeYMajor(calculateYUcVc, mSymbolTable, x, y, z, dPdxX, dPdxY, dPdxZ,
                                          dUdx, dVdx, Swizzle1(pRecipVar->deepCopy(), 1));
                TransformDerivativeYMajor(calculateYUcVc, mSymbolTable, x, y, z, dPdyX, dPdyY, dPdyZ,
                                          dUdy, dVdy, Swizzle1(pRecipVar->deepCopy(), 1));
                TransformDerivativeZMajor(calculateZUcVc, mSymbolTable, x, y, z, dPdxX, dPdxY, dPdxZ,
                                          dUdx, dVdx, Swizzle1(pRecipVar->deepCopy(), 2));
                TransformDerivativeZMajor(calculateZUcVc, mSymbolTable, x, y, z, dPdyX, dPdyY, dPdyZ,
                                          dUdy, dVdy, Swizzle1(pRecipVar->deepCopy(), 2));
            }
    
            // Create the if-else paths:
            TIntermIfElse *calculateYZUcVc =
                new TIntermIfElse(isYMajor, calculateYUcVc, calculateZUcVc);
            TIntermBlock *calculateYZUcVcBlock = new TIntermBlock;
            calculateYZUcVcBlock->appendStatement(calculateYZUcVc);
            TIntermIfElse *calculateXYZUcVc =
                new TIntermIfElse(isXMajor, calculateXUcVc, calculateYZUcVcBlock);
            body->appendStatement(calculateXYZUcVc);
    
            // u = (1 + uc/|ma|) / 2
            // v = (1 + vc/|ma|) / 2
            TIntermTyped *maTimesTwoRecip =
                new TIntermBinary(EOpAssign, ma->deepCopy(),
                                  new TIntermBinary(EOpDiv, CreateFloatNode(0.5f), ma->deepCopy()));
            body->appendStatement(maTimesTwoRecip);
    
            TIntermTyped *ucDivMa     = new TIntermBinary(EOpMul, uc, ma->deepCopy());
            TIntermTyped *vcDivMa     = new TIntermBinary(EOpMul, vc, ma->deepCopy());
            TIntermTyped *uNormalized = new TIntermBinary(EOpAdd, CreateFloatNode(0.5f), ucDivMa);
            TIntermTyped *vNormalized = new TIntermBinary(EOpAdd, CreateFloatNode(0.5f), vcDivMa);
    
            body->appendStatement(new TIntermBinary(EOpAssign, uc->deepCopy(), uNormalized));
            body->appendStatement(new TIntermBinary(EOpAssign, vc->deepCopy(), vNormalized));
    
            TIntermTyped *dUVdxValue =
                TIntermAggregate::CreateConstructor(*vec2Type, new TIntermSequence({dUdx, dVdx}));
            TIntermTyped *dUVdyValue =
                TIntermAggregate::CreateConstructor(*vec2Type, new TIntermSequence({dUdy, dVdy}));
    
            body->appendStatement(new TIntermBinary(EOpAssign, dUVdx, dUVdxValue));
            body->appendStatement(new TIntermBinary(EOpAssign, dUVdy, dUVdyValue));
    
            // return vec3(u, v, l)
            TIntermBranch *returnStatement = new TIntermBranch(
                EOpReturn, TIntermAggregate::CreateConstructor(
                               *vec3Type, new TIntermSequence({uc->deepCopy(), vc->deepCopy(), l})));
            body->appendStatement(returnStatement);
    
            TFunction *function;
            function = new TFunction(mSymbolTable, name, SymbolType::AngleInternal, vec3Type, true);
            function->addParameter(pVar);
            function->addParameter(dPdxVar);
            function->addParameter(dPdyVar);
            function->addParameter(dUVdxVar);
            function->addParameter(dUVdyVar);
    
            *functionOut = function;
    
            *declOut = CreateInternalFunctionDefinitionNode(*function, body);
        }
    
        TIntermTyped *createCoordTransformationCall(TIntermTyped *P,
                                                    TIntermTyped *dPdx,
                                                    TIntermTyped *dPdy,
                                                    TIntermTyped *dUVdx,
                                                    TIntermTyped *dUVdy)
        {
            TIntermSequence *args = new TIntermSequence({P, dPdx, dPdy, dUVdx, dUVdy});
            return TIntermAggregate::CreateFunctionCall(*mCubeXYZToArrayUVL, args);
        }
    
        TIntermTyped *createImplicitCoordTransformationCall(TIntermTyped *P,
                                                            TIntermTyped *dUVdx,
                                                            TIntermTyped *dUVdy)
        {
            const TType *vec3Type = StaticType::GetBasic<EbtFloat, 3>();
            TIntermTyped *dPdx    = CreateZeroNode(*vec3Type);
            TIntermTyped *dPdy    = CreateZeroNode(*vec3Type);
            TIntermSequence *args = new TIntermSequence({P, dPdx, dPdy, dUVdx, dUVdy});
            return TIntermAggregate::CreateFunctionCall(*mCubeXYZToArrayUVLImplicit, args);
        }
    
        TVariable *convertFunctionParameter(TIntermNode *parent, const TVariable *param)
        {
            if (!param->getType().isSamplerCube())
            {
                return nullptr;
            }
    
            TType *newType = new TType(param->getType());
            newType->setBasicType(EbtSampler2DArray);
    
            TVariable *replacementVar =
                new TVariable(mSymbolTable, param->name(), newType, SymbolType::UserDefined);
    
            return replacementVar;
        }
    
        void convertBuiltinFunction(TIntermAggregate *node)
        {
            const TFunction *function = node->getFunction();
            if (!function->name().beginsWith("textureCube"))
            {
                return;
            }
    
            // All textureCube* functions are in the form:
            //
            //     textureCube??(samplerCube, vec3, ??)
            //
            // They should be converted to:
            //
            //     texture??(sampler2DArray, convertCoords(vec3), ??)
            //
            // We assume the target platform supports texture() functions (currently only used in
            // Vulkan).
            //
            // The intrinsics map as follows:
            //
            //     textureCube -> textureGrad
            //     textureCubeLod -> textureLod
            //     textureCubeLodEXT -> textureLod
            //     textureCubeGrad -> textureGrad
            //     textureCubeGradEXT -> textureGrad
            //
            // Note that dPdx and dPdy in textureCubeGrad* are vec3, while the textureGrad equivalent
            // for sampler2DArray is vec2.  The EXT_shader_texture_lod that introduces thid function
            // says:
            //
            // > For the "Grad" functions, dPdx is the explicit derivative of P with respect
            // > to window x, and similarly dPdy with respect to window y. ...  For a cube map texture,
            // > dPdx and dPdy are vec3.
            // >
            // > Let
            // >
            // >     dSdx = dPdx.s;
            // >     dSdy = dPdy.s;
            // >     dTdx = dPdx.t;
            // >     dTdy = dPdy.t;
            // >
            // > and
            // >
            // >             / 0.0;    for two-dimensional texture
            // >     dRdx = (
            // >             \ dPdx.p; for cube map texture
            // >
            // >             / 0.0;    for two-dimensional texture
            // >     dRdy = (
            // >             \ dPdy.p; for cube map texture
            // >
            // > (See equation 3.12a in The OpenGL ES 2.0 Specification.)
            //
            // It's unclear to me what dRdx and dRdy are.  EXT_gpu_shader4 that promotes this function
            // has the following additional information:
            //
            // > For the "Cube" versions, the partial
            // > derivatives ddx and ddy are assumed to be in the coordinate system used
            // > before texture coordinates are projected onto the appropriate cube
            // > face. The partial derivatives of the post-projection texture coordinates,
            // > which are used for level-of-detail and anisotropic filtering
            // > calculations, are derived from coord, ddx and ddy in an
            // > implementation-dependent manner.
            //
            // The calculation of dPdx and dPdy is declared as implementation-dependent, so we have
            // freedom to calculate it as fit, even if not precisely the same as hardware might.
    
            const char *substituteFunctionName = "textureGrad";
            bool isGrad                        = false;
            bool isTranslatedGrad              = true;
            bool hasBias                       = false;
            if (function->name().beginsWith("textureCubeLod"))
            {
                substituteFunctionName = "textureLod";
                isTranslatedGrad       = false;
            }
            else if (function->name().beginsWith("textureCubeGrad"))
            {
                isGrad = true;
            }
            else if (!mIsFragmentShader)
            {
                substituteFunctionName = "texture";
                isTranslatedGrad       = false;
            }
    
            TIntermSequence *arguments = node->getSequence();
            ASSERT(arguments->size() >= 2);
    
            const TType *vec2Type = StaticType::GetBasic<EbtFloat, 2>();
            const TType *vec3Type = StaticType::GetBasic<EbtFloat, 3>();
            TIntermSymbol *uvl    = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type));
            TIntermSymbol *dUVdx  = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec2Type));
            TIntermSymbol *dUVdy  = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec2Type));
    
            TIntermTyped *dPdx = nullptr;
            TIntermTyped *dPdy = nullptr;
            if (isGrad)
            {
                ASSERT(arguments->size() == 4);
                dPdx = (*arguments)[2]->getAsTyped()->deepCopy();
                dPdy = (*arguments)[3]->getAsTyped()->deepCopy();
            }
            else if (isTranslatedGrad && mIsFragmentShader && arguments->size() == 3)
            {
                hasBias = true;
            }
            else
            {
                dPdx = CreateZeroNode(*vec3Type);
                dPdy = CreateZeroNode(*vec3Type);
            }
    
            if (isTranslatedGrad && !mIsFragmentShader)
            {
                substituteFunctionName = "texture";
                isTranslatedGrad       = false;
            }
    
            // The function call to transform the coordinates, dPdx and dPdy.  If not textureCubeGrad,
            // the driver compiler will optimize out the unnecessary calculations.
            TIntermSequence *coordTransform = new TIntermSequence;
            coordTransform->push_back(CreateTempDeclarationNode(&dUVdx->variable()));
            coordTransform->push_back(CreateTempDeclarationNode(&dUVdy->variable()));
            TIntermTyped *coordTransformCall;
            if (isGrad || !isTranslatedGrad)
            {
                coordTransformCall = createCoordTransformationCall(
                    (*arguments)[1]->getAsTyped()->deepCopy(), dPdx, dPdy, dUVdx, dUVdy);
            }
            else
            {
                coordTransformCall = createImplicitCoordTransformationCall(
                    (*arguments)[1]->getAsTyped()->deepCopy(), dUVdx, dUVdy);
            }
            coordTransform->push_back(
                CreateTempInitDeclarationNode(&uvl->variable(), coordTransformCall));
    
            TIntermTyped *dUVdxArg = dUVdx;
            TIntermTyped *dUVdyArg = dUVdy;
            if (hasBias)
            {
                const TType *floatType = StaticType::GetBasic<EbtFloat>();
                TIntermTyped *bias     = (*arguments)[2]->getAsTyped()->deepCopy();
                TIntermTyped *exp2Call = CreateBuiltInFunctionCallNode(
                    "exp2", new TIntermSequence({bias}), *mSymbolTable, 100);
                TIntermSymbol *biasFac = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
                coordTransform->push_back(
                    CreateTempInitDeclarationNode(&biasFac->variable(), exp2Call));
                dUVdxArg =
                    new TIntermBinary(EOpVectorTimesScalar, biasFac->deepCopy(), dUVdx->deepCopy());
                dUVdyArg =
                    new TIntermBinary(EOpVectorTimesScalar, biasFac->deepCopy(), dUVdy->deepCopy());
            }
    
            insertStatementsInParentBlock(*coordTransform);
    
            TIntermSequence *substituteArguments = new TIntermSequence;
            // Replace the first argument (samplerCube) with the sampler2DArray.
            substituteArguments->push_back(mRetyper.getFunctionCallArgReplacement((*arguments)[0]));
            // Replace the second argument with the coordination transformation.
            substituteArguments->push_back(uvl->deepCopy());
            if (isTranslatedGrad)
            {
                substituteArguments->push_back(dUVdxArg->deepCopy());
                substituteArguments->push_back(dUVdyArg->deepCopy());
            }
            else
            {
                // Pass the rest of the parameters as is.
                for (size_t argIndex = 2; argIndex < arguments->size(); ++argIndex)
                {
                    substituteArguments->push_back((*arguments)[argIndex]->getAsTyped()->deepCopy());
                }
            }
    
            TIntermTyped *substituteCall = CreateBuiltInFunctionCallNode(
                substituteFunctionName, substituteArguments, *mSymbolTable, 300);
    
            queueReplacement(substituteCall, OriginalNode::IS_DROPPED);
        }
    
        RetypeOpaqueVariablesHelper mRetyper;
    
        // A helper function to convert xyz coordinates passed to a cube map sampling function into the
        // array layer (cube map face) and uv coordinates.
        TFunction *mCubeXYZToArrayUVL;
        // A specialized version of the same function which uses implicit derivatives.
        TFunction *mCubeXYZToArrayUVLImplicit;
    
        bool mIsFragmentShader;
    
        // Stored to be put before the first function after the pass.
        TIntermFunctionDefinition *mCoordTranslationFunctionDecl;
        TIntermFunctionDefinition *mCoordTranslationFunctionImplicitDecl;
    };
    
    }  // anonymous namespace
    
    bool RewriteCubeMapSamplersAs2DArray(TCompiler *compiler,
                                         TIntermBlock *root,
                                         TSymbolTable *symbolTable,
                                         bool isFragmentShader)
    {
        RewriteCubeMapSamplersAs2DArrayTraverser traverser(symbolTable, isFragmentShader);
        root->traverse(&traverser);
        if (!traverser.updateTree(compiler, root))
        {
            return false;
        }
    
        TIntermFunctionDefinition *coordTranslationFunctionDecl =
            traverser.getCoordTranslationFunctionDecl();
        TIntermFunctionDefinition *coordTranslationFunctionDeclImplicit =
            traverser.getCoordTranslationFunctionDeclImplicit();
        size_t firstFunctionIndex = FindFirstFunctionDefinitionIndex(root);
        if (coordTranslationFunctionDecl)
        {
            root->insertChildNodes(firstFunctionIndex, TIntermSequence({coordTranslationFunctionDecl}));
        }
        if (coordTranslationFunctionDeclImplicit)
        {
            root->insertChildNodes(firstFunctionIndex,
                                   TIntermSequence({coordTranslationFunctionDeclImplicit}));
        }
    
        return compiler->validateAST(root);
    }
    
    }  // namespace sh