/*-------------------------------------------------------------------------
 * 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 Stencil texturing tests.
 *//*--------------------------------------------------------------------*/

#include "es31fStencilTexturingTests.hpp"

#include "gluStrUtil.hpp"
#include "gluObjectWrapper.hpp"
#include "gluRenderContext.hpp"
#include "gluShaderProgram.hpp"
#include "gluDrawUtil.hpp"
#include "gluPixelTransfer.hpp"
#include "gluTextureUtil.hpp"
#include "gluContextInfo.hpp"

#include "glsTextureTestUtil.hpp"

#include "tcuVector.hpp"
#include "tcuTexture.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuTestLog.hpp"
#include "tcuTexLookupVerifier.hpp"

#include "glwFunctions.hpp"
#include "glwEnums.hpp"

#include "deStringUtil.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{

using std::string;
using std::vector;
using tcu::IVec4;
using tcu::TestLog;
using tcu::TextureFormat;
using tcu::TextureLevel;
using tcu::Vec2;
using tcu::Vec4;

namespace
{

static void genTestRects(vector<IVec4> &rects, int width, int height)
{
    int curWidth  = width;
    int curHeight = height;
    int ndx       = 0;

    for (;;)
    {
        rects.push_back(IVec4(width - curWidth, height - curHeight, curWidth, curHeight));

        DE_ASSERT(curWidth >= 1 && curHeight >= 1);
        if (curWidth == 1 && curHeight == 1)
            break;
        else if (curHeight > 1 && ((ndx % 2) == 0 || curWidth == 1))
            curHeight -= 1;
        else
            curWidth -= 1;

        ndx += 1;
    }
}

static void rectsToTriangles(const vector<IVec4> &rects, int width, int height, vector<Vec2> &positions,
                             vector<uint16_t> &indices)
{
    const float w = float(width);
    const float h = float(height);

    positions.resize(rects.size() * 4);
    indices.resize(rects.size() * 6);

    for (int rectNdx = 0; rectNdx < (int)rects.size(); rectNdx++)
    {
        const int rx = rects[rectNdx].x();
        const int ry = rects[rectNdx].y();
        const int rw = rects[rectNdx].z();
        const int rh = rects[rectNdx].w();

        const float x0 = float(rx * 2) / w - 1.0f;
        const float x1 = float((rx + rw) * 2) / w - 1.0f;
        const float y0 = float(ry * 2) / h - 1.0f;
        const float y1 = float((ry + rh) * 2) / h - 1.0f;

        positions[rectNdx * 4 + 0] = Vec2(x0, y0);
        positions[rectNdx * 4 + 1] = Vec2(x1, y0);
        positions[rectNdx * 4 + 2] = Vec2(x0, y1);
        positions[rectNdx * 4 + 3] = Vec2(x1, y1);

        indices[rectNdx * 6 + 0] = (uint16_t)(rectNdx * 4 + 0);
        indices[rectNdx * 6 + 1] = (uint16_t)(rectNdx * 4 + 1);
        indices[rectNdx * 6 + 2] = (uint16_t)(rectNdx * 4 + 2);
        indices[rectNdx * 6 + 3] = (uint16_t)(rectNdx * 4 + 2);
        indices[rectNdx * 6 + 4] = (uint16_t)(rectNdx * 4 + 1);
        indices[rectNdx * 6 + 5] = (uint16_t)(rectNdx * 4 + 3);
    }
}

static void drawTestPattern(const glu::RenderContext &renderCtx, int width, int height)
{
    const glu::ShaderProgram program(renderCtx, glu::ProgramSources()
                                                    << glu::VertexSource("#version 300 es\n"
                                                                         "in highp vec4 a_position;\n"
                                                                         "void main (void)\n"
                                                                         "{\n"
                                                                         "    gl_Position = a_position;\n"
                                                                         "}\n")
                                                    << glu::FragmentSource("#version 300 es\n"
                                                                           "void main (void) {}\n"));

    const glw::Functions &gl = renderCtx.getFunctions();
    vector<IVec4> rects;
    vector<Vec2> positions;
    vector<uint16_t> indices;

    if (!program.isOk())
        throw tcu::TestError("Compile failed");

    gl.useProgram(program.getProgram());
    gl.viewport(0, 0, width, height);
    gl.clear(GL_STENCIL_BUFFER_BIT);
    gl.enable(GL_STENCIL_TEST);
    gl.stencilOp(GL_KEEP, GL_KEEP, GL_INCR_WRAP);
    gl.stencilFunc(GL_ALWAYS, 0, ~0u);
    GLU_EXPECT_NO_ERROR(gl.getError(), "State setup failed");

    genTestRects(rects, width, height);
    rectsToTriangles(rects, width, height, positions, indices);

    {
        const glu::VertexArrayBinding posBinding =
            glu::va::Float("a_position", 2, (int)positions.size(), 0, positions[0].getPtr());
        glu::draw(renderCtx, program.getProgram(), 1, &posBinding,
                  glu::pr::Triangles((int)indices.size(), &indices[0]));
    }

    gl.disable(GL_STENCIL_TEST);
}

static void renderTestPatternReference(const tcu::PixelBufferAccess &dst)
{
    const int stencilBits =
        tcu::getTextureFormatBitDepth(tcu::getEffectiveDepthStencilAccess(dst, tcu::Sampler::MODE_STENCIL).getFormat())
            .x();
    const uint32_t stencilMask = (1u << stencilBits) - 1u;
    vector<IVec4> rects;

    DE_ASSERT(dst.getFormat().order == TextureFormat::S || dst.getFormat().order == TextureFormat::DS);

    // clear depth and stencil
    if (dst.getFormat().order == TextureFormat::DS)
        tcu::clearDepth(dst, 0.0f);
    tcu::clearStencil(dst, 0u);

    genTestRects(rects, dst.getWidth(), dst.getHeight());

    for (vector<IVec4>::const_iterator rectIter = rects.begin(); rectIter != rects.end(); ++rectIter)
    {
        const int x0 = rectIter->x();
        const int y0 = rectIter->y();
        const int x1 = x0 + rectIter->z();
        const int y1 = y0 + rectIter->w();

        for (int y = y0; y < y1; y++)
        {
            for (int x = x0; x < x1; x++)
            {
                const int oldVal = dst.getPixStencil(x, y);
                const int newVal = (oldVal + 1) & stencilMask;

                dst.setPixStencil(newVal, x, y);
            }
        }
    }
}

static void blitStencilToColor2D(const glu::RenderContext &renderCtx, uint32_t srcTex, int width, int height)
{
    const glu::ShaderProgram program(
        renderCtx, glu::ProgramSources() << glu::VertexSource("#version 300 es\n"
                                                              "in highp vec4 a_position;\n"
                                                              "in highp vec2 a_texCoord;\n"
                                                              "out highp vec2 v_texCoord;\n"
                                                              "void main (void)\n"
                                                              "{\n"
                                                              "    gl_Position = a_position;\n"
                                                              "    v_texCoord = a_texCoord;\n"
                                                              "}\n")
                                         << glu::FragmentSource("#version 300 es\n"
                                                                "uniform highp usampler2D u_sampler;\n"
                                                                "in highp vec2 v_texCoord;\n"
                                                                "layout(location = 0) out highp uint o_stencil;\n"
                                                                "void main (void)\n"
                                                                "{\n"
                                                                "    o_stencil = texture(u_sampler, v_texCoord).x;\n"
                                                                "}\n"));

    const float positions[]                      = {-1.0f, -1.0f, +1.0f, -1.0f, -1.0f, +1.0f, +1.0f, +1.0f};
    const float texCoord[]                       = {0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f};
    const glu::VertexArrayBinding vertexArrays[] = {glu::va::Float("a_position", 2, 4, 0, &positions[0]),
                                                    glu::va::Float("a_texCoord", 2, 4, 0, &texCoord[0])};
    const uint8_t indices[]                      = {0, 1, 2, 2, 1, 3};

    const glw::Functions &gl = renderCtx.getFunctions();

    if (!program.isOk())
        throw tcu::TestError("Compile failed");

    gl.activeTexture(GL_TEXTURE0);
    gl.bindTexture(GL_TEXTURE_2D, srcTex);
    gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    gl.texParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
    GLU_EXPECT_NO_ERROR(gl.getError(), "Texture state setup failed");

    gl.useProgram(program.getProgram());
    gl.uniform1i(gl.getUniformLocation(program.getProgram(), "u_sampler"), 0);
    GLU_EXPECT_NO_ERROR(gl.getError(), "Program setup failed");

    gl.viewport(0, 0, width, height);
    glu::draw(renderCtx, program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
              glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
}

static void blitStencilToColor2DArray(const glu::RenderContext &renderCtx, uint32_t srcTex, int width, int height,
                                      int level)
{
    const glu::ShaderProgram program(
        renderCtx, glu::ProgramSources() << glu::VertexSource("#version 300 es\n"
                                                              "in highp vec4 a_position;\n"
                                                              "in highp vec3 a_texCoord;\n"
                                                              "out highp vec3 v_texCoord;\n"
                                                              "void main (void)\n"
                                                              "{\n"
                                                              "    gl_Position = a_position;\n"
                                                              "    v_texCoord = a_texCoord;\n"
                                                              "}\n")
                                         << glu::FragmentSource("#version 300 es\n"
                                                                "uniform highp usampler2DArray u_sampler;\n"
                                                                "in highp vec3 v_texCoord;\n"
                                                                "layout(location = 0) out highp uint o_stencil;\n"
                                                                "void main (void)\n"
                                                                "{\n"
                                                                "    o_stencil = texture(u_sampler, v_texCoord).x;\n"
                                                                "}\n"));

    const float positions[]                      = {-1.0f, -1.0f, +1.0f, -1.0f, -1.0f, +1.0f, +1.0f, +1.0f};
    const float texCoord[]                       = {0.0f, 0.0f, float(level), 1.0f, 0.0f, float(level),
                                                    0.0f, 1.0f, float(level), 1.0f, 1.0f, float(level)};
    const glu::VertexArrayBinding vertexArrays[] = {glu::va::Float("a_position", 2, 4, 0, &positions[0]),
                                                    glu::va::Float("a_texCoord", 3, 4, 0, &texCoord[0])};
    const uint8_t indices[]                      = {0, 1, 2, 2, 1, 3};

    const glw::Functions &gl = renderCtx.getFunctions();

    if (!program.isOk())
        throw tcu::TestError("Compile failed");

    gl.activeTexture(GL_TEXTURE0);
    gl.bindTexture(GL_TEXTURE_2D_ARRAY, srcTex);
    gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    gl.texParameteri(GL_TEXTURE_2D_ARRAY, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
    GLU_EXPECT_NO_ERROR(gl.getError(), "Texture state setup failed");

    gl.useProgram(program.getProgram());
    gl.uniform1i(gl.getUniformLocation(program.getProgram(), "u_sampler"), 0);
    GLU_EXPECT_NO_ERROR(gl.getError(), "Program setup failed");

    gl.viewport(0, 0, width, height);
    glu::draw(renderCtx, program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
              glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
}

static void blitStencilToColorCube(const glu::RenderContext &renderCtx, uint32_t srcTex, const float *texCoord,
                                   int width, int height)
{
    const glu::ShaderProgram program(
        renderCtx, glu::ProgramSources() << glu::VertexSource("#version 300 es\n"
                                                              "in highp vec4 a_position;\n"
                                                              "in highp vec3 a_texCoord;\n"
                                                              "out highp vec3 v_texCoord;\n"
                                                              "void main (void)\n"
                                                              "{\n"
                                                              "    gl_Position = a_position;\n"
                                                              "    v_texCoord = a_texCoord;\n"
                                                              "}\n")
                                         << glu::FragmentSource(
                                                "#version 300 es\n"
                                                "uniform highp usamplerCube u_sampler;\n"
                                                "in highp vec3 v_texCoord;\n"
                                                "layout(location = 0) out highp vec4 o_color;\n"
                                                "void main (void)\n"
                                                "{\n"
                                                "    o_color.x = float(texture(u_sampler, v_texCoord).x) / 255.0;\n"
                                                "    o_color.yzw = vec3(0.0, 0.0, 1.0);\n"
                                                "}\n"));

    const float positions[]                      = {-1.0f, -1.0f, -1.0f, +1.0f, +1.0f, -1.0f, +1.0f, +1.0f};
    const glu::VertexArrayBinding vertexArrays[] = {glu::va::Float("a_position", 2, 4, 0, &positions[0]),
                                                    glu::va::Float("a_texCoord", 3, 4, 0, texCoord)};
    const uint8_t indices[]                      = {0, 1, 2, 2, 1, 3};

    const glw::Functions &gl = renderCtx.getFunctions();

    if (!program.isOk())
        throw tcu::TestError("Compile failed");

    gl.activeTexture(GL_TEXTURE0);
    gl.bindTexture(GL_TEXTURE_CUBE_MAP, srcTex);
    gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    gl.texParameteri(GL_TEXTURE_CUBE_MAP, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
    GLU_EXPECT_NO_ERROR(gl.getError(), "Texture state setup failed");

    gl.useProgram(program.getProgram());
    gl.uniform1i(gl.getUniformLocation(program.getProgram(), "u_sampler"), 0);
    GLU_EXPECT_NO_ERROR(gl.getError(), "Program setup failed");

    gl.viewport(0, 0, width, height);
    glu::draw(renderCtx, program.getProgram(), DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0],
              glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));
}

static inline tcu::ConstPixelBufferAccess stencilToRedAccess(const tcu::ConstPixelBufferAccess &access)
{
    DE_ASSERT(access.getFormat() == TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8));
    return tcu::ConstPixelBufferAccess(TextureFormat(TextureFormat::R, TextureFormat::UNSIGNED_INT8), access.getSize(),
                                       access.getPitch(), access.getDataPtr());
}

static bool compareStencilToRed(tcu::TestLog &log, const tcu::ConstPixelBufferAccess &stencilRef,
                                const tcu::ConstPixelBufferAccess &result)
{
    const int maxPrints = 10;
    int numFailed       = 0;

    DE_ASSERT(stencilRef.getFormat().order == TextureFormat::S);
    DE_ASSERT(stencilRef.getWidth() == result.getWidth() && stencilRef.getHeight() == result.getHeight());

    for (int y = 0; y < stencilRef.getHeight(); y++)
    {
        for (int x = 0; x < stencilRef.getWidth(); x++)
        {
            const int ref = stencilRef.getPixStencil(x, y);
            const int res = result.getPixelInt(x, y).x();

            if (ref != res)
            {
                if (numFailed < maxPrints)
                    log << TestLog::Message << "ERROR: Expected " << ref << ", got " << res << " at (" << x << ", " << y
                        << ")" << TestLog::EndMessage;
                else if (numFailed == maxPrints)
                    log << TestLog::Message << "..." << TestLog::EndMessage;

                numFailed += 1;
            }
        }
    }

    log << TestLog::Message << "Found " << numFailed << " faulty pixels, comparison "
        << (numFailed == 0 ? "passed." : "FAILED!") << TestLog::EndMessage;

    log << TestLog::ImageSet("ComparisonResult", "Image comparison result")
        << TestLog::Image("Result", "Result stencil buffer", result);

    if (numFailed > 0)
        log << TestLog::Image("Reference", "Reference stencil buffer", stencilToRedAccess(stencilRef));

    log << TestLog::EndImageSet;

    return numFailed == 0;
}

static bool compareRedChannel(tcu::TestLog &log, const tcu::ConstPixelBufferAccess &result, int reference)
{
    const int maxPrints = 10;
    int numFailed       = 0;

    for (int y = 0; y < result.getHeight(); y++)
    {
        for (int x = 0; x < result.getWidth(); x++)
        {
            const int res = result.getPixelInt(x, y).x();

            if (reference != res)
            {
                if (numFailed < maxPrints)
                    log << TestLog::Message << "ERROR: Expected " << reference << ", got " << res << " at (" << x
                        << ", " << y << ")" << TestLog::EndMessage;
                else if (numFailed == maxPrints)
                    log << TestLog::Message << "..." << TestLog::EndMessage;

                numFailed += 1;
            }
        }
    }

    log << TestLog::Message << "Found " << numFailed << " faulty pixels, comparison "
        << (numFailed == 0 ? "passed." : "FAILED!") << TestLog::EndMessage;

    log << TestLog::ImageSet("ComparisonResult", "Image comparison result")
        << TestLog::Image("Result", "Result stencil buffer", result);

    log << TestLog::EndImageSet;

    return numFailed == 0;
}

static void stencilToUnorm8(const tcu::TextureCube &src, tcu::TextureCube &dst)
{
    for (int levelNdx = 0; levelNdx < src.getNumLevels(); levelNdx++)
    {
        for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
        {
            const tcu::CubeFace face = tcu::CubeFace(faceNdx);

            if (!src.isLevelEmpty(face, levelNdx))
            {
                dst.allocLevel(face, levelNdx);

                const tcu::ConstPixelBufferAccess srcLevel = src.getLevelFace(levelNdx, face);
                const tcu::PixelBufferAccess dstLevel      = dst.getLevelFace(levelNdx, face);

                for (int y = 0; y < src.getSize(); y++)
                    for (int x = 0; x < src.getSize(); x++)
                        dstLevel.setPixel(Vec4(float(srcLevel.getPixStencil(x, y)) / 255.f, 0.f, 0.f, 1.f), x, y);
            }
        }
    }
}

static void checkDepthStencilFormatSupport(Context &context, uint32_t format)
{
    if (format == GL_STENCIL_INDEX8)
    {
        const char *reqExt           = "GL_OES_texture_stencil8";
        glu::ContextType contextType = context.getRenderContext().getType();
        if (!glu::contextSupports(contextType, glu::ApiType::es(3, 2)) &&
            !glu::contextSupports(contextType, glu::ApiType::core(4, 5)) &&
            !context.getContextInfo().isExtensionSupported(reqExt))
            throw tcu::NotSupportedError(glu::getTextureFormatStr(format).toString() + " requires " + reqExt);
    }
    else
    {
        DE_ASSERT(format == GL_DEPTH32F_STENCIL8 || format == GL_DEPTH24_STENCIL8);
    }
}

static void checkFramebufferStatus(const glw::Functions &gl)
{
    const uint32_t status = gl.checkFramebufferStatus(GL_FRAMEBUFFER);

    if (status == GL_FRAMEBUFFER_UNSUPPORTED)
        throw tcu::NotSupportedError("Unsupported framebuffer configuration");
    else if (status != GL_FRAMEBUFFER_COMPLETE)
        throw tcu::TestError("Incomplete framebuffer: " + glu::getFramebufferStatusStr(status).toString());
}

class UploadTex2DCase : public TestCase
{
public:
    UploadTex2DCase(Context &context, const char *name, uint32_t format)
        : TestCase(context, name, glu::getTextureFormatName(format))
        , m_format(format)
    {
    }

    IterateResult iterate(void)
    {
        const glu::RenderContext &renderCtx = m_context.getRenderContext();
        const glw::Functions &gl            = renderCtx.getFunctions();
        const int width                     = 129;
        const int height                    = 113;
        glu::Framebuffer fbo(renderCtx);
        glu::Renderbuffer colorBuf(renderCtx);
        glu::Texture depthStencilTex(renderCtx);
        TextureLevel uploadLevel(glu::mapGLInternalFormat(m_format), width, height);
        TextureLevel readLevel(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), width, height);
        TextureLevel stencilOnlyLevel(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), width, height);

        checkDepthStencilFormatSupport(m_context, m_format);

        renderTestPatternReference(uploadLevel);
        renderTestPatternReference(stencilOnlyLevel);

        gl.bindTexture(GL_TEXTURE_2D, *depthStencilTex);
        gl.texStorage2D(GL_TEXTURE_2D, 1, m_format, width, height);
        glu::texSubImage2D(renderCtx, GL_TEXTURE_2D, 0, 0, 0, uploadLevel);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading texture data failed");

        gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
        gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, width, height);

        gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
        gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
        checkFramebufferStatus(gl);

        blitStencilToColor2D(renderCtx, *depthStencilTex, width, height);
        glu::readPixels(renderCtx, 0, 0, readLevel);

        {
            const bool compareOk = compareStencilToRed(m_testCtx.getLog(), stencilOnlyLevel, readLevel);
            m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                    compareOk ? "Pass" : "Image comparison failed");
        }

        return STOP;
    }

private:
    const uint32_t m_format;
};

class UploadTex2DArrayCase : public TestCase
{
public:
    UploadTex2DArrayCase(Context &context, const char *name, uint32_t format)
        : TestCase(context, name, glu::getTextureFormatName(format))
        , m_format(format)
    {
    }

    IterateResult iterate(void)
    {
        const glu::RenderContext &renderCtx = m_context.getRenderContext();
        const glw::Functions &gl            = renderCtx.getFunctions();
        const int width                     = 41;
        const int height                    = 13;
        const int levels                    = 7;
        const int ptrnLevel                 = 3;
        glu::Framebuffer fbo(renderCtx);
        glu::Renderbuffer colorBuf(renderCtx);
        glu::Texture depthStencilTex(renderCtx);
        TextureLevel uploadLevel(glu::mapGLInternalFormat(m_format), width, height, levels);

        checkDepthStencilFormatSupport(m_context, m_format);

        for (int levelNdx = 0; levelNdx < levels; levelNdx++)
        {
            const tcu::PixelBufferAccess levelAccess =
                tcu::getSubregion(uploadLevel.getAccess(), 0, 0, levelNdx, width, height, 1);

            if (levelNdx == ptrnLevel)
                renderTestPatternReference(levelAccess);
            else
                tcu::clearStencil(levelAccess, levelNdx);
        }

        gl.bindTexture(GL_TEXTURE_2D_ARRAY, *depthStencilTex);
        gl.texStorage3D(GL_TEXTURE_2D_ARRAY, 1, m_format, width, height, levels);
        glu::texSubImage3D(renderCtx, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, uploadLevel);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading texture data failed");

        gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
        gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, width, height);

        gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
        gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
        checkFramebufferStatus(gl);

        {
            TextureLevel readLevel(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), width, height);
            bool allLevelsOk = true;

            for (int levelNdx = 0; levelNdx < levels; levelNdx++)
            {
                tcu::ScopedLogSection section(m_testCtx.getLog(), "Level" + de::toString(levelNdx),
                                              "Level " + de::toString(levelNdx));
                bool levelOk;

                blitStencilToColor2DArray(renderCtx, *depthStencilTex, width, height, levelNdx);
                glu::readPixels(renderCtx, 0, 0, readLevel);

                if (levelNdx == ptrnLevel)
                {
                    TextureLevel reference(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), width,
                                           height);
                    renderTestPatternReference(reference);

                    levelOk = compareStencilToRed(m_testCtx.getLog(), reference, readLevel);
                }
                else
                    levelOk = compareRedChannel(m_testCtx.getLog(), readLevel, levelNdx);

                if (!levelOk)
                {
                    allLevelsOk = false;
                    break;
                }
            }

            m_testCtx.setTestResult(allLevelsOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                    allLevelsOk ? "Pass" : "Image comparison failed");
        }

        return STOP;
    }

private:
    const uint32_t m_format;
};

class UploadTexCubeCase : public TestCase
{
public:
    UploadTexCubeCase(Context &context, const char *name, uint32_t format)
        : TestCase(context, name, glu::getTextureFormatName(format))
        , m_format(format)
    {
    }

