/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 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 Multisample interpolation tests
 *//*--------------------------------------------------------------------*/

#include "es31fShaderMultisampleInterpolationTests.hpp"
#include "es31fMultisampleShaderRenderCase.hpp"
#include "tcuTestLog.hpp"
#include "tcuRGBA.hpp"
#include "tcuSurface.hpp"
#include "tcuStringTemplate.hpp"
#include "tcuRenderTarget.hpp"
#include "gluContextInfo.hpp"
#include "gluShaderProgram.hpp"
#include "gluRenderContext.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deArrayUtil.hpp"
#include "deStringUtil.hpp"
#include "deMath.h"

#include <map>

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

static std::string specializeShader(const std::string &shaderSource, const glu::ContextType &contextType)
{
    const bool isES32orGL45 = glu::contextSupports(contextType, glu::ApiType::es(3, 2)) ||
                              glu::contextSupports(contextType, glu::ApiType::core(4, 5));

    std::map<std::string, std::string> args;
    args["GLSL_VERSION_DECL"] = glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(contextType));
    args["GLSL_EXT_SHADER_MULTISAMPLE_INTERPOLATION"] =
        isES32orGL45 ? "" : "#extension GL_OES_shader_multisample_interpolation : require\n";
    args["GLSL_EXT_SAMPLE_VARIABLES"] = isES32orGL45 ? "" : "#extension GL_OES_sample_variables : require\n";

    return tcu::StringTemplate(shaderSource).specialize(args);
}

static bool checkSupport(Context &ctx)
{
    auto ctxType = ctx.getRenderContext().getType();
    return glu::contextSupports(ctxType, glu::ApiType::es(3, 2)) ||
           glu::contextSupports(ctxType, glu::ApiType::core(4, 5));
}

static bool verifyGreenImage(const tcu::Surface &image, tcu::TestLog &log)
{
    bool error = false;

    log << tcu::TestLog::Message << "Verifying result image, expecting green." << tcu::TestLog::EndMessage;

    // all pixels must be green

    for (int y = 0; y < image.getHeight(); ++y)
        for (int x = 0; x < image.getWidth(); ++x)
        {
            const tcu::RGBA color    = image.getPixel(x, y);
            const int greenThreshold = 8;

            if (color.getRed() > 0 || color.getGreen() < 255 - greenThreshold || color.getBlue() > 0)
                error = true;
        }

    if (error)
        log << tcu::TestLog::Image("ResultImage", "Result Image", image.getAccess()) << tcu::TestLog::Message
            << "Image verification failed." << tcu::TestLog::EndMessage;
    else
        log << tcu::TestLog::Image("ResultImage", "Result Image", image.getAccess()) << tcu::TestLog::Message
            << "Image verification passed." << tcu::TestLog::EndMessage;

    return !error;
}

class MultisampleShadeCountRenderCase : public MultisampleShaderRenderUtil::MultisampleRenderCase
{
public:
    MultisampleShadeCountRenderCase(Context &context, const char *name, const char *description, int numSamples,
                                    RenderTarget target);
    virtual ~MultisampleShadeCountRenderCase(void);

    void init(void);

private:
    enum
    {
        RENDER_SIZE = 128
    };

    virtual std::string getIterationDescription(int iteration) const;
    bool verifyImage(const tcu::Surface &resultImage);
};

MultisampleShadeCountRenderCase::MultisampleShadeCountRenderCase(Context &context, const char *name,
                                                                 const char *description, int numSamples,
                                                                 RenderTarget target)
    : MultisampleShaderRenderUtil::MultisampleRenderCase(
          context, name, description, numSamples, target, RENDER_SIZE,
          MultisampleShaderRenderUtil::MultisampleRenderCase::FLAG_PER_ITERATION_SHADER)
{
    m_numIterations = -1; // must be set by deriving class
}

MultisampleShadeCountRenderCase::~MultisampleShadeCountRenderCase(void)
{
}

void MultisampleShadeCountRenderCase::init(void)
{
    // requirements
    if (!checkSupport(m_context) &&
        !m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
        TCU_THROW(NotSupportedError, "Test requires GL_OES_shader_multisample_interpolation extension");

    MultisampleShaderRenderUtil::MultisampleRenderCase::init();
}

std::string MultisampleShadeCountRenderCase::getIterationDescription(int iteration) const
{
    // must be overriden
    DE_UNREF(iteration);
    DE_ASSERT(false);
    return "";
}

bool MultisampleShadeCountRenderCase::verifyImage(const tcu::Surface &resultImage)
{
    const bool isSingleSampleTarget =
        (m_renderTarget != TARGET_DEFAULT && m_numRequestedSamples == 0) ||
        (m_renderTarget == TARGET_DEFAULT && m_context.getRenderTarget().getNumSamples() <= 1);
    const int numShadesRequired = (isSingleSampleTarget) ? (2) : (m_numTargetSamples + 1);
    const int rareThreshold     = 100;
    int rareCount               = 0;
    std::map<uint32_t, int> shadeFrequency;

    m_testCtx.getLog() << tcu::TestLog::Image("ResultImage", "Result Image", resultImage.getAccess())
                       << tcu::TestLog::Message << "Verifying image has (at least) " << numShadesRequired
                       << " different shades.\n"
                       << "Excluding pixels with no full coverage (pixels on the shared edge of the triangle pair)."
                       << tcu::TestLog::EndMessage;

    for (int y = 0; y < RENDER_SIZE; ++y)
        for (int x = 0; x < RENDER_SIZE; ++x)
        {
            const tcu::RGBA color = resultImage.getPixel(x, y);
            const uint32_t packed =
                ((uint32_t)color.getRed()) + ((uint32_t)color.getGreen() << 8) + ((uint32_t)color.getGreen() << 16);

            // on the triangle edge, skip
            if (x == y)
                continue;

            if (shadeFrequency.find(packed) == shadeFrequency.end())
                shadeFrequency[packed] = 1;
            else
                shadeFrequency[packed] = shadeFrequency[packed] + 1;
        }

    for (std::map<uint32_t, int>::const_iterator it = shadeFrequency.begin(); it != shadeFrequency.end(); ++it)
        if (it->second < rareThreshold)
            rareCount++;

    m_testCtx.getLog() << tcu::TestLog::Message << "Found " << (int)shadeFrequency.size() << " different shades.\n"
                       << "\tRare (less than " << rareThreshold << " pixels): " << rareCount << "\n"
                       << "\tCommon: " << (int)shadeFrequency.size() - rareCount << "\n"
                       << tcu::TestLog::EndMessage;

    if ((int)shadeFrequency.size() < numShadesRequired)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage;
        return false;
    }
    return true;
}

class SampleQualifierRenderCase : public MultisampleShadeCountRenderCase
{
public:
    SampleQualifierRenderCase(Context &context, const char *name, const char *description, int numSamples,
                              RenderTarget target);
    ~SampleQualifierRenderCase(void);

    void init(void);

private:
    std::string genVertexSource(int numTargetSamples) const;
    std::string genFragmentSource(int numTargetSamples) const;
    std::string getIterationDescription(int iteration) const;
};

SampleQualifierRenderCase::SampleQualifierRenderCase(Context &context, const char *name, const char *description,
                                                     int numSamples, RenderTarget target)
    : MultisampleShadeCountRenderCase(context, name, description, numSamples, target)
{
    m_numIterations = 6; // float, vec2, .3, .4, array, struct
}

SampleQualifierRenderCase::~SampleQualifierRenderCase(void)
{
}

void SampleQualifierRenderCase::init(void)
{
    const bool isSingleSampleTarget =
        (m_renderTarget != TARGET_DEFAULT && m_numRequestedSamples == 0) ||
        (m_renderTarget == TARGET_DEFAULT && m_context.getRenderTarget().getNumSamples() <= 1);

    // test purpose and expectations
    if (isSingleSampleTarget)
    {
        m_testCtx.getLog()
            << tcu::TestLog::Message
            << "Verifying that a sample-qualified varying is given different values for different samples.\n"
            << "    Render high-frequency function, map result to black/white.\n"
            << " => Resulting image image should contain both black and white pixels.\n"
            << tcu::TestLog::EndMessage;
    }
    else
    {
        m_testCtx.getLog()
            << tcu::TestLog::Message
            << "Verifying that a sample-qualified varying is given different values for different samples.\n"
            << "    Render high-frequency function, map result to black/white.\n"
            << " => Resulting image should contain n+1 shades of gray, n = sample count.\n"
            << tcu::TestLog::EndMessage;
    }

    MultisampleShadeCountRenderCase::init();
}

