/*-------------------------------------------------------------------------
 * drawElements Quality Program EGL 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 EGL image tests.
 *//*--------------------------------------------------------------------*/

#include "teglImageFormatTests.hpp"

#include "deStringUtil.hpp"
#include "deSTLUtil.hpp"

#include "tcuTestLog.hpp"
#include "tcuSurface.hpp"
#include "tcuTexture.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuImageCompare.hpp"
#include "tcuCommandLine.hpp"

#include "egluNativeDisplay.hpp"
#include "egluNativeWindow.hpp"
#include "egluNativePixmap.hpp"
#include "egluConfigFilter.hpp"
#include "egluUnique.hpp"
#include "egluUtil.hpp"

#include "eglwLibrary.hpp"
#include "eglwEnums.hpp"

#include "gluCallLogWrapper.hpp"
#include "gluShaderProgram.hpp"
#include "gluStrUtil.hpp"
#include "gluTexture.hpp"
#include "gluPixelTransfer.hpp"
#include "gluObjectWrapper.hpp"
#include "gluTextureUtil.hpp"

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

#include "teglImageUtil.hpp"
#include "teglAndroidUtil.hpp"

#include <vector>
#include <string>
#include <set>

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

using de::MovePtr;
using de::UniquePtr;

using glu::Framebuffer;
using glu::Renderbuffer;
using glu::Texture;

using eglu::UniqueImage;

using tcu::ConstPixelBufferAccess;

using namespace glw;
using namespace eglw;

namespace deqp
{
namespace egl
{

namespace
{

glu::ProgramSources programSources(const string &vertexSource, const string &fragmentSource)
{
    glu::ProgramSources sources;

    sources << glu::VertexSource(vertexSource) << glu::FragmentSource(fragmentSource);

    return sources;
}

class Program : public glu::ShaderProgram
{
public:
    Program(const glw::Functions &gl, const char *vertexSource, const char *fragmentSource)
        : glu::ShaderProgram(gl, programSources(vertexSource, fragmentSource))
    {
    }
};

} // namespace

namespace Image
{

class ImageApi;

class IllegalRendererException : public std::exception
{
};

class Action
{
public:
    virtual ~Action(void)
    {
    }
    virtual bool invoke(ImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &refImg) const = 0;
    virtual string getRequiredExtension(void) const                                               = 0;
};

struct TestSpec
{
    std::string name;
    std::string desc;

    enum ApiContext
    {
        API_GLES2 = 0,
        API_GLES3,
        //API_VG
        //API_GLES1

        API_LAST
    };

    struct Operation
    {
        Operation(int apiIndex_, const Action &action_) : apiIndex(apiIndex_), action(&action_)
        {
        }
        int apiIndex;
        const Action *action;
    };

    vector<ApiContext> contexts;
    vector<Operation> operations;
};

class ImageApi
{
public:
    ImageApi(const Library &egl, int contextId, EGLDisplay display, EGLSurface surface);
    virtual ~ImageApi(void)
    {
    }

protected:
    const Library &m_egl;
    int m_contextId;
    EGLDisplay m_display;
    EGLSurface m_surface;
};

ImageApi::ImageApi(const Library &egl, int contextId, EGLDisplay display, EGLSurface surface)
    : m_egl(egl)
    , m_contextId(contextId)
    , m_display(display)
    , m_surface(surface)
{
}

class GLESImageApi : public ImageApi, private glu::CallLogWrapper
{
public:
    class GLESAction : public Action
    {
    public:
        bool invoke(ImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const;
        virtual bool invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const = 0;
    };

    class Create : public GLESAction
    {
    public:
        Create(MovePtr<ImageSource> imgSource, uint32_t numLayers = 1u) : m_imgSource(imgSource), m_numLayers(numLayers)
        {
        }
        string getRequiredExtension(void) const
        {
            return m_imgSource->getRequiredExtension();
        }
        bool invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const;
        uint32_t getNumLayers(void) const
        {
            return m_numLayers;
        }
        glw::GLenum getEffectiveFormat(void) const
        {
            return m_imgSource->getEffectiveFormat();
        }
        bool isYUVFormatImage(void) const
        {
            return m_imgSource->isYUVFormatImage();
        }

    private:
        UniquePtr<ImageSource> m_imgSource;
        uint32_t m_numLayers;
    };

    class Render : public GLESAction
    {
    public:
        virtual string getRequiredExtension(void) const
        {
            return "GL_OES_EGL_image";
        }
    };

    class RenderTexture2D : public Render
    {
    public:
        bool invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const override;
    };
    class RenderTextureCubemap : public Render
    {
    public:
        bool invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const override;
    };
    class RenderReadPixelsRenderbuffer : public Render
    {
    public:
        bool invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const override;
    };
    class RenderDepthbuffer : public Render
    {
    public:
        bool invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const override;
    };
    class RenderStencilbuffer : public Render
    {
    public:
        bool invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const override;
    };
    class RenderTryAll : public Render
    {
    public:
        bool invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const override;
    };

    class RenderTexture2DArray : public Render
    {
    public:
        bool invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const override;
        string getRequiredExtension(void) const override
        {
            return "GL_EXT_EGL_image_array";
        }
    };

    class RenderExternalTexture : public Render
    {
    public:
        bool invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const override;
        string getRequiredExtension(void) const override
        {
            return "GL_OES_EGL_image_external";
        }
    };

    class RenderExternalTextureSamplerArray : public Render
    {
    public:
        bool invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const override;
        string getRequiredExtension(void) const override
        {
            return "GL_OES_EGL_image_external";
        }
    };
    class RenderYUVTexture : public Render
    {
    public:
        bool invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const override;
        string getRequiredExtension(void) const override
        {
            return "GL_EXT_YUV_target";
        }
    };
    class RenderSampleTexture2DArray : public RenderTexture2DArray
    {
    public:
        bool invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const override;
        string getRequiredExtension(void) const override
        {
            return "GL_EXT_EGL_image_array";
        }
    };
    class Modify : public GLESAction
    {
    public:
        string getRequiredExtension(void) const
        {
            return "GL_OES_EGL_image";
        }
    };

    class ModifyTexSubImage : public Modify
    {
    public:
        ModifyTexSubImage(GLenum format, GLenum type) : m_format(format), m_type(type)
        {
        }
        bool invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const;
        GLenum getFormat(void) const
        {
            return m_format;
        }
        GLenum getType(void) const
        {
            return m_type;
        }

    private:
        GLenum m_format;
        GLenum m_type;
    };

    class ModifyRenderbuffer : public Modify
    {
    public:
        bool invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const;

    protected:
        virtual void initializeRbo(GLESImageApi &api, GLuint rbo, tcu::Texture2D &ref) const = 0;
    };

    class ModifyRenderbufferClearColor : public ModifyRenderbuffer
    {
    public:
        ModifyRenderbufferClearColor(tcu::Vec4 color) : m_color(color)
        {
        }

    protected:
        void initializeRbo(GLESImageApi &api, GLuint rbo, tcu::Texture2D &ref) const;

        tcu::Vec4 m_color;
    };

    class ModifyRenderbufferClearDepth : public ModifyRenderbuffer
    {
    public:
        ModifyRenderbufferClearDepth(GLfloat depth) : m_depth(depth)
        {
        }

    protected:
        void initializeRbo(GLESImageApi &api, GLuint rbo, tcu::Texture2D &ref) const;

        GLfloat m_depth;
    };

    class ModifyRenderbufferClearStencil : public ModifyRenderbuffer
    {
    public:
        ModifyRenderbufferClearStencil(GLint stencil) : m_stencil(stencil)
        {
        }

    protected:
        void initializeRbo(GLESImageApi &api, GLuint rbo, tcu::Texture2D &ref) const;

        GLint m_stencil;
    };

    GLESImageApi(const Library &egl, const glw::Functions &gl, int contextId, tcu::TestLog &log, EGLDisplay display,
                 EGLSurface surface, EGLConfig config, EGLint apiVersion);
    ~GLESImageApi(void);

private:
    EGLContext m_context;
    const glw::Functions &m_gl;

