/*-------------------------------------------------------------------------
 * 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 Multi threaded EGL tests
 *//*--------------------------------------------------------------------*/
#include "teglMultiThreadTests.hpp"

#include "egluNativeWindow.hpp"
#include "egluNativePixmap.hpp"
#include "egluUtil.hpp"

#include "tcuTestLog.hpp"
#include "tcuCommandLine.hpp"

#include "deRandom.hpp"

#include "deThread.hpp"
#include "deMutex.hpp"
#include "deSemaphore.hpp"

#include "deAtomic.h"
#include "deClock.h"

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

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

using std::ostringstream;
using std::pair;
using std::set;
using std::string;
using std::vector;

using namespace eglw;

namespace deqp
{
namespace egl
{

class ThreadLog
{
public:
    class BeginMessageToken
    {
    };
    class EndMessageToken
    {
    };

    struct Message
    {
        Message(uint64_t timeUs_, const char *msg_) : timeUs(timeUs_), msg(msg_)
        {
        }

        uint64_t timeUs;
        string msg;
    };

    ThreadLog(void)
    {
        m_messages.reserve(100);
    }

    ThreadLog &operator<<(const BeginMessageToken &)
    {
        return *this;
    }
    ThreadLog &operator<<(const EndMessageToken &);

    template <class T>
    ThreadLog &operator<<(const T &t)
    {
        m_message << t;
        return *this;
    }
    const vector<Message> &getMessages(void) const
    {
        return m_messages;
    }

    static BeginMessageToken BeginMessage;
    static EndMessageToken EndMessage;

private:
    ostringstream m_message;
    vector<Message> m_messages;
};

ThreadLog &ThreadLog::operator<<(const EndMessageToken &)
{
    m_messages.push_back(Message(deGetMicroseconds(), m_message.str().c_str()));
    m_message.str("");
    return *this;
}

ThreadLog::BeginMessageToken ThreadLog::BeginMessage;
ThreadLog::EndMessageToken ThreadLog::EndMessage;

class MultiThreadedTest;

class TestThread : public de::Thread
{
public:
    enum ThreadStatus
    {
        THREADSTATUS_NOT_STARTED = 0,
        THREADSTATUS_RUNNING,
        THREADSTATUS_READY,
    };

    TestThread(MultiThreadedTest &test, int id);
    void run(void);

    ThreadStatus getStatus(void) const
    {
        return m_status;
    }
    ThreadLog &getLog(void)
    {
        return m_log;
    }

    int getId(void) const
    {
        return m_id;
    }

    void setStatus(ThreadStatus status)
    {
        m_status = status;
    }

    const Library &getLibrary(void) const;

    // Test has stopped
    class TestStop
    {
    };

private:
    MultiThreadedTest &m_test;
    const int m_id;
    ThreadStatus m_status;
    ThreadLog m_log;
};

class MultiThreadedTest : public TestCase
{
public:
    MultiThreadedTest(EglTestContext &eglTestCtx, const char *name, const char *description, int threadCount,
                      uint64_t timeoutUs);
    virtual ~MultiThreadedTest(void);

    void init(void);
    void deinit(void);

    virtual bool runThread(TestThread &thread) = 0;
    virtual IterateResult iterate(void);
    void execTest(TestThread &thread);

    const Library &getLibrary(void) const
    {
        return m_eglTestCtx.getLibrary();
    }

protected:
    void barrier(void);

private:
    int m_threadCount;
    bool m_initialized;
    uint64_t m_startTimeUs;
    const uint64_t m_timeoutUs;
    bool m_ok;
    bool m_supported;
    vector<TestThread *> m_threads;

    volatile int32_t m_barrierWaiters;
    de::Semaphore m_barrierSemaphore1;
    de::Semaphore m_barrierSemaphore2;

protected:
    EGLDisplay m_display;
};

inline const Library &TestThread::getLibrary(void) const
{
    return m_test.getLibrary();
}

TestThread::TestThread(MultiThreadedTest &test, int id) : m_test(test), m_id(id), m_status(THREADSTATUS_NOT_STARTED)
{
}

void TestThread::run(void)
{
    m_status = THREADSTATUS_RUNNING;

    try
    {
        m_test.execTest(*this);
    }
    catch (const TestThread::TestStop &)
    {
        getLog() << ThreadLog::BeginMessage << "Thread stopped" << ThreadLog::EndMessage;
    }
    catch (const tcu::NotSupportedError &e)
    {
        getLog() << ThreadLog::BeginMessage << "Not supported: '" << e.what() << "'" << ThreadLog::EndMessage;
    }
    catch (const std::exception &e)
    {
        getLog() << ThreadLog::BeginMessage << "Got exception: '" << e.what() << "'" << ThreadLog::EndMessage;
    }
    catch (...)
    {
        getLog() << ThreadLog::BeginMessage << "Unknown exception" << ThreadLog::EndMessage;
    }

    getLibrary().releaseThread();
    m_status = THREADSTATUS_READY;
}

void MultiThreadedTest::execTest(TestThread &thread)
{
    try
    {
        if (!runThread(thread))
            m_ok = false;
    }
    catch (const TestThread::TestStop &)
    {
        // Thread exited due to error in other thread
        throw;
    }
    catch (const tcu::NotSupportedError &)
    {
        m_supported = false;

        // Release barriers
        for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
        {
            m_barrierSemaphore1.increment();
            m_barrierSemaphore2.increment();
        }

        throw;
    }
    catch (...)
    {
        m_ok = false;

        // Release barriers
        for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
        {
            m_barrierSemaphore1.increment();
            m_barrierSemaphore2.increment();
        }

        throw;
    }
}

MultiThreadedTest::MultiThreadedTest(EglTestContext &eglTestCtx, const char *name, const char *description,
                                     int threadCount, uint64_t timeoutUs)
    : TestCase(eglTestCtx, name, description)
    , m_threadCount(threadCount)
    , m_initialized(false)
    , m_startTimeUs(0)
    , m_timeoutUs(timeoutUs)
    , m_ok(true)
    , m_supported(true)
    , m_barrierWaiters(0)
    , m_barrierSemaphore1(0, 0)
    , m_barrierSemaphore2(1, 0)

    , m_display(EGL_NO_DISPLAY)
{
}

MultiThreadedTest::~MultiThreadedTest(void)
{
    for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
        delete m_threads[threadNdx];
    m_threads.clear();
}

void MultiThreadedTest::init(void)
{
    m_display = eglu::getAndInitDisplay(m_eglTestCtx.getNativeDisplay());
}

void MultiThreadedTest::deinit(void)
{
    if (m_display != EGL_NO_DISPLAY)
    {
        m_eglTestCtx.getLibrary().terminate(m_display);
        m_display = EGL_NO_DISPLAY;
    }
}

void MultiThreadedTest::barrier(void)
{
    {
        const int32_t waiters = deAtomicIncrement32(&m_barrierWaiters);

        if (waiters == m_threadCount)
        {
            m_barrierSemaphore2.decrement();
            m_barrierSemaphore1.increment();
        }
        else
        {
            m_barrierSemaphore1.decrement();
            m_barrierSemaphore1.increment();
        }
    }

    {
        const int32_t waiters = deAtomicDecrement32(&m_barrierWaiters);

        if (waiters == 0)
        {
            m_barrierSemaphore1.decrement();
            m_barrierSemaphore2.increment();
        }
        else
        {
            m_barrierSemaphore2.decrement();
            m_barrierSemaphore2.increment();
        }
    }

    // Barrier was released due an error in other thread
    if (!m_ok || !m_supported)
        throw TestThread::TestStop();
}

TestCase::IterateResult MultiThreadedTest::iterate(void)
{
    if (!m_initialized)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Thread timeout limit: " << m_timeoutUs << "us"
                           << tcu::TestLog::EndMessage;

        m_ok        = true;
        m_supported = true;

        // Create threads
        m_threads.reserve(m_threadCount);

        for (int threadNdx = 0; threadNdx < m_threadCount; threadNdx++)
            m_threads.push_back(new TestThread(*this, threadNdx));

        m_startTimeUs = deGetMicroseconds();

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

        m_initialized = true;
    }

