/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Instanced rendering tests.
 *//*--------------------------------------------------------------------*/

#include "es3fInstancedRenderingTests.hpp"
#include "gluPixelTransfer.hpp"
#include "gluShaderProgram.hpp"
#include "gluShaderUtil.hpp"
#include "tcuTestLog.hpp"
#include "tcuSurface.hpp"
#include "tcuImageCompare.hpp"
#include "tcuVector.hpp"
#include "tcuRenderTarget.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "deString.h"

#include "glw.h"

using std::string;
using std::vector;

namespace deqp
{
namespace gles3
{
namespace Functional
{

static const int MAX_RENDER_WIDTH  = 128;
static const int MAX_RENDER_HEIGHT = 128;

static const int QUAD_GRID_SIZE = 127;

// Attribute divisors for the attributes defining the color's RGB components.
static const int ATTRIB_DIVISOR_R = 3;
static const int ATTRIB_DIVISOR_G = 2;
static const int ATTRIB_DIVISOR_B = 1;

static const int OFFSET_COMPONENTS =
    3; // \note Affects whether a float or a vecN is used in shader, but only first component is non-zero.

// Scale and bias values when converting float to integer, when attribute is of integer type.
static const float FLOAT_INT_SCALE  = 100.0f;
static const float FLOAT_INT_BIAS   = -50.0f;
static const float FLOAT_UINT_SCALE = 100.0f;
static const float FLOAT_UINT_BIAS  = 0.0f;

// \note Non-anonymous namespace needed; VarComp is used as a template parameter.
namespace vcns
{

union VarComp
{
    float f32;
    uint32_t u32;
    int32_t i32;

    VarComp(float v) : f32(v)
    {
    }
    VarComp(uint32_t v) : u32(v)
    {
    }
    VarComp(int32_t v) : i32(v)
    {
    }
};
DE_STATIC_ASSERT(sizeof(VarComp) == sizeof(uint32_t));

} // namespace vcns

using namespace vcns;

class InstancedRenderingCase : public TestCase
{
public:
    enum DrawFunction
    {
        FUNCTION_DRAW_ARRAYS_INSTANCED = 0,
        FUNCTION_DRAW_ELEMENTS_INSTANCED,

        FUNCTION_LAST
    };

    enum InstancingType
    {
        TYPE_INSTANCE_ID = 0,
        TYPE_ATTRIB_DIVISOR,
        TYPE_MIXED,

        TYPE_LAST
    };

    InstancedRenderingCase(Context &context, const char *name, const char *description, DrawFunction function,
                           InstancingType instancingType, glu::DataType rgbAttrType, int numInstances);
    ~InstancedRenderingCase(void);

    void init(void);
    void deinit(void);
    IterateResult iterate(void);

private:
    InstancedRenderingCase(const InstancedRenderingCase &other);
    InstancedRenderingCase &operator=(const InstancedRenderingCase &other);

    void pushVarCompAttrib(vector<VarComp> &vec, float val);

    void setupVarAttribPointer(const void *attrPtr, int startLocation, int divisor);
    void setupAndRender(void);
    void computeReference(tcu::Surface &dst);

    DrawFunction m_function;
    InstancingType m_instancingType;
    glu::DataType
        m_rgbAttrType; // \note Instance attribute types, color components only. Position offset attribute is always float/vecN.
    int m_numInstances;

    vector<float> m_gridVertexPositions; // X and Y components per vertex.
    vector<uint16_t> m_gridIndices;      // \note Only used if m_function is FUNCTION_DRAW_ELEMENTS_INSTANCED.

    // \note Some or all of the following instance attribute parameters may be unused with TYPE_INSTANCE_ID or TYPE_MIXED.
    vector<float> m_instanceOffsets; // Position offsets. OFFSET_COMPONENTS components per offset.
    // Attribute data for float, int or uint (or respective vector types) color components.
    vector<VarComp> m_instanceColorR;
    vector<VarComp> m_instanceColorG;
    vector<VarComp> m_instanceColorB;