    MovePtr<UniqueImage> createImage(const ImageSource &source, const ClientBuffer &buffer) const;
};

GLESImageApi::GLESImageApi(const Library &egl, const glw::Functions &gl, int contextId, tcu::TestLog &log,
                           EGLDisplay display, EGLSurface surface, EGLConfig config, EGLint apiVersion)
    : ImageApi(egl, contextId, display, surface)
    , glu::CallLogWrapper(gl, log)
    , m_context(DE_NULL)
    , m_gl(gl)
{
    const EGLint attriblist[] = {EGL_CONTEXT_CLIENT_VERSION, apiVersion, EGL_NONE};

    EGLint configId = -1;
    EGLU_CHECK_CALL(m_egl, getConfigAttrib(m_display, config, EGL_CONFIG_ID, &configId));
    getLog() << tcu::TestLog::Message << "Creating gles" << apiVersion << " context with config id: " << configId
             << " context: " << m_contextId << tcu::TestLog::EndMessage;
    egl.bindAPI(EGL_OPENGL_ES_API);
    m_context = m_egl.createContext(m_display, config, EGL_NO_CONTEXT, attriblist);
    EGLU_CHECK_MSG(m_egl, "Failed to create GLES context");

    egl.makeCurrent(display, m_surface, m_surface, m_context);
    EGLU_CHECK_MSG(m_egl, "Failed to make context current");
}

GLESImageApi::~GLESImageApi(void)
{
    m_egl.makeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    m_egl.destroyContext(m_display, m_context);
}

bool GLESImageApi::GLESAction::invoke(ImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const
{
    GLESImageApi &glesApi = dynamic_cast<GLESImageApi &>(api);

    glesApi.m_egl.makeCurrent(glesApi.m_display, glesApi.m_surface, glesApi.m_surface, glesApi.m_context);
    return invokeGLES(glesApi, image, ref);
}

bool GLESImageApi::Create::invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &image, tcu::Texture2D &ref) const
{
    de::UniquePtr<ClientBuffer> buffer(m_imgSource->createBuffer(api.m_egl, api.m_gl, &ref));

    GLU_CHECK_GLW_CALL(api.m_gl, finish());

    image = api.createImage(*m_imgSource, *buffer);
    return true;
}

MovePtr<UniqueImage> GLESImageApi::createImage(const ImageSource &source, const ClientBuffer &buffer) const
{
    const EGLImageKHR image = source.createImage(m_egl, m_display, m_context, buffer.get());
    return MovePtr<UniqueImage>(new UniqueImage(m_egl, m_display, image));
}

static void imageTargetTexture2D(const Library &egl, const glw::Functions &gl, GLeglImageOES img)
{
    gl.eglImageTargetTexture2DOES(GL_TEXTURE_2D, img);
    {
        const GLenum error = gl.getError();

        if (error == GL_INVALID_OPERATION)
            TCU_THROW(NotSupportedError, "Creating texture2D from EGLImage type not supported");

        GLU_EXPECT_NO_ERROR(error, "glEGLImageTargetTexture2DOES()");
        EGLU_CHECK_MSG(egl, "glEGLImageTargetTexture2DOES()");
    }
}

static void imageTargetExternalTexture(const Library &egl, const glw::Functions &gl, GLeglImageOES img)
{
    gl.eglImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, img);
    {
        const GLenum error = gl.getError();

        if (error == GL_INVALID_OPERATION)
            TCU_THROW(NotSupportedError, "Creating external texture from EGLImage type not supported");

        GLU_EXPECT_NO_ERROR(error, "glEGLImageTargetTexture2DOES()");
        EGLU_CHECK_MSG(egl, "glEGLImageTargetTexture2DOES()");
    }
}

static void imageTargetTexture2DArray(const Library &egl, const glw::Functions &gl, GLeglImageOES img)
{
    gl.eglImageTargetTexture2DOES(GL_TEXTURE_2D_ARRAY, img);
    {
        const GLenum error = gl.getError();

        if (error == GL_INVALID_OPERATION)
            TCU_THROW(NotSupportedError, "Creating texture2D array from EGLImage type not supported");

        GLU_EXPECT_NO_ERROR(error, "glEGLImageTargetTexture2DOES()");
        EGLU_CHECK_MSG(egl, "glEGLImageTargetTexture2DOES()");
    }
}

static void imageTargetRenderbuffer(const Library &egl, const glw::Functions &gl, GLeglImageOES img)
{
    gl.eglImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, img);
    {
        const GLenum error = gl.getError();

        if (error == GL_INVALID_OPERATION)
            TCU_THROW(NotSupportedError, "Creating renderbuffer from EGLImage type not supported");

        GLU_EXPECT_NO_ERROR(error, "glEGLImageTargetRenderbufferStorageOES()");
        EGLU_CHECK_MSG(egl, "glEGLImageTargetRenderbufferStorageOES()");
    }
}

static void framebufferRenderbuffer(const glw::Functions &gl, GLenum attachment, GLuint rbo)
{
    GLU_CHECK_GLW_CALL(gl, framebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, rbo));
    TCU_CHECK_AND_THROW(
        NotSupportedError, gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE,
        ("EGLImage as " + string(glu::getFramebufferAttachmentName(attachment)) + " not supported").c_str());
}

static set<string> getSupportedExtensions(tcu::TestLog &log, const Library &egl, const EGLDisplay dpy,
                                          const glw::Functions gl)
{
    set<string> exts;
    const vector<string> glExts  = de::splitString((const char *)gl.getString(GL_EXTENSIONS));
    const vector<string> eglExts = eglu::getDisplayExtensions(egl, dpy);

    exts.insert(glExts.begin(), glExts.end());
    exts.insert(eglExts.begin(), eglExts.end());

    if (eglu::getVersion(egl, dpy) >= eglu::Version(1, 5))
    {
        // EGL 1.5 has built-in support for EGLImage and GL sources
        exts.insert("EGL_KHR_image_base");
        exts.insert("EGL_KHR_gl_texture_2D_image");
        exts.insert("EGL_KHR_gl_texture_cubemap_image");
        exts.insert("EGL_KHR_gl_renderbuffer_image");
    }

    if (!de::contains(exts, "EGL_KHR_image_base") && !de::contains(exts, "EGL_KHR_image"))
    {
        log << tcu::TestLog::Message
            << "EGL version is under 1.5 and neither EGL_KHR_image nor EGL_KHR_image_base is supported."
            << "One should be supported." << tcu::TestLog::EndMessage;
        TCU_THROW(NotSupportedError, "Extension not supported: EGL_KHR_image_base");
    }

    return exts;
}

static const float squareTriangleCoords[] = {-1.0, -1.0, 1.0,  -1.0, 1.0,  1.0,

                                             1.0,  1.0,  -1.0, 1.0,  -1.0, -1.0};

bool GLESImageApi::RenderTexture2D::invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &img,
                                               tcu::Texture2D &reference) const
{
    const glw::Functions &gl = api.m_gl;
    tcu::TestLog &log        = api.getLog();
    Texture srcTex(gl);

    // Branch only taken in TryAll case
    if (reference.getFormat().order == tcu::TextureFormat::DS || reference.getFormat().order == tcu::TextureFormat::D)
        throw IllegalRendererException(); // Skip, GLES does not support sampling depth textures
    if (reference.getFormat().order == tcu::TextureFormat::S)
        throw IllegalRendererException(); // Skip, GLES does not support sampling stencil textures

    gl.clearColor(0.0, 0.0, 0.0, 0.0);
    gl.viewport(0, 0, reference.getWidth(), reference.getHeight());
    gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    gl.disable(GL_DEPTH_TEST);

    log << tcu::TestLog::Message << "Rendering EGLImage as GL_TEXTURE_2D in context: " << api.m_contextId
        << tcu::TestLog::EndMessage;
    TCU_CHECK(**img != EGL_NO_IMAGE_KHR);

    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_2D, *srcTex));
    imageTargetTexture2D(api.m_egl, gl, **img);

    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));

    const char *const vertexShader = "attribute highp vec2 a_coord;\n"
                                     "varying mediump vec2 v_texCoord;\n"
                                     "void main(void) {\n"
                                     "\tv_texCoord = vec2((a_coord.x + 1.0) * 0.5, (a_coord.y + 1.0) * 0.5);\n"
                                     "\tgl_Position = vec4(a_coord, -0.1, 1.0);\n"
                                     "}\n";

    const char *const fragmentShader = "varying mediump vec2 v_texCoord;\n"
                                       "uniform sampler2D u_sampler;\n"
                                       "void main(void) {\n"
                                       "\tmediump vec4 texColor = texture2D(u_sampler, v_texCoord);\n"
                                       "\tgl_FragColor = vec4(texColor);\n"
                                       "}";

    Program program(gl, vertexShader, fragmentShader);
    TCU_CHECK(program.isOk());

    GLuint glProgram = program.getProgram();
    GLU_CHECK_GLW_CALL(gl, useProgram(glProgram));

    GLuint coordLoc = gl.getAttribLocation(glProgram, "a_coord");
    TCU_CHECK_MSG((int)coordLoc != -1, "Couldn't find attribute a_coord");

    GLuint samplerLoc = gl.getUniformLocation(glProgram, "u_sampler");
    TCU_CHECK_MSG((int)samplerLoc != (int)-1, "Couldn't find uniform u_sampler");

    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_2D, *srcTex));
    GLU_CHECK_GLW_CALL(gl, uniform1i(samplerLoc, 0));
    GLU_CHECK_GLW_CALL(gl, enableVertexAttribArray(coordLoc));
    GLU_CHECK_GLW_CALL(gl, vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, squareTriangleCoords));

    GLU_CHECK_GLW_CALL(gl, drawArrays(GL_TRIANGLES, 0, 6));
    GLU_CHECK_GLW_CALL(gl, disableVertexAttribArray(coordLoc));
    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_2D, 0));

    tcu::Surface refSurface(reference.getWidth(), reference.getHeight());
    tcu::Surface screen(reference.getWidth(), reference.getHeight());
    GLU_CHECK_GLW_CALL(gl, readPixels(0, 0, screen.getWidth(), screen.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                                      screen.getAccess().getDataPtr()));

    tcu::copy(refSurface.getAccess(), reference.getLevel(0));

    float threshold = 0.05f;
    bool match = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refSurface, screen, threshold,
                                   tcu::COMPARE_LOG_RESULT);

    return match;
}

// Renders using a single layer from a texture array.
bool GLESImageApi::RenderTexture2DArray::invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &img,
                                                    tcu::Texture2D &reference) const
{
    const glw::Functions &gl = api.m_gl;
    tcu::TestLog &log        = api.getLog();
    Texture srcTex(gl);

    gl.clearColor(0.0, 0.0, 0.0, 0.0);
    gl.viewport(0, 0, reference.getWidth(), reference.getHeight());
    gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    gl.disable(GL_DEPTH_TEST);

    log << tcu::TestLog::Message << "Rendering EGLImage as GL_TEXTURE_2D_ARRAY in context: " << api.m_contextId
        << tcu::TestLog::EndMessage;
    TCU_CHECK(**img != EGL_NO_IMAGE_KHR);

    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_2D_ARRAY, *srcTex));
    imageTargetTexture2DArray(api.m_egl, gl, **img);

    glu::TransferFormat transferFormat = glu::getTransferFormat(reference.getFormat());
    // Initializes layer 1.
    GLU_CHECK_GLW_CALL(gl, texSubImage3D(GL_TEXTURE_2D_ARRAY,
                                         0,                                    // Mipmap level
                                         0,                                    // X offset
                                         0,                                    // Y offset
                                         1,                                    // Z offset (layer)
                                         reference.getWidth(),                 // Width
                                         reference.getHeight(),                // Height
                                         1u,                                   // Depth
                                         transferFormat.format,                // Format
                                         transferFormat.dataType,              // Type
                                         reference.getLevel(0).getDataPtr())); // Pixel data

    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));

    const char *const vertexShader = "#version 300 es\n"
                                     "precision highp int;\n"
                                     "precision highp float;\n"
                                     "layout(location = 0) in vec2 pos_in;\n"
                                     "out vec2 texcoord;\n"
                                     "void main()\n"
                                     "{\n"
                                     "    gl_Position = vec4(pos_in, -0.1, 1.0);\n"
                                     "    texcoord = vec2((pos_in.x + 1.0) * 0.5, (pos_in.y + 1.0) * 0.5);\n"
                                     "}\n";

    const char *const fragmentShader = "#version 300 es\n"
                                       "precision highp int;\n"
                                       "precision highp float;\n"
                                       "in vec2 texcoord;\n"
                                       "layout(location = 0) out vec4 color_out;\n"
                                       "uniform highp sampler2DArray tex_sampler;\n"
                                       "void main()\n"
                                       "{\n"
                                       // Samples layer 1.
                                       "    color_out = texture(tex_sampler, vec3(texcoord, 1));\n"
                                       "}\n";

    Program program(gl, vertexShader, fragmentShader);

    if (!program.isOk())
    {
        log << tcu::TestLog::Message << "Shader build failed.\n"
            << "Vertex: " << program.getShaderInfo(glu::SHADERTYPE_VERTEX).infoLog << "\n"
            << vertexShader << "\n"
            << "Fragment: " << program.getShaderInfo(glu::SHADERTYPE_FRAGMENT).infoLog << "\n"
            << fragmentShader << "\n"
            << "Program: " << program.getProgramInfo().infoLog << tcu::TestLog::EndMessage;
    }

    TCU_CHECK(program.isOk());

    GLuint glProgram = program.getProgram();
    GLU_CHECK_GLW_CALL(gl, useProgram(glProgram));

    GLuint coordLoc = gl.getAttribLocation(glProgram, "pos_in");
    TCU_CHECK_MSG((int)coordLoc != -1, "Couldn't find attribute pos_in");

    GLuint samplerLoc = gl.getUniformLocation(glProgram, "tex_sampler");
    TCU_CHECK_MSG((int)samplerLoc != (int)-1, "Couldn't find uniform tex_sampler");

    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_2D_ARRAY, *srcTex));
    GLU_CHECK_GLW_CALL(gl, uniform1i(samplerLoc, 0));
    GLU_CHECK_GLW_CALL(gl, enableVertexAttribArray(coordLoc));
    GLU_CHECK_GLW_CALL(gl, vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, squareTriangleCoords));

    GLU_CHECK_GLW_CALL(gl, drawArrays(GL_TRIANGLES, 0, 6));
    GLU_CHECK_GLW_CALL(gl, disableVertexAttribArray(coordLoc));

    tcu::Surface refSurface(reference.getWidth(), reference.getHeight());
    tcu::Surface screen(reference.getWidth(), reference.getHeight());
    GLU_CHECK_GLW_CALL(gl, readPixels(0, 0, screen.getWidth(), screen.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                                      screen.getAccess().getDataPtr()));

    tcu::copy(refSurface.getAccess(), reference.getLevel(0));

    float threshold = 0.05f;
    bool match = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refSurface, screen, threshold,
                                   tcu::COMPARE_LOG_RESULT);

    return match;
}

