/*-------------------------------------------------------------------------
 * 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 gles2 sharing threaded tests
 *//*--------------------------------------------------------------------*/

#include "teglGLES2SharingThreadedTests.hpp"

#include "tcuTestLog.hpp"
#include "tcuThreadUtil.hpp"

#include "deRandom.hpp"
#include "deThread.hpp"
#include "deSharedPtr.hpp"
#include "deMutex.hpp"
#include "deSemaphore.hpp"
#include "deStringUtil.hpp"

#include "deClock.h"
#include "deString.h"
#include "deMemory.h"
#include "deMath.h"

#include "gluDefs.hpp"

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

#include "egluUtil.hpp"

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

#include <vector>
#include <string>
#include <memory>
#include <sstream>

using de::SharedPtr;
using std::string;
using std::vector;

using namespace glw;
using namespace eglw;

namespace deqp
{
namespace egl
{

namespace GLES2ThreadTest
{

class Texture;
class Buffer;
class Shader;
class Program;
class GLES2ResourceManager
{
public:
    SharedPtr<Texture> popTexture(int index);
    const SharedPtr<Texture> getTexture(int index) const
    {
        return m_textures[index];
    }
    void addTexture(SharedPtr<Texture> texture)
    {
        m_textures.push_back(texture);
    }
    int getTextureCount(void) const
    {
        return (int)m_textures.size();
    }

    SharedPtr<Buffer> popBuffer(int index);
    const SharedPtr<Buffer> getBuffer(int index) const
    {
        return m_buffers[index];
    }
    void addBuffer(SharedPtr<Buffer> buffer)
    {
        m_buffers.push_back(buffer);
    }
    int getBufferCount(void) const
    {
        return (int)m_buffers.size();
    }

    SharedPtr<Shader> popShader(int index);
    const SharedPtr<Shader> getShader(int index) const
    {
        return m_shaders[index];
    }
    void addShader(SharedPtr<Shader> shader)
    {
        m_shaders.push_back(shader);
    }
    int getShaderCount(void) const
    {
        return (int)m_shaders.size();
    }

    SharedPtr<Program> popProgram(int index);
    const SharedPtr<Program> getProgram(int index) const
    {
        return m_programs[index];
    }
    void addProgram(SharedPtr<Program> program)
    {
        m_programs.push_back(program);
    }
    int getProgramCount(void) const
    {
        return (int)m_programs.size();
    }

private:
    std::vector<SharedPtr<Texture>> m_textures;
    std::vector<SharedPtr<Buffer>> m_buffers;
    std::vector<SharedPtr<Shader>> m_shaders;
    std::vector<SharedPtr<Program>> m_programs;
};

SharedPtr<Texture> GLES2ResourceManager::popTexture(int index)
{
    SharedPtr<Texture> texture = m_textures[index];

    m_textures.erase(m_textures.begin() + index);

    return texture;
}

SharedPtr<Buffer> GLES2ResourceManager::popBuffer(int index)
{
    SharedPtr<Buffer> buffer = m_buffers[index];

    m_buffers.erase(m_buffers.begin() + index);

    return buffer;
}

SharedPtr<Shader> GLES2ResourceManager::popShader(int index)
{
    SharedPtr<Shader> shader = m_shaders[index];

    m_shaders.erase(m_shaders.begin() + index);

    return shader;
}

SharedPtr<Program> GLES2ResourceManager::popProgram(int index)
{
    SharedPtr<Program> program = m_programs[index];

    m_programs.erase(m_programs.begin() + index);

    return program;
}

class GLES2Context : public tcu::ThreadUtil::Object
{
public:
    GLES2Context(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<GLES2ResourceManager> resourceManager);
    ~GLES2Context(void);

    // Call generation time attributes
    SharedPtr<GLES2ResourceManager> resourceManager;

    // Run time attributes
    EGLDisplay display;
    EGLContext context;

    struct
    {
        glEGLImageTargetTexture2DOESFunc imageTargetTexture2D;
    } glExtensions;

private:
    GLES2Context(const GLES2Context &);
    GLES2Context &operator=(const GLES2Context &);
};

GLES2Context::GLES2Context(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<GLES2ResourceManager> resourceManager_)
    : tcu::ThreadUtil::Object("Context", event)
    , resourceManager(resourceManager_)
    , display(EGL_NO_DISPLAY)
    , context(EGL_NO_CONTEXT)
{
    glExtensions.imageTargetTexture2D = DE_NULL;
}

GLES2Context::~GLES2Context(void)
{
}

class Surface : public tcu::ThreadUtil::Object
{
public:
    Surface(SharedPtr<tcu::ThreadUtil::Event> event);
    ~Surface(void);

    // Run time attributes
    EGLSurface surface;

private:
    Surface(const Surface &);
    Surface &operator=(const Surface &);
};

Surface::Surface(SharedPtr<tcu::ThreadUtil::Event> event)
    : tcu::ThreadUtil::Object("Surface", event)
    , surface(EGL_NO_SURFACE)
{
}

Surface::~Surface(void)
{
}

// EGL thread with thread specifig state
class EGLThread : public tcu::ThreadUtil::Thread
{
public:
    EGLThread(const Library &egl_, const glw::Functions &gl_, int seed);
    ~EGLThread(void);
    virtual void deinit(void);

    const Library &egl;
    const glw::Functions &gl;

    // Generation time attributes
    SharedPtr<GLES2Context> context;
    SharedPtr<Surface> surface;

    // Runtime attributes

    SharedPtr<GLES2Context> runtimeContext;
    EGLSurface eglSurface;

private:
};

EGLThread::EGLThread(const Library &egl_, const glw::Functions &gl_, int seed)
    : tcu::ThreadUtil::Thread(seed)
    , egl(egl_)
    , gl(gl_)
    , eglSurface(EGL_NO_SURFACE)
{
}

void EGLThread::deinit(void)
{
    if (runtimeContext)
    {
        if (runtimeContext->context != EGL_NO_CONTEXT)
            egl.makeCurrent(runtimeContext->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

        egl.destroyContext(runtimeContext->display, runtimeContext->context);
        runtimeContext->context = EGL_NO_CONTEXT;

        egl.destroySurface(runtimeContext->display, eglSurface);
        eglSurface = EGL_NO_SURFACE;
    }

    egl.releaseThread();
}

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

class FenceSync
{
public:
    FenceSync(void);
    ~FenceSync(void);

    void init(EGLThread &thread, bool serverSync);
    bool waitReady(EGLThread &thread);

    void addWaiter(void);

private:
    EGLDisplay m_display;
    EGLSyncKHR m_sync;
    de::Mutex m_lock;
    int m_waiterCount;
    bool m_serverSync;
};

FenceSync::FenceSync(void) : m_display(EGL_NO_DISPLAY), m_sync(NULL), m_waiterCount(0), m_serverSync(false)
{
}

FenceSync::~FenceSync(void)
{
}

void FenceSync::addWaiter(void)
{
    m_lock.lock();
    m_waiterCount++;
    m_lock.unlock();
}

void FenceSync::init(EGLThread &thread, bool serverSync)
{
    m_display    = thread.runtimeContext->display;
    m_serverSync = serverSync;

    // Use sync only if somebody will actualy depend on it
    m_lock.lock();
    if (m_waiterCount > 0)
    {
        thread.newMessage() << "Begin -- eglCreateSyncKHR(" << ((size_t)m_display) << ", EGL_SYNC_FENCE_KHR, DE_NULL)"
                            << tcu::ThreadUtil::Message::End;
        m_sync = thread.egl.createSyncKHR(m_display, EGL_SYNC_FENCE_KHR, DE_NULL);
        thread.newMessage() << "End -- " << ((size_t)m_sync) << " = eglCreateSyncKHR()"
                            << tcu::ThreadUtil::Message::End;
        TCU_CHECK(m_sync);
    }
    m_lock.unlock();
}

bool FenceSync::waitReady(EGLThread &thread)
{
    bool ok = true;
    if (m_serverSync)
    {
        thread.newMessage() << "Begin -- eglWaitSyncKHR(" << ((size_t)m_display) << ", " << ((size_t)m_sync) << ", 0)"
                            << tcu::ThreadUtil::Message::End;
        EGLint result = thread.egl.waitSyncKHR(m_display, m_sync, 0);
        thread.newMessage() << "End -- " << result << " = eglWaitSyncKHR()" << tcu::ThreadUtil::Message::End;
        ok = result == EGL_TRUE;
    }
    else
    {
        thread.newMessage() << "Begin -- eglClientWaitSyncKHR(" << ((size_t)m_display) << ", " << ((size_t)m_sync)
                            << ", EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, 1000 000 000)" << tcu::ThreadUtil::Message::End;
        EGLint result = thread.egl.clientWaitSyncKHR(m_display, m_sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, 1000000000);
        thread.newMessage() << "End -- " << result << " = eglClientWaitSyncKHR()" << tcu::ThreadUtil::Message::End;
        ok = result == EGL_CONDITION_SATISFIED_KHR;
    }

    m_lock.lock();
    m_waiterCount--;
    DE_ASSERT(m_waiterCount >= 0);

    if (m_waiterCount == 0)
    {
        // \note [mika] This is no longer deterministic and eglDestroySyncKHR might happen in different places and in different threads
        thread.newMessage() << "Begin -- eglDestroySyncKHR(" << ((size_t)m_display) << ", " << ((size_t)m_sync) << ")"
                            << tcu::ThreadUtil::Message::End;
        EGLint destroyResult = thread.egl.destroySyncKHR(m_display, m_sync);
        thread.newMessage() << "End -- " << destroyResult << " = eglDestroySyncKHR()" << tcu::ThreadUtil::Message::End;
        m_sync = DE_NULL;
    }

    m_lock.unlock();

    return ok;
}

class Object : public tcu::ThreadUtil::Object
{
public:
    Object(const char *type, SharedPtr<tcu::ThreadUtil::Event> e, SharedPtr<FenceSync> sync);
    ~Object(void);

    void readGL(SharedPtr<FenceSync> sync, std::vector<SharedPtr<FenceSync>> &deps);
    void modifyGL(SharedPtr<FenceSync> sync, std::vector<SharedPtr<FenceSync>> &deps);

private:
    SharedPtr<FenceSync> m_modifySync;
    vector<SharedPtr<FenceSync>> m_readSyncs;
};

Object::Object(const char *type, SharedPtr<tcu::ThreadUtil::Event> e, SharedPtr<FenceSync> sync)
    : tcu::ThreadUtil::Object(type, e)
    , m_modifySync(sync)
{
}

Object::~Object(void)
{
}

void Object::readGL(SharedPtr<FenceSync> sync, std::vector<SharedPtr<FenceSync>> &deps)
{
    if (m_modifySync)
        m_modifySync->addWaiter();

    // Make call depend on last modifying call
    deps.push_back(m_modifySync);

    // Add read dependency
    m_readSyncs.push_back(sync);
}

void Object::modifyGL(SharedPtr<FenceSync> sync, std::vector<SharedPtr<FenceSync>> &deps)
{
    // Make call depend on all reads
    for (int readNdx = 0; readNdx < (int)m_readSyncs.size(); readNdx++)
    {
        if (m_readSyncs[readNdx])
            m_readSyncs[readNdx]->addWaiter();

        deps.push_back(m_readSyncs[readNdx]);
    }

    if (m_modifySync)
        m_modifySync->addWaiter();

    deps.push_back(m_modifySync);

    // Update last modifying call
    m_modifySync = sync;

    // Clear read dependencies of last "version" of this object
    m_readSyncs.clear();
}

class Operation : public tcu::ThreadUtil::Operation
{
public:
    Operation(const char *name, bool useSync, bool serverSync);
    virtual ~Operation(void);

    SharedPtr<FenceSync> getSync(void)
    {
        return m_sync;
    }
    void readGLObject(SharedPtr<Object> object);
    void modifyGLObject(SharedPtr<Object> object);

    virtual void execute(tcu::ThreadUtil::Thread &thread);

private:
    bool m_useSync;
    bool m_serverSync;
    std::vector<SharedPtr<FenceSync>> m_syncDeps;
    SharedPtr<FenceSync> m_sync;
};

Operation::Operation(const char *name, bool useSync, bool serverSync)
    : tcu::ThreadUtil::Operation(name)
    , m_useSync(useSync)
    , m_serverSync(serverSync)
    , m_sync(useSync ? SharedPtr<FenceSync>(new FenceSync()) : SharedPtr<FenceSync>())
{
}

Operation::~Operation(void)
{
}

void Operation::readGLObject(SharedPtr<Object> object)
{
    object->read(m_event, m_deps);
    object->readGL(m_sync, m_syncDeps);
}

void Operation::modifyGLObject(SharedPtr<Object> object)
{
    object->modify(m_event, m_deps);
    object->modifyGL(m_sync, m_syncDeps);
}

void Operation::execute(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    bool success = true;

    // Wait for dependencies and check that they succeeded
    for (int depNdx = 0; depNdx < (int)m_deps.size(); depNdx++)
    {
        if (!m_deps[depNdx]->waitReady())
            success = false;
    }

    // Try execute operation
    if (success)
    {
        try
        {
            if (m_useSync)
            {
                for (int depNdx = 0; depNdx < (int)m_syncDeps.size(); depNdx++)
                {
                    EGLThread *eglThread = dynamic_cast<EGLThread *>(&thread);
                    DE_ASSERT(eglThread);
                    if (m_syncDeps[depNdx]->waitReady(*eglThread) != tcu::ThreadUtil::Event::RESULT_OK)
                    {
                        success = false;
                        break;
                    }
                }
            }

            if (success)
            {
                exec(thread);
                if (m_useSync)
                {
                    EGLThread *eglThread = dynamic_cast<EGLThread *>(&thread);
                    DE_ASSERT(eglThread);
                    m_sync->init(*eglThread, m_serverSync);
                    thread.newMessage() << "Begin -- glFlush()" << tcu::ThreadUtil::Message::End;
                    GLU_CHECK_GLW_CALL(thread.gl, flush());
                    thread.newMessage() << "End -- glFlush()" << tcu::ThreadUtil::Message::End;
                }
                else
                {
                    thread.newMessage() << "Begin -- glFinish()" << tcu::ThreadUtil::Message::End;
                    GLU_CHECK_GLW_CALL(thread.gl, finish());
                    thread.newMessage() << "End -- glFinish()" << tcu::ThreadUtil::Message::End;
                }
            }
        }
        catch (...)
        {
            // Got exception event failed
            m_event->setResult(tcu::ThreadUtil::Event::RESULT_FAILED);
            throw;
        }
    }

    if (success)
        m_event->setResult(tcu::ThreadUtil::Event::RESULT_OK);
    else
        m_event->setResult(tcu::ThreadUtil::Event::RESULT_FAILED);

    m_deps.clear();
    m_event = SharedPtr<tcu::ThreadUtil::Event>();
    m_syncDeps.clear();
    m_sync = SharedPtr<FenceSync>();
}

class EGLImage : public Object
{
public:
    EGLImage(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync);
    virtual ~EGLImage(void)
    {
    }

    EGLImageKHR image;
};

// EGLResource manager
class EGLResourceManager
{
public:
    void addContext(SharedPtr<GLES2Context> context)
    {
        m_contexts.push_back(context);
    }
    void addSurface(SharedPtr<Surface> surface)
    {
        m_surfaces.push_back(surface);
    }
    void addImage(SharedPtr<EGLImage> image)
    {
        m_images.push_back(image);
    }

    SharedPtr<Surface> popSurface(int index);
    SharedPtr<GLES2Context> popContext(int index);
    SharedPtr<EGLImage> popImage(int index);

    int getContextCount(void) const
    {
        return (int)m_contexts.size();
    }
    int getSurfaceCount(void) const
    {
        return (int)m_surfaces.size();
    }
    int getImageCount(void) const
    {
        return (int)m_images.size();
    }

private:
    std::vector<SharedPtr<GLES2Context>> m_contexts;
    std::vector<SharedPtr<Surface>> m_surfaces;
    std::vector<SharedPtr<EGLImage>> m_images;
};

SharedPtr<Surface> EGLResourceManager::popSurface(int index)
{
    SharedPtr<Surface> surface = m_surfaces[index];
    m_surfaces.erase(m_surfaces.begin() + index);
    return surface;
}

SharedPtr<GLES2Context> EGLResourceManager::popContext(int index)
{
    SharedPtr<GLES2Context> context = m_contexts[index];
    m_contexts.erase(m_contexts.begin() + index);
    return context;
}

SharedPtr<EGLImage> EGLResourceManager::popImage(int index)
{
    SharedPtr<EGLImage> image = m_images[index];
    m_images.erase(m_images.begin() + index);
    return image;
}

class CreateContext : public tcu::ThreadUtil::Operation
{
public:
    CreateContext(EGLDisplay display, EGLConfig config, SharedPtr<GLES2Context> shared,
                  SharedPtr<GLES2Context> &context);

    void exec(tcu::ThreadUtil::Thread &thread);

private:
    EGLDisplay m_display;
    EGLConfig m_config;
    SharedPtr<GLES2Context> m_shared;
    SharedPtr<GLES2Context> m_context;
};

CreateContext::CreateContext(EGLDisplay display, EGLConfig config, SharedPtr<GLES2Context> shared,
                             SharedPtr<GLES2Context> &context)
    : tcu::ThreadUtil::Operation("CreateContext")
    , m_display(display)
    , m_config(config)
    , m_shared(shared)
{
    if (shared)
        modifyObject(SharedPtr<tcu::ThreadUtil::Object>(shared));

    context   = SharedPtr<GLES2Context>(new GLES2Context(
        getEvent(), (shared ? shared->resourceManager : SharedPtr<GLES2ResourceManager>(new GLES2ResourceManager))));
    m_context = context;
}

void CreateContext::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread  = dynamic_cast<EGLThread &>(t);
    m_context->display = m_display;

    const EGLint attriblist[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};

    thread.newMessage() << "Begin -- eglBindAPI(EGL_OPENGL_ES_API)" << tcu::ThreadUtil::Message::End;
    EGLU_CHECK_CALL(thread.egl, bindAPI(EGL_OPENGL_ES_API));
    thread.newMessage() << "End -- eglBindAPI()" << tcu::ThreadUtil::Message::End;

    if (m_shared)
    {
        DE_ASSERT(m_shared->context != EGL_NO_CONTEXT);
        DE_ASSERT(m_shared->display != EGL_NO_DISPLAY);
        DE_ASSERT(m_shared->display == m_display);

        thread.newMessage() << "Begin -- eglCreateContext(" << m_display << ", " << m_config << ", "
                            << m_shared->context << ", { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE })"
                            << tcu::ThreadUtil::Message::End;
        m_context->context = thread.egl.createContext(m_display, m_config, m_shared->context, attriblist);
        thread.newMessage() << "End -- " << m_context->context << " = eglCreateContext()"
                            << tcu::ThreadUtil::Message::End;
    }
    else
    {
        thread.newMessage() << "Begin -- eglCreateContext(" << m_display << ", " << m_config
                            << ", EGL_NO_CONTEXT, { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE })"
                            << tcu::ThreadUtil::Message::End;
        m_context->context = thread.egl.createContext(m_display, m_config, EGL_NO_CONTEXT, attriblist);
        thread.newMessage() << "End -- " << m_context->context << " = eglCreateContext()"
                            << tcu::ThreadUtil::Message::End;
    }

    EGLU_CHECK_MSG(thread.egl, "Failed to create GLES2 context");
    TCU_CHECK(m_context->context != EGL_NO_CONTEXT);
}

class DestroyContext : public tcu::ThreadUtil::Operation
{
public:
    DestroyContext(SharedPtr<GLES2Context> contex);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<GLES2Context> m_context;
};

DestroyContext::DestroyContext(SharedPtr<GLES2Context> contex)
    : tcu::ThreadUtil::Operation("DestroyContext")
    , m_context(contex)
{
    modifyObject(SharedPtr<tcu::ThreadUtil::Object>(m_context));
}

void DestroyContext::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    thread.newMessage() << "Begin -- eglDestroyContext(" << m_context->display << ", " << m_context->context << ")"
                        << tcu::ThreadUtil::Message::End;
    EGLU_CHECK_CALL(thread.egl, destroyContext(m_context->display, m_context->context));
    thread.newMessage() << "End -- eglDestroyContext()" << tcu::ThreadUtil::Message::End;
    m_context->display = EGL_NO_DISPLAY;
    m_context->context = EGL_NO_CONTEXT;
}

class MakeCurrent : public tcu::ThreadUtil::Operation
{
public:
    MakeCurrent(EGLThread &thread, EGLDisplay display, SharedPtr<Surface> surface, SharedPtr<GLES2Context> context);

    void exec(tcu::ThreadUtil::Thread &thread);

private:
    EGLDisplay m_display;
    SharedPtr<Surface> m_surface;
    SharedPtr<GLES2Context> m_context;
};

MakeCurrent::MakeCurrent(EGLThread &thread, EGLDisplay display, SharedPtr<Surface> surface,
                         SharedPtr<GLES2Context> context)
    : tcu::ThreadUtil::Operation("MakeCurrent")
    , m_display(display)
    , m_surface(surface)
    , m_context(context)
{
    if (m_context)
        modifyObject(SharedPtr<tcu::ThreadUtil::Object>(m_context));

    if (m_surface)
        modifyObject(SharedPtr<tcu::ThreadUtil::Object>(m_surface));

    // Release old contexts
    if (thread.context)
    {
        modifyObject(SharedPtr<tcu::ThreadUtil::Object>(thread.context));
    }

    // Release old surface
    if (thread.surface)
    {
        modifyObject(SharedPtr<tcu::ThreadUtil::Object>(thread.surface));
    }

    thread.context = m_context;
    thread.surface = m_surface;
}

void MakeCurrent::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    if (m_context)
    {
        thread.eglSurface     = m_surface->surface;
        thread.runtimeContext = m_context;

        DE_ASSERT(m_surface);
        thread.newMessage() << "Begin -- eglMakeCurrent(" << m_display << ", " << m_surface->surface << ", "
                            << m_surface->surface << ", " << m_context->context << ")" << tcu::ThreadUtil::Message::End;
        EGLU_CHECK_CALL(thread.egl, makeCurrent(m_display, m_surface->surface, m_surface->surface, m_context->context));
        thread.newMessage() << "End -- eglMakeCurrent()" << tcu::ThreadUtil::Message::End;
    }
    else
    {
        thread.runtimeContext = m_context;

        thread.newMessage() << "Begin -- eglMakeCurrent(" << m_display
                            << ", EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)" << tcu::ThreadUtil::Message::End;
        EGLU_CHECK_CALL(thread.egl, makeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
        thread.newMessage() << "End -- eglMakeCurrent()" << tcu::ThreadUtil::Message::End;
    }
}

class InitGLExtension : public tcu::ThreadUtil::Operation
{
public:
    InitGLExtension(const char *extension);