    IterateResult iterate(void)
    {
        const glu::RenderContext &renderCtx = m_context.getRenderContext();
        const glw::Functions &gl            = renderCtx.getFunctions();
        const int size                      = 64;
        const int renderWidth               = 128;
        const int renderHeight              = 128;
        vector<float> texCoord;
        glu::Framebuffer fbo(renderCtx);
        glu::Renderbuffer colorBuf(renderCtx);
        glu::Texture depthStencilTex(renderCtx);
        tcu::TextureCube texData(glu::mapGLInternalFormat(m_format), size);
        tcu::TextureLevel result(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8), renderWidth,
                                 renderHeight);

        checkDepthStencilFormatSupport(m_context, m_format);

        for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
        {
            const tcu::CubeFace face = tcu::CubeFace(faceNdx);
            const int stencilVal     = 42 * faceNdx;

            texData.allocLevel(face, 0);
            tcu::clearStencil(texData.getLevelFace(0, face), stencilVal);
        }

        glu::TextureTestUtil::computeQuadTexCoordCube(texCoord, tcu::CUBEFACE_NEGATIVE_X, Vec2(-1.5f, -1.3f),
                                                      Vec2(1.3f, 1.4f));

        gl.bindTexture(GL_TEXTURE_CUBE_MAP, *depthStencilTex);
        gl.texStorage2D(GL_TEXTURE_CUBE_MAP, 1, m_format, size, size);