//Texture2D array can be both rendered and sampled as a texture.
bool GLESImageApi::RenderSampleTexture2DArray::invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &img,
                                                          tcu::Texture2D &reference) const
{
    const glw::Functions &gl = api.m_gl;
    tcu::TestLog &log        = api.getLog();
    Texture srcTex(gl);
    Texture srcTexArray(gl);

    gl.clearColor(0.0, 0.0, 0.0, 0.0);
    gl.viewport(0, 0, reference.getWidth(), reference.getHeight());
    gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    gl.disable(GL_DEPTH_TEST);

    log << tcu::TestLog::Message
        << "Rendering and sampling EGLImage as GL_TEXTURE_2D_ARRAY in context: " << api.m_contextId
        << tcu::TestLog::EndMessage;
    TCU_CHECK(**img != EGL_NO_IMAGE_KHR);

    glu::TransferFormat transferFormat = glu::getTransferFormat(reference.getFormat());
    uint32_t internalForat             = glu::getInternalFormat(reference.getFormat());
    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_2D, *srcTex));
    GLU_CHECK_GLW_CALL(gl, texImage2D(GL_TEXTURE_2D,
                                      0,                                    // Level
                                      internalForat,                        // Internal format
                                      reference.getWidth(),                 // Width
                                      reference.getHeight(),                // Height
                                      0,                                    // Border
                                      transferFormat.format,                // Format
                                      transferFormat.dataType,              // Type
                                      reference.getLevel(0).getDataPtr())); // Pixel data

    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));

    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_2D_ARRAY, *srcTexArray));
    imageTargetTexture2DArray(api.m_egl, gl, **img);

    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));

    /* Create FBO and attach source texture layer 0 */
    glu::Framebuffer fbo(gl);
    GLU_CHECK_GLW_CALL(gl, bindFramebuffer(GL_FRAMEBUFFER, *fbo));
    GLU_CHECK_GLW_CALL(gl, framebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, *srcTexArray, 0, 0));

    const char *const vertexShader0 = "attribute highp vec2 a_coord;\n"
                                      "varying mediump vec2 v_texCoord;\n"
                                      "void main(void) {\n"
                                      "\tv_texCoord = vec2((a_coord.x + 1.0) * 0.5, (a_coord.y + 1.0) * 0.5);\n"
                                      "\tgl_Position = vec4(a_coord, -0.1, 1.0);\n"
                                      "}\n";

    const char *const fragmentShader0 = "varying mediump vec2 v_texCoord;\n"
                                        "uniform sampler2D u_sampler;\n"
                                        "void main(void) {\n"
                                        "\tmediump vec4 texColor = texture2D(u_sampler, v_texCoord);\n"
                                        "\tgl_FragColor = vec4(texColor);\n"
                                        "}";

    Program program0(gl, vertexShader0, fragmentShader0);
    if (!program0.isOk())
    {
        log << tcu::TestLog::Message << "Shader build failed.\n"
            << "Vertex: " << program0.getShaderInfo(glu::SHADERTYPE_VERTEX).infoLog << "\n"
            << vertexShader0 << "\n"
            << "Fragment: " << program0.getShaderInfo(glu::SHADERTYPE_FRAGMENT).infoLog << "\n"
            << fragmentShader0 << "\n"
            << "Program: " << program0.getProgramInfo().infoLog << tcu::TestLog::EndMessage;
    }

    TCU_CHECK(program0.isOk());

    GLuint glProgram0 = program0.getProgram();
    GLU_CHECK_GLW_CALL(gl, useProgram(glProgram0));

    GLuint coordLoc = gl.getAttribLocation(glProgram0, "a_coord");
    TCU_CHECK_MSG((int)coordLoc != -1, "Couldn't find attribute a_coord");
    GLuint samplerLoc = gl.getUniformLocation(glProgram0, "u_sampler");
    TCU_CHECK_MSG((int)samplerLoc != (int)-1, "Couldn't find uniform u_sampler");
    GLU_CHECK_GLW_CALL(gl, uniform1i(samplerLoc, 0));

    GLU_CHECK_GLW_CALL(gl, enableVertexAttribArray(coordLoc));
    GLU_CHECK_GLW_CALL(gl, vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, squareTriangleCoords));
    GLU_CHECK_GLW_CALL(gl, drawArrays(GL_TRIANGLES, 0, 6));
    GLU_CHECK_GLW_CALL(gl, disableVertexAttribArray(coordLoc));

    tcu::Surface refSurface0(reference.getWidth(), reference.getHeight());
    tcu::Surface screen0(reference.getWidth(), reference.getHeight());
    GLU_CHECK_GLW_CALL(gl, readPixels(0, 0, screen0.getWidth(), screen0.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                                      screen0.getAccess().getDataPtr()));

    tcu::copy(refSurface0.getAccess(), reference.getLevel(0));

    float threshold = 0.01f;
    bool match = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refSurface0, screen0, threshold,
                                   tcu::COMPARE_LOG_RESULT);

    if (match == false)
        return match;

    /* Bind texture to FBO. */
    GLU_CHECK_GLW_CALL(gl, framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *srcTex, 0));

    const char *const vertexShader = "#version 300 es\n"
                                     "precision highp int;\n"
                                     "precision highp float;\n"
                                     "layout(location = 0) in vec2 pos_in;\n"
                                     "out vec2 texcoord;\n"
                                     "void main()\n"
                                     "{\n"
                                     "    gl_Position = vec4(pos_in, -0.1, 1.0);\n"
                                     "    texcoord = vec2((pos_in.x + 1.0) * 0.5, (pos_in.y + 1.0) * 0.5);\n"
                                     "}\n";

    const char *const fragmentShader = "#version 300 es\n"
                                       "precision highp int;\n"
                                       "precision highp float;\n"
                                       "in vec2 texcoord;\n"
                                       "layout(location = 0) out vec4 color_out;\n"
                                       "uniform highp sampler2DArray tex_sampler;\n"
                                       "void main()\n"
                                       "{\n"
                                       // Samples layer 0.
                                       "    color_out = texture(tex_sampler, vec3(texcoord, 0));\n"
                                       "}\n";

    Program program(gl, vertexShader, fragmentShader);

    if (!program.isOk())
    {
        log << tcu::TestLog::Message << "Shader build failed.\n"
            << "Vertex: " << program.getShaderInfo(glu::SHADERTYPE_VERTEX).infoLog << "\n"
            << vertexShader << "\n"
            << "Fragment: " << program.getShaderInfo(glu::SHADERTYPE_FRAGMENT).infoLog << "\n"
            << fragmentShader << "\n"
            << "Program: " << program.getProgramInfo().infoLog << tcu::TestLog::EndMessage;
    }

    TCU_CHECK(program.isOk());

    GLuint glProgram = program.getProgram();
    GLU_CHECK_GLW_CALL(gl, useProgram(glProgram));

    coordLoc = gl.getAttribLocation(glProgram, "pos_in");
    TCU_CHECK_MSG((int)coordLoc != -1, "Couldn't find attribute pos_in");

    samplerLoc = gl.getUniformLocation(glProgram, "tex_sampler");
    TCU_CHECK_MSG((int)samplerLoc != (int)-1, "Couldn't find uniform tex_sampler");

    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_2D_ARRAY, *srcTexArray));
    GLU_CHECK_GLW_CALL(gl, uniform1i(samplerLoc, 0));
    GLU_CHECK_GLW_CALL(gl, enableVertexAttribArray(coordLoc));
    GLU_CHECK_GLW_CALL(gl, vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, squareTriangleCoords));

    GLU_CHECK_GLW_CALL(gl, drawArrays(GL_TRIANGLES, 0, 6));
    GLU_CHECK_GLW_CALL(gl, disableVertexAttribArray(coordLoc));
    GLU_CHECK_GLW_CALL(gl, finish());

    tcu::Surface refSurface(reference.getWidth(), reference.getHeight());
    tcu::Surface screen(reference.getWidth(), reference.getHeight());
    GLU_CHECK_GLW_CALL(gl, readPixels(0, 0, screen.getWidth(), screen.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                                      screen.getAccess().getDataPtr()));

    tcu::copy(refSurface.getAccess(), reference.getLevel(0));

    GLU_CHECK_GLW_CALL(gl, bindFramebuffer(GL_FRAMEBUFFER, 0));

    threshold = 0.01f;
    match     = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refSurface, screen, threshold,
                                  tcu::COMPARE_LOG_RESULT);
    return match;
}

bool GLESImageApi::RenderExternalTexture::invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &img,
                                                     tcu::Texture2D &reference) const
{
    const glw::Functions &gl = api.m_gl;
    tcu::TestLog &log        = api.getLog();
    Texture srcTex(gl);

    // Branch only taken in TryAll case
    if (reference.getFormat().order == tcu::TextureFormat::DS || reference.getFormat().order == tcu::TextureFormat::D)
        throw IllegalRendererException(); // Skip, GLES2 does not support sampling depth textures
    if (reference.getFormat().order == tcu::TextureFormat::S)
        throw IllegalRendererException(); // Skip, GLES2 does not support sampling stencil textures

    gl.clearColor(0.0, 0.0, 0.0, 0.0);
    gl.viewport(0, 0, reference.getWidth(), reference.getHeight());
    gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    gl.disable(GL_DEPTH_TEST);

    log << tcu::TestLog::Message << "Rendering EGLImage as GL_TEXTURE_EXTERNAL_OES in context: " << api.m_contextId
        << tcu::TestLog::EndMessage;
    TCU_CHECK(**img != EGL_NO_IMAGE_KHR);

    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_EXTERNAL_OES, *srcTex));
    imageTargetExternalTexture(api.m_egl, gl, **img);

    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));

    const char *const vertexShader = "attribute highp vec2 a_coord;\n"
                                     "varying mediump vec2 v_texCoord;\n"
                                     "void main(void) {\n"
                                     "\tv_texCoord = vec2((a_coord.x + 1.0) * 0.5, (a_coord.y + 1.0) * 0.5);\n"
                                     "\tgl_Position = vec4(a_coord, -0.1, 1.0);\n"
                                     "}\n";

    const char *const fragmentShader = "#extension GL_OES_EGL_image_external : require\n"
                                       "varying mediump vec2 v_texCoord;\n"
                                       "uniform samplerExternalOES u_sampler;\n"
                                       "void main(void) {\n"
                                       "\tmediump vec4 texColor = texture2D(u_sampler, v_texCoord);\n"
                                       "\tgl_FragColor = vec4(texColor);\n"
                                       "}";

    Program program(gl, vertexShader, fragmentShader);
    TCU_CHECK(program.isOk());

    GLuint glProgram = program.getProgram();
    GLU_CHECK_GLW_CALL(gl, useProgram(glProgram));

    GLuint coordLoc = gl.getAttribLocation(glProgram, "a_coord");
    TCU_CHECK_MSG((int)coordLoc != -1, "Couldn't find attribute a_coord");

    GLuint samplerLoc = gl.getUniformLocation(glProgram, "u_sampler");
    TCU_CHECK_MSG((int)samplerLoc != (int)-1, "Couldn't find uniform u_sampler");

    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_EXTERNAL_OES, *srcTex));
    GLU_CHECK_GLW_CALL(gl, uniform1i(samplerLoc, 0));
    GLU_CHECK_GLW_CALL(gl, enableVertexAttribArray(coordLoc));
    GLU_CHECK_GLW_CALL(gl, vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, squareTriangleCoords));

    GLU_CHECK_GLW_CALL(gl, drawArrays(GL_TRIANGLES, 0, 6));
    GLU_CHECK_GLW_CALL(gl, disableVertexAttribArray(coordLoc));
    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_EXTERNAL_OES, 0));

    tcu::Surface refSurface(reference.getWidth(), reference.getHeight());
    tcu::Surface screen(reference.getWidth(), reference.getHeight());
    GLU_CHECK_GLW_CALL(gl, readPixels(0, 0, screen.getWidth(), screen.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                                      screen.getAccess().getDataPtr()));

    tcu::copy(refSurface.getAccess(), reference.getLevel(0));

    float threshold = 0.05f;
    bool match = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refSurface, screen, threshold,
                                   tcu::COMPARE_LOG_RESULT);

    return match;
}

