/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2017 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 Texture format tests.
 *//*--------------------------------------------------------------------*/

#include "es31fSRGBDecodeTests.hpp"
#include "gluContextInfo.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluRenderContext.hpp"
#include "gluTexture.hpp"
#include "glsTextureTestUtil.hpp"
#include "tcuPixelFormat.hpp"
#include "tcuTestContext.hpp"
#include "tcuRenderTarget.hpp"
#include "gluTextureUtil.hpp"
#include "tcuTextureUtil.hpp"
#include "glwFunctions.hpp"
#include "gluDefs.hpp"
#include "glwEnums.hpp"
#include "deUniquePtr.hpp"
#include "gluPixelTransfer.hpp"
#include "tcuDefs.hpp"
#include "tcuVectorUtil.hpp"
#include "gluObjectWrapper.hpp"
#include "gluStrUtil.hpp"
#include "tcuTestLog.hpp"
#include "deStringUtil.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

using glu::TextureTestUtil::TEXTURETYPE_2D;

enum SRGBDecode
{
    SRGBDECODE_SKIP_DECODE = 0,
    SRGBDECODE_DECODE,
    SRGBDECODE_DECODE_DEFAULT
};

enum ShaderOutputs
{
    SHADEROUTPUTS_ONE = 1,
    SHADEROUTPUTS_TWO,
};

enum ShaderUniforms
{
    SHADERUNIFORMS_ONE = 1,
    SHADERUNIFORMS_TWO,
};

enum ShaderSamplingGroup
{
    SHADERSAMPLINGGROUP_TEXTURE = 0,
    SHADERSAMPLINGGROUP_TEXEL_FETCH
};

enum ShaderSamplingType
{
    TEXTURESAMPLING_TEXTURE = 0,
    TEXTURESAMPLING_TEXTURE_LOD,
    TEXTURESAMPLING_TEXTURE_GRAD,
    TEXTURESAMPLING_TEXTURE_OFFSET,
    TEXTURESAMPLING_TEXTURE_PROJ,
    TEXTURESAMPLING_TEXELFETCH,
    TEXTURESAMPLING_TEXELFETCH_OFFSET,

    // ranges required for looping mechanism in a case nodes iteration function
    TEXTURESAMPLING_TEXTURE_START    = TEXTURESAMPLING_TEXTURE,
    TEXTURESAMPLING_TEXTURE_END      = TEXTURESAMPLING_TEXTURE_PROJ + 1,
    TEXTURESAMPLING_TEXELFETCH_START = TEXTURESAMPLING_TEXELFETCH,
    TEXTURESAMPLING_TEXELFETCH_END   = TEXTURESAMPLING_TEXELFETCH_OFFSET + 1
};

enum FunctionParameters
{
    FUNCTIONPARAMETERS_ONE = 1,
    FUNCTIONPARAMETERS_TWO
};

enum Blending
{
    BLENDING_REQUIRED = 0,
    BLENDING_NOT_REQUIRED
};

enum Toggling
{
    TOGGLING_REQUIRED = 0,
    TOGGLING_NOT_REQUIRED
};

tcu::Vec4 getColorReferenceLinear(void)
{
    return tcu::Vec4(0.2f, 0.3f, 0.4f, 1.0f);
}

tcu::Vec4 getColorReferenceSRGB(void)
{
    return tcu::linearToSRGB(tcu::Vec4(0.2f, 0.3f, 0.4f, 1.0f));
}

tcu::Vec4 getColorGreenPass(void)
{
    return tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
}

namespace TestDimensions
{
const int WIDTH  = 128;
const int HEIGHT = 128;
} // namespace TestDimensions

namespace TestSamplingPositions
{
const int X_POS = 0;
const int Y_POS = 0;
} // namespace TestSamplingPositions

const char *getFunctionDefinitionSRGBToLinearCheck(void)
{
    static const char *functionDefinition =
        "mediump vec4 srgbToLinearCheck(in mediump vec4 texelSRGBA, in mediump vec4 texelLinear) \n"
        "{ \n"
        "    const int NUM_CHANNELS = 4;"
        "    mediump vec4 texelSRGBAConverted; \n"
        "    mediump vec4 epsilonErr = vec4(0.005); \n"
        "    mediump vec4 testResult; \n"
        "    for (int idx = 0; idx < NUM_CHANNELS; idx++) \n"
        "    { \n"
        "        texelSRGBAConverted[idx] = pow( (texelSRGBA[idx] + 0.055) / 1.055, 1.0 / 0.4116); \n"
        "    } \n"
        "    if ( all(lessThan(abs(texelSRGBAConverted - texelLinear), epsilonErr)) || all(equal(texelSRGBAConverted, "
        "texelLinear)) ) \n"
        "    { \n"
        "        return testResult = vec4(0.0, 1.0, 0.0, 1.0); \n"
        "    } \n"
        "    else \n"
        "    { \n"
        "        return testResult = vec4(1.0, 0.0, 0.0, 1.0); \n"
        "    } \n"
        "} \n";

    return functionDefinition;
}

const char *getFunctionDefinitionEqualCheck(void)
{
    static const char *functionDefinition =
        "mediump vec4 colorsEqualCheck(in mediump vec4 colorA, in mediump vec4 colorB) \n"
        "{ \n"
        "    mediump vec4 epsilonErr = vec4(0.005); \n"
        "    mediump vec4 testResult; \n"
        "    if ( all(lessThan(abs(colorA - colorB), epsilonErr)) || all(equal(colorA, colorB)) ) \n"
        "    { \n"
        "        return testResult = vec4(0.0, 1.0, 0.0, 1.0); \n"
        "    } \n"
        "    else \n"
        "    { \n"
        "        return testResult = vec4(1.0, 0.0, 0.0, 1.0); \n"
        "    } \n"
        "} \n";

    return functionDefinition;
}

namespace EpsilonError
{
const float CPU = 0.005f;
}

struct TestGroupConfig
{
    TestGroupConfig(const char *groupName, const char *groupDescription, const tcu::TextureFormat groupInternalFormat)
        : name(groupName)
        , description(groupDescription)
        , internalFormat(groupInternalFormat)
    {
    }

    ~TestGroupConfig(void)
    {
    }

    const char *name;
    const char *description;
    const tcu::TextureFormat internalFormat;
};

struct UniformData
{
    UniformData(glw::GLuint uniformLocation, const std::string &uniformName)
        : location(uniformLocation)
        , name(uniformName)
        , toggleDecode(false)
    {
    }

    ~UniformData(void)
    {
    }

    glw::GLuint location;
    std::string name;
    bool toggleDecode;
};

struct UniformToToggle
{
    UniformToToggle(const int uniformProgramIdx, const std::string &uniformName)
        : programIdx(uniformProgramIdx)
        , name(uniformName)
    {
    }

    ~UniformToToggle(void)
    {
    }

    int programIdx;
    std::string name;
};

struct ComparisonFunction
{
    ComparisonFunction(const std::string &funcName, const FunctionParameters funcParameters,
                       const std::string &funcImplementation)
        : name(funcName)
        , parameters(funcParameters)
        , implementation(funcImplementation)
    {
    }

    ~ComparisonFunction(void)
    {
    }

    std::string name;
    FunctionParameters parameters;
    std::string implementation;
};

struct FragmentShaderParameters
{
    FragmentShaderParameters(const ShaderOutputs outputTotal, const ShaderUniforms uniformTotal,
                             ComparisonFunction *comparisonFunction, Blending blendRequired, Toggling toggleRequired);

    ~FragmentShaderParameters(void);

    ShaderOutputs outputTotal;
    ShaderUniforms uniformTotal;
    ShaderSamplingType samplingType;
    std::string functionName;
    FunctionParameters functionParameters;
    std::string functionImplementation;
    bool hasFunction;
    Blending blendRequired;
    Toggling toggleRequired;
    std::vector<std::string> uniformsToToggle;
};

FragmentShaderParameters::FragmentShaderParameters(const ShaderOutputs paramsOutputTotal,
                                                   const ShaderUniforms paramsUniformTotal,
                                                   ComparisonFunction *paramsComparisonFunction,
                                                   Blending paramsBlendRequired, Toggling paramsToggleRequired)
    : outputTotal(paramsOutputTotal)
    , uniformTotal(paramsUniformTotal)
    , samplingType(TEXTURESAMPLING_TEXTURE)
    , blendRequired(paramsBlendRequired)
    , toggleRequired(paramsToggleRequired)
{
    if (paramsComparisonFunction != DE_NULL)
    {
        functionName           = paramsComparisonFunction->name;
        functionParameters     = paramsComparisonFunction->parameters;
        functionImplementation = paramsComparisonFunction->implementation;

        hasFunction = true;
    }
    else
    {
        hasFunction = false;
    }
}

FragmentShaderParameters::~FragmentShaderParameters(void)
{
}

class SRGBTestSampler
{
public:
    SRGBTestSampler(Context &context, const tcu::Sampler::WrapMode wrapS, const tcu::Sampler::WrapMode wrapT,
                    const tcu::Sampler::FilterMode minFilter, const tcu::Sampler::FilterMode magFilter,
                    const SRGBDecode decoding);
    ~SRGBTestSampler(void);

    void setDecode(const SRGBDecode decoding);
    void setTextureUnit(const uint32_t textureUnit);
    void setIsActive(const bool isActive);

    bool getIsActive(void) const;

