Edit

kc3-lang/angle/src/tests/egl_tests/EGLContextSharingTest.cpp

Branch :

  • Show log

    Commit

  • Author : Ian Elliott
    Date : 2021-03-19 16:17:43
    Hash : 90cf278c
    Message : Add multi-thread/context test for compute shaders The "Ragnarok M: Eternal Love" game uncovered a bug where 2 contexts shared a VkCommandBuffer. The one context was used by one thread for rendering, and the other context/thread dispatched compute shaders. When the rendering thread destroyed a VkCommandBuffer, future compute dispatches crashed because the new VkCommandBuffer didn't have a compute pipeline bound. Bug: b/181711029 Change-Id: I8bc85150c1c6202e02feb84a7ccc0ad7b9c39258 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2770681 Commit-Queue: Ian Elliott <ianelliott@google.com> Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>

  • src/tests/egl_tests/EGLContextSharingTest.cpp
  • //
    // Copyright 2016 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.
    //
    // EGLContextSharingTest.cpp:
    //   Tests relating to shared Contexts.
    
    #include <gtest/gtest.h>
    
    #include "EGLMultiThreadSteps.h"
    #include "test_utils/ANGLETest.h"
    #include "test_utils/angle_test_configs.h"
    #include "test_utils/gl_raii.h"
    #include "util/EGLWindow.h"
    
    using namespace angle;
    
    namespace
    {
    
    EGLBoolean SafeDestroyContext(EGLDisplay display, EGLContext &context)
    {
        EGLBoolean result = EGL_TRUE;
        if (context != EGL_NO_CONTEXT)
        {
            result  = eglDestroyContext(display, context);
            context = EGL_NO_CONTEXT;
        }
        return result;
    }
    
    class EGLContextSharingTest : public ANGLETest
    {
      public:
        EGLContextSharingTest() : mContexts{EGL_NO_CONTEXT, EGL_NO_CONTEXT}, mTexture(0) {}
    
        void testTearDown() override
        {
            glDeleteTextures(1, &mTexture);
    
            EGLDisplay display = getEGLWindow()->getDisplay();
    
            if (display != EGL_NO_DISPLAY)
            {
                for (auto &context : mContexts)
                {
                    SafeDestroyContext(display, context);
                }
            }
    
            // Set default test state to not give an error on shutdown.
            getEGLWindow()->makeCurrent();
        }
    
        EGLContext mContexts[2];
        GLuint mTexture;
    };
    
    // Tests that creating resources works after freeing the share context.
    TEST_P(EGLContextSharingTest, BindTextureAfterShareContextFree)
    {
        EGLDisplay display = getEGLWindow()->getDisplay();
        EGLConfig config   = getEGLWindow()->getConfig();
        EGLSurface surface = getEGLWindow()->getSurface();
    
        const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION,
                                         getEGLWindow()->getClientMajorVersion(), EGL_NONE};
    
        mContexts[0] = eglCreateContext(display, config, nullptr, contextAttribs);
        ASSERT_EGL_SUCCESS();
        ASSERT_TRUE(mContexts[0] != EGL_NO_CONTEXT);
        mContexts[1] = eglCreateContext(display, config, mContexts[1], contextAttribs);
        ASSERT_EGL_SUCCESS();
        ASSERT_TRUE(mContexts[1] != EGL_NO_CONTEXT);
    
        ASSERT_EGL_TRUE(SafeDestroyContext(display, mContexts[0]));
        ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[1]));
        ASSERT_EGL_SUCCESS();
    
        glGenTextures(1, &mTexture);
        glBindTexture(GL_TEXTURE_2D, mTexture);
        ASSERT_GL_NO_ERROR();
    }
    
    // Tests the creation of contexts using EGL_ANGLE_display_texture_share_group
    TEST_P(EGLContextSharingTest, DisplayShareGroupContextCreation)
    {
        EGLDisplay display = getEGLWindow()->getDisplay();
        EGLConfig config   = getEGLWindow()->getConfig();
    
        const EGLint inShareGroupContextAttribs[] = {
            EGL_CONTEXT_CLIENT_VERSION, 2, EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE, EGL_TRUE, EGL_NONE};
    
        // Test creating two contexts in the global share group
        mContexts[0] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs);
        mContexts[1] = eglCreateContext(display, config, mContexts[1], inShareGroupContextAttribs);
    
        if (!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_display_texture_share_group"))
        {
            // Make sure an error is generated and early-exit
            ASSERT_EGL_ERROR(EGL_BAD_ATTRIBUTE);
            ASSERT_EQ(EGL_NO_CONTEXT, mContexts[0]);
            return;
        }
    
        ASSERT_EGL_SUCCESS();
    
        ASSERT_NE(EGL_NO_CONTEXT, mContexts[0]);
        ASSERT_NE(EGL_NO_CONTEXT, mContexts[1]);
        eglDestroyContext(display, mContexts[0]);
    
        // Try creating a context that is not in the global share group but tries to share with a
        // context that is
        const EGLint notInShareGroupContextAttribs[] = {
            EGL_CONTEXT_CLIENT_VERSION, 2, EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE, EGL_FALSE, EGL_NONE};
        mContexts[1] = eglCreateContext(display, config, mContexts[1], notInShareGroupContextAttribs);
        ASSERT_EGL_ERROR(EGL_BAD_ATTRIBUTE);
        ASSERT_TRUE(mContexts[1] == EGL_NO_CONTEXT);
    }
    
    // Tests the sharing of textures using EGL_ANGLE_display_texture_share_group
    TEST_P(EGLContextSharingTest, DisplayShareGroupObjectSharing)
    {
        EGLDisplay display = getEGLWindow()->getDisplay();
        if (!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_display_texture_share_group"))
        {
            std::cout << "Test skipped because EGL_ANGLE_display_texture_share_group is not present."
                      << std::endl;
            return;
        }
    
        EGLConfig config   = getEGLWindow()->getConfig();
        EGLSurface surface = getEGLWindow()->getSurface();
    
        const EGLint inShareGroupContextAttribs[] = {
            EGL_CONTEXT_CLIENT_VERSION, 2, EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE, EGL_TRUE, EGL_NONE};
    
        // Create two contexts in the global share group but not in the same context share group
        mContexts[0] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs);
        mContexts[1] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs);
    
        ASSERT_EGL_SUCCESS();
    
        ASSERT_NE(EGL_NO_CONTEXT, mContexts[0]);
        ASSERT_NE(EGL_NO_CONTEXT, mContexts[1]);
    
        ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0]));
        ASSERT_EGL_SUCCESS();
    
        // Create a texture and buffer in ctx 0
        GLTexture textureFromCtx0;
        glBindTexture(GL_TEXTURE_2D, textureFromCtx0);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glBindTexture(GL_TEXTURE_2D, 0);
        ASSERT_GL_TRUE(glIsTexture(textureFromCtx0));
    
        GLBuffer bufferFromCtx0;
        glBindBuffer(GL_ARRAY_BUFFER, bufferFromCtx0);
        glBufferData(GL_ARRAY_BUFFER, 1, nullptr, GL_STATIC_DRAW);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        ASSERT_GL_TRUE(glIsBuffer(bufferFromCtx0));
    
        ASSERT_GL_NO_ERROR();
    
        // Switch to context 1 and verify that the texture is accessible but the buffer is not
        ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[1]));
        ASSERT_EGL_SUCCESS();
    
        ASSERT_GL_TRUE(glIsTexture(textureFromCtx0));
    
        ASSERT_GL_FALSE(glIsBuffer(bufferFromCtx0));
        ASSERT_GL_NO_ERROR();
    
        // Call readpixels on the texture to verify that the backend has proper support
        GLFramebuffer fbo;
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureFromCtx0, 0);
    
        GLubyte pixel[4];
        glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
        ASSERT_GL_NO_ERROR();
    
        // Switch back to context 0 and delete the buffer
        ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0]));
        ASSERT_EGL_SUCCESS();
    }
    
    // Tests that shared textures using EGL_ANGLE_display_texture_share_group are released when the last
    // context is destroyed
    TEST_P(EGLContextSharingTest, DisplayShareGroupReleasedWithLastContext)
    {
        EGLDisplay display = getEGLWindow()->getDisplay();
        if (!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_display_texture_share_group"))
        {
            std::cout << "Test skipped because EGL_ANGLE_display_texture_share_group is not present."
                      << std::endl;
            return;
        }
    
        EGLConfig config   = getEGLWindow()->getConfig();
        EGLSurface surface = getEGLWindow()->getSurface();
    
        const EGLint inShareGroupContextAttribs[] = {
            EGL_CONTEXT_CLIENT_VERSION, 2, EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE, EGL_TRUE, EGL_NONE};
    
        // Create two contexts in the global share group but not in the same context share group
        mContexts[0] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs);
        mContexts[1] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs);
    
        // Create a texture and buffer in ctx 0
        ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0]));
        GLTexture textureFromCtx0;
        glBindTexture(GL_TEXTURE_2D, textureFromCtx0);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glBindTexture(GL_TEXTURE_2D, 0);
        ASSERT_GL_TRUE(glIsTexture(textureFromCtx0));
    
        // Switch to context 1 and verify that the texture is accessible
        ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[1]));
        ASSERT_GL_TRUE(glIsTexture(textureFromCtx0));
    
        // Destroy both contexts, the texture should be cleaned up automatically
        ASSERT_EGL_TRUE(eglDestroyContext(display, mContexts[0]));
        ASSERT_EGL_TRUE(eglDestroyContext(display, mContexts[1]));
    
        // Create a new context and verify it cannot access the texture previously created
        mContexts[0] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs);
    
        ASSERT_GL_FALSE(glIsTexture(textureFromCtx0));
    }
    
    // Tests that deleting an object on one Context doesn't destroy it ahead-of-time. Mostly focused
    // on the Vulkan back-end where we manage object lifetime manually.
    TEST_P(EGLContextSharingTest, TextureLifetime)
    {
        EGLWindow *eglWindow = getEGLWindow();
        EGLConfig config     = getEGLWindow()->getConfig();
        EGLDisplay display   = getEGLWindow()->getDisplay();
    
        // Create a pbuffer surface for use with a shared context.
        EGLSurface surface     = eglWindow->getSurface();
        EGLContext mainContext = eglWindow->getContext();
    
        // Initialize a shared context.
        mContexts[0] = eglCreateContext(display, config, mainContext, nullptr);
        ASSERT_NE(mContexts[0], EGL_NO_CONTEXT);
    
        // Create a Texture on the shared context.
        ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0]));
    
        constexpr GLsizei kTexSize                  = 2;
        const GLColor kTexData[kTexSize * kTexSize] = {GLColor::red, GLColor::green, GLColor::blue,
                                                       GLColor::yellow};
        GLTexture tex;
        glBindTexture(GL_TEXTURE_2D, tex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     kTexData);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
        // Make the main Context current and draw with the texture.
        ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mainContext));
    
        glBindTexture(GL_TEXTURE_2D, tex);
        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
        glUseProgram(program);
    
        // No uniform update because the update seems to hide the error on Vulkan.
    
        // Enqueue the draw call.
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        EXPECT_GL_NO_ERROR();
    
        // Delete the texture in the main context to orphan it.
        // Do not read back the data to keep the commands in the graph.
        tex.reset();
    
        // Bind and delete the test context. This should trigger texture garbage collection.
        ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0]));
        SafeDestroyContext(display, mContexts[0]);
    
        // Bind the main context to clean up the test.
        ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mainContext));
    }
    
    // Tests that deleting an object on one Context doesn't destroy it ahead-of-time. Mostly focused
    // on the Vulkan back-end where we manage object lifetime manually.
    TEST_P(EGLContextSharingTest, SamplerLifetime)
    {
        EGLWindow *eglWindow = getEGLWindow();
        EGLConfig config     = getEGLWindow()->getConfig();
        EGLDisplay display   = getEGLWindow()->getDisplay();
    
        ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
        ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_KHR_create_context"));
    
        // Create a pbuffer surface for use with a shared context.
        EGLSurface surface     = eglWindow->getSurface();
        EGLContext mainContext = eglWindow->getContext();
    
        std::vector<EGLint> contextAttributes;
        contextAttributes.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR);
        contextAttributes.push_back(getClientMajorVersion());
        contextAttributes.push_back(EGL_NONE);
    
        // Initialize a shared context.
        mContexts[0] = eglCreateContext(display, config, mainContext, contextAttributes.data());
        ASSERT_NE(mContexts[0], EGL_NO_CONTEXT);
    
        // Create a Texture on the shared context. Also create a Sampler object.
        ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0]));
    
        constexpr GLsizei kTexSize                  = 2;
        const GLColor kTexData[kTexSize * kTexSize] = {GLColor::red, GLColor::green, GLColor::blue,
                                                       GLColor::yellow};
        GLTexture tex;
        glBindTexture(GL_TEXTURE_2D, tex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     kTexData);
    
        GLSampler sampler;
        glBindSampler(0, sampler);
        glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
        // Make the main Context current and draw with the texture and sampler.
        ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mainContext));
    
        glBindTexture(GL_TEXTURE_2D, tex);
        glBindSampler(0, sampler);
        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
        glUseProgram(program);
    
        // No uniform update because the update seems to hide the error on Vulkan.
    
        // Enqueue the draw call.
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        EXPECT_GL_NO_ERROR();
    
        // Delete the texture and sampler in the main context to orphan them.
        // Do not read back the data to keep the commands in the graph.
        tex.reset();
        sampler.reset();
    
        // Bind and delete the test context. This should trigger texture garbage collection.
        ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0]));
        SafeDestroyContext(display, mContexts[0]);
    
        // Bind the main context to clean up the test.
        ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mainContext));
    }
    
    // Test that deleting an object reading from a shared object in one context doesn't cause the other
    // context to crash.  Mostly focused on the Vulkan back-end where we track resource dependencies in
    // a graph.
    TEST_P(EGLContextSharingTest, DeleteReaderOfSharedTexture)
    {
        ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
        // GL Fences require GLES 3.0+
        ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
    
        // Initialize contexts
        EGLWindow *window = getEGLWindow();
        EGLDisplay dpy    = window->getDisplay();
        EGLConfig config  = window->getConfig();
    
        constexpr size_t kThreadCount    = 2;
        EGLSurface surface[kThreadCount] = {EGL_NO_SURFACE, EGL_NO_SURFACE};
        EGLContext ctx[kThreadCount]     = {EGL_NO_CONTEXT, EGL_NO_CONTEXT};
    
        EGLint pbufferAttributes[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, EGL_NONE};
    
        for (size_t t = 0; t < kThreadCount; ++t)
        {
            surface[t] = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
            EXPECT_EGL_SUCCESS();
    
            ctx[t] = window->createContext(t == 0 ? EGL_NO_CONTEXT : ctx[0]);
            EXPECT_NE(EGL_NO_CONTEXT, ctx[t]);
        }
    
        // Initialize test resources.  They are done outside the threads to reduce the sources of
        // errors and thus deadlock-free teardown.
        ASSERT_EGL_TRUE(eglMakeCurrent(dpy, surface[1], surface[1], ctx[1]));
    
        // Shared texture to read from.
        constexpr GLsizei kTexSize = 1;
        const GLColor kTexData     = GLColor::red;
    
        GLTexture sharedTex;
        glBindTexture(GL_TEXTURE_2D, sharedTex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     &kTexData);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
        // Resources for each context.
        GLRenderbuffer renderbuffer[kThreadCount];
        GLFramebuffer fbo[kThreadCount];
        GLProgram program[kThreadCount];
    
        for (size_t t = 0; t < kThreadCount; ++t)
        {
            ASSERT_EGL_TRUE(eglMakeCurrent(dpy, surface[t], surface[t], ctx[t]));
    
            glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer[t]);
            constexpr int kRenderbufferSize = 4;
            glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kRenderbufferSize, kRenderbufferSize);
    
            glBindFramebuffer(GL_FRAMEBUFFER, fbo[t]);
            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
                                      renderbuffer[t]);
    
            glBindTexture(GL_TEXTURE_2D, sharedTex);
            program[t].makeRaster(essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
            ASSERT_TRUE(program[t].valid());
        }
    
        EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
    
        // Synchronization tools to ensure the two threads are interleaved as designed by this test.
        std::mutex mutex;
        std::condition_variable condVar;
        std::atomic<GLsync> deletingThreadSyncObj;
        std::atomic<GLsync> continuingThreadSyncObj;
    
        enum class Step
        {
            Start,
            Thread0Draw,
            Thread1Draw,
            Thread0Delete,
            Finish,
            Abort,
        };
        Step currentStep = Step::Start;
    
        std::thread deletingThread = std::thread([&]() {
            ThreadSynchronization<Step> threadSynchronization(&currentStep, &mutex, &condVar);
    
            EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[0], surface[0], ctx[0]));
            EXPECT_EGL_SUCCESS();
    
            ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start));
    
            // Draw using the shared texture.
            drawQuad(program[0].get(), essl1_shaders::PositionAttrib(), 0.5f);
    
            deletingThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
            ASSERT_GL_NO_ERROR();
            // Force the fence to be created
            glFlush();
    
            // Wait for the other thread to also draw using the shared texture.
            threadSynchronization.nextStep(Step::Thread0Draw);
            ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Draw));
    
            ASSERT_TRUE(continuingThreadSyncObj != nullptr);
            glWaitSync(continuingThreadSyncObj, 0, GL_TIMEOUT_IGNORED);
            ASSERT_GL_NO_ERROR();
            glDeleteSync(continuingThreadSyncObj);
            ASSERT_GL_NO_ERROR();
            continuingThreadSyncObj = nullptr;
    
            // Delete this thread's framebuffer (reader of the shared texture).
            fbo[0].reset();
    
            // Wait for the other thread to use the shared texture again before unbinding the
            // context (so no implicit flush happens).
            threadSynchronization.nextStep(Step::Thread0Delete);
            ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
    
            EXPECT_GL_NO_ERROR();
            EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
            EXPECT_EGL_SUCCESS();
        });
    
        std::thread continuingThread = std::thread([&]() {
            ThreadSynchronization<Step> threadSynchronization(&currentStep, &mutex, &condVar);
    
            EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[1], surface[1], ctx[1]));
            EXPECT_EGL_SUCCESS();
    
            // Wait for first thread to draw using the shared texture.
            ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Draw));
    
            ASSERT_TRUE(deletingThreadSyncObj != nullptr);
            glWaitSync(deletingThreadSyncObj, 0, GL_TIMEOUT_IGNORED);
            ASSERT_GL_NO_ERROR();
            glDeleteSync(deletingThreadSyncObj);
            ASSERT_GL_NO_ERROR();
            deletingThreadSyncObj = nullptr;
    
            // Draw using the shared texture.
            drawQuad(program[0].get(), essl1_shaders::PositionAttrib(), 0.5f);
    
            continuingThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
            ASSERT_GL_NO_ERROR();
            // Force the fence to be created
            glFlush();
    
            // Wait for the other thread to delete its framebuffer.
            threadSynchronization.nextStep(Step::Thread1Draw);
            ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Delete));
    
            // Write to the shared texture differently, so a dependency is created from the previous
            // readers of the shared texture (the two framebuffers of the two threads) to the new
            // commands being recorded for the shared texture.
            //
            // If the backend attempts to create a dependency from nodes associated with the
            // previous readers of the texture to the new node that will contain the following
            // commands, there will be a use-after-free error.
            const GLColor kTexData2 = GLColor::green;
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                         &kTexData2);
            drawQuad(program[0].get(), essl1_shaders::PositionAttrib(), 0.5f);
    
            threadSynchronization.nextStep(Step::Finish);
    
            EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
            EXPECT_EGL_SUCCESS();
        });
    
        deletingThread.join();
        continuingThread.join();
    
        ASSERT_NE(currentStep, Step::Abort);
    
        // Clean up
        for (size_t t = 0; t < kThreadCount; ++t)
        {
            eglDestroySurface(dpy, surface[t]);
            eglDestroyContext(dpy, ctx[t]);
        }
    }
    }  // anonymous namespace
    
    GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLContextSharingTest);
    ANGLE_INSTANTIATE_TEST(EGLContextSharingTest,
                           ES2_D3D9(),
                           ES2_D3D11(),
                           ES3_D3D11(),
                           ES2_METAL(),
                           ES2_OPENGL(),
                           ES3_OPENGL(),
                           ES2_VULKAN(),
                           ES3_VULKAN());