//
// Copyright 2018 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// MulithreadingTest.cpp : Tests of multithreaded rendering

#include "test_utils/ANGLETest.h"

#include "test_utils/gl_raii.h"

#include <mutex>
#include <thread>

namespace angle
{

class MultithreadingTest : public ANGLETest
{
  protected:
    MultithreadingTest()
    {
        setWindowWidth(128);
        setWindowHeight(128);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
        setContextVirtualization(false);
    }

    bool platformSupportsMultithreading() const { return IsOpenGL() && IsWindows(); }
};

// Test that it's possible to make one context current on different threads
TEST_P(MultithreadingTest, MakeCurrentSingleContext)
{
    ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());

    std::mutex mutex;

    EGLWindow *window  = getEGLWindow();
    EGLDisplay dpy     = window->getDisplay();
    EGLContext ctx     = window->getContext();
    EGLSurface surface = window->getSurface();

    EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
    EXPECT_EGL_SUCCESS();

    constexpr size_t kThreadCount = 16;
    std::array<std::thread, kThreadCount> threads;
    for (std::thread &thread : threads)
    {
        thread = std::thread([&]() {
            std::lock_guard<decltype(mutex)> lock(mutex);

            EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
            EXPECT_EGL_SUCCESS();

            EXPECT_EGL_TRUE(eglSwapBuffers(dpy, surface));
            EXPECT_EGL_SUCCESS();

            EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
            EXPECT_EGL_SUCCESS();
        });
    }

    for (std::thread &thread : threads)
    {
        thread.join();
    }

    EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
    EXPECT_EGL_SUCCESS();
}

// Test that it's possible to make one multiple contexts current on different threads simultaneously
TEST_P(MultithreadingTest, MakeCurrentMultiContext)
{
    ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
    ANGLE_SKIP_TEST_IF(IsWindows() && IsOpenGL() && IsAMD());

    std::mutex mutex;

    EGLWindow *window = getEGLWindow();
    EGLDisplay dpy    = window->getDisplay();
    EGLConfig config  = window->getConfig();

    constexpr size_t kThreadCount         = 16;
    constexpr size_t kIterationsPerThread = 16;

    constexpr EGLint kPBufferSize = 256;

    std::array<std::thread, kThreadCount> threads;
    for (size_t thread = 0; thread < kThreadCount; thread++)
    {
        threads[thread] = std::thread([&, thread]() {
            EGLSurface pbuffer= EGL_NO_SURFACE;
            EGLConfig ctx= EGL_NO_CONTEXT;

            {
                std::lock_guard<decltype(mutex)> lock(mutex);

                // Initialize the pbuffer and context
                EGLint pbufferAttributes[] = {
                    EGL_WIDTH,          kPBufferSize,     EGL_HEIGHT,         kPBufferSize,
                    EGL_NONE,           EGL_NONE,
                };
                pbuffer = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
                EXPECT_EGL_SUCCESS();

                ctx = window->createContext(EGL_NO_CONTEXT);
                EXPECT_NE(EGL_NO_CONTEXT, ctx);

                EXPECT_EGL_TRUE(eglMakeCurrent(dpy, pbuffer, pbuffer, ctx));
                EXPECT_EGL_SUCCESS();
            }

            for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
            {
                std::lock_guard<decltype(mutex)> lock(mutex);

                // Base the clear color on the thread and iteration indexes so every clear color is
                // unique
                const GLColor color(static_cast<GLubyte>(thread), static_cast<GLubyte>(iteration),
                                    0, 255);
                const angle::Vector4 floatColor = color.toNormalizedVector();

                glClearColor(floatColor[0], floatColor[1], floatColor[2], floatColor[3]);
                EXPECT_GL_NO_ERROR();

                glClear(GL_COLOR_BUFFER_BIT);
                EXPECT_GL_NO_ERROR();

                EXPECT_PIXEL_COLOR_EQ(0, 0, color);
            }

            {
                std::lock_guard<decltype(mutex)> lock(mutex);

                // Clean up
                EXPECT_EGL_TRUE(
                    eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
                EXPECT_EGL_SUCCESS();

                eglDestroySurface(dpy, pbuffer);
                eglDestroyContext(dpy, ctx);
            }
        });
    }

    for (std::thread &thread : threads)
    {
        thread.join();
    }
}

// TODO(geofflang): Test sharing a program between multiple shared contexts on multiple threads

ANGLE_INSTANTIATE_TEST(MultithreadingTest,
                       ES2_OPENGL(),
                       ES3_OPENGL(),
                       ES2_OPENGLES(),
                       ES3_OPENGLES());

}  // namespace angle
