//
// Copyright 2015 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.
//
// VaryingPacking:
//   Class which describes a mapping from varyings to registers in D3D
//   for linking between shader stages.
//

#include "libANGLE/renderer/d3d/VaryingPacking.h"

#include "common/utilities.h"
#include "compiler/translator/blocklayoutHLSL.h"
#include "libANGLE/renderer/d3d/DynamicHLSL.h"
#include "libANGLE/renderer/d3d/ProgramD3D.h"

namespace rx
{

SemanticInfo::BuiltinInfo::BuiltinInfo() : enabled(false), index(0), systemValue(false)
{
}

std::string SemanticInfo::BuiltinInfo::str() const
{
    return (systemValue ? semantic : (semantic + Str(index)));
}

void SemanticInfo::BuiltinInfo::enableSystem(const std::string &systemValueSemantic)
{
    enabled     = true;
    semantic    = systemValueSemantic;
    systemValue = true;
}

void SemanticInfo::BuiltinInfo::enable(const std::string &semanticVal, unsigned int indexVal)
{
    enabled  = true;
    semantic = semanticVal;
    index    = indexVal;
}

bool PackVarying(PackedVarying *packedVarying, const int maxVaryingVectors, VaryingPacking &packing)
{
    // Make sure we use transposed matrix types to count registers correctly.
    int registers = 0;
    int elements  = 0;

    const sh::Varying &varying = *packedVarying->varying;

    if (varying.isStruct())
    {
        registers = HLSLVariableRegisterCount(varying, true) * varying.elementCount();
        elements  = 4;
    }
    else
    {
        GLenum transposedType = gl::TransposeMatrixType(varying.type);
        registers             = gl::VariableRowCount(transposedType) * varying.elementCount();
        elements              = gl::VariableColumnCount(transposedType);
    }

    if (elements >= 2 && elements <= 4)
    {
        for (int r = 0; r <= maxVaryingVectors - registers; r++)
        {
            bool available = true;

            for (int y = 0; y < registers && available; y++)
            {
                for (int x = 0; x < elements && available; x++)
                {
                    if (packing[r + y][x])
                    {
                        available = false;
                    }
                }
            }

            if (available)
            {
                packedVarying->registerIndex = r;
                packedVarying->columnIndex   = 0;

                for (int y = 0; y < registers; y++)
                {
                    for (int x = 0; x < elements; x++)
                    {
                        packing[r + y][x] = packedVarying;
                    }
                }

                return true;
            }
        }

        if (elements == 2)
        {
            for (int r = maxVaryingVectors - registers; r >= 0; r--)
            {
                bool available = true;

                for (int y = 0; y < registers && available; y++)
                {
                    for (int x = 2; x < 4 && available; x++)
                    {
                        if (packing[r + y][x])
                        {
                            available = false;
                        }
                    }
                }

                if (available)
                {
                    packedVarying->registerIndex = r;
                    packedVarying->columnIndex   = 2;

                    for (int y = 0; y < registers; y++)
                    {
                        for (int x = 2; x < 4; x++)
                        {
                            packing[r + y][x] = packedVarying;
                        }
                    }

                    return true;
                }
            }
        }
    }
    else if (elements == 1)
    {
        int space[4] = {0};

        for (int y = 0; y < maxVaryingVectors; y++)
        {
            for (int x = 0; x < 4; x++)
            {
                space[x] += packing[y][x] ? 0 : 1;
            }
        }

        int column = 0;

        for (int x = 0; x < 4; x++)
        {
            if (space[x] >= registers && (space[column] < registers || space[x] < space[column]))
            {
                column = x;
            }
        }

        if (space[column] >= registers)
        {
            for (int r = 0; r < maxVaryingVectors; r++)
            {
                if (!packing[r][column])
                {
                    packedVarying->registerIndex = r;
                    packedVarying->columnIndex   = column;

                    for (int y = r; y < r + registers; y++)
                    {
                        packing[y][column] = packedVarying;
                    }

                    break;
                }
            }

            return true;
        }
    }
    else
        UNREACHABLE();

    return false;
}

unsigned int PackedVaryingRegister::registerIndex(
    const gl::Caps &caps,
    const std::vector<PackedVarying> &packedVaryings) const
{
    const PackedVarying &packedVarying = packedVaryings[varyingIndex];
    const sh::Varying &varying         = *packedVarying.varying;

    GLenum transposedType = gl::TransposeMatrixType(varying.type);
    unsigned int variableRows =
        static_cast<unsigned int>(varying.isStruct() ? 1 : gl::VariableRowCount(transposedType));

    return (elementIndex * variableRows + (packedVarying.columnIndex * caps.maxVaryingVectors) +
            (packedVarying.registerIndex + rowIndex));
}

PackedVaryingIterator::PackedVaryingIterator(const std::vector<PackedVarying> &packedVaryings)
    : mPackedVaryings(packedVaryings), mEnd(*this)
{
    mEnd.setEnd();
}

PackedVaryingIterator::Iterator PackedVaryingIterator::begin() const
{
    return Iterator(*this);
}

const PackedVaryingIterator::Iterator &PackedVaryingIterator::end() const
{
    return mEnd;
}

PackedVaryingIterator::Iterator::Iterator(const PackedVaryingIterator &parent) : mParent(parent)
{
    while (mRegister.varyingIndex < mParent.mPackedVaryings.size() &&
           !mParent.mPackedVaryings[mRegister.varyingIndex].registerAssigned())
    {
        ++mRegister.varyingIndex;
    }
}

PackedVaryingIterator::Iterator &PackedVaryingIterator::Iterator::operator++()
{
    const sh::Varying *varying = mParent.mPackedVaryings[mRegister.varyingIndex].varying;
    GLenum transposedType = gl::TransposeMatrixType(varying->type);
    unsigned int variableRows =
        static_cast<unsigned int>(varying->isStruct() ? 1 : gl::VariableRowCount(transposedType));

    // Innermost iteration: row count
    if (mRegister.rowIndex + 1 < variableRows)
    {
        ++mRegister.rowIndex;
        return *this;
    }

    mRegister.rowIndex = 0;

    // Middle iteration: element count
    if (mRegister.elementIndex + 1 < varying->elementCount())
    {
        ++mRegister.elementIndex;
        return *this;
    }

    mRegister.elementIndex = 0;

    // Outer iteration: the varying itself. Once we pass the last varying, this Iterator will
    // equal the end Iterator.
    do
    {
        ++mRegister.varyingIndex;
    } while (mRegister.varyingIndex < mParent.mPackedVaryings.size() &&
             !mParent.mPackedVaryings[mRegister.varyingIndex].registerAssigned());
    return *this;
}

bool PackedVaryingIterator::Iterator::operator==(const Iterator &other) const
{
    return mRegister.elementIndex == other.mRegister.elementIndex &&
           mRegister.rowIndex == other.mRegister.rowIndex &&
           mRegister.varyingIndex == other.mRegister.varyingIndex;
}

bool PackedVaryingIterator::Iterator::operator!=(const Iterator &other) const
{
    return !(*this == other);
}

// Packs varyings into generic varying registers, using the algorithm from [OpenGL ES Shading
// Language 1.00 rev. 17] appendix A section 7 page 111
// Returns the number of used varying registers, or -1 if unsuccesful
bool PackVaryings(const gl::Caps &caps,
                  gl::InfoLog &infoLog,
                  std::vector<PackedVarying> *packedVaryings,
                  const std::vector<std::string> &transformFeedbackVaryings,
                  unsigned int *registerCountOut)
{
    VaryingPacking packing = {};
    *registerCountOut      = 0;

    std::set<std::string> uniqueVaryingNames;

    for (PackedVarying &packedVarying : *packedVaryings)
    {
        const sh::Varying &varying = *packedVarying.varying;

        // Do not assign registers to built-in or unreferenced varyings
        if (varying.isBuiltIn() || !varying.staticUse)
        {
            continue;
        }

        ASSERT(uniqueVaryingNames.count(varying.name) == 0);

        if (PackVarying(&packedVarying, caps.maxVaryingVectors, packing))
        {
            uniqueVaryingNames.insert(varying.name);
        }
        else
        {
            infoLog << "Could not pack varying " << varying.name;
            return false;
        }
    }

    for (const std::string &transformFeedbackVaryingName : transformFeedbackVaryings)
    {
        if (transformFeedbackVaryingName.compare(0, 3, "gl_") == 0)
        {
            // do not pack builtin XFB varyings
            continue;
        }

        for (PackedVarying &packedVarying : *packedVaryings)
        {
            const sh::Varying &varying = *packedVarying.varying;

            // Make sure transform feedback varyings aren't optimized out.
            if (uniqueVaryingNames.count(transformFeedbackVaryingName) == 0)
            {
                bool found = false;
                if (transformFeedbackVaryingName == varying.name)
                {
                    if (!PackVarying(&packedVarying, caps.maxVaryingVectors, packing))
                    {
                        infoLog << "Could not pack varying " << varying.name;
                        return false;
                    }

                    found = true;
                    break;
                }
                if (!found)
                {
                    infoLog << "Transform feedback varying " << transformFeedbackVaryingName
                            << " does not exist in the vertex shader.";
                    return false;
                }
            }

            // Add duplicate transform feedback varyings for 'flat' shaded attributes. This is
            // necessary because we write out modified vertex data to correct for the provoking
            // vertex in D3D11. This extra transform feedback varying is the unmodified stream.
            if (varying.interpolation == sh::INTERPOLATION_FLAT)
            {
                sh::Varying duplicateVarying(varying);
                duplicateVarying.name = "StreamOut_" + duplicateVarying.name;
            }
        }
    }

    // Return the number of used registers
    for (GLuint r = 0; r < caps.maxVaryingVectors; r++)
    {
        if (packing[r][0] || packing[r][1] || packing[r][2] || packing[r][3])
        {
            (*registerCountOut)++;
        }
    }

    return true;
}

SemanticInfo GetSemanticInfo(ShaderType shaderType,
                             const ProgramD3DMetadata &programMetadata,
                             unsigned int startRegisters)
{
    int majorShaderModel = programMetadata.getRendererMajorShaderModel();
    bool position        = programMetadata.usesTransformFeedbackGLPosition();
    bool fragCoord       = programMetadata.usesFragCoord();
    bool pointCoord = shaderType == SHADER_VERTEX ? programMetadata.addsPointCoordToVertexShader()
                                                  : programMetadata.usesPointCoord();
    bool pointSize = programMetadata.usesSystemValuePointSize();
    bool hlsl4     = (majorShaderModel >= 4);

    const std::string &userSemantic    = GetVaryingSemantic(majorShaderModel, pointSize);
    unsigned int reservedRegisterIndex = startRegisters;

    SemanticInfo info;
    if (hlsl4)
    {
        info.dxPosition.enableSystem("SV_Position");
    }
    else if (shaderType == SHADER_PIXEL)
    {
        info.dxPosition.enableSystem("VPOS");
    }
    else
    {
        info.dxPosition.enableSystem("POSITION");
    }

    if (position)
    {
        info.glPosition.enable(userSemantic, reservedRegisterIndex++);
    }

    if (fragCoord)
    {
        info.glFragCoord.enable(userSemantic, reservedRegisterIndex++);
    }

    if (pointCoord)
    {
        // SM3 reserves the TEXCOORD semantic for point sprite texcoords (gl_PointCoord)
        // In D3D11 we manually compute gl_PointCoord in the GS.
        if (hlsl4)
        {
            info.glPointCoord.enable(userSemantic, reservedRegisterIndex++);
        }
        else
        {
            info.glPointCoord.enable("TEXCOORD", 0);
        }
    }

    // Special case: do not include PSIZE semantic in HLSL 3 pixel shaders
    if (pointSize && (shaderType != SHADER_PIXEL || hlsl4))
    {
        info.glPointSize.enableSystem("PSIZE");
    }

    return info;
}

}  // namespace rx