bool GLESImageApi::RenderYUVTexture::invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &img,
                                                tcu::Texture2D &reference) const
{
    const glw::Functions &gl = api.m_gl;
    tcu::TestLog &log        = api.getLog();
    Texture srcTex(gl);

    DE_ASSERT(reference.isYUVTextureUsed());

    gl.clearColor(0.0, 0.0, 0.0, 0.0);
    gl.viewport(0, 0, reference.getWidth(), reference.getHeight());
    gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    gl.disable(GL_DEPTH_TEST);

    log << tcu::TestLog::Message << "Rendering EGLImage as GL_TEXTURE_EXTERNAL_OES in context: " << api.m_contextId
        << tcu::TestLog::EndMessage;
    TCU_CHECK(**img != EGL_NO_IMAGE_KHR);
    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_EXTERNAL_OES, *srcTex));
    imageTargetExternalTexture(api.m_egl, gl, **img);
    {
        /* init YUV texture with glClear, clear color value in YUV color space */
        glu::Framebuffer fbo(gl);
        GLU_CHECK_GLW_CALL(gl, bindFramebuffer(GL_FRAMEBUFFER, *fbo));
        GLU_CHECK_GLW_CALL(
            gl, framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_EXTERNAL_OES, *srcTex, 0));
        const tcu::Vec4 colorValues[] = {
            tcu::Vec4(0.9f, 0.5f, 0.65f, 1.0f), tcu::Vec4(0.5f, 0.7f, 0.65f, 1.0f), tcu::Vec4(0.2f, 0.5f, 0.65f, 1.0f),
            tcu::Vec4(0.3f, 0.1f, 0.5f, 1.0f),  tcu::Vec4(0.8f, 0.2f, 0.3f, 1.0f),  tcu::Vec4(0.9f, 0.4f, 0.8f, 1.0f),
        };
        tcu::clear(reference.getLevel(0), tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f));
        GLU_CHECK_GLW_CALL(gl, enable(GL_SCISSOR_TEST));
        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(colorValues); ++ndx)
        {
            const tcu::IVec2 size =
                tcu::IVec2((int)((float)(DE_LENGTH_OF_ARRAY(colorValues) - ndx) *
                                 ((float)reference.getWidth() / float(DE_LENGTH_OF_ARRAY(colorValues)))),
                           (int)((float)(DE_LENGTH_OF_ARRAY(colorValues) - ndx) *
                                 ((float)reference.getHeight() / float(DE_LENGTH_OF_ARRAY(colorValues)))));

            if (size.x() == 0 || size.y() == 0)
                break;
            GLU_CHECK_GLW_CALL(gl, scissor(0, 0, size.x(), size.y()));

            GLU_CHECK_GLW_CALL(
                gl, clearColor(colorValues[ndx].x(), colorValues[ndx].y(), colorValues[ndx].z(), colorValues[ndx].w()));
            GLU_CHECK_GLW_CALL(gl, clear(GL_COLOR_BUFFER_BIT));
            GLU_CHECK_GLW_CALL(gl, finish());
            uint8_t tmp[4] = {"0"};
            GLU_CHECK_GLW_CALL(gl, readPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void *)tmp));
            tcu::clear(tcu::getSubregion(reference.getLevel(0), 0, 0, size.x(), size.y()),
                       tcu::Vec4(tmp[0] / (255.0f), tmp[1] / (255.0f), tmp[2] / (255.0f), tmp[3] / (255.0f)));
        }
        GLU_CHECK_GLW_CALL(gl, disable(GL_SCISSOR_TEST));
        GLU_CHECK_GLW_CALL(gl,
                           framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_EXTERNAL_OES, 0, 0));
        GLU_CHECK_GLW_CALL(gl, bindFramebuffer(GL_FRAMEBUFFER, 0));
        GLU_CHECK_GLW_CALL(gl, finish());
    }

    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));

    const char *const vertexShader = "attribute highp vec2 a_coord;\n"
                                     "varying mediump vec2 v_texCoord;\n"
                                     "void main(void) {\n"
                                     "\tv_texCoord = vec2((a_coord.x + 1.0) * 0.5, (a_coord.y + 1.0) * 0.5);\n"
                                     "\tgl_Position = vec4(a_coord, -0.1, 1.0);\n"
                                     "}\n";

    const char *const fragmentShader = "#extension GL_OES_EGL_image_external : require\n"
                                       "varying mediump vec2 v_texCoord;\n"
                                       "uniform samplerExternalOES u_sampler;\n"
                                       "void main(void) {\n"
                                       "\tmediump vec4 texColor = texture2D(u_sampler, v_texCoord);\n"
                                       "\tgl_FragColor = vec4(texColor);\n"
                                       "}";

    Program program(gl, vertexShader, fragmentShader);
    TCU_CHECK(program.isOk());

    GLuint glProgram = program.getProgram();
    GLU_CHECK_GLW_CALL(gl, useProgram(glProgram));

    GLuint coordLoc = gl.getAttribLocation(glProgram, "a_coord");
    TCU_CHECK_MSG((int)coordLoc != -1, "Couldn't find attribute a_coord");

    GLuint samplerLoc = gl.getUniformLocation(glProgram, "u_sampler");
    TCU_CHECK_MSG((int)samplerLoc != (int)-1, "Couldn't find uniform u_sampler");

    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_EXTERNAL_OES, *srcTex));
    GLU_CHECK_GLW_CALL(gl, uniform1i(samplerLoc, 0));
    GLU_CHECK_GLW_CALL(gl, enableVertexAttribArray(coordLoc));
    GLU_CHECK_GLW_CALL(gl, vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, squareTriangleCoords));

    GLU_CHECK_GLW_CALL(gl, drawArrays(GL_TRIANGLES, 0, 6));
    GLU_CHECK_GLW_CALL(gl, disableVertexAttribArray(coordLoc));
    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_EXTERNAL_OES, 0));

    tcu::Surface refSurface(reference.getWidth(), reference.getHeight());
    tcu::Surface screen(reference.getWidth(), reference.getHeight());
    GLU_CHECK_GLW_CALL(gl, readPixels(0, 0, screen.getWidth(), screen.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                                      screen.getAccess().getDataPtr()));

    tcu::copy(refSurface.getAccess(), reference.getLevel(0));

    float threshold = 0.05f;
    bool match = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refSurface, screen, threshold,
                                   tcu::COMPARE_LOG_RESULT);

    return match;
}

bool GLESImageApi::RenderExternalTextureSamplerArray::invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &img,
                                                                 tcu::Texture2D &reference) const
{
    const glw::Functions &gl = api.m_gl;
    tcu::TestLog &log        = api.getLog();
    Texture srcTex(gl);

    // Branch only taken in TryAll case
    if (reference.getFormat().order == tcu::TextureFormat::DS || reference.getFormat().order == tcu::TextureFormat::D)
        throw IllegalRendererException(); // Skip, GLES2 does not support sampling depth textures
    if (reference.getFormat().order == tcu::TextureFormat::S)
        throw IllegalRendererException(); // Skip, GLES2 does not support sampling stencil textures

    gl.clearColor(0.0, 0.0, 0.0, 0.0);
    gl.viewport(0, 0, reference.getWidth(), reference.getHeight());
    gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    gl.disable(GL_DEPTH_TEST);

    log << tcu::TestLog::Message
        << "Rendering EGLImage as GL_TEXTURE_EXTERNAL_OES using sampler array in context: " << api.m_contextId
        << tcu::TestLog::EndMessage;
    TCU_CHECK(**img != EGL_NO_IMAGE_KHR);

    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_EXTERNAL_OES, *srcTex));
    imageTargetExternalTexture(api.m_egl, gl, **img);

    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
    GLU_CHECK_GLW_CALL(gl, texParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));

    // Texture not associated with an external texture will return (0, 0, 0, 1) when sampled.
    GLuint emptyTex;
    gl.genTextures(1, &emptyTex);
    gl.activeTexture(GL_TEXTURE1);
    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_EXTERNAL_OES, emptyTex));

    const char *const vertexShader = "attribute highp vec2 a_coord;\n"
                                     "varying mediump vec2 v_texCoord;\n"
                                     "void main(void) {\n"
                                     "\tv_texCoord = vec2((a_coord.x + 1.0) * 0.5, (a_coord.y + 1.0) * 0.5);\n"
                                     "\tgl_Position = vec4(a_coord, -0.1, 1.0);\n"
                                     "}\n";

    const char *const fragmentShader = "#extension GL_OES_EGL_image_external : require\n"
                                       "varying mediump vec2 v_texCoord;\n"
                                       "uniform samplerExternalOES u_sampler[4];\n"
                                       "void main(void) {\n"
                                       "\tmediump vec4 texColor = texture2D(u_sampler[2], v_texCoord);\n"
                                       "\t//These will sample (0, 0, 0, 1) and should not affect the results.\n"
                                       "\ttexColor += texture2D(u_sampler[0], v_texCoord) - vec4(0, 0, 0, 1);\n"
                                       "\ttexColor += texture2D(u_sampler[1], v_texCoord) - vec4(0, 0, 0, 1);\n"
                                       "\ttexColor += texture2D(u_sampler[3], v_texCoord) - vec4(0, 0, 0, 1);\n"
                                       "\tgl_FragColor = vec4(texColor);\n"
                                       "}";

    Program program(gl, vertexShader, fragmentShader);
    TCU_CHECK(program.isOk());

    GLuint glProgram = program.getProgram();
    GLU_CHECK_GLW_CALL(gl, useProgram(glProgram));

    GLuint coordLoc = gl.getAttribLocation(glProgram, "a_coord");
    TCU_CHECK_MSG((int)coordLoc != -1, "Couldn't find attribute a_coord");

    GLuint samplerLoc0 = gl.getUniformLocation(glProgram, "u_sampler[0]");
    TCU_CHECK_MSG((int)samplerLoc0 != (int)-1, "Couldn't find uniform u_sampler[0]");
    GLuint samplerLoc1 = gl.getUniformLocation(glProgram, "u_sampler[1]");
    TCU_CHECK_MSG((int)samplerLoc1 != (int)-1, "Couldn't find uniform u_sampler[1]");
    GLuint samplerLoc2 = gl.getUniformLocation(glProgram, "u_sampler[2]");
    TCU_CHECK_MSG((int)samplerLoc2 != (int)-1, "Couldn't find uniform u_sampler[2]");
    GLuint samplerLoc3 = gl.getUniformLocation(glProgram, "u_sampler[3]");
    TCU_CHECK_MSG((int)samplerLoc3 != (int)-1, "Couldn't find uniform u_sampler[3]");

    gl.activeTexture(GL_TEXTURE0);
    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_EXTERNAL_OES, *srcTex));
    // One sampler reads a gradient and others opaque black.
    GLU_CHECK_GLW_CALL(gl, uniform1i(samplerLoc0, 1));
    GLU_CHECK_GLW_CALL(gl, uniform1i(samplerLoc1, 1));
    GLU_CHECK_GLW_CALL(gl, uniform1i(samplerLoc2, 0));
    GLU_CHECK_GLW_CALL(gl, uniform1i(samplerLoc3, 1));
    GLU_CHECK_GLW_CALL(gl, enableVertexAttribArray(coordLoc));
    GLU_CHECK_GLW_CALL(gl, vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, squareTriangleCoords));

    GLU_CHECK_GLW_CALL(gl, drawArrays(GL_TRIANGLES, 0, 6));
    GLU_CHECK_GLW_CALL(gl, disableVertexAttribArray(coordLoc));
    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_EXTERNAL_OES, 0));
    gl.activeTexture(GL_TEXTURE1);
    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_EXTERNAL_OES, 0));
    gl.deleteTextures(1, &emptyTex);
    gl.activeTexture(GL_TEXTURE0);

    tcu::Surface refSurface(reference.getWidth(), reference.getHeight());
    tcu::Surface screen(reference.getWidth(), reference.getHeight());
    GLU_CHECK_GLW_CALL(gl, readPixels(0, 0, screen.getWidth(), screen.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                                      screen.getAccess().getDataPtr()));

    tcu::copy(refSurface.getAccess(), reference.getLevel(0));

    float threshold = 0.05f;
    bool match = tcu::fuzzyCompare(log, "ComparisonResult", "Image comparison result", refSurface, screen, threshold,
                                   tcu::COMPARE_LOG_RESULT);

    return match;
}