    void exec(tcu::ThreadUtil::Thread &thread);

private:
    std::string m_extension;
};

InitGLExtension::InitGLExtension(const char *extension)
    : tcu::ThreadUtil::Operation("InitGLExtension")
    , m_extension(extension)
{
}

void InitGLExtension::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    // Check extensions
    bool found = false;

    thread.newMessage() << "Begin -- glGetString(GL_EXTENSIONS)" << tcu::ThreadUtil::Message::End;
    std::string extensions = (const char *)thread.gl.getString(GL_EXTENSIONS);
    thread.newMessage() << "End -- glGetString()" << tcu::ThreadUtil::Message::End;

    std::string::size_type pos = extensions.find(" ");

    do
    {
        std::string extension;
        if (pos != std::string::npos)
        {
            extension  = extensions.substr(0, pos);
            extensions = extensions.substr(pos + 1);
        }
        else
        {
            extension  = extensions;
            extensions = "";
        }

        if (extension == m_extension)
        {
            found = true;
            break;
        }
        pos = extensions.find(" ");
    } while (pos != std::string::npos);

    if (!found)
        throw tcu::NotSupportedError((m_extension + " not supported").c_str(), "", __FILE__, __LINE__);

    // Query function pointers
    if (m_extension == "GL_OES_EGL_image")
    {
        thread.newMessage() << "Begin -- eglGetProcAddress(\"glEGLImageTargetTexture2DOES\")"
                            << tcu::ThreadUtil::Message::End;
        thread.runtimeContext->glExtensions.imageTargetTexture2D =
            (glEGLImageTargetTexture2DOESFunc)thread.egl.getProcAddress("glEGLImageTargetTexture2DOES");
        thread.newMessage() << "End --  " << ((void *)thread.runtimeContext->glExtensions.imageTargetTexture2D)
                            << " = eglGetProcAddress()" << tcu::ThreadUtil::Message::End;
    }
}

class CreatePBufferSurface : public tcu::ThreadUtil::Operation
{
public:
    CreatePBufferSurface(EGLDisplay display, EGLConfig config, EGLint width, EGLint height,
                         SharedPtr<Surface> &surface);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    EGLDisplay m_display;
    EGLConfig m_config;
    EGLint m_width;
    EGLint m_height;
    SharedPtr<Surface> m_surface;
};

CreatePBufferSurface::CreatePBufferSurface(EGLDisplay display, EGLConfig config, EGLint width, EGLint height,
                                           SharedPtr<Surface> &surface)
    : tcu::ThreadUtil::Operation("CreatePBufferSurface")
    , m_display(display)
    , m_config(config)
    , m_width(width)
    , m_height(height)
{
    surface   = SharedPtr<Surface>(new Surface(getEvent()));
    m_surface = surface;
}

void CreatePBufferSurface::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    const EGLint attriblist[] = {EGL_WIDTH, m_width, EGL_HEIGHT, m_height, EGL_NONE};

    thread.newMessage() << "Begin -- eglCreatePbufferSurface(" << m_display << ", " << m_config << ", { EGL_WIDTH, "
                        << m_width << ", EGL_HEIGHT, " << m_height << ", EGL_NONE })" << tcu::ThreadUtil::Message::End;
    m_surface->surface = thread.egl.createPbufferSurface(m_display, m_config, attriblist);
    thread.newMessage() << "End -- " << m_surface->surface << "= eglCreatePbufferSurface()"
                        << tcu::ThreadUtil::Message::End;
    EGLU_CHECK_MSG(thread.egl, "eglCreatePbufferSurface()");
}

class DestroySurface : public tcu::ThreadUtil::Operation
{
public:
    DestroySurface(EGLDisplay display, SharedPtr<Surface> surface);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    EGLDisplay m_display;
    SharedPtr<Surface> m_surface;
};

DestroySurface::DestroySurface(EGLDisplay display, SharedPtr<Surface> surface)
    : tcu::ThreadUtil::Operation("DestroySurface")
    , m_display(display)
    , m_surface(surface)
{
    modifyObject(SharedPtr<tcu::ThreadUtil::Object>(m_surface));
}

void DestroySurface::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    thread.newMessage() << "Begin -- eglDestroySurface(" << m_display << ",  " << m_surface->surface << ")"
                        << tcu::ThreadUtil::Message::End;
    EGLU_CHECK_CALL(thread.egl, destroySurface(m_display, m_surface->surface));
    thread.newMessage() << "End -- eglDestroySurface()" << tcu::ThreadUtil::Message::End;
}

EGLImage::EGLImage(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync)
    : Object("EGLImage", event, sync)
    , image(EGL_NO_IMAGE_KHR)
{
}

class Texture : public Object
{
public:
    Texture(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync);

    // Runtime parameters
    GLuint texture;

    // Call generation time parameters
    bool isDefined;

    SharedPtr<EGLImage> sourceImage;
};

Texture::Texture(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync)
    : Object("Texture", event, sync)
    , texture(0)
    , isDefined(false)
{
}

class CreateTexture : public Operation
{
public:
    CreateTexture(SharedPtr<Texture> &texture, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Texture> m_texture;
};

CreateTexture::CreateTexture(SharedPtr<Texture> &texture, bool useSync, bool serverSync)
    : Operation("CreateTexture", useSync, serverSync)
{
    texture   = SharedPtr<Texture>(new Texture(getEvent(), getSync()));
    m_texture = texture;
}

void CreateTexture::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);
    GLuint tex        = 0;

    thread.newMessage() << "Begin -- glGenTextures(1, { 0 })" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, genTextures(1, &tex));
    thread.newMessage() << "End -- glGenTextures(1, { " << tex << " })" << tcu::ThreadUtil::Message::End;

    m_texture->texture = tex;
}

class DeleteTexture : public Operation
{
public:
    DeleteTexture(SharedPtr<Texture> texture, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Texture> m_texture;
};

DeleteTexture::DeleteTexture(SharedPtr<Texture> texture, bool useSync, bool serverSync)
    : Operation("DeleteTexture", useSync, serverSync)
    , m_texture(texture)
{
    modifyGLObject(SharedPtr<Object>(m_texture));
}

void DeleteTexture::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);
    GLuint tex        = m_texture->texture;

    thread.newMessage() << "Begin -- glDeleteTextures(1, { " << tex << " })" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, deleteTextures(1, &tex));
    thread.newMessage() << "End -- glDeleteTextures()" << tcu::ThreadUtil::Message::End;

    m_texture->texture = 0;
}

class TexImage2D : public Operation
{
public:
    TexImage2D(SharedPtr<Texture> texture, GLint level, GLint internalFormat, GLsizei width, GLsizei height,
               GLenum format, GLenum type, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Texture> m_texture;
    GLint m_level;
    GLint m_internalFormat;
    GLsizei m_width;
    GLsizei m_height;
    GLenum m_format;
    GLenum m_type;
};

TexImage2D::TexImage2D(SharedPtr<Texture> texture, GLint level, GLint internalFormat, GLsizei width, GLsizei height,
                       GLenum format, GLenum type, bool useSync, bool serverSync)
    : Operation("TexImage2D", useSync, serverSync)
    , m_texture(texture)
    , m_level(level)
    , m_internalFormat(internalFormat)
    , m_width(width)
    , m_height(height)
    , m_format(format)
    , m_type(type)
{
    modifyGLObject(SharedPtr<Object>(m_texture));
    m_texture->isDefined = true;

    // Orphang texture
    texture->sourceImage = SharedPtr<EGLImage>();
}

void TexImage2D::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);
    void *unusedData  = thread.getUnusedData(m_width * m_height * 4);

    thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, " << m_texture->texture << ")"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindTexture(GL_TEXTURE_2D, m_texture->texture));
    thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glTexImage2D(GL_TEXTURE_2D, " << m_level << ", " << m_internalFormat << ", "
                        << m_width << ", " << m_height << ", 0, " << m_format << ", " << m_type << ", data)"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, texImage2D(GL_TEXTURE_2D, m_level, m_internalFormat, m_width, m_height, 0, m_format,
                                             m_type, unusedData));
    thread.newMessage() << "End -- glTexImage2D()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, 0)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindTexture(GL_TEXTURE_2D, 0));
    thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
}

class TexSubImage2D : public Operation
{
public:
    TexSubImage2D(SharedPtr<Texture> texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height,
                  GLenum format, GLenum type, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Texture> m_texture;
    GLint m_level;
    GLint m_xoffset;
    GLint m_yoffset;
    GLsizei m_width;
    GLsizei m_height;
    GLenum m_format;
    GLenum m_type;
};

TexSubImage2D::TexSubImage2D(SharedPtr<Texture> texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
                             GLsizei height, GLenum format, GLenum type, bool useSync, bool serverSync)
    : Operation("TexSubImage2D", useSync, serverSync)
    , m_texture(texture)
    , m_level(level)
    , m_xoffset(xoffset)
    , m_yoffset(yoffset)
    , m_width(width)
    , m_height(height)
    , m_format(format)
    , m_type(type)
{
    modifyGLObject(SharedPtr<Object>(m_texture));

    if (m_texture->sourceImage)
        modifyGLObject(SharedPtr<Object>(m_texture->sourceImage));
}

void TexSubImage2D::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);
    void *unusedData  = thread.getUnusedData(m_width * m_height * 4);

    thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, " << m_texture->texture << ")"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindTexture(GL_TEXTURE_2D, m_texture->texture));
    thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glTexSubImage2D(GL_TEXTURE_2D, " << m_level << ", " << m_xoffset << ", "
                        << m_yoffset << ", " << m_width << ", " << m_height << ", 0, " << m_format << ", " << m_type
                        << ", <data>)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, texSubImage2D(GL_TEXTURE_2D, m_level, m_xoffset, m_yoffset, m_width, m_height,
                                                m_format, m_type, unusedData));
    thread.newMessage() << "End -- glSubTexImage2D()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, 0)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindTexture(GL_TEXTURE_2D, 0));
    thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
}

class CopyTexImage2D : public Operation
{
public:
    CopyTexImage2D(SharedPtr<Texture> texture, GLint level, GLint internalFormat, GLint x, GLint y, GLsizei width,
                   GLsizei height, GLint border, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Texture> m_texture;
    GLint m_level;
    GLint m_internalFormat;
    GLint m_x;
    GLint m_y;
    GLsizei m_width;
    GLsizei m_height;
    GLint m_border;
};

CopyTexImage2D::CopyTexImage2D(SharedPtr<Texture> texture, GLint level, GLint internalFormat, GLint x, GLint y,
                               GLsizei width, GLsizei height, GLint border, bool useSync, bool serverSync)
    : Operation("CopyTexImage2D", useSync, serverSync)
    , m_texture(texture)
    , m_level(level)
    , m_internalFormat(internalFormat)
    , m_x(x)
    , m_y(y)
    , m_width(width)
    , m_height(height)
    , m_border(border)
{
    modifyGLObject(SharedPtr<Object>(m_texture));
    texture->isDefined = true;

    // Orphang texture
    texture->sourceImage = SharedPtr<EGLImage>();
}

void CopyTexImage2D::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, " << m_texture->texture << ")"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindTexture(GL_TEXTURE_2D, m_texture->texture));
    thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glCopyTexImage2D(GL_TEXTURE_2D, " << m_level << ", " << m_internalFormat << ", "
                        << m_x << ", " << m_y << ", " << m_width << ", " << m_height << ", " << m_border << ")"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl,
                       copyTexImage2D(GL_TEXTURE_2D, m_level, m_internalFormat, m_x, m_y, m_width, m_height, m_border));
    thread.newMessage() << "End -- glCopyTexImage2D()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, 0)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindTexture(GL_TEXTURE_2D, 0));
    thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
}

class CopyTexSubImage2D : public Operation
{
public:
    CopyTexSubImage2D(SharedPtr<Texture> texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
                      GLsizei width, GLsizei height, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Texture> m_texture;
    GLint m_level;
    GLint m_xoffset;
    GLint m_yoffset;
    GLint m_x;
    GLint m_y;
    GLsizei m_width;
    GLsizei m_height;
};

CopyTexSubImage2D::CopyTexSubImage2D(SharedPtr<Texture> texture, GLint level, GLint xoffset, GLint yoffset, GLint x,
                                     GLint y, GLsizei width, GLsizei height, bool useSync, bool serverSync)
    : Operation("CopyTexSubImage2D", useSync, serverSync)
    , m_texture(texture)
    , m_level(level)
    , m_xoffset(xoffset)
    , m_yoffset(yoffset)
    , m_x(x)
    , m_y(y)
    , m_width(width)
    , m_height(height)
{
    modifyGLObject(SharedPtr<Object>(m_texture));

    if (m_texture->sourceImage)
        modifyGLObject(SharedPtr<Object>(m_texture->sourceImage));
}

void CopyTexSubImage2D::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, " << m_texture->texture << ")"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindTexture(GL_TEXTURE_2D, m_texture->texture));
    thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glCopyTexSubImage2D(GL_TEXTURE_2D, " << m_level << ", " << m_xoffset << ", "
                        << m_yoffset << ", " << m_x << ", " << m_y << ", " << m_width << ", " << m_height << ")"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl,
                       copyTexSubImage2D(GL_TEXTURE_2D, m_level, m_xoffset, m_yoffset, m_x, m_y, m_width, m_height));
    thread.newMessage() << "End -- glCopyTexSubImage2D()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, 0)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindTexture(GL_TEXTURE_2D, 0));
    thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
}

class Buffer : public Object
{
public:
    Buffer(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync);

    // Runtime attributes
    GLuint buffer;
    GLsizeiptr size;

    // Call generation time parameters
    bool isDefined;
};

Buffer::Buffer(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync)
    : Object("Buffer", event, sync)
    , buffer(0)
    , size(0)
    , isDefined(false)
{
}

class CreateBuffer : public Operation
{
public:
    CreateBuffer(SharedPtr<Buffer> &buffer, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Buffer> m_buffer;
};

CreateBuffer::CreateBuffer(SharedPtr<Buffer> &buffer, bool useSync, bool serverSync)
    : Operation("CreateBuffer", useSync, serverSync)
{
    buffer   = SharedPtr<Buffer>(new Buffer(getEvent(), getSync()));
    m_buffer = buffer;
}

void CreateBuffer::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);
    GLuint buffer     = 0;

    thread.newMessage() << "Begin -- glGenBuffers(1, { 0 })" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, genBuffers(1, &buffer));
    thread.newMessage() << "End -- glGenBuffers(1, { " << buffer << " })" << tcu::ThreadUtil::Message::End;

    m_buffer->buffer = buffer;
}

class DeleteBuffer : public Operation
{
public:
    DeleteBuffer(SharedPtr<Buffer> buffer, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Buffer> m_buffer;
};

DeleteBuffer::DeleteBuffer(SharedPtr<Buffer> buffer, bool useSync, bool serverSync)
    : Operation("DeleteBuffer", useSync, serverSync)
    , m_buffer(buffer)
{
    modifyGLObject(SharedPtr<Object>(m_buffer));
}

void DeleteBuffer::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);
    GLuint buffer     = m_buffer->buffer;

    thread.newMessage() << "Begin -- glDeleteBuffers(1, { " << buffer << " })" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, deleteBuffers(1, &buffer));
    thread.newMessage() << "End -- glDeleteBuffers()" << tcu::ThreadUtil::Message::End;

    m_buffer->buffer = 0;
}

class BufferData : public Operation
{
public:
    BufferData(SharedPtr<Buffer> buffer, GLenum target, GLsizeiptr size, GLenum usage, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Buffer> m_buffer;
    GLenum m_target;
    GLsizeiptr m_size;
    GLenum m_usage;
};

BufferData::BufferData(SharedPtr<Buffer> buffer, GLenum target, GLsizeiptr size, GLenum usage, bool useSync,
                       bool serverSync)
    : Operation("BufferData", useSync, serverSync)
    , m_buffer(buffer)
    , m_target(target)
    , m_size(size)
    , m_usage(usage)
{
    modifyGLObject(SharedPtr<Object>(m_buffer));
    buffer->isDefined = true;
    buffer->size      = size;
}

void BufferData::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);
    void *unusedData  = thread.getUnusedData(m_size);

    thread.newMessage() << "Begin -- glBindBuffer(" << m_target << ", " << m_buffer->buffer << ")"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindBuffer(m_target, m_buffer->buffer));
    thread.newMessage() << "End -- glBindBuffer()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glBufferData(" << m_target << ", " << m_size << ", <DATA>, " << m_usage << ")"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bufferData(m_target, m_size, unusedData, m_usage));
    thread.newMessage() << "End -- glBufferData()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glBindBuffer(" << m_target << ", 0)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindBuffer(m_target, 0));
    thread.newMessage() << "End -- glBindBuffer()" << tcu::ThreadUtil::Message::End;
}

class BufferSubData : public Operation
{
public:
    BufferSubData(SharedPtr<Buffer> buffer, GLenum target, GLintptr offset, GLsizeiptr size, bool useSync,
                  bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Buffer> m_buffer;
    GLenum m_target;
    GLintptr m_offset;
    GLsizeiptr m_size;
};

BufferSubData::BufferSubData(SharedPtr<Buffer> buffer, GLenum target, GLintptr offset, GLsizeiptr size, bool useSync,
                             bool serverSync)
    : Operation("BufferSubData", useSync, serverSync)
    , m_buffer(buffer)
    , m_target(target)
    , m_offset(offset)
    , m_size(size)
{
    modifyGLObject(SharedPtr<Object>(m_buffer));
}

void BufferSubData::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);
    void *unusedData  = thread.getUnusedData(m_size);

    thread.newMessage() << "Begin -- glBindBuffer(" << m_target << ", " << m_buffer->buffer << ")"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindBuffer(m_target, m_buffer->buffer));
    thread.newMessage() << "End -- glBindBuffer()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glBufferSubData(" << m_target << ", " << m_offset << ", " << m_size << ", <DATA>)"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bufferSubData(m_target, m_offset, m_size, unusedData));
    thread.newMessage() << "End -- glBufferSubData()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glBindBuffer(" << m_target << ", 0)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindBuffer(m_target, 0));
    thread.newMessage() << "End -- glBindBuffer()" << tcu::ThreadUtil::Message::End;
}

class Shader : public Object
{
public:
    Shader(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync);

    GLuint shader;
    GLenum type;
    bool isDefined;
    bool compiled;
};

Shader::Shader(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync)
    : Object("Shader", event, sync)
    , shader(0)
    , type(GL_NONE)
    , isDefined(false)
    , compiled(false)
{
}

class CreateShader : public Operation
{
public:
    CreateShader(GLenum type, SharedPtr<Shader> &shader, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Shader> m_shader;
    GLenum m_type;
};

CreateShader::CreateShader(GLenum type, SharedPtr<Shader> &shader, bool useSync, bool serverSync)
    : Operation("CreateShader", useSync, serverSync)
    , m_type(type)
{
    shader       = SharedPtr<Shader>(new Shader(getEvent(), getSync()));
    shader->type = type;

    m_shader = shader;
}

void CreateShader::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);
    GLuint shader     = 0;

    thread.newMessage() << "Begin -- glCreateShader(" << m_type << ")" << tcu::ThreadUtil::Message::End;
    shader = thread.gl.createShader(m_type);
    GLU_CHECK_GLW_MSG(thread.gl, "glCreateShader()");
    thread.newMessage() << "End -- " << shader << " = glCreateShader(" << m_type << ")"
                        << tcu::ThreadUtil::Message::End;

    m_shader->shader = shader;
}

class DeleteShader : public Operation
{
public:
    DeleteShader(SharedPtr<Shader> shader, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Shader> m_shader;
};

DeleteShader::DeleteShader(SharedPtr<Shader> shader, bool useSync, bool serverSync)
    : Operation("DeleteShader", useSync, serverSync)
    , m_shader(shader)
{
    modifyGLObject(SharedPtr<Object>(m_shader));
}

void DeleteShader::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);
    GLuint shader     = m_shader->shader;

    thread.newMessage() << "Begin -- glDeleteShader(" << shader << ")" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, deleteShader(shader));
    thread.newMessage() << "End -- glDeleteShader()" << tcu::ThreadUtil::Message::End;

    m_shader->shader = 0;
}

class ShaderSource : public Operation
{
public:
    ShaderSource(SharedPtr<Shader> sharder, const char *source, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Shader> m_shader;
    string m_source;
};

ShaderSource::ShaderSource(SharedPtr<Shader> shader, const char *source, bool useSync, bool serverSync)
    : Operation("ShaderSource", useSync, serverSync)
    , m_shader(shader)
    , m_source(source)
{
    modifyGLObject(SharedPtr<Object>(m_shader));
    m_shader->isDefined = true;
}

void ShaderSource::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread        = dynamic_cast<EGLThread &>(t);
    const char *shaderSource = m_source.c_str();

