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