    void bindToTexture(void);

private:
    const glw::Functions *m_gl;
    uint32_t m_samplerHandle;
    tcu::Sampler::WrapMode m_wrapS;
    tcu::Sampler::WrapMode m_wrapT;
    tcu::Sampler::FilterMode m_minFilter;
    tcu::Sampler::FilterMode m_magFilter;
    SRGBDecode m_decoding;
    uint32_t m_textureUnit;
    bool m_isActive;
};

SRGBTestSampler::SRGBTestSampler(Context &context, const tcu::Sampler::WrapMode wrapS,
                                 const tcu::Sampler::WrapMode wrapT, const tcu::Sampler::FilterMode minFilter,
                                 const tcu::Sampler::FilterMode magFilter, const SRGBDecode decoding)
    : m_gl(&context.getRenderContext().getFunctions())
    , m_wrapS(wrapS)
    , m_wrapT(wrapT)
    , m_minFilter(minFilter)
    , m_magFilter(magFilter)
    , m_isActive(false)
{
    m_gl->genSamplers(1, &m_samplerHandle);

    m_gl->samplerParameteri(m_samplerHandle, GL_TEXTURE_WRAP_S, glu::getGLWrapMode(m_wrapS));
    m_gl->samplerParameteri(m_samplerHandle, GL_TEXTURE_WRAP_T, glu::getGLWrapMode(m_wrapT));
    m_gl->samplerParameteri(m_samplerHandle, GL_TEXTURE_MIN_FILTER, glu::getGLFilterMode(m_minFilter));
    m_gl->samplerParameteri(m_samplerHandle, GL_TEXTURE_MAG_FILTER, glu::getGLFilterMode(m_magFilter));

    this->setDecode(decoding);
}

SRGBTestSampler::~SRGBTestSampler(void)
{
    m_gl->deleteSamplers(1, &m_samplerHandle);
}

void SRGBTestSampler::setDecode(const SRGBDecode decoding)
{
    if (decoding == SRGBDECODE_SKIP_DECODE)
    {
        m_gl->samplerParameteri(m_samplerHandle, GL_TEXTURE_SRGB_DECODE_EXT, GL_SKIP_DECODE_EXT);
        GLU_EXPECT_NO_ERROR(m_gl->getError(),
                            "samplerParameteri(m_samplerID, GL_TEXTURE_SRGB_DECODE_EXT, GL_SKIP_DECODE_EXT)");
    }
    else if (decoding == SRGBDECODE_DECODE)
    {
        m_gl->samplerParameteri(m_samplerHandle, GL_TEXTURE_SRGB_DECODE_EXT, GL_DECODE_EXT);
        GLU_EXPECT_NO_ERROR(m_gl->getError(),
                            "samplerParameteri(m_samplerID, GL_TEXTURE_SRGB_DECODE_EXT, GL_DECODE_EXT)");
    }
    else
    {
        DE_FATAL("sRGB texture sampler must have either GL_SKIP_DECODE_EXT or GL_DECODE_EXT settings");
    }

    m_decoding = decoding;
}

void SRGBTestSampler::setTextureUnit(const uint32_t textureUnit)
{
    m_textureUnit = textureUnit;
}

void SRGBTestSampler::setIsActive(const bool isActive)
{
    m_isActive = isActive;
}

bool SRGBTestSampler::getIsActive(void) const
{
    return m_isActive;
}

void SRGBTestSampler::bindToTexture(void)
{
    m_gl->bindSampler(m_textureUnit, m_samplerHandle);
}

class SRGBTestTexture
{
public:
    SRGBTestTexture(Context &context, const glu::TextureTestUtil::TextureType targetType,
                    const tcu::TextureFormat internalFormat, const int width, const int height, const tcu::Vec4 color,
                    const tcu::Sampler::WrapMode wrapS, const tcu::Sampler::WrapMode wrapT,
                    const tcu::Sampler::FilterMode minFilter, const tcu::Sampler::FilterMode magFilter,
                    const SRGBDecode decoding);
    ~SRGBTestTexture(void);

    void setParameters(void);
    void setDecode(const SRGBDecode decoding);
    void setHasSampler(const bool hasSampler);

    uint32_t getHandle(void) const;
    uint32_t getGLTargetType(void) const;
    SRGBDecode getDecode(void) const;

    void upload(void);

private:
    void setColor(void);

    Context &m_context;
    glu::Texture2D m_source;
    glu::TextureTestUtil::TextureType m_targetType;
    const tcu::TextureFormat m_internalFormat;
    const int m_width;
    const int m_height;
    tcu::Vec4 m_color;
    tcu::Sampler::WrapMode m_wrapS;
    tcu::Sampler::WrapMode m_wrapT;
    tcu::Sampler::FilterMode m_minFilter;
    tcu::Sampler::FilterMode m_magFilter;
    SRGBDecode m_decoding;
    bool m_hasSampler;
};

SRGBTestTexture::SRGBTestTexture(Context &context, const glu::TextureTestUtil::TextureType targetType,
                                 const tcu::TextureFormat internalFormat, const int width, const int height,
                                 const tcu::Vec4 color, const tcu::Sampler::WrapMode wrapS,
                                 const tcu::Sampler::WrapMode wrapT, const tcu::Sampler::FilterMode minFilter,
                                 const tcu::Sampler::FilterMode magFilter, SRGBDecode decoding)
    : m_context(context)
    , m_source(context.getRenderContext(), glu::getInternalFormat(internalFormat), width, height)
    , m_targetType(targetType)
    , m_internalFormat(internalFormat)
    , m_width(width)
    , m_height(height)
    , m_color(color)
    , m_wrapS(wrapS)
    , m_wrapT(wrapT)
    , m_minFilter(minFilter)
    , m_magFilter(magFilter)
    , m_decoding(decoding)
    , m_hasSampler(false)
{
    this->setColor();
}

SRGBTestTexture::~SRGBTestTexture(void)
{
}

void SRGBTestTexture::setParameters(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    gl.bindTexture(this->getGLTargetType(), this->getHandle());

    gl.texParameteri(this->getGLTargetType(), GL_TEXTURE_WRAP_S, glu::getGLWrapMode(m_wrapS));
    gl.texParameteri(this->getGLTargetType(), GL_TEXTURE_WRAP_T, glu::getGLWrapMode(m_wrapT));
    gl.texParameteri(this->getGLTargetType(), GL_TEXTURE_MIN_FILTER, glu::getGLFilterMode(m_minFilter));
    gl.texParameteri(this->getGLTargetType(), GL_TEXTURE_MAG_FILTER, glu::getGLFilterMode(m_magFilter));

    gl.bindTexture(this->getGLTargetType(), 0);

    setDecode(m_decoding);
}

void SRGBTestTexture::setDecode(const SRGBDecode decoding)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    gl.bindTexture(this->getGLTargetType(), this->getHandle());

    switch (decoding)
    {
    case SRGBDECODE_SKIP_DECODE:
    {
        gl.texParameteri(this->getGLTargetType(), GL_TEXTURE_SRGB_DECODE_EXT, GL_SKIP_DECODE_EXT);
        GLU_EXPECT_NO_ERROR(gl.getError(),
                            "glTexParameteri(this->getGLTargetType(), GL_TEXTURE_SRGB_DECODE_EXT, GL_SKIP_DECODE_EXT)");
        break;
    }
    case SRGBDECODE_DECODE:
    {
        gl.texParameteri(this->getGLTargetType(), GL_TEXTURE_SRGB_DECODE_EXT, GL_DECODE_EXT);
        GLU_EXPECT_NO_ERROR(gl.getError(),
                            "glTexParameteri(this->getGLTargetType(), GL_TEXTURE_SRGB_DECODE_EXT, GL_DECODE_EXT)");
        break;
    }
    case SRGBDECODE_DECODE_DEFAULT:
    {
        // do not use srgb decode options. Set to default
        break;
    }
    default:
        DE_FATAL("Error: Decoding option not recognised");
    }

    gl.bindTexture(this->getGLTargetType(), 0);

    m_decoding = decoding;
}

void SRGBTestTexture::setHasSampler(const bool hasSampler)
{
    m_hasSampler = hasSampler;
}

uint32_t SRGBTestTexture::getHandle(void) const
{
    return m_source.getGLTexture();
}

uint32_t SRGBTestTexture::getGLTargetType(void) const
{
    switch (m_targetType)
    {
    case TEXTURETYPE_2D:
    {
        return GL_TEXTURE_2D;
    }
    default:
    {
        DE_FATAL("Error: Target type not recognised");
        return -1;
    }
    }
}

SRGBDecode SRGBTestTexture::getDecode(void) const
{
    return m_decoding;
}

void SRGBTestTexture::upload(void)
{
    m_source.upload();
}

void SRGBTestTexture::setColor(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    gl.bindTexture(this->getGLTargetType(), this->getHandle());

    m_source.getRefTexture().allocLevel(0);

    for (int py = 0; py < m_height; py++)
    {
        for (int px = 0; px < m_width; px++)
        {
            m_source.getRefTexture().getLevel(0).setPixel(m_color, px, py);
        }
    }

    gl.bindTexture(this->getGLTargetType(), 0);
}

class SRGBTestProgram
{
public:
    SRGBTestProgram(Context &context, const FragmentShaderParameters &shaderParameters);
    ~SRGBTestProgram(void);

    void setBlendRequired(bool blendRequired);
    void setToggleRequired(bool toggleRequired);
    void setUniformToggle(int location, bool toggleDecodeValue);

    const std::vector<UniformData> &getUniformDataList(void) const;
    int getUniformLocation(const std::string &name);
    uint32_t getHandle(void) const;
    bool getBlendRequired(void) const;

private:
    std::string genFunctionCall(ShaderSamplingType samplingType, const int uniformIdx);
    void genFragmentShader(void);