    thread.newMessage() << "Begin -- glShaderSource(" << m_shader->shader << ", 1, \"" << shaderSource << "\", DE_NULL)"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, shaderSource(m_shader->shader, 1, &shaderSource, DE_NULL));
    thread.newMessage() << "End -- glShaderSource()" << tcu::ThreadUtil::Message::End;
}

class ShaderCompile : public Operation
{
public:
    ShaderCompile(SharedPtr<Shader> sharder, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Shader> m_shader;
};

ShaderCompile::ShaderCompile(SharedPtr<Shader> shader, bool useSync, bool serverSync)
    : Operation("ShaderCompile", useSync, serverSync)
    , m_shader(shader)
{
    m_shader->compiled = true;
    modifyGLObject(SharedPtr<Object>(m_shader));
}

void ShaderCompile::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    thread.newMessage() << "Begin -- glCompileShader(" << m_shader->shader << ")" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, compileShader(m_shader->shader));
    thread.newMessage() << "End -- glCompileShader()" << tcu::ThreadUtil::Message::End;
}

class Program : public Object
{
public:
    Program(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync);

    // Generation time attributes
    SharedPtr<Shader> vertexShader;
    SharedPtr<Shader> fragmentShader;
    bool linked;

    // Runtime attributes
    GLuint program;
    GLuint runtimeVertexShader;
    GLuint runtimeFragmentShader;
};

Program::Program(SharedPtr<tcu::ThreadUtil::Event> event, SharedPtr<FenceSync> sync)
    : Object("Program", event, sync)
    , linked(false)
    , program(0)
    , runtimeVertexShader(0)
    , runtimeFragmentShader(0)
{
}

class CreateProgram : public Operation
{
public:
    CreateProgram(SharedPtr<Program> &program, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Program> m_program;
};

CreateProgram::CreateProgram(SharedPtr<Program> &program, bool useSync, bool serverSync)
    : Operation("CreateProgram", useSync, serverSync)
{
    program   = SharedPtr<Program>(new Program(getEvent(), getSync()));
    m_program = program;
}

void CreateProgram::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);
    GLuint program    = 0;

    thread.newMessage() << "Begin -- glCreateProgram()" << tcu::ThreadUtil::Message::End;
    program = thread.gl.createProgram();
    GLU_CHECK_GLW_MSG(thread.gl, "glCreateProgram()");
    thread.newMessage() << "End -- " << program << " = glCreateProgram()" << tcu::ThreadUtil::Message::End;

    m_program->program = program;
}

class DeleteProgram : public Operation
{
public:
    DeleteProgram(SharedPtr<Program> program, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Program> m_program;
};

DeleteProgram::DeleteProgram(SharedPtr<Program> program, bool useSync, bool serverSync)
    : Operation("DeleteProgram", useSync, serverSync)
    , m_program(program)
{
    modifyGLObject(SharedPtr<Object>(m_program));
}

void DeleteProgram::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);
    GLuint program    = m_program->program;

    thread.newMessage() << "Begin -- glDeleteProgram(" << program << ")" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, deleteProgram(program));
    thread.newMessage() << "End -- glDeleteProgram()" << tcu::ThreadUtil::Message::End;

    m_program->program = 0;
}

class AttachShader : public Operation
{
public:
    AttachShader(SharedPtr<Program> sharder, SharedPtr<Shader> shader, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Program> m_program;
    SharedPtr<Shader> m_shader;
};

AttachShader::AttachShader(SharedPtr<Program> program, SharedPtr<Shader> shader, bool useSync, bool serverSync)
    : Operation("AttachShader", useSync, serverSync)
    , m_program(program)
    , m_shader(shader)
{
    modifyGLObject(SharedPtr<Object>(m_program));
    readGLObject(SharedPtr<Object>(m_shader));

    if (m_shader->type == GL_VERTEX_SHADER)
        m_program->vertexShader = shader;
    else if (m_shader->type == GL_FRAGMENT_SHADER)
        m_program->fragmentShader = shader;
    else
        DE_ASSERT(false);
}

void AttachShader::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    thread.newMessage() << "Begin -- glAttachShader(" << m_program->program << ", " << m_shader->shader << ")"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, attachShader(m_program->program, m_shader->shader));
    thread.newMessage() << "End -- glAttachShader()" << tcu::ThreadUtil::Message::End;

    if (m_shader->type == GL_VERTEX_SHADER)
        m_program->runtimeVertexShader = m_shader->shader;
    else if (m_shader->type == GL_FRAGMENT_SHADER)
        m_program->runtimeFragmentShader = m_shader->shader;
    else
        DE_ASSERT(false);
}

class DetachShader : public Operation
{
public:
    DetachShader(SharedPtr<Program> sharder, GLenum type, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Program> m_program;
    GLenum m_type;
};

DetachShader::DetachShader(SharedPtr<Program> program, GLenum type, bool useSync, bool serverSync)
    : Operation("DetachShader", useSync, serverSync)
    , m_program(program)
    , m_type(type)
{
    modifyGLObject(SharedPtr<Object>(m_program));

    if (m_type == GL_VERTEX_SHADER)
    {
        DE_ASSERT(m_program->vertexShader);
        m_program->vertexShader = SharedPtr<Shader>();
    }
    else if (m_type == GL_FRAGMENT_SHADER)
    {
        DE_ASSERT(m_program->fragmentShader);
        m_program->fragmentShader = SharedPtr<Shader>();
    }
    else
        DE_ASSERT(false);
}

void DetachShader::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    if (m_type == GL_VERTEX_SHADER)
    {
        thread.newMessage() << "Begin -- glDetachShader(" << m_program->program << ", "
                            << m_program->runtimeVertexShader << ")" << tcu::ThreadUtil::Message::End;
        GLU_CHECK_GLW_CALL(thread.gl, detachShader(m_program->program, m_program->runtimeVertexShader));
        thread.newMessage() << "End -- glDetachShader()" << tcu::ThreadUtil::Message::End;
        m_program->runtimeVertexShader = 0;
    }
    else if (m_type == GL_FRAGMENT_SHADER)
    {
        thread.newMessage() << "Begin -- glDetachShader(" << m_program->program << ", "
                            << m_program->runtimeFragmentShader << ")" << tcu::ThreadUtil::Message::End;
        GLU_CHECK_GLW_CALL(thread.gl, detachShader(m_program->program, m_program->runtimeFragmentShader));
        thread.newMessage() << "End -- glDetachShader()" << tcu::ThreadUtil::Message::End;
        m_program->runtimeFragmentShader = 0;
    }
    else
        DE_ASSERT(false);
}

class LinkProgram : public Operation
{
public:
    LinkProgram(SharedPtr<Program> program, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Program> m_program;
};

LinkProgram::LinkProgram(SharedPtr<Program> program, bool useSync, bool serverSync)
    : Operation("LinkProgram", useSync, serverSync)
    , m_program(program)
{
    modifyGLObject(SharedPtr<Object>(m_program));
    program->linked = true;
}

void LinkProgram::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);
    GLuint program    = m_program->program;

    thread.newMessage() << "Begin -- glLinkProgram(" << program << ")" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, linkProgram(program));
    thread.newMessage() << "End -- glLinkProgram()" << tcu::ThreadUtil::Message::End;
}

class RenderBuffer : public Operation
{
public:
    RenderBuffer(SharedPtr<Program> program, SharedPtr<Buffer> buffer, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Program> m_program;
    SharedPtr<Buffer> m_buffer;
};

RenderBuffer::RenderBuffer(SharedPtr<Program> program, SharedPtr<Buffer> buffer, bool useSync, bool serverSync)
    : Operation("RenderBuffer", useSync, serverSync)
    , m_program(program)
    , m_buffer(buffer)
{
    readGLObject(SharedPtr<Object>(program));
    readGLObject(SharedPtr<Object>(buffer));
}

void RenderBuffer::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    thread.newMessage() << "Begin -- glClearColor(0.5f, 0.5f, 0.5f, 1.0f)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, clearColor(0.5f, 0.5f, 0.5f, 1.0f));
    thread.newMessage() << "End -- glClearColor()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glClear(GL_COLOR_BUFFER_BIT)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, clear(GL_COLOR_BUFFER_BIT));
    thread.newMessage() << "End -- glClear()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glUseProgram(" << m_program->program << ")" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, useProgram(m_program->program));
    thread.newMessage() << "End -- glUseProgram()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glGetAttribLocation(" << m_program->program << ", \"a_pos\")"
                        << tcu::ThreadUtil::Message::End;
    GLint posLoc = thread.gl.getAttribLocation(m_program->program, "a_pos");
    GLU_CHECK_GLW_MSG(thread.gl, "glGetAttribLocation()");
    thread.newMessage() << "End -- " << posLoc << " = glGetAttribLocation()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glEnableVertexAttribArray(" << posLoc << ")" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, enableVertexAttribArray(posLoc));
    thread.newMessage() << "End -- glEnableVertexAttribArray()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glBindBuffer(GL_ARRAY_BUFFER, " << m_buffer->buffer << ")"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindBuffer(GL_ARRAY_BUFFER, m_buffer->buffer));
    thread.newMessage() << "End -- glBindBuffer()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glVertexAttribPointer(" << posLoc << ", GL_BYTE, GL_TRUE, 0, 0)"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, vertexAttribPointer(posLoc, 2, GL_BYTE, GL_TRUE, 0, 0));
    thread.newMessage() << "End -- glVertexAttribPointer()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glDrawArrays(GL_TRIANGLES, 0, " << (m_buffer->size / 2) << ")"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, drawArrays(GL_TRIANGLES, 0, (GLsizei)m_buffer->size / 2));
    thread.newMessage() << "End -- glDrawArrays()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glBindBuffer(GL_ARRAY_BUFFER, 0)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindBuffer(GL_ARRAY_BUFFER, 0));
    thread.newMessage() << "End -- glBindBuffer()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glDisableVertexAttribArray(" << posLoc << ")" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, disableVertexAttribArray(posLoc));
    thread.newMessage() << "End -- glDisableVertexAttribArray()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glUseProgram(0)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, useProgram(0));
    thread.newMessage() << "End -- glUseProgram()" << tcu::ThreadUtil::Message::End;
}

class RenderTexture : public Operation
{
public:
    RenderTexture(SharedPtr<Program> program, SharedPtr<Texture> texture, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Program> m_program;
    SharedPtr<Texture> m_texture;
};

RenderTexture::RenderTexture(SharedPtr<Program> program, SharedPtr<Texture> texture, bool useSync, bool serverSync)
    : Operation("RenderTexture", useSync, serverSync)
    , m_program(program)
    , m_texture(texture)
{
    readGLObject(SharedPtr<Object>(program));
    readGLObject(SharedPtr<Object>(texture));
}

void RenderTexture::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    thread.newMessage() << "Begin -- glClearColor(0.5f, 0.5f, 0.5f, 1.0f)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, clearColor(0.5f, 0.5f, 0.5f, 1.0f));
    thread.newMessage() << "End -- glClearColor()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glClear(GL_COLOR_BUFFER_BIT)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, clear(GL_COLOR_BUFFER_BIT));
    thread.newMessage() << "End -- glClear()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glUseProgram(" << m_program->program << ")" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, useProgram(m_program->program));
    thread.newMessage() << "End -- glUseProgram()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, " << m_texture->texture << ")"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindTexture(GL_TEXTURE_2D, m_texture->texture));
    thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glGetUniformLocation(" << m_program->program << ", \"u_sampler\")"
                        << tcu::ThreadUtil::Message::End;
    GLint samplerPos = thread.gl.getUniformLocation(m_program->program, "u_sampler");
    GLU_CHECK_GLW_MSG(thread.gl, "glGetUniformLocation()");
    thread.newMessage() << "End -- glGetUniformLocation()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glUniform1i(" << samplerPos << ", 0)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, uniform1i(samplerPos, 0));
    thread.newMessage() << "End -- glUniform1i()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glGetAttribLocation(" << m_program->program << ", \"a_pos\")"
                        << tcu::ThreadUtil::Message::End;
    GLint posLoc = thread.gl.getAttribLocation(m_program->program, "a_pos");
    GLU_CHECK_GLW_MSG(thread.gl, "glGetAttribLocation()");
    thread.newMessage() << "End -- " << posLoc << " = glGetAttribLocation()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glEnableVertexAttribArray(" << posLoc << ")" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, enableVertexAttribArray(posLoc));
    thread.newMessage() << "End -- glEnableVertexAttribArray()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glBindBuffer(GL_ARRAY_BUFFER, 0)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindBuffer(GL_ARRAY_BUFFER, 0));
    thread.newMessage() << "End -- glBindBuffer()" << tcu::ThreadUtil::Message::End;

    float coords[] = {-1.0, -1.0, 1.0,  -1.0, 1.0,  1.0,

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

    thread.newMessage() << "Begin -- glVertexAttribPointer(" << posLoc << ", GL_FLOAT, GL_FALSE, 0, <data>)"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, vertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, coords));
    thread.newMessage() << "End -- glVertexAttribPointer()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glDrawArrays(GL_TRIANGLES, 0, 6)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, drawArrays(GL_TRIANGLES, 0, 6));
    thread.newMessage() << "End -- glDrawArrays()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glBindBuffer(GL_ARRAY_BUFFER, 0)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindBuffer(GL_ARRAY_BUFFER, 0));
    thread.newMessage() << "End -- glBindBuffer()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, 0)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindTexture(GL_TEXTURE_2D, 0));
    thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glDisableVertexAttribArray(" << posLoc << ")" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, disableVertexAttribArray(posLoc));
    thread.newMessage() << "End -- glDisableVertexAttribArray()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glUseProgram(0)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, useProgram(0));
    thread.newMessage() << "End -- glUseProgram()" << tcu::ThreadUtil::Message::End;
}

class ReadPixels : public Operation
{
public:
    ReadPixels(int x, int y, int width, int height, GLenum format, GLenum type,
               SharedPtr<tcu::ThreadUtil::DataBlock> &data, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    int m_x;
    int m_y;
    int m_width;
    int m_height;
    GLenum m_format;
    GLenum m_type;
    SharedPtr<tcu::ThreadUtil::DataBlock> m_data;
};

ReadPixels::ReadPixels(int x, int y, int width, int height, GLenum format, GLenum type,
                       SharedPtr<tcu::ThreadUtil::DataBlock> &data, bool useSync, bool serverSync)
    : Operation("ReadPixels", useSync, serverSync)
    , m_x(x)
    , m_y(y)
    , m_width(width)
    , m_height(height)
    , m_format(format)
    , m_type(type)
{
    data   = SharedPtr<tcu::ThreadUtil::DataBlock>(new tcu::ThreadUtil::DataBlock(getEvent()));
    m_data = data;
}

void ReadPixels::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    DE_ASSERT(m_type == GL_UNSIGNED_BYTE);
    DE_ASSERT(m_format == GL_RGBA);

    std::vector<uint8_t> data((m_width - m_x) * (m_height - m_y) * 4);

    thread.newMessage() << "Begin -- glReadPixels(" << m_x << ", " << m_y << ", " << m_width << ", " << m_height << ", "
                        << m_format << ", " << m_type << ", <data>)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, readPixels(m_x, m_y, m_width, m_height, m_format, m_type, &(data[0])));
    thread.newMessage() << "End -- glReadPixels()" << tcu::ThreadUtil::Message::End;

    m_data->setData(data.size(), &(data[0]));
}

class CreateImageFromTexture : public Operation
{
public:
    // \note [mika] Unlike eglCreateImageKHR this operation requires current context and uses it for creating EGLImage
    //                Current context is required to support EGL sync objects in current tests system
    CreateImageFromTexture(SharedPtr<EGLImage> &image, SharedPtr<Texture> texture, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Texture> m_texture;
    SharedPtr<EGLImage> m_image;
};

CreateImageFromTexture::CreateImageFromTexture(SharedPtr<EGLImage> &image, SharedPtr<Texture> texture, bool useSync,
                                               bool serverSync)
    : Operation("CreateImageFromTexture", useSync, serverSync)
{
    modifyGLObject(SharedPtr<Object>(texture));
    image = SharedPtr<EGLImage>(new EGLImage(getEvent(), getSync()));

    m_image                = image;
    m_texture              = texture;
    m_texture->sourceImage = m_image;
}

void CreateImageFromTexture::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    EGLint attribList[] = {EGL_GL_TEXTURE_LEVEL_KHR, 0, EGL_NONE};

    thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, " << m_texture->texture << ")"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindTexture(GL_TEXTURE_2D, m_texture->texture));
    thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;

    // Make texture image complete...
    thread.newMessage() << "Begin -- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
    thread.newMessage() << "End -- glTexParameteri()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
    thread.newMessage() << "End -- glTexParameteri()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
    thread.newMessage() << "End -- glTexParameteri()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
    thread.newMessage() << "End -- glTexParameteri()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- eglCreateImageKHR(" << thread.runtimeContext->display << ", "
                        << thread.runtimeContext->context << ", EGL_GL_TEXTURE_2D_KHR, " << m_texture->texture
                        << ", { EGL_GL_TEXTURE_LEVEL_KHR, 0, EGL_NONE })" << tcu::ThreadUtil::Message::End;
    m_image->image =
        thread.egl.createImageKHR(thread.runtimeContext->display, thread.runtimeContext->context, EGL_GL_TEXTURE_2D_KHR,
                                  (EGLClientBuffer)(uintptr_t)m_texture->texture, attribList);
    EGLU_CHECK_MSG(thread.egl, "eglCreateImageKHR()");
    thread.newMessage() << "End -- " << m_image->image << " = eglCreateImageKHR()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, 0)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindTexture(GL_TEXTURE_2D, 0));
    thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
}

class DestroyImage : public Operation
{
public:
    // \note [mika] Unlike eglDestroyImageKHR this operation requires current context and uses it for creating EGLImage
    //                Current context is required to support EGL sync objects in current tests system
    DestroyImage(SharedPtr<EGLImage> image, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<EGLImage> m_image;
};

DestroyImage::DestroyImage(SharedPtr<EGLImage> image, bool useSync, bool serverSync)
    : Operation("CreateImageFromTexture", useSync, serverSync)
    , m_image(image)
{
    modifyGLObject(SharedPtr<Object>(image));
}

void DestroyImage::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    thread.newMessage() << "Begin -- eglDestroyImageKHR(" << thread.runtimeContext->display << ", " << m_image->image
                        << ")" << tcu::ThreadUtil::Message::End;
    thread.egl.destroyImageKHR(thread.runtimeContext->display, m_image->image);
    m_image->image = EGL_NO_IMAGE_KHR;
    EGLU_CHECK_MSG(thread.egl, "eglDestroyImageKHR()");
    thread.newMessage() << "End -- eglDestroyImageKHR()" << tcu::ThreadUtil::Message::End;
}

class DefineTextureFromImage : public Operation
{
public:
    DefineTextureFromImage(SharedPtr<Texture> texture, SharedPtr<EGLImage> image, bool useSync, bool serverSync);
    void exec(tcu::ThreadUtil::Thread &thread);

private:
    SharedPtr<Texture> m_texture;
    SharedPtr<EGLImage> m_image;
};

DefineTextureFromImage::DefineTextureFromImage(SharedPtr<Texture> texture, SharedPtr<EGLImage> image, bool useSync,
                                               bool serverSync)
    : Operation("DefineTextureFromImage", useSync, serverSync)
{
    readGLObject(SharedPtr<Object>(image));
    modifyGLObject(SharedPtr<Object>(texture));

    texture->isDefined   = true;
    texture->sourceImage = image;

    m_image   = image;
    m_texture = texture;
}

void DefineTextureFromImage::exec(tcu::ThreadUtil::Thread &t)
{
    EGLThread &thread = dynamic_cast<EGLThread &>(t);

    thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, " << m_texture->texture << ")"
                        << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindTexture(GL_TEXTURE_2D, m_texture->texture));
    thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, " << m_image->image << ")"
                        << tcu::ThreadUtil::Message::End;
    thread.runtimeContext->glExtensions.imageTargetTexture2D(GL_TEXTURE_2D, m_image->image);
    GLU_CHECK_GLW_MSG(thread.gl, "glEGLImageTargetTexture2DOES()");
    thread.newMessage() << "End -- glEGLImageTargetTexture2DOES()" << tcu::ThreadUtil::Message::End;

    thread.newMessage() << "Begin -- glBindTexture(GL_TEXTURE_2D, 0)" << tcu::ThreadUtil::Message::End;
    GLU_CHECK_GLW_CALL(thread.gl, bindTexture(GL_TEXTURE_2D, 0));
    thread.newMessage() << "End -- glBindTexture()" << tcu::ThreadUtil::Message::End;
}

} // namespace GLES2ThreadTest

static void requireEGLExtension(const Library &egl, EGLDisplay eglDisplay, const char *requiredExtension)
{
    if (!eglu::hasExtension(egl, eglDisplay, requiredExtension))
        TCU_THROW(NotSupportedError, (string(requiredExtension) + " not supported").c_str());
}

enum OperationId
{
    THREADOPERATIONID_NONE = 0,

    THREADOPERATIONID_CREATE_BUFFER,
    THREADOPERATIONID_DESTROY_BUFFER,
    THREADOPERATIONID_BUFFER_DATA,
    THREADOPERATIONID_BUFFER_SUBDATA,

    THREADOPERATIONID_CREATE_TEXTURE,
    THREADOPERATIONID_DESTROY_TEXTURE,
    THREADOPERATIONID_TEXIMAGE2D,
    THREADOPERATIONID_TEXSUBIMAGE2D,
    THREADOPERATIONID_COPYTEXIMAGE2D,
    THREADOPERATIONID_COPYTEXSUBIMAGE2D,