    glu::ShaderProgram *m_program;
};

InstancedRenderingCase::InstancedRenderingCase(Context &context, const char *name, const char *description,
                                               DrawFunction function, InstancingType instancingType,
                                               glu::DataType rgbAttrType, int numInstances)
    : TestCase(context, name, description)
    , m_function(function)
    , m_instancingType(instancingType)
    , m_rgbAttrType(rgbAttrType)
    , m_numInstances(numInstances)
    , m_program(DE_NULL)
{
}

InstancedRenderingCase::~InstancedRenderingCase(void)
{
    InstancedRenderingCase::deinit();
}

// Helper function that does biasing and scaling when converting float to integer.
void InstancedRenderingCase::pushVarCompAttrib(vector<VarComp> &vec, float val)
{
    bool isFloatCase = glu::isDataTypeFloatOrVec(m_rgbAttrType);
    bool isIntCase   = glu::isDataTypeIntOrIVec(m_rgbAttrType);
    bool isUintCase  = glu::isDataTypeUintOrUVec(m_rgbAttrType);
    bool isMatCase   = glu::isDataTypeMatrix(m_rgbAttrType);

    if (isFloatCase || isMatCase)
        vec.push_back(VarComp(val));
    else if (isIntCase)
        vec.push_back(VarComp((int32_t)(val * FLOAT_INT_SCALE + FLOAT_INT_BIAS)));
    else if (isUintCase)
        vec.push_back(VarComp((uint32_t)(val * FLOAT_UINT_SCALE + FLOAT_UINT_BIAS)));
    else
        DE_ASSERT(false);
}

void InstancedRenderingCase::init(void)
{
    bool isFloatCase    = glu::isDataTypeFloatOrVec(m_rgbAttrType);
    bool isIntCase      = glu::isDataTypeIntOrIVec(m_rgbAttrType);
    bool isUintCase     = glu::isDataTypeUintOrUVec(m_rgbAttrType);
    bool isMatCase      = glu::isDataTypeMatrix(m_rgbAttrType);
    int typeSize        = glu::getDataTypeScalarSize(m_rgbAttrType);
    bool isScalarCase   = typeSize == 1;
    string swizzleFirst = isScalarCase ? "" : ".x";
    string typeName     = glu::getDataTypeName(m_rgbAttrType);

    string floatIntScaleStr  = "(" + de::floatToString(FLOAT_INT_SCALE, 3) + ")";
    string floatIntBiasStr   = "(" + de::floatToString(FLOAT_INT_BIAS, 3) + ")";
    string floatUintScaleStr = "(" + de::floatToString(FLOAT_UINT_SCALE, 3) + ")";
    string floatUintBiasStr  = "(" + de::floatToString(FLOAT_UINT_BIAS, 3) + ")";

    DE_ASSERT(isFloatCase || isIntCase || isUintCase || isMatCase);

    // Generate shader.
    // \note For case TYPE_MIXED, vertex position offset and color red component get their values from instance id, while green and blue get their values from instanced attributes.

    string numInstancesStr = de::toString(m_numInstances) + ".0";

    string instanceAttribs;
    string posExpression;
    string colorRExpression;
    string colorGExpression;
    string colorBExpression;

    if (m_instancingType == TYPE_INSTANCE_ID || m_instancingType == TYPE_MIXED)
    {
        posExpression    = "a_position + vec4(float(gl_InstanceID) * 2.0 / " + numInstancesStr + ", 0.0, 0.0, 0.0)";
        colorRExpression = "float(gl_InstanceID)/" + numInstancesStr;

        if (m_instancingType == TYPE_INSTANCE_ID)
        {
            colorGExpression = "float(gl_InstanceID)*2.0/" + numInstancesStr;
            colorBExpression = "1.0 - float(gl_InstanceID)/" + numInstancesStr;
        }
    }

    if (m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED)
    {
        if (m_instancingType == TYPE_ATTRIB_DIVISOR)
        {
            posExpression = "a_position + vec4(a_instanceOffset";

            DE_STATIC_ASSERT(OFFSET_COMPONENTS >= 1 && OFFSET_COMPONENTS <= 4);

            for (int i = 0; i < 4 - OFFSET_COMPONENTS; i++)
                posExpression += ", 0.0";
            posExpression += ")";

            if (isFloatCase)
                colorRExpression = "a_instanceR" + swizzleFirst;
            else if (isIntCase)
                colorRExpression =
                    "(float(a_instanceR" + swizzleFirst + ") - " + floatIntBiasStr + ") / " + floatIntScaleStr;
            else if (isUintCase)
                colorRExpression =
                    "(float(a_instanceR" + swizzleFirst + ") - " + floatUintBiasStr + ") / " + floatUintScaleStr;
            else if (isMatCase)
                colorRExpression = "a_instanceR[0][0]";
            else
                DE_ASSERT(false);

            instanceAttribs += "in highp " +
                               (OFFSET_COMPONENTS == 1 ? string("float") : "vec" + de::toString(OFFSET_COMPONENTS)) +
                               " a_instanceOffset;\n";
            instanceAttribs += "in mediump " + typeName + " a_instanceR;\n";
        }

        if (isFloatCase)
        {
            colorGExpression = "a_instanceG" + swizzleFirst;
            colorBExpression = "a_instanceB" + swizzleFirst;
        }
        else if (isIntCase)
        {
            colorGExpression =
                "(float(a_instanceG" + swizzleFirst + ") - " + floatIntBiasStr + ") / " + floatIntScaleStr;
            colorBExpression =
                "(float(a_instanceB" + swizzleFirst + ") - " + floatIntBiasStr + ") / " + floatIntScaleStr;
        }
        else if (isUintCase)
        {
            colorGExpression =
                "(float(a_instanceG" + swizzleFirst + ") - " + floatUintBiasStr + ") / " + floatUintScaleStr;
            colorBExpression =
                "(float(a_instanceB" + swizzleFirst + ") - " + floatUintBiasStr + ") / " + floatUintScaleStr;
        }
        else if (isMatCase)
        {
            colorGExpression = "a_instanceG[0][0]";
            colorBExpression = "a_instanceB[0][0]";
        }
        else
            DE_ASSERT(false);

        instanceAttribs += "in mediump " + typeName + " a_instanceG;\n";
        instanceAttribs += "in mediump " + typeName + " a_instanceB;\n";
    }

    DE_ASSERT(!posExpression.empty());
    DE_ASSERT(!colorRExpression.empty());
    DE_ASSERT(!colorGExpression.empty());
    DE_ASSERT(!colorBExpression.empty());

    std::string vertShaderSourceStr = "#version 300 es\n"
                                      "in highp vec4 a_position;\n" +
                                      instanceAttribs +
                                      "out mediump vec4 v_color;\n"
                                      "\n"
                                      "void main()\n"
                                      "{\n"
                                      "    gl_Position = " +
                                      posExpression +
                                      ";\n"
                                      "    v_color.r = " +
                                      colorRExpression +
                                      ";\n"
                                      "    v_color.g = " +
                                      colorGExpression +
                                      ";\n"
                                      "    v_color.b = " +
                                      colorBExpression +
                                      ";\n"
                                      "    v_color.a = 1.0;\n"
                                      "}\n";

    static const char *fragShaderSource = "#version 300 es\n"
                                          "layout(location = 0) out mediump vec4 o_color;\n"
                                          "in mediump vec4 v_color;\n"
                                          "\n"
                                          "void main()\n"
                                          "{\n"
                                          "    o_color = v_color;\n"
                                          "}\n";

    // Create shader program and log it.

    DE_ASSERT(!m_program);
    m_program = new glu::ShaderProgram(m_context.getRenderContext(),
                                       glu::makeVtxFragSources(vertShaderSourceStr, fragShaderSource));

    tcu::TestLog &log = m_testCtx.getLog();

    log << *m_program;

    if (!m_program->isOk())
        TCU_FAIL("Failed to compile shader");

    // Vertex shader attributes.

    if (m_function == FUNCTION_DRAW_ELEMENTS_INSTANCED)
    {
        // Vertex positions. Positions form a vertical bar of width <screen width>/<number of instances>.

        for (int y = 0; y < QUAD_GRID_SIZE + 1; y++)
            for (int x = 0; x < QUAD_GRID_SIZE + 1; x++)
            {
                float fx = -1.0f + (float)x / (float)QUAD_GRID_SIZE * 2.0f / (float)m_numInstances;
                float fy = -1.0f + (float)y / (float)QUAD_GRID_SIZE * 2.0f;

                m_gridVertexPositions.push_back(fx);
                m_gridVertexPositions.push_back(fy);
            }

        // Indices.

        for (int y = 0; y < QUAD_GRID_SIZE; y++)
            for (int x = 0; x < QUAD_GRID_SIZE; x++)
            {
                int ndx00 = y * (QUAD_GRID_SIZE + 1) + x;
                int ndx10 = y * (QUAD_GRID_SIZE + 1) + x + 1;
                int ndx01 = (y + 1) * (QUAD_GRID_SIZE + 1) + x;
                int ndx11 = (y + 1) * (QUAD_GRID_SIZE + 1) + x + 1;

                // Lower-left triangle of a quad.
                m_gridIndices.push_back((uint16_t)ndx00);
                m_gridIndices.push_back((uint16_t)ndx10);
                m_gridIndices.push_back((uint16_t)ndx01);

                // Upper-right triangle of a quad.
                m_gridIndices.push_back((uint16_t)ndx11);
                m_gridIndices.push_back((uint16_t)ndx01);
                m_gridIndices.push_back((uint16_t)ndx10);
            }
    }
    else
    {
        DE_ASSERT(m_function == FUNCTION_DRAW_ARRAYS_INSTANCED);

        // Vertex positions. Positions form a vertical bar of width <screen width>/<number of instances>.

        for (int y = 0; y < QUAD_GRID_SIZE; y++)
            for (int x = 0; x < QUAD_GRID_SIZE; x++)
            {
                float fx0 = -1.0f + (float)(x + 0) / (float)QUAD_GRID_SIZE * 2.0f / (float)m_numInstances;
                float fx1 = -1.0f + (float)(x + 1) / (float)QUAD_GRID_SIZE * 2.0f / (float)m_numInstances;
                float fy0 = -1.0f + (float)(y + 0) / (float)QUAD_GRID_SIZE * 2.0f;
                float fy1 = -1.0f + (float)(y + 1) / (float)QUAD_GRID_SIZE * 2.0f;

                // Vertices of a quad's lower-left triangle: (fx0, fy0), (fx1, fy0) and (fx0, fy1)
                m_gridVertexPositions.push_back(fx0);
                m_gridVertexPositions.push_back(fy0);
                m_gridVertexPositions.push_back(fx1);
                m_gridVertexPositions.push_back(fy0);
                m_gridVertexPositions.push_back(fx0);
                m_gridVertexPositions.push_back(fy1);

                // Vertices of a quad's upper-right triangle: (fx1, fy1), (fx0, fy1) and (fx1, fy0)
                m_gridVertexPositions.push_back(fx1);
                m_gridVertexPositions.push_back(fy1);
                m_gridVertexPositions.push_back(fx0);
                m_gridVertexPositions.push_back(fy1);
                m_gridVertexPositions.push_back(fx1);
                m_gridVertexPositions.push_back(fy0);
            }
    }

    // Instanced attributes: position offset and color RGB components.

    if (m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED)
    {
        if (m_instancingType == TYPE_ATTRIB_DIVISOR)
        {
            // Offsets are such that the vertical bars are drawn next to each other.
            for (int i = 0; i < m_numInstances; i++)
            {
                m_instanceOffsets.push_back((float)i * 2.0f / (float)m_numInstances);

                DE_STATIC_ASSERT(OFFSET_COMPONENTS >= 1 && OFFSET_COMPONENTS <= 4);

                for (int j = 0; j < OFFSET_COMPONENTS - 1; j++)
                    m_instanceOffsets.push_back(0.0f);
            }

            int rInstances = m_numInstances / ATTRIB_DIVISOR_R + (m_numInstances % ATTRIB_DIVISOR_R == 0 ? 0 : 1);
            for (int i = 0; i < rInstances; i++)
            {
                pushVarCompAttrib(m_instanceColorR, (float)i / (float)rInstances);

                for (int j = 0; j < typeSize - 1; j++)
                    pushVarCompAttrib(m_instanceColorR, 0.0f);
            }
        }

        int gInstances = m_numInstances / ATTRIB_DIVISOR_G + (m_numInstances % ATTRIB_DIVISOR_G == 0 ? 0 : 1);
        for (int i = 0; i < gInstances; i++)
        {
            pushVarCompAttrib(m_instanceColorG, (float)i * 2.0f / (float)gInstances);

            for (int j = 0; j < typeSize - 1; j++)
                pushVarCompAttrib(m_instanceColorG, 0.0f);
        }

        int bInstances = m_numInstances / ATTRIB_DIVISOR_B + (m_numInstances % ATTRIB_DIVISOR_B == 0 ? 0 : 1);
        for (int i = 0; i < bInstances; i++)
        {
            pushVarCompAttrib(m_instanceColorB, 1.0f - (float)i / (float)bInstances);

            for (int j = 0; j < typeSize - 1; j++)
                pushVarCompAttrib(m_instanceColorB, 0.0f);
        }
    }
}

void InstancedRenderingCase::deinit(void)
{
    delete m_program;
    m_program = DE_NULL;
}

InstancedRenderingCase::IterateResult InstancedRenderingCase::iterate(void)
{
    int width  = deMin32(m_context.getRenderTarget().getWidth(), MAX_RENDER_WIDTH);
    int height = deMin32(m_context.getRenderTarget().getHeight(), MAX_RENDER_HEIGHT);

    int xOffsetMax = m_context.getRenderTarget().getWidth() - width;
    int yOffsetMax = m_context.getRenderTarget().getHeight() - height;

    de::Random rnd(deStringHash(getName()));

    int xOffset = rnd.getInt(0, xOffsetMax);
    int yOffset = rnd.getInt(0, yOffsetMax);
    tcu::Surface referenceImg(width, height);
    tcu::Surface resultImg(width, height);

    // Draw result.

    glViewport(xOffset, yOffset, width, height);

    setupAndRender();

    glu::readPixels(m_context.getRenderContext(), xOffset, yOffset, resultImg.getAccess());

    // Compute reference.

    computeReference(referenceImg);

    // Compare.

    bool testOk = tcu::fuzzyCompare(m_testCtx.getLog(), "ComparisonResult", "Image comparison result", referenceImg,
                                    resultImg, 0.05f, tcu::COMPARE_LOG_RESULT);

    m_testCtx.setTestResult(testOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL, testOk ? "Pass" : "Fail");

    return STOP;
}

void InstancedRenderingCase::setupVarAttribPointer(const void *attrPtr, int location, int divisor)
{
    bool isFloatCase = glu::isDataTypeFloatOrVec(m_rgbAttrType);
    bool isIntCase   = glu::isDataTypeIntOrIVec(m_rgbAttrType);
    bool isUintCase  = glu::isDataTypeUintOrUVec(m_rgbAttrType);
    bool isMatCase   = glu::isDataTypeMatrix(m_rgbAttrType);
    int typeSize     = glu::getDataTypeScalarSize(m_rgbAttrType);
    int numSlots     = isMatCase ? glu::getDataTypeMatrixNumColumns(m_rgbAttrType) :
                                   1; // Matrix uses as many attribute slots as it has columns.

    for (int slotNdx = 0; slotNdx < numSlots; slotNdx++)
    {
        int curLoc = location + slotNdx;

        glEnableVertexAttribArray(curLoc);
        glVertexAttribDivisor(curLoc, divisor);

        if (isFloatCase)
            glVertexAttribPointer(curLoc, typeSize, GL_FLOAT, GL_FALSE, 0, attrPtr);
        else if (isIntCase)
            glVertexAttribIPointer(curLoc, typeSize, GL_INT, 0, attrPtr);
        else if (isUintCase)
            glVertexAttribIPointer(curLoc, typeSize, GL_UNSIGNED_INT, 0, attrPtr);
        else if (isMatCase)
        {
            int numRows = glu::getDataTypeMatrixNumRows(m_rgbAttrType);
            int numCols = glu::getDataTypeMatrixNumColumns(m_rgbAttrType);

            glVertexAttribPointer(curLoc, numRows, GL_FLOAT, GL_FALSE, numCols * numRows * (int)sizeof(float), attrPtr);
        }
        else
            DE_ASSERT(false);
    }
}

void InstancedRenderingCase::setupAndRender(void)
{
    uint32_t program = m_program->getProgram();

    glUseProgram(program);

    {
        // Setup attributes.

        // Position attribute is non-instanced.
        int positionLoc = glGetAttribLocation(program, "a_position");
        glEnableVertexAttribArray(positionLoc);
        glVertexAttribPointer(positionLoc, 2, GL_FLOAT, GL_FALSE, 0, &m_gridVertexPositions[0]);

        if (m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED)
        {
            if (m_instancingType == TYPE_ATTRIB_DIVISOR)
            {
                // Position offset attribute is instanced with separate offset for every instance.
                int offsetLoc = glGetAttribLocation(program, "a_instanceOffset");
                glEnableVertexAttribArray(offsetLoc);
                glVertexAttribDivisor(offsetLoc, 1);
                glVertexAttribPointer(offsetLoc, OFFSET_COMPONENTS, GL_FLOAT, GL_FALSE, 0, &m_instanceOffsets[0]);

                int rLoc = glGetAttribLocation(program, "a_instanceR");
                setupVarAttribPointer((void *)&m_instanceColorR[0].u32, rLoc, ATTRIB_DIVISOR_R);
            }

            int gLoc = glGetAttribLocation(program, "a_instanceG");
            setupVarAttribPointer((void *)&m_instanceColorG[0].u32, gLoc, ATTRIB_DIVISOR_G);

            int bLoc = glGetAttribLocation(program, "a_instanceB");
            setupVarAttribPointer((void *)&m_instanceColorB[0].u32, bLoc, ATTRIB_DIVISOR_B);
        }
    }

    // Draw using appropriate function.

    if (m_function == FUNCTION_DRAW_ARRAYS_INSTANCED)
    {
        const int numPositionComponents = 2;
        glDrawArraysInstanced(GL_TRIANGLES, 0, ((int)m_gridVertexPositions.size() / numPositionComponents),
                              m_numInstances);
    }
    else
        glDrawElementsInstanced(GL_TRIANGLES, (int)m_gridIndices.size(), GL_UNSIGNED_SHORT, &m_gridIndices[0],
                                m_numInstances);

    glUseProgram(0);
}

void InstancedRenderingCase::computeReference(tcu::Surface &dst)
{
    int wid = dst.getWidth();
    int hei = dst.getHeight();

    // Draw a rectangle (vertical bar) for each instance.

    for (int instanceNdx = 0; instanceNdx < m_numInstances; instanceNdx++)
    {
        int xStart = instanceNdx * wid / m_numInstances;
        int xEnd   = (instanceNdx + 1) * wid / m_numInstances;

        // Emulate attribute divisors if that is the case.

        int clrNdxR = m_instancingType == TYPE_ATTRIB_DIVISOR ? instanceNdx / ATTRIB_DIVISOR_R : instanceNdx;
        int clrNdxG = m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED ?
                          instanceNdx / ATTRIB_DIVISOR_G :
                          instanceNdx;
        int clrNdxB = m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED ?
                          instanceNdx / ATTRIB_DIVISOR_B :
                          instanceNdx;

        int rInstances = m_instancingType == TYPE_ATTRIB_DIVISOR ?
                             m_numInstances / ATTRIB_DIVISOR_R + (m_numInstances % ATTRIB_DIVISOR_R == 0 ? 0 : 1) :
                             m_numInstances;
        int gInstances = m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED ?
                             m_numInstances / ATTRIB_DIVISOR_G + (m_numInstances % ATTRIB_DIVISOR_G == 0 ? 0 : 1) :
                             m_numInstances;
        int bInstances = m_instancingType == TYPE_ATTRIB_DIVISOR || m_instancingType == TYPE_MIXED ?
                             m_numInstances / ATTRIB_DIVISOR_B + (m_numInstances % ATTRIB_DIVISOR_B == 0 ? 0 : 1) :
                             m_numInstances;

        // Calculate colors.

        float r = (float)clrNdxR / (float)rInstances;
        float g = (float)clrNdxG * 2.0f / (float)gInstances;
        float b = 1.0f - (float)clrNdxB / (float)bInstances;

        // Convert to integer and back if shader inputs are integers.

        if (glu::isDataTypeIntOrIVec(m_rgbAttrType))
        {
            int32_t intR = (int32_t)(r * FLOAT_INT_SCALE + FLOAT_INT_BIAS);
            int32_t intG = (int32_t)(g * FLOAT_INT_SCALE + FLOAT_INT_BIAS);
            int32_t intB = (int32_t)(b * FLOAT_INT_SCALE + FLOAT_INT_BIAS);
            r            = ((float)intR - FLOAT_INT_BIAS) / FLOAT_INT_SCALE;
            g            = ((float)intG - FLOAT_INT_BIAS) / FLOAT_INT_SCALE;
            b            = ((float)intB - FLOAT_INT_BIAS) / FLOAT_INT_SCALE;
        }
        else if (glu::isDataTypeUintOrUVec(m_rgbAttrType))
        {
            uint32_t uintR = (int32_t)(r * FLOAT_UINT_SCALE + FLOAT_UINT_BIAS);
            uint32_t uintG = (int32_t)(g * FLOAT_UINT_SCALE + FLOAT_UINT_BIAS);
            uint32_t uintB = (int32_t)(b * FLOAT_UINT_SCALE + FLOAT_UINT_BIAS);
            r              = ((float)uintR - FLOAT_UINT_BIAS) / FLOAT_UINT_SCALE;
            g              = ((float)uintG - FLOAT_UINT_BIAS) / FLOAT_UINT_SCALE;
            b              = ((float)uintB - FLOAT_UINT_BIAS) / FLOAT_UINT_SCALE;
        }

        // Draw rectangle.

        for (int y = 0; y < hei; y++)
            for (int x = xStart; x < xEnd; x++)
                dst.setPixel(x, y, tcu::RGBA(tcu::Vec4(r, g, b, 1.0f)));
    }
}

InstancedRenderingTests::InstancedRenderingTests(Context &context)
    : TestCaseGroup(context, "instanced", "Instanced rendering tests")
{
}

InstancedRenderingTests::~InstancedRenderingTests(void)
{
}

void InstancedRenderingTests::init(void)
{
    // Cases testing function, instancing method and instance count.

    static const int instanceCounts[] = {1, 2, 4, 20};

    for (int function = 0; function < (int)InstancedRenderingCase::FUNCTION_LAST; function++)
    {
        const char *functionName =
            function == (int)InstancedRenderingCase::FUNCTION_DRAW_ARRAYS_INSTANCED   ? "draw_arrays_instanced" :
            function == (int)InstancedRenderingCase::FUNCTION_DRAW_ELEMENTS_INSTANCED ? "draw_elements_instanced" :
                                                                                        DE_NULL;

        const char *functionDesc = function == (int)InstancedRenderingCase::FUNCTION_DRAW_ARRAYS_INSTANCED ?
                                       "Use glDrawArraysInstanced()" :
                                   function == (int)InstancedRenderingCase::FUNCTION_DRAW_ELEMENTS_INSTANCED ?
                                       "Use glDrawElementsInstanced()" :
                                       DE_NULL;

        DE_ASSERT(functionName != DE_NULL);
        DE_ASSERT(functionDesc != DE_NULL);

        TestCaseGroup *functionGroup = new TestCaseGroup(m_context, functionName, functionDesc);
        addChild(functionGroup);

        for (int instancingType = 0; instancingType < (int)InstancedRenderingCase::TYPE_LAST; instancingType++)
        {
            const char *instancingTypeName =
                instancingType == (int)InstancedRenderingCase::TYPE_INSTANCE_ID    ? "instance_id" :
                instancingType == (int)InstancedRenderingCase::TYPE_ATTRIB_DIVISOR ? "attribute_divisor" :
                instancingType == (int)InstancedRenderingCase::TYPE_MIXED          ? "mixed" :
                                                                                     DE_NULL;

            const char *instancingTypeDesc = instancingType == (int)InstancedRenderingCase::TYPE_INSTANCE_ID ?
                                                 "Use gl_InstanceID for instancing" :
                                             instancingType == (int)InstancedRenderingCase::TYPE_ATTRIB_DIVISOR ?
                                                 "Use vertex attribute divisors for instancing" :
                                             instancingType == (int)InstancedRenderingCase::TYPE_MIXED ?
                                                 "Use both gl_InstanceID and vertex attribute divisors for instancing" :
                                                 DE_NULL;

            DE_ASSERT(instancingTypeName != DE_NULL);
            DE_ASSERT(instancingTypeDesc != DE_NULL);

            TestCaseGroup *instancingTypeGroup = new TestCaseGroup(m_context, instancingTypeName, instancingTypeDesc);
            functionGroup->addChild(instancingTypeGroup);

            for (int countNdx = 0; countNdx < DE_LENGTH_OF_ARRAY(instanceCounts); countNdx++)
            {
                std::string countName = de::toString(instanceCounts[countNdx]) + "_instances";

                instancingTypeGroup->addChild(new InstancedRenderingCase(
                    m_context, countName.c_str(), "", (InstancedRenderingCase::DrawFunction)function,
                    (InstancedRenderingCase::InstancingType)instancingType, glu::TYPE_FLOAT, instanceCounts[countNdx]));
            }
        }
    }

    // Data type specific cases.

    static const glu::DataType s_testTypes[] = {
        glu::TYPE_FLOAT,      glu::TYPE_FLOAT_VEC2,   glu::TYPE_FLOAT_VEC3,   glu::TYPE_FLOAT_VEC4,
        glu::TYPE_FLOAT_MAT2, glu::TYPE_FLOAT_MAT2X3, glu::TYPE_FLOAT_MAT2X4, glu::TYPE_FLOAT_MAT3X2,
        glu::TYPE_FLOAT_MAT3, glu::TYPE_FLOAT_MAT3X4, glu::TYPE_FLOAT_MAT4X2, glu::TYPE_FLOAT_MAT4X3,
        glu::TYPE_FLOAT_MAT4,

        glu::TYPE_INT,        glu::TYPE_INT_VEC2,     glu::TYPE_INT_VEC3,     glu::TYPE_INT_VEC4,

        glu::TYPE_UINT,       glu::TYPE_UINT_VEC2,    glu::TYPE_UINT_VEC3,    glu::TYPE_UINT_VEC4};

    const int typeTestNumInstances = 4;

    TestCaseGroup *typesGroup =
        new TestCaseGroup(m_context, "types", "Tests for instanced attributes of particular data types");
    addChild(typesGroup);

    for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(s_testTypes); typeNdx++)
    {
        glu::DataType type = s_testTypes[typeNdx];

        typesGroup->addChild(new InstancedRenderingCase(
            m_context, glu::getDataTypeName(type), "", InstancedRenderingCase::FUNCTION_DRAW_ARRAYS_INSTANCED,
            InstancedRenderingCase::TYPE_ATTRIB_DIVISOR, type, typeTestNumInstances));
    }
}

} // namespace Functional
} // namespace gles3
} // namespace deqp