bool GLESImageApi::RenderDepthbuffer::invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &img,
                                                 tcu::Texture2D &reference) const
{
    const glw::Functions &gl = api.m_gl;
    tcu::TestLog &log        = api.getLog();
    Framebuffer framebuffer(gl);
    Renderbuffer renderbufferColor(gl);
    Renderbuffer renderbufferDepth(gl);
    const tcu::RGBA compareThreshold(32, 32, 32, 32); // layer colors are far apart, large thresholds are ok

    // Branch only taken in TryAll case
    if (reference.getFormat().order != tcu::TextureFormat::DS && reference.getFormat().order != tcu::TextureFormat::D)
        throw IllegalRendererException(); // Skip, interpreting non-depth data as depth data is not meaningful

    log << tcu::TestLog::Message << "Rendering with depth buffer" << tcu::TestLog::EndMessage;

    GLU_CHECK_GLW_CALL(gl, bindFramebuffer(GL_FRAMEBUFFER, *framebuffer));

    GLU_CHECK_GLW_CALL(gl, bindRenderbuffer(GL_RENDERBUFFER, *renderbufferColor));
    GLU_CHECK_GLW_CALL(gl, renderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, reference.getWidth(), reference.getHeight()));
    framebufferRenderbuffer(gl, GL_COLOR_ATTACHMENT0, *renderbufferColor);

    GLU_CHECK_GLW_CALL(gl, bindRenderbuffer(GL_RENDERBUFFER, *renderbufferDepth));
    imageTargetRenderbuffer(api.m_egl, gl, **img);
    framebufferRenderbuffer(gl, GL_DEPTH_ATTACHMENT, *renderbufferDepth);
    GLU_CHECK_GLW_CALL(gl, bindRenderbuffer(GL_RENDERBUFFER, 0));

    GLU_CHECK_GLW_CALL(gl, viewport(0, 0, reference.getWidth(), reference.getHeight()));

    // Render
    const char *vertexShader = "attribute highp vec2 a_coord;\n"
                               "uniform highp float u_depth;\n"
                               "void main(void) {\n"
                               "\tgl_Position = vec4(a_coord, u_depth, 1.0);\n"
                               "}\n";

    const char *fragmentShader = "uniform mediump vec4 u_color;\n"
                                 "void main(void) {\n"
                                 "\tgl_FragColor = u_color;\n"
                                 "}";

    Program program(gl, vertexShader, fragmentShader);
    TCU_CHECK(program.isOk());

    GLuint glProgram = program.getProgram();
    GLU_CHECK_GLW_CALL(gl, useProgram(glProgram));

    GLuint coordLoc = gl.getAttribLocation(glProgram, "a_coord");
    TCU_CHECK_MSG((int)coordLoc != -1, "Couldn't find attribute a_coord");

    GLuint colorLoc = gl.getUniformLocation(glProgram, "u_color");
    TCU_CHECK_MSG((int)colorLoc != (int)-1, "Couldn't find uniform u_color");

    GLuint depthLoc = gl.getUniformLocation(glProgram, "u_depth");
    TCU_CHECK_MSG((int)depthLoc != (int)-1, "Couldn't find uniform u_depth");

    GLU_CHECK_GLW_CALL(gl, clearColor(0.5f, 1.0f, 0.5f, 1.0f));
    GLU_CHECK_GLW_CALL(gl, clear(GL_COLOR_BUFFER_BIT));

    tcu::Vec4 depthLevelColors[] = {
        tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f), tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f),
        tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f), tcu::Vec4(1.0f, 0.0f, 1.0f, 1.0f),

        tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f), tcu::Vec4(0.5f, 0.0f, 0.0f, 1.0f),
        tcu::Vec4(0.0f, 0.5f, 0.0f, 1.0f), tcu::Vec4(0.5f, 0.5f, 0.0f, 1.0f)};

    GLU_CHECK_GLW_CALL(gl, enableVertexAttribArray(coordLoc));
    GLU_CHECK_GLW_CALL(gl, vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, squareTriangleCoords));

    GLU_CHECK_GLW_CALL(gl, enable(GL_DEPTH_TEST));
    GLU_CHECK_GLW_CALL(gl, depthFunc(GL_LESS));
    GLU_CHECK_GLW_CALL(gl, depthMask(GL_FALSE));

    for (int level = 0; level < DE_LENGTH_OF_ARRAY(depthLevelColors); level++)
    {
        const tcu::Vec4 color = depthLevelColors[level];
        const float clipDepth = ((float)(level + 1) * 0.1f) * 2.0f - 1.0f; // depth in clip coords

        GLU_CHECK_GLW_CALL(gl, uniform4f(colorLoc, color.x(), color.y(), color.z(), color.w()));
        GLU_CHECK_GLW_CALL(gl, uniform1f(depthLoc, clipDepth));
        GLU_CHECK_GLW_CALL(gl, drawArrays(GL_TRIANGLES, 0, 6));
    }

    GLU_CHECK_GLW_CALL(gl, depthMask(GL_TRUE));
    GLU_CHECK_GLW_CALL(gl, disable(GL_DEPTH_TEST));
    GLU_CHECK_GLW_CALL(gl, disableVertexAttribArray(coordLoc));

    const ConstPixelBufferAccess &refAccess = reference.getLevel(0);
    tcu::Surface screen(reference.getWidth(), reference.getHeight());
    tcu::Surface referenceScreen(reference.getWidth(), reference.getHeight());

    gl.readPixels(0, 0, screen.getWidth(), screen.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                  screen.getAccess().getDataPtr());

    tcu::Texture2D tmpDepth(refAccess.getFormat(), 1, 1);
    tmpDepth.allocLevel(0);
    for (int y = 0; y < reference.getHeight(); y++)
    {
        for (int x = 0; x < reference.getWidth(); x++)
        {
            tcu::Vec4 result = tcu::Vec4(0.5f, 1.0f, 0.5f, 1.0f);

            for (int level = 0; level < DE_LENGTH_OF_ARRAY(depthLevelColors); level++)
            {
                tcu::clearDepth(tmpDepth.getLevel(0), (float)(level + 1) * 0.1f);
                if (tmpDepth.getLevel(0).getPixDepth(0, 0) < refAccess.getPixDepth(x, y))
                    result = depthLevelColors[level];
            }

            referenceScreen.getAccess().setPixel(result, x, y);
        }
    }

    GLU_CHECK_GLW_CALL(gl, bindFramebuffer(GL_FRAMEBUFFER, 0));
    GLU_CHECK_GLW_CALL(gl, finish());

    return tcu::pixelThresholdCompare(log, "Depth buffer rendering result", "Result from rendering with depth buffer",
                                      referenceScreen, screen, compareThreshold, tcu::COMPARE_LOG_RESULT);
}

bool GLESImageApi::RenderStencilbuffer::invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &img,
                                                   tcu::Texture2D &reference) const
{
    // Branch only taken in TryAll case
    if (reference.getFormat().order != tcu::TextureFormat::DS && reference.getFormat().order != tcu::TextureFormat::S)
        throw IllegalRendererException(); // Skip, interpreting non-stencil data as stencil data is not meaningful

    const glw::Functions &gl = api.m_gl;
    tcu::TestLog &log        = api.getLog();
    Framebuffer framebuffer(gl);
    Renderbuffer renderbufferColor(gl);
    Renderbuffer renderbufferStencil(gl);
    const tcu::RGBA compareThreshold(32, 32, 32, 32); // layer colors are far apart, large thresholds are ok
    const uint32_t numStencilBits =
        tcu::getTextureFormatBitDepth(
            tcu::getEffectiveDepthStencilTextureFormat(reference.getLevel(0).getFormat(), tcu::Sampler::MODE_STENCIL))
            .x();
    const uint32_t maxStencil = deBitMask32(0, numStencilBits);

    log << tcu::TestLog::Message << "Rendering with stencil buffer" << tcu::TestLog::EndMessage;

    GLU_CHECK_GLW_CALL(gl, bindFramebuffer(GL_FRAMEBUFFER, *framebuffer));

    GLU_CHECK_GLW_CALL(gl, bindRenderbuffer(GL_RENDERBUFFER, *renderbufferColor));
    GLU_CHECK_GLW_CALL(gl, renderbufferStorage(GL_RENDERBUFFER, GL_RGBA4, reference.getWidth(), reference.getHeight()));
    framebufferRenderbuffer(gl, GL_COLOR_ATTACHMENT0, *renderbufferColor);

    GLU_CHECK_GLW_CALL(gl, bindRenderbuffer(GL_RENDERBUFFER, *renderbufferStencil));
    imageTargetRenderbuffer(api.m_egl, gl, **img);
    framebufferRenderbuffer(gl, GL_STENCIL_ATTACHMENT, *renderbufferStencil);
    GLU_CHECK_GLW_CALL(gl, bindRenderbuffer(GL_RENDERBUFFER, 0));

    GLU_CHECK_GLW_CALL(gl, viewport(0, 0, reference.getWidth(), reference.getHeight()));

    // Render
    const char *vertexShader = "attribute highp vec2 a_coord;\n"
                               "void main(void) {\n"
                               "\tgl_Position = vec4(a_coord, 0.0, 1.0);\n"
                               "}\n";

    const char *fragmentShader = "uniform mediump vec4 u_color;\n"
                                 "void main(void) {\n"
                                 "\tgl_FragColor = u_color;\n"
                                 "}";

    Program program(gl, vertexShader, fragmentShader);
    TCU_CHECK(program.isOk());

    GLuint glProgram = program.getProgram();
    GLU_CHECK_GLW_CALL(gl, useProgram(glProgram));

    GLuint coordLoc = gl.getAttribLocation(glProgram, "a_coord");
    TCU_CHECK_MSG((int)coordLoc != -1, "Couldn't find attribute a_coord");

    GLuint colorLoc = gl.getUniformLocation(glProgram, "u_color");
    TCU_CHECK_MSG((int)colorLoc != (int)-1, "Couldn't find uniform u_color");

    GLU_CHECK_GLW_CALL(gl, clearColor(0.5f, 1.0f, 0.5f, 1.0f));
    GLU_CHECK_GLW_CALL(gl, clear(GL_COLOR_BUFFER_BIT));

    tcu::Vec4 stencilLevelColors[] = {
        tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f), tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f),
        tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f), tcu::Vec4(1.0f, 0.0f, 1.0f, 1.0f),

        tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f), tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f), tcu::Vec4(0.5f, 0.0f, 0.0f, 1.0f),
        tcu::Vec4(0.0f, 0.5f, 0.0f, 1.0f), tcu::Vec4(0.5f, 0.5f, 0.0f, 1.0f)};

    GLU_CHECK_GLW_CALL(gl, enableVertexAttribArray(coordLoc));
    GLU_CHECK_GLW_CALL(gl, vertexAttribPointer(coordLoc, 2, GL_FLOAT, GL_FALSE, 0, squareTriangleCoords));

    GLU_CHECK_GLW_CALL(gl, enable(GL_STENCIL_TEST));
    GLU_CHECK_GLW_CALL(gl, stencilOp(GL_KEEP, GL_KEEP, GL_KEEP));

    for (int level = 0; level < DE_LENGTH_OF_ARRAY(stencilLevelColors); level++)
    {
        const tcu::Vec4 color = stencilLevelColors[level];
        const int stencil     = (int)(((float)(level + 1) * 0.1f) * (float)maxStencil);

        GLU_CHECK_GLW_CALL(gl, stencilFunc(GL_LESS, stencil, 0xFFFFFFFFu));
        GLU_CHECK_GLW_CALL(gl, uniform4f(colorLoc, color.x(), color.y(), color.z(), color.w()));
        GLU_CHECK_GLW_CALL(gl, drawArrays(GL_TRIANGLES, 0, 6));
    }

    GLU_CHECK_GLW_CALL(gl, disable(GL_STENCIL_TEST));
    GLU_CHECK_GLW_CALL(gl, disableVertexAttribArray(coordLoc));

    const ConstPixelBufferAccess &refAccess = reference.getLevel(0);
    tcu::Surface screen(reference.getWidth(), reference.getHeight());
    tcu::Surface referenceScreen(reference.getWidth(), reference.getHeight());

    gl.readPixels(0, 0, screen.getWidth(), screen.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                  screen.getAccess().getDataPtr());

    for (int y = 0; y < reference.getHeight(); y++)
        for (int x = 0; x < reference.getWidth(); x++)
        {
            tcu::Vec4 result = tcu::Vec4(0.5f, 1.0f, 0.5f, 1.0f);

            for (int level = 0; level < DE_LENGTH_OF_ARRAY(stencilLevelColors); level++)
            {
                const int levelStencil = (int)(((float)(level + 1) * 0.1f) * (float)maxStencil);
                if (levelStencil < refAccess.getPixStencil(x, y))
                    result = stencilLevelColors[level];
            }

            referenceScreen.getAccess().setPixel(result, x, y);
        }

    GLU_CHECK_GLW_CALL(gl, bindFramebuffer(GL_FRAMEBUFFER, 0));
    GLU_CHECK_GLW_CALL(gl, finish());

    return tcu::pixelThresholdCompare(log, "StencilResult", "Result from rendering with stencil buffer",
                                      referenceScreen, screen, compareThreshold, tcu::COMPARE_LOG_RESULT);
}

bool GLESImageApi::RenderReadPixelsRenderbuffer::invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &img,
                                                            tcu::Texture2D &reference) const
{
    switch (glu::getInternalFormat(reference.getFormat()))
    {
    case GL_RGBA4:
    case GL_RGB5_A1:
    case GL_RGB565:
        break;
    default:
        // Skip, not in the list of allowed render buffer formats for GLES.
        throw tcu::NotSupportedError("Image format not allowed for glReadPixels.");
    }

    const glw::Functions &gl  = api.m_gl;
    const tcu::IVec4 bitDepth = tcu::getTextureFormatMantissaBitDepth(reference.getFormat());
    const tcu::IVec4 threshold(2 * (tcu::IVec4(1) << (tcu::IVec4(8) - bitDepth)));
    const tcu::RGBA threshold8((uint8_t)(de::clamp(threshold[0], 0, 255)), (uint8_t)(de::clamp(threshold[1], 0, 255)),
                               (uint8_t)(de::clamp(threshold[2], 0, 255)), (uint8_t)(de::clamp(threshold[3], 0, 255)));
    tcu::TestLog &log = api.getLog();
    Framebuffer framebuffer(gl);
    Renderbuffer renderbuffer(gl);
    tcu::Surface screen(reference.getWidth(), reference.getHeight());
    tcu::Surface refSurface(reference.getWidth(), reference.getHeight());

    log << tcu::TestLog::Message << "Reading with ReadPixels from renderbuffer" << tcu::TestLog::EndMessage;

    GLU_CHECK_GLW_CALL(gl, bindFramebuffer(GL_FRAMEBUFFER, *framebuffer));
    GLU_CHECK_GLW_CALL(gl, bindRenderbuffer(GL_RENDERBUFFER, *renderbuffer));
    imageTargetRenderbuffer(api.m_egl, gl, **img);

    GLU_EXPECT_NO_ERROR(gl.getError(), "imageTargetRenderbuffer");
    framebufferRenderbuffer(gl, GL_COLOR_ATTACHMENT0, *renderbuffer);
    GLU_EXPECT_NO_ERROR(gl.getError(), "framebufferRenderbuffer");

    GLU_CHECK_GLW_CALL(gl, viewport(0, 0, reference.getWidth(), reference.getHeight()));

    GLU_CHECK_GLW_CALL(gl, readPixels(0, 0, screen.getWidth(), screen.getHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                                      screen.getAccess().getDataPtr()));

    GLU_CHECK_GLW_CALL(gl, bindFramebuffer(GL_FRAMEBUFFER, 0));
    GLU_CHECK_GLW_CALL(gl, bindRenderbuffer(GL_RENDERBUFFER, 0));
    GLU_CHECK_GLW_CALL(gl, finish());

    tcu::copy(refSurface.getAccess(), reference.getLevel(0));

    return tcu::pixelThresholdCompare(log, "Renderbuffer read", "Result from reading renderbuffer", refSurface, screen,
                                      threshold8, tcu::COMPARE_LOG_RESULT);
}