        for (int faceNdx = 0; faceNdx < tcu::CUBEFACE_LAST; faceNdx++)
            glu::texSubImage2D(renderCtx, glu::getGLCubeFace(tcu::CubeFace(faceNdx)), 0, 0, 0,
                               texData.getLevelFace(0, tcu::CubeFace(faceNdx)));

        GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading texture data failed");

        gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
        gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, renderWidth, renderHeight);

        gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
        gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
        checkFramebufferStatus(gl);

        blitStencilToColorCube(renderCtx, *depthStencilTex, &texCoord[0], renderWidth, renderHeight);
        glu::readPixels(renderCtx, 0, 0, result);

        {
            using namespace glu::TextureTestUtil;

            tcu::TextureCube redTex(TextureFormat(TextureFormat::R, TextureFormat::UNORM_INT8), size);
            const ReferenceParams sampleParams(TEXTURETYPE_CUBE,
                                               tcu::Sampler(tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::CLAMP_TO_EDGE,
                                                            tcu::Sampler::CLAMP_TO_EDGE, tcu::Sampler::NEAREST,
                                                            tcu::Sampler::NEAREST));
            tcu::LookupPrecision lookupPrec;
            tcu::LodPrecision lodPrec;
            bool compareOk;

            lookupPrec.colorMask      = tcu::BVec4(true, true, true, true);
            lookupPrec.colorThreshold = tcu::computeFixedPointThreshold(IVec4(8, 8, 8, 8));
            lookupPrec.coordBits      = tcu::IVec3(22, 22, 22);
            lookupPrec.uvwBits        = tcu::IVec3(5, 5, 0);
            lodPrec.lodBits           = 7;
            lodPrec.derivateBits      = 16;

            stencilToUnorm8(texData, redTex);

            compareOk = verifyTextureResult(m_testCtx, result, redTex, &texCoord[0], sampleParams, lookupPrec, lodPrec,
                                            tcu::PixelFormat(8, 8, 8, 8));

            m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                    compareOk ? "Pass" : "Image comparison failed");
        }

        return STOP;
    }