    Context &m_context;
    de::MovePtr<glu::ShaderProgram> m_program;
    FragmentShaderParameters m_shaderFragmentParameters;
    std::string m_shaderVertex;
    std::string m_shaderFragment;
    std::vector<UniformData> m_uniformDataList;
    bool m_blendRequired;
    bool m_toggleRequired;
};

SRGBTestProgram::SRGBTestProgram(Context &context, const FragmentShaderParameters &shaderParameters)
    : m_context(context)
    , m_shaderFragmentParameters(shaderParameters)
    , m_blendRequired(false)
    , m_toggleRequired(false)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    tcu::TestLog &log        = m_context.getTestContext().getLog();
    glu::ShaderProgramInfo buildInfo;
    const int totalShaderStages = 2;

    // default vertex shader used in all tests
    std::string ver(glu::isContextTypeGLCore(m_context.getRenderContext().getType()) ? "#version 450\n" :
                                                                                       "#version 310 es\n");
    m_shaderVertex = ver + "layout (location = 0) in mediump vec3 aPosition; \n"
                           "layout (location = 1) in mediump vec2 aTexCoord; \n"
                           "out mediump vec2 vs_aTexCoord; \n"
                           "void main () \n"
                           "{ \n"
                           "    gl_Position = vec4(aPosition, 1.0); \n"
                           "    vs_aTexCoord = aTexCoord; \n"
                           "} \n";

    this->genFragmentShader();

    m_program = de::MovePtr<glu::ShaderProgram>(new glu::ShaderProgram(
        m_context.getRenderContext(), glu::makeVtxFragSources(m_shaderVertex, m_shaderFragment)));

    if (!m_program->isOk())
    {
        TCU_FAIL("Failed to compile shaders and link program");
    }

    glw::GLint activeUniforms, maxLen;
    glw::GLint size, location;
    glw::GLenum type;

    gl.getProgramiv(this->getHandle(), GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxLen);
    gl.getProgramiv(this->getHandle(), GL_ACTIVE_UNIFORMS, &activeUniforms);

    std::vector<glw::GLchar> uniformName(static_cast<int>(maxLen));
    for (int idx = 0; idx < activeUniforms; idx++)
    {
        gl.getActiveUniform(this->getHandle(), idx, maxLen, NULL, &size, &type, &uniformName[0]);
        location = gl.getUniformLocation(this->getHandle(), &uniformName[0]);

        UniformData uniformData(location, std::string(&uniformName[0], strlen(&uniformName[0])));
        m_uniformDataList.push_back(uniformData);
    }

    // log shader program info. Only vertex and fragment shaders included
    buildInfo.program = m_program->getProgramInfo();
    for (int shaderIdx = 0; shaderIdx < totalShaderStages; shaderIdx++)
    {
        glu::ShaderInfo shaderInfo = m_program->getShaderInfo(
            static_cast<glu::ShaderType>(static_cast<int>(glu::SHADERTYPE_VERTEX) + static_cast<int>(shaderIdx)), 0);
        buildInfo.shaders.push_back(shaderInfo);
    }

    log << buildInfo;
}

SRGBTestProgram::~SRGBTestProgram(void)
{
    m_program = de::MovePtr<glu::ShaderProgram>(DE_NULL);
}

void SRGBTestProgram::setBlendRequired(bool blendRequired)
{
    m_blendRequired = blendRequired;
}

void SRGBTestProgram::setToggleRequired(bool toggleRequired)
{
    m_toggleRequired = toggleRequired;
}

void SRGBTestProgram::setUniformToggle(int location, bool toggleDecodeValue)
{
    if ((m_uniformDataList.empty() == false) && (location >= 0) && (location <= (int)m_uniformDataList.size()))
    {
        m_uniformDataList[location].toggleDecode = toggleDecodeValue;
    }
    else
    {
        TCU_THROW(TestError, "Error: Uniform location not found. glGetActiveUniforms returned uniforms incorrectly ");
    }
}

const std::vector<UniformData> &SRGBTestProgram::getUniformDataList(void) const
{
    return m_uniformDataList;
}

int SRGBTestProgram::getUniformLocation(const std::string &name)
{
    for (std::size_t idx = 0; idx < m_uniformDataList.size(); idx++)
    {
        if (m_uniformDataList[idx].name == name)
        {
            return m_uniformDataList[idx].location;
        }
    }

    TCU_THROW(TestError,
              "Error: If name correctly requested then glGetActiveUniforms() returned active uniform data incorrectly");
    return -1;
}

glw::GLuint SRGBTestProgram::getHandle(void) const
{
    return m_program->getProgram();
}

bool SRGBTestProgram::getBlendRequired(void) const
{
    return m_blendRequired;
}

std::string SRGBTestProgram::genFunctionCall(ShaderSamplingType samplingType, const int uniformIdx)
{
    std::ostringstream functionCall;

    functionCall << "    mediump vec4 texelColor" << uniformIdx << " = ";

    switch (samplingType)
    {
    case TEXTURESAMPLING_TEXTURE:
    {
        functionCall << "texture(uTexture" << uniformIdx << ", vs_aTexCoord); \n";
        break;
    }
    case TEXTURESAMPLING_TEXTURE_LOD:
    {
        functionCall << "textureLod(uTexture" << uniformIdx << ", vs_aTexCoord, 0.0); \n";
        break;
    }
    case TEXTURESAMPLING_TEXTURE_GRAD:
    {
        functionCall << "textureGrad(uTexture" << uniformIdx << ", vs_aTexCoord, vec2(0.0, 0.0), vec2(0.0, 0.0)); \n";
        break;
    }
    case TEXTURESAMPLING_TEXTURE_OFFSET:
    {
        functionCall << "textureOffset(uTexture" << uniformIdx << ", vs_aTexCoord, ivec2(0.0, 0.0)); \n";
        break;
    }
    case TEXTURESAMPLING_TEXTURE_PROJ:
    {
        functionCall << "textureProj(uTexture" << uniformIdx << ", vec3(vs_aTexCoord, 1.0)); \n";
        break;
    }
    case TEXTURESAMPLING_TEXELFETCH:
    {
        functionCall << "texelFetch(uTexture" << uniformIdx << ", ivec2(vs_aTexCoord), 0); \n";
        break;
    }
    case TEXTURESAMPLING_TEXELFETCH_OFFSET:
    {
        functionCall << "texelFetchOffset(uTexture" << uniformIdx << ", ivec2(vs_aTexCoord), 0, ivec2(0.0, 0.0)); \n";
        break;
    }
    default:
    {
        DE_FATAL("Error: Sampling type not recognised");
    }
    }

    return functionCall.str();
}

void SRGBTestProgram::genFragmentShader(void)
{
    std::ostringstream source;
    std::ostringstream sampleTexture;
    std::ostringstream functionParameters;
    std::ostringstream shaderOutputs;

    // if comparison function is present resulting shader requires precisely one output
    DE_ASSERT(!(m_shaderFragmentParameters.hasFunction &&
                (static_cast<int>(m_shaderFragmentParameters.outputTotal) != static_cast<int>(SHADEROUTPUTS_ONE))));

    // function parameters must equal the number of uniforms i.e. textures passed into the function
    DE_ASSERT(
        !(m_shaderFragmentParameters.hasFunction && (static_cast<int>(m_shaderFragmentParameters.uniformTotal) !=
                                                     static_cast<int>(m_shaderFragmentParameters.functionParameters))));

    // fragment shader cannot contain more outputs than the number of texture uniforms
    DE_ASSERT(!(static_cast<int>(m_shaderFragmentParameters.outputTotal) >
                static_cast<int>(m_shaderFragmentParameters.uniformTotal)));

    source << (glu::isContextTypeGLCore(m_context.getRenderContext().getType()) ? "#version 450\n" :
                                                                                  "#version 310 es\n")
           << "in mediump vec2 vs_aTexCoord; \n";

    for (int output = 0; output < m_shaderFragmentParameters.outputTotal; output++)
    {
        source << "layout (location = " << output << ") out mediump vec4 fs_aColor" << output << "; \n";
    }

    for (int uniform = 0; uniform < m_shaderFragmentParameters.uniformTotal; uniform++)
    {
        source << "uniform sampler2D uTexture" << uniform << "; \n";
    }

    if (m_shaderFragmentParameters.hasFunction == true)
    {
        source << m_shaderFragmentParameters.functionImplementation;
    }

    source << "void main () \n"
           << "{ \n";

    for (int uniformIdx = 0; uniformIdx < m_shaderFragmentParameters.uniformTotal; uniformIdx++)
    {
        source << this->genFunctionCall(m_shaderFragmentParameters.samplingType, uniformIdx);
    }

    if (m_shaderFragmentParameters.hasFunction == true)
    {
        switch (static_cast<FunctionParameters>(m_shaderFragmentParameters.functionParameters))
        {
        case FUNCTIONPARAMETERS_ONE:
        {
            functionParameters << "(texelColor0)";
            break;
        }
        case FUNCTIONPARAMETERS_TWO:
        {
            functionParameters << "(texelColor0, texelColor1)";
            break;
        }
        default:
        {
            DE_FATAL("Error: Number of comparison function parameters invalid");
        }
        }

        shaderOutputs << "    fs_aColor0 = " << m_shaderFragmentParameters.functionName << functionParameters.str()
                      << "; \n";
    }
    else
    {
        for (int output = 0; output < m_shaderFragmentParameters.outputTotal; output++)
        {
            shaderOutputs << "    fs_aColor" << output << " = texelColor" << output << "; \n";
        }
    }

    source << shaderOutputs.str();
    source << "} \n";

    m_shaderFragment = source.str();
}

