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
testSetUp()148 void testSetUp() override { mTexture2D = create2DTexture(); }
149
testTearDown()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 readback pixels successfully at the same time while EGL
356 // images are bound between them
TEST_P(MultithreadingTest,MultiContextDrawWithEGLImage)357 TEST_P(MultithreadingTest, MultiContextDrawWithEGLImage)
358 {
359 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
360
361 ANGLE_SKIP_TEST_IF(isSwiftshader());
362
363 EGLWindow *window = getEGLWindow();
364 EGLDisplay dpy = window->getDisplay();
365 ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_OES_EGL_image"));
366 ANGLE_SKIP_TEST_IF(IsEGLDisplayExtensionEnabled(dpy, "EGL_KHR_gl_texture_2D_image"));
367
368 // Create a source 2D texture
369 GLTexture sourceTexture;
370 glBindTexture(GL_TEXTURE_2D, sourceTexture);
371 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::red);
372
373 // Disable mipmapping
374 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
375 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
376
377 ASSERT_GL_NO_ERROR();
378
379 // Create an image from the source texture
380 constexpr EGLint attribs[] = {
381 EGL_IMAGE_PRESERVED,
382 EGL_TRUE,
383 EGL_NONE,
384 };
385 EGLImageKHR image = eglCreateImageKHR(
386 dpy, window->getContext(), EGL_GL_TEXTURE_2D_KHR,
387 reinterpret_cast<EGLClientBuffer>(static_cast<size_t>(sourceTexture.get())), attribs);
388
389 auto testBody = [image](EGLSurface surface, size_t thread) {
390 constexpr size_t kIterationsPerThread = 32;
391 constexpr size_t kDrawsPerIteration = 500;
392
393 // Just bind the EGL image to this context and leave it bound while drawing
394 GLTexture destTexture;
395 glBindTexture(GL_TEXTURE_EXTERNAL_OES, destTexture);
396 glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image);
397
398 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
399 glUseProgram(program);
400
401 GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
402
403 auto quadVertices = GetQuadVertices();
404
405 GLBuffer vertexBuffer;
406 glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
407 glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(), GL_STATIC_DRAW);
408
409 GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
410 glEnableVertexAttribArray(positionLocation);
411 glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
412
413 for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
414 {
415 // Base the clear color on the thread and iteration indexes so every clear color is
416 // unique
417 const GLColor color(static_cast<GLubyte>(thread % 255),
418 static_cast<GLubyte>(iteration % 255), 0, 255);
419 const angle::Vector4 floatColor = color.toNormalizedVector();
420 glUniform4fv(colorLocation, 1, floatColor.data());
421
422 for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
423 {
424 glDrawArrays(GL_TRIANGLES, 0, 6);
425 }
426
427 EXPECT_PIXEL_COLOR_EQ(0, 0, color);
428 }
429 };
430 runMultithreadedGLTest(testBody, 4);
431
432 eglDestroyImage(dpy, image);
433 }
434
435 // Test that multiple threads can draw and read back pixels correctly.
436 // Using eglSwapBuffers stresses race conditions around use of QueueSerials.
TEST_P(MultithreadingTest,MultiContextDrawWithSwapBuffers)437 TEST_P(MultithreadingTest, MultiContextDrawWithSwapBuffers)
438 {
439 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
440
441 // http://anglebug.com/5099
442 ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());
443
444 EGLWindow *window = getEGLWindow();
445 EGLDisplay dpy = window->getDisplay();
446
447 auto testBody = [dpy](EGLSurface surface, size_t thread) {
448 constexpr size_t kIterationsPerThread = 100;
449 constexpr size_t kDrawsPerIteration = 10;
450
451 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
452 glUseProgram(program);
453
454 GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
455
456 auto quadVertices = GetQuadVertices();
457
458 GLBuffer vertexBuffer;
459 glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
460 glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(), GL_STATIC_DRAW);
461
462 GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
463 glEnableVertexAttribArray(positionLocation);
464 glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
465
466 for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
467 {
468 // Base the clear color on the thread and iteration indexes so every clear color is
469 // unique
470 const GLColor color(static_cast<GLubyte>(thread % 255),
471 static_cast<GLubyte>(iteration % 255), 0, 255);
472 const angle::Vector4 floatColor = color.toNormalizedVector();
473 glUniform4fv(colorLocation, 1, floatColor.data());
474
475 for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
476 {
477 glDrawArrays(GL_TRIANGLES, 0, 6);
478 }
479
480 EXPECT_EGL_TRUE(eglSwapBuffers(dpy, surface));
481 EXPECT_EGL_SUCCESS();
482
483 EXPECT_PIXEL_COLOR_EQ(0, 0, color);
484 }
485 };
486 runMultithreadedGLTest(testBody, 32);
487 }
488
489 // Test that ANGLE handles multiple threads creating and destroying resources (vertex buffer in this
490 // case). Disable defer_flush_until_endrenderpass so that glFlush will issue work to GPU in order to
491 // maximize the chance we resources can be destroyed at the wrong time.
TEST_P(MultithreadingTest,MultiContextCreateAndDeleteResources)492 TEST_P(MultithreadingTest, MultiContextCreateAndDeleteResources)
493 {
494 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
495
496 EGLWindow *window = getEGLWindow();
497 EGLDisplay dpy = window->getDisplay();
498
499 auto testBody = [dpy](EGLSurface surface, size_t thread) {
500 constexpr size_t kIterationsPerThread = 32;
501 constexpr size_t kDrawsPerIteration = 1;
502
503 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
504 glUseProgram(program);
505
506 GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
507
508 auto quadVertices = GetQuadVertices();
509
510 for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
511 {
512 GLBuffer vertexBuffer;
513 glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
514 glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(),
515 GL_STATIC_DRAW);
516
517 GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
518 glEnableVertexAttribArray(positionLocation);
519 glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
520
521 // Base the clear color on the thread and iteration indexes so every clear color is
522 // unique
523 const GLColor color(static_cast<GLubyte>(thread % 255),
524 static_cast<GLubyte>(iteration % 255), 0, 255);
525 const angle::Vector4 floatColor = color.toNormalizedVector();
526 glUniform4fv(colorLocation, 1, floatColor.data());
527
528 for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
529 {
530 glDrawArrays(GL_TRIANGLES, 0, 6);
531 }
532
533 EXPECT_EGL_TRUE(eglSwapBuffers(dpy, surface));
534 EXPECT_EGL_SUCCESS();
535
536 EXPECT_PIXEL_COLOR_EQ(0, 0, color);
537 }
538 glFinish();
539 };
540 runMultithreadedGLTest(testBody, 32);
541 }
542
TEST_P(MultithreadingTest,MultiCreateContext)543 TEST_P(MultithreadingTest, MultiCreateContext)
544 {
545 // Supported by CGL, GLX, and WGL (https://anglebug.com/4725)
546 // Not supported on Ozone (https://crbug.com/1103009)
547 ANGLE_SKIP_TEST_IF(!(IsWindows() || IsLinux() || IsOSX()) || IsOzone());
548
549 EGLWindow *window = getEGLWindow();
550 EGLDisplay dpy = window->getDisplay();
551 EGLContext ctx = window->getContext();
552 EGLSurface surface = window->getSurface();
553
554 // Un-makeCurrent the test window's context
555 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
556 EXPECT_EGL_SUCCESS();
557
558 constexpr size_t kThreadCount = 16;
559 std::atomic<uint32_t> barrier(0);
560 std::vector<std::thread> threads(kThreadCount);
561 std::vector<EGLContext> contexts(kThreadCount);
562 for (size_t threadIdx = 0; threadIdx < kThreadCount; threadIdx++)
563 {
564 threads[threadIdx] = std::thread([&, threadIdx]() {
565 contexts[threadIdx] = EGL_NO_CONTEXT;
566 {
567 contexts[threadIdx] = createMultithreadedContext(window, EGL_NO_CONTEXT);
568 EXPECT_NE(EGL_NO_CONTEXT, contexts[threadIdx]);
569
570 barrier++;
571 }
572
573 while (barrier < kThreadCount)
574 {
575 }
576
577 {
578 EXPECT_TRUE(eglDestroyContext(dpy, contexts[threadIdx]));
579 }
580 });
581 }
582
583 for (std::thread &thread : threads)
584 {
585 thread.join();
586 }
587
588 // Re-make current the test window's context for teardown.
589 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
590 EXPECT_EGL_SUCCESS();
591 }
592
textureThreadFunction(bool useDraw)593 void MultithreadingTestES3::textureThreadFunction(bool useDraw)
594 {
595 EGLWindow *window = getEGLWindow();
596 EGLDisplay dpy = window->getDisplay();
597 EGLConfig config = window->getConfig();
598 EGLSurface surface = EGL_NO_SURFACE;
599 EGLContext ctx = EGL_NO_CONTEXT;
600
601 // Initialize the pbuffer and context
602 EGLint pbufferAttributes[] = {
603 EGL_WIDTH, kSize, EGL_HEIGHT, kSize, EGL_NONE, EGL_NONE,
604 };
605 surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
606 EXPECT_EGL_SUCCESS();
607 EXPECT_NE(EGL_NO_SURFACE, surface);
608
609 ctx = createMultithreadedContext(window, window->getContext());
610 EXPECT_NE(EGL_NO_CONTEXT, ctx);
611
612 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
613 EXPECT_EGL_SUCCESS();
614
615 std::vector<GLColor> greenColor(kSize * kSize, GLColor::green);
616 std::vector<GLColor> redColor(kSize * kSize, GLColor::red);
617 ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
618 ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
619
620 glBindTexture(GL_TEXTURE_2D, mTexture2D);
621 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
622 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
623 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
624 ASSERT_GL_NO_ERROR();
625
626 GLFramebuffer fbo;
627 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
628 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture2D, 0);
629 ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
630
631 mSecondThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
632 ASSERT_GL_NO_ERROR();
633 // Force the fence to be created
634 glFlush();
635
636 // Draw something
637 while (!mExitThread)
638 {
639 std::lock_guard<decltype(mutex)> lock(mutex);
640
641 if (mMainThreadSyncObj != nullptr)
642 {
643 glWaitSync(mMainThreadSyncObj, 0, GL_TIMEOUT_IGNORED);
644 ASSERT_GL_NO_ERROR();
645 glDeleteSync(mSecondThreadSyncObj);
646 ASSERT_GL_NO_ERROR();
647 mMainThreadSyncObj = nullptr;
648 }
649 else
650 {
651 continue;
652 }
653
654 glBindTexture(GL_TEXTURE_2D, mTexture2D);
655 ASSERT_GL_NO_ERROR();
656
657 if (mDrawGreen)
658 {
659 if (useDraw)
660 {
661 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
662 drawQuad(greenProgram.get(), std::string(essl1_shaders::PositionAttrib()), 0.0f);
663 }
664 else
665 {
666 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
667 greenColor.data());
668 }
669 ASSERT_GL_NO_ERROR();
670 }
671 else
672 {
673 if (useDraw)
674 {
675 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
676 drawQuad(redProgram.get(), std::string(essl1_shaders::PositionAttrib()), 0.0f);
677 }
678 else
679 {
680 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
681 redColor.data());
682 }
683 ASSERT_GL_NO_ERROR();
684 }
685
686 ASSERT_EQ(mSecondThreadSyncObj.load(), nullptr);
687 mSecondThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
688 ASSERT_GL_NO_ERROR();
689 // Force the fence to be created
690 glFlush();
691
692 mDrawGreen = !mDrawGreen;
693 }
694
695 // Clean up
696 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
697 EXPECT_EGL_SUCCESS();
698
699 eglDestroySurface(dpy, surface);
700 eglDestroyContext(dpy, ctx);
701 }
702
703 // Test fence sync with multiple threads drawing
mainThreadDraw(bool useDraw)704 void MultithreadingTestES3::mainThreadDraw(bool useDraw)
705 {
706 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
707
708 EGLWindow *window = getEGLWindow();
709 EGLDisplay dpy = window->getDisplay();
710 EGLContext ctx = window->getContext();
711 EGLSurface surface = window->getSurface();
712 // Use odd numbers so we bounce between red and green in the final image
713 constexpr int kNumIterations = 5;
714 constexpr int kNumDraws = 5;
715
716 mDrawGreen = false;
717
718 std::thread textureThread(&MultithreadingTestES3::textureThreadFunction, this, true);
719
720 ANGLE_GL_PROGRAM(texProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
721
722 for (int iterations = 0; iterations < kNumIterations; ++iterations)
723 {
724 for (int draws = 0; draws < kNumDraws;)
725 {
726 std::lock_guard<decltype(mutex)> lock(mutex);
727
728 if (mSecondThreadSyncObj != nullptr)
729 {
730 glWaitSync(mSecondThreadSyncObj, 0, GL_TIMEOUT_IGNORED);
731 ASSERT_GL_NO_ERROR();
732 glDeleteSync(mSecondThreadSyncObj);
733 ASSERT_GL_NO_ERROR();
734 mSecondThreadSyncObj = nullptr;
735 }
736 else
737 {
738 continue;
739 }
740
741 glBindFramebuffer(GL_FRAMEBUFFER, 0);
742 glBindTexture(GL_TEXTURE_2D, mTexture2D);
743 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
744 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
745 glUseProgram(texProgram);
746 drawQuad(texProgram.get(), std::string(essl1_shaders::PositionAttrib()), 0.0f);
747
748 ASSERT_EQ(mMainThreadSyncObj.load(), nullptr);
749 mMainThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
750 ASSERT_GL_NO_ERROR();
751 // Force the fence to be created
752 glFlush();
753
754 ++draws;
755 }
756
757 ASSERT_GL_NO_ERROR();
758 swapBuffers();
759 }
760
761 mExitThread = true;
762 textureThread.join();
763
764 ASSERT_GL_NO_ERROR();
765 GLColor color;
766 if (mDrawGreen)
767 {
768 color = GLColor::green;
769 }
770 else
771 {
772 color = GLColor::red;
773 }
774 EXPECT_PIXEL_RECT_EQ(0, 0, kSize, kSize, color);
775
776 // Re-make current the test window's context for teardown.
777 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
778 EXPECT_EGL_SUCCESS();
779 }
780
781 // Test that glFenceSync/glWaitSync works correctly with multithreading.
782 // Main thread: Samples from the shared texture to draw to the default FBO.
783 // Secondary (Texture) thread: Draws to the shared texture, which the Main thread samples from.
784 // The overall execution flow is:
785 // Main Thread:
786 // 1. Wait for the mSecondThreadSyncObj fence object to be created.
787 // - This fence object is used by synchronize access to the shared texture by indicating that the
788 // Secondary thread's draws to the texture have all completed and it's now safe to sample from
789 // it.
790 // 2. Once the fence is created, add a glWaitSync(mSecondThreadSyncObj) to the command stream and
791 // then delete it.
792 // 3. Draw, sampling from the shared texture.
793 // 4. Create a new mMainThreadSyncObj.
794 // - This fence object is used to synchronize access to the shared texture by indicating that the
795 // Main thread's draws are no longer sampling from the texture, so it's now safe for the
796 // Secondary thread to draw to it again with a new color.
797 // Secondary (Texture) Thread:
798 // 1. Wait for the mMainThreadSyncObj fence object to be created.
799 // 2. Once the fence is created, add a glWaitSync(mMainThreadSyncObj) to the command stream and then
800 // delete it.
801 // 3. Draw/Fill the texture.
802 // 4. Create a new mSecondThreadSyncObj.
803 //
804 // These threads loop for the specified number of iterations, drawing/sampling the shared texture
805 // with the necessary glFlush()s and occasional eglSwapBuffers() to mimic a real multithreaded GLES
806 // application.
TEST_P(MultithreadingTestES3,MultithreadFenceDraw)807 TEST_P(MultithreadingTestES3, MultithreadFenceDraw)
808 {
809 // http://anglebug.com/5418
810 ANGLE_SKIP_TEST_IF(IsLinux() && IsVulkan() && (IsIntel() || isSwiftshader()));
811
812 // Have the secondary thread use glDrawArrays()
813 mainThreadDraw(true);
814 }
815
816 // Same as MultithreadFenceDraw, but with the secondary thread using glTexImage2D rather than
817 // glDrawArrays.
TEST_P(MultithreadingTestES3,MultithreadFenceTexImage)818 TEST_P(MultithreadingTestES3, MultithreadFenceTexImage)
819 {
820 // http://anglebug.com/5418
821 ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel() && IsVulkan());
822
823 // http://anglebug.com/5439
824 ANGLE_SKIP_TEST_IF(IsLinux() && isSwiftshader());
825
826 // Have the secondary thread use glTexImage2D()
827 mainThreadDraw(false);
828 }
829
830 // Test that waiting on a sync object that hasn't been flushed and without a current context returns
831 // TIMEOUT_EXPIRED or CONDITION_SATISFIED, but doesn't generate an error or crash.
TEST_P(MultithreadingTest,NoFlushNoContextReturnsTimeout)832 TEST_P(MultithreadingTest, NoFlushNoContextReturnsTimeout)
833 {
834 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
835 ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
836
837 std::mutex mutex;
838
839 EGLWindow *window = getEGLWindow();
840 EGLDisplay dpy = window->getDisplay();
841
842 glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
843 glClear(GL_COLOR_BUFFER_BIT);
844
845 EGLSyncKHR sync = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, nullptr);
846 EXPECT_NE(sync, EGL_NO_SYNC_KHR);
847
848 std::thread thread = std::thread([&]() {
849 std::lock_guard<decltype(mutex)> lock(mutex);
850 // Make sure there is no active context on this thread.
851 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
852 EXPECT_EGL_SUCCESS();
853 // Don't wait forever to make sure the test terminates
854 constexpr GLuint64 kTimeout = 1'000'000'000; // 1 second
855 int result = eglClientWaitSyncKHR(dpy, sync, 0, kTimeout);
856 // We typically expect to get back TIMEOUT_EXPIRED since the sync object was never flushed.
857 // However, the OpenGL ES backend returns CONDITION_SATISFIED, which is also a passing
858 // result.
859 ASSERT_TRUE(result == EGL_TIMEOUT_EXPIRED_KHR || result == EGL_CONDITION_SATISFIED_KHR);
860 });
861
862 thread.join();
863
864 EXPECT_EGL_TRUE(eglDestroySyncKHR(dpy, sync));
865 }
866
867 // Test that waiting on sync object that hasn't been flushed yet, but is later flushed by another
868 // thread, correctly returns when the fence is signalled without a timeout.
TEST_P(MultithreadingTest,CreateFenceThreadAClientWaitSyncThreadBDelayedFlush)869 TEST_P(MultithreadingTest, CreateFenceThreadAClientWaitSyncThreadBDelayedFlush)
870 {
871 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
872 ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
873 // TODO: Fails on Pixel 4 with OpenGLES backend.
874 ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());
875
876 EGLWindow *window = getEGLWindow();
877 EGLDisplay dpy = window->getDisplay();
878 EGLConfig config = window->getConfig();
879 EGLSurface surface;
880 EGLContext context;
881 constexpr EGLint kPBufferSize = 256;
882 // Initialize the pbuffer and context
883 EGLint pbufferAttributes[] = {
884 EGL_WIDTH, kPBufferSize, EGL_HEIGHT, kPBufferSize, EGL_NONE, EGL_NONE,
885 };
886
887 // Create 2 surfaces, one for each thread
888 surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
889 EXPECT_EGL_SUCCESS();
890 // Create 2 shared contexts, one for each thread
891 context = window->createContext(EGL_NO_CONTEXT, nullptr);
892 EXPECT_NE(EGL_NO_CONTEXT, context);
893 // Sync object
894 EGLSyncKHR sync = EGL_NO_SYNC_KHR;
895
896 // Synchronization tools to ensure the two threads are interleaved as designed by this test.
897 std::mutex mutex;
898 std::condition_variable condVar;
899
900 enum class Step
901 {
902 Start,
903 Thread0Clear,
904 Thread1CreateFence,
905 Thread0ClientWaitSync,
906 Thread1Flush,
907 Finish,
908 Abort,
909 };
910 Step currentStep = Step::Start;
911
912 std::thread thread0 = std::thread([&]() {
913 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
914
915 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start));
916
917 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
918
919 // Do work.
920 glClearColor(1.0, 0.0, 0.0, 1.0);
921 glClear(GL_COLOR_BUFFER_BIT);
922
923 // Wait for thread 1 to clear.
924 threadSynchronization.nextStep(Step::Thread0Clear);
925 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1CreateFence));
926
927 // Wait on the sync object, but do *not* flush it, since the other thread will flush.
928 constexpr GLuint64 kTimeout = 2'000'000'000; // 1 second
929 threadSynchronization.nextStep(Step::Thread0ClientWaitSync);
930 ASSERT_EQ(EGL_CONDITION_SATISFIED_KHR, eglClientWaitSyncKHR(dpy, sync, 0, kTimeout));
931
932 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
933 });
934
935 std::thread thread1 = std::thread([&]() {
936 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
937
938 // Wait for thread 0 to clear.
939 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Clear));
940
941 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
942
943 // Do work.
944 glClearColor(0.0, 1.0, 0.0, 1.0);
945 glClear(GL_COLOR_BUFFER_BIT);
946
947 sync = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, nullptr);
948 EXPECT_NE(sync, EGL_NO_SYNC_KHR);
949
950 // Wait for the thread 0 to eglClientWaitSyncKHR().
951 threadSynchronization.nextStep(Step::Thread1CreateFence);
952 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0ClientWaitSync));
953
954 // Wait a little to give thread 1 time to wait on the sync object before flushing it.
955 angle::Sleep(500);
956 glFlush();
957
958 // Clean up
959 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
960
961 threadSynchronization.nextStep(Step::Finish);
962 });
963
964 thread0.join();
965 thread1.join();
966
967 // Clean up
968 if (surface != EGL_NO_SURFACE)
969 {
970 eglDestroySurface(dpy, surface);
971 }
972 if (context != EGL_NO_CONTEXT)
973 {
974 eglDestroyContext(dpy, context);
975 }
976
977 ASSERT_NE(currentStep, Step::Abort);
978 }
979
980 // TODO(geofflang): Test sharing a program between multiple shared contexts on multiple threads
981
982 ANGLE_INSTANTIATE_TEST(MultithreadingTest,
983 ES2_OPENGL(),
984 ES3_OPENGL(),
985 ES2_OPENGLES(),
986 ES3_OPENGLES(),
987 ES3_VULKAN(),
988 ES3_VULKAN_SWIFTSHADER(),
989 ES2_D3D11(),
990 ES3_D3D11());
991
992 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MultithreadingTestES3);
993 ANGLE_INSTANTIATE_TEST(MultithreadingTestES3,
994 ES3_OPENGL(),
995 ES3_OPENGLES(),
996 ES3_VULKAN(),
997 ES3_VULKAN_SWIFTSHADER(),
998 ES3_D3D11());
999
1000 } // namespace angle
1001