    THREADOPERATIONID_CREATE_VERTEX_SHADER,
    THREADOPERATIONID_CREATE_FRAGMENT_SHADER,
    THREADOPERATIONID_DESTROY_SHADER,
    THREADOPERATIONID_SHADER_SOURCE,
    THREADOPERATIONID_SHADER_COMPILE,

    THREADOPERATIONID_ATTACH_SHADER,
    THREADOPERATIONID_DETACH_SHADER,

    THREADOPERATIONID_CREATE_PROGRAM,
    THREADOPERATIONID_DESTROY_PROGRAM,
    THREADOPERATIONID_LINK_PROGRAM,

    THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE,
    THREADOPERATIONID_DESTROY_IMAGE,
    THREADOPERATIONID_TEXTURE_FROM_IMAGE,

    THREADOPERATIONID_LAST
};

class GLES2SharingRandomTest : public TestCase
{
public:
    struct TestConfig
    {
        TestConfig(void);
        int threadCount;
        int operationCount;
        bool serverSync;
        bool useFenceSync;
        bool useImages;

        float probabilities[THREADOPERATIONID_LAST][THREADOPERATIONID_LAST];
    };
    GLES2SharingRandomTest(EglTestContext &context, const TestConfig &config, const char *name,
                           const char *description);
    ~GLES2SharingRandomTest(void);

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

    void addRandomOperation(GLES2ThreadTest::EGLResourceManager &resourceManager);

private:
    TestConfig m_config;
    int m_seed;
    de::Random m_random;
    tcu::TestLog &m_log;
    bool m_threadsStarted;
    bool m_threadsRunning;
    bool m_executionReady;
    bool m_requiresRestart;
    uint64_t m_beginTimeUs;
    uint64_t m_timeOutUs;
    uint32_t m_sleepTimeMs;
    uint64_t m_timeOutTimeUs;

    std::vector<GLES2ThreadTest::EGLThread *> m_threads;

    EGLDisplay m_eglDisplay;
    EGLConfig m_eglConfig;
    OperationId m_lastOperation;

    glw::Functions m_gl;
};

GLES2SharingRandomTest::TestConfig::TestConfig(void)
    : threadCount(0)
    , operationCount(0)
    , serverSync(false)
    , useFenceSync(false)
    , useImages(false)
{
    deMemset(probabilities, 0, sizeof(probabilities));
}

GLES2SharingRandomTest::GLES2SharingRandomTest(EglTestContext &context, const TestConfig &config, const char *name,
                                               const char *description)
    : TestCase(context, name, description)
    , m_config(config)
    , m_seed(deStringHash(name))
    , m_random(deStringHash(name))
    , m_log(m_testCtx.getLog())
    , m_threadsStarted(false)
    , m_threadsRunning(false)
    , m_executionReady(false)
    , m_requiresRestart(false)
    , m_beginTimeUs(0)
    , m_timeOutUs(10000000) // 10 seconds
    , m_sleepTimeMs(1)      // 1 milliseconds
    , m_timeOutTimeUs(0)
    , m_eglDisplay(EGL_NO_DISPLAY)
    , m_eglConfig(0)
    , m_lastOperation(THREADOPERATIONID_NONE)
{
}

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

void GLES2SharingRandomTest::init(void)
{
    const Library &egl = m_eglTestCtx.getLibrary();

    const EGLint attribList[] = {
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_ALPHA_SIZE, 1, EGL_NONE};

    m_eglDisplay = eglu::getAndInitDisplay(m_eglTestCtx.getNativeDisplay());
    m_eglConfig  = eglu::chooseSingleConfig(egl, m_eglDisplay, attribList);

    m_eglTestCtx.initGLFunctions(&m_gl, glu::ApiType::es(2, 0));

    // Check extensions
    if (m_config.useFenceSync)
        requireEGLExtension(egl, m_eglDisplay, "EGL_KHR_fence_sync");

    if (m_config.serverSync)
        requireEGLExtension(egl, m_eglDisplay, "EGL_KHR_wait_sync");

    if (m_config.useImages)
    {
        requireEGLExtension(egl, m_eglDisplay, "EGL_KHR_image_base");
        requireEGLExtension(egl, m_eglDisplay, "EGL_KHR_gl_texture_2D_image");
    }

    GLES2ThreadTest::EGLResourceManager resourceManager;
    // Create contexts
    for (int threadNdx = 0; threadNdx < m_config.threadCount; threadNdx++)
    {
        m_threads.push_back(new GLES2ThreadTest::EGLThread(egl, m_gl, deInt32Hash(m_seed + threadNdx)));
        SharedPtr<GLES2ThreadTest::GLES2Context> context;
        SharedPtr<GLES2ThreadTest::GLES2Context> shared =
            (threadNdx > 0 ? resourceManager.popContext(0) : SharedPtr<GLES2ThreadTest::GLES2Context>());
        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::CreateContext(m_eglDisplay, m_eglConfig, shared, context));

        resourceManager.addContext(context);

        if (shared)
            resourceManager.addContext(shared);
    }

    // Create surfaces
    for (int threadNdx = 0; threadNdx < m_config.threadCount; threadNdx++)
    {
        SharedPtr<GLES2ThreadTest::Surface> surface;
        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::CreatePBufferSurface(m_eglDisplay, m_eglConfig, 400, 400, surface));
        resourceManager.addSurface(surface);
    }

    // Make contexts current
    for (int threadNdx = 0; threadNdx < m_config.threadCount; threadNdx++)
    {
        m_threads[threadNdx]->addOperation(new GLES2ThreadTest::MakeCurrent(
            *m_threads[threadNdx], m_eglDisplay, resourceManager.popSurface(0), resourceManager.popContext(0)));
    }

    // Operations to check fence sync support
    if (m_config.useFenceSync)
    {
        for (int threadNdx = 0; threadNdx < m_config.threadCount; threadNdx++)
        {
            m_threads[threadNdx]->addOperation(new GLES2ThreadTest::InitGLExtension("GL_OES_EGL_sync"));
        }
    }

    // Init EGLimage support
    if (m_config.useImages)
    {
        for (int threadNdx = 0; threadNdx < m_config.threadCount; threadNdx++)
        {
            m_threads[threadNdx]->addOperation(new GLES2ThreadTest::InitGLExtension("GL_OES_EGL_image"));
        }
    }

    // Add random operations
    for (int operationNdx = 0; operationNdx < m_config.operationCount; operationNdx++)
        addRandomOperation(resourceManager);

    {
        int threadNdx = 0;

        // Destroy images
        // \note Android reference counts EGLDisplays so we can't trust the eglTerminate() to clean up resources
        while (resourceManager.getImageCount() > 0)
        {
            const SharedPtr<GLES2ThreadTest::EGLImage> image = resourceManager.popImage(0);

            m_threads[threadNdx]->addOperation(
                new GLES2ThreadTest::DestroyImage(image, m_config.useFenceSync, m_config.serverSync));

            threadNdx = (threadNdx + 1) % m_config.threadCount;
        }
    }

    // Release contexts
    for (int threadNdx = 0; threadNdx < m_config.threadCount; threadNdx++)
    {
        SharedPtr<GLES2ThreadTest::GLES2Context> context = m_threads[threadNdx]->context;
        SharedPtr<GLES2ThreadTest::Surface> surface      = m_threads[threadNdx]->surface;

        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::MakeCurrent(*m_threads[threadNdx], m_eglDisplay, SharedPtr<GLES2ThreadTest::Surface>(),
                                             SharedPtr<GLES2ThreadTest::GLES2Context>()));

        resourceManager.addSurface(surface);
        resourceManager.addContext(context);
    }

    // Destroy contexts
    for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
        m_threads[threadNdx]->addOperation(new GLES2ThreadTest::DestroyContext(resourceManager.popContext(0)));

    // Destroy surfaces
    for (int threadNdx = 0; threadNdx < m_config.threadCount; threadNdx++)
        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::DestroySurface(m_eglDisplay, resourceManager.popSurface(0)));
}

void GLES2SharingRandomTest::deinit(void)
{
    for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
    {
        delete m_threads[threadNdx];
        m_threads[threadNdx] = DE_NULL;
    }

    m_threads.clear();

    if (m_eglDisplay != EGL_NO_DISPLAY)
    {
        m_eglTestCtx.getLibrary().terminate(m_eglDisplay);
        m_eglDisplay = EGL_NO_DISPLAY;
    }

    TCU_CHECK(!m_requiresRestart);
}

void GLES2SharingRandomTest::addRandomOperation(GLES2ThreadTest::EGLResourceManager &resourceManager)
{
    int threadNdx = m_random.getUint32() % (uint32_t)m_threads.size();

    std::vector<OperationId> operations;
    std::vector<float> weights;

    operations.push_back(THREADOPERATIONID_CREATE_BUFFER);
    weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_CREATE_BUFFER]);

    operations.push_back(THREADOPERATIONID_CREATE_TEXTURE);
    weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_CREATE_TEXTURE]);

    operations.push_back(THREADOPERATIONID_CREATE_VERTEX_SHADER);
    weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_CREATE_VERTEX_SHADER]);

    operations.push_back(THREADOPERATIONID_CREATE_FRAGMENT_SHADER);
    weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_CREATE_FRAGMENT_SHADER]);

    operations.push_back(THREADOPERATIONID_CREATE_PROGRAM);
    weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_CREATE_PROGRAM]);

    int destroyableBufferNdx  = -1;
    int destroyableTextureNdx = -1;
    int destroyableShaderNdx  = -1;
    int destroyableProgramNdx = -1;

    int vertexShaderNdx   = -1;
    int fragmentShaderNdx = -1;

    int definedTextureNdx = -1;

    int definedBufferNdx = -1;

    int definedShaderNdx = -1;

    int detachableProgramNdx = -1;
    GLenum detachShaderType  = GL_VERTEX_SHADER;

    int unusedVertexAttachmentProgramNdx   = -1;
    int unusedFragmentAttachmentProgramNdx = -1;

    int linkableProgramNdx = -1;

    int attachProgramNdx = -1;
    int attachShaderNdx  = -1;

    int nonSiblingTextureNdx = -1;

    if (m_threads[threadNdx]->context->resourceManager->getBufferCount() > 0)
        destroyableBufferNdx = m_random.getUint32() % m_threads[threadNdx]->context->resourceManager->getBufferCount();

    if (m_threads[threadNdx]->context->resourceManager->getTextureCount() > 0)
        destroyableTextureNdx =
            m_random.getUint32() % m_threads[threadNdx]->context->resourceManager->getTextureCount();

    if (m_threads[threadNdx]->context->resourceManager->getShaderCount() > 0)
        destroyableShaderNdx = m_random.getUint32() % m_threads[threadNdx]->context->resourceManager->getShaderCount();

    if (m_threads[threadNdx]->context->resourceManager->getProgramCount() > 0)
        destroyableProgramNdx =
            m_random.getUint32() % m_threads[threadNdx]->context->resourceManager->getProgramCount();

    // Check what kind of buffers we have
    for (int bufferNdx = 0; bufferNdx < m_threads[threadNdx]->context->resourceManager->getBufferCount(); bufferNdx++)
    {
        SharedPtr<GLES2ThreadTest::Buffer> buffer =
            m_threads[threadNdx]->context->resourceManager->getBuffer(bufferNdx);

        if (buffer->isDefined)
        {
            if (definedBufferNdx == -1)
                definedBufferNdx = bufferNdx;
            else if (m_random.getBool())
                definedBufferNdx = bufferNdx;
        }
    }

    // Check what kind of textures we have
    for (int textureNdx = 0; textureNdx < m_threads[threadNdx]->context->resourceManager->getTextureCount();
         textureNdx++)
    {
        SharedPtr<GLES2ThreadTest::Texture> texture =
            m_threads[threadNdx]->context->resourceManager->getTexture(textureNdx);

        if (texture->isDefined)
        {
            if (definedTextureNdx == -1)
                definedTextureNdx = textureNdx;
            else if (m_random.getBool())
                definedTextureNdx = textureNdx;

            if (!texture->sourceImage)
            {
                if (nonSiblingTextureNdx == -1)
                    nonSiblingTextureNdx = textureNdx;
                else if (m_random.getBool())
                    nonSiblingTextureNdx = textureNdx;
            }
        }
    }

    // Check what kind of shaders we have
    for (int shaderNdx = 0; shaderNdx < m_threads[threadNdx]->context->resourceManager->getShaderCount(); shaderNdx++)
    {
        SharedPtr<GLES2ThreadTest::Shader> shader =
            m_threads[threadNdx]->context->resourceManager->getShader(shaderNdx);

        // Defined shader found
        if (shader->isDefined)
        {
            if (definedShaderNdx == -1)
                definedShaderNdx = shaderNdx;
            else if (m_random.getBool())
                definedShaderNdx = shaderNdx;
        }

        // Vertex shader found
        if (shader->type == GL_VERTEX_SHADER)
        {
            if (vertexShaderNdx == -1)
                vertexShaderNdx = shaderNdx;
            else if (m_random.getBool())
                vertexShaderNdx = shaderNdx;
        }

        // Fragmet shader found
        if (shader->type == GL_FRAGMENT_SHADER)
        {
            if (fragmentShaderNdx == -1)
                fragmentShaderNdx = shaderNdx;
            else if (m_random.getBool())
                fragmentShaderNdx = shaderNdx;
        }
    }

    // Check what kind of programs we have
    for (int programNdx = 0; programNdx < m_threads[threadNdx]->context->resourceManager->getProgramCount();
         programNdx++)
    {
        SharedPtr<GLES2ThreadTest::Program> program =
            m_threads[threadNdx]->context->resourceManager->getProgram(programNdx);

        // Program that can be detached
        if (program->vertexShader || program->fragmentShader)
        {
            if (detachableProgramNdx == -1)
            {
                detachableProgramNdx = programNdx;

                if (program->vertexShader)
                    detachShaderType = GL_VERTEX_SHADER;
                else if (program->fragmentShader)
                    detachShaderType = GL_FRAGMENT_SHADER;
                else
                    DE_ASSERT(false);
            }
            else if (m_random.getBool())
            {
                detachableProgramNdx = programNdx;

                if (program->vertexShader)
                    detachShaderType = GL_VERTEX_SHADER;
                else if (program->fragmentShader)
                    detachShaderType = GL_FRAGMENT_SHADER;
                else
                    DE_ASSERT(false);
            }
        }

        // Program that can be attached vertex shader
        if (!program->vertexShader)
        {
            if (unusedVertexAttachmentProgramNdx == -1)
                unusedVertexAttachmentProgramNdx = programNdx;
            else if (m_random.getBool())
                unusedVertexAttachmentProgramNdx = programNdx;
        }

        // Program that can be attached fragment shader
        if (!program->fragmentShader)
        {
            if (unusedFragmentAttachmentProgramNdx == -1)
                unusedFragmentAttachmentProgramNdx = programNdx;
            else if (m_random.getBool())
                unusedFragmentAttachmentProgramNdx = programNdx;
        }

        // Program that can be linked
        if (program->vertexShader && program->fragmentShader)
        {
            if (linkableProgramNdx == -1)
                linkableProgramNdx = programNdx;
            else if (m_random.getBool())
                linkableProgramNdx = programNdx;
        }
    }

    // Has images
    if (resourceManager.getImageCount() > 0)
    {
        weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_DESTROY_IMAGE]);
        operations.push_back(THREADOPERATIONID_DESTROY_IMAGE);

        if (m_threads[threadNdx]->context->resourceManager->getTextureCount() > 0)
        {
            weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_TEXTURE_FROM_IMAGE]);
            operations.push_back(THREADOPERATIONID_TEXTURE_FROM_IMAGE);
        }
    }

    // Has buffer
    if (destroyableBufferNdx != -1)
    {
        weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_DESTROY_BUFFER]);
        operations.push_back(THREADOPERATIONID_DESTROY_BUFFER);

        weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_BUFFER_DATA]);
        operations.push_back(THREADOPERATIONID_BUFFER_DATA);
    }

    // Has buffer with defined data
    if (definedBufferNdx != -1)
    {
        weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_BUFFER_SUBDATA]);
        operations.push_back(THREADOPERATIONID_BUFFER_SUBDATA);
    }

    // Has texture
    if (destroyableTextureNdx != -1)
    {
        weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_DESTROY_TEXTURE]);
        operations.push_back(THREADOPERATIONID_DESTROY_TEXTURE);

        weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_TEXIMAGE2D]);
        operations.push_back(THREADOPERATIONID_TEXIMAGE2D);

        weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_COPYTEXIMAGE2D]);
        operations.push_back(THREADOPERATIONID_COPYTEXIMAGE2D);
    }

    // Has texture with data
    if (definedTextureNdx != -1)
    {
        weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_TEXSUBIMAGE2D]);
        operations.push_back(THREADOPERATIONID_TEXSUBIMAGE2D);

        weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_COPYTEXSUBIMAGE2D]);
        operations.push_back(THREADOPERATIONID_COPYTEXSUBIMAGE2D);
    }

    // Has texture that can be used as EGLimage source
    if (nonSiblingTextureNdx != -1)
    {
        weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]);
        operations.push_back(THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE);
    }

    // Has shader
    if (destroyableShaderNdx != -1)
    {
        weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_DESTROY_SHADER]);
        operations.push_back(THREADOPERATIONID_DESTROY_SHADER);

        weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_SHADER_SOURCE]);
        operations.push_back(THREADOPERATIONID_SHADER_SOURCE);
    }

    // Has shader with defined source
    if (definedShaderNdx != -1)
    {
        weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_SHADER_COMPILE]);
        operations.push_back(THREADOPERATIONID_SHADER_COMPILE);
    }

    // Has program
    if (destroyableProgramNdx != -1)
    {
        weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_DESTROY_PROGRAM]);
        operations.push_back(THREADOPERATIONID_DESTROY_PROGRAM);
    }

    // Has program that can be linked
    if (linkableProgramNdx != -1)
    {
        weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_LINK_PROGRAM]);
        operations.push_back(THREADOPERATIONID_LINK_PROGRAM);
    }

    // has program with attachments
    if (detachableProgramNdx != -1)
    {
        weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_DETACH_SHADER]);
        operations.push_back(THREADOPERATIONID_DETACH_SHADER);
    }

    // Has program and shader pair that can be attached
    if (fragmentShaderNdx != -1 && unusedFragmentAttachmentProgramNdx != -1)
    {
        if (attachProgramNdx == -1)
        {
            DE_ASSERT(attachShaderNdx == -1);
            attachProgramNdx = unusedFragmentAttachmentProgramNdx;
            attachShaderNdx  = fragmentShaderNdx;

            weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_ATTACH_SHADER]);
            operations.push_back(THREADOPERATIONID_ATTACH_SHADER);
        }
        else if (m_random.getBool())
        {
            attachProgramNdx = unusedFragmentAttachmentProgramNdx;
            attachShaderNdx  = fragmentShaderNdx;
        }
    }

    if (vertexShaderNdx != -1 && unusedVertexAttachmentProgramNdx != -1)
    {
        if (attachProgramNdx == -1)
        {
            DE_ASSERT(attachShaderNdx == -1);
            attachProgramNdx = unusedVertexAttachmentProgramNdx;
            attachShaderNdx  = vertexShaderNdx;

            weights.push_back(m_config.probabilities[m_lastOperation][THREADOPERATIONID_ATTACH_SHADER]);
            operations.push_back(THREADOPERATIONID_ATTACH_SHADER);
        }
        else if (m_random.getBool())
        {
            attachProgramNdx = unusedVertexAttachmentProgramNdx;
            attachShaderNdx  = vertexShaderNdx;
        }
    }

    OperationId op = m_random.chooseWeighted<OperationId, std::vector<OperationId>::iterator>(
        operations.begin(), operations.end(), weights.begin());

    switch (op)
    {
    case THREADOPERATIONID_CREATE_BUFFER:
    {
        SharedPtr<GLES2ThreadTest::Buffer> buffer;
        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::CreateBuffer(buffer, m_config.useFenceSync, m_config.serverSync));
        m_threads[threadNdx]->context->resourceManager->addBuffer(buffer);
        break;
    }

    case THREADOPERATIONID_DESTROY_BUFFER:
    {
        SharedPtr<GLES2ThreadTest::Buffer> buffer =
            m_threads[threadNdx]->context->resourceManager->popBuffer(destroyableBufferNdx);
        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::DeleteBuffer(buffer, m_config.useFenceSync, m_config.serverSync));
        break;
    }

    case THREADOPERATIONID_BUFFER_DATA:
    {
        SharedPtr<GLES2ThreadTest::Buffer> buffer =
            m_threads[threadNdx]->context->resourceManager->popBuffer(destroyableBufferNdx);
        m_threads[threadNdx]->addOperation(new GLES2ThreadTest::BufferData(
            buffer, GL_ARRAY_BUFFER, 1024, GL_DYNAMIC_DRAW, m_config.useFenceSync, m_config.serverSync));
        m_threads[threadNdx]->context->resourceManager->addBuffer(buffer);
        break;
    }

    case THREADOPERATIONID_BUFFER_SUBDATA:
    {
        SharedPtr<GLES2ThreadTest::Buffer> buffer =
            m_threads[threadNdx]->context->resourceManager->popBuffer(definedBufferNdx);
        m_threads[threadNdx]->addOperation(new GLES2ThreadTest::BufferSubData(
            buffer, GL_ARRAY_BUFFER, 1, 20, m_config.useFenceSync, m_config.serverSync));
        m_threads[threadNdx]->context->resourceManager->addBuffer(buffer);
        break;
    }

    case THREADOPERATIONID_CREATE_TEXTURE:
    {
        SharedPtr<GLES2ThreadTest::Texture> texture;
        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::CreateTexture(texture, m_config.useFenceSync, m_config.serverSync));
        m_threads[threadNdx]->context->resourceManager->addTexture(texture);
        break;
    }

    case THREADOPERATIONID_DESTROY_TEXTURE:
        m_threads[threadNdx]->addOperation(new GLES2ThreadTest::DeleteTexture(
            m_threads[threadNdx]->context->resourceManager->popTexture(destroyableTextureNdx), m_config.useFenceSync,
            m_config.serverSync));
        break;

    case THREADOPERATIONID_TEXIMAGE2D:
    {
        SharedPtr<GLES2ThreadTest::Texture> texture =
            m_threads[threadNdx]->context->resourceManager->popTexture(destroyableTextureNdx);
        m_threads[threadNdx]->addOperation(new GLES2ThreadTest::TexImage2D(
            texture, 0, GL_RGBA, 400, 400, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
        m_threads[threadNdx]->context->resourceManager->addTexture(texture);
        break;
    }

    case THREADOPERATIONID_TEXSUBIMAGE2D:
    {
        SharedPtr<GLES2ThreadTest::Texture> texture =
            m_threads[threadNdx]->context->resourceManager->popTexture(definedTextureNdx);
        m_threads[threadNdx]->addOperation(new GLES2ThreadTest::TexSubImage2D(
            texture, 0, 30, 30, 50, 50, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
        m_threads[threadNdx]->context->resourceManager->addTexture(texture);
        break;
    }

    case THREADOPERATIONID_COPYTEXIMAGE2D:
    {
        SharedPtr<GLES2ThreadTest::Texture> texture =
            m_threads[threadNdx]->context->resourceManager->popTexture(destroyableTextureNdx);
        m_threads[threadNdx]->addOperation(new GLES2ThreadTest::CopyTexImage2D(
            texture, 0, GL_RGBA, 20, 20, 300, 300, 0, m_config.useFenceSync, m_config.serverSync));
        m_threads[threadNdx]->context->resourceManager->addTexture(texture);
        break;
    }

    case THREADOPERATIONID_COPYTEXSUBIMAGE2D:
    {
        SharedPtr<GLES2ThreadTest::Texture> texture =
            m_threads[threadNdx]->context->resourceManager->popTexture(definedTextureNdx);
        m_threads[threadNdx]->addOperation(new GLES2ThreadTest::CopyTexSubImage2D(
            texture, 0, 10, 10, 30, 30, 50, 50, m_config.useFenceSync, m_config.serverSync));
        m_threads[threadNdx]->context->resourceManager->addTexture(texture);
        break;
    }

    case THREADOPERATIONID_CREATE_VERTEX_SHADER:
    {
        SharedPtr<GLES2ThreadTest::Shader> shader;
        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::CreateShader(GL_VERTEX_SHADER, shader, m_config.useFenceSync, m_config.serverSync));
        m_threads[threadNdx]->context->resourceManager->addShader(shader);
        break;
    }

    case THREADOPERATIONID_CREATE_FRAGMENT_SHADER:
    {
        SharedPtr<GLES2ThreadTest::Shader> shader;
        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::CreateShader(GL_FRAGMENT_SHADER, shader, m_config.useFenceSync, m_config.serverSync));
        m_threads[threadNdx]->context->resourceManager->addShader(shader);
        break;
    }

    case THREADOPERATIONID_DESTROY_SHADER:
        m_threads[threadNdx]->addOperation(new GLES2ThreadTest::DeleteShader(
            m_threads[threadNdx]->context->resourceManager->popShader(destroyableShaderNdx), m_config.useFenceSync,
            m_config.serverSync));
        break;

    case THREADOPERATIONID_SHADER_SOURCE:
    {
        const char *vertexShaderSource = "attribute mediump vec4 a_pos;\n"
                                         "varying mediump vec4 v_pos;\n"
                                         "void main (void)\n"
                                         "{\n"
                                         "\tv_pos = a_pos;\n"
                                         "\tgl_Position = a_pos;\n"
                                         "}\n";

        const char *fragmentShaderSource = "varying mediump vec4 v_pos;\n"
                                           "void main (void)\n"
                                           "{\n"
                                           "\tgl_FragColor = v_pos;\n"
                                           "}\n";
        SharedPtr<GLES2ThreadTest::Shader> shader =
            m_threads[threadNdx]->context->resourceManager->popShader(destroyableShaderNdx);
        m_threads[threadNdx]->addOperation(new GLES2ThreadTest::ShaderSource(
            shader, (shader->type == GL_VERTEX_SHADER ? vertexShaderSource : fragmentShaderSource),
            m_config.useFenceSync, m_config.serverSync));
        m_threads[threadNdx]->context->resourceManager->addShader(shader);
        break;
    }

    case THREADOPERATIONID_SHADER_COMPILE:
    {
        SharedPtr<GLES2ThreadTest::Shader> shader =
            m_threads[threadNdx]->context->resourceManager->popShader(definedShaderNdx);
        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::ShaderCompile(shader, m_config.useFenceSync, m_config.serverSync));
        m_threads[threadNdx]->context->resourceManager->addShader(shader);
        break;
    }

    case THREADOPERATIONID_CREATE_PROGRAM:
    {
        SharedPtr<GLES2ThreadTest::Program> program;
        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::CreateProgram(program, m_config.useFenceSync, m_config.serverSync));
        m_threads[threadNdx]->context->resourceManager->addProgram(program);
        break;
    }

    case THREADOPERATIONID_DESTROY_PROGRAM:
        m_threads[threadNdx]->addOperation(new GLES2ThreadTest::DeleteProgram(
            m_threads[threadNdx]->context->resourceManager->popProgram(destroyableProgramNdx), m_config.useFenceSync,
            m_config.serverSync));
        break;

    case THREADOPERATIONID_ATTACH_SHADER:
    {
        SharedPtr<GLES2ThreadTest::Program> program =
            m_threads[threadNdx]->context->resourceManager->popProgram(attachProgramNdx);
        SharedPtr<GLES2ThreadTest::Shader> shader =
            m_threads[threadNdx]->context->resourceManager->popShader(attachShaderNdx);

        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::AttachShader(program, shader, m_config.useFenceSync, m_config.serverSync));

        m_threads[threadNdx]->context->resourceManager->addProgram(program);
        m_threads[threadNdx]->context->resourceManager->addShader(shader);
        break;
    }

    case THREADOPERATIONID_DETACH_SHADER:
    {
        SharedPtr<GLES2ThreadTest::Program> program =
            m_threads[threadNdx]->context->resourceManager->popProgram(detachableProgramNdx);
        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::DetachShader(program, detachShaderType, m_config.useFenceSync, m_config.serverSync));
        m_threads[threadNdx]->context->resourceManager->addProgram(program);
        break;
    }

    case THREADOPERATIONID_LINK_PROGRAM:
    {
        SharedPtr<GLES2ThreadTest::Program> program =
            m_threads[threadNdx]->context->resourceManager->popProgram(linkableProgramNdx);
        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::LinkProgram(program, m_config.useFenceSync, m_config.serverSync));
        m_threads[threadNdx]->context->resourceManager->addProgram(program);
        break;
    }

    case THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE:
    {
        SharedPtr<GLES2ThreadTest::EGLImage> image;
        SharedPtr<GLES2ThreadTest::Texture> texture =
            m_threads[threadNdx]->context->resourceManager->popTexture(nonSiblingTextureNdx);
        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::CreateImageFromTexture(image, texture, m_config.useFenceSync, m_config.serverSync));
        // \note [mika] Can source be added back to resourceManager?
        m_threads[threadNdx]->context->resourceManager->addTexture(texture);
        resourceManager.addImage(image);
        break;
    }

    case THREADOPERATIONID_DESTROY_IMAGE:
    {
        int imageNdx                               = m_random.getInt(0, resourceManager.getImageCount() - 1);
        SharedPtr<GLES2ThreadTest::EGLImage> image = resourceManager.popImage(imageNdx);
        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::DestroyImage(image, m_config.useFenceSync, m_config.serverSync));
        break;
    }

    case THREADOPERATIONID_TEXTURE_FROM_IMAGE:
    {
        int imageNdx = m_random.getInt(0, resourceManager.getImageCount() - 1);
        SharedPtr<GLES2ThreadTest::Texture> texture =
            m_threads[threadNdx]->context->resourceManager->popTexture(destroyableTextureNdx);
        SharedPtr<GLES2ThreadTest::EGLImage> image = resourceManager.popImage(imageNdx);
        m_threads[threadNdx]->addOperation(
            new GLES2ThreadTest::DefineTextureFromImage(texture, image, m_config.useFenceSync, m_config.serverSync));
        m_threads[threadNdx]->context->resourceManager->addTexture(texture);
        resourceManager.addImage(image);
        break;
    }

    default:
        DE_ASSERT(false);
    }

    m_lastOperation = op;
}