    int readyCount = 0;
    for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
    {
        if (m_threads[threadNdx]->getStatus() != TestThread::THREADSTATUS_RUNNING)
            readyCount++;
    }

    if (readyCount == m_threadCount)
    {
        // Join threads
        for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
            m_threads[threadNdx]->join();

        // Get logs
        {
            vector<int> messageNdx;

            messageNdx.resize(m_threads.size(), 0);

            while (true)
            {
                int nextThreadNdx         = -1;
                uint64_t nextThreadTimeUs = 0;

                for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
                {
                    if (messageNdx[threadNdx] >= (int)m_threads[threadNdx]->getLog().getMessages().size())
                        continue;

                    if (nextThreadNdx == -1 ||
                        nextThreadTimeUs > m_threads[threadNdx]->getLog().getMessages()[messageNdx[threadNdx]].timeUs)
                    {
                        nextThreadNdx    = threadNdx;
                        nextThreadTimeUs = m_threads[threadNdx]->getLog().getMessages()[messageNdx[threadNdx]].timeUs;
                    }
                }

                if (nextThreadNdx == -1)
                    break;

                m_testCtx.getLog() << tcu::TestLog::Message << "[" << (nextThreadTimeUs - m_startTimeUs) << "] ("
                                   << nextThreadNdx << ") "
                                   << m_threads[nextThreadNdx]->getLog().getMessages()[messageNdx[nextThreadNdx]].msg
                                   << tcu::TestLog::EndMessage;

                messageNdx[nextThreadNdx]++;
            }
        }

        // Destroy threads
        for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
            delete m_threads[threadNdx];

        m_threads.clear();

        // Set result
        if (m_ok)
        {
            if (!m_supported)
                m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Not Supported");
            else
                m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
        }
        else
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");

        return STOP;
    }
    else
    {
        // Check for timeout
        const uint64_t currentTimeUs = deGetMicroseconds();

        if (currentTimeUs - m_startTimeUs > m_timeoutUs)
        {
            // Get logs
            {
                vector<int> messageNdx;

                messageNdx.resize(m_threads.size(), 0);

                while (true)
                {
                    int nextThreadNdx         = -1;
                    uint64_t nextThreadTimeUs = 0;

                    for (int threadNdx = 0; threadNdx < (int)m_threads.size(); threadNdx++)
                    {
                        if (messageNdx[threadNdx] >= (int)m_threads[threadNdx]->getLog().getMessages().size())
                            continue;

                        if (nextThreadNdx == -1 ||
                            nextThreadTimeUs >
                                m_threads[threadNdx]->getLog().getMessages()[messageNdx[threadNdx]].timeUs)
                        {
                            nextThreadNdx = threadNdx;
                            nextThreadTimeUs =
                                m_threads[threadNdx]->getLog().getMessages()[messageNdx[threadNdx]].timeUs;
                        }
                    }

                    if (nextThreadNdx == -1)
                        break;

                    m_testCtx.getLog() << tcu::TestLog::Message << "[" << (nextThreadTimeUs - m_startTimeUs) << "] ("
                                       << nextThreadNdx << ") "
                                       << m_threads[nextThreadNdx]
                                              ->getLog()
                                              .getMessages()[messageNdx[nextThreadNdx]]
                                              .msg
                                       << tcu::TestLog::EndMessage;

                    messageNdx[nextThreadNdx]++;
                }
            }

            m_testCtx.getLog() << tcu::TestLog::Message << "[" << (currentTimeUs - m_startTimeUs)
                               << "] (-) Timeout, Limit: " << m_timeoutUs << "us" << tcu::TestLog::EndMessage;
            m_testCtx.getLog() << tcu::TestLog::Message << "[" << (currentTimeUs - m_startTimeUs)
                               << "] (-) Trying to perform resource cleanup..." << tcu::TestLog::EndMessage;

            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
            return STOP;
        }

        // Sleep
        deSleep(10);
    }