std::string SampleQualifierRenderCase::genVertexSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_EXT_SHADER_MULTISAMPLE_INTERPOLATION}"
           "in highp vec4 a_position;\n";

    if (m_iteration == 0)
        buf << "sample out highp float v_input;\n";
    else if (m_iteration == 1)
        buf << "sample out highp vec2 v_input;\n";
    else if (m_iteration == 2)
        buf << "sample out highp vec3 v_input;\n";
    else if (m_iteration == 3)
        buf << "sample out highp vec4 v_input;\n";
    else if (m_iteration == 4)
        buf << "sample out highp float[2] v_input;\n";
    else if (m_iteration == 5)
        buf << "struct VaryingStruct { highp float a; highp float b; };\n"
               "sample out VaryingStruct v_input;\n";
    else
        DE_ASSERT(false);

    buf << "void main (void)\n"
           "{\n"
           "    gl_Position = a_position;\n";

    if (m_iteration == 0)
        buf << "    v_input = a_position.x + exp(a_position.y) + step(0.9, a_position.x)*step(a_position.y, "
               "-0.9)*8.0;\n";
    else if (m_iteration == 1)
        buf << "    v_input = a_position.xy;\n";
    else if (m_iteration == 2)
        buf << "    v_input = vec3(a_position.xy, a_position.x * 2.0 - a_position.y);\n";
    else if (m_iteration == 3)
        buf << "    v_input = vec4(a_position.xy, a_position.x * 2.0 - a_position.y, a_position.x*a_position.y);\n";
    else if (m_iteration == 4)
        buf << "    v_input[0] = a_position.x;\n"
               "    v_input[1] = a_position.y;\n";
    else if (m_iteration == 5)
        buf << "    v_input.a = a_position.x;\n"
               "    v_input.b = a_position.y;\n";
    else
        DE_ASSERT(false);

    buf << "}";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string SampleQualifierRenderCase::genFragmentSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_EXT_SHADER_MULTISAMPLE_INTERPOLATION}";

    if (m_iteration == 0)
        buf << "sample in highp float v_input;\n";
    else if (m_iteration == 1)
        buf << "sample in highp vec2 v_input;\n";
    else if (m_iteration == 2)
        buf << "sample in highp vec3 v_input;\n";
    else if (m_iteration == 3)
        buf << "sample in highp vec4 v_input;\n";
    else if (m_iteration == 4)
        buf << "sample in highp float[2] v_input;\n";
    else if (m_iteration == 5)
        buf << "struct VaryingStruct { highp float a; highp float b; };\n"
               "sample in VaryingStruct v_input;\n";
    else
        DE_ASSERT(false);

    buf << "layout(location = 0) out mediump vec4 fragColor;\n"
           "void main (void)\n"
           "{\n";

    if (m_iteration == 0)
        buf << "    highp float field = exp(v_input) + v_input*v_input;\n";
    else if (m_iteration == 1)
        buf << "    highp float field = dot(v_input.xy, v_input.xy) + dot(21.0 * v_input.xx, sin(3.1 * v_input.xy));\n";
    else if (m_iteration == 2)
        buf << "    highp float field = dot(v_input.xy, v_input.xy) + dot(21.0 * v_input.zx, sin(3.1 * v_input.zy));\n";
    else if (m_iteration == 3)
        buf << "    highp float field = dot(v_input.xy, v_input.zw) + dot(21.0 * v_input.zy, sin(3.1 * v_input.zw));\n";
    else if (m_iteration == 4)
        buf << "    highp float field = dot(vec2(v_input[0], v_input[1]), vec2(v_input[0], v_input[1])) + dot(21.0 * "
               "vec2(v_input[0]), sin(3.1 * vec2(v_input[0], v_input[1])));\n";
    else if (m_iteration == 5)
        buf << "    highp float field = dot(vec2(v_input.a, v_input.b), vec2(v_input.a, v_input.b)) + dot(21.0 * "
               "vec2(v_input.a), sin(3.1 * vec2(v_input.a, v_input.b)));\n";
    else
        DE_ASSERT(false);

    buf << "    fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
           "\n"
           "    if (fract(field) > 0.5)\n"
           "        fragColor = vec4(0.0, 0.0, 0.0, 1.0);\n"
           "}";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string SampleQualifierRenderCase::getIterationDescription(int iteration) const
{
    if (iteration == 0)
        return "Test with float varying";
    else if (iteration == 1)
        return "Test with vec2 varying";
    else if (iteration == 2)
        return "Test with vec3 varying";
    else if (iteration == 3)
        return "Test with vec4 varying";
    else if (iteration == 4)
        return "Test with array varying";
    else if (iteration == 5)
        return "Test with struct varying";

    DE_ASSERT(false);
    return "";
}

class InterpolateAtSampleRenderCase : public MultisampleShadeCountRenderCase
{
public:
    enum IndexingMode
    {
        INDEXING_STATIC,
        INDEXING_DYNAMIC,

        INDEXING_LAST
    };
    InterpolateAtSampleRenderCase(Context &context, const char *name, const char *description, int numSamples,
                                  RenderTarget target, IndexingMode mode);
    ~InterpolateAtSampleRenderCase(void);

    void init(void);
    void preDraw(void);

private:
    std::string genVertexSource(int numTargetSamples) const;
    std::string genFragmentSource(int numTargetSamples) const;
    std::string getIterationDescription(int iteration) const;

    const IndexingMode m_indexMode;
};

InterpolateAtSampleRenderCase::InterpolateAtSampleRenderCase(Context &context, const char *name,
                                                             const char *description, int numSamples,
                                                             RenderTarget target, IndexingMode mode)
    : MultisampleShadeCountRenderCase(context, name, description, numSamples, target)
    , m_indexMode(mode)
{
    DE_ASSERT(mode < INDEXING_LAST);

    m_numIterations = 5; // float, vec2, .3, .4, array
}

InterpolateAtSampleRenderCase::~InterpolateAtSampleRenderCase(void)
{
}

void InterpolateAtSampleRenderCase::init(void)
{
    const bool isSingleSampleTarget =
        (m_renderTarget != TARGET_DEFAULT && m_numRequestedSamples == 0) ||
        (m_renderTarget == TARGET_DEFAULT && m_context.getRenderTarget().getNumSamples() <= 1);

    // test purpose and expectations
    if (isSingleSampleTarget)
    {
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Verifying that a interpolateAtSample returns different values for different samples.\n"
                           << "    Render high-frequency function, map result to black/white.\n"
                           << " => Resulting image image should contain both black and white pixels.\n"
                           << tcu::TestLog::EndMessage;
    }
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Verifying that a interpolateAtSample returns different values for different samples.\n"
                           << "    Render high-frequency function, map result to black/white.\n"
                           << " => Resulting image should contain n+1 shades of gray, n = sample count.\n"
                           << tcu::TestLog::EndMessage;
    }

    MultisampleShadeCountRenderCase::init();
}

void InterpolateAtSampleRenderCase::preDraw(void)
{
    if (m_indexMode == INDEXING_DYNAMIC)
    {
        const int32_t range      = m_numTargetSamples;
        const int32_t offset     = 1;
        const glw::Functions &gl = m_context.getRenderContext().getFunctions();
        const int32_t offsetLoc  = gl.getUniformLocation(m_program->getProgram(), "u_offset");
        const int32_t rangeLoc   = gl.getUniformLocation(m_program->getProgram(), "u_range");

        if (offsetLoc == -1)
            throw tcu::TestError("Location of u_offset was -1");
        if (rangeLoc == -1)
            throw tcu::TestError("Location of u_range was -1");

        gl.uniform1i(offsetLoc, 0);
        gl.uniform1i(rangeLoc, m_numTargetSamples);
        GLU_EXPECT_NO_ERROR(gl.getError(), "set uniforms");

        m_testCtx.getLog() << tcu::TestLog::Message << "Set u_offset = " << offset << "\n"
                           << "Set u_range = " << range << tcu::TestLog::EndMessage;
    }
}