class SRGBTestCase : public TestCase
{
public:
    SRGBTestCase(Context &context, const char *name, const char *description, const tcu::TextureFormat internalFormat);
    ~SRGBTestCase(void);

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

    void setSamplingGroup(const ShaderSamplingGroup samplingGroup);
    void setSamplingLocations(const int px, const int py);
    void setUniformToggle(const int programIdx, const std::string &uniformName, bool toggleDecode);

    void addTexture(const glu::TextureTestUtil::TextureType targetType, const int width, const int height,
                    const tcu::Vec4 color, const tcu::Sampler::WrapMode wrapS, const tcu::Sampler::WrapMode wrapT,
                    const tcu::Sampler::FilterMode minFilter, const tcu::Sampler::FilterMode magFilter,
                    const SRGBDecode decoding);
    void addSampler(const tcu::Sampler::WrapMode wrapS, const tcu::Sampler::WrapMode wrapT,
                    const tcu::Sampler::FilterMode minFilter, const tcu::Sampler::FilterMode magFilter,
                    const SRGBDecode decoding);
    void addShaderProgram(const FragmentShaderParameters &shaderParameters);

    void genShaderPrograms(ShaderSamplingType samplingType);
    void deleteShaderPrograms(void);

    void readResultTextures(void);
    void storeResultPixels(std::vector<tcu::Vec4> &resultPixelData);

    void toggleDecode(const std::vector<UniformData> &uniformDataList);
    void bindSamplerToTexture(const int samplerIdx, const int textureIdx, const uint32_t textureUnit);
    void activateSampler(const int samplerIdx, const bool active);
    void logColor(const std::string &colorLogMessage, int colorIdx, tcu::Vec4 color) const;
    tcu::Vec4 formatReferenceColor(tcu::Vec4 referenceColor);

    // render function has a default implentation. Can be overriden for special cases
    virtual void render(void);

    // following functions must be overidden to perform individual test cases
    virtual void setupTest(void)    = 0;
    virtual bool verifyResult(void) = 0;

protected:
    de::MovePtr<glu::Framebuffer> m_framebuffer;
    std::vector<SRGBTestTexture *> m_textureSourceList;
    std::vector<SRGBTestSampler *> m_samplerList;
    std::vector<glw::GLuint> m_renderBufferList;
    const tcu::Vec4 m_epsilonError;
    std::vector<tcu::TextureLevel> m_textureResultList;
    int m_resultOutputTotal;
    tcu::TextureFormat m_resultTextureFormat;
    glw::GLuint m_vaoID;
    glw::GLuint m_vertexDataID;
    std::vector<FragmentShaderParameters> m_shaderParametersList;
    std::vector<SRGBTestProgram *> m_shaderProgramList;
    ShaderSamplingGroup m_samplingGroup;
    int m_px;
    int m_py;
    const tcu::TextureFormat m_internalFormat;

private:
    void uploadTextures(void);
    void initFrameBuffer(void);
    void initVertexData(void);

    SRGBTestCase(const SRGBTestCase &);
    SRGBTestCase &operator=(const SRGBTestCase &);
};

SRGBTestCase::SRGBTestCase(Context &context, const char *name, const char *description,
                           const tcu::TextureFormat internalFormat)
    : TestCase(context, name, description)
    , m_epsilonError(EpsilonError::CPU)
    , m_resultOutputTotal(0)
    , m_resultTextureFormat(tcu::TextureFormat(tcu::TextureFormat::sRGBA, tcu::TextureFormat::UNORM_INT8))
    , m_vaoID(0)
    , m_vertexDataID(0)
    , m_samplingGroup(SHADERSAMPLINGGROUP_TEXTURE)
    , m_px(0)
    , m_py(0)
    , m_internalFormat(internalFormat)
{
}

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