    return CONTINUE;
}

namespace
{

const char *configAttributeToString(EGLint e)
{
    switch (e)
    {
    case EGL_BUFFER_SIZE:
        return "EGL_BUFFER_SIZE";
    case EGL_RED_SIZE:
        return "EGL_RED_SIZE";
    case EGL_GREEN_SIZE:
        return "EGL_GREEN_SIZE";
    case EGL_BLUE_SIZE:
        return "EGL_BLUE_SIZE";
    case EGL_LUMINANCE_SIZE:
        return "EGL_LUMINANCE_SIZE";
    case EGL_ALPHA_SIZE:
        return "EGL_ALPHA_SIZE";
    case EGL_ALPHA_MASK_SIZE:
        return "EGL_ALPHA_MASK_SIZE";
    case EGL_BIND_TO_TEXTURE_RGB:
        return "EGL_BIND_TO_TEXTURE_RGB";
    case EGL_BIND_TO_TEXTURE_RGBA:
        return "EGL_BIND_TO_TEXTURE_RGBA";
    case EGL_COLOR_BUFFER_TYPE:
        return "EGL_COLOR_BUFFER_TYPE";
    case EGL_CONFIG_CAVEAT:
        return "EGL_CONFIG_CAVEAT";
    case EGL_CONFIG_ID:
        return "EGL_CONFIG_ID";
    case EGL_CONFORMANT:
        return "EGL_CONFORMANT";
    case EGL_DEPTH_SIZE:
        return "EGL_DEPTH_SIZE";
    case EGL_LEVEL:
        return "EGL_LEVEL";
    case EGL_MAX_PBUFFER_WIDTH:
        return "EGL_MAX_PBUFFER_WIDTH";
    case EGL_MAX_PBUFFER_HEIGHT:
        return "EGL_MAX_PBUFFER_HEIGHT";
    case EGL_MAX_PBUFFER_PIXELS:
        return "EGL_MAX_PBUFFER_PIXELS";
    case EGL_MAX_SWAP_INTERVAL:
        return "EGL_MAX_SWAP_INTERVAL";
    case EGL_MIN_SWAP_INTERVAL:
        return "EGL_MIN_SWAP_INTERVAL";
    case EGL_NATIVE_RENDERABLE:
        return "EGL_NATIVE_RENDERABLE";
    case EGL_NATIVE_VISUAL_ID:
        return "EGL_NATIVE_VISUAL_ID";
    case EGL_NATIVE_VISUAL_TYPE:
        return "EGL_NATIVE_VISUAL_TYPE";
    case EGL_RENDERABLE_TYPE:
        return "EGL_RENDERABLE_TYPE";
    case EGL_SAMPLE_BUFFERS:
        return "EGL_SAMPLE_BUFFERS";
    case EGL_SAMPLES:
        return "EGL_SAMPLES";
    case EGL_STENCIL_SIZE:
        return "EGL_STENCIL_SIZE";
    case EGL_SURFACE_TYPE:
        return "EGL_SURFACE_TYPE";
    case EGL_TRANSPARENT_TYPE:
        return "EGL_TRANSPARENT_TYPE";
    case EGL_TRANSPARENT_RED_VALUE:
        return "EGL_TRANSPARENT_RED_VALUE";
    case EGL_TRANSPARENT_GREEN_VALUE:
        return "EGL_TRANSPARENT_GREEN_VALUE";
    case EGL_TRANSPARENT_BLUE_VALUE:
        return "EGL_TRANSPARENT_BLUE_VALUE";
    case EGL_RECORDABLE_ANDROID:
        return "EGL_RECORDABLE_ANDROID";
    default:
        return "<Unknown>";
    }
}

} // namespace

class MultiThreadedConfigTest : public MultiThreadedTest
{
public:
    MultiThreadedConfigTest(EglTestContext &context, const char *name, const char *description, int getConfigs,
                            int chooseConfigs, int query);
    bool runThread(TestThread &thread);

private:
    const int m_getConfigs;
    const int m_chooseConfigs;
    const int m_query;
};

MultiThreadedConfigTest::MultiThreadedConfigTest(EglTestContext &context, const char *name, const char *description,
                                                 int getConfigs, int chooseConfigs, int query)
    : MultiThreadedTest(context, name, description, 2,
                        20000000 /*us = 20s*/) // \todo [mika] Set timeout to something relevant to frameworks timeout?
    , m_getConfigs(getConfigs)
    , m_chooseConfigs(chooseConfigs)
    , m_query(query)
{
}

bool MultiThreadedConfigTest::runThread(TestThread &thread)
{
    const Library &egl = getLibrary();
    de::Random rnd(deInt32Hash(thread.getId() + 10435));
    vector<EGLConfig> configs;

    barrier();

    for (int getConfigsNdx = 0; getConfigsNdx < m_getConfigs; getConfigsNdx++)
    {
        EGLint configCount;

        // Get number of configs
        {
            EGLBoolean result;

            result = egl.getConfigs(m_display, NULL, 0, &configCount);
            thread.getLog() << ThreadLog::BeginMessage << result << " = eglGetConfigs(" << m_display << ", NULL, 0, "
                            << configCount << ")" << ThreadLog::EndMessage;
            EGLU_CHECK_MSG(egl, "eglGetConfigs()");

            if (!result)
                return false;
        }

        configs.resize(configs.size() + configCount);

        // Get configs
        if (configCount != 0)
        {
            EGLBoolean result;

            result = egl.getConfigs(m_display, &(configs[configs.size() - configCount]), configCount, &configCount);
            thread.getLog() << ThreadLog::BeginMessage << result << " = eglGetConfigs(" << m_display << ", &configs' "
                            << configCount << ", " << configCount << ")" << ThreadLog::EndMessage;
            EGLU_CHECK_MSG(egl, "eglGetConfigs()");

            if (!result)
                return false;
        }

        // Pop configs to stop config list growing
        if (configs.size() > 40)
        {
            configs.erase(configs.begin() + 40, configs.end());
        }
        else
        {
            const int popCount = rnd.getInt(0, (int)(configs.size() - 2));

            configs.erase(configs.begin() + (configs.size() - popCount), configs.end());
        }
    }

    for (int chooseConfigsNdx = 0; chooseConfigsNdx < m_chooseConfigs; chooseConfigsNdx++)
    {
        EGLint configCount;

        static const EGLint attribList[] = {EGL_NONE};

        // Get number of configs
        {
            EGLBoolean result;

            result = egl.chooseConfig(m_display, attribList, NULL, 0, &configCount);
            thread.getLog() << ThreadLog::BeginMessage << result << " = eglChooseConfig(" << m_display
                            << ", { EGL_NONE }, NULL, 0, " << configCount << ")" << ThreadLog::EndMessage;
            EGLU_CHECK_MSG(egl, "eglChooseConfig()");

            if (!result)
                return false;
        }

        configs.resize(configs.size() + configCount);

        // Get configs
        if (configCount != 0)
        {
            EGLBoolean result;

            result = egl.chooseConfig(m_display, attribList, &(configs[configs.size() - configCount]), configCount,
                                      &configCount);
            thread.getLog() << ThreadLog::BeginMessage << result << " = eglChooseConfig(" << m_display
                            << ", { EGL_NONE }, &configs, " << configCount << ", " << configCount << ")"
                            << ThreadLog::EndMessage;
            EGLU_CHECK_MSG(egl, "eglChooseConfig()");

            if (!result)
                return false;
        }

        // Pop configs to stop config list growing
        if (configs.size() > 40)
        {
            configs.erase(configs.begin() + 40, configs.end());
        }
        else
        {
            const int popCount = rnd.getInt(0, (int)(configs.size() - 2));

            configs.erase(configs.begin() + (configs.size() - popCount), configs.end());
        }
    }

    {
        // Perform queries on configs
        std::vector<EGLint> attributes = {
            EGL_BUFFER_SIZE,
            EGL_RED_SIZE,
            EGL_GREEN_SIZE,
            EGL_BLUE_SIZE,
            EGL_LUMINANCE_SIZE,
            EGL_ALPHA_SIZE,
            EGL_ALPHA_MASK_SIZE,
            EGL_BIND_TO_TEXTURE_RGB,
            EGL_BIND_TO_TEXTURE_RGBA,
            EGL_COLOR_BUFFER_TYPE,
            EGL_CONFIG_CAVEAT,
            EGL_CONFIG_ID,
            EGL_CONFORMANT,
            EGL_DEPTH_SIZE,
            EGL_LEVEL,
            EGL_MAX_PBUFFER_WIDTH,
            EGL_MAX_PBUFFER_HEIGHT,
            EGL_MAX_PBUFFER_PIXELS,
            EGL_MAX_SWAP_INTERVAL,
            EGL_MIN_SWAP_INTERVAL,
            EGL_NATIVE_RENDERABLE,
            EGL_NATIVE_VISUAL_ID,
            EGL_NATIVE_VISUAL_TYPE,
            EGL_RENDERABLE_TYPE,
            EGL_SAMPLE_BUFFERS,
            EGL_SAMPLES,
            EGL_STENCIL_SIZE,
            EGL_SURFACE_TYPE,
            EGL_TRANSPARENT_TYPE,
            EGL_TRANSPARENT_RED_VALUE,
            EGL_TRANSPARENT_GREEN_VALUE,
            EGL_TRANSPARENT_BLUE_VALUE,
        };

        if (eglu::hasExtension(egl, m_display, "EGL_ANDROID_recordable"))
            attributes.emplace_back(EGL_RECORDABLE_ANDROID);

        for (int queryNdx = 0; queryNdx < m_query; queryNdx++)
        {
            const EGLint attribute = attributes[rnd.getInt(0, static_cast<int>(attributes.size()) - 1)];
            EGLConfig config       = configs[rnd.getInt(0, (int)(configs.size() - 1))];
            EGLint value;
            EGLBoolean result;

            result = egl.getConfigAttrib(m_display, config, attribute, &value);
            thread.getLog() << ThreadLog::BeginMessage << result << " = eglGetConfigAttrib(" << m_display << ", "
                            << config << ", " << configAttributeToString(attribute) << ", " << value << ")"
                            << ThreadLog::EndMessage;
            EGLU_CHECK_MSG(egl, "eglGetConfigAttrib()");

            if (!result)
                return false;
        }
    }

    return true;
}

class MultiThreadedObjectTest : public MultiThreadedTest
{
public:
    enum Type
    {
        TYPE_PBUFFER       = (1 << 0),
        TYPE_PIXMAP        = (1 << 1),
        TYPE_WINDOW        = (1 << 2),
        TYPE_SINGLE_WINDOW = (1 << 3),
        TYPE_CONTEXT       = (1 << 4)
    };

    MultiThreadedObjectTest(EglTestContext &context, const char *name, const char *description, uint32_t types);
    ~MultiThreadedObjectTest(void);

    virtual void deinit(void);

    bool runThread(TestThread &thread);

