// // Copyright 2021 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. // // EGLMultiContextTest.cpp: // Tests relating to multiple non-shared Contexts. #include #include "test_utils/ANGLETest.h" #include "test_utils/MultiThreadSteps.h" #include "test_utils/angle_test_configs.h" #include "test_utils/gl_raii.h" #include "util/EGLWindow.h" #include "util/test_utils.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 EGLMultiContextTest : public ANGLETest<> { public: EGLMultiContextTest() : 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(); } bool chooseConfig(EGLDisplay dpy, EGLConfig *config) const { bool result = false; EGLint count = 0; EGLint clientVersion = EGL_OPENGL_ES3_BIT; EGLint attribs[] = {EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_RENDERABLE_TYPE, clientVersion, EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT, EGL_NONE}; result = eglChooseConfig(dpy, attribs, config, 1, &count); EXPECT_EGL_TRUE(result && (count > 0)); return result; } bool createContext(EGLDisplay dpy, EGLConfig config, EGLContext *context) { bool result = false; EGLint attribs[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE}; *context = eglCreateContext(dpy, config, nullptr, attribs); result = (*context != EGL_NO_CONTEXT); EXPECT_TRUE(result); return result; } bool createPbufferSurface(EGLDisplay dpy, EGLConfig config, EGLint width, EGLint height, EGLSurface *surface) { bool result = false; EGLint attribs[] = {EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE}; *surface = eglCreatePbufferSurface(dpy, config, attribs); result = (*surface != EGL_NO_SURFACE); EXPECT_TRUE(result); return result; } enum class FenceTest { ClientWait, ServerWait, GetStatus, }; enum class FlushMethod { Flush, Finish, }; void testFenceWithOpenRenderPass(FenceTest test, FlushMethod flushMethod); EGLContext mContexts[2]; GLuint mTexture; }; // Test that calling eglDeleteContext on a context that is not current succeeds. TEST_P(EGLMultiContextTest, TestContextDestroySimple) { EGLWindow *window = getEGLWindow(); EGLDisplay dpy = window->getDisplay(); EGLContext context1 = window->createContext(EGL_NO_CONTEXT, nullptr); EGLContext context2 = window->createContext(EGL_NO_CONTEXT, nullptr); EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, context1)); EXPECT_EGL_TRUE(eglDestroyContext(dpy, context2)); EXPECT_EGL_SUCCESS(); // Cleanup EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); EXPECT_EGL_TRUE(eglDestroyContext(dpy, context1)); EXPECT_EGL_SUCCESS(); } // Test that an error is generated when using EGL objects after calling eglTerminate. TEST_P(EGLMultiContextTest, NegativeTestAfterEglTerminate) { EGLWindow *window = getEGLWindow(); EGLDisplay dpy = window->getDisplay(); EGLConfig config = EGL_NO_CONFIG_KHR; EXPECT_TRUE(chooseConfig(dpy, &config)); EGLContext context = EGL_NO_CONTEXT; EXPECT_TRUE(createContext(dpy, config, &context)); ASSERT_EGL_SUCCESS() << "eglCreateContext failed."; EGLSurface drawSurface = EGL_NO_SURFACE; EXPECT_TRUE(createPbufferSurface(dpy, config, 2560, 1080, &drawSurface)); ASSERT_EGL_SUCCESS() << "eglCreatePbufferSurface failed."; EGLSurface readSurface = EGL_NO_SURFACE; EXPECT_TRUE(createPbufferSurface(dpy, config, 2560, 1080, &readSurface)); ASSERT_EGL_SUCCESS() << "eglCreatePbufferSurface failed."; EXPECT_EGL_TRUE(eglMakeCurrent(dpy, drawSurface, readSurface, context)); EXPECT_EGL_SUCCESS(); // Terminate the display EXPECT_EGL_TRUE(eglTerminate(dpy)); EXPECT_EGL_SUCCESS(); // Try to use invalid handles EGLint value; eglQuerySurface(dpy, drawSurface, EGL_SWAP_BEHAVIOR, &value); EXPECT_EGL_ERROR(EGL_BAD_SURFACE); eglQuerySurface(dpy, readSurface, EGL_HEIGHT, &value); EXPECT_EGL_ERROR(EGL_BAD_SURFACE); // Cleanup EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); EXPECT_EGL_SUCCESS(); window->destroyGL(); } // Test that a compute shader running in one thread will still work when rendering is happening in // another thread (with non-shared contexts). The non-shared context will still share a Vulkan // command buffer. TEST_P(EGLMultiContextTest, ComputeShaderOkayWithRendering) { ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading()); ANGLE_SKIP_TEST_IF(!isVulkanRenderer()); ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 || getClientMinorVersion() < 1); // 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(EGL_NO_CONTEXT, nullptr); EXPECT_NE(EGL_NO_CONTEXT, ctx[t]); } // Synchronization tools to ensure the two threads are interleaved as designed by this test. std::mutex mutex; std::condition_variable condVar; enum class Step { Thread0Start, Thread0DispatchedCompute, Thread1Drew, Thread0DispatchedComputeAgain, Finish, Abort, }; Step currentStep = Step::Thread0Start; // This first thread dispatches a compute shader. It immediately starts. std::thread deletingThread = std::thread([&]() { ThreadSynchronization threadSynchronization(¤tStep, &mutex, &condVar); EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[0], surface[0], ctx[0])); EXPECT_EGL_SUCCESS(); // Potentially wait to be signalled to start. ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Start)); // Wake up and do next step: Create, detach, and dispatch a compute shader program. constexpr char kCS[] = R"(#version 310 es layout(local_size_x=1) in; void main() { })"; GLuint computeProgram = glCreateProgram(); GLuint cs = CompileShader(GL_COMPUTE_SHADER, kCS); EXPECT_NE(0u, cs); glAttachShader(computeProgram, cs); glDeleteShader(cs); glLinkProgram(computeProgram); GLint linkStatus; glGetProgramiv(computeProgram, GL_LINK_STATUS, &linkStatus); EXPECT_GL_TRUE(linkStatus); glDetachShader(computeProgram, cs); EXPECT_GL_NO_ERROR(); glUseProgram(computeProgram); glDispatchCompute(8, 4, 2); EXPECT_GL_NO_ERROR(); // Signal the second thread and wait for it to draw and flush. threadSynchronization.nextStep(Step::Thread0DispatchedCompute); ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Drew)); // Wake up and do next step: Dispatch the same compute shader again. glDispatchCompute(8, 4, 2); // Signal the second thread and wait for it to draw and flush again. threadSynchronization.nextStep(Step::Thread0DispatchedComputeAgain); ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish)); // Wake up and do next step: Dispatch the same compute shader again, and force flush the // underlying command buffer. glDispatchCompute(8, 4, 2); glFinish(); // Clean-up and exit this thread. EXPECT_GL_NO_ERROR(); EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); EXPECT_EGL_SUCCESS(); }); // This second thread renders. It starts once the other thread does its first nextStep() std::thread continuingThread = std::thread([&]() { ThreadSynchronization threadSynchronization(¤tStep, &mutex, &condVar); EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[1], surface[1], ctx[1])); EXPECT_EGL_SUCCESS(); // Wait for first thread to create and dispatch a compute shader. ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0DispatchedCompute)); // Wake up and do next step: Create graphics resources, draw, and force flush the // underlying command buffer. GLTexture texture; glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); GLRenderbuffer renderbuffer; GLFramebuffer fbo; glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); constexpr int kRenderbufferSize = 4; glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kRenderbufferSize, kRenderbufferSize); glBindFramebuffer(GL_FRAMEBUFFER, fbo); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); glBindTexture(GL_TEXTURE_2D, texture); GLProgram graphicsProgram; graphicsProgram.makeRaster(essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D()); ASSERT_TRUE(graphicsProgram.valid()); drawQuad(graphicsProgram.get(), essl1_shaders::PositionAttrib(), 0.5f); glFinish(); // Signal the first thread and wait for it to dispatch a compute shader again. threadSynchronization.nextStep(Step::Thread1Drew); ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0DispatchedComputeAgain)); // Wake up and do next step: Draw and force flush the underlying command buffer again. drawQuad(graphicsProgram.get(), essl1_shaders::PositionAttrib(), 0.5f); glFinish(); // Signal the first thread and wait exit this thread. 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 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); for (size_t t = 0; t < kThreadCount; ++t) { eglDestroySurface(dpy, surface[t]); eglDestroyContext(dpy, ctx[t]); } } // Test that repeated EGL init + terminate with improper cleanup doesn't cause an OOM crash. // To reproduce the OOM error - // 1. Increase the loop count to a large number // 2. Remove the call to "eglReleaseThread" in the for loop TEST_P(EGLMultiContextTest, RepeatedEglInitAndTerminate) { ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading()); // Release all resources in parent thread getEGLWindow()->destroyGL(); EGLDisplay dpy; EGLSurface srf; EGLContext ctx; EGLint dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, GetParam().getDeviceType(), EGL_NONE}; for (int i = 0; i < 50; i++) // Note: this test is fairly slow b/303089709 { std::thread thread = std::thread([&]() { dpy = eglGetPlatformDisplayEXT( EGL_PLATFORM_ANGLE_ANGLE, reinterpret_cast(EGL_DEFAULT_DISPLAY), dispattrs); EXPECT_TRUE(dpy != EGL_NO_DISPLAY); EXPECT_EGL_TRUE(eglInitialize(dpy, nullptr, nullptr)); EGLConfig config = EGL_NO_CONFIG_KHR; EXPECT_TRUE(chooseConfig(dpy, &config)); EXPECT_TRUE(createPbufferSurface(dpy, config, 2560, 1080, &srf)); ASSERT_EGL_SUCCESS() << "eglCreatePbufferSurface failed."; EXPECT_TRUE(createContext(dpy, config, &ctx)); EXPECT_EGL_TRUE(eglMakeCurrent(dpy, srf, srf, ctx)); // Clear and read back to make sure thread uses context. glClearColor(1.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255); eglTerminate(dpy); EXPECT_EGL_SUCCESS(); eglReleaseThread(); EXPECT_EGL_SUCCESS(); dpy = EGL_NO_DISPLAY; srf = EGL_NO_SURFACE; ctx = EGL_NO_CONTEXT; }); thread.join(); } } // Test that thread B can reuse the unterminated display created by thread A // even after thread A is destroyed. TEST_P(EGLMultiContextTest, ReuseUnterminatedDisplay) { // Release all resources in parent thread getEGLWindow()->destroyGL(); EGLDisplay dpy; EGLint dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, GetParam().getDeviceType(), EGL_NONE}; std::thread threadA = std::thread([&]() { dpy = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, reinterpret_cast(EGL_DEFAULT_DISPLAY), dispattrs); EXPECT_TRUE(dpy != EGL_NO_DISPLAY); EXPECT_EGL_TRUE(eglInitialize(dpy, nullptr, nullptr)); }); threadA.join(); std::thread threadB = std::thread([&]() { EGLSurface srf; EGLContext ctx; EGLConfig config = EGL_NO_CONFIG_KHR; // If threadA's termination caused "dpy" to be incorrectly terminated all EGL APIs below // staring with eglChooseConfig(...) will error out with an EGL_NOT_INITIALIZED error. EXPECT_TRUE(chooseConfig(dpy, &config)); EXPECT_TRUE(createPbufferSurface(dpy, config, 2560, 1080, &srf)); ASSERT_EGL_SUCCESS() << "eglCreatePbufferSurface failed."; EXPECT_TRUE(createContext(dpy, config, &ctx)); EXPECT_EGL_TRUE(eglMakeCurrent(dpy, srf, srf, ctx)); // Clear and read back to make sure thread uses context. glClearColor(1.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255); srf = EGL_NO_SURFACE; ctx = EGL_NO_CONTEXT; EXPECT_EGL_TRUE(eglMakeCurrent(dpy, srf, srf, ctx)); eglTerminate(dpy); EXPECT_EGL_SUCCESS(); EXPECT_EGL_SUCCESS(); dpy = EGL_NO_DISPLAY; }); threadB.join(); } // Test that thread B can wait on thread A's sync before thread A flushes it, and wakes up after // that. Note that only validatity of the fence operations are tested here. The test could // potentially be enhanced with EGL images similarly to how // MultithreadingTestES3::testFenceWithOpenRenderPass tests correctness of synchronization through // a shared texture. void EGLMultiContextTest::testFenceWithOpenRenderPass(FenceTest test, FlushMethod flushMethod) { ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading()); constexpr uint32_t kWidth = 100; constexpr uint32_t kHeight = 200; EGLSyncKHR sync = EGL_NO_SYNC_KHR; std::mutex mutex; std::condition_variable condVar; enum class Step { Start, Thread0CreateFence, Thread1WaitFence, Finish, Abort, }; Step currentStep = Step::Start; auto thread0 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) { ThreadSynchronization threadSynchronization(¤tStep, &mutex, &condVar); ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start)); EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context)); // Issue a draw ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f); ASSERT_GL_NO_ERROR(); // Issue a fence. A render pass is currently open, but it should be closed in the Vulkan // backend. sync = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, nullptr); EXPECT_NE(sync, EGL_NO_SYNC_KHR); // Wait for thread 1 to wait on it. threadSynchronization.nextStep(Step::Thread0CreateFence); ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1WaitFence)); // Wait a little to give thread 1 time to wait on the sync object before flushing it. angle::Sleep(500); switch (flushMethod) { case FlushMethod::Flush: glFlush(); break; case FlushMethod::Finish: glFinish(); break; } ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish)); EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::red); // Clean up EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); }; auto thread1 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) { ThreadSynchronization threadSynchronization(¤tStep, &mutex, &condVar); EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context)); // Wait for thread 0 to create the fence object. ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0CreateFence)); // Test access to the fence object threadSynchronization.nextStep(Step::Thread1WaitFence); constexpr GLuint64 kTimeout = 2'000'000'000; // 2 seconds EGLint result = EGL_CONDITION_SATISFIED_KHR; switch (test) { case FenceTest::ClientWait: result = eglClientWaitSyncKHR(dpy, sync, 0, kTimeout); break; case FenceTest::ServerWait: ASSERT_TRUE(eglWaitSyncKHR(dpy, sync, 0)); break; case FenceTest::GetStatus: { EGLint value; EXPECT_EGL_TRUE(eglGetSyncAttribKHR(dpy, sync, EGL_SYNC_STATUS_KHR, &value)); if (value != EGL_SIGNALED_KHR) { result = eglClientWaitSyncKHR(dpy, sync, 0, kTimeout); } break; } } ASSERT_TRUE(result == EGL_CONDITION_SATISFIED_KHR); // Issue a draw ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green()); drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f); ASSERT_GL_NO_ERROR(); EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green); // Clean up EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); threadSynchronization.nextStep(Step::Finish); }; std::array threadFuncs = { std::move(thread0), std::move(thread1), }; RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data()); ASSERT_NE(currentStep, Step::Abort); } // Test that thread B can wait on thread A's sync before thread A flushes it, and wakes up after // that. TEST_P(EGLMultiContextTest, ThreadBClientWaitBeforeThreadASyncFlush) { testFenceWithOpenRenderPass(FenceTest::ClientWait, FlushMethod::Flush); } // Test that thread B can wait on thread A's sync before thread A flushes it, and wakes up after // that. TEST_P(EGLMultiContextTest, ThreadBServerWaitBeforeThreadASyncFlush) { testFenceWithOpenRenderPass(FenceTest::ServerWait, FlushMethod::Flush); } // Test that thread B can wait on thread A's sync before thread A flushes it, and wakes up after // that. TEST_P(EGLMultiContextTest, ThreadBGetStatusBeforeThreadASyncFlush) { testFenceWithOpenRenderPass(FenceTest::GetStatus, FlushMethod::Flush); } // Test that thread B can wait on thread A's sync before thread A flushes it, and wakes up after // that. TEST_P(EGLMultiContextTest, ThreadBClientWaitBeforeThreadASyncFinish) { testFenceWithOpenRenderPass(FenceTest::ClientWait, FlushMethod::Finish); } // Test that thread B can wait on thread A's sync before thread A flushes it, and wakes up after // that. TEST_P(EGLMultiContextTest, ThreadBServerWaitBeforeThreadASyncFinish) { testFenceWithOpenRenderPass(FenceTest::ServerWait, FlushMethod::Finish); } // Test that thread B can wait on thread A's sync before thread A flushes it, and wakes up after // that. TEST_P(EGLMultiContextTest, ThreadBGetStatusBeforeThreadASyncFinish) { testFenceWithOpenRenderPass(FenceTest::GetStatus, FlushMethod::Finish); } // Test that thread B can submit while three other threads are waiting for GPU to finish. TEST_P(EGLMultiContextTest, ThreadBCanSubmitWhileThreadAWaiting) { ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading()); ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_buffer_storage")); constexpr uint32_t kWidth = 100; constexpr uint32_t kHeight = 200; std::mutex mutex; std::condition_variable condVar; enum class Step { Start, ThreadAMapBufferBufferRange, ThreadBSubmit, Finish, Abort, }; Step currentStep = Step::Start; std::atomic threadCountMakingMapBufferCall(0); std::atomic threadCountFinishedMapBufferCall(0); constexpr size_t kMaxThreadCountMakingMapBufferCall = 3; auto threadA = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) { ThreadSynchronization threadSynchronization(¤tStep, &mutex, &condVar); ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start)); EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context)); // Issue a draw ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f); // Issue a compute shader write to a buffer constexpr char kCS[] = R"(#version 310 es layout(local_size_x=1, local_size_y=1, local_size_z=1) in; layout(std140, binding = 0) buffer block { uvec4 data; } outBlock; void main() { outBlock.data += uvec4(1); })"; ANGLE_GL_COMPUTE_PROGRAM(computeProgram, kCS); glUseProgram(computeProgram); constexpr std::array kInitData = {}; GLBuffer coherentBuffer; glBindBuffer(GL_SHADER_STORAGE_BUFFER, coherentBuffer); glBufferStorageEXT(GL_SHADER_STORAGE_BUFFER, sizeof(kInitData), kInitData.data(), GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT_EXT | GL_MAP_COHERENT_BIT_EXT); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, coherentBuffer); glDispatchCompute(100, 100, 100); EXPECT_GL_NO_ERROR(); // Map the buffers for read. This should trigger driver wait for GPU to finish glMemoryBarrier(GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT_EXT); glBindBuffer(GL_SHADER_STORAGE_BUFFER, coherentBuffer); if (++threadCountMakingMapBufferCall == kMaxThreadCountMakingMapBufferCall) { threadSynchronization.nextStep(Step::ThreadAMapBufferBufferRange); } // Wait for thread B to start submit commands. ASSERT_TRUE(threadSynchronization.waitForStep(Step::ThreadBSubmit)); glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(kInitData), GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT_EXT | GL_MAP_COHERENT_BIT_EXT); ASSERT_GL_NO_ERROR(); ++threadCountFinishedMapBufferCall; ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish)); EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::red); // Clean up EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); }; auto threadB = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) { ThreadSynchronization threadSynchronization(¤tStep, &mutex, &condVar); EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context)); // Prepare for draw and then wait until threadA is ready to wait ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green()); glScissor(0, 0, 1, 1); ASSERT_TRUE(threadSynchronization.waitForStep(Step::ThreadAMapBufferBufferRange)); // Test submit in a loop threadSynchronization.nextStep(Step::ThreadBSubmit); for (int loop = 0; loop < 16 && threadCountFinishedMapBufferCall < kMaxThreadCountMakingMapBufferCall; loop++) { drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f); glFinish(); EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green); } // Clean up ASSERT_GL_NO_ERROR(); EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); threadSynchronization.nextStep(Step::Finish); }; std::array threadFuncs; for (size_t i = 0; i < kMaxThreadCountMakingMapBufferCall; i++) { threadFuncs[i] = std::move(threadA); } threadFuncs[kMaxThreadCountMakingMapBufferCall] = std::move(threadB); RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data()); ASSERT_NE(currentStep, Step::Abort); } // Test that if there are any placeholder objects when the programs don't use any resources // (such as textures), they can correctly be used in non-shared contexts (without causing // double-free). TEST_P(EGLMultiContextTest, NonSharedContextsReuseDescritorSetLayoutHandle) { EGLWindow *window = getEGLWindow(); EGLDisplay dpy = window->getDisplay(); EGLSurface surface = window->getSurface(); EGLContext context1 = window->createContext(EGL_NO_CONTEXT, nullptr); EGLContext context2 = window->createContext(EGL_NO_CONTEXT, nullptr); EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context1)); EXPECT_EGL_SUCCESS(); ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green()); drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f); ASSERT_GL_NO_ERROR(); EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context2)); EXPECT_EGL_SUCCESS(); ANGLE_GL_PROGRAM(program1, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); drawQuad(program1, essl1_shaders::PositionAttrib(), 0.5f); ASSERT_GL_NO_ERROR(); // Cleanup EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); EXPECT_EGL_TRUE(eglDestroyContext(dpy, context1)); EXPECT_EGL_TRUE(eglDestroyContext(dpy, context2)); EXPECT_EGL_SUCCESS(); } } // anonymous namespace GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLMultiContextTest); ANGLE_INSTANTIATE_TEST_ES31(EGLMultiContextTest);