void SRGBTestCase::init(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    // extension requirements for test
    if ((glu::getInternalFormat(m_internalFormat) == GL_SRGB8_ALPHA8) &&
        !m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_sRGB_decode"))
    {
        throw tcu::NotSupportedError("Test requires GL_EXT_texture_sRGB_decode extension");
    }

    if ((glu::getInternalFormat(m_internalFormat) == GL_SRG8_EXT) &&
        !(m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_sRGB_RG8")))
    {
        throw tcu::NotSupportedError("Test requires GL_EXT_texture_sRGB_RG8 extension");
    }

    if ((glu::getInternalFormat(m_internalFormat) == GL_SR8_EXT) &&
        !(m_context.getContextInfo().isExtensionSupported("GL_EXT_texture_sRGB_R8")))
    {
        throw tcu::NotSupportedError("Test requires GL_EXT_texture_sRGB_R8 extension");
    }

    m_framebuffer = de::MovePtr<glu::Framebuffer>(new glu::Framebuffer(m_context.getRenderContext()));

    if (glu::isContextTypeGLCore(m_context.getRenderContext().getType()))
    {
        gl.enable(GL_FRAMEBUFFER_SRGB);
    }
}

void SRGBTestCase::deinit(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    m_framebuffer = de::MovePtr<glu::Framebuffer>(DE_NULL);

    for (std::size_t renderBufferIdx = 0; renderBufferIdx < m_renderBufferList.size(); renderBufferIdx++)
    {
        gl.deleteRenderbuffers(1, &m_renderBufferList[renderBufferIdx]);
    }
    m_renderBufferList.clear();

    if (glu::isContextTypeGLCore(m_context.getRenderContext().getType()))
    {
        gl.disable(GL_FRAMEBUFFER_SRGB);
    }

    for (std::size_t textureSourceIdx = 0; textureSourceIdx < m_textureSourceList.size(); textureSourceIdx++)
    {
        delete m_textureSourceList[textureSourceIdx];
    }
    m_textureSourceList.clear();

    for (std::size_t samplerIdx = 0; samplerIdx < m_samplerList.size(); samplerIdx++)
    {
        delete m_samplerList[samplerIdx];
    }
    m_samplerList.clear();

    if (m_vaoID != 0)
    {
        gl.deleteVertexArrays(1, &m_vaoID);
        m_vaoID = 0;
    }

    if (m_vertexDataID != 0)
    {
        gl.deleteBuffers(1, &m_vertexDataID);
        m_vertexDataID = 0;
    }
}

SRGBTestCase::IterateResult SRGBTestCase::iterate(void)
{
    bool result;
    int startIdx = -1;
    int endIdx   = -1;

    this->setupTest();

    if (m_samplingGroup == SHADERSAMPLINGGROUP_TEXTURE)
    {
        startIdx = static_cast<int>(TEXTURESAMPLING_TEXTURE_START);
        endIdx   = static_cast<int>(TEXTURESAMPLING_TEXTURE_END);
    }
    else if (m_samplingGroup == SHADERSAMPLINGGROUP_TEXEL_FETCH)
    {
        startIdx = static_cast<int>(TEXTURESAMPLING_TEXELFETCH_START);
        endIdx   = static_cast<int>(TEXTURESAMPLING_TEXELFETCH_END);
    }
    else
    {
        DE_FATAL("Error: Sampling group not defined");
    }

    this->initVertexData();
    this->initFrameBuffer();

    // loop through all sampling types in the required sampling group, performing individual tests for each
    for (int samplingTypeIdx = startIdx; samplingTypeIdx < endIdx; samplingTypeIdx++)
    {
        this->genShaderPrograms(static_cast<ShaderSamplingType>(samplingTypeIdx));
        this->uploadTextures();
        this->render();

        result = this->verifyResult();

        this->deleteShaderPrograms();

        if (result == true)
        {
            m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
        }
        else
        {
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Result verification failed");
            return STOP;
        }
    }

    return STOP;
}

void SRGBTestCase::setSamplingGroup(const ShaderSamplingGroup samplingGroup)
{
    m_samplingGroup = samplingGroup;
}

void SRGBTestCase::setSamplingLocations(const int px, const int py)
{
    m_px = px;
    m_py = py;
}

void SRGBTestCase::addTexture(const glu::TextureTestUtil::TextureType targetType, const int width, const int height,
                              const tcu::Vec4 color, const tcu::Sampler::WrapMode wrapS,
                              const tcu::Sampler::WrapMode wrapT, const tcu::Sampler::FilterMode minFilter,
                              const tcu::Sampler::FilterMode magFilter, const SRGBDecode decoding)
{
    SRGBTestTexture *texture = new SRGBTestTexture(m_context, targetType, m_internalFormat, width, height, color, wrapS,
                                                   wrapT, minFilter, magFilter, decoding);
    m_textureSourceList.push_back(texture);
}

void SRGBTestCase::addSampler(const tcu::Sampler::WrapMode wrapS, const tcu::Sampler::WrapMode wrapT,
                              const tcu::Sampler::FilterMode minFilter, const tcu::Sampler::FilterMode magFilter,
                              const SRGBDecode decoding)
{
    SRGBTestSampler *sampler = new SRGBTestSampler(m_context, wrapS, wrapT, minFilter, magFilter, decoding);
    m_samplerList.push_back(sampler);
}

void SRGBTestCase::addShaderProgram(const FragmentShaderParameters &shaderParameters)
{
    m_shaderParametersList.push_back(shaderParameters);
    m_resultOutputTotal = shaderParameters.outputTotal;
}

void SRGBTestCase::genShaderPrograms(ShaderSamplingType samplingType)
{
    for (int shaderParamsIdx = 0; shaderParamsIdx < (int)m_shaderParametersList.size(); shaderParamsIdx++)
    {
        m_shaderParametersList[shaderParamsIdx].samplingType = samplingType;
        SRGBTestProgram *shaderProgram = new SRGBTestProgram(m_context, m_shaderParametersList[shaderParamsIdx]);

        if (m_shaderParametersList[shaderParamsIdx].blendRequired == BLENDING_REQUIRED)
        {
            shaderProgram->setBlendRequired(true);
        }

        if (m_shaderParametersList[shaderParamsIdx].toggleRequired == TOGGLING_REQUIRED)
        {
            shaderProgram->setToggleRequired(true);
            std::vector<std::string> uniformsToToggle = m_shaderParametersList[shaderParamsIdx].uniformsToToggle;

            for (int uniformNameIdx = 0; uniformNameIdx < (int)uniformsToToggle.size(); uniformNameIdx++)
            {
                shaderProgram->setUniformToggle(shaderProgram->getUniformLocation(uniformsToToggle[uniformNameIdx]),
                                                true);
            }
        }

        m_shaderProgramList.push_back(shaderProgram);
    }
}

void SRGBTestCase::deleteShaderPrograms(void)
{
    for (std::size_t idx = 0; idx < m_shaderProgramList.size(); idx++)
    {
        delete m_shaderProgramList[idx];
    }
    m_shaderProgramList.clear();
}

void SRGBTestCase::readResultTextures(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    int width                = m_context.getRenderContext().getRenderTarget().getWidth();
    int height               = m_context.getRenderContext().getRenderTarget().getHeight();

    gl.bindFramebuffer(GL_FRAMEBUFFER, **m_framebuffer);

    m_textureResultList.resize(m_renderBufferList.size());

    for (std::size_t renderBufferIdx = 0; renderBufferIdx < m_renderBufferList.size(); renderBufferIdx++)
    {
        gl.readBuffer(GL_COLOR_ATTACHMENT0 + (glw::GLenum)renderBufferIdx);
        m_textureResultList[renderBufferIdx].setStorage(m_resultTextureFormat, width, height);
        glu::readPixels(m_context.getRenderContext(), 0, 0, m_textureResultList[renderBufferIdx].getAccess());
        GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels()");
    }

    gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
}

void SRGBTestCase::storeResultPixels(std::vector<tcu::Vec4> &resultPixelData)
{
    tcu::TestLog &log = m_context.getTestContext().getLog();
    std::ostringstream message;
    int width  = m_context.getRenderContext().getRenderTarget().getWidth();
    int height = m_context.getRenderContext().getRenderTarget().getHeight();

    // ensure result sampling coordinates are within range of the result color attachment
    DE_ASSERT((m_px >= 0) && (m_px < width));
    DE_ASSERT((m_py >= 0) && (m_py < height));
    DE_UNREF(width && height);

    for (int idx = 0; idx < (int)m_textureResultList.size(); idx++)
    {
        resultPixelData.push_back(m_textureResultList[idx].getAccess().getPixel(m_px, m_py));
        this->logColor(std::string("Result color: "), idx, resultPixelData[idx]);
    }

    // log error rate (threshold)
    message << m_epsilonError;
    log << tcu::TestLog::Message << std::string("Epsilon error: ") << message.str() << tcu::TestLog::EndMessage;
}

void SRGBTestCase::toggleDecode(const std::vector<UniformData> &uniformDataList)
{
    DE_ASSERT(uniformDataList.size() <= m_textureSourceList.size());

    for (int uniformIdx = 0; uniformIdx < (int)uniformDataList.size(); uniformIdx++)
    {
        if (uniformDataList[uniformIdx].toggleDecode == true)
        {
            if (m_textureSourceList[uniformIdx]->getDecode() == SRGBDECODE_DECODE_DEFAULT)
            {
                // cannot toggle default
                continue;
            }

            // toggle sRGB decode values (ignoring value if set to default)
            m_textureSourceList[uniformIdx]->setDecode(
                (SRGBDecode)((m_textureSourceList[uniformIdx]->getDecode() + 1) % SRGBDECODE_DECODE_DEFAULT));
        }
    }
}

void SRGBTestCase::bindSamplerToTexture(const int samplerIdx, const int textureIdx, const uint32_t textureUnit)
{
    uint32_t enumConversion = textureUnit - GL_TEXTURE0;
    m_textureSourceList[textureIdx]->setHasSampler(true);
    m_samplerList[samplerIdx]->setTextureUnit(enumConversion);
}

void SRGBTestCase::activateSampler(const int samplerIdx, const bool active)
{
    m_samplerList[samplerIdx]->setIsActive(active);
}

void SRGBTestCase::logColor(const std::string &colorLogMessage, int colorIdx, tcu::Vec4 color) const
{
    tcu::TestLog &log = m_context.getTestContext().getLog();
    std::ostringstream message;

    message << colorLogMessage << colorIdx << " = (" << color.x() << ", " << color.y() << ", " << color.z() << ", "
            << color.w() << ")";
    log << tcu::TestLog::Message << message.str() << tcu::TestLog::EndMessage;
}

tcu::Vec4 SRGBTestCase::formatReferenceColor(tcu::Vec4 referenceColor)
{
    switch (glu::getInternalFormat(m_internalFormat))
    {
    case GL_SRGB8_ALPHA8:
    {
        return referenceColor;
    }
    case GL_SRG8_EXT:
    {
        // zero unwanted color channels
        referenceColor.z() = 0;
        return referenceColor;
    }
    case GL_SR8_EXT:
    {
        // zero unwanted color channels
        referenceColor.y() = 0;
        referenceColor.z() = 0;
        return referenceColor;
    }
    default:
    {
        DE_FATAL("Error: Internal format not recognised");
        return referenceColor;
    }
    }
}

void SRGBTestCase::render(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    // default rendering only uses one program
    gl.bindFramebuffer(GL_FRAMEBUFFER, **m_framebuffer);
    gl.bindVertexArray(m_vaoID);

    gl.useProgram(m_shaderProgramList[0]->getHandle());

    for (int textureSourceIdx = 0; textureSourceIdx < (int)m_textureSourceList.size(); textureSourceIdx++)
    {
        gl.activeTexture(GL_TEXTURE0 + (glw::GLenum)textureSourceIdx);
        gl.bindTexture(m_textureSourceList[textureSourceIdx]->getGLTargetType(),
                       m_textureSourceList[textureSourceIdx]->getHandle());
        glw::GLuint samplerUniformLocationID = gl.getUniformLocation(
            m_shaderProgramList[0]->getHandle(), (std::string("uTexture") + de::toString(textureSourceIdx)).c_str());
        TCU_CHECK(samplerUniformLocationID != (glw::GLuint)-1);
        gl.uniform1i(samplerUniformLocationID, (glw::GLenum)textureSourceIdx);
    }

    for (int samplerIdx = 0; samplerIdx < (int)m_samplerList.size(); samplerIdx++)
    {
        if (m_samplerList[samplerIdx]->getIsActive() == true)
        {
            m_samplerList[samplerIdx]->bindToTexture();
        }
    }

    gl.drawArrays(GL_TRIANGLES, 0, 6);

    for (std::size_t textureSourceIdx = 0; textureSourceIdx < m_textureSourceList.size(); textureSourceIdx++)
    {
        gl.bindTexture(m_textureSourceList[textureSourceIdx]->getGLTargetType(), 0);
    }
    gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
    gl.bindVertexArray(0);
    gl.bindBuffer(GL_ARRAY_BUFFER, 0);
}

void SRGBTestCase::uploadTextures(void)
{
    for (std::size_t idx = 0; idx < m_textureSourceList.size(); idx++)
    {
        m_textureSourceList[idx]->upload();
        m_textureSourceList[idx]->setParameters();
    }
}

void SRGBTestCase::initFrameBuffer(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    int width                = m_context.getRenderContext().getRenderTarget().getWidth();
    int height               = m_context.getRenderContext().getRenderTarget().getHeight();

    if (m_resultOutputTotal == 0)
    {
        throw std::invalid_argument("SRGBTestExecutor must have at least 1 rendered result");
    }

    gl.bindFramebuffer(GL_FRAMEBUFFER, **m_framebuffer);

    DE_ASSERT(m_renderBufferList.empty());
    for (int outputIdx = 0; outputIdx < m_resultOutputTotal; outputIdx++)
    {
        glw::GLuint renderBuffer = -1;
        m_renderBufferList.push_back(renderBuffer);
    }

    for (std::size_t renderBufferIdx = 0; renderBufferIdx < m_renderBufferList.size(); renderBufferIdx++)
    {
        gl.genRenderbuffers(1, &m_renderBufferList[renderBufferIdx]);
        gl.bindRenderbuffer(GL_RENDERBUFFER, m_renderBufferList[renderBufferIdx]);
        gl.renderbufferStorage(GL_RENDERBUFFER, glu::getInternalFormat(m_resultTextureFormat), width, height);
        gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + (glw::GLenum)renderBufferIdx, GL_RENDERBUFFER,
                                   m_renderBufferList[renderBufferIdx]);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Create and setup renderbuffer object");
    }
    TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);

    std::vector<glw::GLenum> renderBufferTargets(m_renderBufferList.size());
    for (std::size_t renderBufferIdx = 0; renderBufferIdx < m_renderBufferList.size(); renderBufferIdx++)
    {
        renderBufferTargets[renderBufferIdx] = GL_COLOR_ATTACHMENT0 + (glw::GLenum)renderBufferIdx;
    }
    gl.drawBuffers((glw::GLsizei)renderBufferTargets.size(), &renderBufferTargets[0]);
    GLU_EXPECT_NO_ERROR(gl.getError(), "glDrawBuffer()");

    gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
}

void SRGBTestCase::initVertexData(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    static const glw::GLfloat squareVertexData[] = {
        // position                // texcoord
        -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom left corner
        1.0f,  -1.0f, 0.0f, 1.0f, 0.0f, // bottom right corner
        1.0f,  1.0f,  0.0f, 1.0f, 1.0f, // Top right corner

        -1.0f, 1.0f,  0.0f, 0.0f, 1.0f, // top left corner
        1.0f,  1.0f,  0.0f, 1.0f, 1.0f, // Top right corner
        -1.0f, -1.0f, 0.0f, 0.0f, 0.0f  // bottom left corner
    };

    DE_ASSERT(m_vaoID == 0);
    gl.genVertexArrays(1, &m_vaoID);
    gl.bindVertexArray(m_vaoID);

    gl.genBuffers(1, &m_vertexDataID);
    gl.bindBuffer(GL_ARRAY_BUFFER, m_vertexDataID);
    gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizei)sizeof(squareVertexData), squareVertexData, GL_STATIC_DRAW);

    gl.vertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * (glw::GLsizei)sizeof(float), (glw::GLvoid *)0);
    gl.enableVertexAttribArray(0);
    gl.vertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * (glw::GLsizei)sizeof(float),
                           (glw::GLvoid *)(3 * sizeof(float)));
    gl.enableVertexAttribArray(1);

    gl.bindVertexArray(0);
    gl.bindBuffer(GL_ARRAY_BUFFER, 0);
}

