Edit

kc3-lang/angle/src/tests/gl_tests/MultithreadingTest.cpp

Branch :

  • Show log

    Commit

  • Author : Ian Elliott
    Date : 2021-08-25 14:50:49
    Hash : 39e58580
    Message : Move some end2end suppressions to expectations file With 2 previous Wrangler-suppression CLs, I followed the pattern initially done for bug 5945. Jamie pointed out that these are better handled in the expectations file. This CL moves all 3 suppressions. Bug: angleproject:5945 Bug: angleproject:6318 Change-Id: Ib70c3ede4c1fcd92cfd7dc65f2b37e221380fefe Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3119683 Reviewed-by: Ian Elliott <ianelliott@google.com> Reviewed-by: Jamie Madill <jmadill@chromium.org> Commit-Queue: Ian Elliott <ianelliott@google.com>

  • src/tests/gl_tests/MultithreadingTest.cpp
  • //
    // 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 "platform/FeaturesVk.h"
    #include "test_utils/ANGLETest.h"
    #include "test_utils/gl_raii.h"
    #include "util/EGLWindow.h"
    
    #include <atomic>
    #include <mutex>
    #include <thread>
    
    namespace angle
    {
    
    class MultithreadingTest : public ANGLETest
    {
      public:
        static constexpr uint32_t kSize = 512;
    
      protected:
        MultithreadingTest()
        {
            setWindowWidth(kSize);
            setWindowHeight(kSize);
            setConfigRedBits(8);
            setConfigGreenBits(8);
            setConfigBlueBits(8);
            setConfigAlphaBits(8);
        }
    
        bool hasFenceSyncExtension() const
        {
            return IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(), "EGL_KHR_fence_sync");
        }
        bool hasGLSyncExtension() const { return IsGLExtensionEnabled("GL_OES_EGL_sync"); }
    
        void runMultithreadedGLTest(
            std::function<void(EGLSurface surface, size_t threadIndex)> testBody,
            size_t threadCount)
        {
            std::mutex mutex;
    
            EGLWindow *window = getEGLWindow();
            EGLDisplay dpy    = window->getDisplay();
            EGLConfig config  = window->getConfig();
    
            constexpr EGLint kPBufferSize = 256;
    
            std::vector<std::thread> threads(threadCount);
            for (size_t threadIdx = 0; threadIdx < threadCount; threadIdx++)
            {
                threads[threadIdx] = std::thread([&, threadIdx]() {
                    EGLSurface surface = EGL_NO_SURFACE;
                    EGLContext 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,
                        };
                        surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
                        EXPECT_EGL_SUCCESS();
    
                        ctx = window->createContext(EGL_NO_CONTEXT);
                        EXPECT_NE(EGL_NO_CONTEXT, ctx);
    
                        EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
                        EXPECT_EGL_SUCCESS();
                    }
    
                    testBody(surface, threadIdx);
    
                    {
                        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, surface);
                        eglDestroyContext(dpy, ctx);
                    }
                });
            }
    
            for (std::thread &thread : threads)
            {
                thread.join();
            }
        }
    };
    
    class MultithreadingTestES3 : public MultithreadingTest
    {
      public:
        void textureThreadFunction(bool useDraw);
        void mainThreadDraw(bool useDraw);
    
      protected:
        MultithreadingTestES3()
            : mTexture2D(0), mExitThread(false), mMainThreadSyncObj(NULL), mSecondThreadSyncObj(NULL)
        {
            setWindowWidth(kSize);
            setWindowHeight(kSize);
            setConfigRedBits(8);
            setConfigGreenBits(8);
            setConfigBlueBits(8);
            setConfigAlphaBits(8);
        }
    
        GLuint create2DTexture()
        {
            GLuint texture2D;
            glGenTextures(1, &texture2D);
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, texture2D);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                         nullptr);
            EXPECT_GL_NO_ERROR();
            return texture2D;
        }
    
        void testSetUp() override { mTexture2D = create2DTexture(); }
    
        void testTearDown() override
        {
            if (mTexture2D)
            {
                glDeleteTextures(1, &mTexture2D);
            }
        }
    
        std::mutex mutex;
        GLuint mTexture2D;
        std::atomic<bool> mExitThread;
        std::atomic<bool> mDrawGreen;  // Toggle drawing green or red
        std::atomic<GLsync> mMainThreadSyncObj;
        std::atomic<GLsync> mSecondThreadSyncObj;
    };
    
    // 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 multiple threads can clear and readback pixels successfully at the same time
    TEST_P(MultithreadingTest, MultiContextClear)
    {
        ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
    
        auto testBody = [](EGLSurface surface, size_t thread) {
            constexpr size_t kIterationsPerThread = 32;
            for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
            {
                // Base the clear color on the thread and iteration indexes so every clear color is
                // unique
                const GLColor color(static_cast<GLubyte>(thread % 255),
                                    static_cast<GLubyte>(iteration % 255), 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);
            }
        };
        runMultithreadedGLTest(testBody, 72);
    }
    
    // Verify that threads can interleave eglDestroyContext and draw calls without
    // any crashes.
    TEST_P(MultithreadingTest, MultiContextDeleteDraw)
    {
        // Skip this test on non-D3D11 backends, as it has the potential to time-out
        // and this test was originally intended to catch a crash on the D3D11 backend.
        ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
        ANGLE_SKIP_TEST_IF(!IsD3D11());
    
        EGLWindow *window = getEGLWindow();
        EGLDisplay dpy    = window->getDisplay();
        EGLConfig config  = window->getConfig();
    
        std::thread t1 = std::thread([&]() {
            // 5000 is chosen here as it reliably reproduces the former crash.
            for (int i = 0; i < 5000; i++)
            {
                EGLContext ctx1 = window->createContext(EGL_NO_CONTEXT);
                EGLContext ctx2 = window->createContext(EGL_NO_CONTEXT);
    
                EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx2));
                EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx1));
    
                EXPECT_EGL_TRUE(eglDestroyContext(dpy, ctx2));
                EXPECT_EGL_TRUE(eglDestroyContext(dpy, ctx1));
            }
        });
    
        std::thread t2 = std::thread([&]() {
            EGLint pbufferAttributes[] = {
                EGL_WIDTH, 256, EGL_HEIGHT, 256, EGL_NONE, EGL_NONE,
            };
    
            EGLSurface surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
            EXPECT_EGL_SUCCESS();
    
            auto ctx = window->createContext(EGL_NO_CONTEXT);
            EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
    
            constexpr size_t kIterationsPerThread = 512;
            constexpr size_t kDrawsPerIteration   = 512;
    
            ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
            glUseProgram(program);
    
            GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
    
            auto quadVertices = GetQuadVertices();
    
            GLBuffer vertexBuffer;
            glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
            glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(), GL_STATIC_DRAW);
    
            GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
            glEnableVertexAttribArray(positionLocation);
            glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
            for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
            {
                const GLColor color(static_cast<GLubyte>(15151 % 255),
                                    static_cast<GLubyte>(iteration % 255), 0, 255);
                const angle::Vector4 floatColor = color.toNormalizedVector();
                glUniform4fv(colorLocation, 1, floatColor.data());
                for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
                {
                    EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
                    glDrawArrays(GL_TRIANGLES, 0, 6);
                }
            }
        });
    
        t1.join();
        t2.join();
    }
    
    // Test that multiple threads can draw and readback pixels successfully at the same time
    TEST_P(MultithreadingTest, MultiContextDraw)
    {
        ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
    
        ANGLE_SKIP_TEST_IF(isSwiftshader());
    
        auto testBody = [](EGLSurface surface, size_t thread) {
            constexpr size_t kIterationsPerThread = 32;
            constexpr size_t kDrawsPerIteration   = 500;
    
            ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
            glUseProgram(program);
    
            GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
    
            auto quadVertices = GetQuadVertices();
    
            GLBuffer vertexBuffer;
            glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
            glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(), GL_STATIC_DRAW);
    
            GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
            glEnableVertexAttribArray(positionLocation);
            glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
    
            for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
            {
                // Base the clear color on the thread and iteration indexes so every clear color is
                // unique
                const GLColor color(static_cast<GLubyte>(thread % 255),
                                    static_cast<GLubyte>(iteration % 255), 0, 255);
                const angle::Vector4 floatColor = color.toNormalizedVector();
                glUniform4fv(colorLocation, 1, floatColor.data());
    
                for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
                {
                    glDrawArrays(GL_TRIANGLES, 0, 6);
                }
    
                EXPECT_PIXEL_COLOR_EQ(0, 0, color);
            }
        };
        runMultithreadedGLTest(testBody, 4);
    }
    
    // Test that multiple threads can draw and read back pixels correctly.
    // Using eglSwapBuffers stresses race conditions around use of QueueSerials.
    TEST_P(MultithreadingTest, MultiContextDrawWithSwapBuffers)
    {
        ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
    
        // http://anglebug.com/5099
        ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());
    
        EGLWindow *window = getEGLWindow();
        EGLDisplay dpy    = window->getDisplay();
    
        auto testBody = [dpy](EGLSurface surface, size_t thread) {
            constexpr size_t kIterationsPerThread = 100;
            constexpr size_t kDrawsPerIteration   = 10;
    
            ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
            glUseProgram(program);
    
            GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
    
            auto quadVertices = GetQuadVertices();
    
            GLBuffer vertexBuffer;
            glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
            glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(), GL_STATIC_DRAW);
    
            GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
            glEnableVertexAttribArray(positionLocation);
            glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
    
            for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
            {
                // Base the clear color on the thread and iteration indexes so every clear color is
                // unique
                const GLColor color(static_cast<GLubyte>(thread % 255),
                                    static_cast<GLubyte>(iteration % 255), 0, 255);
                const angle::Vector4 floatColor = color.toNormalizedVector();
                glUniform4fv(colorLocation, 1, floatColor.data());
    
                for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
                {
                    glDrawArrays(GL_TRIANGLES, 0, 6);
                }
    
                EXPECT_EGL_TRUE(eglSwapBuffers(dpy, surface));
                EXPECT_EGL_SUCCESS();
    
                EXPECT_PIXEL_COLOR_EQ(0, 0, color);
            }
        };
        runMultithreadedGLTest(testBody, 32);
    }
    
    // Test that ANGLE handles multiple threads creating and destroying resources (vertex buffer in this
    // case). Disable defer_flush_until_endrenderpass so that glFlush will issue work to GPU in order to
    // maximize the chance we resources can be destroyed at the wrong time.
    TEST_P(MultithreadingTest, MultiContextCreateAndDeleteResources)
    {
        ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
    
        EGLWindow *window = getEGLWindow();
        EGLDisplay dpy    = window->getDisplay();
    
        auto testBody = [dpy](EGLSurface surface, size_t thread) {
            constexpr size_t kIterationsPerThread = 32;
            constexpr size_t kDrawsPerIteration   = 1;
    
            ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
            glUseProgram(program);
    
            GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
    
            auto quadVertices = GetQuadVertices();
    
            for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
            {
                GLBuffer vertexBuffer;
                glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
                glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(),
                             GL_STATIC_DRAW);
    
                GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
                glEnableVertexAttribArray(positionLocation);
                glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
    
                // Base the clear color on the thread and iteration indexes so every clear color is
                // unique
                const GLColor color(static_cast<GLubyte>(thread % 255),
                                    static_cast<GLubyte>(iteration % 255), 0, 255);
                const angle::Vector4 floatColor = color.toNormalizedVector();
                glUniform4fv(colorLocation, 1, floatColor.data());
    
                for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
                {
                    glDrawArrays(GL_TRIANGLES, 0, 6);
                }
    
                EXPECT_EGL_TRUE(eglSwapBuffers(dpy, surface));
                EXPECT_EGL_SUCCESS();
    
                EXPECT_PIXEL_COLOR_EQ(0, 0, color);
            }
            glFinish();
        };
        runMultithreadedGLTest(testBody, 32);
    }
    
    TEST_P(MultithreadingTest, MultiCreateContext)
    {
        // Supported by CGL, GLX, and WGL (https://anglebug.com/4725)
        // Not supported on Ozone (https://crbug.com/1103009)
        ANGLE_SKIP_TEST_IF(!(IsWindows() || IsLinux() || IsOSX()) || IsOzone());
    
        EGLWindow *window  = getEGLWindow();
        EGLDisplay dpy     = window->getDisplay();
        EGLContext ctx     = window->getContext();
        EGLSurface surface = window->getSurface();
    
        // Un-makeCurrent the test window's context
        EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
        EXPECT_EGL_SUCCESS();
    
        constexpr size_t kThreadCount = 16;
        std::atomic<uint32_t> barrier(0);
        std::vector<std::thread> threads(kThreadCount);
        std::vector<EGLContext> contexts(kThreadCount);
        for (size_t threadIdx = 0; threadIdx < kThreadCount; threadIdx++)
        {
            threads[threadIdx] = std::thread([&, threadIdx]() {
                contexts[threadIdx] = EGL_NO_CONTEXT;
                {
                    contexts[threadIdx] = window->createContext(EGL_NO_CONTEXT);
                    EXPECT_NE(EGL_NO_CONTEXT, contexts[threadIdx]);
    
                    barrier++;
                }
    
                while (barrier < kThreadCount)
                {
                }
    
                {
                    EXPECT_TRUE(eglDestroyContext(dpy, contexts[threadIdx]));
                }
            });
        }
    
        for (std::thread &thread : threads)
        {
            thread.join();
        }
    
        // Re-make current the test window's context for teardown.
        EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
        EXPECT_EGL_SUCCESS();
    }
    
    void MultithreadingTestES3::textureThreadFunction(bool useDraw)
    {
        EGLWindow *window  = getEGLWindow();
        EGLDisplay dpy     = window->getDisplay();
        EGLConfig config   = window->getConfig();
        EGLSurface surface = EGL_NO_SURFACE;
        EGLContext ctx     = EGL_NO_CONTEXT;
    
        // Initialize the pbuffer and context
        EGLint pbufferAttributes[] = {
            EGL_WIDTH, kSize, EGL_HEIGHT, kSize, EGL_NONE, EGL_NONE,
        };
        surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
        EXPECT_EGL_SUCCESS();
        EXPECT_NE(EGL_NO_SURFACE, surface);
    
        ctx = window->createContext(window->getContext());
        EXPECT_NE(EGL_NO_CONTEXT, ctx);
    
        EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
        EXPECT_EGL_SUCCESS();
    
        std::vector<GLColor> greenColor(kSize * kSize, GLColor::green);
        std::vector<GLColor> redColor(kSize * kSize, GLColor::red);
        ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
        ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    
        glBindTexture(GL_TEXTURE_2D, mTexture2D);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        ASSERT_GL_NO_ERROR();
    
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture2D, 0);
        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    
        mSecondThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
        ASSERT_GL_NO_ERROR();
        // Force the fence to be created
        glFlush();
    
        // Draw something
        while (!mExitThread)
        {
            std::lock_guard<decltype(mutex)> lock(mutex);
    
            if (mMainThreadSyncObj != nullptr)
            {
                glWaitSync(mMainThreadSyncObj, 0, GL_TIMEOUT_IGNORED);
                ASSERT_GL_NO_ERROR();
                glDeleteSync(mSecondThreadSyncObj);
                ASSERT_GL_NO_ERROR();
                mMainThreadSyncObj = nullptr;
            }
            else
            {
                continue;
            }
    
            glBindTexture(GL_TEXTURE_2D, mTexture2D);
            ASSERT_GL_NO_ERROR();
    
            if (mDrawGreen)
            {
                if (useDraw)
                {
                    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
                    drawQuad(greenProgram.get(), std::string(essl1_shaders::PositionAttrib()), 0.0f);
                }
                else
                {
                    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                                 greenColor.data());
                }
                ASSERT_GL_NO_ERROR();
            }
            else
            {
                if (useDraw)
                {
                    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
                    drawQuad(redProgram.get(), std::string(essl1_shaders::PositionAttrib()), 0.0f);
                }
                else
                {
                    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                                 redColor.data());
                }
                ASSERT_GL_NO_ERROR();
            }
    
            ASSERT_EQ(mSecondThreadSyncObj.load(), nullptr);
            mSecondThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
            ASSERT_GL_NO_ERROR();
            // Force the fence to be created
            glFlush();
    
            mDrawGreen = !mDrawGreen;
        }
    
        // Clean up
        EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
        EXPECT_EGL_SUCCESS();
    
        eglDestroySurface(dpy, surface);
        eglDestroyContext(dpy, ctx);
    }
    
    // Test fence sync with multiple threads drawing
    void MultithreadingTestES3::mainThreadDraw(bool useDraw)
    {
        ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
    
        EGLWindow *window  = getEGLWindow();
        EGLDisplay dpy     = window->getDisplay();
        EGLContext ctx     = window->getContext();
        EGLSurface surface = window->getSurface();
        // Use odd numbers so we bounce between red and green in the final image
        constexpr int kNumIterations = 5;
        constexpr int kNumDraws      = 5;
    
        std::thread textureThread(&MultithreadingTestES3::textureThreadFunction, this, true);
    
        ANGLE_GL_PROGRAM(texProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
    
        for (int iterations = 0; iterations < kNumIterations; ++iterations)
        {
            for (int draws = 0; draws < kNumDraws;)
            {
                std::lock_guard<decltype(mutex)> lock(mutex);
    
                if (mSecondThreadSyncObj != nullptr)
                {
                    glWaitSync(mSecondThreadSyncObj, 0, GL_TIMEOUT_IGNORED);
                    ASSERT_GL_NO_ERROR();
                    glDeleteSync(mSecondThreadSyncObj);
                    ASSERT_GL_NO_ERROR();
                    mSecondThreadSyncObj = nullptr;
                }
                else
                {
                    continue;
                }
    
                glBindFramebuffer(GL_FRAMEBUFFER, 0);
                glBindTexture(GL_TEXTURE_2D, mTexture2D);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                glUseProgram(texProgram);
                drawQuad(texProgram.get(), std::string(essl1_shaders::PositionAttrib()), 0.0f);
    
                ASSERT_EQ(mMainThreadSyncObj.load(), nullptr);
                mMainThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
                ASSERT_GL_NO_ERROR();
                // Force the fence to be created
                glFlush();
    
                ++draws;
            }
    
            ASSERT_GL_NO_ERROR();
            swapBuffers();
        }
    
        mExitThread = true;
        textureThread.join();
    
        ASSERT_GL_NO_ERROR();
        GLColor color;
        if (mDrawGreen)
        {
            color = GLColor::green;
        }
        else
        {
            color = GLColor::red;
        }
        EXPECT_PIXEL_RECT_EQ(0, 0, kSize, kSize, color);
    
        // Re-make current the test window's context for teardown.
        EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
        EXPECT_EGL_SUCCESS();
    }
    
    // Test that glFenceSync/glWaitSync works correctly with multithreading.
    // Main thread: Samples from the shared texture to draw to the default FBO.
    // Secondary (Texture) thread: Draws to the shared texture, which the Main thread samples from.
    // The overall execution flow is:
    // Main Thread:
    // 1. Wait for the mSecondThreadSyncObj fence object to be created.
    //    - This fence object is used by synchronize access to the shared texture by indicating that the
    //    Secondary thread's draws to the texture have all completed and it's now safe to sample from
    //    it.
    // 2. Once the fence is created, add a glWaitSync(mSecondThreadSyncObj) to the command stream and
    //    then delete it.
    // 3. Draw, sampling from the shared texture.
    // 4. Create a new mMainThreadSyncObj.
    //    - This fence object is used to synchronize access to the shared texture by indicating that the
    //    Main thread's draws are no longer sampling from the texture, so it's now safe for the
    //    Secondary thread to draw to it again with a new color.
    // Secondary (Texture) Thread:
    // 1. Wait for the mMainThreadSyncObj fence object to be created.
    // 2. Once the fence is created, add a glWaitSync(mMainThreadSyncObj) to the command stream and then
    //    delete it.
    // 3. Draw/Fill the texture.
    // 4. Create a new mSecondThreadSyncObj.
    //
    // These threads loop for the specified number of iterations, drawing/sampling the shared texture
    // with the necessary glFlush()s and occasional eglSwapBuffers() to mimic a real multithreaded GLES
    // application.
    TEST_P(MultithreadingTestES3, MultithreadFenceDraw)
    {
        // http://anglebug.com/5418
        ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel() && IsVulkan());
    
        // Have the secondary thread use glDrawArrays()
        mainThreadDraw(true);
    }
    
    // Same as MultithreadFenceDraw, but with the secondary thread using glTexImage2D rather than
    // glDrawArrays.
    TEST_P(MultithreadingTestES3, MultithreadFenceTexImage)
    {
        // http://anglebug.com/5418
        ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel() && IsVulkan());
    
        // http://anglebug.com/5439
        ANGLE_SKIP_TEST_IF(IsLinux() && isSwiftshader());
    
        // Have the secondary thread use glTexImage2D()
        mainThreadDraw(false);
    }
    
    // Test that waiting on a sync object that hasn't been flushed and without a current context returns
    // TIMEOUT_EXPIRED or CONDITION_SATISFIED, but doesn't generate an error or crash.
    TEST_P(MultithreadingTest, NoFlushNoContextReturnsTimeout)
    {
        ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
        ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
    
        std::mutex mutex;
    
        EGLWindow *window = getEGLWindow();
        EGLDisplay dpy    = window->getDisplay();
    
        glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
    
        EGLSyncKHR sync = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, nullptr);
        EXPECT_NE(sync, EGL_NO_SYNC_KHR);
    
        std::thread thread = std::thread([&]() {
            std::lock_guard<decltype(mutex)> lock(mutex);
            // Make sure there is no active context on this thread.
            EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
            EXPECT_EGL_SUCCESS();
            // Don't wait forever to make sure the test terminates
            constexpr GLuint64 kTimeout = 1'000'000'000;  // 1 second
            int result                  = eglClientWaitSyncKHR(dpy, sync, 0, kTimeout);
            // We typically expect to get back TIMEOUT_EXPIRED since the sync object was never flushed.
            // However, the OpenGL ES backend returns CONDITION_SATISFIED, which is also a passing
            // result.
            ASSERT_TRUE(result == EGL_TIMEOUT_EXPIRED_KHR || result == EGL_CONDITION_SATISFIED_KHR);
        });
    
        thread.join();
    
        EXPECT_EGL_TRUE(eglDestroySyncKHR(dpy, sync));
    }
    
    // TODO(geofflang): Test sharing a program between multiple shared contexts on multiple threads
    
    ANGLE_INSTANTIATE_TEST(MultithreadingTest,
                           WithNoVirtualContexts(ES2_OPENGL()),
                           WithNoVirtualContexts(ES3_OPENGL()),
                           WithNoVirtualContexts(ES2_OPENGLES()),
                           WithNoVirtualContexts(ES3_OPENGLES()),
                           WithNoVirtualContexts(ES3_VULKAN()),
                           WithNoVirtualContexts(ES3_VULKAN_SWIFTSHADER()),
                           WithNoVirtualContexts(ES2_D3D11()),
                           WithNoVirtualContexts(ES3_D3D11()));
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MultithreadingTestES3);
    ANGLE_INSTANTIATE_TEST(MultithreadingTestES3,
                           WithNoVirtualContexts(ES3_OPENGL()),
                           WithNoVirtualContexts(ES3_OPENGLES()),
                           WithNoVirtualContexts(ES3_VULKAN()),
                           WithNoVirtualContexts(ES3_VULKAN_SWIFTSHADER()),
                           WithNoVirtualContexts(ES3_D3D11()));
    
    }  // namespace angle