private:
    const uint32_t m_format;
};

class RenderTex2DCase : public TestCase
{
public:
    RenderTex2DCase(Context &context, const char *name, uint32_t format)
        : TestCase(context, name, glu::getTextureFormatName(format))
        , m_format(format)
    {
    }

    IterateResult iterate(void)
    {
        const glu::RenderContext &renderCtx = m_context.getRenderContext();
        const glw::Functions &gl            = renderCtx.getFunctions();
        const int width                     = 117;
        const int height                    = 193;
        glu::Framebuffer fbo(renderCtx);
        glu::Renderbuffer colorBuf(renderCtx);
        glu::Texture depthStencilTex(renderCtx);
        TextureLevel result(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), width, height);
        TextureLevel reference(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), width, height);

        checkDepthStencilFormatSupport(m_context, m_format);

        gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
        gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, width, height);

        gl.bindTexture(GL_TEXTURE_2D, *depthStencilTex);
        gl.texStorage2D(GL_TEXTURE_2D, 1, m_format, width, height);

        gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
        gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
        gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, *depthStencilTex, 0);
        checkFramebufferStatus(gl);

        drawTestPattern(renderCtx, width, height);

        gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
        checkFramebufferStatus(gl);

        blitStencilToColor2D(renderCtx, *depthStencilTex, width, height);
        glu::readPixels(renderCtx, 0, 0, result.getAccess());

        renderTestPatternReference(reference);

        {
            const bool compareOk = compareStencilToRed(m_testCtx.getLog(), reference, result);
            m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                    compareOk ? "Pass" : "Image comparison failed");
        }

        return STOP;
    }

private:
    const uint32_t m_format;
};

class ClearTex2DCase : public TestCase
{
public:
    ClearTex2DCase(Context &context, const char *name, uint32_t format)
        : TestCase(context, name, glu::getTextureFormatName(format))
        , m_format(format)
    {
    }

