1 //
2 // Copyright 2018 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // MulithreadingTest.cpp : Tests of multithreaded rendering
7
8 #include "platform/FeaturesVk.h"
9 #include "test_utils/ANGLETest.h"
10 #include "test_utils/MultiThreadSteps.h"
11 #include "test_utils/gl_raii.h"
12 #include "util/EGLWindow.h"
13 #include "util/test_utils.h"
14
15 #include <atomic>
16 #include <mutex>
17 #include <thread>
18
19 namespace angle
20 {
21
22 class MultithreadingTest : public ANGLETest
23 {
24 public:
25 static constexpr uint32_t kSize = 512;
26
27 protected:
MultithreadingTest()28 MultithreadingTest()
29 {
30 setWindowWidth(kSize);
31 setWindowHeight(kSize);
32 setConfigRedBits(8);
33 setConfigGreenBits(8);
34 setConfigBlueBits(8);
35 setConfigAlphaBits(8);
36 }
37
hasFenceSyncExtension() const38 bool hasFenceSyncExtension() const
39 {
40 return IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(), "EGL_KHR_fence_sync");
41 }
hasGLSyncExtension() const42 bool hasGLSyncExtension() const { return IsGLExtensionEnabled("GL_OES_EGL_sync"); }
43
createMultithreadedContext(EGLWindow *window, EGLContext shareCtx)44 EGLContext createMultithreadedContext(EGLWindow *window, EGLContext shareCtx)
45 {
46 EGLint attribs[] = {EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE, mVirtualizationGroup++,
47 EGL_NONE};
48 if (!IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(),
49 "EGL_ANGLE_context_virtualization"))
50 {
51 attribs[0] = EGL_NONE;
52 }
53
54 return window->createContext(shareCtx, attribs);
55 }
56
runMultithreadedGLTest( std::function<void(EGLSurface surface, size_t threadIndex)> testBody, size_t threadCount)57 void runMultithreadedGLTest(
58 std::function<void(EGLSurface surface, size_t threadIndex)> testBody,
59 size_t threadCount)
60 {
61 std::mutex mutex;
62
63 EGLWindow *window = getEGLWindow();
64 EGLDisplay dpy = window->getDisplay();
65 EGLConfig config = window->getConfig();
66
67 constexpr EGLint kPBufferSize = 256;
68
69 std::vector<std::thread> threads(threadCount);
70 for (size_t threadIdx = 0; threadIdx < threadCount; threadIdx++)
71 {
72 threads[threadIdx] = std::thread([&, threadIdx]() {
73 EGLSurface surface = EGL_NO_SURFACE;
74 EGLContext ctx = EGL_NO_CONTEXT;
75
76 {
77 std::lock_guard<decltype(mutex)> lock(mutex);
78
79 // Initialize the pbuffer and context
80 EGLint pbufferAttributes[] = {
81 EGL_WIDTH, kPBufferSize, EGL_HEIGHT, kPBufferSize, EGL_NONE, EGL_NONE,
82 };
83 surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
84 EXPECT_EGL_SUCCESS();
85
86 ctx = createMultithreadedContext(window, EGL_NO_CONTEXT);
87 EXPECT_NE(EGL_NO_CONTEXT, ctx);
88
89 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
90 EXPECT_EGL_SUCCESS();
91 }
92
93 testBody(surface, threadIdx);
94
95 {
96 std::lock_guard<decltype(mutex)> lock(mutex);
97
98 // Clean up
99 EXPECT_EGL_TRUE(
100 eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
101 EXPECT_EGL_SUCCESS();
102
103 eglDestroySurface(dpy, surface);
104 eglDestroyContext(dpy, ctx);
105 }
106 });
107 }
108
109 for (std::thread &thread : threads)
110 {
111 thread.join();
112 }
113 }
114
115 std::atomic<EGLint> mVirtualizationGroup;
116 };
117
118 class MultithreadingTestES3 : public MultithreadingTest
119 {
120 public:
121 void textureThreadFunction(bool useDraw);
122 void mainThreadDraw(bool useDraw);
123
124 protected:
MultithreadingTestES3()125 MultithreadingTestES3()
126 : mTexture2D(0), mExitThread(false), mMainThreadSyncObj(NULL), mSecondThreadSyncObj(NULL)
127 {
128 setWindowWidth(kSize);
129 setWindowHeight(kSize);
130 setConfigRedBits(8);
131 setConfigGreenBits(8);
132 setConfigBlueBits(8);
133 setConfigAlphaBits(8);
134 }
135
create2DTexture()136 GLuint create2DTexture()
137 {
138 GLuint texture2D;
139 glGenTextures(1, &texture2D);
140 glActiveTexture(GL_TEXTURE0);
141 glBindTexture(GL_TEXTURE_2D, texture2D);
142 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
143 nullptr);
144 EXPECT_GL_NO_ERROR();
145 return texture2D;
146 }
147
148 void testSetUp() override { mTexture2D = create2DTexture(); }
149
150 void testTearDown() override
151 {
152 if (mTexture2D)
153 {
154 glDeleteTextures(1, &mTexture2D);
155 }
156 }
157
158 std::mutex mutex;
159 GLuint mTexture2D;
160 std::atomic<bool> mExitThread;
161 std::atomic<bool> mDrawGreen; // Toggle drawing green or red
162 std::atomic<GLsync> mMainThreadSyncObj;
163 std::atomic<GLsync> mSecondThreadSyncObj;
164 };
165
166 // Test that it's possible to make one context current on different threads
TEST_P(MultithreadingTest, MakeCurrentSingleContext)167 TEST_P(MultithreadingTest, MakeCurrentSingleContext)
168 {
169 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
170
171 std::mutex mutex;
172
173 EGLWindow *window = getEGLWindow();
174 EGLDisplay dpy = window->getDisplay();
175 EGLContext ctx = window->getContext();
176 EGLSurface surface = window->getSurface();
177
178 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
179 EXPECT_EGL_SUCCESS();
180
181 constexpr size_t kThreadCount = 16;
182 std::array<std::thread, kThreadCount> threads;
183 for (std::thread &thread : threads)
184 {
185 thread = std::thread([&]() {
186 std::lock_guard<decltype(mutex)> lock(mutex);
187
188 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
189 EXPECT_EGL_SUCCESS();
190
191 EXPECT_EGL_TRUE(eglSwapBuffers(dpy, surface));
192 EXPECT_EGL_SUCCESS();
193
194 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
195 EXPECT_EGL_SUCCESS();
196 });
197 }
198
199 for (std::thread &thread : threads)
200 {
201 thread.join();
202 }
203
204 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
205 EXPECT_EGL_SUCCESS();
206 }
207
208 // Test that multiple threads can clear and readback pixels successfully at the same time
TEST_P(MultithreadingTest, MultiContextClear)209 TEST_P(MultithreadingTest, MultiContextClear)
210 {
211 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
212
213 auto testBody = [](EGLSurface surface, size_t thread) {
214 constexpr size_t kIterationsPerThread = 32;
215 for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
216 {
217 // Base the clear color on the thread and iteration indexes so every clear color is
218 // unique
219 const GLColor color(static_cast<GLubyte>(thread % 255),
220 static_cast<GLubyte>(iteration % 255), 0, 255);
221 const angle::Vector4 floatColor = color.toNormalizedVector();
222
223 glClearColor(floatColor[0], floatColor[1], floatColor[2], floatColor[3]);
224 EXPECT_GL_NO_ERROR();
225
226 glClear(GL_COLOR_BUFFER_BIT);
227 EXPECT_GL_NO_ERROR();
228
229 EXPECT_PIXEL_COLOR_EQ(0, 0, color);
230 }
231 };
232 runMultithreadedGLTest(testBody, 72);
233 }
234
235 // Verify that threads can interleave eglDestroyContext and draw calls without
236 // any crashes.
TEST_P(MultithreadingTest, MultiContextDeleteDraw)237 TEST_P(MultithreadingTest, MultiContextDeleteDraw)
238 {
239 // Skip this test on non-D3D11 backends, as it has the potential to time-out
240 // and this test was originally intended to catch a crash on the D3D11 backend.
241 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
242 ANGLE_SKIP_TEST_IF(!IsD3D11());
243
244 EGLWindow *window = getEGLWindow();
245 EGLDisplay dpy = window->getDisplay();
246 EGLConfig config = window->getConfig();
247
248 std::thread t1 = std::thread([&]() {
249 // 5000 is chosen here as it reliably reproduces the former crash.
250 for (int i = 0; i < 5000; i++)
251 {
252 EGLContext ctx1 = createMultithreadedContext(window, EGL_NO_CONTEXT);
253 EGLContext ctx2 = createMultithreadedContext(window, EGL_NO_CONTEXT);
254
255 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx2));
256 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx1));
257
258 EXPECT_EGL_TRUE(eglDestroyContext(dpy, ctx2));
259 EXPECT_EGL_TRUE(eglDestroyContext(dpy, ctx1));
260 }
261 });
262
263 std::thread t2 = std::thread([&]() {
264 EGLint pbufferAttributes[] = {
265 EGL_WIDTH, 256, EGL_HEIGHT, 256, EGL_NONE, EGL_NONE,
266 };
267
268 EGLSurface surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
269 EXPECT_EGL_SUCCESS();
270
271 auto ctx = createMultithreadedContext(window, EGL_NO_CONTEXT);
272 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
273
274 constexpr size_t kIterationsPerThread = 512;
275 constexpr size_t kDrawsPerIteration = 512;
276
277 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
278 glUseProgram(program);
279
280 GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
281
282 auto quadVertices = GetQuadVertices();
283
284 GLBuffer vertexBuffer;
285 glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
286 glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(), GL_STATIC_DRAW);
287
288 GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
289 glEnableVertexAttribArray(positionLocation);
290 glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
291 for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
292 {
293 const GLColor color(static_cast<GLubyte>(15151 % 255),
294 static_cast<GLubyte>(iteration % 255), 0, 255);
295 const angle::Vector4 floatColor = color.toNormalizedVector();
296 glUniform4fv(colorLocation, 1, floatColor.data());
297 for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
298 {
299 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
300 glDrawArrays(GL_TRIANGLES, 0, 6);
301 }
302 }
303 });
304
305 t1.join();
306 t2.join();
307 }
308
309 // Test that multiple threads can draw and readback pixels successfully at the same time
TEST_P(MultithreadingTest, MultiContextDraw)310 TEST_P(MultithreadingTest, MultiContextDraw)
311 {
312 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
313
314 ANGLE_SKIP_TEST_IF(isSwiftshader());
315
316 auto testBody = [](EGLSurface surface, size_t thread) {
317 constexpr size_t kIterationsPerThread = 32;
318 constexpr size_t kDrawsPerIteration = 500;
319
320 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
321 glUseProgram(program);
322
323 GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
324
325 auto quadVertices = GetQuadVertices();
326
327 GLBuffer vertexBuffer;
328 glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
329 glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(), GL_STATIC_DRAW);
330
331 GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
332 glEnableVertexAttribArray(positionLocation);
333 glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
334
335 for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
336 {
337 // Base the clear color on the thread and iteration indexes so every clear color is
338 // unique
339 const GLColor color(static_cast<GLubyte>(thread % 255),
340 static_cast<GLubyte>(iteration % 255), 0, 255);
341 const angle::Vector4 floatColor = color.toNormalizedVector();
342 glUniform4fv(colorLocation, 1, floatColor.data());
343
344 for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
345 {
346 glDrawArrays(GL_TRIANGLES, 0, 6);
347 }
348
349 EXPECT_PIXEL_COLOR_EQ(0, 0, color);
350 }
351 };
352 runMultithreadedGLTest(testBody, 4);
353 }
354
355 // Test that multiple threads can draw and read back pixels correctly.
356 // Using eglSwapBuffers stresses race conditions around use of QueueSerials.
TEST_P(MultithreadingTest, MultiContextDrawWithSwapBuffers)357 TEST_P(MultithreadingTest, MultiContextDrawWithSwapBuffers)
358 {
359 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
360
361 // http://anglebug.com/5099
362 ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());
363
364 EGLWindow *window = getEGLWindow();
365 EGLDisplay dpy = window->getDisplay();
366
367 auto testBody = [dpy](EGLSurface surface, size_t thread) {
368 constexpr size_t kIterationsPerThread = 100;
369 constexpr size_t kDrawsPerIteration = 10;
370
371 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
372 glUseProgram(program);
373
374 GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
375
376 auto quadVertices = GetQuadVertices();
377
378 GLBuffer vertexBuffer;
379 glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
380 glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(), GL_STATIC_DRAW);
381
382 GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
383 glEnableVertexAttribArray(positionLocation);
384 glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
385
386 for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
387 {
388 // Base the clear color on the thread and iteration indexes so every clear color is
389 // unique
390 const GLColor color(static_cast<GLubyte>(thread % 255),
391 static_cast<GLubyte>(iteration % 255), 0, 255);
392 const angle::Vector4 floatColor = color.toNormalizedVector();
393 glUniform4fv(colorLocation, 1, floatColor.data());
394
395 for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
396 {
397 glDrawArrays(GL_TRIANGLES, 0, 6);
398 }
399
400 EXPECT_EGL_TRUE(eglSwapBuffers(dpy, surface));
401 EXPECT_EGL_SUCCESS();
402
403 EXPECT_PIXEL_COLOR_EQ(0, 0, color);
404 }
405 };
406 runMultithreadedGLTest(testBody, 32);
407 }
408
409 // Test that ANGLE handles multiple threads creating and destroying resources (vertex buffer in this
410 // case). Disable defer_flush_until_endrenderpass so that glFlush will issue work to GPU in order to
411 // maximize the chance we resources can be destroyed at the wrong time.
TEST_P(MultithreadingTest, MultiContextCreateAndDeleteResources)412 TEST_P(MultithreadingTest, MultiContextCreateAndDeleteResources)
413 {
414 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
415
416 EGLWindow *window = getEGLWindow();
417 EGLDisplay dpy = window->getDisplay();
418
419 auto testBody = [dpy](EGLSurface surface, size_t thread) {
420 constexpr size_t kIterationsPerThread = 32;
421 constexpr size_t kDrawsPerIteration = 1;
422
423 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
424 glUseProgram(program);
425
426 GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
427
428 auto quadVertices = GetQuadVertices();
429
430 for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
431 {
432 GLBuffer vertexBuffer;
433 glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
434 glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(),
435 GL_STATIC_DRAW);
436
437 GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
438 glEnableVertexAttribArray(positionLocation);
439 glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
440
441 // Base the clear color on the thread and iteration indexes so every clear color is
442 // unique
443 const GLColor color(static_cast<GLubyte>(thread % 255),
444 static_cast<GLubyte>(iteration % 255), 0, 255);
445 const angle::Vector4 floatColor = color.toNormalizedVector();
446 glUniform4fv(colorLocation, 1, floatColor.data());
447
448 for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
449 {
450 glDrawArrays(GL_TRIANGLES, 0, 6);
451 }
452
453 EXPECT_EGL_TRUE(eglSwapBuffers(dpy, surface));
454 EXPECT_EGL_SUCCESS();
455
456 EXPECT_PIXEL_COLOR_EQ(0, 0, color);
457 }
458 glFinish();
459 };
460 runMultithreadedGLTest(testBody, 32);
461 }
462
TEST_P(MultithreadingTest, MultiCreateContext)463 TEST_P(MultithreadingTest, MultiCreateContext)
464 {
465 // Supported by CGL, GLX, and WGL (https://anglebug.com/4725)
466 // Not supported on Ozone (https://crbug.com/1103009)
467 ANGLE_SKIP_TEST_IF(!(IsWindows() || IsLinux() || IsOSX()) || IsOzone());
468
469 EGLWindow *window = getEGLWindow();
470 EGLDisplay dpy = window->getDisplay();
471 EGLContext ctx = window->getContext();
472 EGLSurface surface = window->getSurface();
473
474 // Un-makeCurrent the test window's context
475 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
476 EXPECT_EGL_SUCCESS();
477
478 constexpr size_t kThreadCount = 16;
479 std::atomic<uint32_t> barrier(0);
480 std::vector<std::thread> threads(kThreadCount);
481 std::vector<EGLContext> contexts(kThreadCount);
482 for (size_t threadIdx = 0; threadIdx < kThreadCount; threadIdx++)
483 {
484 threads[threadIdx] = std::thread([&, threadIdx]() {
485 contexts[threadIdx] = EGL_NO_CONTEXT;
486 {
487 contexts[threadIdx] = createMultithreadedContext(window, EGL_NO_CONTEXT);
488 EXPECT_NE(EGL_NO_CONTEXT, contexts[threadIdx]);
489
490 barrier++;
491 }
492
493 while (barrier < kThreadCount)
494 {
495 }
496
497 {
498 EXPECT_TRUE(eglDestroyContext(dpy, contexts[threadIdx]));
499 }
500 });
501 }
502
503 for (std::thread &thread : threads)
504 {
505 thread.join();
506 }
507
508 // Re-make current the test window's context for teardown.
509 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
510 EXPECT_EGL_SUCCESS();
511 }
512
textureThreadFunction(bool useDraw)513 void MultithreadingTestES3::textureThreadFunction(bool useDraw)
514 {
515 EGLWindow *window = getEGLWindow();
516 EGLDisplay dpy = window->getDisplay();
517 EGLConfig config = window->getConfig();
518 EGLSurface surface = EGL_NO_SURFACE;
519 EGLContext ctx = EGL_NO_CONTEXT;
520
521 // Initialize the pbuffer and context
522 EGLint pbufferAttributes[] = {
523 EGL_WIDTH, kSize, EGL_HEIGHT, kSize, EGL_NONE, EGL_NONE,
524 };
525 surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
526 EXPECT_EGL_SUCCESS();
527 EXPECT_NE(EGL_NO_SURFACE, surface);
528
529 ctx = createMultithreadedContext(window, window->getContext());
530 EXPECT_NE(EGL_NO_CONTEXT, ctx);
531
532 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
533 EXPECT_EGL_SUCCESS();
534
535 std::vector<GLColor> greenColor(kSize * kSize, GLColor::green);
536 std::vector<GLColor> redColor(kSize * kSize, GLColor::red);
537 ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
538 ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
539
540 glBindTexture(GL_TEXTURE_2D, mTexture2D);
541 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
542 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
543 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
544 ASSERT_GL_NO_ERROR();
545
546 GLFramebuffer fbo;
547 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
548 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture2D, 0);
549 ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
550
551 mSecondThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
552 ASSERT_GL_NO_ERROR();
553 // Force the fence to be created
554 glFlush();
555
556 // Draw something
557 while (!mExitThread)
558 {
559 std::lock_guard<decltype(mutex)> lock(mutex);
560
561 if (mMainThreadSyncObj != nullptr)
562 {
563 glWaitSync(mMainThreadSyncObj, 0, GL_TIMEOUT_IGNORED);
564 ASSERT_GL_NO_ERROR();
565 glDeleteSync(mSecondThreadSyncObj);
566 ASSERT_GL_NO_ERROR();
567 mMainThreadSyncObj = nullptr;
568 }
569 else
570 {
571 continue;
572 }
573
574 glBindTexture(GL_TEXTURE_2D, mTexture2D);
575 ASSERT_GL_NO_ERROR();
576
577 if (mDrawGreen)
578 {
579 if (useDraw)
580 {
581 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
582 drawQuad(greenProgram.get(), std::string(essl1_shaders::PositionAttrib()), 0.0f);
583 }
584 else
585 {
586 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
587 greenColor.data());
588 }
589 ASSERT_GL_NO_ERROR();
590 }
591 else
592 {
593 if (useDraw)
594 {
595 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
596 drawQuad(redProgram.get(), std::string(essl1_shaders::PositionAttrib()), 0.0f);
597 }
598 else
599 {
600 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
601 redColor.data());
602 }
603 ASSERT_GL_NO_ERROR();
604 }
605
606 ASSERT_EQ(mSecondThreadSyncObj.load(), nullptr);
607 mSecondThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
608 ASSERT_GL_NO_ERROR();
609 // Force the fence to be created
610 glFlush();
611
612 mDrawGreen = !mDrawGreen;
613 }
614
615 // Clean up
616 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
617 EXPECT_EGL_SUCCESS();
618
619 eglDestroySurface(dpy, surface);
620 eglDestroyContext(dpy, ctx);
621 }
622
623 // Test fence sync with multiple threads drawing
mainThreadDraw(bool useDraw)624 void MultithreadingTestES3::mainThreadDraw(bool useDraw)
625 {
626 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
627
628 EGLWindow *window = getEGLWindow();
629 EGLDisplay dpy = window->getDisplay();
630 EGLContext ctx = window->getContext();
631 EGLSurface surface = window->getSurface();
632 // Use odd numbers so we bounce between red and green in the final image
633 constexpr int kNumIterations = 5;
634 constexpr int kNumDraws = 5;
635
636 mDrawGreen = false;
637
638 std::thread textureThread(&MultithreadingTestES3::textureThreadFunction, this, true);
639
640 ANGLE_GL_PROGRAM(texProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
641
642 for (int iterations = 0; iterations < kNumIterations; ++iterations)
643 {
644 for (int draws = 0; draws < kNumDraws;)
645 {
646 std::lock_guard<decltype(mutex)> lock(mutex);
647
648 if (mSecondThreadSyncObj != nullptr)
649 {
650 glWaitSync(mSecondThreadSyncObj, 0, GL_TIMEOUT_IGNORED);
651 ASSERT_GL_NO_ERROR();
652 glDeleteSync(mSecondThreadSyncObj);
653 ASSERT_GL_NO_ERROR();
654 mSecondThreadSyncObj = nullptr;
655 }
656 else
657 {
658 continue;
659 }
660
661 glBindFramebuffer(GL_FRAMEBUFFER, 0);
662 glBindTexture(GL_TEXTURE_2D, mTexture2D);
663 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
664 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
665 glUseProgram(texProgram);
666 drawQuad(texProgram.get(), std::string(essl1_shaders::PositionAttrib()), 0.0f);
667
668 ASSERT_EQ(mMainThreadSyncObj.load(), nullptr);
669 mMainThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
670 ASSERT_GL_NO_ERROR();
671 // Force the fence to be created
672 glFlush();
673
674 ++draws;
675 }
676
677 ASSERT_GL_NO_ERROR();
678 swapBuffers();
679 }
680
681 mExitThread = true;
682 textureThread.join();
683
684 ASSERT_GL_NO_ERROR();
685 GLColor color;
686 if (mDrawGreen)
687 {
688 color = GLColor::green;
689 }
690 else
691 {
692 color = GLColor::red;
693 }
694 EXPECT_PIXEL_RECT_EQ(0, 0, kSize, kSize, color);
695
696 // Re-make current the test window's context for teardown.
697 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
698 EXPECT_EGL_SUCCESS();
699 }
700
701 // Test that glFenceSync/glWaitSync works correctly with multithreading.
702 // Main thread: Samples from the shared texture to draw to the default FBO.
703 // Secondary (Texture) thread: Draws to the shared texture, which the Main thread samples from.
704 // The overall execution flow is:
705 // Main Thread:
706 // 1. Wait for the mSecondThreadSyncObj fence object to be created.
707 // - This fence object is used by synchronize access to the shared texture by indicating that the
708 // Secondary thread's draws to the texture have all completed and it's now safe to sample from
709 // it.
710 // 2. Once the fence is created, add a glWaitSync(mSecondThreadSyncObj) to the command stream and
711 // then delete it.
712 // 3. Draw, sampling from the shared texture.
713 // 4. Create a new mMainThreadSyncObj.
714 // - This fence object is used to synchronize access to the shared texture by indicating that the
715 // Main thread's draws are no longer sampling from the texture, so it's now safe for the
716 // Secondary thread to draw to it again with a new color.
717 // Secondary (Texture) Thread:
718 // 1. Wait for the mMainThreadSyncObj fence object to be created.
719 // 2. Once the fence is created, add a glWaitSync(mMainThreadSyncObj) to the command stream and then
720 // delete it.
721 // 3. Draw/Fill the texture.
722 // 4. Create a new mSecondThreadSyncObj.
723 //
724 // These threads loop for the specified number of iterations, drawing/sampling the shared texture
725 // with the necessary glFlush()s and occasional eglSwapBuffers() to mimic a real multithreaded GLES
726 // application.
TEST_P(MultithreadingTestES3, MultithreadFenceDraw)727 TEST_P(MultithreadingTestES3, MultithreadFenceDraw)
728 {
729 // http://anglebug.com/5418
730 ANGLE_SKIP_TEST_IF(IsLinux() && IsVulkan() && (IsIntel() || IsSwiftshaderDevice()));
731
732 // Have the secondary thread use glDrawArrays()
733 mainThreadDraw(true);
734 }
735
736 // Same as MultithreadFenceDraw, but with the secondary thread using glTexImage2D rather than
737 // glDrawArrays.
TEST_P(MultithreadingTestES3, MultithreadFenceTexImage)738 TEST_P(MultithreadingTestES3, MultithreadFenceTexImage)
739 {
740 // http://anglebug.com/5418
741 ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel() && IsVulkan());
742
743 // http://anglebug.com/5439
744 ANGLE_SKIP_TEST_IF(IsLinux() && isSwiftshader());
745
746 // Have the secondary thread use glTexImage2D()
747 mainThreadDraw(false);
748 }
749
750 // Test that waiting on a sync object that hasn't been flushed and without a current context returns
751 // TIMEOUT_EXPIRED or CONDITION_SATISFIED, but doesn't generate an error or crash.
TEST_P(MultithreadingTest, NoFlushNoContextReturnsTimeout)752 TEST_P(MultithreadingTest, NoFlushNoContextReturnsTimeout)
753 {
754 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
755 ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
756
757 std::mutex mutex;
758
759 EGLWindow *window = getEGLWindow();
760 EGLDisplay dpy = window->getDisplay();
761
762 glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
763 glClear(GL_COLOR_BUFFER_BIT);
764
765 EGLSyncKHR sync = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, nullptr);
766 EXPECT_NE(sync, EGL_NO_SYNC_KHR);
767
768 std::thread thread = std::thread([&]() {
769 std::lock_guard<decltype(mutex)> lock(mutex);
770 // Make sure there is no active context on this thread.
771 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
772 EXPECT_EGL_SUCCESS();
773 // Don't wait forever to make sure the test terminates
774 constexpr GLuint64 kTimeout = 1'000'000'000; // 1 second
775 int result = eglClientWaitSyncKHR(dpy, sync, 0, kTimeout);
776 // We typically expect to get back TIMEOUT_EXPIRED since the sync object was never flushed.
777 // However, the OpenGL ES backend returns CONDITION_SATISFIED, which is also a passing
778 // result.
779 ASSERT_TRUE(result == EGL_TIMEOUT_EXPIRED_KHR || result == EGL_CONDITION_SATISFIED_KHR);
780 });
781
782 thread.join();
783
784 EXPECT_EGL_TRUE(eglDestroySyncKHR(dpy, sync));
785 }
786
787 // Test that waiting on sync object that hasn't been flushed yet, but is later flushed by another
788 // thread, correctly returns when the fence is signalled without a timeout.
789 TEST_P(MultithreadingTest, CreateFenceThreadAClientWaitSyncThreadBDelayedFlush)
790 {
791 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
792 ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
793 // TODO: Fails on Pixel 4 with OpenGLES backend.
794 ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());
795
796 EGLWindow *window = getEGLWindow();
797 EGLDisplay dpy = window->getDisplay();
798 EGLConfig config = window->getConfig();
799 EGLSurface surface;
800 EGLContext context;
801 constexpr EGLint kPBufferSize = 256;
802 // Initialize the pbuffer and context
803 EGLint pbufferAttributes[] = {
804 EGL_WIDTH, kPBufferSize, EGL_HEIGHT, kPBufferSize, EGL_NONE, EGL_NONE,
805 };
806
807 // Create 2 surfaces, one for each thread
808 surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
809 EXPECT_EGL_SUCCESS();
810 // Create 2 shared contexts, one for each thread
811 context = window->createContext(EGL_NO_CONTEXT, nullptr);
812 EXPECT_NE(EGL_NO_CONTEXT, context);
813 // Sync object
814 EGLSyncKHR sync = EGL_NO_SYNC_KHR;
815
816 // Synchronization tools to ensure the two threads are interleaved as designed by this test.
817 std::mutex mutex;
818 std::condition_variable condVar;
819
820 enum class Step
821 {
822 Start,
823 Thread0Clear,
824 Thread1CreateFence,
825 Thread0ClientWaitSync,
826 Thread1Flush,
827 Finish,
828 Abort,
829 };
830 Step currentStep = Step::Start;
831
832 std::thread thread0 = std::thread([&]() {
833 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
834
835 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start));
836
837 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
838
839 // Do work.
840 glClearColor(1.0, 0.0, 0.0, 1.0);
841 glClear(GL_COLOR_BUFFER_BIT);
842
843 // Wait for thread 1 to clear.
844 threadSynchronization.nextStep(Step::Thread0Clear);
845 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1CreateFence));
846
847 // Wait on the sync object, but do *not* flush it, since the other thread will flush.
848 constexpr GLuint64 kTimeout = 2'000'000'000; // 1 second
849 threadSynchronization.nextStep(Step::Thread0ClientWaitSync);
850 ASSERT_EQ(EGL_CONDITION_SATISFIED_KHR, eglClientWaitSyncKHR(dpy, sync, 0, kTimeout));
851
852 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
853 });
854
855 std::thread thread1 = std::thread([&]() {
856 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
857
858 // Wait for thread 0 to clear.
859 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Clear));
860
861 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
862
863 // Do work.
864 glClearColor(0.0, 1.0, 0.0, 1.0);
865 glClear(GL_COLOR_BUFFER_BIT);
866
867 sync = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, nullptr);
868 EXPECT_NE(sync, EGL_NO_SYNC_KHR);
869
870 // Wait for the thread 0 to eglClientWaitSyncKHR().
871 threadSynchronization.nextStep(Step::Thread1CreateFence);
872 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0ClientWaitSync));
873
874 // Wait a little to give thread 1 time to wait on the sync object before flushing it.
875 angle::Sleep(500);
876 glFlush();
877
878 // Clean up
879 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
880
881 threadSynchronization.nextStep(Step::Finish);
882 });
883
884 thread0.join();
885 thread1.join();
886
887 // Clean up
888 if (surface != EGL_NO_SURFACE)
889 {
890 eglDestroySurface(dpy, surface);
891 }
892 if (context != EGL_NO_CONTEXT)
893 {
894 eglDestroyContext(dpy, context);
895 }
896
897 ASSERT_NE(currentStep, Step::Abort);
898 }
899
900 // TODO(geofflang): Test sharing a program between multiple shared contexts on multiple threads
901
902 ANGLE_INSTANTIATE_TEST(MultithreadingTest,
903 ES2_OPENGL(),
904 ES3_OPENGL(),
905 ES2_OPENGLES(),
906 ES3_OPENGLES(),
907 ES3_VULKAN(),
908 ES3_VULKAN_SWIFTSHADER(),
909 ES2_D3D11(),
910 ES3_D3D11());
911
912 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MultithreadingTestES3);
913 ANGLE_INSTANTIATE_TEST(MultithreadingTestES3,
914 ES3_OPENGL(),
915 ES3_OPENGLES(),
916 ES3_VULKAN(),
917 ES3_VULKAN_SWIFTSHADER(),
918 ES3_D3D11());
919
920 } // namespace angle
921