class TextureDecodeSkippedCase : public SRGBTestCase
{
public:
    TextureDecodeSkippedCase(Context &context, const char *name, const char *description,
                             const tcu::TextureFormat internalFormat)
        : SRGBTestCase(context, name, description, internalFormat)
    {
    }

    ~TextureDecodeSkippedCase(void)
    {
    }

    void setupTest(void);
    bool verifyResult(void);
};

void TextureDecodeSkippedCase::setupTest(void)
{
    // TEST STEPS:
    //    - create and set texture to DECODE_SKIP_EXT
    //    - store texture on GPU
    //    - in fragment shader, sample the texture using texture*() and render texel values to a color attachment in the FBO
    //    - on the host, read back the pixel values into a tcu::TextureLevel
    //    - analyse the texel values, expecting them in sRGB format i.e. linear space decoding was skipped

    FragmentShaderParameters shaderParameters(SHADEROUTPUTS_ONE, SHADERUNIFORMS_ONE, NULL, BLENDING_NOT_REQUIRED,
                                              TOGGLING_NOT_REQUIRED);

    this->addTexture(TEXTURETYPE_2D, TestDimensions::WIDTH, TestDimensions::HEIGHT, getColorReferenceLinear(),
                     tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::LINEAR,
                     tcu::Sampler::LINEAR, SRGBDECODE_SKIP_DECODE);

    this->addShaderProgram(shaderParameters);
    this->setSamplingLocations(TestSamplingPositions::X_POS, TestSamplingPositions::Y_POS);

    this->setSamplingGroup(SHADERSAMPLINGGROUP_TEXTURE);
}

bool TextureDecodeSkippedCase::verifyResult(void)
{
    tcu::TestLog &log        = m_context.getTestContext().getLog();
    const int resultColorIdx = 0;
    std::vector<tcu::Vec4> pixelResultList;
    tcu::Vec4 pixelConverted;
    tcu::Vec4 pixelReference;
    tcu::Vec4 pixelExpected;

    this->readResultTextures();
    this->storeResultPixels(pixelResultList);

    pixelConverted = tcu::sRGBToLinear(pixelResultList[resultColorIdx]);
    pixelReference = this->formatReferenceColor(getColorReferenceLinear());
    pixelExpected  = this->formatReferenceColor(getColorReferenceSRGB());

    this->formatReferenceColor(pixelReference);
    this->logColor(std::string("Expected color: "), resultColorIdx, pixelExpected);

    // result color 0 should be sRGB. Compare with linear reference color
    if ((tcu::boolAll(tcu::lessThan(tcu::abs(pixelConverted - pixelReference), m_epsilonError))) ||
        (tcu::boolAll(tcu::equal(pixelConverted, pixelReference))))
    {
        log << tcu::TestLog::Message << std::string("sRGB as expected") << tcu::TestLog::EndMessage;
        return true;
    }
    else
    {
        log << tcu::TestLog::Message << std::string("not sRGB as expected") << tcu::TestLog::EndMessage;
        return false;
    }
}

class TextureDecodeEnabledCase : public SRGBTestCase
{
public:
    TextureDecodeEnabledCase(Context &context, const char *name, const char *description,
                             const tcu::TextureFormat internalFormat)
        : SRGBTestCase(context, name, description, internalFormat)
    {
    }

    ~TextureDecodeEnabledCase(void)
    {
    }

    void setupTest(void);
    bool verifyResult(void);
};

void TextureDecodeEnabledCase::setupTest(void)
{
    // TEST STEPS:
    //    - create and set texture to DECODE_EXT
    //    - store texture on GPU
    //    - in fragment shader, sample the texture using texture*() and render texel values to a color attachment in the FBO
    //    - on the host, read back the pixel values into a tcu::TextureLevel
    //    - analyse the texel values, expecting them in lRGB format i.e. linear space decoding was enabled

    FragmentShaderParameters shaderParameters(SHADEROUTPUTS_ONE, SHADERUNIFORMS_ONE, NULL, BLENDING_NOT_REQUIRED,
                                              TOGGLING_NOT_REQUIRED);

    this->addTexture(TEXTURETYPE_2D, TestDimensions::WIDTH, TestDimensions::HEIGHT, getColorReferenceLinear(),
                     tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::LINEAR,
                     tcu::Sampler::LINEAR, SRGBDECODE_DECODE);

    this->addShaderProgram(shaderParameters);

    this->setSamplingLocations(TestSamplingPositions::X_POS, TestSamplingPositions::Y_POS);

    this->setSamplingGroup(SHADERSAMPLINGGROUP_TEXTURE);
}

bool TextureDecodeEnabledCase::verifyResult(void)
{
    tcu::TestLog &log        = m_context.getTestContext().getLog();
    const int resultColorIdx = 0;
    std::vector<tcu::Vec4> pixelResultList;
    tcu::Vec4 pixelConverted;
    tcu::Vec4 pixelReference;
    tcu::Vec4 pixelExpected;

    this->readResultTextures();
    this->storeResultPixels(pixelResultList);

    pixelConverted = tcu::linearToSRGB(pixelResultList[resultColorIdx]);
    pixelReference = this->formatReferenceColor(getColorReferenceSRGB());
    pixelExpected  = this->formatReferenceColor(getColorReferenceLinear());

    this->logColor(std::string("Expected color: "), resultColorIdx, pixelExpected);

    // result color 0 should be SRGB. Compare with sRGB reference color
    if ((tcu::boolAll(tcu::lessThan(tcu::abs(pixelConverted - pixelReference), m_epsilonError))) ||
        (tcu::boolAll(tcu::equal(pixelConverted, pixelReference))))
    {
        log << tcu::TestLog::Message << std::string("linear as expected") << tcu::TestLog::EndMessage;
        return true;
    }
    else
    {
        log << tcu::TestLog::Message << std::string("not linear as expected") << tcu::TestLog::EndMessage;
        return false;
    }
}

class TexelFetchDecodeSkippedcase : public SRGBTestCase
{
public:
    TexelFetchDecodeSkippedcase(Context &context, const char *name, const char *description,
                                const tcu::TextureFormat internalFormat)
        : SRGBTestCase(context, name, description, internalFormat)
    {
    }

    ~TexelFetchDecodeSkippedcase(void)
    {
    }

    void setupTest(void);
    bool verifyResult(void);
};

void TexelFetchDecodeSkippedcase::setupTest(void)
{
    // TEST STEPS:
    //    - create and set texture to DECODE_SKIP_EXT
    //    - store texture on GPU
    //    - in fragment shader, sample the texture using texelFetch*() and render texel values to a color attachment in the FBO
    //    - on the host, read back the pixel values into a tcu::TextureLevel
    //    - analyse the texel values, expecting them in lRGB format i.e. linear space decoding is always enabled with texelFetch*()

    FragmentShaderParameters shaderParameters(SHADEROUTPUTS_ONE, SHADERUNIFORMS_ONE, NULL, BLENDING_NOT_REQUIRED,
                                              TOGGLING_NOT_REQUIRED);

    this->addTexture(TEXTURETYPE_2D, TestDimensions::WIDTH, TestDimensions::HEIGHT, getColorReferenceLinear(),
                     tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::LINEAR,
                     tcu::Sampler::LINEAR, SRGBDECODE_SKIP_DECODE);

    this->addShaderProgram(shaderParameters);

    this->setSamplingLocations(TestSamplingPositions::X_POS, TestSamplingPositions::Y_POS);

    this->setSamplingGroup(SHADERSAMPLINGGROUP_TEXEL_FETCH);
}