    IterateResult iterate(void)
    {
        const glu::RenderContext &renderCtx = m_context.getRenderContext();
        const glw::Functions &gl            = renderCtx.getFunctions();
        const int width                     = 125;
        const int height                    = 117;
        const int cellSize                  = 8;
        glu::Framebuffer fbo(renderCtx);
        glu::Renderbuffer colorBuf(renderCtx);
        glu::Texture depthStencilTex(renderCtx);
        TextureLevel result(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), width, height);
        TextureLevel reference(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), width, height);

        checkDepthStencilFormatSupport(m_context, m_format);

        gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
        gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, width, height);

        gl.bindTexture(GL_TEXTURE_2D, *depthStencilTex);
        gl.texStorage2D(GL_TEXTURE_2D, 1, m_format, width, height);

        gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
        gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
        gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, *depthStencilTex, 0);
        checkFramebufferStatus(gl);

        gl.enable(GL_SCISSOR_TEST);

        for (int y = 0; y < height; y += cellSize)
        {
            for (int x = 0; x < width; x += cellSize)
            {
                const int clearW  = de::min(cellSize, width - x);
                const int clearH  = de::min(cellSize, height - y);
                const int stencil = int((deInt32Hash(x) ^ deInt32Hash(y)) & 0xff);

                gl.clearStencil(stencil);
                gl.scissor(x, y, clearW, clearH);
                gl.clear(GL_STENCIL_BUFFER_BIT);

                tcu::clearStencil(tcu::getSubregion(reference.getAccess(), x, y, clearW, clearH), stencil);
            }
        }

        gl.disable(GL_SCISSOR_TEST);

        gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
        checkFramebufferStatus(gl);

        blitStencilToColor2D(renderCtx, *depthStencilTex, width, height);
        glu::readPixels(renderCtx, 0, 0, result.getAccess());

        {
            const bool compareOk = compareStencilToRed(m_testCtx.getLog(), reference, result);
            m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                    compareOk ? "Pass" : "Image comparison failed");
        }

        return STOP;
    }

private:
    const uint32_t m_format;
};

class CompareModeCase : public TestCase
{
public:
    CompareModeCase(Context &context, const char *name, uint32_t format)
        : TestCase(context, name, glu::getTextureFormatName(format))
        , m_format(format)
    {
    }

    IterateResult iterate(void)
    {
        const glu::RenderContext &renderCtx = m_context.getRenderContext();
        const glw::Functions &gl            = renderCtx.getFunctions();
        const int width                     = 64;
        const int height                    = 64;
        glu::Framebuffer fbo(renderCtx);
        glu::Renderbuffer colorBuf(renderCtx);
        glu::Texture depthStencilTex(renderCtx);
        TextureLevel uploadLevel(glu::mapGLInternalFormat(m_format), width, height);
        TextureLevel readLevel(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), width, height);
        TextureLevel stencilOnlyLevel(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), width, height);

        checkDepthStencilFormatSupport(m_context, m_format);

        m_testCtx.getLog() << TestLog::Message
                           << "NOTE: Texture compare mode has no effect when reading stencil values."
                           << TestLog::EndMessage;

        renderTestPatternReference(uploadLevel);
        renderTestPatternReference(stencilOnlyLevel);

        gl.bindTexture(GL_TEXTURE_2D, *depthStencilTex);
        gl.texStorage2D(GL_TEXTURE_2D, 1, m_format, width, height);
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LESS);
        glu::texSubImage2D(renderCtx, GL_TEXTURE_2D, 0, 0, 0, uploadLevel);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading texture data failed");

        gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
        gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, width, height);

        gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
        gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
        checkFramebufferStatus(gl);

        blitStencilToColor2D(renderCtx, *depthStencilTex, width, height);
        glu::readPixels(renderCtx, 0, 0, readLevel);

        {
            const bool compareOk = compareStencilToRed(m_testCtx.getLog(), stencilOnlyLevel, readLevel);
            m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                    compareOk ? "Pass" : "Image comparison failed");
        }

        return STOP;
    }

private:
    const uint32_t m_format;
};

class BaseLevelCase : public TestCase
{
public:
    BaseLevelCase(Context &context, const char *name, uint32_t format)
        : TestCase(context, name, glu::getTextureFormatName(format))
        , m_format(format)
    {
    }