    void createDestroyObjects(TestThread &thread, int count);
    void pushObjectsToShared(TestThread &thread);
    void pullObjectsFromShared(TestThread &thread, int pbufferCount, int pixmapCount, int windowCount,
                               int contextCount);
    void querySetSharedObjects(TestThread &thread, int count);
    void destroyObjects(TestThread &thread);

private:
    EGLConfig m_config;
    de::Random m_rnd0;
    de::Random m_rnd1;
    Type m_types;

    volatile uint32_t m_hasWindow;

    vector<pair<eglu::NativePixmap *, EGLSurface>> m_sharedNativePixmaps;
    vector<pair<eglu::NativePixmap *, EGLSurface>> m_nativePixmaps0;
    vector<pair<eglu::NativePixmap *, EGLSurface>> m_nativePixmaps1;

    vector<pair<eglu::NativeWindow *, EGLSurface>> m_sharedNativeWindows;
    vector<pair<eglu::NativeWindow *, EGLSurface>> m_nativeWindows0;
    vector<pair<eglu::NativeWindow *, EGLSurface>> m_nativeWindows1;

    vector<EGLSurface> m_sharedPbuffers;
    vector<EGLSurface> m_pbuffers0;
    vector<EGLSurface> m_pbuffers1;

    vector<EGLContext> m_sharedContexts;
    vector<EGLContext> m_contexts0;
    vector<EGLContext> m_contexts1;
};

MultiThreadedObjectTest::MultiThreadedObjectTest(EglTestContext &context, const char *name, const char *description,
                                                 uint32_t type)
    : MultiThreadedTest(context, name, description, 2,
                        20000000 /*us = 20s*/) // \todo [mika] Set timeout to something relevant to frameworks timeout?
    , m_config(DE_NULL)
    , m_rnd0(58204327)
    , m_rnd1(230983)
    , m_types((Type)type)
    , m_hasWindow(0)
{
}

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

void MultiThreadedObjectTest::deinit(void)
{
    const Library &egl = getLibrary();

    // Clear pbuffers
    for (int pbufferNdx = 0; pbufferNdx < (int)m_pbuffers0.size(); pbufferNdx++)
    {
        if (m_pbuffers0[pbufferNdx] != EGL_NO_SURFACE)
        {
            egl.destroySurface(m_display, m_pbuffers0[pbufferNdx]);
            EGLU_CHECK_MSG(egl, "eglDestroySurface()");
            m_pbuffers0[pbufferNdx] = EGL_NO_SURFACE;
        }
    }
    m_pbuffers0.clear();

    for (int pbufferNdx = 0; pbufferNdx < (int)m_pbuffers1.size(); pbufferNdx++)
    {
        if (m_pbuffers1[pbufferNdx] != EGL_NO_SURFACE)
        {
            egl.destroySurface(m_display, m_pbuffers1[pbufferNdx]);
            EGLU_CHECK_MSG(egl, "eglDestroySurface()");
            m_pbuffers1[pbufferNdx] = EGL_NO_SURFACE;
        }
    }
    m_pbuffers1.clear();

    for (int pbufferNdx = 0; pbufferNdx < (int)m_sharedPbuffers.size(); pbufferNdx++)
    {
        if (m_sharedPbuffers[pbufferNdx] != EGL_NO_SURFACE)
        {
            egl.destroySurface(m_display, m_sharedPbuffers[pbufferNdx]);
            EGLU_CHECK_MSG(egl, "eglDestroySurface()");
            m_sharedPbuffers[pbufferNdx] = EGL_NO_SURFACE;
        }
    }
    m_sharedPbuffers.clear();

    for (int contextNdx = 0; contextNdx < (int)m_sharedContexts.size(); contextNdx++)
    {
        if (m_sharedContexts[contextNdx] != EGL_NO_CONTEXT)
        {
            egl.destroyContext(m_display, m_sharedContexts[contextNdx]);
            EGLU_CHECK_MSG(egl, "eglDestroyContext()");
            m_sharedContexts[contextNdx] = EGL_NO_CONTEXT;
        }
    }
    m_sharedContexts.clear();

    for (int contextNdx = 0; contextNdx < (int)m_contexts0.size(); contextNdx++)
    {
        if (m_contexts0[contextNdx] != EGL_NO_CONTEXT)
        {
            egl.destroyContext(m_display, m_contexts0[contextNdx]);
            EGLU_CHECK_MSG(egl, "eglDestroyContext()");
            m_contexts0[contextNdx] = EGL_NO_CONTEXT;
        }
    }
    m_contexts0.clear();

    for (int contextNdx = 0; contextNdx < (int)m_contexts1.size(); contextNdx++)
    {
        if (m_contexts1[contextNdx] != EGL_NO_CONTEXT)
        {
            egl.destroyContext(m_display, m_contexts1[contextNdx]);
            EGLU_CHECK_MSG(egl, "eglDestroyContext()");
            m_contexts1[contextNdx] = EGL_NO_CONTEXT;
        }
    }
    m_contexts1.clear();

    // Clear pixmaps
    for (int pixmapNdx = 0; pixmapNdx < (int)m_nativePixmaps0.size(); pixmapNdx++)
    {
        if (m_nativePixmaps0[pixmapNdx].second != EGL_NO_SURFACE)
            EGLU_CHECK_CALL(egl, destroySurface(m_display, m_nativePixmaps0[pixmapNdx].second));

        m_nativePixmaps0[pixmapNdx].second = EGL_NO_SURFACE;
        delete m_nativePixmaps0[pixmapNdx].first;
        m_nativePixmaps0[pixmapNdx].first = NULL;
    }
    m_nativePixmaps0.clear();

    for (int pixmapNdx = 0; pixmapNdx < (int)m_nativePixmaps1.size(); pixmapNdx++)
    {
        if (m_nativePixmaps1[pixmapNdx].second != EGL_NO_SURFACE)
            EGLU_CHECK_CALL(egl, destroySurface(m_display, m_nativePixmaps1[pixmapNdx].second));

        m_nativePixmaps1[pixmapNdx].second = EGL_NO_SURFACE;
        delete m_nativePixmaps1[pixmapNdx].first;
        m_nativePixmaps1[pixmapNdx].first = NULL;
    }
    m_nativePixmaps1.clear();

    for (int pixmapNdx = 0; pixmapNdx < (int)m_sharedNativePixmaps.size(); pixmapNdx++)
    {
        if (m_sharedNativePixmaps[pixmapNdx].second != EGL_NO_SURFACE)
            EGLU_CHECK_CALL(egl, destroySurface(m_display, m_sharedNativePixmaps[pixmapNdx].second));

        m_sharedNativePixmaps[pixmapNdx].second = EGL_NO_SURFACE;
        delete m_sharedNativePixmaps[pixmapNdx].first;
        m_sharedNativePixmaps[pixmapNdx].first = NULL;
    }
    m_sharedNativePixmaps.clear();

    // Clear windows
    for (int windowNdx = 0; windowNdx < (int)m_nativeWindows1.size(); windowNdx++)
    {
        if (m_nativeWindows1[windowNdx].second != EGL_NO_SURFACE)
            EGLU_CHECK_CALL(egl, destroySurface(m_display, m_nativeWindows1[windowNdx].second));

        m_nativeWindows1[windowNdx].second = EGL_NO_SURFACE;
        delete m_nativeWindows1[windowNdx].first;
        m_nativeWindows1[windowNdx].first = NULL;
    }
    m_nativeWindows1.clear();

    for (int windowNdx = 0; windowNdx < (int)m_nativeWindows0.size(); windowNdx++)
    {
        if (m_nativeWindows0[windowNdx].second != EGL_NO_SURFACE)
            EGLU_CHECK_CALL(egl, destroySurface(m_display, m_nativeWindows0[windowNdx].second));

        m_nativeWindows0[windowNdx].second = EGL_NO_SURFACE;
        delete m_nativeWindows0[windowNdx].first;
        m_nativeWindows0[windowNdx].first = NULL;
    }
    m_nativeWindows0.clear();

    for (int windowNdx = 0; windowNdx < (int)m_sharedNativeWindows.size(); windowNdx++)
    {
        if (m_sharedNativeWindows[windowNdx].second != EGL_NO_SURFACE)
            EGLU_CHECK_CALL(egl, destroySurface(m_display, m_sharedNativeWindows[windowNdx].second));

        m_sharedNativeWindows[windowNdx].second = EGL_NO_SURFACE;
        delete m_sharedNativeWindows[windowNdx].first;
        m_sharedNativeWindows[windowNdx].first = NULL;
    }
    m_sharedNativeWindows.clear();

    MultiThreadedTest::deinit();
}

bool MultiThreadedObjectTest::runThread(TestThread &thread)
{
    const Library &egl = getLibrary();

    if (thread.getId() == 0)
    {
        EGLint surfaceTypes = 0;

        if ((m_types & TYPE_WINDOW) != 0)
            surfaceTypes |= EGL_WINDOW_BIT;

        if ((m_types & TYPE_PBUFFER) != 0)
            surfaceTypes |= EGL_PBUFFER_BIT;

        if ((m_types & TYPE_PIXMAP) != 0)
            surfaceTypes |= EGL_PIXMAP_BIT;

        EGLint configCount;
        EGLint attribList[] = {EGL_SURFACE_TYPE, surfaceTypes, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE};

        EGLU_CHECK_CALL(egl, chooseConfig(m_display, attribList, &m_config, 1, &configCount));

        if (configCount == 0)
            TCU_THROW(NotSupportedError, "No usable config found");
    }

    barrier();

    // Create / Destroy Objects
    if ((m_types & TYPE_SINGLE_WINDOW) != 0 && (m_types & TYPE_PBUFFER) == 0 && (m_types & TYPE_PIXMAP) == 0 &&
        (m_types & TYPE_CONTEXT) == 0)
    {
        if (thread.getId() == 0)
            createDestroyObjects(thread, 1);
    }
    else
        createDestroyObjects(thread, 100);

    // Push first threads objects to shared
    if (thread.getId() == 0)
        pushObjectsToShared(thread);

    barrier();

    // Push second threads objects to shared
    if (thread.getId() == 1)
        pushObjectsToShared(thread);

    barrier();

    // Make queries from shared surfaces
    querySetSharedObjects(thread, 100);

    barrier();

    // Pull surfaces for first thread from shared surfaces
    if (thread.getId() == 0)
        pullObjectsFromShared(thread, (int)(m_sharedPbuffers.size() / 2), (int)(m_sharedNativePixmaps.size() / 2),
                              (int)(m_sharedNativeWindows.size() / 2), (int)(m_sharedContexts.size() / 2));

    barrier();

    // Pull surfaces for second thread from shared surfaces
    if (thread.getId() == 1)
        pullObjectsFromShared(thread, (int)m_sharedPbuffers.size(), (int)m_sharedNativePixmaps.size(),
                              (int)m_sharedNativeWindows.size(), (int)m_sharedContexts.size());

    barrier();

    // Create / Destroy Objects
    if ((m_types & TYPE_SINGLE_WINDOW) == 0)
        createDestroyObjects(thread, 100);

    // Destroy surfaces
    destroyObjects(thread);

    return true;
}

void MultiThreadedObjectTest::createDestroyObjects(TestThread &thread, int count)
{
    const Library &egl           = getLibrary();
    de::Random &rnd              = (thread.getId() == 0 ? m_rnd0 : m_rnd1);
    vector<EGLSurface> &pbuffers = (thread.getId() == 0 ? m_pbuffers0 : m_pbuffers1);
    vector<pair<eglu::NativeWindow *, EGLSurface>> &windows =
        (thread.getId() == 0 ? m_nativeWindows0 : m_nativeWindows1);
    vector<pair<eglu::NativePixmap *, EGLSurface>> &pixmaps =
        (thread.getId() == 0 ? m_nativePixmaps0 : m_nativePixmaps1);
    vector<EGLContext> &contexts = (thread.getId() == 0 ? m_contexts0 : m_contexts1);
    set<Type> objectTypes;

    if ((m_types & TYPE_PBUFFER) != 0)
        objectTypes.insert(TYPE_PBUFFER);

    if ((m_types & TYPE_PIXMAP) != 0)
        objectTypes.insert(TYPE_PIXMAP);

    if ((m_types & TYPE_WINDOW) != 0)
        objectTypes.insert(TYPE_WINDOW);

    if ((m_types & TYPE_CONTEXT) != 0)
        objectTypes.insert(TYPE_CONTEXT);

    for (int createDestroyNdx = 0; createDestroyNdx < count; createDestroyNdx++)
    {
        bool create;
        Type type;

        if (pbuffers.size() > 5 && ((m_types & TYPE_PBUFFER) != 0))
        {
            create = false;
            type   = TYPE_PBUFFER;
        }
        else if (windows.size() > 5 && ((m_types & TYPE_WINDOW) != 0))
        {
            create = false;
            type   = TYPE_WINDOW;
        }
        else if (pixmaps.size() > 5 && ((m_types & TYPE_PIXMAP) != 0))
        {
            create = false;
            type   = TYPE_PIXMAP;
        }
        else if (contexts.size() > 5 && ((m_types & TYPE_CONTEXT) != 0))
        {
            create = false;
            type   = TYPE_CONTEXT;
        }
        else if (pbuffers.size() < 3 && ((m_types & TYPE_PBUFFER) != 0))
        {
            create = true;
            type   = TYPE_PBUFFER;
        }
        else if (pixmaps.size() < 3 && ((m_types & TYPE_PIXMAP) != 0))
        {
            create = true;
            type   = TYPE_PIXMAP;
        }
        else if (contexts.size() < 3 && ((m_types & TYPE_CONTEXT) != 0))
        {
            create = true;
            type   = TYPE_CONTEXT;
        }
        else if (windows.size() < 3 && ((m_types & TYPE_WINDOW) != 0) && ((m_types & TYPE_SINGLE_WINDOW) == 0))
        {
            create = true;
            type   = TYPE_WINDOW;
        }
        else if (windows.empty() && (m_hasWindow == 0) && ((m_types & TYPE_WINDOW) != 0) &&
                 ((m_types & TYPE_SINGLE_WINDOW) != 0))
        {
            create = true;
            type   = TYPE_WINDOW;
        }
        else
        {
            create = rnd.getBool();

            if (!create && windows.empty())
                objectTypes.erase(TYPE_WINDOW);

            type = rnd.choose<Type>(objectTypes.begin(), objectTypes.end());
        }

        if (create)
        {
            switch (type)
            {
            case TYPE_PBUFFER:
            {
                EGLSurface surface;

                const EGLint attributes[] = {EGL_WIDTH, 64, EGL_HEIGHT, 64,

                                             EGL_NONE};

                surface = egl.createPbufferSurface(m_display, m_config, attributes);
                thread.getLog() << ThreadLog::BeginMessage << surface << " = eglCreatePbufferSurface(" << m_display
                                << ", " << m_config << ", { EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE })"
                                << ThreadLog::EndMessage;
                EGLU_CHECK_MSG(egl, "eglCreatePbufferSurface()");

                pbuffers.push_back(surface);

                break;
            }

            case TYPE_WINDOW:
            {
                const eglu::NativeWindowFactory &windowFactory =
                    eglu::selectNativeWindowFactory(m_eglTestCtx.getNativeDisplayFactory(), m_testCtx.getCommandLine());

                if ((m_types & TYPE_SINGLE_WINDOW) != 0)
                {
                    if (deAtomicCompareExchange32(&m_hasWindow, 0, 1) == 0)
                    {
                        eglu::NativeWindow *window = DE_NULL;
                        EGLSurface surface         = EGL_NO_SURFACE;

                        try
                        {
                            window = windowFactory.createWindow(
                                &m_eglTestCtx.getNativeDisplay(), m_display, m_config, DE_NULL,
                                eglu::WindowParams(64, 64, eglu::parseWindowVisibility(m_testCtx.getCommandLine())));
                            surface = eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *window, m_display,
                                                                m_config, DE_NULL);

                            thread.getLog() << ThreadLog::BeginMessage << surface << " = eglCreateWindowSurface()"
                                            << ThreadLog::EndMessage;
                            windows.push_back(std::make_pair(window, surface));
                        }
                        catch (const std::exception &)
                        {
                            if (surface != EGL_NO_SURFACE)
                                EGLU_CHECK_CALL(egl, destroySurface(m_display, surface));
                            delete window;
                            m_hasWindow = 0;
                            throw;
                        }
                    }
                    else
                    {
                        createDestroyNdx--;
                    }
                }
                else
                {
                    eglu::NativeWindow *window = DE_NULL;
                    EGLSurface surface         = EGL_NO_SURFACE;

                    try
                    {
                        window = windowFactory.createWindow(
                            &m_eglTestCtx.getNativeDisplay(), m_display, m_config, DE_NULL,
                            eglu::WindowParams(64, 64, eglu::parseWindowVisibility(m_testCtx.getCommandLine())));
                        surface = eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *window, m_display,
                                                            m_config, DE_NULL);

                        thread.getLog() << ThreadLog::BeginMessage << surface << " = eglCreateWindowSurface()"
                                        << ThreadLog::EndMessage;
                        windows.push_back(std::make_pair(window, surface));
                    }
                    catch (const std::exception &)
                    {
                        if (surface != EGL_NO_SURFACE)
                            EGLU_CHECK_CALL(egl, destroySurface(m_display, surface));
                        delete window;
                        throw;
                    }
                }
                break;
            }

            case TYPE_PIXMAP:
            {
                const eglu::NativePixmapFactory &pixmapFactory =
                    eglu::selectNativePixmapFactory(m_eglTestCtx.getNativeDisplayFactory(), m_testCtx.getCommandLine());
                eglu::NativePixmap *pixmap = DE_NULL;
                EGLSurface surface         = EGL_NO_SURFACE;

                try
                {
                    pixmap  = pixmapFactory.createPixmap(&m_eglTestCtx.getNativeDisplay(), m_display, m_config, DE_NULL,
                                                         64, 64);
                    surface = eglu::createPixmapSurface(m_eglTestCtx.getNativeDisplay(), *pixmap, m_display, m_config,
                                                        DE_NULL);

                    thread.getLog() << ThreadLog::BeginMessage << surface << " = eglCreatePixmapSurface()"
                                    << ThreadLog::EndMessage;
                    pixmaps.push_back(std::make_pair(pixmap, surface));
                }
                catch (const std::exception &)
                {
                    if (surface != EGL_NO_SURFACE)
                        EGLU_CHECK_CALL(egl, destroySurface(m_display, surface));
                    delete pixmap;
                    throw;
                }
                break;
            }

            case TYPE_CONTEXT:
            {
                EGLContext context;

                EGLU_CHECK_CALL(egl, bindAPI(EGL_OPENGL_ES_API));
                thread.getLog() << ThreadLog::BeginMessage << "eglBindAPI(EGL_OPENGL_ES_API)" << ThreadLog::EndMessage;

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

                context = egl.createContext(m_display, m_config, EGL_NO_CONTEXT, attributes);
                thread.getLog() << ThreadLog::BeginMessage << context << " = eglCreateContext(" << m_display << ", "
                                << m_config << ", EGL_NO_CONTEXT, { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE })"
                                << ThreadLog::EndMessage;
                EGLU_CHECK_MSG(egl, "eglCreateContext()");
                contexts.push_back(context);
                break;
            }

            default:
                DE_ASSERT(false);
            }
        }
        else
        {
            switch (type)
            {
            case TYPE_PBUFFER:
            {
                const int pbufferNdx = rnd.getInt(0, (int)(pbuffers.size() - 1));
                EGLBoolean result;

                result = egl.destroySurface(m_display, pbuffers[pbufferNdx]);
                thread.getLog() << ThreadLog::BeginMessage << result << " = eglDestroySurface(" << m_display << ", "
                                << pbuffers[pbufferNdx] << ")" << ThreadLog::EndMessage;
                EGLU_CHECK_MSG(egl, "eglDestroySurface()");

                pbuffers.erase(pbuffers.begin() + pbufferNdx);

                break;
            }

            case TYPE_WINDOW:
            {
                const int windowNdx = rnd.getInt(0, (int)(windows.size() - 1));

                thread.getLog() << ThreadLog::BeginMessage << "eglDestroySurface(" << m_display << ", "
                                << windows[windowNdx].second << ")" << ThreadLog::EndMessage;

                EGLU_CHECK_CALL(egl, destroySurface(m_display, windows[windowNdx].second));
                windows[windowNdx].second = EGL_NO_SURFACE;
                delete windows[windowNdx].first;
                windows[windowNdx].first = DE_NULL;
                windows.erase(windows.begin() + windowNdx);

                if ((m_types & TYPE_SINGLE_WINDOW) != 0)
                    m_hasWindow = 0;

                break;
            }

            case TYPE_PIXMAP:
            {
                const int pixmapNdx = rnd.getInt(0, (int)(pixmaps.size() - 1));

                thread.getLog() << ThreadLog::BeginMessage << "eglDestroySurface(" << m_display << ", "
                                << pixmaps[pixmapNdx].second << ")" << ThreadLog::EndMessage;
                EGLU_CHECK_CALL(egl, destroySurface(m_display, pixmaps[pixmapNdx].second));
                pixmaps[pixmapNdx].second = EGL_NO_SURFACE;
                delete pixmaps[pixmapNdx].first;
                pixmaps[pixmapNdx].first = DE_NULL;
                pixmaps.erase(pixmaps.begin() + pixmapNdx);

                break;
            }

            case TYPE_CONTEXT:
            {
                const int contextNdx = rnd.getInt(0, (int)(contexts.size() - 1));

                EGLU_CHECK_CALL(egl, destroyContext(m_display, contexts[contextNdx]));
                thread.getLog() << ThreadLog::BeginMessage << "eglDestroyContext(" << m_display << ", "
                                << contexts[contextNdx] << ")" << ThreadLog::EndMessage;
                contexts.erase(contexts.begin() + contextNdx);

                break;
            }

            default:
                DE_ASSERT(false);
            }
        }
    }
}