tcu::TestCase::IterateResult GLES2SharingRandomTest::iterate(void)
{
    if (!m_threadsStarted)
    {
        m_beginTimeUs = deGetMicroseconds();

        // Execute threads
        for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
            m_threads[threadNdx]->exec();

        m_threadsStarted = true;
        m_threadsRunning = true;
    }

    if (m_threadsRunning)
    {
        // Wait threads to finish
        int readyThreads = 0;
        for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
        {
            const tcu::ThreadUtil::Thread::ThreadStatus status = m_threads[threadNdx]->getStatus();

            if (status != tcu::ThreadUtil::Thread::THREADSTATUS_RUNNING &&
                status != tcu::ThreadUtil::Thread::THREADSTATUS_NOT_STARTED)
                readyThreads++;
        }

        if (readyThreads == (int)m_threads.size())
        {
            for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
                m_threads[threadNdx]->join();

            m_executionReady  = true;
            m_requiresRestart = false;
        }

        if (deGetMicroseconds() - m_beginTimeUs > m_timeOutUs)
        {
            for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
            {
                if (m_threads[threadNdx]->getStatus() != tcu::ThreadUtil::Thread::THREADSTATUS_RUNNING)
                {
                    if (m_threads[threadNdx]->isStarted())
                        m_threads[threadNdx]->join();
                }
            }
            m_executionReady  = true;
            m_requiresRestart = true;
            m_timeOutTimeUs   = deGetMicroseconds();
        }
        else
        {
            deSleep(m_sleepTimeMs);
        }
    }

    if (m_executionReady)
    {
        std::vector<int> indices(m_threads.size(), 0);

        if (m_timeOutTimeUs != 0)
            m_log << tcu::TestLog::Message
                  << "Execution timeout limit reached. Trying to get per thread logs. This is potentially dangerous."
                  << tcu::TestLog::EndMessage;

        while (true)
        {
            int firstThread = -1;

            // Find first thread with messages
            for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
            {
                if (m_threads[threadNdx]->getMessageCount() > indices[threadNdx])
                {
                    firstThread = threadNdx;
                    break;
                }
            }

            // No more messages
            if (firstThread == -1)
                break;

            for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
            {
                // No more messages in this thread
                if (m_threads[threadNdx]->getMessageCount() <= indices[threadNdx])
                    continue;

                if ((m_threads[threadNdx]->getMessage(indices[threadNdx]).getTime() - m_beginTimeUs) <
                    (m_threads[firstThread]->getMessage(indices[firstThread]).getTime() - m_beginTimeUs))
                    firstThread = threadNdx;
            }

            tcu::ThreadUtil::Message message = m_threads[firstThread]->getMessage(indices[firstThread]);

            m_log << tcu::TestLog::Message << "[" << (message.getTime() - m_beginTimeUs) << "] (" << firstThread << ") "
                  << message.getMessage() << tcu::TestLog::EndMessage;
            indices[firstThread]++;
        }

        if (m_timeOutTimeUs != 0)
            m_log << tcu::TestLog::Message << "[" << (m_timeOutTimeUs - m_beginTimeUs)
                  << "] Execution timeout limit reached" << tcu::TestLog::EndMessage;

        bool isOk         = true;
        bool notSupported = false;

        for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
        {
            const tcu::ThreadUtil::Thread::ThreadStatus status = m_threads[threadNdx]->getStatus();

            switch (status)
            {
            case tcu::ThreadUtil::Thread::THREADSTATUS_FAILED:
            case tcu::ThreadUtil::Thread::THREADSTATUS_INIT_FAILED:
            case tcu::ThreadUtil::Thread::THREADSTATUS_RUNNING:
                isOk = false;
                break;

            case tcu::ThreadUtil::Thread::THREADSTATUS_NOT_SUPPORTED:
                notSupported = true;
                break;

            case tcu::ThreadUtil::Thread::THREADSTATUS_READY:
                // Nothing
                break;

            default:
                DE_ASSERT(false);
                isOk = false;
            }
        }

        if (notSupported)
            throw tcu::NotSupportedError("Thread threw tcu::NotSupportedError", "", __FILE__, __LINE__);

        if (isOk)
            m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
        else
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");

        return STOP;
    }

    return CONTINUE;
}

class GLES2ThreadedSharingTest : public TestCase
{
public:
    struct TestConfig
    {
        enum ResourceType
        {
            RESOURCETYPE_BUFFER = 0,
            RESOURCETYPE_TEXTURE,
            RESOURCETYPE_VERTEX_SHADER,
            RESOURCETYPE_FRAGMENT_SHADER,
            RESOURCETYPE_PROGRAM,
            RESOURCETYPE_IMAGE
        };

        ResourceType resourceType;
        bool singleContext;
        int define;
        int modify;
        bool useFenceSync;
        bool serverSync;
        bool render;
    };
    GLES2ThreadedSharingTest(EglTestContext &context, const TestConfig &config, const char *name,
                             const char *description);
    ~GLES2ThreadedSharingTest(void);

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

    void addBufferOperations(void);
    void addTextureOperations(void);
    void addImageOperations(void);
    void addShaderOperations(GLenum type);
    void addProgramOperations(void);

private:
    TestConfig m_config;
    tcu::TestLog &m_log;
    int m_seed;
    bool m_threadsStarted;
    bool m_threadsRunning;
    bool m_executionReady;
    bool m_requiresRestart;
    uint64_t m_beginTimeUs;
    uint64_t m_timeOutUs;
    uint32_t m_sleepTimeMs;
    uint64_t m_timeOutTimeUs;

    std::vector<GLES2ThreadTest::EGLThread *> m_threads;

    EGLDisplay m_eglDisplay;
    EGLConfig m_eglConfig;
    glw::Functions m_gl;
};

GLES2ThreadedSharingTest::GLES2ThreadedSharingTest(EglTestContext &context, const TestConfig &config, const char *name,
                                                   const char *description)
    : TestCase(context, name, description)
    , m_config(config)
    , m_log(m_testCtx.getLog())
    , m_seed(deStringHash(name))
    , m_threadsStarted(false)
    , m_threadsRunning(false)
    , m_executionReady(false)
    , m_requiresRestart(false)
    , m_beginTimeUs(0)
    , m_timeOutUs(10000000) // 10 seconds
    , m_sleepTimeMs(1)      // 1 milliseconds
    , m_timeOutTimeUs(0)
    , m_eglDisplay(EGL_NO_DISPLAY)
    , m_eglConfig(0)
{
}

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

void GLES2ThreadedSharingTest::init(void)
{
    const Library &egl = m_eglTestCtx.getLibrary();

    const EGLint attribList[] = {
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_ALPHA_SIZE, 1, EGL_NONE};

    m_eglDisplay = eglu::getAndInitDisplay(m_eglTestCtx.getNativeDisplay());
    m_eglConfig  = eglu::chooseSingleConfig(egl, m_eglDisplay, attribList);

    m_eglTestCtx.initGLFunctions(&m_gl, glu::ApiType::es(2, 0));

    // Check extensions
    if (m_config.useFenceSync)
        requireEGLExtension(egl, m_eglDisplay, "EGL_KHR_fence_sync");

    if (m_config.serverSync)
        requireEGLExtension(egl, m_eglDisplay, "EGL_KHR_wait_sync");

    if (m_config.resourceType == TestConfig::RESOURCETYPE_IMAGE)
    {
        requireEGLExtension(egl, m_eglDisplay, "EGL_KHR_image_base");
        requireEGLExtension(egl, m_eglDisplay, "EGL_KHR_gl_texture_2D_image");
    }

    // Create threads
    m_threads.push_back(new GLES2ThreadTest::EGLThread(egl, m_gl, deInt32Hash(m_seed)));
    m_threads.push_back(new GLES2ThreadTest::EGLThread(egl, m_gl, deInt32Hash(~m_seed)));

    SharedPtr<GLES2ThreadTest::GLES2Context> contex1;
    SharedPtr<GLES2ThreadTest::GLES2Context> contex2;

    SharedPtr<GLES2ThreadTest::Surface> surface1;
    SharedPtr<GLES2ThreadTest::Surface> surface2;

    // Create contexts
    m_threads[0]->addOperation(new GLES2ThreadTest::CreateContext(m_eglDisplay, m_eglConfig,
                                                                  SharedPtr<GLES2ThreadTest::GLES2Context>(), contex1));
    m_threads[1]->addOperation(new GLES2ThreadTest::CreateContext(m_eglDisplay, m_eglConfig, contex1, contex2));

    // Create surfaces
    m_threads[0]->addOperation(
        new GLES2ThreadTest::CreatePBufferSurface(m_eglDisplay, m_eglConfig, 400, 400, surface1));
    m_threads[1]->addOperation(
        new GLES2ThreadTest::CreatePBufferSurface(m_eglDisplay, m_eglConfig, 400, 400, surface2));

    // Make current contexts
    m_threads[0]->addOperation(new GLES2ThreadTest::MakeCurrent(*m_threads[0], m_eglDisplay, surface1, contex1));
    m_threads[1]->addOperation(new GLES2ThreadTest::MakeCurrent(*m_threads[1], m_eglDisplay, surface2, contex2));
    // Operations to check fence sync support
    if (m_config.useFenceSync)
    {
        m_threads[0]->addOperation(new GLES2ThreadTest::InitGLExtension("GL_OES_EGL_sync"));
        m_threads[1]->addOperation(new GLES2ThreadTest::InitGLExtension("GL_OES_EGL_sync"));
    }

    switch (m_config.resourceType)
    {
    case TestConfig::RESOURCETYPE_BUFFER:
        addBufferOperations();
        break;

    case TestConfig::RESOURCETYPE_TEXTURE:
        addTextureOperations();
        break;

    case TestConfig::RESOURCETYPE_IMAGE:
        addImageOperations();
        break;

    case TestConfig::RESOURCETYPE_VERTEX_SHADER:
        addShaderOperations(GL_VERTEX_SHADER);
        break;

    case TestConfig::RESOURCETYPE_FRAGMENT_SHADER:
        addShaderOperations(GL_FRAGMENT_SHADER);
        break;

    case TestConfig::RESOURCETYPE_PROGRAM:
        addProgramOperations();
        break;

    default:
        DE_ASSERT(false);
    }

    // Relaese contexts
    m_threads[0]->addOperation(new GLES2ThreadTest::MakeCurrent(*m_threads[0], m_eglDisplay,
                                                                SharedPtr<GLES2ThreadTest::Surface>(),
                                                                SharedPtr<GLES2ThreadTest::GLES2Context>()));
    m_threads[1]->addOperation(new GLES2ThreadTest::MakeCurrent(*m_threads[0], m_eglDisplay,
                                                                SharedPtr<GLES2ThreadTest::Surface>(),
                                                                SharedPtr<GLES2ThreadTest::GLES2Context>()));

    // Destory context
    m_threads[0]->addOperation(new GLES2ThreadTest::DestroyContext(contex1));
    m_threads[1]->addOperation(new GLES2ThreadTest::DestroyContext(contex2));

    // Destroy surfaces
    m_threads[0]->addOperation(new GLES2ThreadTest::DestroySurface(m_eglDisplay, surface1));
    m_threads[1]->addOperation(new GLES2ThreadTest::DestroySurface(m_eglDisplay, surface2));
}