    IterateResult iterate(void)
    {
        const glu::RenderContext &renderCtx = m_context.getRenderContext();
        const glw::Functions &gl            = renderCtx.getFunctions();
        const int width                     = 128;
        const int height                    = 128;
        const int levelNdx                  = 2;
        const int levelWidth                = width >> levelNdx;
        const int levelHeight               = height >> levelNdx;
        glu::Framebuffer fbo(renderCtx);
        glu::Renderbuffer colorBuf(renderCtx);
        glu::Texture depthStencilTex(renderCtx);
        TextureLevel uploadLevel(glu::mapGLInternalFormat(m_format), levelWidth, levelHeight);
        TextureLevel readLevel(TextureFormat(TextureFormat::RGBA, TextureFormat::UNSIGNED_INT32), levelWidth,
                               levelHeight);
        TextureLevel stencilOnlyLevel(TextureFormat(TextureFormat::S, TextureFormat::UNSIGNED_INT8), levelWidth,
                                      levelHeight);

        checkDepthStencilFormatSupport(m_context, m_format);

        m_testCtx.getLog() << TestLog::Message << "GL_TEXTURE_BASE_LEVEL = " << levelNdx << TestLog::EndMessage;

        renderTestPatternReference(uploadLevel);
        renderTestPatternReference(stencilOnlyLevel);

        gl.bindTexture(GL_TEXTURE_2D, *depthStencilTex);
        gl.texStorage2D(GL_TEXTURE_2D, deLog2Floor32(de::max(width, height)) + 1, m_format, width, height);
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, levelNdx);
        glu::texSubImage2D(renderCtx, GL_TEXTURE_2D, levelNdx, 0, 0, uploadLevel);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading texture data failed");

        gl.bindRenderbuffer(GL_RENDERBUFFER, *colorBuf);
        gl.renderbufferStorage(GL_RENDERBUFFER, GL_R32UI, levelWidth, levelHeight);

        gl.bindFramebuffer(GL_FRAMEBUFFER, *fbo);
        gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBuf);
        checkFramebufferStatus(gl);

        blitStencilToColor2D(renderCtx, *depthStencilTex, levelWidth, levelHeight);
        glu::readPixels(renderCtx, 0, 0, readLevel);

        {
            const bool compareOk = compareStencilToRed(m_testCtx.getLog(), stencilOnlyLevel, readLevel);
            m_testCtx.setTestResult(compareOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                    compareOk ? "Pass" : "Image comparison failed");
        }

        return STOP;
    }

private:
    const uint32_t m_format;
};

} // namespace

StencilTexturingTests::StencilTexturingTests(Context &context)
    : TestCaseGroup(context, "stencil_texturing", "Stencil texturing tests")
{
}

StencilTexturingTests::~StencilTexturingTests(void)
{
}

void StencilTexturingTests::init(void)
{
    // .format
    {
        tcu::TestCaseGroup *const formatGroup = new tcu::TestCaseGroup(m_testCtx, "format", "Formats");
        addChild(formatGroup);

        formatGroup->addChild(new UploadTex2DCase(m_context, "depth32f_stencil8_2d", GL_DEPTH32F_STENCIL8));
        formatGroup->addChild(new UploadTex2DArrayCase(m_context, "depth32f_stencil8_2d_array", GL_DEPTH32F_STENCIL8));
        formatGroup->addChild(new UploadTexCubeCase(m_context, "depth32f_stencil8_cube", GL_DEPTH32F_STENCIL8));
        formatGroup->addChild(new UploadTex2DCase(m_context, "depth24_stencil8_2d", GL_DEPTH24_STENCIL8));
        formatGroup->addChild(new UploadTex2DArrayCase(m_context, "depth24_stencil8_2d_array", GL_DEPTH24_STENCIL8));
        formatGroup->addChild(new UploadTexCubeCase(m_context, "depth24_stencil8_cube", GL_DEPTH24_STENCIL8));

        // OES_texture_stencil8
        formatGroup->addChild(new UploadTex2DCase(m_context, "stencil_index8_2d", GL_STENCIL_INDEX8));
        formatGroup->addChild(new UploadTex2DArrayCase(m_context, "stencil_index8_2d_array", GL_STENCIL_INDEX8));
        formatGroup->addChild(new UploadTexCubeCase(m_context, "stencil_index8_cube", GL_STENCIL_INDEX8));
    }

    // .render
    {
        tcu::TestCaseGroup *const readRenderGroup =
            new tcu::TestCaseGroup(m_testCtx, "render", "Read rendered stencil values");
        addChild(readRenderGroup);

        readRenderGroup->addChild(new ClearTex2DCase(m_context, "depth32f_stencil8_clear", GL_DEPTH32F_STENCIL8));
        readRenderGroup->addChild(new RenderTex2DCase(m_context, "depth32f_stencil8_draw", GL_DEPTH32F_STENCIL8));
        readRenderGroup->addChild(new ClearTex2DCase(m_context, "depth24_stencil8_clear", GL_DEPTH24_STENCIL8));
        readRenderGroup->addChild(new RenderTex2DCase(m_context, "depth24_stencil8_draw", GL_DEPTH24_STENCIL8));
    }

    // .misc
    {
        tcu::TestCaseGroup *const miscGroup = new tcu::TestCaseGroup(m_testCtx, "misc", "Misc cases");
        addChild(miscGroup);

        miscGroup->addChild(new CompareModeCase(m_context, "compare_mode_effect", GL_DEPTH24_STENCIL8));
        miscGroup->addChild(new BaseLevelCase(m_context, "base_level", GL_DEPTH24_STENCIL8));
    }
}

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