bool GLESImageApi::RenderTryAll::invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &img,
                                            tcu::Texture2D &reference) const
{
    bool foundSupported = false;
    tcu::TestLog &log   = api.getLog();
    GLESImageApi::RenderTexture2D renderTex2D;
    GLESImageApi::RenderExternalTexture renderExternal;
    GLESImageApi::RenderExternalTextureSamplerArray renderExternalSamplerArray;
    GLESImageApi::RenderReadPixelsRenderbuffer renderReadPixels;
    GLESImageApi::RenderDepthbuffer renderDepth;
    GLESImageApi::RenderStencilbuffer renderStencil;
    Action *actions[] = {&renderTex2D,      &renderExternal, &renderExternalSamplerArray,
                         &renderReadPixels, &renderDepth,    &renderStencil};
    set<string> exts  = getSupportedExtensions(log, api.m_egl, api.m_display, api.m_gl);

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(actions); ++ndx)
    {
        try
        {
            const string ext = actions[ndx]->getRequiredExtension();

            if (!de::contains(exts, ext))
                TCU_THROW_EXPR(NotSupportedError, "Extension not supported", ext.c_str());

            if (!actions[ndx]->invoke(api, img, reference))
                return false;

            foundSupported = true;
        }
        catch (const tcu::NotSupportedError &error)
        {
            log << tcu::TestLog::Message << error.what() << tcu::TestLog::EndMessage;
        }
        catch (const IllegalRendererException &)
        {
            // not valid renderer
        }
    }

    if (!foundSupported)
        throw tcu::NotSupportedError("Rendering not supported", "", __FILE__, __LINE__);

    return true;
}

bool GLESImageApi::ModifyTexSubImage::invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &img,
                                                 tcu::Texture2D &reference) const
{
    const glw::Functions &gl = api.m_gl;
    tcu::TestLog &log        = api.getLog();
    glu::Texture srcTex(gl);
    const int xOffset = 8;
    const int yOffset = 16;
    const int xSize   = de::clamp(16, 0, reference.getWidth() - xOffset);
    const int ySize   = de::clamp(16, 0, reference.getHeight() - yOffset);
    tcu::Texture2D src(glu::mapGLTransferFormat(m_format, m_type), xSize, ySize);

    log << tcu::TestLog::Message << "Modifying EGLImage with gl.texSubImage2D" << tcu::TestLog::EndMessage;

    src.allocLevel(0);
    tcu::fillWithComponentGradients(src.getLevel(0), tcu::Vec4(0.0f, 0.0f, 0.0f, 0.0f),
                                    tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f));

    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_2D, *srcTex));
    imageTargetTexture2D(api.m_egl, gl, **img);
    GLU_CHECK_GLW_CALL(gl, texSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, src.getWidth(), src.getHeight(), m_format,
                                         m_type, src.getLevel(0).getDataPtr()));
    GLU_CHECK_GLW_CALL(gl, bindTexture(GL_TEXTURE_2D, 0));
    GLU_CHECK_GLW_CALL(gl, finish());

    tcu::copy(tcu::getSubregion(reference.getLevel(0), xOffset, yOffset, 0, xSize, ySize, 1), src.getLevel(0));

    return true;
}

bool GLESImageApi::ModifyRenderbuffer::invokeGLES(GLESImageApi &api, MovePtr<UniqueImage> &img,
                                                  tcu::Texture2D &reference) const
{
    const glw::Functions &gl = api.m_gl;
    tcu::TestLog &log        = api.getLog();
    glu::Framebuffer framebuffer(gl);
    glu::Renderbuffer renderbuffer(gl);

    log << tcu::TestLog::Message << "Modifying EGLImage with glClear to renderbuffer" << tcu::TestLog::EndMessage;

    GLU_CHECK_GLW_CALL(gl, bindFramebuffer(GL_FRAMEBUFFER, *framebuffer));
    GLU_CHECK_GLW_CALL(gl, bindRenderbuffer(GL_RENDERBUFFER, *renderbuffer));

    imageTargetRenderbuffer(api.m_egl, gl, **img);

    initializeRbo(api, *renderbuffer, reference);

    GLU_CHECK_GLW_CALL(gl, bindFramebuffer(GL_FRAMEBUFFER, 0));
    GLU_CHECK_GLW_CALL(gl, bindRenderbuffer(GL_RENDERBUFFER, 0));

    GLU_CHECK_GLW_CALL(gl, finish());

    return true;
}

void GLESImageApi::ModifyRenderbufferClearColor::initializeRbo(GLESImageApi &api, GLuint renderbuffer,
                                                               tcu::Texture2D &reference) const
{
    const glw::Functions &gl = api.m_gl;

    framebufferRenderbuffer(gl, GL_COLOR_ATTACHMENT0, renderbuffer);

    GLU_CHECK_GLW_CALL(gl, viewport(0, 0, reference.getWidth(), reference.getHeight()));
    GLU_CHECK_GLW_CALL(gl, clearColor(m_color.x(), m_color.y(), m_color.z(), m_color.w()));
    GLU_CHECK_GLW_CALL(gl, clear(GL_COLOR_BUFFER_BIT));

    tcu::clear(reference.getLevel(0), m_color);
}

void GLESImageApi::ModifyRenderbufferClearDepth::initializeRbo(GLESImageApi &api, GLuint renderbuffer,
                                                               tcu::Texture2D &reference) const
{
    const glw::Functions &gl = api.m_gl;

    framebufferRenderbuffer(gl, GL_DEPTH_ATTACHMENT, renderbuffer);

    GLU_CHECK_GLW_CALL(gl, viewport(0, 0, reference.getWidth(), reference.getHeight()));
    GLU_CHECK_GLW_CALL(gl, clearDepthf(m_depth));
    GLU_CHECK_GLW_CALL(gl, clear(GL_DEPTH_BUFFER_BIT));

    tcu::clearDepth(reference.getLevel(0), m_depth);
}

void GLESImageApi::ModifyRenderbufferClearStencil::initializeRbo(GLESImageApi &api, GLuint renderbuffer,
                                                                 tcu::Texture2D &reference) const
{
    const glw::Functions &gl = api.m_gl;

    framebufferRenderbuffer(gl, GL_STENCIL_ATTACHMENT, renderbuffer);

    GLU_CHECK_GLW_CALL(gl, viewport(0, 0, reference.getWidth(), reference.getHeight()));
    GLU_CHECK_GLW_CALL(gl, clearStencil(m_stencil));
    GLU_CHECK_GLW_CALL(gl, clear(GL_STENCIL_BUFFER_BIT));

    tcu::clearStencil(reference.getLevel(0), m_stencil);
}

class ImageFormatCase : public TestCase, private glu::CallLogWrapper
{
public:
    ImageFormatCase(EglTestContext &eglTestCtx, const TestSpec &spec);
    ~ImageFormatCase(void);

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

private:
    EGLConfig getConfig(void);

    const TestSpec m_spec;

    vector<ImageApi *> m_apiContexts;

    EGLDisplay m_display;
    eglu::NativeWindow *m_window;
    EGLSurface m_surface;
    EGLConfig m_config;
    int m_curIter;
    MovePtr<UniqueImage> m_img;
    tcu::Texture2D m_refImg;
    glw::Functions m_gl;
};

EGLConfig ImageFormatCase::getConfig(void)
{
    const GLint glesApi       = m_spec.contexts[0] == TestSpec::API_GLES3 ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT;
    const EGLint attribList[] = {EGL_RENDERABLE_TYPE, glesApi, EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
                                 EGL_RED_SIZE,        8,       EGL_BLUE_SIZE,    8,
                                 EGL_GREEN_SIZE,      8,       EGL_ALPHA_SIZE,   8,
                                 EGL_DEPTH_SIZE,      8,       EGL_NONE};

    return eglu::chooseSingleConfig(m_eglTestCtx.getLibrary(), m_display, attribList);
}

ImageFormatCase::ImageFormatCase(EglTestContext &eglTestCtx, const TestSpec &spec)
    : TestCase(eglTestCtx, spec.name.c_str(), spec.desc.c_str())
    , glu::CallLogWrapper(m_gl, eglTestCtx.getTestContext().getLog())
    , m_spec(spec)
    , m_display(EGL_NO_DISPLAY)
    , m_window(DE_NULL)
    , m_surface(EGL_NO_SURFACE)
    , m_config(0)
    , m_curIter(0)
    , m_refImg(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8), 1, 1)
{
}

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

void ImageFormatCase::checkExtensions(void)
{
    set<string> exts = getSupportedExtensions(getLog(), m_eglTestCtx.getLibrary(), m_display, m_gl);

    for (int operationNdx = 0; operationNdx < (int)m_spec.operations.size(); operationNdx++)
    {
        const TestSpec::Operation &op = m_spec.operations[operationNdx];
        const string ext              = op.action->getRequiredExtension();

        if (!de::contains(exts, ext))
            TCU_THROW_EXPR(NotSupportedError, "Extension not supported", ext.c_str());
    }
}

void ImageFormatCase::init(void)
{
    const Library &egl = m_eglTestCtx.getLibrary();
    const eglu::NativeWindowFactory &windowFactory =
        eglu::selectNativeWindowFactory(m_eglTestCtx.getNativeDisplayFactory(), m_testCtx.getCommandLine());

    try
    {
        m_display = eglu::getAndInitDisplay(m_eglTestCtx.getNativeDisplay());

        // GLES3 requires either EGL 1.5 or EGL_KHR_create_context extension.
        if (m_spec.contexts[0] == TestSpec::API_GLES3 && eglu::getVersion(egl, m_display) < eglu::Version(1, 5))
        {
            set<string> exts;
            const vector<string> eglExts = eglu::getDisplayExtensions(egl, m_display);
            exts.insert(eglExts.begin(), eglExts.end());

            if (!de::contains(exts, "EGL_KHR_create_context"))
            {
                getLog() << tcu::TestLog::Message << "EGL version is under 1.5 and the test is using OpenGL ES 3.2."
                         << "This requires EGL_KHR_create_context extension." << tcu::TestLog::EndMessage;
                TCU_THROW(NotSupportedError, "Extension not supported: EGL_KHR_create_context");
            }
        }

        m_config = getConfig();
        m_window = windowFactory.createWindow(
            &m_eglTestCtx.getNativeDisplay(), m_display, m_config, DE_NULL,
            eglu::WindowParams(480, 480, eglu::parseWindowVisibility(m_testCtx.getCommandLine())));
        m_surface = eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *m_window, m_display, m_config, DE_NULL);

        {
            const char *extensions[] = {"GL_OES_EGL_image"};
            int major                = 2;
            int minor                = 0;

            if (m_spec.contexts[0] == TestSpec::API_GLES3)
            {
                major = 3;
                minor = 2;
            }
            m_eglTestCtx.initGLFunctions(&m_gl, glu::ApiType::es(major, minor), DE_LENGTH_OF_ARRAY(extensions),
                                         &extensions[0]);
        }

        for (int contextNdx = 0; contextNdx < (int)m_spec.contexts.size(); contextNdx++)
        {
            ImageApi *api = DE_NULL;
            switch (m_spec.contexts[contextNdx])
            {
            case TestSpec::API_GLES2:
            {
                api = new GLESImageApi(egl, m_gl, contextNdx, getLog(), m_display, m_surface, m_config, 2);
                break;
            }

            case TestSpec::API_GLES3:
            {
                api = new GLESImageApi(egl, m_gl, contextNdx, getLog(), m_display, m_surface, m_config, 3);
                break;
            }

            default:
                DE_ASSERT(false);
                break;
            }
            m_apiContexts.push_back(api);
        }
        checkExtensions();
    }
    catch (...)
    {
        deinit();
        throw;
    }
}

void ImageFormatCase::deinit(void)
{
    const Library &egl = m_eglTestCtx.getLibrary();

    m_img.clear();

    for (int contexNdx = 0; contexNdx < (int)m_apiContexts.size(); contexNdx++)
        delete m_apiContexts[contexNdx];

    m_apiContexts.clear();

    if (m_surface != EGL_NO_SURFACE)
    {
        egl.destroySurface(m_display, m_surface);
        m_surface = EGL_NO_SURFACE;
    }

    delete m_window;
    m_window = DE_NULL;

    if (m_display != EGL_NO_DISPLAY)
    {
        egl.terminate(m_display);
        m_display = EGL_NO_DISPLAY;
    }
}