std::string InterpolateAtSampleRenderCase::genVertexSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "in highp vec4 a_position;\n";

    if (m_iteration == 0)
        buf << "out highp float v_input;\n";
    else if (m_iteration == 1)
        buf << "out highp vec2 v_input;\n";
    else if (m_iteration == 2)
        buf << "out highp vec3 v_input;\n";
    else if (m_iteration == 3)
        buf << "out highp vec4 v_input;\n";
    else if (m_iteration == 4)
        buf << "out highp vec2[2] v_input;\n";
    else
        DE_ASSERT(false);

    buf << "void main (void)\n"
           "{\n"
           "    gl_Position = a_position;\n";

    if (m_iteration == 0)
        buf << "    v_input = a_position.x + exp(a_position.y) + step(0.9, a_position.x)*step(a_position.y, "
               "-0.9)*8.0;\n";
    else if (m_iteration == 1)
        buf << "    v_input = a_position.xy;\n";
    else if (m_iteration == 2)
        buf << "    v_input = vec3(a_position.xy, a_position.x * 2.0 - a_position.y);\n";
    else if (m_iteration == 3)
        buf << "    v_input = vec4(a_position.xy, a_position.x * 2.0 - a_position.y, a_position.x*a_position.y);\n";
    else if (m_iteration == 4)
        buf << "    v_input[0] = a_position.yx + vec2(0.5, 0.5);\n"
               "    v_input[1] = a_position.xy;\n";
    else
        DE_ASSERT(false);

    buf << "}";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string InterpolateAtSampleRenderCase::genFragmentSource(int numTargetSamples) const
{
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_EXT_SHADER_MULTISAMPLE_INTERPOLATION}";

    if (m_iteration == 0)
        buf << "in highp float v_input;\n";
    else if (m_iteration == 1)
        buf << "in highp vec2 v_input;\n";
    else if (m_iteration == 2)
        buf << "in highp vec3 v_input;\n";
    else if (m_iteration == 3)
        buf << "in highp vec4 v_input;\n";
    else if (m_iteration == 4)
        buf << "in highp vec2[2] v_input;\n";
    else
        DE_ASSERT(false);

    buf << "layout(location = 0) out mediump vec4 fragColor;\n";

    if (m_indexMode == INDEXING_DYNAMIC)
        buf << "uniform highp int u_offset;\n"
               "uniform highp int u_range;\n";

    buf << "void main (void)\n"
           "{\n"
           "    mediump int coverage = 0;\n"
           "\n";

    if (m_indexMode == INDEXING_STATIC)
    {
        for (int ndx = 0; ndx < numTargetSamples; ++ndx)
        {
            if (m_iteration == 0)
                buf << "    highp float sampleInput" << ndx << " = interpolateAtSample(v_input, " << ndx << ");\n";
            else if (m_iteration == 1)
                buf << "    highp vec2 sampleInput" << ndx << " = interpolateAtSample(v_input, " << ndx << ");\n";
            else if (m_iteration == 2)
                buf << "    highp vec3 sampleInput" << ndx << " = interpolateAtSample(v_input, " << ndx << ");\n";
            else if (m_iteration == 3)
                buf << "    highp vec4 sampleInput" << ndx << " = interpolateAtSample(v_input, " << ndx << ");\n";
            else if (m_iteration == 4)
                buf << "    highp vec2 sampleInput" << ndx << " = interpolateAtSample(v_input[1], " << ndx << ");\n";
            else
                DE_ASSERT(false);
        }
        buf << "\n";

        for (int ndx = 0; ndx < numTargetSamples; ++ndx)
        {
            if (m_iteration == 0)
                buf << "    highp float field" << ndx << " = exp(sampleInput" << ndx << ") + sampleInput" << ndx
                    << "*sampleInput" << ndx << ";\n";
            else if (m_iteration == 1 || m_iteration == 4)
                buf << "    highp float field" << ndx << " = dot(sampleInput" << ndx << ", sampleInput" << ndx
                    << ") + dot(21.0 * sampleInput" << ndx << ".xx, sin(3.1 * sampleInput" << ndx << "));\n";
            else if (m_iteration == 2)
                buf << "    highp float field" << ndx << " = dot(sampleInput" << ndx << ".xy, sampleInput" << ndx
                    << ".xy) + dot(21.0 * sampleInput" << ndx << ".zx, sin(3.1 * sampleInput" << ndx << ".zy));\n";
            else if (m_iteration == 3)
                buf << "    highp float field" << ndx << " = dot(sampleInput" << ndx << ".xy, sampleInput" << ndx
                    << ".zw) + dot(21.0 * sampleInput" << ndx << ".zy, sin(3.1 * sampleInput" << ndx << ".zw));\n";
            else
                DE_ASSERT(false);
        }
        buf << "\n";

        for (int ndx = 0; ndx < numTargetSamples; ++ndx)
            buf << "    if (fract(field" << ndx
                << ") <= 0.5)\n"
                   "        ++coverage;\n";
    }
    else if (m_indexMode == INDEXING_DYNAMIC)
    {
        buf << "    for (int ndx = 0; ndx < " << numTargetSamples
            << "; ++ndx)\n"
               "    {\n";

        if (m_iteration == 0)
            buf << "        highp float sampleInput = interpolateAtSample(v_input, (u_offset + ndx) % u_range);\n";
        else if (m_iteration == 1)
            buf << "        highp vec2 sampleInput = interpolateAtSample(v_input, (u_offset + ndx) % u_range);\n";
        else if (m_iteration == 2)
            buf << "        highp vec3 sampleInput = interpolateAtSample(v_input, (u_offset + ndx) % u_range);\n";
        else if (m_iteration == 3)
            buf << "        highp vec4 sampleInput = interpolateAtSample(v_input, (u_offset + ndx) % u_range);\n";
        else if (m_iteration == 4)
            buf << "        highp vec2 sampleInput = interpolateAtSample(v_input[1], (u_offset + ndx) % u_range);\n";
        else
            DE_ASSERT(false);

        if (m_iteration == 0)
            buf << "        highp float field = exp(sampleInput) + sampleInput*sampleInput;\n";
        else if (m_iteration == 1 || m_iteration == 4)
            buf << "        highp float field = dot(sampleInput, sampleInput) + dot(21.0 * sampleInput.xx, sin(3.1 * "
                   "sampleInput));\n";
        else if (m_iteration == 2)
            buf << "        highp float field = dot(sampleInput.xy, sampleInput.xy) + dot(21.0 * sampleInput.zx, "
                   "sin(3.1 * sampleInput.zy));\n";
        else if (m_iteration == 3)
            buf << "        highp float field = dot(sampleInput.xy, sampleInput.zw) + dot(21.0 * sampleInput.zy, "
                   "sin(3.1 * sampleInput.zw));\n";
        else
            DE_ASSERT(false);

        buf << "        if (fract(field) <= 0.5)\n"
               "            ++coverage;\n"
               "    }\n";
    }

    buf << "    fragColor = vec4(vec3(float(coverage) / float(" << numTargetSamples
        << ")), 1.0);\n"
           "}";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string InterpolateAtSampleRenderCase::getIterationDescription(int iteration) const
{
    if (iteration == 0)
        return "Test with float varying";
    else if (iteration < 4)
        return "Test with vec" + de::toString(iteration + 1) + " varying";
    else if (iteration == 4)
        return "Test with array varying";

    DE_ASSERT(false);
    return "";
}

class SingleSampleInterpolateAtSampleCase : public MultisampleShaderRenderUtil::MultisampleRenderCase
{
public:
    enum SampleCase
    {
        SAMPLE_0 = 0,
        SAMPLE_N,

        SAMPLE_LAST
    };

    SingleSampleInterpolateAtSampleCase(Context &context, const char *name, const char *description, int numSamples,
                                        RenderTarget target, SampleCase sampleCase);
    virtual ~SingleSampleInterpolateAtSampleCase(void);

    void init(void);

private:
    enum
    {
        RENDER_SIZE = 32
    };

    std::string genVertexSource(int numTargetSamples) const;
    std::string genFragmentSource(int numTargetSamples) const;
    bool verifyImage(const tcu::Surface &resultImage);

    const SampleCase m_sampleCase;
};

SingleSampleInterpolateAtSampleCase::SingleSampleInterpolateAtSampleCase(Context &context, const char *name,
                                                                         const char *description, int numSamples,
                                                                         RenderTarget target, SampleCase sampleCase)
    : MultisampleShaderRenderUtil::MultisampleRenderCase(context, name, description, numSamples, target, RENDER_SIZE)
    , m_sampleCase(sampleCase)
{
    DE_ASSERT(numSamples == 0);
    DE_ASSERT(sampleCase < SAMPLE_LAST);
}

SingleSampleInterpolateAtSampleCase::~SingleSampleInterpolateAtSampleCase(void)
{
}

