| // |
| // 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); |
| } |
| |
| 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); |
| } |
| |
| // 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) |
| { |
| // 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) |
| { |
| // Have the secondary thread use glTexImage2D() |
| mainThreadDraw(false); |
| } |
| |
| // 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())); |
| |
| ANGLE_INSTANTIATE_TEST(MultithreadingTestES3, |
| WithNoVirtualContexts(ES3_OPENGL()), |
| WithNoVirtualContexts(ES3_OPENGLES()), |
| WithNoVirtualContexts(ES3_VULKAN()), |
| WithNoVirtualContexts(ES3_VULKAN_SWIFTSHADER())); |
| |
| } // namespace angle |