void MultiThreadedObjectTest::pushObjectsToShared(TestThread &thread)
{
    vector<EGLSurface> &pbuffers = (thread.getId() == 0 ? m_pbuffers0 : m_pbuffers1);
    vector<pair<eglu::NativeWindow *, EGLSurface>> &windows =
        (thread.getId() == 0 ? m_nativeWindows0 : m_nativeWindows1);
    vector<pair<eglu::NativePixmap *, EGLSurface>> &pixmaps =
        (thread.getId() == 0 ? m_nativePixmaps0 : m_nativePixmaps1);
    vector<EGLContext> &contexts = (thread.getId() == 0 ? m_contexts0 : m_contexts1);

    for (int pbufferNdx = 0; pbufferNdx < (int)pbuffers.size(); pbufferNdx++)
        m_sharedPbuffers.push_back(pbuffers[pbufferNdx]);

    pbuffers.clear();

    for (int windowNdx = 0; windowNdx < (int)windows.size(); windowNdx++)
        m_sharedNativeWindows.push_back(windows[windowNdx]);

    windows.clear();

    for (int pixmapNdx = 0; pixmapNdx < (int)pixmaps.size(); pixmapNdx++)
        m_sharedNativePixmaps.push_back(pixmaps[pixmapNdx]);

    pixmaps.clear();

    for (int contextNdx = 0; contextNdx < (int)contexts.size(); contextNdx++)
        m_sharedContexts.push_back(contexts[contextNdx]);

    contexts.clear();
}