void GLES2ThreadedSharingTest::addBufferOperations(void)
{
    // Add operations for verify
    SharedPtr<GLES2ThreadTest::Shader> vertexShader;
    SharedPtr<GLES2ThreadTest::Shader> fragmentShader;
    SharedPtr<GLES2ThreadTest::Program> program;

    if (m_config.render)
    {
        const char *vertexShaderSource = "attribute highp vec2 a_pos;\n"
                                         "varying mediump vec2 v_pos;\n"
                                         "void main(void)\n"
                                         "{\n"
                                         "\tv_pos = a_pos;\n"
                                         "\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
                                         "}\n";

        const char *fragmentShaderSource = "varying mediump vec2 v_pos;\n"
                                           "void main(void)\n"
                                           "{\n"
                                           "\tgl_FragColor = vec4(v_pos, 0.5, 1.0);\n"
                                           "}\n";

        m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(GL_VERTEX_SHADER, vertexShader,
                                                                     m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(vertexShader, vertexShaderSource,
                                                                     m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::ShaderCompile(vertexShader, m_config.useFenceSync, m_config.serverSync));

        m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(GL_FRAGMENT_SHADER, fragmentShader,
                                                                     m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(fragmentShader, fragmentShaderSource,
                                                                     m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::ShaderCompile(fragmentShader, m_config.useFenceSync, m_config.serverSync));

        m_threads[0]->addOperation(
            new GLES2ThreadTest::CreateProgram(program, m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::AttachShader(program, fragmentShader, m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::AttachShader(program, vertexShader, m_config.useFenceSync, m_config.serverSync));

        m_threads[0]->addOperation(
            new GLES2ThreadTest::LinkProgram(program, m_config.useFenceSync, m_config.serverSync));
    }

    SharedPtr<GLES2ThreadTest::Buffer> buffer;

    m_threads[0]->addOperation(new GLES2ThreadTest::CreateBuffer(buffer, m_config.useFenceSync, m_config.serverSync));

    if (m_config.define)
    {
        if (m_config.modify || m_config.render)
            m_threads[0]->addOperation(new GLES2ThreadTest::BufferData(buffer, GL_ARRAY_BUFFER, 1024, GL_DYNAMIC_DRAW,
                                                                       m_config.useFenceSync, m_config.serverSync));
        else
            m_threads[1]->addOperation(new GLES2ThreadTest::BufferData(buffer, GL_ARRAY_BUFFER, 1024, GL_DYNAMIC_DRAW,
                                                                       m_config.useFenceSync, m_config.serverSync));
    }

    if (m_config.modify)
    {
        if (m_config.render)
            m_threads[0]->addOperation(new GLES2ThreadTest::BufferSubData(buffer, GL_ARRAY_BUFFER, 17, 17,
                                                                          m_config.useFenceSync, m_config.serverSync));
        else
            m_threads[1]->addOperation(new GLES2ThreadTest::BufferSubData(buffer, GL_ARRAY_BUFFER, 17, 17,
                                                                          m_config.useFenceSync, m_config.serverSync));
    }

    if (m_config.render)
    {
        m_threads[0]->addOperation(
            new GLES2ThreadTest::RenderBuffer(program, buffer, m_config.useFenceSync, m_config.serverSync));
        m_threads[1]->addOperation(
            new GLES2ThreadTest::RenderBuffer(program, buffer, m_config.useFenceSync, m_config.serverSync));

        SharedPtr<tcu::ThreadUtil::DataBlock> pixels1;
        SharedPtr<tcu::ThreadUtil::DataBlock> pixels2;

        m_threads[0]->addOperation(new GLES2ThreadTest::ReadPixels(0, 0, 400, 400, GL_RGBA, GL_UNSIGNED_BYTE, pixels1,
                                                                   m_config.useFenceSync, m_config.serverSync));
        m_threads[1]->addOperation(new GLES2ThreadTest::ReadPixels(0, 0, 400, 400, GL_RGBA, GL_UNSIGNED_BYTE, pixels2,
                                                                   m_config.useFenceSync, m_config.serverSync));

        m_threads[0]->addOperation(new tcu::ThreadUtil::CompareData(pixels1, pixels2));
    }

    if (m_config.modify || m_config.render)
        m_threads[0]->addOperation(
            new GLES2ThreadTest::DeleteBuffer(buffer, m_config.useFenceSync, m_config.serverSync));
    else
        m_threads[1]->addOperation(
            new GLES2ThreadTest::DeleteBuffer(buffer, m_config.useFenceSync, m_config.serverSync));

    if (m_config.render)
    {
        m_threads[0]->addOperation(
            new GLES2ThreadTest::DeleteShader(vertexShader, m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::DeleteShader(fragmentShader, m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::DeleteProgram(program, m_config.useFenceSync, m_config.serverSync));
    }
}

void GLES2ThreadedSharingTest::addTextureOperations(void)
{
    // Add operations for verify
    SharedPtr<GLES2ThreadTest::Shader> vertexShader;
    SharedPtr<GLES2ThreadTest::Shader> fragmentShader;
    SharedPtr<GLES2ThreadTest::Program> program;

    if (m_config.render)
    {
        const char *vertexShaderSource = "attribute highp vec2 a_pos;\n"
                                         "varying mediump vec2 v_pos;\n"
                                         "void main(void)\n"
                                         "{\n"
                                         "\tv_pos = a_pos;\n"
                                         "\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
                                         "}\n";

        const char *fragmentShaderSource = "varying mediump vec2 v_pos;\n"
                                           "uniform sampler2D u_sampler;\n"
                                           "void main(void)\n"
                                           "{\n"
                                           "\tgl_FragColor = texture2D(u_sampler, v_pos);\n"
                                           "}\n";

        m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(GL_VERTEX_SHADER, vertexShader,
                                                                     m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(vertexShader, vertexShaderSource,
                                                                     m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::ShaderCompile(vertexShader, m_config.useFenceSync, m_config.serverSync));

        m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(GL_FRAGMENT_SHADER, fragmentShader,
                                                                     m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(fragmentShader, fragmentShaderSource,
                                                                     m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::ShaderCompile(fragmentShader, m_config.useFenceSync, m_config.serverSync));

        m_threads[0]->addOperation(
            new GLES2ThreadTest::CreateProgram(program, m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::AttachShader(program, fragmentShader, m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::AttachShader(program, vertexShader, m_config.useFenceSync, m_config.serverSync));

        m_threads[0]->addOperation(
            new GLES2ThreadTest::LinkProgram(program, m_config.useFenceSync, m_config.serverSync));
    }

    SharedPtr<GLES2ThreadTest::Texture> texture;

    m_threads[0]->addOperation(new GLES2ThreadTest::CreateTexture(texture, m_config.useFenceSync, m_config.serverSync));

    if (m_config.define == 1)
    {
        if (m_config.modify || m_config.render)
            m_threads[0]->addOperation(new GLES2ThreadTest::TexImage2D(
                texture, 0, GL_RGBA, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
        else
            m_threads[1]->addOperation(new GLES2ThreadTest::TexImage2D(
                texture, 0, GL_RGBA, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
    }

    if (m_config.define == 2)
    {
        if (m_config.modify || m_config.render)
            m_threads[0]->addOperation(new GLES2ThreadTest::CopyTexImage2D(texture, 0, GL_RGBA, 17, 17, 256, 256, 0,
                                                                           m_config.useFenceSync, m_config.serverSync));
        else
            m_threads[1]->addOperation(new GLES2ThreadTest::CopyTexImage2D(texture, 0, GL_RGBA, 17, 17, 256, 256, 0,
                                                                           m_config.useFenceSync, m_config.serverSync));
    }

    if (m_config.modify == 1)
    {
        if (m_config.render)
            m_threads[0]->addOperation(new GLES2ThreadTest::TexSubImage2D(
                texture, 0, 17, 17, 29, 29, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
        else
            m_threads[1]->addOperation(new GLES2ThreadTest::TexSubImage2D(
                texture, 0, 17, 17, 29, 29, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
    }

    if (m_config.modify == 2)
    {
        if (m_config.render)
            m_threads[0]->addOperation(new GLES2ThreadTest::CopyTexSubImage2D(
                texture, 0, 7, 7, 17, 17, 29, 29, m_config.useFenceSync, m_config.serverSync));
        else
            m_threads[1]->addOperation(new GLES2ThreadTest::CopyTexSubImage2D(
                texture, 0, 7, 7, 17, 17, 29, 29, m_config.useFenceSync, m_config.serverSync));
    }

    if (m_config.render)
    {
        SharedPtr<tcu::ThreadUtil::DataBlock> pixels1;
        SharedPtr<tcu::ThreadUtil::DataBlock> pixels2;

        m_threads[0]->addOperation(
            new GLES2ThreadTest::RenderTexture(program, texture, m_config.useFenceSync, m_config.serverSync));
        m_threads[1]->addOperation(
            new GLES2ThreadTest::RenderTexture(program, texture, m_config.useFenceSync, m_config.serverSync));

        m_threads[0]->addOperation(new GLES2ThreadTest::ReadPixels(0, 0, 400, 400, GL_RGBA, GL_UNSIGNED_BYTE, pixels1,
                                                                   m_config.useFenceSync, m_config.serverSync));
        m_threads[1]->addOperation(new GLES2ThreadTest::ReadPixels(0, 0, 400, 400, GL_RGBA, GL_UNSIGNED_BYTE, pixels2,
                                                                   m_config.useFenceSync, m_config.serverSync));

        m_threads[0]->addOperation(new tcu::ThreadUtil::CompareData(pixels1, pixels2));
    }

    if (m_config.modify || m_config.render)
        m_threads[0]->addOperation(
            new GLES2ThreadTest::DeleteTexture(texture, m_config.useFenceSync, m_config.serverSync));
    else
        m_threads[1]->addOperation(
            new GLES2ThreadTest::DeleteTexture(texture, m_config.useFenceSync, m_config.serverSync));

    if (m_config.render)
    {
        m_threads[0]->addOperation(
            new GLES2ThreadTest::DeleteShader(vertexShader, m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::DeleteShader(fragmentShader, m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::DeleteProgram(program, m_config.useFenceSync, m_config.serverSync));
    }
}

void GLES2ThreadedSharingTest::addImageOperations(void)
{
    // Add operations for verify
    SharedPtr<GLES2ThreadTest::Shader> vertexShader;
    SharedPtr<GLES2ThreadTest::Shader> fragmentShader;
    SharedPtr<GLES2ThreadTest::Program> program;

    m_threads[0]->addOperation(new GLES2ThreadTest::InitGLExtension("GL_OES_EGL_image"));
    m_threads[1]->addOperation(new GLES2ThreadTest::InitGLExtension("GL_OES_EGL_image"));

    if (m_config.render)
    {
        const char *vertexShaderSource = "attribute highp vec2 a_pos;\n"
                                         "varying mediump vec2 v_pos;\n"
                                         "void main(void)\n"
                                         "{\n"
                                         "\tv_pos = a_pos;\n"
                                         "\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
                                         "}\n";

        const char *fragmentShaderSource = "varying mediump vec2 v_pos;\n"
                                           "uniform sampler2D u_sampler;\n"
                                           "void main(void)\n"
                                           "{\n"
                                           "\tgl_FragColor = texture2D(u_sampler, v_pos);\n"
                                           "}\n";

        m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(GL_VERTEX_SHADER, vertexShader,
                                                                     m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(vertexShader, vertexShaderSource,
                                                                     m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::ShaderCompile(vertexShader, m_config.useFenceSync, m_config.serverSync));

        m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(GL_FRAGMENT_SHADER, fragmentShader,
                                                                     m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(fragmentShader, fragmentShaderSource,
                                                                     m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::ShaderCompile(fragmentShader, m_config.useFenceSync, m_config.serverSync));

        m_threads[0]->addOperation(
            new GLES2ThreadTest::CreateProgram(program, m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::AttachShader(program, fragmentShader, m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::AttachShader(program, vertexShader, m_config.useFenceSync, m_config.serverSync));

        m_threads[0]->addOperation(
            new GLES2ThreadTest::LinkProgram(program, m_config.useFenceSync, m_config.serverSync));
    }

    SharedPtr<GLES2ThreadTest::Texture> sourceTexture;
    SharedPtr<GLES2ThreadTest::Texture> texture;
    SharedPtr<GLES2ThreadTest::EGLImage> image;

    m_threads[0]->addOperation(
        new GLES2ThreadTest::CreateTexture(sourceTexture, m_config.useFenceSync, m_config.serverSync));
    m_threads[0]->addOperation(new GLES2ThreadTest::TexImage2D(
        sourceTexture, 0, GL_RGBA, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));

    if (m_config.define == 1)
    {
        if (m_config.modify || m_config.render)
            m_threads[0]->addOperation(new GLES2ThreadTest::CreateImageFromTexture(
                image, sourceTexture, m_config.useFenceSync, m_config.serverSync));
        else
            m_threads[1]->addOperation(new GLES2ThreadTest::CreateImageFromTexture(
                image, sourceTexture, m_config.useFenceSync, m_config.serverSync));
    }

    if (m_config.define == 2)
    {
        m_threads[0]->addOperation(new GLES2ThreadTest::CreateImageFromTexture(
            image, sourceTexture, m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::CreateTexture(texture, m_config.useFenceSync, m_config.serverSync));

        if (m_config.modify || m_config.render)
            m_threads[0]->addOperation(new GLES2ThreadTest::DefineTextureFromImage(
                texture, image, m_config.useFenceSync, m_config.serverSync));
        else
            m_threads[1]->addOperation(new GLES2ThreadTest::DefineTextureFromImage(
                texture, image, m_config.useFenceSync, m_config.serverSync));
    }

    m_threads[0]->addOperation(
        new GLES2ThreadTest::DeleteTexture(sourceTexture, m_config.useFenceSync, m_config.serverSync));

    if (m_config.modify == 1)
    {
        DE_ASSERT(m_config.define != 1);

        if (m_config.render)
            m_threads[0]->addOperation(new GLES2ThreadTest::TexSubImage2D(
                texture, 0, 17, 17, 29, 29, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
        else
            m_threads[1]->addOperation(new GLES2ThreadTest::TexSubImage2D(
                texture, 0, 17, 17, 29, 29, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
    }

    if (m_config.modify == 2)
    {
        DE_ASSERT(m_config.define != 1);

        if (m_config.render)
            m_threads[0]->addOperation(new GLES2ThreadTest::CopyTexSubImage2D(
                texture, 0, 7, 7, 17, 17, 29, 29, m_config.useFenceSync, m_config.serverSync));
        else
            m_threads[1]->addOperation(new GLES2ThreadTest::CopyTexSubImage2D(
                texture, 0, 7, 7, 17, 17, 29, 29, m_config.useFenceSync, m_config.serverSync));
    }

    if (m_config.modify == 3)
    {
        DE_ASSERT(m_config.define != 1);

        if (m_config.render)
            m_threads[0]->addOperation(new GLES2ThreadTest::TexImage2D(
                texture, 0, GL_RGBA, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
        else
            m_threads[1]->addOperation(new GLES2ThreadTest::TexImage2D(
                texture, 0, GL_RGBA, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, m_config.useFenceSync, m_config.serverSync));
    }

    if (m_config.modify == 4)
    {
        DE_ASSERT(m_config.define != 1);

        if (m_config.render)
            m_threads[0]->addOperation(new GLES2ThreadTest::CopyTexImage2D(texture, 0, GL_RGBA, 7, 7, 256, 256, 0,
                                                                           m_config.useFenceSync, m_config.serverSync));
        else
            m_threads[1]->addOperation(new GLES2ThreadTest::CopyTexImage2D(texture, 0, GL_RGBA, 7, 7, 256, 256, 0,
                                                                           m_config.useFenceSync, m_config.serverSync));
    }

    if (m_config.render)
    {
        DE_ASSERT(m_config.define != 1);

        SharedPtr<tcu::ThreadUtil::DataBlock> pixels1;
        SharedPtr<tcu::ThreadUtil::DataBlock> pixels2;

        m_threads[0]->addOperation(
            new GLES2ThreadTest::RenderTexture(program, texture, m_config.useFenceSync, m_config.serverSync));
        m_threads[1]->addOperation(
            new GLES2ThreadTest::RenderTexture(program, texture, m_config.useFenceSync, m_config.serverSync));

        m_threads[0]->addOperation(new GLES2ThreadTest::ReadPixels(0, 0, 400, 400, GL_RGBA, GL_UNSIGNED_BYTE, pixels1,
                                                                   m_config.useFenceSync, m_config.serverSync));
        m_threads[1]->addOperation(new GLES2ThreadTest::ReadPixels(0, 0, 400, 400, GL_RGBA, GL_UNSIGNED_BYTE, pixels2,
                                                                   m_config.useFenceSync, m_config.serverSync));

        m_threads[0]->addOperation(new tcu::ThreadUtil::CompareData(pixels1, pixels2));
    }

    if (texture)
    {
        if (m_config.modify || m_config.render)
            m_threads[0]->addOperation(
                new GLES2ThreadTest::DeleteTexture(texture, m_config.useFenceSync, m_config.serverSync));
        else
            m_threads[1]->addOperation(
                new GLES2ThreadTest::DeleteTexture(texture, m_config.useFenceSync, m_config.serverSync));
    }

    if (m_config.modify || m_config.render)
        m_threads[0]->addOperation(
            new GLES2ThreadTest::DestroyImage(image, m_config.useFenceSync, m_config.serverSync));
    else
        m_threads[1]->addOperation(
            new GLES2ThreadTest::DestroyImage(image, m_config.useFenceSync, m_config.serverSync));

    if (m_config.render)
    {
        m_threads[0]->addOperation(
            new GLES2ThreadTest::DeleteShader(vertexShader, m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::DeleteShader(fragmentShader, m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::DeleteProgram(program, m_config.useFenceSync, m_config.serverSync));
    }
}

void GLES2ThreadedSharingTest::addShaderOperations(GLenum type)
{
    SharedPtr<GLES2ThreadTest::Shader> shader;

    m_threads[0]->addOperation(
        new GLES2ThreadTest::CreateShader(type, shader, m_config.useFenceSync, m_config.serverSync));

    if (m_config.define)
    {
        const char *vertexShaderSource = "attribute mediump vec4 a_pos;\n"
                                         "void main(void)\n"
                                         "{\n"
                                         "\tgl_Position = a_pos;\n"
                                         "}";

        const char *fragmentShaderSource = "void main(void)\n"
                                           "{\n"
                                           "\tgl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
                                           "}";

        if (m_config.modify || m_config.render)
            m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(
                shader, (type == GL_VERTEX_SHADER ? vertexShaderSource : fragmentShaderSource), m_config.useFenceSync,
                m_config.serverSync));
        else
            m_threads[1]->addOperation(new GLES2ThreadTest::ShaderSource(
                shader, (type == GL_VERTEX_SHADER ? vertexShaderSource : fragmentShaderSource), m_config.useFenceSync,
                m_config.serverSync));
    }

    if (m_config.modify)
    {
        if (m_config.render)
            m_threads[0]->addOperation(
                new GLES2ThreadTest::ShaderCompile(shader, m_config.useFenceSync, m_config.serverSync));
        else
            m_threads[1]->addOperation(
                new GLES2ThreadTest::ShaderCompile(shader, m_config.useFenceSync, m_config.serverSync));
    }

    DE_ASSERT(!m_config.render);

    if (m_config.modify || m_config.render)
        m_threads[0]->addOperation(
            new GLES2ThreadTest::DeleteShader(shader, m_config.useFenceSync, m_config.serverSync));
    else
        m_threads[1]->addOperation(
            new GLES2ThreadTest::DeleteShader(shader, m_config.useFenceSync, m_config.serverSync));
}

void GLES2ThreadedSharingTest::addProgramOperations(void)
{
    // Add operations for verify
    SharedPtr<GLES2ThreadTest::Shader> vertexShader;
    SharedPtr<GLES2ThreadTest::Shader> fragmentShader;

    if (m_config.define)
    {
        const char *vertexShaderSource = "attribute highp vec2 a_pos;\n"
                                         "varying mediump vec2 v_pos;\n"
                                         "void main(void)\n"
                                         "{\n"
                                         "\tv_pos = a_pos;\n"
                                         "\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
                                         "}\n";

        const char *fragmentShaderSource = "varying mediump vec2 v_pos;\n"
                                           "void main(void)\n"
                                           "{\n"
                                           "\tgl_FragColor = vec4(v_pos, 0.5, 1.0);\n"
                                           "}\n";

        m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(GL_VERTEX_SHADER, vertexShader,
                                                                     m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(vertexShader, vertexShaderSource,
                                                                     m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::ShaderCompile(vertexShader, m_config.useFenceSync, m_config.serverSync));

        m_threads[0]->addOperation(new GLES2ThreadTest::CreateShader(GL_FRAGMENT_SHADER, fragmentShader,
                                                                     m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(new GLES2ThreadTest::ShaderSource(fragmentShader, fragmentShaderSource,
                                                                     m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::ShaderCompile(fragmentShader, m_config.useFenceSync, m_config.serverSync));
    }

    SharedPtr<GLES2ThreadTest::Program> program;

    m_threads[0]->addOperation(new GLES2ThreadTest::CreateProgram(program, m_config.useFenceSync, m_config.serverSync));

    if (m_config.define)
    {
        // Attach shaders
        if (m_config.modify || m_config.render)
        {
            m_threads[0]->addOperation(
                new GLES2ThreadTest::AttachShader(program, vertexShader, m_config.useFenceSync, m_config.serverSync));
            m_threads[0]->addOperation(
                new GLES2ThreadTest::AttachShader(program, fragmentShader, m_config.useFenceSync, m_config.serverSync));
        }
        else
        {
            m_threads[1]->addOperation(
                new GLES2ThreadTest::AttachShader(program, vertexShader, m_config.useFenceSync, m_config.serverSync));
            m_threads[1]->addOperation(
                new GLES2ThreadTest::AttachShader(program, fragmentShader, m_config.useFenceSync, m_config.serverSync));
        }
    }

    if (m_config.modify == 1)
    {
        // Link program
        if (m_config.render)
            m_threads[0]->addOperation(
                new GLES2ThreadTest::LinkProgram(program, m_config.useFenceSync, m_config.serverSync));
        else
            m_threads[1]->addOperation(
                new GLES2ThreadTest::LinkProgram(program, m_config.useFenceSync, m_config.serverSync));
    }

    if (m_config.modify == 2)
    {
        // Link program
        if (m_config.render)
        {
            m_threads[0]->addOperation(new GLES2ThreadTest::DetachShader(program, GL_VERTEX_SHADER,
                                                                         m_config.useFenceSync, m_config.serverSync));
            m_threads[0]->addOperation(new GLES2ThreadTest::DetachShader(program, GL_FRAGMENT_SHADER,
                                                                         m_config.useFenceSync, m_config.serverSync));
        }
        else
        {
            m_threads[1]->addOperation(new GLES2ThreadTest::DetachShader(program, GL_VERTEX_SHADER,
                                                                         m_config.useFenceSync, m_config.serverSync));
            m_threads[1]->addOperation(new GLES2ThreadTest::DetachShader(program, GL_FRAGMENT_SHADER,
                                                                         m_config.useFenceSync, m_config.serverSync));
        }
    }

    if (m_config.render)
    {
        DE_ASSERT(false);
    }

    if (m_config.modify || m_config.render)
        m_threads[0]->addOperation(
            new GLES2ThreadTest::DeleteProgram(program, m_config.useFenceSync, m_config.serverSync));
    else
        m_threads[1]->addOperation(
            new GLES2ThreadTest::DeleteProgram(program, m_config.useFenceSync, m_config.serverSync));

    if (m_config.render)
    {
        m_threads[0]->addOperation(
            new GLES2ThreadTest::DeleteShader(vertexShader, m_config.useFenceSync, m_config.serverSync));
        m_threads[0]->addOperation(
            new GLES2ThreadTest::DeleteShader(fragmentShader, m_config.useFenceSync, m_config.serverSync));
    }
}

void GLES2ThreadedSharingTest::deinit(void)
{
    for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
    {
        delete m_threads[threadNdx];
        m_threads[threadNdx] = DE_NULL;
    }

    m_threads.clear();

    if (m_eglDisplay != EGL_NO_DISPLAY)
    {
        m_eglTestCtx.getLibrary().terminate(m_eglDisplay);
        m_eglDisplay = EGL_NO_DISPLAY;
    }

    TCU_CHECK(!m_requiresRestart);
}

tcu::TestCase::IterateResult GLES2ThreadedSharingTest::iterate(void)
{
    if (!m_threadsStarted)
    {
        m_beginTimeUs = deGetMicroseconds();

        // Execute threads
        for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
            m_threads[threadNdx]->exec();

        m_threadsStarted = true;
        m_threadsRunning = true;
    }

    if (m_threadsRunning)
    {
        // Wait threads to finish
        int readyThreads = 0;
        for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
        {
            const tcu::ThreadUtil::Thread::ThreadStatus status = m_threads[threadNdx]->getStatus();

            if (status != tcu::ThreadUtil::Thread::THREADSTATUS_RUNNING &&
                status != tcu::ThreadUtil::Thread::THREADSTATUS_NOT_STARTED)
                readyThreads++;
        }

        if (readyThreads == (int)m_threads.size())
        {
            for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
                m_threads[threadNdx]->join();

            m_executionReady  = true;
            m_requiresRestart = false;
        }

        if (deGetMicroseconds() - m_beginTimeUs > m_timeOutUs)
        {
            for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
            {
                if (m_threads[threadNdx]->getStatus() != tcu::ThreadUtil::Thread::THREADSTATUS_RUNNING)
                    m_threads[threadNdx]->join();
            }
            m_executionReady  = true;
            m_requiresRestart = true;
            m_timeOutTimeUs   = deGetMicroseconds();
        }
        else
        {
            deSleep(m_sleepTimeMs);
        }
    }

    if (m_executionReady)
    {
        std::vector<int> indices(m_threads.size(), 0);

        if (m_timeOutTimeUs != 0)
            m_log << tcu::TestLog::Message
                  << "Execution timeout limit reached. Trying to get per thread logs. This is potentially dangerous."
                  << tcu::TestLog::EndMessage;

        while (true)
        {
            int firstThread = -1;

            // Find first thread with messages
            for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
            {
                if (m_threads[threadNdx]->getMessageCount() > indices[threadNdx])
                {
                    firstThread = threadNdx;
                    break;
                }
            }

            // No more messages
            if (firstThread == -1)
                break;

            for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
            {
                // No more messages in this thread
                if (m_threads[threadNdx]->getMessageCount() <= indices[threadNdx])
                    continue;

                if ((m_threads[threadNdx]->getMessage(indices[threadNdx]).getTime() - m_beginTimeUs) <
                    (m_threads[firstThread]->getMessage(indices[firstThread]).getTime() - m_beginTimeUs))
                    firstThread = threadNdx;
            }

            tcu::ThreadUtil::Message message = m_threads[firstThread]->getMessage(indices[firstThread]);

            m_log << tcu::TestLog::Message << "[" << (message.getTime() - m_beginTimeUs) << "] (" << firstThread << ") "
                  << message.getMessage() << tcu::TestLog::EndMessage;
            indices[firstThread]++;
        }

        if (m_timeOutTimeUs != 0)
            m_log << tcu::TestLog::Message << "[" << (m_timeOutTimeUs - m_beginTimeUs)
                  << "] Execution timeout limit reached" << tcu::TestLog::EndMessage;

        bool isOk         = true;
        bool notSupported = false;

        for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
        {
            const tcu::ThreadUtil::Thread::ThreadStatus status = m_threads[threadNdx]->getStatus();

            switch (status)
            {
            case tcu::ThreadUtil::Thread::THREADSTATUS_FAILED:
            case tcu::ThreadUtil::Thread::THREADSTATUS_INIT_FAILED:
            case tcu::ThreadUtil::Thread::THREADSTATUS_RUNNING:
                isOk = false;
                break;

            case tcu::ThreadUtil::Thread::THREADSTATUS_NOT_SUPPORTED:
                notSupported = true;
                break;

            case tcu::ThreadUtil::Thread::THREADSTATUS_READY:
                // Nothing
                break;

            default:
                DE_ASSERT(false);
                isOk = false;
            }
        }

        if (notSupported)
            throw tcu::NotSupportedError("Thread threw tcu::NotSupportedError", "", __FILE__, __LINE__);

        if (isOk)
            m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
        else
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");

        return STOP;
    }

    return CONTINUE;
}

static void addSimpleTests(EglTestContext &ctx, tcu::TestCaseGroup *group, bool useSync, bool serverSync)
{
    {
        TestCaseGroup *bufferTests = new TestCaseGroup(ctx, "buffers", "Buffer management tests");

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_BUFFER;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 0;
            config.modify       = 0;
            config.render       = false;
            bufferTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "gen_delete", "Generate and delete buffer"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_BUFFER;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 0;
            config.render       = false;
            bufferTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "bufferdata", "Generate, set data and delete buffer"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_BUFFER;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 1;
            config.render       = false;
            bufferTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "buffersubdata",
                                                               "Generate, set data, update data and delete buffer"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_BUFFER;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 0;
            config.render       = true;
            bufferTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "bufferdata_render",
                                                               "Generate, set data, render and delete buffer"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_BUFFER;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 1;
            config.render       = true;
            bufferTests->addChild(new GLES2ThreadedSharingTest(
                ctx, config, "buffersubdata_render", "Generate, set data, update data, render and delete buffer"));
        }

        group->addChild(bufferTests);
    }

    {
        TestCaseGroup *textureTests = new TestCaseGroup(ctx, "textures", "Texture management tests");

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 0;
            config.modify       = 0;
            config.render       = false;
            textureTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "gen_delete", "Generate and delete texture"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 0;
            config.render       = false;
            textureTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "teximage2d", "Generate, set data and delete texture"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 1;
            config.render       = false;
            textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "teximage2d_texsubimage2d",
                                                                "Generate, set data, update data and delete texture"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 2;
            config.render       = false;
            textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "teximage2d_copytexsubimage2d",
                                                                "Generate, set data, update data and delete texture"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 0;
            config.render       = true;
            textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "teximage2d_render",
                                                                "Generate, set data, render and delete texture"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 1;
            config.render       = true;
            textureTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "teximage2d_texsubimage2d_render",
                                             "Generate, set data, update data, render and delete texture"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 2;
            config.render       = true;
            textureTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "teximage2d_copytexsubimage2d_render",
                                             "Generate, set data, update data, render and delete texture"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 2;
            config.modify       = 0;
            config.render       = false;
            textureTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "copyteximage2d", "Generate, set data and delete texture"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 2;
            config.modify       = 1;
            config.render       = false;
            textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "copyteximage2d_texsubimage2d",
                                                                "Generate, set data, update data and delete texture"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 2;
            config.modify       = 2;
            config.render       = false;
            textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "copyteximage2d_copytexsubimage2d",
                                                                "Generate, set data, update data and delete texture"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 2;
            config.modify       = 0;
            config.render       = true;
            textureTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "copyteximage2d_render",
                                                                "Generate, set data, render and delete texture"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 2;
            config.modify       = 1;
            config.render       = true;
            textureTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "copyteximage2d_texsubimage2d_render",
                                             "Generate, set data, update data, render and delete texture"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_TEXTURE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 2;
            config.modify       = 2;
            config.render       = true;
            textureTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "copyteximage2d_copytexsubimage2d_render",
                                             "Generate, set data, update data, render and delete texture"));
        }

        group->addChild(textureTests);
    }

    {
        TestCaseGroup *shaderTests = new TestCaseGroup(ctx, "shaders", "Shader management tests");

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_VERTEX_SHADER;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 0;
            config.modify       = 0;
            config.render       = false;
            shaderTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "vtx_create_destroy", "Create and delete shader"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_VERTEX_SHADER;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 0;
            config.render       = false;
            shaderTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "vtx_shadersource", "Create, set source and delete shader"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_VERTEX_SHADER;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 1;
            config.render       = false;
            shaderTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "vtx_compile",
                                                               "Create, set source, compile and delete shader"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_FRAGMENT_SHADER;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 0;
            config.modify       = 0;
            config.render       = false;
            shaderTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "frag_create_destroy", "Create and delete shader"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_FRAGMENT_SHADER;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 0;
            config.render       = false;
            shaderTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "frag_shadersource", "Create, set source and delete shader"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_FRAGMENT_SHADER;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 1;
            config.render       = false;
            shaderTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "frag_compile",
                                                               "Create, set source, compile and delete shader"));
        }

        group->addChild(shaderTests);
    }

    {
        TestCaseGroup *programTests = new TestCaseGroup(ctx, "programs", "Program management tests");

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_PROGRAM;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 0;
            config.modify       = 0;
            config.render       = false;
            programTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "create_destroy", "Create and delete program"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_PROGRAM;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 0;
            config.render       = false;
            programTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "attach", "Create, attach shaders and delete program"));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_PROGRAM;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 1;
            config.render       = false;
            programTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "link", "Create, attach shaders, link and delete program"));
        }

        group->addChild(programTests);
    }

    {
        TestCaseGroup *imageTests = new TestCaseGroup(ctx, "images", "Image management tests");

        TestCaseGroup *textureSourceTests =
            new TestCaseGroup(ctx, "texture_source", "Image management tests with texture source.");
        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 1;
            config.modify       = 0;
            config.render       = false;
            textureSourceTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "create_destroy", "Create and destroy EGLImage."));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 2;
            config.modify       = 0;
            config.render       = false;
            textureSourceTests->addChild(
                new GLES2ThreadedSharingTest(ctx, config, "create_texture", "Create texture from image."));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 2;
            config.modify       = 1;
            config.render       = false;
            textureSourceTests->addChild(new GLES2ThreadedSharingTest(
                ctx, config, "texsubimage2d", "Modify texture created from image with glTexSubImage2D."));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 2;
            config.modify       = 2;
            config.render       = false;
            textureSourceTests->addChild(new GLES2ThreadedSharingTest(
                ctx, config, "copytexsubimage2d", "Modify texture created from image with glCopyTexSubImage2D."));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 2;
            config.modify       = 3;
            config.render       = false;
            textureSourceTests->addChild(new GLES2ThreadedSharingTest(
                ctx, config, "teximage2d", "Modify texture created from image with glTexImage2D."));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 2;
            config.modify       = 4;
            config.render       = false;
            textureSourceTests->addChild(new GLES2ThreadedSharingTest(
                ctx, config, "copyteximage2d", "Modify texture created from image with glCopyTexImage2D."));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 2;
            config.modify       = 0;
            config.render       = true;
            textureSourceTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "create_texture_render",
                                                                      "Create texture from image and render."));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 2;
            config.modify       = 1;
            config.render       = true;
            textureSourceTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "texsubimage2d_render",
                                                                      "Modify texture created from image and render."));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 2;
            config.modify       = 2;
            config.render       = true;
            textureSourceTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "copytexsubimage2d_render",
                                                                      "Modify texture created from image and render."));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 2;
            config.modify       = 3;
            config.render       = true;
            textureSourceTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "teximage2d_render",
                                                                      "Modify texture created from image and render."));
        }

        {
            GLES2ThreadedSharingTest::TestConfig config;

            config.resourceType = GLES2ThreadedSharingTest::TestConfig::RESOURCETYPE_IMAGE;
            config.useFenceSync = useSync;
            config.serverSync   = serverSync;
            config.define       = 2;
            config.modify       = 4;
            config.render       = true;
            textureSourceTests->addChild(new GLES2ThreadedSharingTest(ctx, config, "copyteximage2d_render",
                                                                      "Modify texture created from image and render."));
        }

        imageTests->addChild(textureSourceTests);

        group->addChild(imageTests);
    }
}

static void addRandomTests(EglTestContext &ctx, tcu::TestCaseGroup *group, bool useSync, bool serverSync)
{
    {
        TestCaseGroup *textureTests = new TestCaseGroup(ctx, "textures", "Texture management tests");

        {
            TestCaseGroup *genTextureTests = new TestCaseGroup(ctx, "gen_delete", "Texture gen and delete tests");

            for (int textureTestNdx = 0; textureTestNdx < 20; textureTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + textureTestNdx % 5;
                config.operationCount = 30 + textureTestNdx;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE] = 1.0f;

                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.75f;

                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.5f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.5f;

                std::string name = de::toString(textureTestNdx);
                genTextureTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            textureTests->addChild(genTextureTests);
        }

        {
            TestCaseGroup *texImage2DTests =
                new TestCaseGroup(ctx, "teximage2d", "Texture gen, delete and teximage2D tests");

            for (int textureTestNdx = 0; textureTestNdx < 20; textureTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + textureTestNdx % 5;
                config.operationCount = 40 + textureTestNdx;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE] = 1.0f;

                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]      = 0.80f;

                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.30f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.40f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]      = 0.30f;

                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE] = 0.40f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]  = 0.40f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]      = 0.20f;

                std::string name = de::toString(textureTestNdx);
                texImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            textureTests->addChild(texImage2DTests);
        }

        {
            TestCaseGroup *texSubImage2DTests =
                new TestCaseGroup(ctx, "texsubimage2d", "Texture gen, delete, teximage2D and texsubimage2d tests");

            for (int textureTestNdx = 0; textureTestNdx < 20; textureTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + textureTestNdx % 5;
                config.operationCount = 50 + textureTestNdx;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE] = 1.0f;

                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.05f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]      = 0.80f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXSUBIMAGE2D]   = 0.05f;

                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.30f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.40f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]      = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXSUBIMAGE2D]   = 0.10f;

                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE] = 0.20f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]  = 0.20f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]      = 0.10f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXSUBIMAGE2D]   = 0.50f;

                config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE] = 0.20f;
                config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]  = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]      = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_TEXSUBIMAGE2D]   = 0.30f;

                std::string name = de::toString(textureTestNdx);
                texSubImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            textureTests->addChild(texSubImage2DTests);
        }

        {
            TestCaseGroup *copyTexImage2DTests =
                new TestCaseGroup(ctx, "copyteximage2d", "Texture gen, delete and copyteximage2d tests");

            for (int textureTestNdx = 0; textureTestNdx < 20; textureTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + textureTestNdx % 5;
                config.operationCount = 40 + textureTestNdx;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE] = 1.0f;

                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_COPYTEXIMAGE2D]  = 0.80f;

                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.30f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.40f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_COPYTEXIMAGE2D]  = 0.30f;

                config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE] = 0.40f;
                config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]  = 0.40f;
                config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_COPYTEXIMAGE2D]  = 0.20f;

                std::string name = de::toString(textureTestNdx);
                copyTexImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            textureTests->addChild(copyTexImage2DTests);
        }

        {
            TestCaseGroup *copyTexSubImage2DTests = new TestCaseGroup(
                ctx, "copytexsubimage2d", "Texture gen, delete, teximage2D and copytexsubimage2d tests");

            for (int textureTestNdx = 0; textureTestNdx < 20; textureTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + textureTestNdx % 5;
                config.operationCount = 50 + textureTestNdx;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE] = 1.0f;

                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]   = 0.05f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]    = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]        = 0.80f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_COPYTEXSUBIMAGE2D] = 0.05f;

                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE]   = 0.30f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]    = 0.40f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]        = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_COPYTEXSUBIMAGE2D] = 0.10f;

                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]   = 0.20f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]    = 0.20f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]        = 0.10f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_COPYTEXSUBIMAGE2D] = 0.50f;

                config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]   = 0.20f;
                config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]    = 0.25f;
                config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]        = 0.25f;
                config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_COPYTEXSUBIMAGE2D] = 0.30f;

                std::string name = de::toString(textureTestNdx);
                copyTexSubImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            textureTests->addChild(copyTexSubImage2DTests);
        }

        group->addChild(textureTests);

        TestCaseGroup *bufferTests = new TestCaseGroup(ctx, "buffers", "Buffer management tests");

        {
            TestCaseGroup *genBufferTests = new TestCaseGroup(ctx, "gen_delete", "Buffer gen and delete tests");

            for (int bufferTestNdx = 0; bufferTestNdx < 20; bufferTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + bufferTestNdx % 5;
                config.operationCount = 30 + bufferTestNdx;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_BUFFER] = 1.0f;

                config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_DESTROY_BUFFER] = 0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_CREATE_BUFFER]  = 0.75f;

                config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_DESTROY_BUFFER] = 0.5f;
                config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_CREATE_BUFFER]  = 0.5f;

                std::string name = de::toString(bufferTestNdx);
                genBufferTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            bufferTests->addChild(genBufferTests);
        }

        {
            TestCaseGroup *texImage2DTests =
                new TestCaseGroup(ctx, "bufferdata", "Buffer gen, delete and bufferdata tests");

            for (int bufferTestNdx = 0; bufferTestNdx < 20; bufferTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + bufferTestNdx % 5;
                config.operationCount = 40 + bufferTestNdx;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_BUFFER] = 1.0f;

                config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_DESTROY_BUFFER] = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_CREATE_BUFFER]  = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_BUFFER_DATA]    = 0.80f;

                config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_DESTROY_BUFFER] = 0.30f;
                config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_CREATE_BUFFER]  = 0.40f;
                config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_BUFFER_DATA]    = 0.30f;

                config.probabilities[THREADOPERATIONID_BUFFER_DATA][THREADOPERATIONID_DESTROY_BUFFER] = 0.40f;
                config.probabilities[THREADOPERATIONID_BUFFER_DATA][THREADOPERATIONID_CREATE_BUFFER]  = 0.40f;
                config.probabilities[THREADOPERATIONID_BUFFER_DATA][THREADOPERATIONID_BUFFER_DATA]    = 0.20f;

                std::string name = de::toString(bufferTestNdx);
                texImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            bufferTests->addChild(texImage2DTests);
        }

        {
            TestCaseGroup *texSubImage2DTests =
                new TestCaseGroup(ctx, "buffersubdata", "Buffer gen, delete, bufferdata and bufferdata tests");

            for (int bufferTestNdx = 0; bufferTestNdx < 20; bufferTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + bufferTestNdx % 5;
                config.operationCount = 50 + bufferTestNdx;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_BUFFER] = 1.0f;

                config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_DESTROY_BUFFER] = 0.05f;
                config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_CREATE_BUFFER]  = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_BUFFER_DATA]    = 0.80f;
                config.probabilities[THREADOPERATIONID_CREATE_BUFFER][THREADOPERATIONID_BUFFER_SUBDATA] = 0.05f;

                config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_DESTROY_BUFFER] = 0.30f;
                config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_CREATE_BUFFER]  = 0.40f;
                config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_BUFFER_DATA]    = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_BUFFER][THREADOPERATIONID_BUFFER_SUBDATA] = 0.10f;

                config.probabilities[THREADOPERATIONID_BUFFER_DATA][THREADOPERATIONID_DESTROY_BUFFER] = 0.20f;
                config.probabilities[THREADOPERATIONID_BUFFER_DATA][THREADOPERATIONID_CREATE_BUFFER]  = 0.20f;
                config.probabilities[THREADOPERATIONID_BUFFER_DATA][THREADOPERATIONID_BUFFER_DATA]    = 0.10f;
                config.probabilities[THREADOPERATIONID_BUFFER_DATA][THREADOPERATIONID_BUFFER_SUBDATA] = 0.50f;

                config.probabilities[THREADOPERATIONID_BUFFER_SUBDATA][THREADOPERATIONID_DESTROY_BUFFER] = 0.20f;
                config.probabilities[THREADOPERATIONID_BUFFER_SUBDATA][THREADOPERATIONID_CREATE_BUFFER]  = 0.25f;
                config.probabilities[THREADOPERATIONID_BUFFER_SUBDATA][THREADOPERATIONID_BUFFER_DATA]    = 0.25f;
                config.probabilities[THREADOPERATIONID_BUFFER_SUBDATA][THREADOPERATIONID_BUFFER_SUBDATA] = 0.30f;

                std::string name = de::toString(bufferTestNdx);
                texSubImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            bufferTests->addChild(texSubImage2DTests);
        }

        group->addChild(bufferTests);

        TestCaseGroup *shaderTests = new TestCaseGroup(ctx, "shaders", "Shader management tests");

        {
            TestCaseGroup *createShaderTests =
                new TestCaseGroup(ctx, "create_destroy", "Shader create and destroy tests");

            for (int shaderTestNdx = 0; shaderTestNdx < 20; shaderTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + shaderTestNdx % 5;
                config.operationCount = 30 + shaderTestNdx;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_VERTEX_SHADER]   = 0.5f;
                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] = 0.5f;

                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_DESTROY_SHADER] = 0.20f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER] =
                    0.40f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.40f;

                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_DESTROY_SHADER] =
                    0.20f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER] =
                    0.40f;
                config
                    .probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.40f;

                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_DESTROY_SHADER]       = 0.5f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER] = 0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.25f;

                std::string name = de::toString(shaderTestNdx);
                createShaderTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            shaderTests->addChild(createShaderTests);
        }

        {
            TestCaseGroup *texImage2DTests =
                new TestCaseGroup(ctx, "source", "Shader create, destroy and source tests");

            for (int shaderTestNdx = 0; shaderTestNdx < 20; shaderTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + shaderTestNdx % 5;
                config.operationCount = 40 + shaderTestNdx;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_VERTEX_SHADER]   = 0.5f;
                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] = 0.5f;

                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_DESTROY_SHADER] = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER] =
                    0.20f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.20f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_SHADER_SOURCE] = 0.50f;

                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_DESTROY_SHADER] =
                    0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER] =
                    0.20f;
                config
                    .probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.20f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_SHADER_SOURCE] = 0.50f;

                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_DESTROY_SHADER]       = 0.30f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER] = 0.30f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.30f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_SHADER_SOURCE] = 0.10f;

                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_DESTROY_SHADER]         = 0.20f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_VERTEX_SHADER]   = 0.20f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] = 0.20f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_SHADER_SOURCE]          = 0.40f;

                std::string name = de::toString(shaderTestNdx);
                texImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            shaderTests->addChild(texImage2DTests);
        }

        {
            TestCaseGroup *texSubImage2DTests =
                new TestCaseGroup(ctx, "compile", "Shader create, destroy, source and compile tests");

            for (int shaderTestNdx = 0; shaderTestNdx < 20; shaderTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + shaderTestNdx % 5;
                config.operationCount = 50 + shaderTestNdx;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_VERTEX_SHADER]   = 0.5f;
                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] = 0.5f;

                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_DESTROY_SHADER] = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER] =
                    0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_SHADER_SOURCE]  = 0.50f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_SHADER_COMPILE] = 0.10f;

                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_DESTROY_SHADER] =
                    0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER] =
                    0.15f;
                config
                    .probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_SHADER_SOURCE] = 0.50f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_SHADER_COMPILE] =
                    0.10f;

                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_DESTROY_SHADER]       = 0.30f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER] = 0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_SHADER_SOURCE]  = 0.10f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_SHADER_COMPILE] = 0.10f;

                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_DESTROY_SHADER]         = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_VERTEX_SHADER]   = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_SHADER_SOURCE]          = 0.20f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_SHADER_COMPILE]         = 0.50f;

                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_DESTROY_SHADER]       = 0.15f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_CREATE_VERTEX_SHADER] = 0.15f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.15f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_SHADER_SOURCE]  = 0.30f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_SHADER_COMPILE] = 0.30f;

                std::string name = de::toString(shaderTestNdx);
                texSubImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            shaderTests->addChild(texSubImage2DTests);
        }

        group->addChild(shaderTests);

        TestCaseGroup *programTests = new TestCaseGroup(ctx, "programs", "Program management tests");

        {
            TestCaseGroup *createProgramTests =
                new TestCaseGroup(ctx, "create_destroy", "Program create and destroy tests");

            for (int programTestNdx = 0; programTestNdx < 20; programTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + programTestNdx % 5;
                config.operationCount = 30 + programTestNdx;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_PROGRAM] = 1.0f;

                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_DESTROY_PROGRAM] = 0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_CREATE_PROGRAM]  = 0.75f;

                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_DESTROY_PROGRAM] = 0.5f;
                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_CREATE_PROGRAM]  = 0.5f;

                std::string name = de::toString(programTestNdx);
                createProgramTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            programTests->addChild(createProgramTests);
        }

        {
            TestCaseGroup *texImage2DTests =
                new TestCaseGroup(ctx, "attach_detach", "Program create, destroy, attach and detach tests");

            for (int programTestNdx = 0; programTestNdx < 20; programTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + programTestNdx % 5;
                config.operationCount = 60 + programTestNdx;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_VERTEX_SHADER]   = 0.35f;
                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] = 0.35f;
                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_PROGRAM]         = 0.30f;

                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_DESTROY_SHADER] = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER] =
                    0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_SHADER_SOURCE]   = 0.30f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_SHADER_COMPILE]  = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_PROGRAM]  = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_DESTROY_PROGRAM] = 0.05f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_ATTACH_SHADER]   = 0.15f;

                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_DESTROY_SHADER] =
                    0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER] =
                    0.10f;
                config
                    .probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_SHADER_SOURCE] = 0.30f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_SHADER_COMPILE] =
                    0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_PROGRAM] =
                    0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_DESTROY_PROGRAM] =
                    0.05f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_ATTACH_SHADER] = 0.15f;

                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_DESTROY_SHADER]       = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER] = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_SHADER_SOURCE]   = 0.10f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_SHADER_COMPILE]  = 0.10f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_PROGRAM]  = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_DESTROY_PROGRAM] = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_ATTACH_SHADER]   = 0.15f;

                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_DESTROY_SHADER]         = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_VERTEX_SHADER]   = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_SHADER_SOURCE]          = 0.20f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_SHADER_COMPILE]         = 0.50f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_PROGRAM]         = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_DESTROY_PROGRAM]        = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_ATTACH_SHADER]          = 0.25f;

                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_DESTROY_SHADER]       = 0.15f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_CREATE_VERTEX_SHADER] = 0.15f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.15f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_SHADER_SOURCE]   = 0.30f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_SHADER_COMPILE]  = 0.30f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_CREATE_PROGRAM]  = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_DESTROY_PROGRAM] = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_ATTACH_SHADER]   = 0.35f;

                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_DESTROY_SHADER]       = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_CREATE_VERTEX_SHADER] = 0.20f;
                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.20f;
                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_SHADER_SOURCE]   = 0.05f;
                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_SHADER_COMPILE]  = 0.05f;
                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_CREATE_PROGRAM]  = 0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_DESTROY_PROGRAM] = 0.05f;
                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_ATTACH_SHADER]   = 0.40f;

                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_DESTROY_SHADER]       = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_CREATE_VERTEX_SHADER] = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_SHADER_SOURCE]   = 0.10f;
                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_SHADER_COMPILE]  = 0.10f;
                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_CREATE_PROGRAM]  = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_DESTROY_PROGRAM] = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_ATTACH_SHADER]   = 0.10f;

                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_DESTROY_SHADER]         = 0.20f;
                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]   = 0.20f;
                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] = 0.20f;
                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_SHADER_SOURCE]          = 0.10f;
                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_SHADER_COMPILE]         = 0.10f;
                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_CREATE_PROGRAM]         = 0.15f;
                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_DESTROY_PROGRAM]        = 0.15f;
                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_ATTACH_SHADER]          = 0.30f;

                std::string name = de::toString(programTestNdx);
                texImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            programTests->addChild(texImage2DTests);
        }

        {
            TestCaseGroup *texSubImage2DTests =
                new TestCaseGroup(ctx, "link", "Program create, destroy, attach and link tests");

            for (int programTestNdx = 0; programTestNdx < 20; programTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + programTestNdx % 5;
                config.operationCount = 70 + programTestNdx;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_VERTEX_SHADER]   = 0.35f;
                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] = 0.35f;
                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_PROGRAM]         = 0.30f;

                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_DESTROY_SHADER] = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER] =
                    0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_SHADER_SOURCE]   = 0.30f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_SHADER_COMPILE]  = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_CREATE_PROGRAM]  = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_DESTROY_PROGRAM] = 0.05f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_ATTACH_SHADER]   = 0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_VERTEX_SHADER][THREADOPERATIONID_LINK_PROGRAM]    = 0.10f;

                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_DESTROY_SHADER] =
                    0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER] =
                    0.10f;
                config
                    .probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_SHADER_SOURCE] = 0.30f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_SHADER_COMPILE] =
                    0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_CREATE_PROGRAM] =
                    0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_DESTROY_PROGRAM] =
                    0.05f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_ATTACH_SHADER] = 0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_FRAGMENT_SHADER][THREADOPERATIONID_LINK_PROGRAM]  = 0.10f;

                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_DESTROY_SHADER]       = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER] = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_SHADER_SOURCE]   = 0.10f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_SHADER_COMPILE]  = 0.10f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_CREATE_PROGRAM]  = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_DESTROY_PROGRAM] = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_ATTACH_SHADER]   = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_SHADER][THREADOPERATIONID_LINK_PROGRAM]    = 0.10f;

                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_DESTROY_SHADER]         = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_VERTEX_SHADER]   = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_SHADER_SOURCE]          = 0.20f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_SHADER_COMPILE]         = 0.50f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_CREATE_PROGRAM]         = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_DESTROY_PROGRAM]        = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_ATTACH_SHADER]          = 0.25f;
                config.probabilities[THREADOPERATIONID_SHADER_SOURCE][THREADOPERATIONID_LINK_PROGRAM]           = 0.20f;

                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_DESTROY_SHADER]       = 0.15f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_CREATE_VERTEX_SHADER] = 0.15f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.15f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_SHADER_SOURCE]   = 0.30f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_SHADER_COMPILE]  = 0.30f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_CREATE_PROGRAM]  = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_DESTROY_PROGRAM] = 0.10f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_ATTACH_SHADER]   = 0.35f;
                config.probabilities[THREADOPERATIONID_SHADER_COMPILE][THREADOPERATIONID_LINK_PROGRAM]    = 0.20f;

                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_DESTROY_SHADER]       = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_CREATE_VERTEX_SHADER] = 0.20f;
                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.20f;
                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_SHADER_SOURCE]   = 0.05f;
                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_SHADER_COMPILE]  = 0.05f;
                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_CREATE_PROGRAM]  = 0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_DESTROY_PROGRAM] = 0.05f;
                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_ATTACH_SHADER]   = 0.40f;
                config.probabilities[THREADOPERATIONID_CREATE_PROGRAM][THREADOPERATIONID_LINK_PROGRAM]    = 0.05f;

                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_DESTROY_SHADER]       = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_CREATE_VERTEX_SHADER] = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] =
                    0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_SHADER_SOURCE]   = 0.10f;
                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_SHADER_COMPILE]  = 0.10f;
                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_CREATE_PROGRAM]  = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_DESTROY_PROGRAM] = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_ATTACH_SHADER]   = 0.10f;
                config.probabilities[THREADOPERATIONID_DESTROY_PROGRAM][THREADOPERATIONID_LINK_PROGRAM]    = 0.05f;

                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_DESTROY_SHADER]         = 0.20f;
                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_CREATE_VERTEX_SHADER]   = 0.20f;
                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] = 0.20f;
                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_SHADER_SOURCE]          = 0.10f;
                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_SHADER_COMPILE]         = 0.10f;
                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_CREATE_PROGRAM]         = 0.15f;
                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_DESTROY_PROGRAM]        = 0.15f;
                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_ATTACH_SHADER]          = 0.30f;
                config.probabilities[THREADOPERATIONID_ATTACH_SHADER][THREADOPERATIONID_LINK_PROGRAM]           = 0.30f;

                config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_DESTROY_SHADER]         = 0.20f;
                config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_CREATE_VERTEX_SHADER]   = 0.20f;
                config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_CREATE_FRAGMENT_SHADER] = 0.20f;
                config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_SHADER_SOURCE]          = 0.10f;
                config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_SHADER_COMPILE]         = 0.10f;
                config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_CREATE_PROGRAM]         = 0.20f;
                config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_DESTROY_PROGRAM]        = 0.15f;
                config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_ATTACH_SHADER]          = 0.10f;
                config.probabilities[THREADOPERATIONID_LINK_PROGRAM][THREADOPERATIONID_LINK_PROGRAM]           = 0.05f;

                std::string name = de::toString(programTestNdx);
                texSubImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            programTests->addChild(texSubImage2DTests);
        }

        group->addChild(programTests);

        TestCaseGroup *imageTests = new TestCaseGroup(ctx, "images", "Image management tests");

        {
            TestCaseGroup *texImage2DTests =
                new TestCaseGroup(ctx, "create_destroy", "Image gen, delete and teximage2D tests");

            for (int imageTestNdx = 0; imageTestNdx < 20; imageTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + imageTestNdx % 5;
                config.operationCount = 70 + imageTestNdx;
                config.useImages      = true;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE] = 1.0f;

                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.40f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE] = 0.35f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]    = 0.30f;

                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.40f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE] = 0.35f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]    = 0.15f;

                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]
                                    [THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] = 0.40f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE] =
                    0.35f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXIMAGE2D] = 0.15f;

                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.40f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_IMAGE] = 0.35f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXIMAGE2D]    = 0.15f;

                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]           = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]            = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] = 0.40f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_IMAGE]             = 0.35f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]                = 0.15f;

                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.25f;
                config
                    .probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.30f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_IMAGE] = 0.15f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXIMAGE2D]    = 0.15f;

                std::string name = de::toString(imageTestNdx);
                texImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            imageTests->addChild(texImage2DTests);
        }

        {
            TestCaseGroup *texImage2DTests =
                new TestCaseGroup(ctx, "teximage2d", "Image gen, delete and teximage2D tests");

            for (int imageTestNdx = 0; imageTestNdx < 20; imageTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + imageTestNdx % 5;
                config.operationCount = 70 + imageTestNdx;
                config.useImages      = true;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE] = 1.0f;

                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.20f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]      = 0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]         = 0.30f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] = 0.20f;

                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]      = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]         = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] = 0.15f;

                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]
                                    [THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXIMAGE2D] = 0.15f;
                config
                    .probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] =
                    0.15f;

                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_IMAGE]      = 0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXIMAGE2D]         = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] = 0.15f;

                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]           = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]            = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_IMAGE]             = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]                = 0.15f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXTURE_FROM_IMAGE]        = 0.15f;

                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.25f;
                config
                    .probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_IMAGE] = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXIMAGE2D]    = 0.15f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] =
                    0.15f;

                std::string name = de::toString(imageTestNdx);
                texImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            imageTests->addChild(texImage2DTests);
        }

        {
            TestCaseGroup *texSubImage2DTests =
                new TestCaseGroup(ctx, "texsubimage2d", "Image gen, delete, teximage2D and texsubimage2d tests");

            for (int imageTestNdx = 0; imageTestNdx < 20; imageTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + imageTestNdx % 5;
                config.operationCount = 70 + imageTestNdx;
                config.useImages      = true;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE] = 1.0f;

                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.20f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]      = 0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]         = 0.30f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] = 0.20f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXSUBIMAGE2D]      = 0.10f;

                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]      = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]         = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXSUBIMAGE2D]      = 0.10f;

                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]
                                    [THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXIMAGE2D] = 0.15f;
                config
                    .probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] =
                    0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXSUBIMAGE2D] =
                    0.10f;

                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_IMAGE]      = 0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXIMAGE2D]         = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXSUBIMAGE2D]      = 0.10f;

                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]           = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]            = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_IMAGE]             = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]                = 0.15f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXTURE_FROM_IMAGE]        = 0.15f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXSUBIMAGE2D]             = 0.10f;

                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.25f;
                config
                    .probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_IMAGE] = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXIMAGE2D]    = 0.15f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] =
                    0.15f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXSUBIMAGE2D] = 0.10f;

                config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]  = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_DESTROY_IMAGE]      = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]         = 0.15f;
                config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_TEXTURE_FROM_IMAGE] = 0.15f;
                config.probabilities[THREADOPERATIONID_TEXSUBIMAGE2D][THREADOPERATIONID_TEXSUBIMAGE2D]      = 0.10f;

                std::string name = de::toString(imageTestNdx);
                texSubImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            imageTests->addChild(texSubImage2DTests);
        }

        {
            TestCaseGroup *copyTexImage2DTests =
                new TestCaseGroup(ctx, "copyteximage2d", "Image gen, delete and copyteximage2d tests");

            for (int imageTestNdx = 0; imageTestNdx < 20; imageTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + imageTestNdx % 5;
                config.operationCount = 70 + imageTestNdx;
                config.useImages      = true;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE] = 1.0f;

                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.20f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]      = 0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_COPYTEXIMAGE2D]     = 0.30f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] = 0.20f;

                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]      = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_COPYTEXIMAGE2D]     = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] = 0.15f;

                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]
                                    [THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_COPYTEXIMAGE2D] =
                    0.15f;
                config
                    .probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] =
                    0.15f;

                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_IMAGE]      = 0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_COPYTEXIMAGE2D]     = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] = 0.15f;

                config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]  = 0.25f;
                config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_DESTROY_IMAGE]      = 0.25f;
                config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_COPYTEXIMAGE2D]     = 0.15f;
                config.probabilities[THREADOPERATIONID_COPYTEXIMAGE2D][THREADOPERATIONID_TEXTURE_FROM_IMAGE] = 0.15f;

                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.25f;
                config
                    .probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_IMAGE]  = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_COPYTEXIMAGE2D] = 0.15f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] =
                    0.15f;

                std::string name = de::toString(imageTestNdx);
                copyTexImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            imageTests->addChild(copyTexImage2DTests);
        }

        {
            TestCaseGroup *copyTexSubImage2DTests = new TestCaseGroup(
                ctx, "copytexsubimage2d", "Image gen, delete, teximage2D and copytexsubimage2d tests");

            for (int imageTestNdx = 0; imageTestNdx < 20; imageTestNdx++)
            {
                GLES2SharingRandomTest::TestConfig config;
                config.useFenceSync   = useSync;
                config.serverSync     = serverSync;
                config.threadCount    = 2 + imageTestNdx % 5;
                config.operationCount = 70 + imageTestNdx;
                config.useImages      = true;

                config.probabilities[THREADOPERATIONID_NONE][THREADOPERATIONID_CREATE_TEXTURE] = 1.0f;

                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.10f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.20f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]      = 0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]         = 0.30f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] = 0.20f;
                config.probabilities[THREADOPERATIONID_CREATE_TEXTURE][THREADOPERATIONID_COPYTEXSUBIMAGE2D]  = 0.10f;

                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.20f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE]      = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXIMAGE2D]         = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_TEXTURE][THREADOPERATIONID_COPYTEXSUBIMAGE2D]  = 0.10f;

                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_CREATE_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE]
                                    [THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_DESTROY_IMAGE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXIMAGE2D] = 0.15f;
                config
                    .probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] =
                    0.15f;
                config.probabilities[THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE][THREADOPERATIONID_COPYTEXSUBIMAGE2D] =
                    0.10f;

                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_DESTROY_IMAGE]      = 0.25f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXIMAGE2D]         = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] = 0.15f;
                config.probabilities[THREADOPERATIONID_DESTROY_IMAGE][THREADOPERATIONID_COPYTEXSUBIMAGE2D]  = 0.10f;

                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE]           = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]            = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_DESTROY_IMAGE]             = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]                = 0.15f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_TEXTURE_FROM_IMAGE]        = 0.15f;
                config.probabilities[THREADOPERATIONID_TEXIMAGE2D][THREADOPERATIONID_COPYTEXSUBIMAGE2D]         = 0.10f;

                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_TEXTURE]  = 0.25f;
                config
                    .probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_DESTROY_IMAGE] = 0.25f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXIMAGE2D]    = 0.15f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_TEXTURE_FROM_IMAGE] =
                    0.15f;
                config.probabilities[THREADOPERATIONID_TEXTURE_FROM_IMAGE][THREADOPERATIONID_COPYTEXSUBIMAGE2D] = 0.10f;

                config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_DESTROY_TEXTURE] = 0.25f;
                config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_CREATE_TEXTURE]  = 0.25f;
                config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_CREATE_IMAGE_FROM_TEXTURE] =
                    0.25f;
                config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_DESTROY_IMAGE]      = 0.25f;
                config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_TEXIMAGE2D]         = 0.15f;
                config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_TEXTURE_FROM_IMAGE] = 0.15f;
                config.probabilities[THREADOPERATIONID_COPYTEXSUBIMAGE2D][THREADOPERATIONID_COPYTEXSUBIMAGE2D]  = 0.10f;

                std::string name = de::toString(imageTestNdx);
                copyTexSubImage2DTests->addChild(new GLES2SharingRandomTest(ctx, config, name.c_str(), name.c_str()));
            }

            imageTests->addChild(copyTexSubImage2DTests);
        }

        group->addChild(imageTests);
    }
}