bool TexelFetchDecodeSkippedcase::verifyResult(void)
{
    tcu::TestLog &log        = m_context.getTestContext().getLog();
    const int resultColorIdx = 0;
    std::vector<tcu::Vec4> pixelResultList;
    tcu::Vec4 pixelReference;
    tcu::Vec4 pixelExpected;

    this->readResultTextures();
    this->storeResultPixels(pixelResultList);

    pixelReference = pixelExpected = this->formatReferenceColor(getColorReferenceLinear());

    this->logColor(std::string("Expected color: "), resultColorIdx, pixelExpected);

    // result color 0 should be linear due to automatic conversion via texelFetch*(). Compare with linear reference color
    if ((tcu::boolAll(tcu::lessThan(tcu::abs(pixelResultList[0] - pixelReference), m_epsilonError))) ||
        (tcu::boolAll(tcu::equal(pixelResultList[0], pixelReference))))
    {
        log << tcu::TestLog::Message << std::string("linear as expected") << tcu::TestLog::EndMessage;
        return true;
    }
    else
    {
        log << tcu::TestLog::Message << std::string("not linear as expected") << tcu::TestLog::EndMessage;
        return false;
    }
}

class GPUConversionDecodeEnabledCase : public SRGBTestCase
{
public:
    GPUConversionDecodeEnabledCase(Context &context, const char *name, const char *description,
                                   const tcu::TextureFormat internalFormat)
        : SRGBTestCase(context, name, description, internalFormat)
    {
    }

    ~GPUConversionDecodeEnabledCase(void)
    {
    }

    void setupTest(void);
    bool verifyResult(void);
};

void GPUConversionDecodeEnabledCase::setupTest(void)
{
    // TEST STEPS:
    //    - create and set texture_a to DECODE_SKIP_EXT and texture_b to default
    //    - store textures on GPU
    //    - in fragment shader, sample both textures using texture*() and manually perform sRGB to lRGB conversion on texture_b
    //    - in fragment shader, compare converted texture_b with texture_a
    //    - render green image for pass or red for fail

    ComparisonFunction comparisonFunction("srgbToLinearCheck", FUNCTIONPARAMETERS_TWO,
                                          getFunctionDefinitionSRGBToLinearCheck());

    FragmentShaderParameters shaderParameters(SHADEROUTPUTS_ONE, SHADERUNIFORMS_TWO, &comparisonFunction,
                                              BLENDING_NOT_REQUIRED, TOGGLING_NOT_REQUIRED);

    this->addTexture(TEXTURETYPE_2D, TestDimensions::WIDTH, TestDimensions::HEIGHT, getColorReferenceLinear(),
                     tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::LINEAR,
                     tcu::Sampler::LINEAR, SRGBDECODE_SKIP_DECODE);

    this->addTexture(TEXTURETYPE_2D, TestDimensions::WIDTH, TestDimensions::HEIGHT, getColorReferenceLinear(),
                     tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::LINEAR,
                     tcu::Sampler::LINEAR, SRGBDECODE_DECODE_DEFAULT);

    this->addShaderProgram(shaderParameters);

    this->setSamplingLocations(TestSamplingPositions::X_POS, TestSamplingPositions::Y_POS);

    this->setSamplingGroup(SHADERSAMPLINGGROUP_TEXTURE);
}

bool GPUConversionDecodeEnabledCase::verifyResult(void)
{
    tcu::TestLog &log        = m_context.getTestContext().getLog();
    const int resultColorIdx = 0;
    std::vector<tcu::Vec4> pixelResultList;

    this->readResultTextures();
    this->storeResultPixels(pixelResultList);
    this->logColor(std::string("Expected color: "), resultColorIdx, getColorGreenPass());

    // result color returned from GPU is either green (pass) or fail (red)
    if (tcu::boolAll(tcu::equal(pixelResultList[resultColorIdx], getColorGreenPass())))
    {
        log << tcu::TestLog::Message << std::string("returned pass color from GPU") << tcu::TestLog::EndMessage;
        return true;
    }
    else
    {
        log << tcu::TestLog::Message << std::string("returned fail color from GPU") << tcu::TestLog::EndMessage;
        return false;
    }
}

class DecodeToggledCase : public SRGBTestCase
{
public:
    DecodeToggledCase(Context &context, const char *name, const char *description,
                      const tcu::TextureFormat internalFormat)
        : SRGBTestCase(context, name, description, internalFormat)
    {
    }

    ~DecodeToggledCase(void)
    {
    }

    void render(void);
    void setupTest(void);
    bool verifyResult(void);
};

void DecodeToggledCase::render(void)
{
    // override the base SRGBTestCase render function with the purpose of switching between shader programs,
    // toggling texture sRGB decode state between draw calls
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    gl.bindFramebuffer(GL_FRAMEBUFFER, **m_framebuffer);
    gl.bindVertexArray(m_vaoID);

    for (std::size_t programIdx = 0; programIdx < m_shaderProgramList.size(); programIdx++)
    {
        gl.useProgram(m_shaderProgramList[programIdx]->getHandle());

        this->toggleDecode(m_shaderProgramList[programIdx]->getUniformDataList());

        for (int textureSourceIdx = 0; textureSourceIdx < (int)m_textureSourceList.size(); textureSourceIdx++)
        {
            gl.activeTexture(GL_TEXTURE0 + (glw::GLenum)textureSourceIdx);
            gl.bindTexture(m_textureSourceList[textureSourceIdx]->getGLTargetType(),
                           m_textureSourceList[textureSourceIdx]->getHandle());
            glw::GLuint samplerUniformLocationID =
                gl.getUniformLocation(m_shaderProgramList[programIdx]->getHandle(),
                                      (std::string("uTexture") + de::toString(textureSourceIdx)).c_str());
            TCU_CHECK(samplerUniformLocationID != (glw::GLuint)-1);
            gl.uniform1i(samplerUniformLocationID, (glw::GLenum)textureSourceIdx);
        }

        for (int samplerIdx = 0; samplerIdx < (int)m_samplerList.size(); samplerIdx++)
        {
            if (m_samplerList[samplerIdx]->getIsActive() == true)
            {
                m_samplerList[samplerIdx]->bindToTexture();
            }
        }

        if (m_shaderProgramList[programIdx]->getBlendRequired() == true)
        {
            gl.enable(GL_BLEND);
            gl.blendEquation(GL_MAX);
            gl.blendFunc(GL_ONE, GL_ONE);
        }
        else
        {
            gl.disable(GL_BLEND);
        }

        gl.drawArrays(GL_TRIANGLES, 0, 6);

        // reset sRGB decode state on textures
        this->toggleDecode(m_shaderProgramList[programIdx]->getUniformDataList());
    }

    for (std::size_t textureSourceIdx = 0; textureSourceIdx < m_textureSourceList.size(); textureSourceIdx++)
    {
        gl.bindTexture(m_textureSourceList[textureSourceIdx]->getGLTargetType(), 0);
    }
    gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
    gl.bindVertexArray(0);
    gl.bindBuffer(GL_ARRAY_BUFFER, 0);
}

void DecodeToggledCase::setupTest(void)
{
    // TEST STEPS:
    //    - create and set texture_a to DECODE_SKIP_EXT and texture_b to DECODE_EXT
    //    - create and use two seperate shader programs, program_a and program_b, each using different fragment shaders
    //    - store texture_a and texture_b on GPU
    // FIRST PASS:
    //    - use program_a
    //    - in fragment shader, sample both textures using texture*() and manually perform sRGB to lRGB conversion on texture_a
    //    - in fragment shader, test converted texture_a value with texture_b
    //    - render green image for pass or red for fail
    //    - store result in a color attachement 0
    // TOGGLE STAGE
    //    - during rendering, toggle texture_a from DECODE_SKIP_EXT to DECODE_EXT
    // SECOND PASS:
    //    - use program_b
    //    - in fragment shader, sample both textures using texture*() and manually perform equality check. Both should be linear
    //    - blend first pass result with second pass. Anything but a green result equals fail

    ComparisonFunction srgbToLinearFunction("srgbToLinearCheck", FUNCTIONPARAMETERS_TWO,
                                            getFunctionDefinitionSRGBToLinearCheck());
    ComparisonFunction colorsEqualFunction("colorsEqualCheck", FUNCTIONPARAMETERS_TWO,
                                           getFunctionDefinitionEqualCheck());

    FragmentShaderParameters shaderParametersA(SHADEROUTPUTS_ONE, SHADERUNIFORMS_TWO, &srgbToLinearFunction,
                                               BLENDING_NOT_REQUIRED, TOGGLING_NOT_REQUIRED);
    FragmentShaderParameters shaderParametersB(SHADEROUTPUTS_ONE, SHADERUNIFORMS_TWO, &colorsEqualFunction,
                                               BLENDING_REQUIRED, TOGGLING_REQUIRED);

    // need to specify which texture uniform to toggle DECODE_EXT/SKIP_DECODE_EXT
    shaderParametersB.uniformsToToggle.push_back("uTexture0");

    this->addTexture(TEXTURETYPE_2D, TestDimensions::WIDTH, TestDimensions::HEIGHT, getColorReferenceLinear(),
                     tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::LINEAR,
                     tcu::Sampler::LINEAR, SRGBDECODE_SKIP_DECODE);

    this->addTexture(TEXTURETYPE_2D, TestDimensions::WIDTH, TestDimensions::HEIGHT, getColorReferenceLinear(),
                     tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::LINEAR,
                     tcu::Sampler::LINEAR, SRGBDECODE_DECODE);

    this->addShaderProgram(shaderParametersA);
    this->addShaderProgram(shaderParametersB);

    this->setSamplingLocations(TestSamplingPositions::X_POS, TestSamplingPositions::Y_POS);
    this->setSamplingGroup(SHADERSAMPLINGGROUP_TEXTURE);
}

