// // 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 "test_utils/ANGLETest.h" #include "test_utils/gl_raii.h" #include "util/EGLWindow.h" #include #include namespace angle { class MultithreadingTest : public ANGLETest { protected: MultithreadingTest() { setWindowWidth(128); setWindowHeight(128); setConfigRedBits(8); setConfigGreenBits(8); setConfigBlueBits(8); setConfigAlphaBits(8); } void runMultithreadedGLTest( std::function testBody, size_t threadCount) { std::mutex mutex; EGLWindow *window = getEGLWindow(); EGLDisplay dpy = window->getDisplay(); EGLConfig config = window->getConfig(); constexpr EGLint kPBufferSize = 256; std::vector 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 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 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(); } } }; // 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 threads; for (std::thread &thread : threads) { thread = std::thread([&]() { std::lock_guard 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(thread % 255), static_cast(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()); 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(thread % 255), static_cast(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); } // 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())); } // namespace angle