void SingleSampleInterpolateAtSampleCase::init(void)
{
    // requirements
    if (!checkSupport(m_context) &&
        !m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
        TCU_THROW(NotSupportedError, "Test requires GL_OES_shader_multisample_interpolation extension");
    if (m_renderTarget == TARGET_DEFAULT && m_context.getRenderTarget().getNumSamples() > 1)
        TCU_THROW(NotSupportedError, "Non-multisample framebuffer required");

    // test purpose and expectations
    m_testCtx.getLog() << tcu::TestLog::Message
                       << "Verifying that using interpolateAtSample with multisample buffers not available returns "
                          "sample evaluated at the center of the pixel.\n"
                       << "    Interpolate varying containing screen space location.\n"
                       << " => fract(screen space location) should be (about) (0.5, 0.5)\n"
                       << tcu::TestLog::EndMessage;

    MultisampleShaderRenderUtil::MultisampleRenderCase::init();
}

std::string SingleSampleInterpolateAtSampleCase::genVertexSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "in highp vec4 a_position;\n"
           "out highp vec2 v_position;\n"
           "void main (void)\n"
           "{\n"
           "    gl_Position = a_position;\n"
           "    v_position = (a_position.xy + vec2(1.0, 1.0)) / 2.0 * vec2("
        << (int)RENDER_SIZE << ".0, " << (int)RENDER_SIZE
        << ".0);\n"
           "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string SingleSampleInterpolateAtSampleCase::genFragmentSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_EXT_SHADER_MULTISAMPLE_INTERPOLATION}"
           "in highp vec2 v_position;\n"
           "layout(location = 0) out mediump vec4 fragColor;\n"
           "void main (void)\n"
           "{\n"
           "    const highp float threshold = 0.15625; // 4 subpixel bits. Assume 3 accurate bits + 0.03125 for other "
           "errors\n"; // 0.03125 = mediump epsilon when value = 32 (RENDER_SIZE)

    if (m_sampleCase == SAMPLE_0)
    {
        buf << "    highp vec2 samplePosition = interpolateAtSample(v_position, 0);\n"
               "    highp vec2 positionInsideAPixel = fract(samplePosition);\n"
               "\n"
               "    if (abs(positionInsideAPixel.x - 0.5) <= threshold && abs(positionInsideAPixel.y - 0.5) <= "
               "threshold)\n"
               "        fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
               "    else\n"
               "        fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
               "}\n";
    }
    else if (m_sampleCase == SAMPLE_N)
    {
        buf << "    bool allOk = true;\n"
               "    for (int sampleNdx = 159; sampleNdx < 163; ++sampleNdx)\n"
               "    {\n"
               "        highp vec2 samplePosition = interpolateAtSample(v_position, sampleNdx);\n"
               "        highp vec2 positionInsideAPixel = fract(samplePosition);\n"
               "        if (abs(positionInsideAPixel.x - 0.5) > threshold || abs(positionInsideAPixel.y - 0.5) > "
               "threshold)\n"
               "            allOk = false;\n"
               "    }\n"
               "\n"
               "    if (allOk)\n"
               "        fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
               "    else\n"
               "        fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
               "}\n";
    }
    else
        DE_ASSERT(false);

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

bool SingleSampleInterpolateAtSampleCase::verifyImage(const tcu::Surface &resultImage)
{
    return verifyGreenImage(resultImage, m_testCtx.getLog());
}

class CentroidRenderCase : public MultisampleShaderRenderUtil::MultisampleRenderCase
{
public:
    CentroidRenderCase(Context &context, const char *name, const char *description, int numSamples, RenderTarget target,
                       int renderSize);
    virtual ~CentroidRenderCase(void);

    void init(void);

private:
    void setupRenderData(void);
};

CentroidRenderCase::CentroidRenderCase(Context &context, const char *name, const char *description, int numSamples,
                                       RenderTarget target, int renderSize)
    : MultisampleShaderRenderUtil::MultisampleRenderCase(context, name, description, numSamples, target, renderSize)
{
}

CentroidRenderCase::~CentroidRenderCase(void)
{
}

void CentroidRenderCase::init(void)
{
    // requirements
    if (!checkSupport(m_context) &&
        !m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
        TCU_THROW(NotSupportedError, "Test requires GL_OES_shader_multisample_interpolation extension");

    MultisampleShaderRenderUtil::MultisampleRenderCase::init();
}

void CentroidRenderCase::setupRenderData(void)
{
    const int numTriangles   = 200;
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    std::vector<tcu::Vec4> data(numTriangles * 3 * 3);

    m_renderMode             = GL_TRIANGLES;
    m_renderCount            = numTriangles * 3;
    m_renderSceneDescription = "triangle fan of narrow triangles";

    m_renderAttribs["a_position"].offset      = 0;
    m_renderAttribs["a_position"].stride      = (int)sizeof(float[4]) * 3;
    m_renderAttribs["a_barycentricsA"].offset = (int)sizeof(float[4]);
    m_renderAttribs["a_barycentricsA"].stride = (int)sizeof(float[4]) * 3;
    m_renderAttribs["a_barycentricsB"].offset = (int)sizeof(float[4]) * 2;
    m_renderAttribs["a_barycentricsB"].stride = (int)sizeof(float[4]) * 3;

    for (int triangleNdx = 0; triangleNdx < numTriangles; ++triangleNdx)
    {
        const float angle     = ((float)triangleNdx) / (float)numTriangles * 2.0f * DE_PI;
        const float nextAngle = ((float)triangleNdx + 1.0f) / (float)numTriangles * 2.0f * DE_PI;

        data[(triangleNdx * 3 + 0) * 3 + 0] = tcu::Vec4(0.2f, -0.3f, 0.0f, 1.0f);
        data[(triangleNdx * 3 + 0) * 3 + 1] = tcu::Vec4(1.0f, 0.0f, 0.0f, 0.0f);
        data[(triangleNdx * 3 + 0) * 3 + 2] = tcu::Vec4(1.0f, 0.0f, 0.0f, 0.0f);

        data[(triangleNdx * 3 + 1) * 3 + 0] = tcu::Vec4(2.0f * deFloatCos(angle), 2.0f * deFloatSin(angle), 0.0f, 1.0f);
        data[(triangleNdx * 3 + 1) * 3 + 1] = tcu::Vec4(0.0f, 1.0f, 0.0f, 0.0f);
        data[(triangleNdx * 3 + 1) * 3 + 2] = tcu::Vec4(0.0f, 1.0f, 0.0f, 0.0f);

        data[(triangleNdx * 3 + 2) * 3 + 0] =
            tcu::Vec4(2.0f * deFloatCos(nextAngle), 2.0f * deFloatSin(nextAngle), 0.0f, 1.0f);
        data[(triangleNdx * 3 + 2) * 3 + 1] = tcu::Vec4(0.0f, 0.0f, 1.0f, 0.0f);
        data[(triangleNdx * 3 + 2) * 3 + 2] = tcu::Vec4(0.0f, 0.0f, 1.0f, 0.0f);
    }

    gl.bindBuffer(GL_ARRAY_BUFFER, m_buffer);
    gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(data.size() * sizeof(tcu::Vec4)), data[0].getPtr(),
                  GL_STATIC_DRAW);
}

class CentroidQualifierAtSampleCase : public CentroidRenderCase
{
public:
    CentroidQualifierAtSampleCase(Context &context, const char *name, const char *description, int numSamples,
                                  RenderTarget target);
    virtual ~CentroidQualifierAtSampleCase(void);

    void init(void);

private:
    enum
    {
        RENDER_SIZE = 128
    };

    std::string genVertexSource(int numTargetSamples) const;
    std::string genFragmentSource(int numTargetSamples) const;
    bool verifyImage(const tcu::Surface &resultImage);
};

CentroidQualifierAtSampleCase::CentroidQualifierAtSampleCase(Context &context, const char *name,
                                                             const char *description, int numSamples,
                                                             RenderTarget target)
    : CentroidRenderCase(context, name, description, numSamples, target, RENDER_SIZE)
{
}

CentroidQualifierAtSampleCase::~CentroidQualifierAtSampleCase(void)
{
}

void CentroidQualifierAtSampleCase::init(void)
{
    // test purpose and expectations
    m_testCtx.getLog() << tcu::TestLog::Message
                       << "Verifying that interpolateAtSample ignores the centroid-qualifier.\n"
                       << "    Draw a fan of narrow triangles (large number of pixels on the edges).\n"
                       << "    Set varyings 'barycentricsA' and 'barycentricsB' to contain barycentric coordinates.\n"
                       << "    Add centroid-qualifier for barycentricsB.\n"
                       << " => interpolateAtSample(barycentricsB, N) ~= interpolateAtSample(barycentricsA, N)\n"
                       << tcu::TestLog::EndMessage;

    CentroidRenderCase::init();
}

std::string CentroidQualifierAtSampleCase::genVertexSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "in highp vec4 a_position;\n"
           "in highp vec4 a_barycentricsA;\n"
           "in highp vec4 a_barycentricsB;\n"
           "out highp vec3 v_barycentricsA;\n"
           "centroid out highp vec3 v_barycentricsB;\n"
           "void main (void)\n"
           "{\n"
           "    gl_Position = a_position;\n"
           "    v_barycentricsA = a_barycentricsA.xyz;\n"
           "    v_barycentricsB = a_barycentricsB.xyz;\n"
           "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string CentroidQualifierAtSampleCase::genFragmentSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_EXT_SHADER_MULTISAMPLE_INTERPOLATION}"
           "in highp vec3 v_barycentricsA;\n"
           "centroid in highp vec3 v_barycentricsB;\n"
           "layout(location = 0) out mediump vec4 fragColor;\n"
           "void main (void)\n"
           "{\n"
           "    const highp float threshold = 0.0005;\n"
           "    bool allOk = true;\n"
           "\n"
           "    for (int sampleNdx = 0; sampleNdx < "
        << numTargetSamples
        << "; ++sampleNdx)\n"
           "    {\n"
           "        highp vec3 sampleA = interpolateAtSample(v_barycentricsA, sampleNdx);\n"
           "        highp vec3 sampleB = interpolateAtSample(v_barycentricsB, sampleNdx);\n"
           "        bool valuesEqual = all(lessThan(abs(sampleA - sampleB), vec3(threshold)));\n"
           "        if (!valuesEqual)\n"
           "            allOk = false;\n"
           "    }\n"
           "\n"
           "    if (allOk)\n"
           "        fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
           "    else\n"
           "        fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
           "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

bool CentroidQualifierAtSampleCase::verifyImage(const tcu::Surface &resultImage)
{
    return verifyGreenImage(resultImage, m_testCtx.getLog());
}

class InterpolateAtSampleIDCase : public MultisampleShaderRenderUtil::MultisampleRenderCase
{
public:
    InterpolateAtSampleIDCase(Context &context, const char *name, const char *description, int numSamples,
                              RenderTarget target);
    virtual ~InterpolateAtSampleIDCase(void);

    void init(void);

private:
    enum
    {
        RENDER_SIZE = 32
    };

    std::string genVertexSource(int numTargetSamples) const;
    std::string genFragmentSource(int numTargetSamples) const;
    bool verifyImage(const tcu::Surface &resultImage);
};

InterpolateAtSampleIDCase::InterpolateAtSampleIDCase(Context &context, const char *name, const char *description,
                                                     int numSamples, RenderTarget target)
    : MultisampleShaderRenderUtil::MultisampleRenderCase(context, name, description, numSamples, target, RENDER_SIZE)
{
}

InterpolateAtSampleIDCase::~InterpolateAtSampleIDCase(void)
{
}

void InterpolateAtSampleIDCase::init(void)
{
    // requirements
    if (!checkSupport(m_context) &&
        !m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
        TCU_THROW(NotSupportedError, "Test requires GL_OES_shader_multisample_interpolation extension");
    if (!checkSupport(m_context) && !m_context.getContextInfo().isExtensionSupported("GL_OES_sample_variables"))
        TCU_THROW(NotSupportedError, "Test requires GL_OES_sample_variables extension");

    // test purpose and expectations
    m_testCtx.getLog()
        << tcu::TestLog::Message
        << "Verifying that interpolateAtSample with the sample set to the current sampleID returns consistent values.\n"
        << "    Interpolate varying containing screen space location.\n"
        << " => interpolateAtSample(varying, sampleID) = varying" << tcu::TestLog::EndMessage;

    MultisampleShaderRenderUtil::MultisampleRenderCase::init();
}

std::string InterpolateAtSampleIDCase::genVertexSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_EXT_SHADER_MULTISAMPLE_INTERPOLATION}"
           "in highp vec4 a_position;\n"
           "sample out highp vec2 v_screenPosition;\n"
           "void main (void)\n"
           "{\n"
           "    gl_Position = a_position;\n"
           "    v_screenPosition = (a_position.xy + vec2(1.0, 1.0)) / 2.0 * vec2("
        << (int)RENDER_SIZE << ".0, " << (int)RENDER_SIZE
        << ".0);\n"
           "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string InterpolateAtSampleIDCase::genFragmentSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_EXT_SAMPLE_VARIABLES}"
           "${GLSL_EXT_SHADER_MULTISAMPLE_INTERPOLATION}"
           "sample in highp vec2 v_screenPosition;\n"
           "layout(location = 0) out mediump vec4 fragColor;\n"
           "void main (void)\n"
           "{\n"
           "    const highp float threshold = 0.15625; // 4 subpixel bits. Assume 3 accurate bits + 0.03125 for other "
           "errors\n" // 0.03125 = mediump epsilon when value = 32 (RENDER_SIZE)
           "\n"
           "    highp vec2 offsetValue = interpolateAtSample(v_screenPosition, gl_SampleID);\n"
           "    highp vec2 refValue = v_screenPosition;\n"
           "\n"
           "    bool valuesEqual = all(lessThan(abs(offsetValue - refValue), vec2(threshold)));\n"
           "    if (valuesEqual)\n"
           "        fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
           "    else\n"
           "        fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
           "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

bool InterpolateAtSampleIDCase::verifyImage(const tcu::Surface &resultImage)
{
    return verifyGreenImage(resultImage, m_testCtx.getLog());
}

class InterpolateAtCentroidCase : public CentroidRenderCase
{
public:
    enum TestType
    {
        TEST_CONSISTENCY = 0,
        TEST_ARRAY_ELEMENT,

        TEST_LAST
    };

    InterpolateAtCentroidCase(Context &context, const char *name, const char *description, int numSamples,
                              RenderTarget target, TestType type);
    virtual ~InterpolateAtCentroidCase(void);

    void init(void);

private:
    enum
    {
        RENDER_SIZE = 128
    };

    std::string genVertexSource(int numTargetSamples) const;
    std::string genFragmentSource(int numTargetSamples) const;
    bool verifyImage(const tcu::Surface &resultImage);

    const TestType m_type;
};

InterpolateAtCentroidCase::InterpolateAtCentroidCase(Context &context, const char *name, const char *description,
                                                     int numSamples, RenderTarget target, TestType type)
    : CentroidRenderCase(context, name, description, numSamples, target, RENDER_SIZE)
    , m_type(type)
{
}

InterpolateAtCentroidCase::~InterpolateAtCentroidCase(void)
{
}

void InterpolateAtCentroidCase::init(void)
{
    // test purpose and expectations
    if (m_type == TEST_CONSISTENCY)
    {
        m_testCtx.getLog()
            << tcu::TestLog::Message
            << "Verifying that interpolateAtCentroid does not return different values than a "
               "corresponding centroid-qualified varying.\n"
            << "    Draw a fan of narrow triangles (large number of pixels on the edges).\n"
            << "    Set varyings 'barycentricsA' and 'barycentricsB' to contain barycentric coordinates.\n"
            << "    Add centroid-qualifier for barycentricsB.\n"
            << " => interpolateAtCentroid(barycentricsA) ~= barycentricsB\n"
            << tcu::TestLog::EndMessage;
    }
    else if (m_type == TEST_ARRAY_ELEMENT)
    {
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Testing interpolateAtCentroid with element of array as an argument."
                           << tcu::TestLog::EndMessage;
    }
    else
        DE_ASSERT(false);

    CentroidRenderCase::init();
}

std::string InterpolateAtCentroidCase::genVertexSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;

    if (m_type == TEST_CONSISTENCY)
        buf << "${GLSL_VERSION_DECL}\n"
               "in highp vec4 a_position;\n"
               "in highp vec4 a_barycentricsA;\n"
               "in highp vec4 a_barycentricsB;\n"
               "out highp vec3 v_barycentricsA;\n"
               "centroid out highp vec3 v_barycentricsB;\n"
               "void main (void)\n"
               "{\n"
               "    gl_Position = a_position;\n"
               "    v_barycentricsA = a_barycentricsA.xyz;\n"
               "    v_barycentricsB = a_barycentricsB.xyz;\n"
               "}\n";
    else if (m_type == TEST_ARRAY_ELEMENT)
        buf << "${GLSL_VERSION_DECL}\n"
               "in highp vec4 a_position;\n"
               "in highp vec4 a_barycentricsA;\n"
               "in highp vec4 a_barycentricsB;\n"
               "out highp vec3[2] v_barycentrics;\n"
               "void main (void)\n"
               "{\n"
               "    gl_Position = a_position;\n"
               "    v_barycentrics[0] = a_barycentricsA.xyz;\n"
               "    v_barycentrics[1] = a_barycentricsB.xyz;\n"
               "}\n";
    else
        DE_ASSERT(false);

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string InterpolateAtCentroidCase::genFragmentSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;

    if (m_type == TEST_CONSISTENCY)
        buf << "${GLSL_VERSION_DECL}\n"
               "${GLSL_EXT_SHADER_MULTISAMPLE_INTERPOLATION}"
               "in highp vec3 v_barycentricsA;\n"
               "centroid in highp vec3 v_barycentricsB;\n"
               "layout(location = 0) out highp vec4 fragColor;\n"
               "void main (void)\n"
               "{\n"
               "    const highp float threshold = 0.0005;\n"
               "\n"
               "    highp vec3 centroidASampled = interpolateAtCentroid(v_barycentricsA);\n"
               "    bool valuesEqual = all(lessThan(abs(centroidASampled - v_barycentricsB), vec3(threshold)));\n"
               "    bool centroidAIsInvalid = any(greaterThan(centroidASampled, vec3(1.0))) ||\n"
               "                              any(lessThan(centroidASampled, vec3(0.0)));\n"
               "    bool centroidBIsInvalid = any(greaterThan(v_barycentricsB, vec3(1.0))) ||\n"
               "                              any(lessThan(v_barycentricsB, vec3(0.0)));\n"
               "\n"
               "    if (valuesEqual && !centroidAIsInvalid && !centroidBIsInvalid)\n"
               "        fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
               "    else if (centroidAIsInvalid || centroidBIsInvalid)\n"
               "        fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
               "    else\n"
               "        fragColor = vec4(1.0, 1.0, 0.0, 1.0);\n"
               "}\n";
    else if (m_type == TEST_ARRAY_ELEMENT)
        buf << "${GLSL_VERSION_DECL}\n"
               "${GLSL_EXT_SHADER_MULTISAMPLE_INTERPOLATION}"
               "in highp vec3[2] v_barycentrics;\n"
               "layout(location = 0) out mediump vec4 fragColor;\n"
               "void main (void)\n"
               "{\n"
               "    const highp float threshold = 0.0005;\n"
               "\n"
               "    highp vec3 centroidInterpolated = interpolateAtCentroid(v_barycentrics[1]);\n"
               "    bool centroidIsInvalid = any(greaterThan(centroidInterpolated, vec3(1.0))) ||\n"
               "                             any(lessThan(centroidInterpolated, vec3(0.0)));\n"
               "\n"
               "    if (!centroidIsInvalid)\n"
               "        fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
               "    else\n"
               "        fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
               "}\n";
    else
        DE_ASSERT(false);

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

bool InterpolateAtCentroidCase::verifyImage(const tcu::Surface &resultImage)
{
    return verifyGreenImage(resultImage, m_testCtx.getLog());
}

class InterpolateAtOffsetCase : public MultisampleShaderRenderUtil::MultisampleRenderCase
{
public:
    enum TestType
    {
        TEST_QUALIFIER_NONE = 0,
        TEST_QUALIFIER_CENTROID,
        TEST_QUALIFIER_SAMPLE,
        TEST_ARRAY_ELEMENT,

        TEST_LAST
    };
    InterpolateAtOffsetCase(Context &context, const char *name, const char *description, int numSamples,
                            RenderTarget target, TestType testType);
    virtual ~InterpolateAtOffsetCase(void);

    void init(void);

private:
    enum
    {
        RENDER_SIZE = 32
    };

    std::string genVertexSource(int numTargetSamples) const;
    std::string genFragmentSource(int numTargetSamples) const;
    bool verifyImage(const tcu::Surface &resultImage);

    const TestType m_testType;
};

InterpolateAtOffsetCase::InterpolateAtOffsetCase(Context &context, const char *name, const char *description,
                                                 int numSamples, RenderTarget target, TestType testType)
    : MultisampleShaderRenderUtil::MultisampleRenderCase(context, name, description, numSamples, target, RENDER_SIZE)
    , m_testType(testType)
{
    DE_ASSERT(testType < TEST_LAST);
}

InterpolateAtOffsetCase::~InterpolateAtOffsetCase(void)
{
}

void InterpolateAtOffsetCase::init(void)
{
    // requirements
    if (!checkSupport(m_context) &&
        !m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
        TCU_THROW(NotSupportedError, "Test requires GL_OES_shader_multisample_interpolation extension");

    // test purpose and expectations
    m_testCtx.getLog()
        << tcu::TestLog::Message << "Verifying that interpolateAtOffset returns correct values.\n"
        << "    Interpolate varying containing screen space location.\n"
        << " => interpolateAtOffset(varying, offset) should be \"varying value at the pixel center\" + offset"
        << tcu::TestLog::EndMessage;

    MultisampleShaderRenderUtil::MultisampleRenderCase::init();
}

std::string InterpolateAtOffsetCase::genVertexSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;
    buf << "${GLSL_VERSION_DECL}\n"
        << "${GLSL_EXT_SHADER_MULTISAMPLE_INTERPOLATION}"
        << "in highp vec4 a_position;\n";

    if (m_testType == TEST_QUALIFIER_NONE || m_testType == TEST_QUALIFIER_CENTROID ||
        m_testType == TEST_QUALIFIER_SAMPLE)
    {
        const char *const qualifier = (m_testType == TEST_QUALIFIER_CENTROID) ? ("centroid ") :
                                      (m_testType == TEST_QUALIFIER_SAMPLE)   ? ("sample ") :
                                                                                ("");
        buf << qualifier << "out highp vec2 v_screenPosition;\n" << qualifier << "out highp vec2 v_offset;\n";
    }
    else if (m_testType == TEST_ARRAY_ELEMENT)
    {
        buf << "out highp vec2[2] v_screenPosition;\n"
            << "out highp vec2 v_offset;\n";
    }
    else
        DE_ASSERT(false);

    buf << "void main (void)\n"
        << "{\n"
        << "    gl_Position = a_position;\n";

    if (m_testType != TEST_ARRAY_ELEMENT)
        buf << "    v_screenPosition = (a_position.xy + vec2(1.0, 1.0)) / 2.0 * vec2(" << (int)RENDER_SIZE << ".0, "
            << (int)RENDER_SIZE << ".0);\n";
    else
        buf << "    v_screenPosition[0] = a_position.xy; // not used\n"
               "    v_screenPosition[1] = (a_position.xy + vec2(1.0, 1.0)) / 2.0 * vec2("
            << (int)RENDER_SIZE << ".0, " << (int)RENDER_SIZE << ".0);\n";

    buf << "    v_offset = a_position.xy * 0.5f;\n"
        << "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string InterpolateAtOffsetCase::genFragmentSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    const char *const arrayIndexing = (m_testType == TEST_ARRAY_ELEMENT) ? ("[1]") : ("");
    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_EXT_SHADER_MULTISAMPLE_INTERPOLATION}";

    if (m_testType == TEST_QUALIFIER_NONE || m_testType == TEST_QUALIFIER_CENTROID ||
        m_testType == TEST_QUALIFIER_SAMPLE)
    {
        const char *const qualifier = (m_testType == TEST_QUALIFIER_CENTROID) ? ("centroid ") :
                                      (m_testType == TEST_QUALIFIER_SAMPLE)   ? ("sample ") :
                                                                                ("");
        buf << qualifier << "in highp vec2 v_screenPosition;\n" << qualifier << "in highp vec2 v_offset;\n";
    }
    else if (m_testType == TEST_ARRAY_ELEMENT)
    {
        buf << "in highp vec2[2] v_screenPosition;\n"
            << "in highp vec2 v_offset;\n";
    }
    else
        DE_ASSERT(false);

    buf << "layout(location = 0) out mediump vec4 fragColor;\n"
           "void main (void)\n"
           "{\n"
           "    const highp float threshold = 0.15625; // 4 subpixel bits. Assume 3 accurate bits + 0.03125 for other "
           "errors\n" // 0.03125 = mediump epsilon when value = 32 (RENDER_SIZE)
           "\n"
           "    highp vec2 pixelCenter = floor(v_screenPosition"
        << arrayIndexing
        << ") + vec2(0.5, 0.5);\n"
           "    highp vec2 offsetValue = interpolateAtOffset(v_screenPosition"
        << arrayIndexing
        << ", v_offset);\n"
           "    highp vec2 refValue = pixelCenter + v_offset;\n"
           "\n"
           "    bool valuesEqual = all(lessThan(abs(offsetValue - refValue), vec2(threshold)));\n"
           "    if (valuesEqual)\n"
           "        fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
           "    else\n"
           "        fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
           "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

bool InterpolateAtOffsetCase::verifyImage(const tcu::Surface &resultImage)
{
    return verifyGreenImage(resultImage, m_testCtx.getLog());
}

class InterpolateAtSamplePositionCase : public MultisampleShaderRenderUtil::MultisampleRenderCase
{
public:
    InterpolateAtSamplePositionCase(Context &context, const char *name, const char *description, int numSamples,
                                    RenderTarget target);
    virtual ~InterpolateAtSamplePositionCase(void);

    void init(void);

private:
    enum
    {
        RENDER_SIZE = 32
    };

    std::string genVertexSource(int numTargetSamples) const;
    std::string genFragmentSource(int numTargetSamples) const;
    bool verifyImage(const tcu::Surface &resultImage);
};

InterpolateAtSamplePositionCase::InterpolateAtSamplePositionCase(Context &context, const char *name,
                                                                 const char *description, int numSamples,
                                                                 RenderTarget target)
    : MultisampleShaderRenderUtil::MultisampleRenderCase(context, name, description, numSamples, target, RENDER_SIZE)
{
}

InterpolateAtSamplePositionCase::~InterpolateAtSamplePositionCase(void)
{
}

void InterpolateAtSamplePositionCase::init(void)
{
    // requirements
    if (!checkSupport(m_context) &&
        !m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
        TCU_THROW(NotSupportedError, "Test requires GL_OES_shader_multisample_interpolation extension");
    if (!checkSupport(m_context) && !m_context.getContextInfo().isExtensionSupported("GL_OES_sample_variables"))
        TCU_THROW(NotSupportedError, "Test requires GL_OES_sample_variables extension");

    // test purpose and expectations
    m_testCtx.getLog()
        << tcu::TestLog::Message
        << "Verifying that interpolateAtOffset with the offset of current sample position returns consistent values.\n"
        << "    Interpolate varying containing screen space location.\n"
        << " => interpolateAtOffset(varying, currentOffset) = varying" << tcu::TestLog::EndMessage;

    MultisampleShaderRenderUtil::MultisampleRenderCase::init();
}

std::string InterpolateAtSamplePositionCase::genVertexSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_EXT_SHADER_MULTISAMPLE_INTERPOLATION}"
           "in highp vec4 a_position;\n"
           "sample out highp vec2 v_screenPosition;\n"
           "void main (void)\n"
           "{\n"
           "    gl_Position = a_position;\n"
           "    v_screenPosition = (a_position.xy + vec2(1.0, 1.0)) / 2.0 * vec2("
        << (int)RENDER_SIZE << ".0, " << (int)RENDER_SIZE
        << ".0);\n"
           "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

std::string InterpolateAtSamplePositionCase::genFragmentSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;

    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_EXT_SAMPLE_VARIABLES}"
           "${GLSL_EXT_SHADER_MULTISAMPLE_INTERPOLATION}"
           "sample in highp vec2 v_screenPosition;\n"
           "layout(location = 0) out mediump vec4 fragColor;\n"
           "void main (void)\n"
           "{\n"
           "    const highp float threshold = 0.15625; // 4 subpixel bits. Assume 3 accurate bits + 0.03125 for other "
           "errors\n" // 0.03125 = mediump epsilon when value = 32 (RENDER_SIZE)
           "\n"
           "    highp vec2 offset = gl_SamplePosition - vec2(0.5, 0.5);\n"
           "    highp vec2 offsetValue = interpolateAtOffset(v_screenPosition, offset);\n"
           "    highp vec2 refValue = v_screenPosition;\n"
           "\n"
           "    bool valuesEqual = all(lessThan(abs(offsetValue - refValue), vec2(threshold)));\n"
           "    if (valuesEqual)\n"
           "        fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
           "    else\n"
           "        fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
           "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