bool DecodeToggledCase::verifyResult(void)
{
    tcu::TestLog &log        = m_context.getTestContext().getLog();
    const int resultColorIdx = 0;
    std::vector<tcu::Vec4> pixelResultList;

    this->readResultTextures();
    this->storeResultPixels(pixelResultList);
    this->logColor(std::string("Expected color: "), resultColorIdx, getColorGreenPass());

    //    result color is either green (pass) or fail (red)
    if (tcu::boolAll(tcu::equal(pixelResultList[resultColorIdx], getColorGreenPass())))
    {
        log << tcu::TestLog::Message << std::string("returned pass color from GPU") << tcu::TestLog::EndMessage;
        return true;
    }
    else
    {
        log << tcu::TestLog::Message << std::string("returned fail color from GPU") << tcu::TestLog::EndMessage;
        return false;
    }
}

class DecodeMultipleTexturesCase : public SRGBTestCase
{
public:
    DecodeMultipleTexturesCase(Context &context, const char *name, const char *description,
                               const tcu::TextureFormat internalFormat)
        : SRGBTestCase(context, name, description, internalFormat)
    {
    }

    ~DecodeMultipleTexturesCase(void)
    {
    }

    void setupTest(void);
    bool verifyResult(void);
};

void DecodeMultipleTexturesCase::setupTest(void)
{
    // TEST STEPS:
    //    - create and set texture_a to DECODE_SKIP_EXT and texture_b to DECODE_EXT
    //    - upload textures to the GPU and bind to seperate uniform variables
    //    - sample both textures using texture*()
    //    - read texel values back to the CPU
    //    - compare the texel values, both should be different from each other

    FragmentShaderParameters shaderParameters(SHADEROUTPUTS_TWO, SHADERUNIFORMS_TWO, NULL, BLENDING_NOT_REQUIRED,
                                              TOGGLING_NOT_REQUIRED);

    this->addTexture(TEXTURETYPE_2D, TestDimensions::WIDTH, TestDimensions::HEIGHT, getColorReferenceLinear(),
                     tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::LINEAR,
                     tcu::Sampler::LINEAR, SRGBDECODE_SKIP_DECODE);

    this->addTexture(TEXTURETYPE_2D, TestDimensions::WIDTH, TestDimensions::HEIGHT, getColorReferenceLinear(),
                     tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::LINEAR,
                     tcu::Sampler::LINEAR, SRGBDECODE_DECODE);

    this->addShaderProgram(shaderParameters);

    this->setSamplingLocations(TestSamplingPositions::X_POS, TestSamplingPositions::Y_POS);
    this->setSamplingGroup(SHADERSAMPLINGGROUP_TEXTURE);
}

bool DecodeMultipleTexturesCase::verifyResult(void)
{
    tcu::TestLog &log        = m_context.getTestContext().getLog();
    const int resultColorIdx = 0;
    std::vector<tcu::Vec4> pixelResultList;
    tcu::Vec4 pixelExpected0;
    tcu::Vec4 pixelExpected1;

    this->readResultTextures();
    this->storeResultPixels(pixelResultList);

    pixelExpected0 = this->formatReferenceColor(getColorReferenceSRGB());
    pixelExpected1 = this->formatReferenceColor(getColorReferenceLinear());

    this->logColor(std::string("Expected color: "), resultColorIdx, pixelExpected0);
    this->logColor(std::string("Expected color: "), resultColorIdx + 1, pixelExpected1);

    //    check if the two textures have different values i.e. uTexture0 = sRGB and uTexture1 = linear
    if (!(tcu::boolAll(tcu::equal(pixelResultList[resultColorIdx], pixelResultList[resultColorIdx + 1]))))
    {
        log << tcu::TestLog::Message << std::string("texel values are different") << tcu::TestLog::EndMessage;
        return true;
    }
    else
    {
        log << tcu::TestLog::Message << std::string("texel values are equal") << tcu::TestLog::EndMessage;
        return false;
    }
}

class DecodeSamplerCase : public SRGBTestCase
{
public:
    DecodeSamplerCase(Context &context, const char *name, const char *description,
                      const tcu::TextureFormat internalFormat)
        : SRGBTestCase(context, name, description, internalFormat)
    {
    }

    ~DecodeSamplerCase(void)
    {
    }

    void setupTest(void);
    bool verifyResult(void);
};

void DecodeSamplerCase::setupTest(void)
{
    // TEST STEPS:
    //    - create and set texture_a to DECODE_SKIP_EXT
    //    - upload texture to the GPU and bind to sampler
    //    - sample texture using texture*()
    //    - read texel values back to the CPU
    //    - compare the texel values, should be in sampler format (linear)

    FragmentShaderParameters shaderParameters(SHADEROUTPUTS_ONE, SHADERUNIFORMS_ONE, NULL, BLENDING_NOT_REQUIRED,
                                              TOGGLING_NOT_REQUIRED);

    this->addTexture(TEXTURETYPE_2D, TestDimensions::WIDTH, TestDimensions::HEIGHT, getColorReferenceLinear(),
                     tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::LINEAR,
                     tcu::Sampler::LINEAR, SRGBDECODE_SKIP_DECODE);

    this->addSampler(tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::MIRRORED_REPEAT_GL, tcu::Sampler::LINEAR,
                     tcu::Sampler::LINEAR, SRGBDECODE_DECODE);

    this->addShaderProgram(shaderParameters);

    this->bindSamplerToTexture(0, 0, GL_TEXTURE0);
    this->activateSampler(0, true);

    this->setSamplingLocations(TestSamplingPositions::X_POS, TestSamplingPositions::Y_POS);
    this->setSamplingGroup(SHADERSAMPLINGGROUP_TEXTURE);
}

bool DecodeSamplerCase::verifyResult(void)
{
    tcu::TestLog &log        = m_context.getTestContext().getLog();
    const int resultColorIdx = 0;
    std::vector<tcu::Vec4> pixelResultList;
    tcu::Vec4 pixelConverted;
    tcu::Vec4 pixelReference;
    tcu::Vec4 pixelExpected;

    this->readResultTextures();
    this->storeResultPixels(pixelResultList);

    pixelConverted = tcu::linearToSRGB(pixelResultList[resultColorIdx]);
    pixelReference = this->formatReferenceColor(getColorReferenceSRGB());
    pixelExpected  = this->formatReferenceColor(getColorReferenceLinear());

    this->logColor(std::string("Expected color: "), resultColorIdx, pixelExpected);

    //    texture was rendered using a sampler object with setting DECODE_EXT, therefore, results should be linear
    if ((tcu::boolAll(tcu::lessThan(tcu::abs(pixelConverted - pixelReference), m_epsilonError))) ||
        (tcu::boolAll(tcu::equal(pixelConverted, pixelReference))))
    {
        log << tcu::TestLog::Message << std::string("linear as expected") << tcu::TestLog::EndMessage;
        return true;
    }
    else
    {
        log << tcu::TestLog::Message << std::string("not linear as expected") << tcu::TestLog::EndMessage;
        return false;
    }
}

} // namespace

SRGBDecodeTests::SRGBDecodeTests(Context &context) : TestCaseGroup(context, "skip_decode", "sRGB skip decode tests")
{
}

SRGBDecodeTests::~SRGBDecodeTests(void)
{
}

void SRGBDecodeTests::init(void)
{
    const TestGroupConfig testGroupConfigList[] = {
        TestGroupConfig("srgba8", "srgb decode tests using srgba internal format",
                        tcu::TextureFormat(tcu::TextureFormat::sRGBA, tcu::TextureFormat::UNORM_INT8)),
        TestGroupConfig("srg8", "srgb decode tests using srg8 internal format",
                        tcu::TextureFormat(tcu::TextureFormat::sRG, tcu::TextureFormat::UNORM_INT8)),
        TestGroupConfig("sr8", "srgb decode tests using sr8 internal format",
                        tcu::TextureFormat(tcu::TextureFormat::sR, tcu::TextureFormat::UNORM_INT8))};

    // create groups for all desired internal formats, adding test cases to each
    for (std::size_t idx = 0; idx < DE_LENGTH_OF_ARRAY(testGroupConfigList); idx++)
    {
        tcu::TestCaseGroup *const testGroup =
            new tcu::TestCaseGroup(m_testCtx, testGroupConfigList[idx].name, testGroupConfigList[idx].description);
        tcu::TestNode::addChild(testGroup);

        testGroup->addChild(new TextureDecodeSkippedCase(
            m_context, "skipped", "testing for sRGB color values with sRGB texture decoding skipped",
            testGroupConfigList[idx].internalFormat));
        testGroup->addChild(new TextureDecodeEnabledCase(
            m_context, "enabled", "testing for linear color values with sRGB texture decoding enabled",
            testGroupConfigList[idx].internalFormat));
        testGroup->addChild(new TexelFetchDecodeSkippedcase(
            m_context, "texel_fetch", "testing for linear color values with sRGB texture decoding skipped",
            testGroupConfigList[idx].internalFormat));
        testGroup->addChild(new GPUConversionDecodeEnabledCase(
            m_context, "conversion_gpu", "sampling linear values and performing conversion on the gpu",
            testGroupConfigList[idx].internalFormat));
        testGroup->addChild(new DecodeToggledCase(m_context, "toggled", "toggle the sRGB decoding between draw calls",
                                                  testGroupConfigList[idx].internalFormat));
        testGroup->addChild(new DecodeMultipleTexturesCase(
            m_context, "multiple_textures", "upload multiple textures with different sRGB decode values and sample",
            testGroupConfigList[idx].internalFormat));
        testGroup->addChild(new DecodeSamplerCase(m_context, "using_sampler",
                                                  "testing that sampler object takes priority over texture state",
                                                  testGroupConfigList[idx].internalFormat));
    }
}

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