void MultiThreadedObjectTest::pullObjectsFromShared(TestThread &thread, int pbufferCount, int pixmapCount,
                                                    int windowCount, int contextCount)
{
    de::Random &rnd              = (thread.getId() == 0 ? m_rnd0 : m_rnd1);
    vector<EGLSurface> &pbuffers = (thread.getId() == 0 ? m_pbuffers0 : m_pbuffers1);
    vector<pair<eglu::NativeWindow *, EGLSurface>> &windows =
        (thread.getId() == 0 ? m_nativeWindows0 : m_nativeWindows1);
    vector<pair<eglu::NativePixmap *, EGLSurface>> &pixmaps =
        (thread.getId() == 0 ? m_nativePixmaps0 : m_nativePixmaps1);
    vector<EGLContext> &contexts = (thread.getId() == 0 ? m_contexts0 : m_contexts1);

    for (int pbufferNdx = 0; pbufferNdx < pbufferCount; pbufferNdx++)
    {
        const int ndx = rnd.getInt(0, (int)(m_sharedPbuffers.size() - 1));

        pbuffers.push_back(m_sharedPbuffers[ndx]);
        m_sharedPbuffers.erase(m_sharedPbuffers.begin() + ndx);
    }

    for (int pixmapNdx = 0; pixmapNdx < pixmapCount; pixmapNdx++)
    {
        const int ndx = rnd.getInt(0, (int)(m_sharedNativePixmaps.size() - 1));

        pixmaps.push_back(m_sharedNativePixmaps[ndx]);
        m_sharedNativePixmaps.erase(m_sharedNativePixmaps.begin() + ndx);
    }

    for (int windowNdx = 0; windowNdx < windowCount; windowNdx++)
    {
        const int ndx = rnd.getInt(0, (int)(m_sharedNativeWindows.size() - 1));

        windows.push_back(m_sharedNativeWindows[ndx]);
        m_sharedNativeWindows.erase(m_sharedNativeWindows.begin() + ndx);
    }

    for (int contextNdx = 0; contextNdx < contextCount; contextNdx++)
    {
        const int ndx = rnd.getInt(0, (int)(m_sharedContexts.size() - 1));

        contexts.push_back(m_sharedContexts[ndx]);
        m_sharedContexts.erase(m_sharedContexts.begin() + ndx);
    }
}