bool InterpolateAtSamplePositionCase::verifyImage(const tcu::Surface &resultImage)
{
    return verifyGreenImage(resultImage, m_testCtx.getLog());
}

class NegativeCompileInterpolationCase : public TestCase
{
public:
    enum CaseType
    {
        CASE_VEC4_IDENTITY_SWIZZLE = 0,
        CASE_VEC4_CROP_SWIZZLE,
        CASE_VEC4_MIXED_SWIZZLE,
        CASE_INTERPOLATE_IVEC4,
        CASE_INTERPOLATE_UVEC4,
        CASE_INTERPOLATE_ARRAY,
        CASE_INTERPOLATE_STRUCT,
        CASE_INTERPOLATE_STRUCT_MEMBER,
        CASE_INTERPOLATE_LOCAL,
        CASE_INTERPOLATE_GLOBAL,
        CASE_INTERPOLATE_CONSTANT,

        CASE_LAST
    };
    enum InterpolatorType
    {
        INTERPOLATE_AT_SAMPLE = 0,
        INTERPOLATE_AT_CENTROID,
        INTERPOLATE_AT_OFFSET,

        INTERPOLATE_LAST
    };

    NegativeCompileInterpolationCase(Context &context, const char *name, const char *description, CaseType caseType,
                                     InterpolatorType interpolator);

private:
    void init(void);
    IterateResult iterate(void);