TestCase::IterateResult ImageFormatCase::iterate(void)
{
    const TestSpec::Operation &op = m_spec.operations[m_curIter++];
    ImageApi &api                 = *m_apiContexts[op.apiIndex];
    const bool isOk               = op.action->invoke(api, m_img, m_refImg);

    if (isOk && m_curIter < (int)m_spec.operations.size())
        return CONTINUE;
    else if (isOk)
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");

    return STOP;
}

struct LabeledAction
{
    string label;
    MovePtr<Action> action;
};

// A simple vector mockup that we need because MovePtr isn't copy-constructible.
struct LabeledActions
{
    LabeledActions(void) : m_numActions(0)
    {
    }
    LabeledAction &operator[](int ndx)
    {
        DE_ASSERT(0 <= ndx && ndx < m_numActions);
        return m_actions[ndx];
    }
    void add(const string &label, MovePtr<Action> action);
    int size(void) const
    {
        return m_numActions;
    }

private:
    LabeledAction m_actions[64];
    int m_numActions;
};

void LabeledActions::add(const string &label, MovePtr<Action> action)
{
    DE_ASSERT(m_numActions < DE_LENGTH_OF_ARRAY(m_actions));
    m_actions[m_numActions].label  = label;
    m_actions[m_numActions].action = action;
    ++m_numActions;
}

class ImageTests : public TestCaseGroup
{
protected:
    ImageTests(EglTestContext &eglTestCtx, const string &name, const string &desc)
        : TestCaseGroup(eglTestCtx, name.c_str(), desc.c_str())
    {
    }

    void addCreateTexture(const string &name, EGLenum source, GLenum internalFormat, GLenum format, GLenum type);
    void addCreateRenderbuffer(const string &name, GLenum format);
    void addCreateAndroidNative(const string &name, GLenum format, bool isYUV);
    void addCreateAndroidNativeArray(const string &name, GLenum format, uint32_t numLayers);
    void addCreateTexture2DActions(const string &prefix);
    void addCreateTextureCubemapActions(const string &suffix, GLenum internalFormat, GLenum format, GLenum type);
    void addCreateRenderbufferActions(void);
    void addCreateAndroidNativeActions(void);

    LabeledActions m_createActions;
};

void ImageTests::addCreateTexture(const string &name, EGLenum source, GLenum internalFormat, GLenum format, GLenum type)
{
    m_createActions.add(name, MovePtr<Action>(new GLESImageApi::Create(
                                  createTextureImageSource(source, internalFormat, format, type))));
}

void ImageTests::addCreateRenderbuffer(const string &name, GLenum format)
{
    m_createActions.add(name, MovePtr<Action>(new GLESImageApi::Create(createRenderbufferImageSource(format))));
}

void ImageTests::addCreateAndroidNative(const string &name, GLenum format, bool isYUV = false)
{
    m_createActions.add(name,
                        MovePtr<Action>(new GLESImageApi::Create(createAndroidNativeImageSource(format, 1u, isYUV))));
}

void ImageTests::addCreateAndroidNativeArray(const string &name, GLenum format, uint32_t numLayers)
{
    m_createActions.add(name, MovePtr<Action>(new GLESImageApi::Create(
                                  createAndroidNativeImageSource(format, numLayers, false), numLayers)));
}