GLES2SharingThreadedTests::GLES2SharingThreadedTests(EglTestContext &eglTestCtx)
    : TestCaseGroup(eglTestCtx, "multithread", "EGL GLES2 sharing multithread tests")
{
}

void GLES2SharingThreadedTests::init(void)
{
    tcu::TestCaseGroup *simpleTests = new TestCaseGroup(m_eglTestCtx, "simple", "Simple multithreaded tests");
    addSimpleTests(m_eglTestCtx, simpleTests, false, false);
    addChild(simpleTests);

    TestCaseGroup *randomTests = new TestCaseGroup(m_eglTestCtx, "random", "Random tests");
    addRandomTests(m_eglTestCtx, randomTests, false, false);
    addChild(randomTests);

    tcu::TestCaseGroup *simpleTestsSync =
        new TestCaseGroup(m_eglTestCtx, "simple_egl_sync", "Simple multithreaded tests with EGL_KHR_fence_sync");
    addSimpleTests(m_eglTestCtx, simpleTestsSync, true, false);
    addChild(simpleTestsSync);

    TestCaseGroup *randomTestsSync =
        new TestCaseGroup(m_eglTestCtx, "random_egl_sync", "Random tests with EGL_KHR_fence_sync");
    addRandomTests(m_eglTestCtx, randomTestsSync, true, false);
    addChild(randomTestsSync);

    tcu::TestCaseGroup *simpleTestsServerSync =
        new TestCaseGroup(m_eglTestCtx, "simple_egl_server_sync",
                          "Simple multithreaded tests with EGL_KHR_fence_sync and EGL_KHR_wait_sync");
    addSimpleTests(m_eglTestCtx, simpleTestsServerSync, true, true);
    addChild(simpleTestsServerSync);

    TestCaseGroup *randomTestsServerSync = new TestCaseGroup(
        m_eglTestCtx, "random_egl_server_sync", "Random tests with EGL_KHR_fence_sync and EGL_KHR_wait_sync");
    addRandomTests(m_eglTestCtx, randomTestsServerSync, true, true);
    addChild(randomTestsServerSync);
}

} // namespace egl
} // namespace deqp