    std::string genShaderSource(void) const;

    const CaseType m_caseType;
    const InterpolatorType m_interpolation;
};

NegativeCompileInterpolationCase::NegativeCompileInterpolationCase(Context &context, const char *name,
                                                                   const char *description, CaseType caseType,
                                                                   InterpolatorType interpolator)
    : TestCase(context, name, description)
    , m_caseType(caseType)
    , m_interpolation(interpolator)
{
    DE_ASSERT(m_caseType < CASE_LAST);
    DE_ASSERT(m_interpolation < INTERPOLATE_LAST);
}

void NegativeCompileInterpolationCase::init(void)
{
    if (!checkSupport(m_context) &&
        !m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation"))
        TCU_THROW(NotSupportedError, "Test requires GL_OES_shader_multisample_interpolation extension");

    if (!glu::isContextTypeES(m_context.getRenderContext().getType()))
    {
        if (m_caseType == CASE_VEC4_IDENTITY_SWIZZLE || m_caseType == CASE_VEC4_CROP_SWIZZLE ||
            m_caseType == CASE_VEC4_MIXED_SWIZZLE || m_caseType == CASE_INTERPOLATE_IVEC4 ||
            m_caseType == CASE_INTERPOLATE_UVEC4 || m_caseType == CASE_INTERPOLATE_STRUCT_MEMBER)
            TCU_THROW(NotSupportedError, "Test requires a GLES context");
    }

    m_testCtx.getLog() << tcu::TestLog::Message << "Trying to compile illegal shader, expecting compile to fail."
                       << tcu::TestLog::EndMessage;
}

NegativeCompileInterpolationCase::IterateResult NegativeCompileInterpolationCase::iterate(void)
{
    const std::string source = genShaderSource();
    glu::Shader shader(m_context.getRenderContext(), glu::SHADERTYPE_FRAGMENT);
    const char *const sourceStrPtr = source.c_str();

    m_testCtx.getLog() << tcu::TestLog::Message << "Fragment shader source:" << tcu::TestLog::EndMessage
                       << tcu::TestLog::KernelSource(source);

    shader.setSources(1, &sourceStrPtr, DE_NULL);
    shader.compile();

    m_testCtx.getLog() << tcu::TestLog::Message << "Info log:" << tcu::TestLog::EndMessage
                       << tcu::TestLog::KernelSource(shader.getInfoLog());

    if (shader.getCompileStatus())
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "ERROR: Illegal shader compiled successfully."
                           << tcu::TestLog::EndMessage;
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected compile status");
    }
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Compile failed as expected." << tcu::TestLog::EndMessage;
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    }
    return STOP;
}