void MultiThreadedObjectTest::querySetSharedObjects(TestThread &thread, int count)
{
    const Library &egl = getLibrary();
    de::Random &rnd    = (thread.getId() == 0 ? m_rnd0 : m_rnd1);
    vector<Type> objectTypes;

    if ((m_types & TYPE_PBUFFER) != 0)
        objectTypes.push_back(TYPE_PBUFFER);

    if ((m_types & TYPE_PIXMAP) != 0)
        objectTypes.push_back(TYPE_PIXMAP);

    if (!m_sharedNativeWindows.empty() && (m_types & TYPE_WINDOW) != 0)
        objectTypes.push_back(TYPE_WINDOW);

    if ((m_types & TYPE_CONTEXT) != 0)
        objectTypes.push_back(TYPE_CONTEXT);

    for (int queryNdx = 0; queryNdx < count; queryNdx++)
    {
        const Type type    = rnd.choose<Type>(objectTypes.begin(), objectTypes.end());
        EGLSurface surface = EGL_NO_SURFACE;
        EGLContext context = EGL_NO_CONTEXT;

        switch (type)
        {
        case TYPE_PBUFFER:
            surface = m_sharedPbuffers[rnd.getInt(0, (int)(m_sharedPbuffers.size() - 1))];
            break;

        case TYPE_PIXMAP:
            surface = m_sharedNativePixmaps[rnd.getInt(0, (int)(m_sharedNativePixmaps.size() - 1))].second;
            break;

        case TYPE_WINDOW:
            surface = m_sharedNativeWindows[rnd.getInt(0, (int)(m_sharedNativeWindows.size() - 1))].second;
            break;

        case TYPE_CONTEXT:
            context = m_sharedContexts[rnd.getInt(0, (int)(m_sharedContexts.size() - 1))];
            break;

        default:
            DE_ASSERT(false);
        }

        if (surface != EGL_NO_SURFACE)
        {
            static const EGLint queryAttributes[] = {EGL_LARGEST_PBUFFER, EGL_HEIGHT, EGL_WIDTH};

            const EGLint attribute = queryAttributes[rnd.getInt(0, DE_LENGTH_OF_ARRAY(queryAttributes) - 1)];
            EGLBoolean result;
            EGLint value;

            result = egl.querySurface(m_display, surface, attribute, &value);
            thread.getLog() << ThreadLog::BeginMessage << result << " = eglQuerySurface(" << m_display << ", "
                            << surface << ", " << attribute << ", " << value << ")" << ThreadLog::EndMessage;
            EGLU_CHECK_MSG(egl, "eglQuerySurface()");
        }
        else if (context != EGL_NO_CONTEXT)
        {
            static const EGLint attributes[] = {EGL_CONFIG_ID, EGL_CONTEXT_CLIENT_TYPE, EGL_CONTEXT_CLIENT_VERSION,
                                                EGL_RENDER_BUFFER};

            const EGLint attribute = attributes[rnd.getInt(0, DE_LENGTH_OF_ARRAY(attributes) - 1)];
            EGLint value;
            EGLBoolean result;

            result = egl.queryContext(m_display, context, attribute, &value);
            thread.getLog() << ThreadLog::BeginMessage << result << " = eglQueryContext(" << m_display << ", "
                            << context << ", " << attribute << ", " << value << ")" << ThreadLog::EndMessage;
            EGLU_CHECK_MSG(egl, "eglQueryContext()");
        }
        else
            DE_ASSERT(false);
    }
}