void ImageTests::addCreateTexture2DActions(const string &prefix)
{
    addCreateTexture(prefix + "rgb8", EGL_GL_TEXTURE_2D_KHR, GL_RGB, GL_RGB, GL_UNSIGNED_BYTE);
    addCreateTexture(prefix + "rgb565", EGL_GL_TEXTURE_2D_KHR, GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5);
    addCreateTexture(prefix + "rgba8", EGL_GL_TEXTURE_2D_KHR, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
    addCreateTexture(prefix + "rgb5_a1", EGL_GL_TEXTURE_2D_KHR, GL_RGBA, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1);
    addCreateTexture(prefix + "rgba4", EGL_GL_TEXTURE_2D_KHR, GL_RGBA, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4);
}

void ImageTests::addCreateTextureCubemapActions(const string &suffix, GLenum internalFormat, GLenum format, GLenum type)
{
    addCreateTexture("cubemap_positive_x" + suffix, EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X_KHR, internalFormat, format,
                     type);
    addCreateTexture("cubemap_positive_y" + suffix, EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y_KHR, internalFormat, format,
                     type);
    addCreateTexture("cubemap_positive_z" + suffix, EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z_KHR, internalFormat, format,
                     type);
    addCreateTexture("cubemap_negative_x" + suffix, EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X_KHR, internalFormat, format,
                     type);
    addCreateTexture("cubemap_negative_y" + suffix, EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_KHR, internalFormat, format,
                     type);
    addCreateTexture("cubemap_negative_z" + suffix, EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_KHR, internalFormat, format,
                     type);
}

void ImageTests::addCreateRenderbufferActions(void)
{
    addCreateRenderbuffer("renderbuffer_rgba4", GL_RGBA4);
    addCreateRenderbuffer("renderbuffer_rgb5_a1", GL_RGB5_A1);
    addCreateRenderbuffer("renderbuffer_rgb565", GL_RGB565);
    addCreateRenderbuffer("renderbuffer_depth16", GL_DEPTH_COMPONENT16);
    addCreateRenderbuffer("renderbuffer_stencil", GL_STENCIL_INDEX8);
}

void ImageTests::addCreateAndroidNativeActions(void)
{
    addCreateAndroidNative("android_native_rgba4", GL_RGBA4);
    addCreateAndroidNative("android_native_rgb5_a1", GL_RGB5_A1);
    addCreateAndroidNative("android_native_rgb565", GL_RGB565);
    addCreateAndroidNative("android_native_rgb8", GL_RGB8);
    addCreateAndroidNative("android_native_rgba8", GL_RGBA8);
    addCreateAndroidNative("android_native_d16", GL_DEPTH_COMPONENT16);
    addCreateAndroidNative("android_native_d24", GL_DEPTH_COMPONENT24);
    addCreateAndroidNative("android_native_d24s8", GL_DEPTH24_STENCIL8);
    addCreateAndroidNative("android_native_d32f", GL_DEPTH_COMPONENT32F);
    addCreateAndroidNative("android_native_d32fs8", GL_DEPTH32F_STENCIL8);
    addCreateAndroidNative("android_native_rgb10a2", GL_RGB10_A2);
    addCreateAndroidNative("android_native_rgba16f", GL_RGBA16F);
    addCreateAndroidNative("android_native_s8", GL_STENCIL_INDEX8);
    addCreateAndroidNative("android_native_yuv420", GL_RGBA8, true);

    addCreateAndroidNativeArray("android_native_array_rgba4", GL_RGBA4, 4u);
    addCreateAndroidNativeArray("android_native_array_rgb5_a1", GL_RGB5_A1, 4u);
    addCreateAndroidNativeArray("android_native_array_rgb565", GL_RGB565, 4u);
    addCreateAndroidNativeArray("android_native_array_rgb8", GL_RGB8, 4u);
    addCreateAndroidNativeArray("android_native_array_rgba8", GL_RGBA8, 4u);
}

class RenderTests : public ImageTests
{
protected:
    RenderTests(EglTestContext &eglTestCtx, const string &name, const string &desc) : ImageTests(eglTestCtx, name, desc)
    {
    }

    void addRenderActions(void);
    LabeledActions m_renderActions;
};

void RenderTests::addRenderActions(void)
{
    m_renderActions.add("texture", MovePtr<Action>(new GLESImageApi::RenderTexture2D()));
    m_renderActions.add("texture_array", MovePtr<Action>(new GLESImageApi::RenderTexture2DArray()));
    m_renderActions.add("read_pixels", MovePtr<Action>(new GLESImageApi::RenderReadPixelsRenderbuffer()));
    m_renderActions.add("depth_buffer", MovePtr<Action>(new GLESImageApi::RenderDepthbuffer()));
    m_renderActions.add("stencil_buffer", MovePtr<Action>(new GLESImageApi::RenderStencilbuffer()));
    m_renderActions.add("yuv_texture", MovePtr<Action>(new GLESImageApi::RenderYUVTexture()));
    m_renderActions.add("render_sample_texture_array", MovePtr<Action>(new GLESImageApi::RenderSampleTexture2DArray()));
}

class SimpleCreationTests : public RenderTests
{
public:
    SimpleCreationTests(EglTestContext &eglTestCtx, const string &name, const string &desc)
        : RenderTests(eglTestCtx, name, desc)
    {
    }
    void init(void);
};

bool isDepthFormat(GLenum format)
{
    switch (format)
    {
    case GL_RGB:
    case GL_RGB8:
    case GL_RGB565:
    case GL_RGBA:
    case GL_RGBA4:
    case GL_RGBA8:
    case GL_RGB5_A1:
    case GL_RGB10_A2:
    case GL_RGBA16F:
        return false;

    case GL_DEPTH_COMPONENT16:
    case GL_DEPTH_COMPONENT24:
    case GL_DEPTH_COMPONENT32:
    case GL_DEPTH_COMPONENT32F:
    case GL_DEPTH24_STENCIL8:
    case GL_DEPTH32F_STENCIL8:
        return true;

    case GL_STENCIL_INDEX8:
        return false;

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

bool isStencilFormat(GLenum format)
{
    switch (format)
    {
    case GL_RGB:
    case GL_RGB8:
    case GL_RGB565:
    case GL_RGBA:
    case GL_RGBA4:
    case GL_RGBA8:
    case GL_RGB5_A1:
    case GL_RGB10_A2:
    case GL_RGBA16F:
        return false;

    case GL_DEPTH_COMPONENT16:
    case GL_DEPTH_COMPONENT24:
    case GL_DEPTH_COMPONENT32:
    case GL_DEPTH_COMPONENT32F:
        return false;

    case GL_STENCIL_INDEX8:
    case GL_DEPTH24_STENCIL8:
    case GL_DEPTH32F_STENCIL8:
        return true;

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

bool isCompatibleCreateAndRenderActions(const Action &create, const Action &render)
{
    if (const GLESImageApi::Create *glesCreate = dynamic_cast<const GLESImageApi::Create *>(&create))
    {
        bool yuvFormatTest = glesCreate->isYUVFormatImage();
        // this path only for none-yuv format tests
        if (!yuvFormatTest)
        {
            const GLenum createFormat = glesCreate->getEffectiveFormat();

            if (dynamic_cast<const GLESImageApi::RenderTexture2DArray *>(&render))
            {
                // Makes sense only for texture arrays.
                if (glesCreate->getNumLayers() <= 1u)
                    return false;
            }
            else if (glesCreate->getNumLayers() != 1u)
            {
                // Skip other render actions for texture arrays.
                return false;
            }

            if (dynamic_cast<const GLESImageApi::RenderTexture2D *>(&render))
            {
                // GLES does not have depth or stencil textures
                if (isDepthFormat(createFormat) || isStencilFormat(createFormat))
                    return false;
            }

            if (dynamic_cast<const GLESImageApi::RenderReadPixelsRenderbuffer *>(&render))
            {
                // GLES does not support readPixels for depth or stencil.
                if (isDepthFormat(createFormat) || isStencilFormat(createFormat))
                    return false;
            }

            if (dynamic_cast<const GLESImageApi::RenderDepthbuffer *>(&render))
            {
                // Copying non-depth data to depth renderbuffer and expecting meaningful
                // results just doesn't make any sense.
                if (!isDepthFormat(createFormat))
                    return false;
            }

            if (dynamic_cast<const GLESImageApi::RenderStencilbuffer *>(&render))
            {
                // Copying non-stencil data to stencil renderbuffer and expecting meaningful
                // results just doesn't make any sense.
                if (!isStencilFormat(createFormat))
                    return false;
            }

            if (dynamic_cast<const GLESImageApi::RenderYUVTexture *>(&render))
            {
                // In yuv path rendering with non-yuv format native buffer and expecting meaningful
                // results just doesn't make any sense
                return false;
            }

            return true;
        }
        else if (dynamic_cast<const GLESImageApi::RenderYUVTexture *>(&render))
        {
            return true;
        }
    }
    else
        DE_ASSERT(false);

    return false;
}

void SimpleCreationTests::init(void)
{
    addCreateTexture2DActions("texture_");
    addCreateTextureCubemapActions("_rgba", GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
    addCreateTextureCubemapActions("_rgb", GL_RGB, GL_RGB, GL_UNSIGNED_BYTE);
    addCreateRenderbufferActions();
    addCreateAndroidNativeActions();
    addRenderActions();

    for (int createNdx = 0; createNdx < m_createActions.size(); createNdx++)
    {
        const LabeledAction &createAction = m_createActions[createNdx];

        for (int renderNdx = 0; renderNdx < m_renderActions.size(); renderNdx++)
        {
            const LabeledAction &renderAction = m_renderActions[renderNdx];
            TestSpec spec;

            if (!isCompatibleCreateAndRenderActions(*createAction.action, *renderAction.action))
                continue;

            if (dynamic_cast<const GLESImageApi::RenderTexture2DArray *>(renderAction.action.get()) ||
                dynamic_cast<const GLESImageApi::RenderYUVTexture *>(renderAction.action.get()) ||
                dynamic_cast<const GLESImageApi::RenderSampleTexture2DArray *>(renderAction.action.get()))
            {
                // Texture array tests require GLES3.
                spec.name = std::string("gles3_") + createAction.label + "_" + renderAction.label;
                spec.contexts.push_back(TestSpec::API_GLES3);
            }
            else
            {
                spec.name = std::string("gles2_") + createAction.label + "_" + renderAction.label;
                spec.contexts.push_back(TestSpec::API_GLES2);
            }

            spec.desc = spec.name;
            spec.operations.push_back(TestSpec::Operation(0, *createAction.action));
            spec.operations.push_back(TestSpec::Operation(0, *renderAction.action));

            addChild(new ImageFormatCase(m_eglTestCtx, spec));
        }
    }
}

TestCaseGroup *createSimpleCreationTests(EglTestContext &eglTestCtx, const string &name, const string &desc)
{
    return new SimpleCreationTests(eglTestCtx, name, desc);
}

bool isCompatibleFormats(GLenum createFormat, GLenum modifyFormat, GLenum modifyType)
{
    switch (modifyFormat)
    {
    case GL_RGB:
        switch (modifyType)
        {
        case GL_UNSIGNED_BYTE:
            return createFormat == GL_RGB || createFormat == GL_RGB8 || createFormat == GL_RGB565 ||
                   createFormat == GL_SRGB8;

        case GL_BYTE:
            return createFormat == GL_RGB8_SNORM;

        case GL_UNSIGNED_SHORT_5_6_5:
            return createFormat == GL_RGB || createFormat == GL_RGB565;

        case GL_UNSIGNED_INT_10F_11F_11F_REV:
            return createFormat == GL_R11F_G11F_B10F;

        case GL_UNSIGNED_INT_5_9_9_9_REV:
            return createFormat == GL_RGB9_E5;

        case GL_HALF_FLOAT:
            return createFormat == GL_RGB16F || createFormat == GL_R11F_G11F_B10F || createFormat == GL_RGB9_E5;

        case GL_FLOAT:
            return createFormat == GL_RGB16F || createFormat == GL_RGB32F || createFormat == GL_R11F_G11F_B10F ||
                   createFormat == GL_RGB9_E5;

        default:
            DE_FATAL("Unknown modify type");
            return false;
        }

    case GL_RGBA:
        switch (modifyType)
        {
        case GL_UNSIGNED_BYTE:
            return createFormat == GL_RGBA8 || createFormat == GL_RGB5_A1 || createFormat == GL_RGBA4 ||
                   createFormat == GL_SRGB8_ALPHA8 || createFormat == GL_RGBA;

        case GL_UNSIGNED_SHORT_4_4_4_4:
            return createFormat == GL_RGBA4 || createFormat == GL_RGBA;

        case GL_UNSIGNED_SHORT_5_5_5_1:
            return createFormat == GL_RGB5_A1 || createFormat == GL_RGBA;

        case GL_UNSIGNED_INT_2_10_10_10_REV:
            return createFormat == GL_RGB10_A2 || createFormat == GL_RGB5_A1;

        case GL_HALF_FLOAT:
            return createFormat == GL_RGBA16F;

        case GL_FLOAT:
            return createFormat == GL_RGBA16F || createFormat == GL_RGBA32F;

        default:
            DE_FATAL("Unknown modify type");
            return false;
        }

    default:
        DE_FATAL("Unknown modify format");
        return false;
    }
}

bool isCompatibleCreateAndModifyActions(const Action &create, const Action &modify)
{
    if (const GLESImageApi::Create *glesCreate = dynamic_cast<const GLESImageApi::Create *>(&create))
    {
        // No modify tests for texture arrays.
        if (glesCreate->getNumLayers() > 1u)
            return false;
        // No modify tests for yuv format image.
        if (glesCreate->isYUVFormatImage())
            return false;

        const GLenum createFormat = glesCreate->getEffectiveFormat();

        if (const GLESImageApi::ModifyTexSubImage *glesTexSubImageModify =
                dynamic_cast<const GLESImageApi::ModifyTexSubImage *>(&modify))
        {
            const GLenum modifyFormat = glesTexSubImageModify->getFormat();
            const GLenum modifyType   = glesTexSubImageModify->getType();

            return isCompatibleFormats(createFormat, modifyFormat, modifyType);
        }

        if (dynamic_cast<const GLESImageApi::ModifyRenderbufferClearColor *>(&modify))
        {
            // reintepreting color as non-color is not meaningful
            if (isDepthFormat(createFormat) || isStencilFormat(createFormat))
                return false;
        }

        if (dynamic_cast<const GLESImageApi::ModifyRenderbufferClearDepth *>(&modify))
        {
            // reintepreting depth as non-depth is not meaningful
            if (!isDepthFormat(createFormat))
                return false;
        }

        if (dynamic_cast<const GLESImageApi::ModifyRenderbufferClearStencil *>(&modify))
        {
            // reintepreting stencil as non-stencil is not meaningful
            if (!isStencilFormat(createFormat))
                return false;
        }

        return true;
    }
    else
        DE_ASSERT(false);

    return false;
}

class MultiContextRenderTests : public RenderTests
{
public:
    MultiContextRenderTests(EglTestContext &eglTestCtx, const string &name, const string &desc);
    void init(void);
    void addClearActions(void);

private:
    LabeledActions m_clearActions;
};

MultiContextRenderTests::MultiContextRenderTests(EglTestContext &eglTestCtx, const string &name, const string &desc)
    : RenderTests(eglTestCtx, name, desc)
{
}

void MultiContextRenderTests::addClearActions(void)
{
    m_clearActions.add("clear_color", MovePtr<Action>(new GLESImageApi::ModifyRenderbufferClearColor(
                                          tcu::Vec4(0.8f, 0.2f, 0.9f, 1.0f))));
    m_clearActions.add("clear_depth", MovePtr<Action>(new GLESImageApi::ModifyRenderbufferClearDepth(0.75f)));
    m_clearActions.add("clear_stencil", MovePtr<Action>(new GLESImageApi::ModifyRenderbufferClearStencil(97)));
}

void MultiContextRenderTests::init(void)
{
    addCreateTexture2DActions("texture_");
    addCreateTextureCubemapActions("_rgba8", GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
    addCreateTextureCubemapActions("_rgb8", GL_RGB, GL_RGB, GL_UNSIGNED_BYTE);
    addCreateRenderbufferActions();
    addCreateAndroidNativeActions();
    addRenderActions();
    addClearActions();

    for (int createNdx = 0; createNdx < m_createActions.size(); createNdx++)
        for (int renderNdx = 0; renderNdx < m_renderActions.size(); renderNdx++)
            for (int clearNdx = 0; clearNdx < m_clearActions.size(); clearNdx++)
            {
                const LabeledAction &createAction = m_createActions[createNdx];
                const LabeledAction &renderAction = m_renderActions[renderNdx];
                const LabeledAction &clearAction  = m_clearActions[clearNdx];
                TestSpec spec;

                if (!isCompatibleCreateAndRenderActions(*createAction.action, *renderAction.action))
                    continue;
                if (!isCompatibleCreateAndModifyActions(*createAction.action, *clearAction.action))
                    continue;

                spec.name = std::string("gles2_") + createAction.label + "_" + renderAction.label;

                const GLESImageApi::Create *glesCreate =
                    dynamic_cast<const GLESImageApi::Create *>(createAction.action.get());

                if (!glesCreate)
                    DE_FATAL("Dynamic casting to GLESImageApi::Create* failed");

                const GLenum createFormat = glesCreate->getEffectiveFormat();

                if (isDepthFormat(createFormat) && isStencilFormat(createFormat))
                {
                    // Combined depth and stencil format. Add the clear action label to avoid test
                    // name clashes.
                    spec.name += std::string("_") + clearAction.label;
                }

                spec.desc = spec.name;

                spec.contexts.push_back(TestSpec::API_GLES2);
                spec.contexts.push_back(TestSpec::API_GLES2);

                spec.operations.push_back(TestSpec::Operation(0, *createAction.action));
                spec.operations.push_back(TestSpec::Operation(0, *renderAction.action));
                spec.operations.push_back(TestSpec::Operation(0, *clearAction.action));
                spec.operations.push_back(TestSpec::Operation(1, *createAction.action));
                spec.operations.push_back(TestSpec::Operation(0, *renderAction.action));
                spec.operations.push_back(TestSpec::Operation(1, *renderAction.action));

                addChild(new ImageFormatCase(m_eglTestCtx, spec));
            }
}

TestCaseGroup *createMultiContextRenderTests(EglTestContext &eglTestCtx, const string &name, const string &desc)
{
    return new MultiContextRenderTests(eglTestCtx, name, desc);
}

class ModifyTests : public ImageTests
{
public:
    ModifyTests(EglTestContext &eglTestCtx, const string &name, const string &desc) : ImageTests(eglTestCtx, name, desc)
    {
    }

    void init(void);

protected:
    void addModifyActions(void);

    LabeledActions m_modifyActions;
    GLESImageApi::RenderTryAll m_renderAction;
};

void ModifyTests::addModifyActions(void)
{
    m_modifyActions.add("tex_subimage_rgb8",
                        MovePtr<Action>(new GLESImageApi::ModifyTexSubImage(GL_RGB, GL_UNSIGNED_BYTE)));
    m_modifyActions.add("tex_subimage_rgb565",
                        MovePtr<Action>(new GLESImageApi::ModifyTexSubImage(GL_RGB, GL_UNSIGNED_SHORT_5_6_5)));
    m_modifyActions.add("tex_subimage_rgba8",
                        MovePtr<Action>(new GLESImageApi::ModifyTexSubImage(GL_RGBA, GL_UNSIGNED_BYTE)));
    m_modifyActions.add("tex_subimage_rgb5_a1",
                        MovePtr<Action>(new GLESImageApi::ModifyTexSubImage(GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1)));
    m_modifyActions.add("tex_subimage_rgba4",
                        MovePtr<Action>(new GLESImageApi::ModifyTexSubImage(GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4)));

    m_modifyActions.add("renderbuffer_clear_color", MovePtr<Action>(new GLESImageApi::ModifyRenderbufferClearColor(
                                                        tcu::Vec4(0.4f, 0.5f, 0.6f, 1.0f))));
    m_modifyActions.add("renderbuffer_clear_depth",
                        MovePtr<Action>(new GLESImageApi::ModifyRenderbufferClearDepth(0.7f)));
    m_modifyActions.add("renderbuffer_clear_stencil",
                        MovePtr<Action>(new GLESImageApi::ModifyRenderbufferClearStencil(78)));
}

void ModifyTests::init(void)
{
    addCreateTexture2DActions("tex_");
    addCreateRenderbufferActions();
    addCreateAndroidNativeActions();
    addModifyActions();

    for (int createNdx = 0; createNdx < m_createActions.size(); createNdx++)
    {
        LabeledAction &createAction = m_createActions[createNdx];

        for (int modifyNdx = 0; modifyNdx < m_modifyActions.size(); modifyNdx++)
        {
            LabeledAction &modifyAction = m_modifyActions[modifyNdx];

            if (!isCompatibleCreateAndModifyActions(*createAction.action, *modifyAction.action))
                continue;

            TestSpec spec;
            spec.name = createAction.label + "_" + modifyAction.label;
            spec.desc = "gles2_tex_sub_image";

            spec.contexts.push_back(TestSpec::API_GLES2);

            spec.operations.push_back(TestSpec::Operation(0, *createAction.action));
            spec.operations.push_back(TestSpec::Operation(0, m_renderAction));
            spec.operations.push_back(TestSpec::Operation(0, *modifyAction.action));
            spec.operations.push_back(TestSpec::Operation(0, m_renderAction));

            addChild(new ImageFormatCase(m_eglTestCtx, spec));
        }
    }
}

TestCaseGroup *createModifyTests(EglTestContext &eglTestCtx, const string &name, const string &desc)
{
    return new ModifyTests(eglTestCtx, name, desc);
}

} // namespace Image
} // namespace egl
} // namespace deqp