std::string NegativeCompileInterpolationCase::genShaderSource(void) const
{
    std::ostringstream buf;
    std::string interpolation;
    const char *interpolationTemplate;
    const char *description;
    const char *globalDeclarations  = "";
    const char *localDeclarations   = "";
    const char *interpolationTarget = "";
    const char *postSelector        = "";

    switch (m_caseType)
    {
    case CASE_VEC4_IDENTITY_SWIZZLE:
        globalDeclarations  = "in highp vec4 v_var;\n";
        interpolationTarget = "v_var.xyzw";
        description         = "component selection is illegal";
        break;

    case CASE_VEC4_CROP_SWIZZLE:
        globalDeclarations  = "in highp vec4 v_var;\n";
        interpolationTarget = "v_var.xy";
        postSelector        = ".x";
        description         = "component selection is illegal";
        break;

    case CASE_VEC4_MIXED_SWIZZLE:
        globalDeclarations  = "in highp vec4 v_var;\n";
        interpolationTarget = "v_var.yzxw";
        description         = "component selection is illegal";
        break;

    case CASE_INTERPOLATE_IVEC4:
        globalDeclarations  = "flat in highp ivec4 v_var;\n";
        interpolationTarget = "v_var";
        description         = "no overload for ivec";
        break;

    case CASE_INTERPOLATE_UVEC4:
        globalDeclarations  = "flat in highp uvec4 v_var;\n";
        interpolationTarget = "v_var";
        description         = "no overload for uvec";
        break;

    case CASE_INTERPOLATE_ARRAY:
        globalDeclarations  = "in highp float v_var[2];\n";
        interpolationTarget = "v_var";
        postSelector        = "[1]";
        description         = "no overload for arrays";
        break;

    case CASE_INTERPOLATE_STRUCT:
    case CASE_INTERPOLATE_STRUCT_MEMBER:
        globalDeclarations = "struct S\n"
                             "{\n"
                             "    highp float a;\n"
                             "    highp float b;\n"
                             "};\n"
                             "in S v_var;\n";

        interpolationTarget = (m_caseType == CASE_INTERPOLATE_STRUCT) ? ("v_var") : ("v_var.a");
        postSelector        = (m_caseType == CASE_INTERPOLATE_STRUCT) ? (".a") : ("");
        description         = (m_caseType == CASE_INTERPOLATE_STRUCT) ?
                                  ("no overload for this type") :
                                  ("<interpolant> is not an input variable (just a member of)");
        break;

    case CASE_INTERPOLATE_LOCAL:
        localDeclarations   = "    highp vec4 local_var = gl_FragCoord;\n";
        interpolationTarget = "local_var";
        description         = "<interpolant> is not an input variable";
        break;

    case CASE_INTERPOLATE_GLOBAL:
        globalDeclarations  = "highp vec4 global_var;\n";
        localDeclarations   = "    global_var = gl_FragCoord;\n";
        interpolationTarget = "global_var";
        description         = "<interpolant> is not an input variable";
        break;

    case CASE_INTERPOLATE_CONSTANT:
        globalDeclarations  = "const highp vec4 const_var = vec4(0.2);\n";
        interpolationTarget = "const_var";
        description         = "<interpolant> is not an input variable";
        break;

    default:
        DE_ASSERT(false);
        return "";
    }

    switch (m_interpolation)
    {
    case INTERPOLATE_AT_SAMPLE:
        interpolationTemplate = "interpolateAtSample(${TARGET}, 0)${POST_SELECTOR}";
        break;

    case INTERPOLATE_AT_CENTROID:
        interpolationTemplate = "interpolateAtCentroid(${TARGET})${POST_SELECTOR}";
        break;

    case INTERPOLATE_AT_OFFSET:
        interpolationTemplate = "interpolateAtOffset(${TARGET}, vec2(0.2, 0.2))${POST_SELECTOR}";
        break;

    default:
        DE_ASSERT(false);
        return "";
    }

    {
        std::map<std::string, std::string> args;
        args["TARGET"]        = interpolationTarget;
        args["POST_SELECTOR"] = postSelector;

        interpolation = tcu::StringTemplate(interpolationTemplate).specialize(args);
    }

    buf << glu::getGLSLVersionDeclaration(glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType()))
        << "\n"
        << "${GLSL_EXT_SHADER_MULTISAMPLE_INTERPOLATION}" << globalDeclarations
        << "layout(location = 0) out mediump vec4 fragColor;\n"
           "void main (void)\n"
           "{\n"
        << localDeclarations << "    fragColor = vec4(" << interpolation << "); // " << description
        << "\n"
           "}\n";

    return specializeShader(buf.str(), m_context.getRenderContext().getType());
}

} // namespace

ShaderMultisampleInterpolationTests::ShaderMultisampleInterpolationTests(Context &context)
    : TestCaseGroup(context, "multisample_interpolation", "Test multisample interpolation")
{
}

ShaderMultisampleInterpolationTests::~ShaderMultisampleInterpolationTests(void)
{
}