void MultiThreadedObjectTest::destroyObjects(TestThread &thread)
{
    const Library &egl           = getLibrary();
    vector<EGLSurface> &pbuffers = (thread.getId() == 0 ? m_pbuffers0 : m_pbuffers1);
    vector<pair<eglu::NativeWindow *, EGLSurface>> &windows =
        (thread.getId() == 0 ? m_nativeWindows0 : m_nativeWindows1);
    vector<pair<eglu::NativePixmap *, EGLSurface>> &pixmaps =
        (thread.getId() == 0 ? m_nativePixmaps0 : m_nativePixmaps1);
    vector<EGLContext> &contexts = (thread.getId() == 0 ? m_contexts0 : m_contexts1);

    for (int pbufferNdx = 0; pbufferNdx < (int)pbuffers.size(); pbufferNdx++)
    {
        if (pbuffers[pbufferNdx] != EGL_NO_SURFACE)
        {
            // Destroy EGLSurface
            EGLBoolean result;

            result = egl.destroySurface(m_display, pbuffers[pbufferNdx]);
            thread.getLog() << ThreadLog::BeginMessage << result << " = eglDestroySurface(" << m_display << ", "
                            << pbuffers[pbufferNdx] << ")" << ThreadLog::EndMessage;
            EGLU_CHECK_MSG(egl, "eglDestroySurface()");
            pbuffers[pbufferNdx] = EGL_NO_SURFACE;
        }
    }
    pbuffers.clear();

    for (int windowNdx = 0; windowNdx < (int)windows.size(); windowNdx++)
    {
        if (windows[windowNdx].second != EGL_NO_SURFACE)
        {
            thread.getLog() << ThreadLog::BeginMessage << "eglDestroySurface(" << m_display << ", "
                            << windows[windowNdx].second << ")" << ThreadLog::EndMessage;
            EGLU_CHECK_CALL(egl, destroySurface(m_display, windows[windowNdx].second));
            windows[windowNdx].second = EGL_NO_SURFACE;
        }

        if (windows[windowNdx].first)
        {
            delete windows[windowNdx].first;
            windows[windowNdx].first = NULL;
        }
    }
    windows.clear();

    for (int pixmapNdx = 0; pixmapNdx < (int)pixmaps.size(); pixmapNdx++)
    {
        if (pixmaps[pixmapNdx].first != EGL_NO_SURFACE)
        {
            thread.getLog() << ThreadLog::BeginMessage << "eglDestroySurface(" << m_display << ", "
                            << pixmaps[pixmapNdx].second << ")" << ThreadLog::EndMessage;
            EGLU_CHECK_CALL(egl, destroySurface(m_display, pixmaps[pixmapNdx].second));
            pixmaps[pixmapNdx].second = EGL_NO_SURFACE;
        }

        if (pixmaps[pixmapNdx].first)
        {
            delete pixmaps[pixmapNdx].first;
            pixmaps[pixmapNdx].first = NULL;
        }
    }
    pixmaps.clear();

    for (int contextNdx = 0; contextNdx < (int)contexts.size(); contextNdx++)
    {
        if (contexts[contextNdx] != EGL_NO_CONTEXT)
        {
            EGLU_CHECK_CALL(egl, destroyContext(m_display, contexts[contextNdx]));
            thread.getLog() << ThreadLog::BeginMessage << "eglDestroyContext(" << m_display << ", "
                            << contexts[contextNdx] << ")" << ThreadLog::EndMessage;
            contexts[contextNdx] = EGL_NO_CONTEXT;
        }
    }
    contexts.clear();
}

MultiThreadedTests::MultiThreadedTests(EglTestContext &context)
    : TestCaseGroup(context, "multithread", "Multithreaded EGL tests")
{
}

void MultiThreadedTests::init(void)
{
    // Config tests
    addChild(new MultiThreadedConfigTest(m_eglTestCtx, "config", "", 30, 30, 30));

    // Object tests
    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pbuffer", "", MultiThreadedObjectTest::TYPE_PBUFFER));
    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pixmap", "", MultiThreadedObjectTest::TYPE_PIXMAP));
    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "window", "", MultiThreadedObjectTest::TYPE_WINDOW));
    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "single_window", "",
                                         MultiThreadedObjectTest::TYPE_WINDOW |
                                             MultiThreadedObjectTest::TYPE_SINGLE_WINDOW));
    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "context", "", MultiThreadedObjectTest::TYPE_CONTEXT));

    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pbuffer_pixmap", "",
                                         MultiThreadedObjectTest::TYPE_PBUFFER | MultiThreadedObjectTest::TYPE_PIXMAP));
    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pbuffer_window", "",
                                         MultiThreadedObjectTest::TYPE_PBUFFER | MultiThreadedObjectTest::TYPE_WINDOW));
    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pbuffer_single_window", "",
                                         MultiThreadedObjectTest::TYPE_PBUFFER | MultiThreadedObjectTest::TYPE_WINDOW |
                                             MultiThreadedObjectTest::TYPE_SINGLE_WINDOW));
    addChild(
        new MultiThreadedObjectTest(m_eglTestCtx, "pbuffer_context", "",
                                    MultiThreadedObjectTest::TYPE_PBUFFER | MultiThreadedObjectTest::TYPE_CONTEXT));

    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pixmap_window", "",
                                         MultiThreadedObjectTest::TYPE_PIXMAP | MultiThreadedObjectTest::TYPE_WINDOW));
    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pixmap_single_window", "",
                                         MultiThreadedObjectTest::TYPE_PIXMAP | MultiThreadedObjectTest::TYPE_WINDOW |
                                             MultiThreadedObjectTest::TYPE_SINGLE_WINDOW));
    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pixmap_context", "",
                                         MultiThreadedObjectTest::TYPE_PIXMAP | MultiThreadedObjectTest::TYPE_CONTEXT));

    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "window_context", "",
                                         MultiThreadedObjectTest::TYPE_WINDOW | MultiThreadedObjectTest::TYPE_CONTEXT));
    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "single_window_context", "",
                                         MultiThreadedObjectTest::TYPE_WINDOW |
                                             MultiThreadedObjectTest::TYPE_SINGLE_WINDOW |
                                             MultiThreadedObjectTest::TYPE_CONTEXT));

    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pbuffer_pixmap_window", "",
                                         MultiThreadedObjectTest::TYPE_PBUFFER | MultiThreadedObjectTest::TYPE_PIXMAP |
                                             MultiThreadedObjectTest::TYPE_WINDOW));
    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pbuffer_pixmap_single_window", "",
                                         MultiThreadedObjectTest::TYPE_PBUFFER | MultiThreadedObjectTest::TYPE_PIXMAP |
                                             MultiThreadedObjectTest::TYPE_WINDOW |
                                             MultiThreadedObjectTest::TYPE_SINGLE_WINDOW));
    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pbuffer_pixmap_context", "",
                                         MultiThreadedObjectTest::TYPE_PBUFFER | MultiThreadedObjectTest::TYPE_PIXMAP |
                                             MultiThreadedObjectTest::TYPE_CONTEXT));

    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pbuffer_window_context", "",
                                         MultiThreadedObjectTest::TYPE_PBUFFER | MultiThreadedObjectTest::TYPE_WINDOW |
                                             MultiThreadedObjectTest::TYPE_CONTEXT));
    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pbuffer_single_window_context", "",
                                         MultiThreadedObjectTest::TYPE_PBUFFER | MultiThreadedObjectTest::TYPE_WINDOW |
                                             MultiThreadedObjectTest::TYPE_SINGLE_WINDOW |
                                             MultiThreadedObjectTest::TYPE_CONTEXT));

    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pixmap_window_context", "",
                                         MultiThreadedObjectTest::TYPE_PIXMAP | MultiThreadedObjectTest::TYPE_WINDOW |
                                             MultiThreadedObjectTest::TYPE_CONTEXT));
    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pixmap_single_window_context", "",
                                         MultiThreadedObjectTest::TYPE_PIXMAP | MultiThreadedObjectTest::TYPE_WINDOW |
                                             MultiThreadedObjectTest::TYPE_SINGLE_WINDOW |
                                             MultiThreadedObjectTest::TYPE_CONTEXT));

    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pbuffer_pixmap_window_context", "",
                                         MultiThreadedObjectTest::TYPE_PBUFFER | MultiThreadedObjectTest::TYPE_PIXMAP |
                                             MultiThreadedObjectTest::TYPE_WINDOW |
                                             MultiThreadedObjectTest::TYPE_CONTEXT));
    addChild(new MultiThreadedObjectTest(m_eglTestCtx, "pbuffer_pixmap_single_window_context", "",
                                         MultiThreadedObjectTest::TYPE_PBUFFER | MultiThreadedObjectTest::TYPE_PIXMAP |
                                             MultiThreadedObjectTest::TYPE_WINDOW |
                                             MultiThreadedObjectTest::TYPE_SINGLE_WINDOW |
                                             MultiThreadedObjectTest::TYPE_CONTEXT));
}

} // namespace egl
} // namespace deqp
