Edit

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

Branch :

  • Show log

    Commit

  • Author : Tim Van Patten
    Date : 2020-11-20 13:07:53
    Hash : 909ea88b
    Message : Reland "Vulkan: Ignore glFlush to reduce vkQueueSubmits in Asphalt 9" This is a reland of 5cf7472dd161bbda329dfc5e4e65bb6ce0c06fbd The ShareGroupVk::mResourceUseLists was not being cleared each call to RendererVk::submitFrame(), so it was growing indefinitely. Each vk::ResourceUseList within it was cleared, so it was holding an essentially "infinite" list of empty lists, but that caused the loop in RendererVk::submitFrame() to take more and more time until the tests timed out. The fix is to do 'resourceUseLists.clear()' once the loop to release all resources has completed, like releaseResourceUsesAndUpdateSerials() does for each individual list. Additionally, ASSERTs are added to guarantee that the lists are empty when the ContextVk and ShareGroupVk are destroyed. Original change's description: > Vulkan: Ignore glFlush to reduce vkQueueSubmits in Asphalt 9 > > Multithreaded apps can use the following pattern: > > glDrawElements() > glFenceSync() > glFlush() > glWaitSync() > > This currently results in a vkQueueSubmit for every glFlush() to ensure > that the work has landed in the command queue in the correct order. > However, ANGLE can instead avoid the vkQueueSubmit during the glFlush() > in this situation by instead flushing the ContextVk's commands and > ending the render pass to ensure the commands are submitted in the > correct order to the renderer. This improves performance for Asphalt 9 > by reducing frame times from 150-200msec to 35-55msec. > > Specifically, ANGLE will call flushCommandsAndEndRenderPass() when > there is a sync object pending a flush or if the ContextVk is currently > shared. > > Additionally, on all devices except Qualcomm, ANGLE can ignore all other > glFlush() calls entirely and return immediately. For Qualcomm devices, > ANGLE is still required to perform a full flush (resulting in a > vkQueueSubmit), since ignoring the glFlush() reduces the Manhattan 3.0 > offscreen score by ~3%. > > Bug: angleproject:5306 > Bug: angleproject:5425 > Change-Id: I9d747caf5bf306166be0fec630a78caf41208c27 > Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2552718 > Commit-Queue: Tim Van Patten <timvp@google.com> > Reviewed-by: Charlie Lao <cclao@google.com> > Reviewed-by: Jamie Madill <jmadill@chromium.org> Bug: angleproject:5306 Bug: angleproject:5425 Bug: angleproject:5470 Change-Id: I14ee424d032f22e5285d67accbec078ad1955dd0 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2595811 Commit-Queue: Tim Van Patten <timvp@google.com> Reviewed-by: Charlie Lao <cclao@google.com> Reviewed-by: Jamie Madill <jmadill@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 "test_utils/ANGLETest.h"
    #include "test_utils/angle_test_configs.h"
    #include "test_utils/gl_raii.h"
    #include "util/EGLWindow.h"
    
    #include <atomic>
    #include <condition_variable>
    #include <mutex>
    #include <thread>
    
    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;
    
        // Helper functions to synchronize the threads so that the operations are executed in the
        // specific order the test is written for.
        auto waitForStep = [&](Step waitStep) -> bool {
            std::unique_lock<std::mutex> lock(mutex);
            while (currentStep != waitStep)
            {
                // If necessary, abort execution as the other thread has encountered a GL error.
                if (currentStep == Step::Abort)
                {
                    return false;
                }
                condVar.wait(lock);
            }
    
            return true;
        };
        auto nextStep = [&](Step newStep) {
            {
                std::unique_lock<std::mutex> lock(mutex);
                currentStep = newStep;
            }
            condVar.notify_one();
        };
    
        class AbortOnFailure
        {
          public:
            AbortOnFailure(Step *currentStep, std::mutex *mutex, std::condition_variable *condVar)
                : mCurrentStep(currentStep), mMutex(mutex), mCondVar(condVar)
            {}
    
            ~AbortOnFailure()
            {
                bool isAborting = false;
                {
                    std::unique_lock<std::mutex> lock(*mMutex);
                    isAborting = *mCurrentStep != Step::Finish;
    
                    if (isAborting)
                    {
                        *mCurrentStep = Step::Abort;
                    }
                }
                mCondVar->notify_all();
            }
    
          private:
            Step *mCurrentStep;
            std::mutex *mMutex;
            std::condition_variable *mCondVar;
        };
    
        std::thread deletingThread = std::thread([&]() {
            AbortOnFailure abortOnFailure(&currentStep, &mutex, &condVar);
    
            EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[0], surface[0], ctx[0]));
            EXPECT_EGL_SUCCESS();
    
            ASSERT_TRUE(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.
            nextStep(Step::Thread0Draw);
            ASSERT_TRUE(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).
            nextStep(Step::Thread0Delete);
            ASSERT_TRUE(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([&]() {
            AbortOnFailure abortOnFailure(&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(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.
            nextStep(Step::Thread1Draw);
            ASSERT_TRUE(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);
    
            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
    
    ANGLE_INSTANTIATE_TEST(EGLContextSharingTest,
                           ES2_D3D9(),
                           ES2_D3D11(),
                           ES3_D3D11(),
                           ES2_METAL(),
                           ES2_OPENGL(),
                           ES3_OPENGL(),
                           ES2_VULKAN(),
                           ES3_VULKAN());