//
// Copyright (c) 2016 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.
//
// TextureFunctionHLSL: Class for writing implementations of ESSL texture functions into HLSL
// output. Some of the implementations are straightforward and just call the HLSL equivalent of the
// ESSL texture function, others do more work to emulate ESSL texture sampling or size query
// behavior.
//

#include "compiler/translator/TextureFunctionHLSL.h"

#include "compiler/translator/ImmutableStringBuilder.h"
#include "compiler/translator/UtilsHLSL.h"

namespace sh
{

namespace
{

void OutputIntTexCoordWrap(TInfoSinkBase &out,
                           const char *wrapMode,
                           const char *size,
                           const ImmutableString &texCoord,
                           const char *texCoordOffset,
                           const char *texCoordOutName)
{
    // GLES 3.0.4 table 3.22 specifies how the wrap modes work. We don't use the formulas verbatim
    // but rather use equivalent formulas that map better to HLSL.
    out << "int " << texCoordOutName << ";\n";
    out << "float " << texCoordOutName << "Offset = " << texCoord << " + float(" << texCoordOffset
        << ") / " << size << ";\n";

    // CLAMP_TO_EDGE
    out << "if (" << wrapMode << " == 1)\n";
    out << "{\n";
    out << "    " << texCoordOutName << " = clamp(int(floor(" << size << " * " << texCoordOutName
        << "Offset)), 0, int(" << size << ") - 1);\n";
    out << "}\n";

    // MIRRORED_REPEAT
    out << "else if (" << wrapMode << " == 3)\n";
    out << "{\n";
    out << "    float coordWrapped = 1.0 - abs(frac(abs(" << texCoordOutName
        << "Offset) * 0.5) * 2.0 - 1.0);\n";
    out << "    " << texCoordOutName << " = int(floor(" << size << " * coordWrapped));\n";
    out << "}\n";

    // REPEAT
    out << "else\n";
    out << "{\n";
    out << "    " << texCoordOutName << " = int(floor(" << size << " * frac(" << texCoordOutName
        << "Offset)));\n";
    out << "}\n";
}

void OutputIntTexCoordWraps(TInfoSinkBase &out,
                            const TextureFunctionHLSL::TextureFunction &textureFunction,
                            ImmutableString *texCoordX,
                            ImmutableString *texCoordY,
                            ImmutableString *texCoordZ)
{
    // Convert from normalized floating-point to integer
    out << "int wrapS = samplerMetadata[samplerIndex].wrapModes & 0x3;\n";
    if (textureFunction.offset)
    {
        OutputIntTexCoordWrap(out, "wrapS", "width", *texCoordX, "offset.x", "tix");
    }
    else
    {
        OutputIntTexCoordWrap(out, "wrapS", "width", *texCoordX, "0", "tix");
    }
    *texCoordX = ImmutableString("tix");
    out << "int wrapT = (samplerMetadata[samplerIndex].wrapModes >> 2) & 0x3;\n";
    if (textureFunction.offset)
    {
        OutputIntTexCoordWrap(out, "wrapT", "height", *texCoordY, "offset.y", "tiy");
    }
    else
    {
        OutputIntTexCoordWrap(out, "wrapT", "height", *texCoordY, "0", "tiy");
    }
    *texCoordY = ImmutableString("tiy");

    if (IsSamplerArray(textureFunction.sampler))
    {
        *texCoordZ = ImmutableString("int(max(0, min(layers - 1, floor(0.5 + t.z))))");
    }
    else if (!IsSamplerCube(textureFunction.sampler) && !IsSampler2D(textureFunction.sampler))
    {
        out << "int wrapR = (samplerMetadata[samplerIndex].wrapModes >> 4) & 0x3;\n";
        if (textureFunction.offset)
        {
            OutputIntTexCoordWrap(out, "wrapR", "depth", *texCoordZ, "offset.z", "tiz");
        }
        else
        {
            OutputIntTexCoordWrap(out, "wrapR", "depth", *texCoordZ, "0", "tiz");
        }
        *texCoordZ = ImmutableString("tiz");
    }
}

void OutputHLSL4SampleFunctionPrefix(TInfoSinkBase &out,
                                     const TextureFunctionHLSL::TextureFunction &textureFunction,
                                     const ImmutableString &textureReference,
                                     const ImmutableString &samplerReference)
{
    out << textureReference;
    if (IsIntegerSampler(textureFunction.sampler) ||
        textureFunction.method == TextureFunctionHLSL::TextureFunction::FETCH)
    {
        out << ".Load(";
        return;
    }

    if (IsShadowSampler(textureFunction.sampler))
    {
        switch (textureFunction.method)
        {
            case TextureFunctionHLSL::TextureFunction::IMPLICIT:
            case TextureFunctionHLSL::TextureFunction::BIAS:
            case TextureFunctionHLSL::TextureFunction::LOD:
                out << ".SampleCmp(";
                break;
            case TextureFunctionHLSL::TextureFunction::LOD0:
            case TextureFunctionHLSL::TextureFunction::LOD0BIAS:
            case TextureFunctionHLSL::TextureFunction::GRAD:
                out << ".SampleCmpLevelZero(";
                break;
            default:
                UNREACHABLE();
        }
    }
    else
    {
        switch (textureFunction.method)
        {
            case TextureFunctionHLSL::TextureFunction::IMPLICIT:
                out << ".Sample(";
                break;
            case TextureFunctionHLSL::TextureFunction::BIAS:
                out << ".SampleBias(";
                break;
            case TextureFunctionHLSL::TextureFunction::LOD:
            case TextureFunctionHLSL::TextureFunction::LOD0:
            case TextureFunctionHLSL::TextureFunction::LOD0BIAS:
                out << ".SampleLevel(";
                break;
            case TextureFunctionHLSL::TextureFunction::GRAD:
                out << ".SampleGrad(";
                break;
            default:
                UNREACHABLE();
        }
    }
    out << samplerReference << ", ";
}

const char *GetSamplerCoordinateTypeString(
    const TextureFunctionHLSL::TextureFunction &textureFunction,
    int hlslCoords)
{
    if (IsIntegerSampler(textureFunction.sampler) ||
        textureFunction.method == TextureFunctionHLSL::TextureFunction::FETCH)
    {
        switch (hlslCoords)
        {
            case 2:
                if (IsSampler2DMS(textureFunction.sampler))
                {
                    return "int2";
                }
                else
                {
                    return "int3";
                }
            case 3:
                if (IsSampler2DMSArray(textureFunction.sampler))
                {
                    return "int3";
                }
                else
                {
                    return "int4";
                }
            default:
                UNREACHABLE();
        }
    }
    else
    {
        switch (hlslCoords)
        {
            case 2:
                return "float2";
            case 3:
                return "float3";
            case 4:
                return "float4";
            default:
                UNREACHABLE();
        }
    }
    return "";
}

int GetHLSLCoordCount(const TextureFunctionHLSL::TextureFunction &textureFunction,
                      ShShaderOutput outputType)
{
    if (outputType == SH_HLSL_3_0_OUTPUT)
    {
        int hlslCoords = 2;
        switch (textureFunction.sampler)
        {
            case EbtSampler2D:
            case EbtSamplerExternalOES:
            case EbtSampler2DMS:
                hlslCoords = 2;
                break;
            case EbtSamplerCube:
                hlslCoords = 3;
                break;
            default:
                UNREACHABLE();
        }

        switch (textureFunction.method)
        {
            case TextureFunctionHLSL::TextureFunction::IMPLICIT:
            case TextureFunctionHLSL::TextureFunction::GRAD:
                return hlslCoords;
            case TextureFunctionHLSL::TextureFunction::BIAS:
            case TextureFunctionHLSL::TextureFunction::LOD:
            case TextureFunctionHLSL::TextureFunction::LOD0:
            case TextureFunctionHLSL::TextureFunction::LOD0BIAS:
                return 4;
            default:
                UNREACHABLE();
        }
    }
    else
    {
        if (IsSampler3D(textureFunction.sampler) || IsSamplerArray(textureFunction.sampler) ||
            IsSamplerCube(textureFunction.sampler))
        {
            return 3;
        }
        ASSERT(IsSampler2D(textureFunction.sampler));
        return 2;
    }
    return 0;
}

void OutputTextureFunctionArgumentList(TInfoSinkBase &out,
                                       const TextureFunctionHLSL::TextureFunction &textureFunction,
                                       const ShShaderOutput outputType)
{
    if (outputType == SH_HLSL_3_0_OUTPUT)
    {
        switch (textureFunction.sampler)
        {
            case EbtSampler2D:
            case EbtSamplerExternalOES:
                out << "sampler2D s";
                break;
            case EbtSamplerCube:
                out << "samplerCUBE s";
                break;
            default:
                UNREACHABLE();
        }
    }
    else
    {
        if (outputType == SH_HLSL_4_0_FL9_3_OUTPUT)
        {
            out << TextureString(textureFunction.sampler) << " x, "
                << SamplerString(textureFunction.sampler) << " s";
        }
        else
        {
            ASSERT(outputType == SH_HLSL_4_1_OUTPUT);
            // A bug in the D3D compiler causes some nested sampling operations to fail.
            // See http://anglebug.com/1923
            // TODO(jmadill): Reinstate the const keyword when possible.
            out << /*"const"*/ "uint samplerIndex";
        }
    }

    if (textureFunction.method ==
        TextureFunctionHLSL::TextureFunction::FETCH)  // Integer coordinates
    {
        switch (textureFunction.coords)
        {
            case 2:
                out << ", int2 t";
                break;
            case 3:
                out << ", int3 t";
                break;
            default:
                UNREACHABLE();
        }
    }
    else  // Floating-point coordinates (except textureSize)
    {
        switch (textureFunction.coords)
        {
            case 0:
                break;  // textureSize(gSampler2DMS sampler)
            case 1:
                out << ", int lod";
                break;  // textureSize()
            case 2:
                out << ", float2 t";
                break;
            case 3:
                out << ", float3 t";
                break;
            case 4:
                out << ", float4 t";
                break;
            default:
                UNREACHABLE();
        }
    }

    if (textureFunction.method == TextureFunctionHLSL::TextureFunction::GRAD)
    {
        switch (textureFunction.sampler)
        {
            case EbtSampler2D:
            case EbtISampler2D:
            case EbtUSampler2D:
            case EbtSampler2DArray:
            case EbtISampler2DArray:
            case EbtUSampler2DArray:
            case EbtSampler2DShadow:
            case EbtSampler2DArrayShadow:
            case EbtSamplerExternalOES:
                out << ", float2 ddx, float2 ddy";
                break;
            case EbtSampler3D:
            case EbtISampler3D:
            case EbtUSampler3D:
            case EbtSamplerCube:
            case EbtISamplerCube:
            case EbtUSamplerCube:
            case EbtSamplerCubeShadow:
                out << ", float3 ddx, float3 ddy";
                break;
            default:
                UNREACHABLE();
        }
    }

    switch (textureFunction.method)
    {
        case TextureFunctionHLSL::TextureFunction::IMPLICIT:
            break;
        case TextureFunctionHLSL::TextureFunction::BIAS:
            break;  // Comes after the offset parameter
        case TextureFunctionHLSL::TextureFunction::LOD:
            out << ", float lod";
            break;
        case TextureFunctionHLSL::TextureFunction::LOD0:
            break;
        case TextureFunctionHLSL::TextureFunction::LOD0BIAS:
            break;  // Comes after the offset parameter
        case TextureFunctionHLSL::TextureFunction::SIZE:
            break;
        case TextureFunctionHLSL::TextureFunction::FETCH:
            if (IsSampler2DMS(textureFunction.sampler) ||
                IsSampler2DMSArray(textureFunction.sampler))
                out << ", int index";
            else
                out << ", int mip";
            break;
        case TextureFunctionHLSL::TextureFunction::GRAD:
            break;
        case TextureFunctionHLSL::TextureFunction::GATHER:
            break;
        default:
            UNREACHABLE();
    }

    if (textureFunction.offset)
    {
        switch (textureFunction.sampler)
        {
            case EbtSampler3D:
            case EbtISampler3D:
            case EbtUSampler3D:
                out << ", int3 offset";
                break;
            case EbtSampler2D:
            case EbtSampler2DArray:
            case EbtISampler2D:
            case EbtISampler2DArray:
            case EbtUSampler2D:
            case EbtUSampler2DArray:
            case EbtSampler2DShadow:
            case EbtSampler2DArrayShadow:
            case EbtSamplerExternalOES:
                out << ", int2 offset";
                break;
            default:
                // Offset is not supported for multisampled textures.
                UNREACHABLE();
        }
    }

    if (textureFunction.method == TextureFunctionHLSL::TextureFunction::BIAS ||
        textureFunction.method == TextureFunctionHLSL::TextureFunction::LOD0BIAS)
    {
        out << ", float bias";
    }
    else if (textureFunction.method == TextureFunctionHLSL::TextureFunction::GATHER)
    {
        out << ", int comp = 0";
    }
}

void GetTextureReference(TInfoSinkBase &out,
                         const TextureFunctionHLSL::TextureFunction &textureFunction,
                         const ShShaderOutput outputType,
                         ImmutableString *textureReference,
                         ImmutableString *samplerReference)
{
    if (outputType == SH_HLSL_4_1_OUTPUT)
    {
        static const ImmutableString kTexturesStr("textures");
        static const ImmutableString kSamplersStr("samplers");
        static const ImmutableString kSamplerIndexStr("[samplerIndex]");
        static const ImmutableString kTextureIndexStr("[textureIndex]");
        static const ImmutableString kSamplerArrayIndexStr("[samplerArrayIndex]");
        ImmutableString suffix(TextureGroupSuffix(textureFunction.sampler));

        if (TextureGroup(textureFunction.sampler) == HLSL_TEXTURE_2D)
        {
            ImmutableStringBuilder textureRefBuilder(kTexturesStr.length() + suffix.length() +
                                                     kSamplerIndexStr.length());
            textureRefBuilder << kTexturesStr << suffix << kSamplerIndexStr;
            *textureReference = textureRefBuilder;
            ImmutableStringBuilder samplerRefBuilder(kSamplersStr.length() + suffix.length() +
                                                     kSamplerIndexStr.length());
            samplerRefBuilder << kSamplersStr << suffix << kSamplerIndexStr;
            *samplerReference = samplerRefBuilder;
        }
        else
        {
            out << "    const uint textureIndex = samplerIndex - textureIndexOffset"
                << suffix.data() << ";\n";
            ImmutableStringBuilder textureRefBuilder(kTexturesStr.length() + suffix.length() +
                                                     kTextureIndexStr.length());
            textureRefBuilder << kTexturesStr << suffix << kTextureIndexStr;
            *textureReference = textureRefBuilder;

            out << "    const uint samplerArrayIndex = samplerIndex - samplerIndexOffset"
                << suffix.data() << ";\n";
            ImmutableStringBuilder samplerRefBuilder(kSamplersStr.length() + suffix.length() +
                                                     kSamplerArrayIndexStr.length());
            samplerRefBuilder << kSamplersStr << suffix << kSamplerArrayIndexStr;
            *samplerReference = samplerRefBuilder;
        }
    }
    else
    {
        *textureReference = ImmutableString("x");
        *samplerReference = ImmutableString("s");
    }
}

void OutputTextureSizeFunctionBody(TInfoSinkBase &out,
                                   const TextureFunctionHLSL::TextureFunction &textureFunction,
                                   const ImmutableString &textureReference,
                                   bool getDimensionsIgnoresBaseLevel)
{
    if (IsSampler2DMS(textureFunction.sampler))
    {
        out << "    uint width; uint height; uint samples;\n"
            << "    " << textureReference << ".GetDimensions(width, height, samples);\n";
    }
    else if (IsSampler2DMSArray(textureFunction.sampler))
    {
        out << "    uint width; uint height; uint depth; uint samples;\n"
            << "    " << textureReference << ".GetDimensions(width, height, depth, samples);\n";
    }
    else
    {
        if (getDimensionsIgnoresBaseLevel)
        {
            out << "    int baseLevel = samplerMetadata[samplerIndex].baseLevel;\n";
        }
        else
        {
            out << "    int baseLevel = 0;\n";
        }

        if (IsSampler3D(textureFunction.sampler) || IsSamplerArray(textureFunction.sampler) ||
            (IsIntegerSampler(textureFunction.sampler) && IsSamplerCube(textureFunction.sampler)))
        {
            // "depth" stores either the number of layers in an array texture or 3D depth
            out << "    uint width; uint height; uint depth; uint numberOfLevels;\n"
                << "    " << textureReference
                << ".GetDimensions(baseLevel, width, height, depth, numberOfLevels);\n"
                << "    width = max(width >> lod, 1);\n"
                << "    height = max(height >> lod, 1);\n";

            if (!IsSamplerArray(textureFunction.sampler))
            {
                out << "    depth = max(depth >> lod, 1);\n";
            }
        }
        else if (IsSampler2D(textureFunction.sampler) || IsSamplerCube(textureFunction.sampler))
        {
            out << "    uint width; uint height; uint numberOfLevels;\n"
                << "    " << textureReference
                << ".GetDimensions(baseLevel, width, height, numberOfLevels);\n"
                << "    width = max(width >> lod, 1);\n"
                << "    height = max(height >> lod, 1);\n";
        }
        else
            UNREACHABLE();
    }

    if (strcmp(textureFunction.getReturnType(), "int3") == 0)
    {
        out << "    return int3(width, height, depth);\n";
    }
    else
    {
        out << "    return int2(width, height);\n";
    }
}

void ProjectTextureCoordinates(const TextureFunctionHLSL::TextureFunction &textureFunction,
                               ImmutableString *texCoordX,
                               ImmutableString *texCoordY,
                               ImmutableString *texCoordZ)
{
    if (textureFunction.proj)
    {
        ImmutableString proj("");
        switch (textureFunction.coords)
        {
            case 3:
                proj = ImmutableString(" / t.z");
                break;
            case 4:
                proj = ImmutableString(" / t.w");
                break;
            default:
                UNREACHABLE();
        }
        ImmutableStringBuilder texCoordXBuilder(texCoordX->length() + proj.length() + 2u);
        texCoordXBuilder << '(' << *texCoordX << proj << ')';
        *texCoordX = texCoordXBuilder;
        ImmutableStringBuilder texCoordYBuilder(texCoordY->length() + proj.length() + 2u);
        texCoordYBuilder << '(' << *texCoordY << proj << ')';
        *texCoordY = texCoordYBuilder;
        ImmutableStringBuilder texCoordZBuilder(texCoordZ->length() + proj.length() + 2u);
        texCoordZBuilder << '(' << *texCoordZ << proj << ')';
        *texCoordZ = texCoordZBuilder;
    }
}

void OutputIntegerTextureSampleFunctionComputations(
    TInfoSinkBase &out,
    const TextureFunctionHLSL::TextureFunction &textureFunction,
    const ShShaderOutput outputType,
    const ImmutableString &textureReference,
    ImmutableString *texCoordX,
    ImmutableString *texCoordY,
    ImmutableString *texCoordZ)
{
    if (!IsIntegerSampler(textureFunction.sampler))
    {
        return;
    }
    if (IsSamplerCube(textureFunction.sampler))
    {
        out << "    float width; float height; float layers; float levels;\n";

        out << "    uint mip = 0;\n";

        out << "    " << textureReference
            << ".GetDimensions(mip, width, height, layers, levels);\n";

        out << "    bool xMajor = abs(t.x) > abs(t.y) && abs(t.x) > abs(t.z);\n";
        out << "    bool yMajor = abs(t.y) > abs(t.z) && abs(t.y) > abs(t.x);\n";
        out << "    bool zMajor = abs(t.z) > abs(t.x) && abs(t.z) > abs(t.y);\n";
        out << "    bool negative = (xMajor && t.x < 0.0f) || (yMajor && t.y < 0.0f) || "
               "(zMajor && t.z < 0.0f);\n";

        // FACE_POSITIVE_X = 000b
        // FACE_NEGATIVE_X = 001b
        // FACE_POSITIVE_Y = 010b
        // FACE_NEGATIVE_Y = 011b
        // FACE_POSITIVE_Z = 100b
        // FACE_NEGATIVE_Z = 101b
        out << "    int face = (int)negative + (int)yMajor * 2 + (int)zMajor * 4;\n";

        out << "    float u = xMajor ? -t.z : (yMajor && t.y < 0.0f ? -t.x : t.x);\n";
        out << "    float v = yMajor ? t.z : (negative ? t.y : -t.y);\n";
        out << "    float m = xMajor ? t.x : (yMajor ? t.y : t.z);\n";

        out << "    t.x = (u * 0.5f / m) + 0.5f;\n";
        out << "    t.y = (v * 0.5f / m) + 0.5f;\n";

        // Mip level computation.
        if (textureFunction.method == TextureFunctionHLSL::TextureFunction::IMPLICIT ||
            textureFunction.method == TextureFunctionHLSL::TextureFunction::LOD ||
            textureFunction.method == TextureFunctionHLSL::TextureFunction::GRAD)
        {
            if (textureFunction.method == TextureFunctionHLSL::TextureFunction::IMPLICIT)
            {
                out << "    float2 tSized = float2(t.x * width, t.y * height);\n"
                       "    float2 dx = ddx(tSized);\n"
                       "    float2 dy = ddy(tSized);\n"
                       "    float lod = 0.5f * log2(max(dot(dx, dx), dot(dy, dy)));\n";
            }
            else if (textureFunction.method == TextureFunctionHLSL::TextureFunction::GRAD)
            {
                // ESSL 3.00.6 spec section 8.8: "For the cube version, the partial
                // derivatives of P are assumed to be in the coordinate system used before
                // texture coordinates are projected onto the appropriate cube face."
                // ddx[0] and ddy[0] are the derivatives of t.x passed into the function
                // ddx[1] and ddy[1] are the derivatives of t.y passed into the function
                // ddx[2] and ddy[2] are the derivatives of t.z passed into the function
                // Determine the derivatives of u, v and m
                out << "    float dudx = xMajor ? ddx[2] : (yMajor && t.y < 0.0f ? -ddx[0] "
                       ": ddx[0]);\n"
                       "    float dudy = xMajor ? ddy[2] : (yMajor && t.y < 0.0f ? -ddy[0] "
                       ": ddy[0]);\n"
                       "    float dvdx = yMajor ? ddx[2] : (negative ? ddx[1] : -ddx[1]);\n"
                       "    float dvdy = yMajor ? ddy[2] : (negative ? ddy[1] : -ddy[1]);\n"
                       "    float dmdx = xMajor ? ddx[0] : (yMajor ? ddx[1] : ddx[2]);\n"
                       "    float dmdy = xMajor ? ddy[0] : (yMajor ? ddy[1] : ddy[2]);\n";
                // Now determine the derivatives of the face coordinates, using the
                // derivatives calculated above.
                // d / dx (u(x) * 0.5 / m(x) + 0.5)
                // = 0.5 * (m(x) * u'(x) - u(x) * m'(x)) / m(x)^2
                out << "    float dfacexdx = 0.5f * (m * dudx - u * dmdx) / (m * m);\n"
                       "    float dfaceydx = 0.5f * (m * dvdx - v * dmdx) / (m * m);\n"
                       "    float dfacexdy = 0.5f * (m * dudy - u * dmdy) / (m * m);\n"
                       "    float dfaceydy = 0.5f * (m * dvdy - v * dmdy) / (m * m);\n"
                       "    float2 sizeVec = float2(width, height);\n"
                       "    float2 faceddx = float2(dfacexdx, dfaceydx) * sizeVec;\n"
                       "    float2 faceddy = float2(dfacexdy, dfaceydy) * sizeVec;\n";
                // Optimization: instead of: log2(max(length(faceddx), length(faceddy)))
                // we compute: log2(max(length(faceddx)^2, length(faceddy)^2)) / 2
                out << "    float lengthfaceddx2 = dot(faceddx, faceddx);\n"
                       "    float lengthfaceddy2 = dot(faceddy, faceddy);\n"
                       "    float lod = log2(max(lengthfaceddx2, lengthfaceddy2)) * 0.5f;\n";
            }
            out << "    mip = uint(min(max(round(lod), 0), levels - 1));\n"
                << "    " << textureReference
                << ".GetDimensions(mip, width, height, layers, levels);\n";
        }

        // Convert from normalized floating-point to integer
        static const ImmutableString kXPrefix("int(floor(width * frac(");
        static const ImmutableString kYPrefix("int(floor(height * frac(");
        static const ImmutableString kSuffix(")))");
        ImmutableStringBuilder texCoordXBuilder(kXPrefix.length() + texCoordX->length() +
                                                kSuffix.length());
        texCoordXBuilder << kXPrefix << *texCoordX << kSuffix;
        *texCoordX = texCoordXBuilder;
        ImmutableStringBuilder texCoordYBuilder(kYPrefix.length() + texCoordX->length() +
                                                kSuffix.length());
        texCoordYBuilder << kYPrefix << *texCoordY << kSuffix;
        *texCoordY = texCoordYBuilder;
        *texCoordZ = ImmutableString("face");
    }
    else if (textureFunction.method != TextureFunctionHLSL::TextureFunction::FETCH)
    {
        if (IsSamplerArray(textureFunction.sampler))
        {
            out << "    float width; float height; float layers; float levels;\n";

            if (textureFunction.method == TextureFunctionHLSL::TextureFunction::LOD0)
            {
                out << "    uint mip = 0;\n";
            }
            else if (textureFunction.method == TextureFunctionHLSL::TextureFunction::LOD0BIAS)
            {
                out << "    uint mip = bias;\n";
            }
            else
            {

                out << "    " << textureReference
                    << ".GetDimensions(0, width, height, layers, levels);\n";
                if (textureFunction.method == TextureFunctionHLSL::TextureFunction::IMPLICIT ||
                    textureFunction.method == TextureFunctionHLSL::TextureFunction::BIAS)
                {
                    out << "    float2 tSized = float2(t.x * width, t.y * height);\n"
                           "    float dx = length(ddx(tSized));\n"
                           "    float dy = length(ddy(tSized));\n"
                           "    float lod = log2(max(dx, dy));\n";

                    if (textureFunction.method == TextureFunctionHLSL::TextureFunction::BIAS)
                    {
                        out << "    lod += bias;\n";
                    }
                }
                else if (textureFunction.method == TextureFunctionHLSL::TextureFunction::GRAD)
                {
                    out << "    float2 sizeVec = float2(width, height);\n"
                           "    float2 sizeDdx = ddx * sizeVec;\n"
                           "    float2 sizeDdy = ddy * sizeVec;\n"
                           "    float lod = log2(max(dot(sizeDdx, sizeDdx), "
                           "dot(sizeDdy, sizeDdy))) * 0.5f;\n";
                }

                out << "    uint mip = uint(min(max(round(lod), 0), levels - 1));\n";
            }

            out << "    " << textureReference
                << ".GetDimensions(mip, width, height, layers, levels);\n";
        }
        else if (IsSampler2D(textureFunction.sampler))
        {
            out << "    float width; float height; float levels;\n";

            if (textureFunction.method == TextureFunctionHLSL::TextureFunction::LOD0)
            {
                out << "    uint mip = 0;\n";
            }
            else if (textureFunction.method == TextureFunctionHLSL::TextureFunction::LOD0BIAS)
            {
                out << "    uint mip = bias;\n";
            }
            else
            {
                out << "    " << textureReference << ".GetDimensions(0, width, height, levels);\n";

                if (textureFunction.method == TextureFunctionHLSL::TextureFunction::IMPLICIT ||
                    textureFunction.method == TextureFunctionHLSL::TextureFunction::BIAS)
                {
                    out << "    float2 tSized = float2(t.x * width, t.y * height);\n"
                           "    float dx = length(ddx(tSized));\n"
                           "    float dy = length(ddy(tSized));\n"
                           "    float lod = log2(max(dx, dy));\n";

                    if (textureFunction.method == TextureFunctionHLSL::TextureFunction::BIAS)
                    {
                        out << "    lod += bias;\n";
                    }
                }
                else if (textureFunction.method == TextureFunctionHLSL::TextureFunction::GRAD)
                {
                    out << "    float2 sizeVec = float2(width, height);\n"
                           "    float2 sizeDdx = ddx * sizeVec;\n"
                           "    float2 sizeDdy = ddy * sizeVec;\n"
                           "    float lod = log2(max(dot(sizeDdx, sizeDdx), "
                           "dot(sizeDdy, sizeDdy))) * 0.5f;\n";
                }

                out << "    uint mip = uint(min(max(round(lod), 0), levels - 1));\n";
            }

            out << "    " << textureReference << ".GetDimensions(mip, width, height, levels);\n";
        }
        else if (IsSampler3D(textureFunction.sampler))
        {
            out << "    float width; float height; float depth; float levels;\n";

            if (textureFunction.method == TextureFunctionHLSL::TextureFunction::LOD0)
            {
                out << "    uint mip = 0;\n";
            }
            else if (textureFunction.method == TextureFunctionHLSL::TextureFunction::LOD0BIAS)
            {
                out << "    uint mip = bias;\n";
            }
            else
            {
                out << "    " << textureReference
                    << ".GetDimensions(0, width, height, depth, levels);\n";

                if (textureFunction.method == TextureFunctionHLSL::TextureFunction::IMPLICIT ||
                    textureFunction.method == TextureFunctionHLSL::TextureFunction::BIAS)
                {
                    out << "    float3 tSized = float3(t.x * width, t.y * height, t.z * depth);\n"
                           "    float dx = length(ddx(tSized));\n"
                           "    float dy = length(ddy(tSized));\n"
                           "    float lod = log2(max(dx, dy));\n";

                    if (textureFunction.method == TextureFunctionHLSL::TextureFunction::BIAS)
                    {
                        out << "    lod += bias;\n";
                    }
                }
                else if (textureFunction.method == TextureFunctionHLSL::TextureFunction::GRAD)
                {
                    out << "    float3 sizeVec = float3(width, height, depth);\n"
                           "    float3 sizeDdx = ddx * sizeVec;\n"
                           "    float3 sizeDdy = ddy * sizeVec;\n"
                           "    float lod = log2(max(dot(sizeDdx, sizeDdx), dot(sizeDdy, "
                           "sizeDdy))) * 0.5f;\n";
                }

                out << "    uint mip = uint(min(max(round(lod), 0), levels - 1));\n";
            }

            out << "    " << textureReference
                << ".GetDimensions(mip, width, height, depth, levels);\n";
        }
        else
            UNREACHABLE();

        OutputIntTexCoordWraps(out, textureFunction, texCoordX, texCoordY, texCoordZ);
    }
}

void OutputTextureGatherFunctionBody(TInfoSinkBase &out,
                                     const TextureFunctionHLSL::TextureFunction &textureFunction,
                                     ShShaderOutput outputType,
                                     const ImmutableString &textureReference,
                                     const ImmutableString &samplerReference,
                                     const ImmutableString &texCoordX,
                                     const ImmutableString &texCoordY,
                                     const ImmutableString &texCoordZ)
{
    const int hlslCoords = GetHLSLCoordCount(textureFunction, outputType);
    ImmutableString samplerCoordTypeString(
        GetSamplerCoordinateTypeString(textureFunction, hlslCoords));
    ImmutableStringBuilder samplerCoordBuilder(
        samplerCoordTypeString.length() + strlen("(") + texCoordX.length() + strlen(", ") +
        texCoordY.length() + strlen(", ") + texCoordZ.length() + strlen(")"));

    samplerCoordBuilder << samplerCoordTypeString << "(" << texCoordX << ", " << texCoordY;
    if (hlslCoords >= 3)
    {
        if (textureFunction.coords < 3)
        {
            samplerCoordBuilder << ", 0";
        }
        else
        {
            samplerCoordBuilder << ", " << texCoordZ;
        }
    }
    samplerCoordBuilder << ")";

    ImmutableString samplerCoordString(samplerCoordBuilder);

    constexpr std::array<const char *, 4> kHLSLGatherFunctions = {
        {"GatherRed", "GatherGreen", "GatherBlue", "GatherAlpha"}};

    out << "    switch(comp)\n"
           "    {\n";
    for (size_t component = 0; component < kHLSLGatherFunctions.size(); ++component)
    {
        out << "        case " << component << ":\n"
            << "            return " << textureReference << "." << kHLSLGatherFunctions[component]
            << "(" << samplerReference << ", " << samplerCoordString;
        if (textureFunction.offset)
        {
            out << ", offset";
        }
        out << ");\n";
    }

    out << "        default:\n"
           "            return float4(0.0, 0.0, 0.0, 1.0);\n"
           "    }\n";
}

void OutputTextureSampleFunctionReturnStatement(
    TInfoSinkBase &out,
    const TextureFunctionHLSL::TextureFunction &textureFunction,
    const ShShaderOutput outputType,
    const ImmutableString &textureReference,
    const ImmutableString &samplerReference,
    const ImmutableString &texCoordX,
    const ImmutableString &texCoordY,
    const ImmutableString &texCoordZ)
{
    out << "    return ";

    // HLSL intrinsic
    if (outputType == SH_HLSL_3_0_OUTPUT)
    {
        switch (textureFunction.sampler)
        {
            case EbtSampler2D:
            case EbtSamplerExternalOES:
                out << "tex2D";
                break;
            case EbtSamplerCube:
                out << "texCUBE";
                break;
            default:
                UNREACHABLE();
        }

        switch (textureFunction.method)
        {
            case TextureFunctionHLSL::TextureFunction::IMPLICIT:
                out << "(" << samplerReference << ", ";
                break;
            case TextureFunctionHLSL::TextureFunction::BIAS:
                out << "bias(" << samplerReference << ", ";
                break;
            case TextureFunctionHLSL::TextureFunction::LOD:
                out << "lod(" << samplerReference << ", ";
                break;
            case TextureFunctionHLSL::TextureFunction::LOD0:
                out << "lod(" << samplerReference << ", ";
                break;
            case TextureFunctionHLSL::TextureFunction::LOD0BIAS:
                out << "lod(" << samplerReference << ", ";
                break;
            case TextureFunctionHLSL::TextureFunction::GRAD:
                out << "grad(" << samplerReference << ", ";
                break;
            default:
                UNREACHABLE();
        }
    }
    else if (outputType == SH_HLSL_4_1_OUTPUT || outputType == SH_HLSL_4_0_FL9_3_OUTPUT)
    {
        OutputHLSL4SampleFunctionPrefix(out, textureFunction, textureReference, samplerReference);
    }
    else
        UNREACHABLE();

    const int hlslCoords = GetHLSLCoordCount(textureFunction, outputType);

    out << GetSamplerCoordinateTypeString(textureFunction, hlslCoords) << "(" << texCoordX << ", "
        << texCoordY;

    if (outputType == SH_HLSL_3_0_OUTPUT)
    {
        if (hlslCoords >= 3)
        {
            if (textureFunction.coords < 3)
            {
                out << ", 0";
            }
            else
            {
                out << ", " << texCoordZ;
            }
        }

        if (hlslCoords == 4)
        {
            switch (textureFunction.method)
            {
                case TextureFunctionHLSL::TextureFunction::BIAS:
                    out << ", bias";
                    break;
                case TextureFunctionHLSL::TextureFunction::LOD:
                    out << ", lod";
                    break;
                case TextureFunctionHLSL::TextureFunction::LOD0:
                    out << ", 0";
                    break;
                case TextureFunctionHLSL::TextureFunction::LOD0BIAS:
                    out << ", bias";
                    break;
                default:
                    UNREACHABLE();
            }
        }

        out << ")";
    }
    else if (outputType == SH_HLSL_4_1_OUTPUT || outputType == SH_HLSL_4_0_FL9_3_OUTPUT)
    {
        if (hlslCoords >= 3)
        {
            ASSERT(!IsIntegerSampler(textureFunction.sampler) ||
                   !IsSamplerCube(textureFunction.sampler) || texCoordZ == "face");
            out << ", " << texCoordZ;
        }

        if (textureFunction.method == TextureFunctionHLSL::TextureFunction::GRAD)
        {
            if (IsIntegerSampler(textureFunction.sampler))
            {
                out << ", mip)";
            }
            else if (IsShadowSampler(textureFunction.sampler))
            {
                // Compare value
                if (textureFunction.proj)
                {
                    // According to ESSL 3.00.4 sec 8.8 p95 on textureProj:
                    // The resulting third component of P' in the shadow forms is used as
                    // Dref
                    out << "), " << texCoordZ;
                }
                else
                {
                    switch (textureFunction.coords)
                    {
                        case 3:
                            out << "), t.z";
                            break;
                        case 4:
                            out << "), t.w";
                            break;
                        default:
                            UNREACHABLE();
                    }
                }
            }
            else
            {
                out << "), ddx, ddy";
            }
        }
        else if (IsIntegerSampler(textureFunction.sampler) ||
                 textureFunction.method == TextureFunctionHLSL::TextureFunction::FETCH)
        {
            if (IsSampler2DMS(textureFunction.sampler) ||
                IsSampler2DMSArray(textureFunction.sampler))
                out << "), index";
            else
                out << ", mip)";
        }
        else if (IsShadowSampler(textureFunction.sampler))
        {
            // Compare value
            if (textureFunction.proj)
            {
                // According to ESSL 3.00.4 sec 8.8 p95 on textureProj:
                // The resulting third component of P' in the shadow forms is used as Dref
                out << "), " << texCoordZ;
            }
            else
            {
                switch (textureFunction.coords)
                {
                    case 3:
                        out << "), t.z";
                        break;
                    case 4:
                        out << "), t.w";
                        break;
                    default:
                        UNREACHABLE();
                }
            }
        }
        else
        {
            switch (textureFunction.method)
            {
                case TextureFunctionHLSL::TextureFunction::IMPLICIT:
                    out << ")";
                    break;
                case TextureFunctionHLSL::TextureFunction::BIAS:
                    out << "), bias";
                    break;
                case TextureFunctionHLSL::TextureFunction::LOD:
                    out << "), lod";
                    break;
                case TextureFunctionHLSL::TextureFunction::LOD0:
                    out << "), 0";
                    break;
                case TextureFunctionHLSL::TextureFunction::LOD0BIAS:
                    out << "), bias";
                    break;
                default:
                    UNREACHABLE();
            }
        }

        if (textureFunction.offset &&
            (!IsIntegerSampler(textureFunction.sampler) ||
             textureFunction.method == TextureFunctionHLSL::TextureFunction::FETCH))
        {
            out << ", offset";
        }
    }
    else
        UNREACHABLE();

    out << ");\n";  // Close the sample function call and return statement
}

}  // Anonymous namespace

ImmutableString TextureFunctionHLSL::TextureFunction::name() const
{
    static const ImmutableString kGlTextureName("gl_texture");

    ImmutableString suffix(TextureTypeSuffix(this->sampler));

    ImmutableStringBuilder name(kGlTextureName.length() + suffix.length() + 4u + 6u + 5u);

    name << kGlTextureName;

    // We need to include full the sampler type in the function name to make the signature unique
    // on D3D11, where samplers are passed to texture functions as indices.
    name << suffix;

    if (proj)
    {
        name << "Proj";
    }

    if (offset)
    {
        name << "Offset";
    }

    switch (method)
    {
        case IMPLICIT:
            break;
        case BIAS:
            break;  // Extra parameter makes the signature unique
        case LOD:
            name << "Lod";
            break;
        case LOD0:
            name << "Lod0";
            break;
        case LOD0BIAS:
            name << "Lod0";
            break;  // Extra parameter makes the signature unique
        case SIZE:
            name << "Size";
            break;
        case FETCH:
            name << "Fetch";
            break;
        case GRAD:
            name << "Grad";
            break;
        case GATHER:
            name << "Gather";
            break;
        default:
            UNREACHABLE();
    }

    return name;
}

const char *TextureFunctionHLSL::TextureFunction::getReturnType() const
{
    if (method == TextureFunction::SIZE)
    {
        switch (sampler)
        {
            case EbtSampler2D:
            case EbtISampler2D:
            case EbtUSampler2D:
            case EbtSampler2DShadow:
            case EbtSamplerCube:
            case EbtISamplerCube:
            case EbtUSamplerCube:
            case EbtSamplerCubeShadow:
            case EbtSamplerExternalOES:
            case EbtSampler2DMS:
            case EbtISampler2DMS:
            case EbtUSampler2DMS:
                return "int2";
            case EbtSampler3D:
            case EbtISampler3D:
            case EbtUSampler3D:
            case EbtSampler2DArray:
            case EbtISampler2DArray:
            case EbtUSampler2DArray:
            case EbtSampler2DMSArray:
            case EbtISampler2DMSArray:
            case EbtUSampler2DMSArray:
            case EbtSampler2DArrayShadow:
                return "int3";
            default:
                UNREACHABLE();
        }
    }
    else  // Sampling function
    {
        switch (sampler)
        {
            case EbtSampler2D:
            case EbtSampler2DMS:
            case EbtSampler2DMSArray:
            case EbtSampler3D:
            case EbtSamplerCube:
            case EbtSampler2DArray:
            case EbtSamplerExternalOES:
                return "float4";
            case EbtISampler2D:
            case EbtISampler2DMS:
            case EbtISampler2DMSArray:
            case EbtISampler3D:
            case EbtISamplerCube:
            case EbtISampler2DArray:
                return "int4";
            case EbtUSampler2D:
            case EbtUSampler2DMS:
            case EbtUSampler2DMSArray:
            case EbtUSampler3D:
            case EbtUSamplerCube:
            case EbtUSampler2DArray:
                return "uint4";
            case EbtSampler2DShadow:
            case EbtSamplerCubeShadow:
            case EbtSampler2DArrayShadow:
                return "float";
            default:
                UNREACHABLE();
        }
    }
    return "";
}

bool TextureFunctionHLSL::TextureFunction::operator<(const TextureFunction &rhs) const
{
    return std::tie(sampler, coords, proj, offset, method) <
           std::tie(rhs.sampler, rhs.coords, rhs.proj, rhs.offset, rhs.method);
}

ImmutableString TextureFunctionHLSL::useTextureFunction(const ImmutableString &name,
                                                        TBasicType samplerType,
                                                        int coords,
                                                        size_t argumentCount,
                                                        bool lod0,
                                                        sh::GLenum shaderType)
{
    TextureFunction textureFunction;
    textureFunction.sampler = samplerType;
    textureFunction.coords  = coords;
    textureFunction.method  = TextureFunction::IMPLICIT;
    textureFunction.proj    = false;
    textureFunction.offset  = false;

    if (name == "texture2D" || name == "textureCube" || name == "texture")
    {
        textureFunction.method = TextureFunction::IMPLICIT;
    }
    else if (name == "texture2DProj" || name == "textureProj")
    {
        textureFunction.method = TextureFunction::IMPLICIT;
        textureFunction.proj   = true;
    }
    else if (name == "texture2DLod" || name == "textureCubeLod" || name == "textureLod" ||
             name == "texture2DLodEXT" || name == "textureCubeLodEXT")
    {
        textureFunction.method = TextureFunction::LOD;
    }
    else if (name == "texture2DProjLod" || name == "textureProjLod" ||
             name == "texture2DProjLodEXT")
    {
        textureFunction.method = TextureFunction::LOD;
        textureFunction.proj   = true;
    }
    else if (name == "textureSize")
    {
        textureFunction.method = TextureFunction::SIZE;
    }
    else if (name == "textureOffset")
    {
        textureFunction.method = TextureFunction::IMPLICIT;
        textureFunction.offset = true;
    }
    else if (name == "textureProjOffset")
    {
        textureFunction.method = TextureFunction::IMPLICIT;
        textureFunction.offset = true;
        textureFunction.proj   = true;
    }
    else if (name == "textureLodOffset")
    {
        textureFunction.method = TextureFunction::LOD;
        textureFunction.offset = true;
    }
    else if (name == "textureProjLodOffset")
    {
        textureFunction.method = TextureFunction::LOD;
        textureFunction.proj   = true;
        textureFunction.offset = true;
    }
    else if (name == "texelFetch")
    {
        textureFunction.method = TextureFunction::FETCH;
    }
    else if (name == "texelFetchOffset")
    {
        textureFunction.method = TextureFunction::FETCH;
        textureFunction.offset = true;
    }
    else if (name == "textureGrad" || name == "texture2DGradEXT")
    {
        textureFunction.method = TextureFunction::GRAD;
    }
    else if (name == "textureGradOffset")
    {
        textureFunction.method = TextureFunction::GRAD;
        textureFunction.offset = true;
    }
    else if (name == "textureProjGrad" || name == "texture2DProjGradEXT" ||
             name == "textureCubeGradEXT")
    {
        textureFunction.method = TextureFunction::GRAD;
        textureFunction.proj   = true;
    }
    else if (name == "textureProjGradOffset")
    {
        textureFunction.method = TextureFunction::GRAD;
        textureFunction.proj   = true;
        textureFunction.offset = true;
    }
    else if (name == "textureGather")
    {
        textureFunction.method = TextureFunction::GATHER;
    }
    else if (name == "textureGatherOffset")
    {
        textureFunction.method = TextureFunction::GATHER;
        textureFunction.offset = true;
    }
    else
        UNREACHABLE();

    if (textureFunction.method ==
        TextureFunction::IMPLICIT)  // Could require lod 0 or have a bias argument
    {
        size_t mandatoryArgumentCount = 2;  // All functions have sampler and coordinate arguments

        if (textureFunction.offset)
        {
            mandatoryArgumentCount++;
        }

        bool bias = (argumentCount > mandatoryArgumentCount);  // Bias argument is optional

        if (lod0 || shaderType == GL_VERTEX_SHADER)
        {
            if (bias)
            {
                textureFunction.method = TextureFunction::LOD0BIAS;
            }
            else
            {
                textureFunction.method = TextureFunction::LOD0;
            }
        }
        else if (bias)
        {
            textureFunction.method = TextureFunction::BIAS;
        }
    }

    mUsesTexture.insert(textureFunction);
    return textureFunction.name();
}

void TextureFunctionHLSL::textureFunctionHeader(TInfoSinkBase &out,
                                                const ShShaderOutput outputType,
                                                bool getDimensionsIgnoresBaseLevel)
{
    for (const TextureFunction &textureFunction : mUsesTexture)
    {
        // Function header
        out << textureFunction.getReturnType() << " " << textureFunction.name() << "(";

        OutputTextureFunctionArgumentList(out, textureFunction, outputType);

        out << ")\n"
               "{\n";

        // In some cases we use a variable to store the texture/sampler objects, but to work around
        // a D3D11 compiler bug related to discard inside a loop that is conditional on texture
        // sampling we need to call the function directly on references to the texture and sampler
        // arrays. The bug was found using dEQP-GLES3.functional.shaders.discard*loop_texture*
        // tests.
        ImmutableString textureReference("");
        ImmutableString samplerReference("");
        GetTextureReference(out, textureFunction, outputType, &textureReference, &samplerReference);

        if (textureFunction.method == TextureFunction::SIZE)
        {
            OutputTextureSizeFunctionBody(out, textureFunction, textureReference,
                                          getDimensionsIgnoresBaseLevel);
        }
        else
        {
            ImmutableString texCoordX("t.x");
            ImmutableString texCoordY("t.y");
            ImmutableString texCoordZ("t.z");
            if (textureFunction.method == TextureFunction::GATHER)
            {
                OutputTextureGatherFunctionBody(out, textureFunction, outputType, textureReference,
                                                samplerReference, texCoordX, texCoordY, texCoordZ);
            }
            else
            {
                ProjectTextureCoordinates(textureFunction, &texCoordX, &texCoordY, &texCoordZ);
                OutputIntegerTextureSampleFunctionComputations(out, textureFunction, outputType,
                                                               textureReference, &texCoordX,
                                                               &texCoordY, &texCoordZ);
                OutputTextureSampleFunctionReturnStatement(out, textureFunction, outputType,
                                                           textureReference, samplerReference,
                                                           texCoordX, texCoordY, texCoordZ);
            }
        }

        out << "}\n"
               "\n";
    }
}

}  // namespace sh