void ShaderMultisampleInterpolationTests::init(void)
{
    using namespace MultisampleShaderRenderUtil;

    static const struct RenderTarget
    {
        const char *name;
        const char *desc;
        int numSamples;
        MultisampleRenderCase::RenderTarget target;
    } targets[] = {
        {"default_framebuffer", "Test with default framebuffer", 0, MultisampleRenderCase::TARGET_DEFAULT},
        {"singlesample_texture", "Test with singlesample texture", 0, MultisampleRenderCase::TARGET_TEXTURE},
        {"multisample_texture_1", "Test with multisample texture", 1, MultisampleRenderCase::TARGET_TEXTURE},
        {"multisample_texture_2", "Test with multisample texture", 2, MultisampleRenderCase::TARGET_TEXTURE},
        {"multisample_texture_4", "Test with multisample texture", 4, MultisampleRenderCase::TARGET_TEXTURE},
        {"multisample_texture_8", "Test with multisample texture", 8, MultisampleRenderCase::TARGET_TEXTURE},
        {"multisample_texture_16", "Test with multisample texture", 16, MultisampleRenderCase::TARGET_TEXTURE},
        {"singlesample_rbo", "Test with singlesample rbo", 0, MultisampleRenderCase::TARGET_RENDERBUFFER},
        {"multisample_rbo_1", "Test with multisample rbo", 1, MultisampleRenderCase::TARGET_RENDERBUFFER},
        {"multisample_rbo_2", "Test with multisample rbo", 2, MultisampleRenderCase::TARGET_RENDERBUFFER},
        {"multisample_rbo_4", "Test with multisample rbo", 4, MultisampleRenderCase::TARGET_RENDERBUFFER},
        {"multisample_rbo_8", "Test with multisample rbo", 8, MultisampleRenderCase::TARGET_RENDERBUFFER},
        {"multisample_rbo_16", "Test with multisample rbo", 16, MultisampleRenderCase::TARGET_RENDERBUFFER},
    };

    static const struct
    {
        const char *name;
        const char *description;
        NegativeCompileInterpolationCase::CaseType caseType;
    } negativeCompileCases[] = {
        {"vec4_identity_swizzle", "use identity swizzle", NegativeCompileInterpolationCase::CASE_VEC4_IDENTITY_SWIZZLE},
        {"vec4_crop_swizzle", "use cropped identity swizzle", NegativeCompileInterpolationCase::CASE_VEC4_CROP_SWIZZLE},
        {"vec4_mixed_swizzle", "use swizzle", NegativeCompileInterpolationCase::CASE_VEC4_MIXED_SWIZZLE},
        {"interpolate_ivec4", "interpolate integer variable", NegativeCompileInterpolationCase::CASE_INTERPOLATE_IVEC4},
        {"interpolate_uvec4", "interpolate integer variable", NegativeCompileInterpolationCase::CASE_INTERPOLATE_UVEC4},
        {"interpolate_array", "interpolate whole array", NegativeCompileInterpolationCase::CASE_INTERPOLATE_ARRAY},
        {"interpolate_struct", "interpolate whole struct", NegativeCompileInterpolationCase::CASE_INTERPOLATE_STRUCT},
        {"interpolate_struct_member", "interpolate struct member",
         NegativeCompileInterpolationCase::CASE_INTERPOLATE_STRUCT_MEMBER},
        {"interpolate_local", "interpolate local variable", NegativeCompileInterpolationCase::CASE_INTERPOLATE_LOCAL},
        {"interpolate_global", "interpolate global variable",
         NegativeCompileInterpolationCase::CASE_INTERPOLATE_GLOBAL},
        {"interpolate_constant", "interpolate constant variable",
         NegativeCompileInterpolationCase::CASE_INTERPOLATE_CONSTANT},
    };

    // .sample_qualifier
    {
        tcu::TestCaseGroup *const sampleQualifierGroup =
            new tcu::TestCaseGroup(m_testCtx, "sample_qualifier", "Test sample qualifier");
        addChild(sampleQualifierGroup);

        for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
            sampleQualifierGroup->addChild(
                new SampleQualifierRenderCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                              targets[targetNdx].numSamples, targets[targetNdx].target));
    }

    // .interpolate_at_sample
    {
        tcu::TestCaseGroup *const interpolateAtSampleGroup =
            new tcu::TestCaseGroup(m_testCtx, "interpolate_at_sample", "Test interpolateAtSample");
        addChild(interpolateAtSampleGroup);

        // .static_sample_number
        {
            tcu::TestCaseGroup *const group =
                new tcu::TestCaseGroup(m_testCtx, "static_sample_number", "Test interpolateAtSample sample number");
            interpolateAtSampleGroup->addChild(group);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
                group->addChild(new InterpolateAtSampleRenderCase(
                    m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples,
                    targets[targetNdx].target, InterpolateAtSampleRenderCase::INDEXING_STATIC));
        }

        // .dynamic_sample_number
        {
            tcu::TestCaseGroup *const group =
                new tcu::TestCaseGroup(m_testCtx, "dynamic_sample_number", "Test interpolateAtSample sample number");
            interpolateAtSampleGroup->addChild(group);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
                group->addChild(new InterpolateAtSampleRenderCase(
                    m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples,
                    targets[targetNdx].target, InterpolateAtSampleRenderCase::INDEXING_DYNAMIC));
        }

        // .non_multisample_buffer
        {
            tcu::TestCaseGroup *const group = new tcu::TestCaseGroup(
                m_testCtx, "non_multisample_buffer", "Test interpolateAtSample with non-multisample buffers");
            interpolateAtSampleGroup->addChild(group);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
                if (targets[targetNdx].numSamples == 0)
                    group->addChild(new SingleSampleInterpolateAtSampleCase(
                        m_context, std::string("sample_0_").append(targets[targetNdx].name).c_str(),
                        targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target,
                        SingleSampleInterpolateAtSampleCase::SAMPLE_0));

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
                if (targets[targetNdx].numSamples == 0)
                    group->addChild(new SingleSampleInterpolateAtSampleCase(
                        m_context, std::string("sample_n_").append(targets[targetNdx].name).c_str(),
                        targets[targetNdx].desc, targets[targetNdx].numSamples, targets[targetNdx].target,
                        SingleSampleInterpolateAtSampleCase::SAMPLE_N));
        }

        // .centroid_qualifier
        {
            tcu::TestCaseGroup *const group = new tcu::TestCaseGroup(
                m_testCtx, "centroid_qualified", "Test interpolateAtSample with centroid qualified varying");
            interpolateAtSampleGroup->addChild(group);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
                group->addChild(
                    new CentroidQualifierAtSampleCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                                      targets[targetNdx].numSamples, targets[targetNdx].target));
        }

        // .at_sample_id
        {
            tcu::TestCaseGroup *const group =
                new tcu::TestCaseGroup(m_testCtx, "at_sample_id", "Test interpolateAtSample at current sample id");
            interpolateAtSampleGroup->addChild(group);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
                group->addChild(new InterpolateAtSampleIDCase(m_context, targets[targetNdx].name,
                                                              targets[targetNdx].desc, targets[targetNdx].numSamples,
                                                              targets[targetNdx].target));
        }

        // .negative
        {
            tcu::TestCaseGroup *const group =
                new tcu::TestCaseGroup(m_testCtx, "negative", "interpolateAtSample negative tests");
            interpolateAtSampleGroup->addChild(group);

            for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(negativeCompileCases); ++ndx)
                group->addChild(new NegativeCompileInterpolationCase(
                    m_context, negativeCompileCases[ndx].name, negativeCompileCases[ndx].description,
                    negativeCompileCases[ndx].caseType, NegativeCompileInterpolationCase::INTERPOLATE_AT_SAMPLE));
        }
    }

    // .interpolate_at_centroid
    {
        tcu::TestCaseGroup *const methodGroup =
            new tcu::TestCaseGroup(m_testCtx, "interpolate_at_centroid", "Test interpolateAtCentroid");
        addChild(methodGroup);

        // .consistency
        {
            tcu::TestCaseGroup *const group = new tcu::TestCaseGroup(
                m_testCtx, "consistency",
                "Test interpolateAtCentroid return value is consistent to centroid qualified value");
            methodGroup->addChild(group);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
                group->addChild(new InterpolateAtCentroidCase(
                    m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples,
                    targets[targetNdx].target, InterpolateAtCentroidCase::TEST_CONSISTENCY));
        }

        // .array_element
        {
            tcu::TestCaseGroup *const group =
                new tcu::TestCaseGroup(m_testCtx, "array_element", "Test interpolateAtCentroid with array element");
            methodGroup->addChild(group);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
                group->addChild(new InterpolateAtCentroidCase(
                    m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples,
                    targets[targetNdx].target, InterpolateAtCentroidCase::TEST_ARRAY_ELEMENT));
        }

        // .negative
        {
            tcu::TestCaseGroup *const group =
                new tcu::TestCaseGroup(m_testCtx, "negative", "interpolateAtCentroid negative tests");
            methodGroup->addChild(group);

            for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(negativeCompileCases); ++ndx)
                group->addChild(new NegativeCompileInterpolationCase(
                    m_context, negativeCompileCases[ndx].name, negativeCompileCases[ndx].description,
                    negativeCompileCases[ndx].caseType, NegativeCompileInterpolationCase::INTERPOLATE_AT_CENTROID));
        }
    }

    // .interpolate_at_offset
    {
        static const struct TestConfig
        {
            const char *name;
            InterpolateAtOffsetCase::TestType type;
        } configs[] = {
            {"no_qualifiers", InterpolateAtOffsetCase::TEST_QUALIFIER_NONE},
            {"centroid_qualifier", InterpolateAtOffsetCase::TEST_QUALIFIER_CENTROID},
            {"sample_qualifier", InterpolateAtOffsetCase::TEST_QUALIFIER_SAMPLE},
        };

        tcu::TestCaseGroup *const methodGroup =
            new tcu::TestCaseGroup(m_testCtx, "interpolate_at_offset", "Test interpolateAtOffset");
        addChild(methodGroup);

        // .no_qualifiers
        // .centroid_qualifier
        // .sample_qualifier
        for (int configNdx = 0; configNdx < DE_LENGTH_OF_ARRAY(configs); ++configNdx)
        {
            tcu::TestCaseGroup *const qualifierGroup = new tcu::TestCaseGroup(
                m_testCtx, configs[configNdx].name, "Test interpolateAtOffset with qualified/non-qualified varying");
            methodGroup->addChild(qualifierGroup);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
                qualifierGroup->addChild(new InterpolateAtOffsetCase(
                    m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples,
                    targets[targetNdx].target, configs[configNdx].type));
        }

        // .at_sample_position
        {
            tcu::TestCaseGroup *const group =
                new tcu::TestCaseGroup(m_testCtx, "at_sample_position", "Test interpolateAtOffset at sample position");
            methodGroup->addChild(group);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
                group->addChild(
                    new InterpolateAtSamplePositionCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                                        targets[targetNdx].numSamples, targets[targetNdx].target));
        }

        // .array_element
        {
            tcu::TestCaseGroup *const group =
                new tcu::TestCaseGroup(m_testCtx, "array_element", "Test interpolateAtOffset with array element");
            methodGroup->addChild(group);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
                group->addChild(new InterpolateAtOffsetCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                                            targets[targetNdx].numSamples, targets[targetNdx].target,
                                                            InterpolateAtOffsetCase::TEST_ARRAY_ELEMENT));
        }

        // .negative
        {
            tcu::TestCaseGroup *const group =
                new tcu::TestCaseGroup(m_testCtx, "negative", "interpolateAtOffset negative tests");
            methodGroup->addChild(group);

            for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(negativeCompileCases); ++ndx)
                group->addChild(new NegativeCompileInterpolationCase(
                    m_context, negativeCompileCases[ndx].name, negativeCompileCases[ndx].description,
                    negativeCompileCases[ndx].caseType, NegativeCompileInterpolationCase::INTERPOLATE_AT_OFFSET));
        }
    }
}

} // namespace Functional
} // namespace gles31
} // namespace deqp
