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 "test_utils/ANGLETest.h"
9 #include "test_utils/MultiThreadSteps.h"
10 #include "test_utils/gl_raii.h"
11 #include "util/EGLWindow.h"
12 #include "util/test_utils.h"
13
14 #include <atomic>
15 #include <mutex>
16 #include <thread>
17
18 namespace angle
19 {
20
21 class MultithreadingTest : public ANGLETest<>
22 {
23 public:
24 static constexpr uint32_t kSize = 512;
25
26 protected:
MultithreadingTest()27 MultithreadingTest()
28 {
29 setWindowWidth(kSize);
30 setWindowHeight(kSize);
31 setConfigRedBits(8);
32 setConfigGreenBits(8);
33 setConfigBlueBits(8);
34 setConfigAlphaBits(8);
35 }
36
hasFenceSyncExtension() const37 bool hasFenceSyncExtension() const
38 {
39 return IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(), "EGL_KHR_fence_sync");
40 }
hasWaitSyncExtension() const41 bool hasWaitSyncExtension() const
42 {
43 return hasFenceSyncExtension() &&
44 IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(), "EGL_KHR_wait_sync");
45 }
hasGLSyncExtension() const46 bool hasGLSyncExtension() const { return IsGLExtensionEnabled("GL_OES_EGL_sync"); }
47
createMultithreadedContext(EGLWindow * window,EGLContext shareCtx)48 EGLContext createMultithreadedContext(EGLWindow *window, EGLContext shareCtx)
49 {
50 EGLint attribs[] = {EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE, mVirtualizationGroup++,
51 EGL_NONE};
52 if (!IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(),
53 "EGL_ANGLE_context_virtualization"))
54 {
55 attribs[0] = EGL_NONE;
56 }
57
58 return window->createContext(shareCtx, attribs);
59 }
60
runMultithreadedGLTest(std::function<void (EGLSurface surface,size_t threadIndex)> testBody,size_t threadCount)61 void runMultithreadedGLTest(
62 std::function<void(EGLSurface surface, size_t threadIndex)> testBody,
63 size_t threadCount)
64 {
65 std::mutex mutex;
66
67 EGLWindow *window = getEGLWindow();
68 EGLDisplay dpy = window->getDisplay();
69 EGLConfig config = window->getConfig();
70
71 constexpr EGLint kPBufferSize = 256;
72
73 std::vector<std::thread> threads(threadCount);
74 for (size_t threadIdx = 0; threadIdx < threadCount; threadIdx++)
75 {
76 threads[threadIdx] = std::thread([&, threadIdx]() {
77 EGLSurface surface = EGL_NO_SURFACE;
78 EGLContext ctx = EGL_NO_CONTEXT;
79
80 {
81 std::lock_guard<decltype(mutex)> lock(mutex);
82
83 // Initialize the pbuffer and context
84 EGLint pbufferAttributes[] = {
85 EGL_WIDTH, kPBufferSize, EGL_HEIGHT, kPBufferSize, EGL_NONE, EGL_NONE,
86 };
87 surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
88 EXPECT_EGL_SUCCESS();
89
90 ctx = createMultithreadedContext(window, EGL_NO_CONTEXT);
91 EXPECT_NE(EGL_NO_CONTEXT, ctx);
92
93 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
94 EXPECT_EGL_SUCCESS();
95 }
96
97 testBody(surface, threadIdx);
98
99 {
100 std::lock_guard<decltype(mutex)> lock(mutex);
101
102 // Clean up
103 EXPECT_EGL_TRUE(
104 eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
105 EXPECT_EGL_SUCCESS();
106
107 eglDestroySurface(dpy, surface);
108 eglDestroyContext(dpy, ctx);
109 }
110 });
111 }
112
113 for (std::thread &thread : threads)
114 {
115 thread.join();
116 }
117 }
118
119 std::atomic<EGLint> mVirtualizationGroup;
120 };
121
122 class MultithreadingTestES3 : public MultithreadingTest
123 {
124 public:
125 void textureThreadFunction(bool useDraw);
126 void mainThreadDraw(bool useDraw);
127
128 protected:
MultithreadingTestES3()129 MultithreadingTestES3()
130 : mTexture2D(0), mExitThread(false), mMainThreadSyncObj(NULL), mSecondThreadSyncObj(NULL)
131 {
132 setWindowWidth(kSize);
133 setWindowHeight(kSize);
134 setConfigRedBits(8);
135 setConfigGreenBits(8);
136 setConfigBlueBits(8);
137 setConfigAlphaBits(8);
138 }
139
create2DTexture()140 GLuint create2DTexture()
141 {
142 GLuint texture2D;
143 glGenTextures(1, &texture2D);
144 glActiveTexture(GL_TEXTURE0);
145 glBindTexture(GL_TEXTURE_2D, texture2D);
146 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
147 nullptr);
148 EXPECT_GL_NO_ERROR();
149 return texture2D;
150 }
151
testSetUp()152 void testSetUp() override { mTexture2D = create2DTexture(); }
153
testTearDown()154 void testTearDown() override
155 {
156 if (mTexture2D)
157 {
158 glDeleteTextures(1, &mTexture2D);
159 }
160 }
161
162 enum class FenceTest
163 {
164 ClientWait,
165 ServerWait,
166 GetStatus,
167 };
168 enum class FlushMethod
169 {
170 Flush,
171 Finish,
172 };
173 void testFenceWithOpenRenderPass(FenceTest test, FlushMethod flushMethod);
174
175 enum class DrawOrder
176 {
177 Before,
178 After,
179 };
180 void testFramebufferFetch(DrawOrder drawOrder);
181
182 std::mutex mMutex;
183 GLuint mTexture2D;
184 std::atomic<bool> mExitThread;
185 std::atomic<bool> mDrawGreen; // Toggle drawing green or red
186 std::atomic<GLsync> mMainThreadSyncObj;
187 std::atomic<GLsync> mSecondThreadSyncObj;
188 };
189
190 // Test that it's possible to make one context current on different threads
TEST_P(MultithreadingTest,MakeCurrentSingleContext)191 TEST_P(MultithreadingTest, MakeCurrentSingleContext)
192 {
193 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
194
195 std::mutex mutex;
196
197 EGLWindow *window = getEGLWindow();
198 EGLDisplay dpy = window->getDisplay();
199 EGLContext ctx = window->getContext();
200 EGLSurface surface = window->getSurface();
201
202 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
203 EXPECT_EGL_SUCCESS();
204
205 constexpr size_t kThreadCount = 16;
206 std::array<std::thread, kThreadCount> threads;
207 for (std::thread &thread : threads)
208 {
209 thread = std::thread([&]() {
210 std::lock_guard<decltype(mutex)> lock(mutex);
211
212 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
213 EXPECT_EGL_SUCCESS();
214
215 EXPECT_EGL_TRUE(eglSwapBuffers(dpy, surface));
216 EXPECT_EGL_SUCCESS();
217
218 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
219 EXPECT_EGL_SUCCESS();
220 });
221 }
222
223 for (std::thread &thread : threads)
224 {
225 thread.join();
226 }
227
228 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
229 EXPECT_EGL_SUCCESS();
230 }
231
232 // Test that multiple threads can clear and readback pixels successfully at the same time
TEST_P(MultithreadingTest,MultiContextClear)233 TEST_P(MultithreadingTest, MultiContextClear)
234 {
235 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
236
237 auto testBody = [](EGLSurface surface, size_t thread) {
238 constexpr size_t kIterationsPerThread = 32;
239 for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
240 {
241 // Base the clear color on the thread and iteration indexes so every clear color is
242 // unique
243 const GLColor color(static_cast<GLubyte>(thread % 255),
244 static_cast<GLubyte>(iteration % 255), 0, 255);
245 const angle::Vector4 floatColor = color.toNormalizedVector();
246
247 glClearColor(floatColor[0], floatColor[1], floatColor[2], floatColor[3]);
248 EXPECT_GL_NO_ERROR();
249
250 glClear(GL_COLOR_BUFFER_BIT);
251 EXPECT_GL_NO_ERROR();
252
253 EXPECT_PIXEL_COLOR_EQ(0, 0, color);
254 }
255 };
256 runMultithreadedGLTest(
257 testBody,
258 getEGLWindow()->isFeatureEnabled(Feature::SlowAsyncCommandQueueForTesting) ? 4 : 72);
259 }
260
261 // Verify that threads can interleave eglDestroyContext and draw calls without
262 // any crashes.
TEST_P(MultithreadingTest,MultiContextDeleteDraw)263 TEST_P(MultithreadingTest, MultiContextDeleteDraw)
264 {
265 // Skip this test on non-D3D11 backends, as it has the potential to time-out
266 // and this test was originally intended to catch a crash on the D3D11 backend.
267 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
268 ANGLE_SKIP_TEST_IF(!IsD3D11());
269
270 EGLWindow *window = getEGLWindow();
271 EGLDisplay dpy = window->getDisplay();
272 EGLConfig config = window->getConfig();
273
274 std::thread t1 = std::thread([&]() {
275 // 5000 is chosen here as it reliably reproduces the former crash.
276 for (int i = 0; i < 5000; i++)
277 {
278 EGLContext ctx1 = createMultithreadedContext(window, EGL_NO_CONTEXT);
279 EGLContext ctx2 = createMultithreadedContext(window, EGL_NO_CONTEXT);
280
281 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx2));
282 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx1));
283
284 EXPECT_EGL_TRUE(eglDestroyContext(dpy, ctx2));
285 EXPECT_EGL_TRUE(eglDestroyContext(dpy, ctx1));
286 }
287 });
288
289 std::thread t2 = std::thread([&]() {
290 EGLint pbufferAttributes[] = {
291 EGL_WIDTH, 256, EGL_HEIGHT, 256, EGL_NONE, EGL_NONE,
292 };
293
294 EGLSurface surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
295 EXPECT_EGL_SUCCESS();
296
297 auto ctx = createMultithreadedContext(window, EGL_NO_CONTEXT);
298 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
299
300 constexpr size_t kIterationsPerThread = 512;
301 constexpr size_t kDrawsPerIteration = 512;
302
303 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
304 glUseProgram(program);
305
306 GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
307
308 auto quadVertices = GetQuadVertices();
309
310 GLBuffer vertexBuffer;
311 glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
312 glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(), GL_STATIC_DRAW);
313
314 GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
315 glEnableVertexAttribArray(positionLocation);
316 glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
317 for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
318 {
319 const GLColor color(static_cast<GLubyte>(15151 % 255),
320 static_cast<GLubyte>(iteration % 255), 0, 255);
321 const angle::Vector4 floatColor = color.toNormalizedVector();
322 glUniform4fv(colorLocation, 1, floatColor.data());
323 for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
324 {
325 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
326 glDrawArrays(GL_TRIANGLES, 0, 6);
327 }
328 }
329 });
330
331 t1.join();
332 t2.join();
333 }
334
335 // Test that multiple threads can draw and readback pixels successfully at the same time
TEST_P(MultithreadingTest,MultiContextDraw)336 TEST_P(MultithreadingTest, MultiContextDraw)
337 {
338 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
339
340 ANGLE_SKIP_TEST_IF(isSwiftshader());
341
342 auto testBody = [](EGLSurface surface, size_t thread) {
343 constexpr size_t kIterationsPerThread = 32;
344 constexpr size_t kDrawsPerIteration = 500;
345
346 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
347 glUseProgram(program);
348
349 GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
350
351 auto quadVertices = GetQuadVertices();
352
353 GLBuffer vertexBuffer;
354 glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
355 glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(), GL_STATIC_DRAW);
356
357 GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
358 glEnableVertexAttribArray(positionLocation);
359 glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
360
361 for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
362 {
363 // Base the clear color on the thread and iteration indexes so every clear color is
364 // unique
365 const GLColor color(static_cast<GLubyte>(thread % 255),
366 static_cast<GLubyte>(iteration % 255), 0, 255);
367 const angle::Vector4 floatColor = color.toNormalizedVector();
368 glUniform4fv(colorLocation, 1, floatColor.data());
369
370 for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
371 {
372 glDrawArrays(GL_TRIANGLES, 0, 6);
373 }
374
375 EXPECT_PIXEL_COLOR_EQ(0, 0, color);
376 }
377 };
378 runMultithreadedGLTest(testBody, 4);
379 }
380
381 // Test that multiple threads can draw and read back pixels correctly.
382 // Using eglSwapBuffers stresses race conditions around use of QueueSerials.
TEST_P(MultithreadingTest,MultiContextDrawWithSwapBuffers)383 TEST_P(MultithreadingTest, MultiContextDrawWithSwapBuffers)
384 {
385 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
386
387 // http://anglebug.com/5099
388 ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());
389
390 EGLWindow *window = getEGLWindow();
391 EGLDisplay dpy = window->getDisplay();
392
393 auto testBody = [dpy](EGLSurface surface, size_t thread) {
394 constexpr size_t kIterationsPerThread = 100;
395 constexpr size_t kDrawsPerIteration = 10;
396
397 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
398 glUseProgram(program);
399
400 GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
401
402 auto quadVertices = GetQuadVertices();
403
404 GLBuffer vertexBuffer;
405 glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
406 glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(), GL_STATIC_DRAW);
407
408 GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
409 glEnableVertexAttribArray(positionLocation);
410 glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
411
412 for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
413 {
414 // Base the clear color on the thread and iteration indexes so every clear color is
415 // unique
416 const GLColor color(static_cast<GLubyte>(thread % 255),
417 static_cast<GLubyte>(iteration % 255), 0, 255);
418 const angle::Vector4 floatColor = color.toNormalizedVector();
419 glUniform4fv(colorLocation, 1, floatColor.data());
420
421 for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
422 {
423 glDrawArrays(GL_TRIANGLES, 0, 6);
424 }
425
426 EXPECT_EGL_TRUE(eglSwapBuffers(dpy, surface));
427 EXPECT_EGL_SUCCESS();
428
429 EXPECT_PIXEL_COLOR_EQ(0, 0, color);
430 }
431 };
432 runMultithreadedGLTest(
433 testBody,
434 getEGLWindow()->isFeatureEnabled(Feature::SlowAsyncCommandQueueForTesting) ? 4 : 32);
435 }
436
437 // Test that ANGLE handles multiple threads creating and destroying resources (vertex buffer in this
438 // case).
TEST_P(MultithreadingTest,MultiContextCreateAndDeleteResources)439 TEST_P(MultithreadingTest, MultiContextCreateAndDeleteResources)
440 {
441 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
442
443 EGLWindow *window = getEGLWindow();
444 EGLDisplay dpy = window->getDisplay();
445
446 auto testBody = [dpy](EGLSurface surface, size_t thread) {
447 constexpr size_t kIterationsPerThread = 32;
448 constexpr size_t kDrawsPerIteration = 1;
449
450 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
451 glUseProgram(program);
452
453 GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
454
455 auto quadVertices = GetQuadVertices();
456
457 for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
458 {
459 GLBuffer vertexBuffer;
460 glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
461 glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(),
462 GL_STATIC_DRAW);
463
464 GLint positionLocation = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
465 glEnableVertexAttribArray(positionLocation);
466 glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
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 glFinish();
486 };
487 runMultithreadedGLTest(
488 testBody,
489 getEGLWindow()->isFeatureEnabled(Feature::SlowAsyncCommandQueueForTesting) ? 4 : 32);
490 }
491
TEST_P(MultithreadingTest,MultiCreateContext)492 TEST_P(MultithreadingTest, MultiCreateContext)
493 {
494 // Supported by CGL, GLX, and WGL (https://anglebug.com/4725)
495 // Not supported on Ozone (https://crbug.com/1103009)
496 ANGLE_SKIP_TEST_IF(!(IsWindows() || IsLinux() || IsMac()) || IsOzone());
497
498 EGLWindow *window = getEGLWindow();
499 EGLDisplay dpy = window->getDisplay();
500 EGLContext ctx = window->getContext();
501 EGLSurface surface = window->getSurface();
502
503 // Un-makeCurrent the test window's context
504 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
505 EXPECT_EGL_SUCCESS();
506
507 constexpr size_t kThreadCount = 16;
508 std::atomic<uint32_t> barrier(0);
509 std::vector<std::thread> threads(kThreadCount);
510 std::vector<EGLContext> contexts(kThreadCount);
511 for (size_t threadIdx = 0; threadIdx < kThreadCount; threadIdx++)
512 {
513 threads[threadIdx] = std::thread([&, threadIdx]() {
514 contexts[threadIdx] = EGL_NO_CONTEXT;
515 {
516 contexts[threadIdx] = createMultithreadedContext(window, EGL_NO_CONTEXT);
517 EXPECT_NE(EGL_NO_CONTEXT, contexts[threadIdx]);
518
519 barrier++;
520 }
521
522 while (barrier < kThreadCount)
523 {
524 }
525
526 {
527 EXPECT_TRUE(eglDestroyContext(dpy, contexts[threadIdx]));
528 }
529 });
530 }
531
532 for (std::thread &thread : threads)
533 {
534 thread.join();
535 }
536
537 // Re-make current the test window's context for teardown.
538 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
539 EXPECT_EGL_SUCCESS();
540 }
541
542 // Create multiple shared context and draw with shared vertex buffer simutanously
TEST_P(MultithreadingTest,CreateMultiSharedContextAndDraw)543 TEST_P(MultithreadingTest, CreateMultiSharedContextAndDraw)
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() || IsMac()) || IsOzone());
548 EGLWindow *window = getEGLWindow();
549 EGLDisplay dpy = window->getDisplay();
550 EGLConfig config = window->getConfig();
551 constexpr EGLint kPBufferSize = 256;
552
553 // Initialize the pbuffer and context
554 EGLint pbufferAttributes[] = {
555 EGL_WIDTH, kPBufferSize, EGL_HEIGHT, kPBufferSize, EGL_NONE, EGL_NONE,
556 };
557 EGLSurface sharedSurface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
558 EXPECT_EGL_SUCCESS();
559 EGLContext sharedCtx = createMultithreadedContext(window, EGL_NO_CONTEXT);
560 EXPECT_NE(EGL_NO_CONTEXT, sharedCtx);
561 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, sharedSurface, sharedSurface, sharedCtx));
562 EXPECT_EGL_SUCCESS();
563
564 // Create a shared vertextBuffer
565 auto quadVertices = GetQuadVertices();
566 GLBuffer sharedVertexBuffer;
567 glBindBuffer(GL_ARRAY_BUFFER, sharedVertexBuffer);
568 glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 3 * 6, quadVertices.data(), GL_STATIC_DRAW);
569 ASSERT_GL_NO_ERROR();
570
571 // Now draw with the buffer and verify
572 {
573 ANGLE_GL_PROGRAM(sharedProgram, essl1_shaders::vs::Simple(),
574 essl1_shaders::fs::UniformColor());
575 glUseProgram(sharedProgram);
576 GLint colorLocation = glGetUniformLocation(sharedProgram, essl1_shaders::ColorUniform());
577 GLint positionLocation =
578 glGetAttribLocation(sharedProgram, essl1_shaders::PositionAttrib());
579 glEnableVertexAttribArray(positionLocation);
580 glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
581 const GLColor color(0, 0, 0, 255);
582 const angle::Vector4 floatColor = color.toNormalizedVector();
583 glUniform4fv(colorLocation, 1, floatColor.data());
584 glDrawArrays(GL_TRIANGLES, 0, 6);
585 EXPECT_PIXEL_COLOR_EQ(0, 0, color);
586 }
587
588 // Create shared context in their own threads and draw with the shared vertex buffer at the same
589 // time.
590 size_t threadCount = 16;
591 constexpr size_t kIterationsPerThread = 3;
592 constexpr size_t kDrawsPerIteration = 50;
593 std::vector<std::thread> threads(threadCount);
594 std::atomic<uint32_t> numOfContextsCreated(0);
595 std::mutex mutex;
596 for (size_t threadIdx = 0; threadIdx < threadCount; threadIdx++)
597 {
598 threads[threadIdx] = std::thread([&, threadIdx]() {
599 EGLSurface surface = EGL_NO_SURFACE;
600 EGLContext ctx = EGL_NO_CONTEXT;
601
602 {
603 std::lock_guard<decltype(mutex)> lock(mutex);
604 // Initialize the pbuffer and context
605 surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
606 EXPECT_EGL_SUCCESS();
607 ctx = createMultithreadedContext(window, /*EGL_NO_CONTEXT*/ sharedCtx);
608 EXPECT_NE(EGL_NO_CONTEXT, ctx);
609 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
610 EXPECT_EGL_SUCCESS();
611 numOfContextsCreated++;
612 }
613
614 // Wait for all contexts created.
615 while (numOfContextsCreated < threadCount)
616 {
617 }
618
619 // Now draw with shared vertex buffer
620 {
621 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(),
622 essl1_shaders::fs::UniformColor());
623 glUseProgram(program);
624
625 GLint colorLocation = glGetUniformLocation(program, essl1_shaders::ColorUniform());
626 GLint positionLocation =
627 glGetAttribLocation(program, essl1_shaders::PositionAttrib());
628
629 // Use sharedVertexBuffer
630 glBindBuffer(GL_ARRAY_BUFFER, sharedVertexBuffer);
631 glEnableVertexAttribArray(positionLocation);
632 glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
633
634 for (size_t iteration = 0; iteration < kIterationsPerThread; iteration++)
635 {
636 // Base the clear color on the thread and iteration indexes so every clear color
637 // is unique
638 const GLColor color(static_cast<GLubyte>(threadIdx % 255),
639 static_cast<GLubyte>(iteration % 255), 0, 255);
640 const angle::Vector4 floatColor = color.toNormalizedVector();
641 glUniform4fv(colorLocation, 1, floatColor.data());
642
643 for (size_t draw = 0; draw < kDrawsPerIteration; draw++)
644 {
645 glDrawArrays(GL_TRIANGLES, 0, 6);
646 }
647
648 EXPECT_PIXEL_COLOR_EQ(0, 0, color);
649 }
650 }
651
652 // tear down shared context
653 {
654 std::lock_guard<decltype(mutex)> lock(mutex);
655 EXPECT_EGL_TRUE(
656 eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
657 EXPECT_EGL_SUCCESS();
658 eglDestroySurface(dpy, surface);
659 eglDestroyContext(dpy, ctx);
660 }
661 });
662 }
663
664 for (std::thread &thread : threads)
665 {
666 thread.join();
667 }
668
669 eglDestroySurface(dpy, sharedSurface);
670 eglDestroyContext(dpy, sharedCtx);
671
672 // Re-make current the test window's context for teardown.
673 EXPECT_EGL_TRUE(
674 eglMakeCurrent(dpy, window->getSurface(), window->getSurface(), window->getContext()));
675 EXPECT_EGL_SUCCESS();
676 }
677
678 // Producer/Consumer test using EGLImages and EGLSyncs
TEST_P(MultithreadingTest,EGLImageProduceConsume)679 TEST_P(MultithreadingTest, EGLImageProduceConsume)
680 {
681 EGLWindow *window = getEGLWindow();
682 EGLDisplay dpy = window->getDisplay();
683 EGLContext rootCtx = window->getContext();
684
685 ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(dpy, "EGL_KHR_image"));
686 ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(dpy, "EGL_KHR_gl_texture_2D_image"));
687 ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(dpy, "EGL_KHR_fence_sync"));
688
689 struct sharedImage
690 {
691 EGLImage image;
692 EGLSync sync;
693 GLuint rootTexture;
694 };
695
696 std::mutex mutex;
697 std::vector<sharedImage> waitingForProduce;
698 std::vector<sharedImage> waitingForConsume;
699
700 constexpr size_t kNumImages = 10;
701 for (size_t i = 0; i < kNumImages; i++)
702 {
703 GLuint texture;
704 glGenTextures(1, &texture);
705 glBindTexture(GL_TEXTURE_2D, texture);
706 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
707
708 sharedImage img;
709 img.image = eglCreateImageKHR(dpy, rootCtx, EGL_GL_TEXTURE_2D_KHR,
710 reinterpret_cast<EGLClientBuffer>(texture), nullptr);
711 img.sync = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, nullptr);
712 img.rootTexture = texture;
713
714 waitingForProduce.push_back(std::move(img));
715 }
716
717 constexpr size_t kIterations = 10000;
718
719 std::thread producerThread([&]() {
720 EGLContext ctx = createMultithreadedContext(window, EGL_NO_CONTEXT);
721 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx));
722
723 {
724 ANGLE_GL_PROGRAM(drawColor, essl1_shaders::vs::Simple(),
725 essl1_shaders::fs::UniformColor());
726 glUseProgram(drawColor);
727 GLint colorUniformLocation =
728 glGetUniformLocation(drawColor, angle::essl1_shaders::ColorUniform());
729 ASSERT_NE(colorUniformLocation, -1);
730
731 size_t iteration = 0;
732 while (iteration < kIterations)
733 {
734 sharedImage img;
735 {
736 std::lock_guard<decltype(mutex)> lock(mutex);
737 if (waitingForProduce.empty())
738 {
739 continue;
740 }
741 img = std::move(waitingForProduce.back());
742 waitingForProduce.pop_back();
743 }
744
745 eglWaitSync(dpy, img.sync, 0);
746 EXPECT_EGL_SUCCESS();
747
748 eglDestroySync(dpy, img.sync);
749
750 GLTexture texture;
751 glBindTexture(GL_TEXTURE_2D, texture);
752 glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, img.image);
753 EXPECT_GL_NO_ERROR();
754
755 GLFramebuffer fbo;
756 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
757 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture,
758 0);
759
760 glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
761 glClear(GL_COLOR_BUFFER_BIT);
762
763 glUniform4f(colorUniformLocation, float(iteration) / kIterations, 0.0f, 0.0f, 1.0f);
764 drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0);
765
766 glBindTexture(GL_TEXTURE_2D, 0);
767 glBindFramebuffer(GL_FRAMEBUFFER, 0);
768
769 img.sync = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, nullptr);
770 EXPECT_EGL_SUCCESS();
771 glFlush();
772
773 {
774 std::lock_guard<decltype(mutex)> lock(mutex);
775 waitingForConsume.insert(waitingForConsume.begin(), std::move(img));
776 }
777
778 iteration++;
779 }
780 }
781
782 eglDestroyContext(dpy, ctx);
783 });
784
785 std::thread consumerThread([&]() {
786 EGLContext ctx = createMultithreadedContext(window, EGL_NO_CONTEXT);
787 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx));
788
789 {
790 ANGLE_GL_PROGRAM(drawTexture, essl1_shaders::vs::Texture2D(),
791 essl1_shaders::fs::Texture2D());
792 glUseProgram(drawTexture);
793 GLint textureUniformLocation =
794 glGetUniformLocation(drawTexture, angle::essl1_shaders::Texture2DUniform());
795 ASSERT_NE(textureUniformLocation, -1);
796 glUniform1i(textureUniformLocation, 0);
797 glActiveTexture(GL_TEXTURE0);
798
799 GLTexture backbufferTexture;
800 glBindTexture(GL_TEXTURE_2D, backbufferTexture);
801 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE,
802 nullptr);
803
804 GLFramebuffer fbo;
805 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
806 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
807 backbufferTexture, 0);
808
809 size_t iteration = 0;
810 while (iteration < kIterations)
811 {
812 sharedImage img;
813 {
814 std::lock_guard<decltype(mutex)> lock(mutex);
815 if (waitingForConsume.empty())
816 {
817 continue;
818 }
819 img = std::move(waitingForConsume.back());
820 waitingForConsume.pop_back();
821 }
822
823 eglWaitSync(dpy, img.sync, 0);
824 EXPECT_EGL_SUCCESS();
825 eglDestroySync(dpy, img.sync);
826
827 GLTexture texture;
828 glBindTexture(GL_TEXTURE_2D, texture);
829 glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, img.image);
830 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
831 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
832
833 drawQuad(drawTexture, essl1_shaders::PositionAttrib(), 0);
834 EXPECT_GL_NO_ERROR();
835
836 glBindTexture(GL_TEXTURE_2D, 0);
837
838 img.sync = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, nullptr);
839 EXPECT_EGL_SUCCESS();
840 glFlush();
841
842 {
843 std::lock_guard<decltype(mutex)> lock(mutex);
844 waitingForProduce.insert(waitingForProduce.begin(), std::move(img));
845 }
846
847 iteration++;
848 }
849 }
850 eglDestroyContext(dpy, ctx);
851 });
852
853 producerThread.join();
854 consumerThread.join();
855
856 // Clean up
857 {
858 for (auto &img : waitingForProduce)
859 {
860 eglDestroyImageKHR(dpy, img.image);
861 eglDestroySync(dpy, img.sync);
862 glDeleteTextures(1, &img.rootTexture);
863 }
864 for (auto &img : waitingForConsume)
865 {
866 eglDestroyImageKHR(dpy, img.image);
867 eglDestroySync(dpy, img.sync);
868 glDeleteTextures(1, &img.rootTexture);
869 }
870 }
871 }
872
textureThreadFunction(bool useDraw)873 void MultithreadingTestES3::textureThreadFunction(bool useDraw)
874 {
875 EGLWindow *window = getEGLWindow();
876 EGLDisplay dpy = window->getDisplay();
877 EGLConfig config = window->getConfig();
878 EGLSurface surface = EGL_NO_SURFACE;
879 EGLContext ctx = EGL_NO_CONTEXT;
880
881 // Initialize the pbuffer and context
882 EGLint pbufferAttributes[] = {
883 EGL_WIDTH, kSize, EGL_HEIGHT, kSize, EGL_NONE, EGL_NONE,
884 };
885 surface = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
886 EXPECT_EGL_SUCCESS();
887 EXPECT_NE(EGL_NO_SURFACE, surface);
888
889 ctx = createMultithreadedContext(window, window->getContext());
890 EXPECT_NE(EGL_NO_CONTEXT, ctx);
891
892 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
893 EXPECT_EGL_SUCCESS();
894
895 std::vector<GLColor> greenColor(kSize * kSize, GLColor::green);
896 std::vector<GLColor> redColor(kSize * kSize, GLColor::red);
897 ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
898 ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
899
900 glBindTexture(GL_TEXTURE_2D, mTexture2D);
901 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
902 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
903 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
904 ASSERT_GL_NO_ERROR();
905
906 GLFramebuffer fbo;
907 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
908 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture2D, 0);
909 ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
910
911 mSecondThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
912 ASSERT_GL_NO_ERROR();
913 // Force the fence to be created
914 glFlush();
915
916 // Draw something
917 while (!mExitThread)
918 {
919 std::lock_guard<decltype(mMutex)> lock(mMutex);
920
921 if (mMainThreadSyncObj != nullptr)
922 {
923 glWaitSync(mMainThreadSyncObj, 0, GL_TIMEOUT_IGNORED);
924 ASSERT_GL_NO_ERROR();
925 glDeleteSync(mMainThreadSyncObj);
926 ASSERT_GL_NO_ERROR();
927 mMainThreadSyncObj = nullptr;
928 }
929 else
930 {
931 continue;
932 }
933
934 glBindTexture(GL_TEXTURE_2D, mTexture2D);
935 ASSERT_GL_NO_ERROR();
936
937 if (mDrawGreen)
938 {
939 if (useDraw)
940 {
941 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
942 drawQuad(greenProgram, essl1_shaders::PositionAttrib(), 0.0f);
943 }
944 else
945 {
946 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
947 greenColor.data());
948 }
949 ASSERT_GL_NO_ERROR();
950 }
951 else
952 {
953 if (useDraw)
954 {
955 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
956 drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.0f);
957 }
958 else
959 {
960 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
961 redColor.data());
962 }
963 ASSERT_GL_NO_ERROR();
964 }
965
966 ASSERT_EQ(mSecondThreadSyncObj.load(), nullptr);
967 mSecondThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
968 ASSERT_GL_NO_ERROR();
969 // Force the fence to be created
970 glFlush();
971
972 mDrawGreen = !mDrawGreen;
973 }
974
975 // Clean up
976 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
977 EXPECT_EGL_SUCCESS();
978
979 eglDestroySurface(dpy, surface);
980 eglDestroyContext(dpy, ctx);
981 }
982
983 // Test fence sync with multiple threads drawing
mainThreadDraw(bool useDraw)984 void MultithreadingTestES3::mainThreadDraw(bool useDraw)
985 {
986 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
987
988 EGLWindow *window = getEGLWindow();
989 EGLDisplay dpy = window->getDisplay();
990 EGLContext ctx = window->getContext();
991 EGLSurface surface = window->getSurface();
992 // Use odd numbers so we bounce between red and green in the final image
993 constexpr int kNumIterations = 5;
994 constexpr int kNumDraws = 5;
995
996 mDrawGreen = false;
997
998 std::thread textureThread(&MultithreadingTestES3::textureThreadFunction, this, true);
999
1000 ANGLE_GL_PROGRAM(texProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
1001
1002 for (int iterations = 0; iterations < kNumIterations; ++iterations)
1003 {
1004 for (int draws = 0; draws < kNumDraws;)
1005 {
1006 std::lock_guard<decltype(mMutex)> lock(mMutex);
1007
1008 if (mSecondThreadSyncObj != nullptr)
1009 {
1010 glWaitSync(mSecondThreadSyncObj, 0, GL_TIMEOUT_IGNORED);
1011 ASSERT_GL_NO_ERROR();
1012 glDeleteSync(mSecondThreadSyncObj);
1013 ASSERT_GL_NO_ERROR();
1014 mSecondThreadSyncObj = nullptr;
1015 }
1016 else
1017 {
1018 continue;
1019 }
1020
1021 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1022 glBindTexture(GL_TEXTURE_2D, mTexture2D);
1023 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1024 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1025 glUseProgram(texProgram);
1026 drawQuad(texProgram, essl1_shaders::PositionAttrib(), 0.0f);
1027
1028 ASSERT_EQ(mMainThreadSyncObj.load(), nullptr);
1029 mMainThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
1030 ASSERT_GL_NO_ERROR();
1031 // Force the fence to be created
1032 glFlush();
1033
1034 ++draws;
1035 }
1036
1037 ASSERT_GL_NO_ERROR();
1038 swapBuffers();
1039 }
1040
1041 mExitThread = true;
1042 textureThread.join();
1043
1044 ASSERT_GL_NO_ERROR();
1045 GLColor color;
1046 if (mDrawGreen)
1047 {
1048 color = GLColor::green;
1049 }
1050 else
1051 {
1052 color = GLColor::red;
1053 }
1054 EXPECT_PIXEL_RECT_EQ(0, 0, kSize, kSize, color);
1055
1056 // Re-make current the test window's context for teardown.
1057 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, ctx));
1058 EXPECT_EGL_SUCCESS();
1059 }
1060
1061 // Test that glFenceSync/glWaitSync works correctly with multithreading.
1062 // Main thread: Samples from the shared texture to draw to the default FBO.
1063 // Secondary (Texture) thread: Draws to the shared texture, which the Main thread samples from.
1064 // The overall execution flow is:
1065 // Main Thread:
1066 // 1. Wait for the mSecondThreadSyncObj fence object to be created.
1067 // - This fence object is used by synchronize access to the shared texture by indicating that the
1068 // Secondary thread's draws to the texture have all completed and it's now safe to sample from
1069 // it.
1070 // 2. Once the fence is created, add a glWaitSync(mSecondThreadSyncObj) to the command stream and
1071 // then delete it.
1072 // 3. Draw, sampling from the shared texture.
1073 // 4. Create a new mMainThreadSyncObj.
1074 // - This fence object is used to synchronize access to the shared texture by indicating that the
1075 // Main thread's draws are no longer sampling from the texture, so it's now safe for the
1076 // Secondary thread to draw to it again with a new color.
1077 // Secondary (Texture) Thread:
1078 // 1. Wait for the mMainThreadSyncObj fence object to be created.
1079 // 2. Once the fence is created, add a glWaitSync(mMainThreadSyncObj) to the command stream and then
1080 // delete it.
1081 // 3. Draw/Fill the texture.
1082 // 4. Create a new mSecondThreadSyncObj.
1083 //
1084 // These threads loop for the specified number of iterations, drawing/sampling the shared texture
1085 // with the necessary glFlush()s and occasional eglSwapBuffers() to mimic a real multithreaded GLES
1086 // application.
TEST_P(MultithreadingTestES3,MultithreadFenceDraw)1087 TEST_P(MultithreadingTestES3, MultithreadFenceDraw)
1088 {
1089 // http://anglebug.com/5418
1090 ANGLE_SKIP_TEST_IF(IsLinux() && IsVulkan() && (IsIntel() || isSwiftshader()));
1091
1092 // Have the secondary thread use glDrawArrays()
1093 mainThreadDraw(true);
1094 }
1095
1096 // Same as MultithreadFenceDraw, but with the secondary thread using glTexImage2D rather than
1097 // glDrawArrays.
TEST_P(MultithreadingTestES3,MultithreadFenceTexImage)1098 TEST_P(MultithreadingTestES3, MultithreadFenceTexImage)
1099 {
1100 // http://anglebug.com/5418
1101 ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel() && IsVulkan());
1102
1103 // http://anglebug.com/5439
1104 ANGLE_SKIP_TEST_IF(IsLinux() && isSwiftshader());
1105
1106 // Have the secondary thread use glTexImage2D()
1107 mainThreadDraw(false);
1108 }
1109
1110 // Test that waiting on a sync object that hasn't been flushed and without a current context returns
1111 // TIMEOUT_EXPIRED or CONDITION_SATISFIED, but doesn't generate an error or crash.
TEST_P(MultithreadingTest,NoFlushNoContextReturnsTimeout)1112 TEST_P(MultithreadingTest, NoFlushNoContextReturnsTimeout)
1113 {
1114 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
1115 ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
1116
1117 std::mutex mutex;
1118
1119 EGLWindow *window = getEGLWindow();
1120 EGLDisplay dpy = window->getDisplay();
1121
1122 glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
1123 glClear(GL_COLOR_BUFFER_BIT);
1124
1125 EGLSyncKHR sync = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, nullptr);
1126 EXPECT_NE(sync, EGL_NO_SYNC_KHR);
1127
1128 std::thread thread = std::thread([&]() {
1129 std::lock_guard<decltype(mutex)> lock(mutex);
1130 // Make sure there is no active context on this thread.
1131 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1132 EXPECT_EGL_SUCCESS();
1133 // Don't wait forever to make sure the test terminates
1134 constexpr GLuint64 kTimeout = 1'000'000'000; // 1 second
1135 int result = eglClientWaitSyncKHR(dpy, sync, 0, kTimeout);
1136 // We typically expect to get back TIMEOUT_EXPIRED since the sync object was never flushed.
1137 // However, the OpenGL ES backend returns CONDITION_SATISFIED, which is also a passing
1138 // result.
1139 ASSERT_TRUE(result == EGL_TIMEOUT_EXPIRED_KHR || result == EGL_CONDITION_SATISFIED_KHR);
1140 });
1141
1142 thread.join();
1143
1144 EXPECT_EGL_TRUE(eglDestroySyncKHR(dpy, sync));
1145 }
1146
1147 // Test that waiting on sync object that hasn't been flushed yet, but is later flushed by another
1148 // thread, correctly returns when the fence is signalled without a timeout.
TEST_P(MultithreadingTest,CreateFenceThreadAClientWaitSyncThreadBDelayedFlush)1149 TEST_P(MultithreadingTest, CreateFenceThreadAClientWaitSyncThreadBDelayedFlush)
1150 {
1151 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
1152 ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
1153
1154 EGLSyncKHR sync = EGL_NO_SYNC_KHR;
1155
1156 std::mutex mutex;
1157 std::condition_variable condVar;
1158
1159 enum class Step
1160 {
1161 Start,
1162 Thread0Clear,
1163 Thread1CreateFence,
1164 Thread0ClientWaitSync,
1165 Thread1Flush,
1166 Finish,
1167 Abort,
1168 };
1169 Step currentStep = Step::Start;
1170
1171 auto thread0 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
1172 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
1173
1174 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start));
1175
1176 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
1177
1178 // Do work.
1179 glClearColor(1.0, 0.0, 0.0, 1.0);
1180 glClear(GL_COLOR_BUFFER_BIT);
1181
1182 // Wait for thread 1 to clear.
1183 threadSynchronization.nextStep(Step::Thread0Clear);
1184 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1CreateFence));
1185
1186 // Wait on the sync object, but do *not* flush it, since the other thread will flush.
1187 constexpr GLuint64 kTimeout = 2'000'000'000; // 2 seconds
1188 threadSynchronization.nextStep(Step::Thread0ClientWaitSync);
1189 ASSERT_EQ(EGL_CONDITION_SATISFIED_KHR, eglClientWaitSyncKHR(dpy, sync, 0, kTimeout));
1190
1191 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
1192 };
1193
1194 auto thread1 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
1195 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
1196
1197 // Wait for thread 0 to clear.
1198 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Clear));
1199
1200 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
1201
1202 // Do work.
1203 glClearColor(0.0, 1.0, 0.0, 1.0);
1204 glClear(GL_COLOR_BUFFER_BIT);
1205
1206 sync = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, nullptr);
1207 EXPECT_NE(sync, EGL_NO_SYNC_KHR);
1208
1209 // Wait for the thread 0 to eglClientWaitSyncKHR().
1210 threadSynchronization.nextStep(Step::Thread1CreateFence);
1211 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0ClientWaitSync));
1212
1213 // Wait a little to give thread 1 time to wait on the sync object before flushing it.
1214 angle::Sleep(500);
1215 glFlush();
1216
1217 // Clean up
1218 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1219
1220 threadSynchronization.nextStep(Step::Finish);
1221 };
1222
1223 std::array<LockStepThreadFunc, 2> threadFuncs = {
1224 std::move(thread0),
1225 std::move(thread1),
1226 };
1227
1228 RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data());
1229
1230 ASSERT_NE(currentStep, Step::Abort);
1231 }
1232
1233 // Test that thread B can wait on thread A's sync before thread A flushes it, and wakes up after
1234 // that.
testFenceWithOpenRenderPass(FenceTest test,FlushMethod flushMethod)1235 void MultithreadingTestES3::testFenceWithOpenRenderPass(FenceTest test, FlushMethod flushMethod)
1236 {
1237 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
1238 ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
1239
1240 constexpr uint32_t kWidth = 100;
1241 constexpr uint32_t kHeight = 200;
1242
1243 GLsync sync = 0;
1244 GLuint texture = 0;
1245
1246 std::mutex mutex;
1247 std::condition_variable condVar;
1248
1249 enum class Step
1250 {
1251 Start,
1252 Thread0CreateFence,
1253 Thread1WaitFence,
1254 Thread0Finish,
1255 Finish,
1256 Abort,
1257 };
1258 Step currentStep = Step::Start;
1259
1260 auto thread0 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
1261 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
1262
1263 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start));
1264
1265 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
1266
1267 // Create a shared texture to test synchronization
1268 GLTexture color;
1269 texture = color;
1270
1271 glBindTexture(GL_TEXTURE_2D, texture);
1272 glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight);
1273
1274 GLFramebuffer fbo;
1275 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1276 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
1277
1278 // Draw to shared texture.
1279 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
1280 drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
1281 ASSERT_GL_NO_ERROR();
1282
1283 // Issue a fence. A render pass is currently open, so the fence is not actually submitted
1284 // in the Vulkan backend.
1285 sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
1286 ASSERT_NE(sync, nullptr);
1287
1288 // Wait for thread 1 to wait on it.
1289 threadSynchronization.nextStep(Step::Thread0CreateFence);
1290 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1WaitFence));
1291
1292 // Wait a little to give thread 1 time to wait on the sync object before flushing it.
1293 angle::Sleep(500);
1294 switch (flushMethod)
1295 {
1296 case FlushMethod::Flush:
1297 glFlush();
1298 break;
1299 case FlushMethod::Finish:
1300 glFinish();
1301 break;
1302 }
1303
1304 // Clean up
1305 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1306
1307 threadSynchronization.nextStep(Step::Thread0Finish);
1308 threadSynchronization.waitForStep(Step::Finish);
1309 };
1310
1311 auto thread1 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
1312 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
1313
1314 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
1315
1316 // Wait for thread 0 to create the fence object.
1317 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0CreateFence));
1318
1319 // Test access to the fence object
1320 threadSynchronization.nextStep(Step::Thread1WaitFence);
1321
1322 constexpr GLuint64 kTimeout = 2'000'000'000; // 2 seconds
1323 GLenum result = GL_CONDITION_SATISFIED;
1324 switch (test)
1325 {
1326 case FenceTest::ClientWait:
1327 result = glClientWaitSync(sync, 0, kTimeout);
1328 break;
1329 case FenceTest::ServerWait:
1330 glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
1331 break;
1332 case FenceTest::GetStatus:
1333 {
1334 GLint value;
1335 glGetSynciv(sync, GL_SYNC_STATUS, 1, nullptr, &value);
1336 if (value != GL_SIGNALED)
1337 {
1338 result = glClientWaitSync(sync, 0, kTimeout);
1339 }
1340 break;
1341 }
1342 }
1343 ASSERT_TRUE(result == GL_CONDITION_SATISFIED || result == GL_ALREADY_SIGNALED);
1344
1345 // Verify the shared texture is drawn to.
1346 glBindTexture(GL_TEXTURE_2D, texture);
1347
1348 GLFramebuffer fbo;
1349 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1350 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
1351
1352 EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::red);
1353
1354 // Clean up
1355 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1356
1357 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Finish));
1358 threadSynchronization.nextStep(Step::Finish);
1359 };
1360
1361 std::array<LockStepThreadFunc, 2> threadFuncs = {
1362 std::move(thread0),
1363 std::move(thread1),
1364 };
1365
1366 RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data());
1367
1368 ASSERT_NE(currentStep, Step::Abort);
1369 }
1370
1371 // Test that thread B can wait on thread A's sync before thread A flushes it, and wakes up after
1372 // that.
TEST_P(MultithreadingTestES3,ThreadBClientWaitBeforeThreadASyncFlush)1373 TEST_P(MultithreadingTestES3, ThreadBClientWaitBeforeThreadASyncFlush)
1374 {
1375 testFenceWithOpenRenderPass(FenceTest::ClientWait, FlushMethod::Flush);
1376 }
1377
1378 // Test that thread B can wait on thread A's sync before thread A flushes it, and wakes up after
1379 // that.
TEST_P(MultithreadingTestES3,ThreadBServerWaitBeforeThreadASyncFlush)1380 TEST_P(MultithreadingTestES3, ThreadBServerWaitBeforeThreadASyncFlush)
1381 {
1382 testFenceWithOpenRenderPass(FenceTest::ServerWait, FlushMethod::Flush);
1383 }
1384
1385 // Test that thread B can wait on thread A's sync before thread A flushes it, and wakes up after
1386 // that.
TEST_P(MultithreadingTestES3,ThreadBGetStatusBeforeThreadASyncFlush)1387 TEST_P(MultithreadingTestES3, ThreadBGetStatusBeforeThreadASyncFlush)
1388 {
1389 testFenceWithOpenRenderPass(FenceTest::GetStatus, FlushMethod::Flush);
1390 }
1391
1392 // Test that thread B can wait on thread A's sync before thread A flushes it, and wakes up after
1393 // that.
TEST_P(MultithreadingTestES3,ThreadBClientWaitBeforeThreadASyncFinish)1394 TEST_P(MultithreadingTestES3, ThreadBClientWaitBeforeThreadASyncFinish)
1395 {
1396 testFenceWithOpenRenderPass(FenceTest::ClientWait, FlushMethod::Finish);
1397 }
1398
1399 // Test that thread B can wait on thread A's sync before thread A flushes it, and wakes up after
1400 // that.
TEST_P(MultithreadingTestES3,ThreadBServerWaitBeforeThreadASyncFinish)1401 TEST_P(MultithreadingTestES3, ThreadBServerWaitBeforeThreadASyncFinish)
1402 {
1403 testFenceWithOpenRenderPass(FenceTest::ServerWait, FlushMethod::Finish);
1404 }
1405
1406 // Test that thread B can wait on thread A's sync before thread A flushes it, and wakes up after
1407 // that.
TEST_P(MultithreadingTestES3,ThreadBGetStatusBeforeThreadASyncFinish)1408 TEST_P(MultithreadingTestES3, ThreadBGetStatusBeforeThreadASyncFinish)
1409 {
1410 testFenceWithOpenRenderPass(FenceTest::GetStatus, FlushMethod::Finish);
1411 }
1412
1413 // Test the following scenario:
1414 //
1415 // - Thread A opens a render pass, and flushes it. In the Vulkan backend, this may make the flush
1416 // deferred.
1417 // - Thread B opens a render pass and creates a fence. In the Vulkan backend, this also defers the
1418 // flush.
1419 // - Thread C waits on fence
1420 //
1421 // In the Vulkan backend, submission of the fence is implied by thread C's wait, and thread A may
1422 // also be flushed as collateral. If the fence's serial is updated based on thread A's submission,
1423 // synchronization between B and C would be broken.
TEST_P(MultithreadingTestES3,ThreadCWaitBeforeThreadBSyncFinish)1424 TEST_P(MultithreadingTestES3, ThreadCWaitBeforeThreadBSyncFinish)
1425 {
1426 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
1427 ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
1428
1429 constexpr uint32_t kWidth = 100;
1430 constexpr uint32_t kHeight = 200;
1431
1432 GLsync sync = 0;
1433 GLuint texture = 0;
1434
1435 std::mutex mutex;
1436 std::condition_variable condVar;
1437
1438 enum class Step
1439 {
1440 Start,
1441 Thread0DrawAndFlush,
1442 Thread1CreateFence,
1443 Thread2WaitFence,
1444 Thread2Finished,
1445 Finish,
1446 Abort,
1447 };
1448 Step currentStep = Step::Start;
1449
1450 auto thread0 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
1451 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
1452
1453 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start));
1454
1455 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
1456
1457 // Open a render pass and flush it.
1458 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
1459 drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
1460 glFlush();
1461 ASSERT_GL_NO_ERROR();
1462
1463 threadSynchronization.nextStep(Step::Thread0DrawAndFlush);
1464 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
1465
1466 // Clean up
1467 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1468 };
1469
1470 auto thread1 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
1471 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
1472
1473 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
1474
1475 // Wait for thread 0 to set up
1476 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0DrawAndFlush));
1477
1478 // Create a shared texture to test synchronization
1479 GLTexture color;
1480 texture = color;
1481
1482 glBindTexture(GL_TEXTURE_2D, texture);
1483 glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight);
1484
1485 GLFramebuffer fbo;
1486 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1487 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
1488
1489 // Draw to shared texture.
1490 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
1491 drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
1492 ASSERT_GL_NO_ERROR();
1493
1494 // Issue a fence. A render pass is currently open, so the fence is not actually submitted
1495 // in the Vulkan backend.
1496 sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
1497 ASSERT_NE(sync, nullptr);
1498
1499 // Wait for thread 1 to wait on it.
1500 threadSynchronization.nextStep(Step::Thread1CreateFence);
1501 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread2WaitFence));
1502
1503 // Wait a little to give thread 1 time to wait on the sync object before flushing it.
1504 angle::Sleep(500);
1505 glFlush();
1506
1507 // Clean up
1508 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1509
1510 threadSynchronization.nextStep(Step::Thread2Finished);
1511 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
1512 };
1513
1514 auto thread2 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
1515 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
1516
1517 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
1518
1519 // Wait for thread 0 to create the fence object.
1520 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1CreateFence));
1521
1522 // Test access to the fence object
1523 threadSynchronization.nextStep(Step::Thread2WaitFence);
1524
1525 constexpr GLuint64 kTimeout = 2'000'000'000; // 2 seconds
1526 GLenum result = glClientWaitSync(sync, 0, kTimeout);
1527 ASSERT_TRUE(result == GL_CONDITION_SATISFIED || result == GL_ALREADY_SIGNALED);
1528
1529 // Verify the shared texture is drawn to.
1530 glBindTexture(GL_TEXTURE_2D, texture);
1531
1532 GLFramebuffer fbo;
1533 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1534 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
1535
1536 EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::red);
1537
1538 // Clean up
1539 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1540
1541 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread2Finished));
1542 threadSynchronization.nextStep(Step::Finish);
1543 };
1544
1545 std::array<LockStepThreadFunc, 3> threadFuncs = {
1546 std::move(thread0),
1547 std::move(thread1),
1548 std::move(thread2),
1549 };
1550
1551 RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data());
1552
1553 ASSERT_NE(currentStep, Step::Abort);
1554 }
1555
1556 // Test that having commands recorded but not submitted on one thread using a texture, does not
1557 // interfere with similar commands on another thread using the same texture. Regression test for a
1558 // bug in the Vulkan backend where the first thread would batch updates to a descriptor set not
1559 // visible to the other thread, while the other thread picks up the (unupdated) descriptor set from
1560 // a shared cache.
TEST_P(MultithreadingTestES3,UnsynchronizedTextureReads)1561 TEST_P(MultithreadingTestES3, UnsynchronizedTextureReads)
1562 {
1563 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
1564 ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
1565
1566 GLsync sync = 0;
1567 GLuint texture = 0;
1568
1569 constexpr GLubyte kInitialData[4] = {127, 63, 191, 255};
1570
1571 std::mutex mutex;
1572 std::condition_variable condVar;
1573
1574 enum class Step
1575 {
1576 Start,
1577 Thread0CreateTextureAndDraw,
1578 Thread1DrawAndFlush,
1579 Finish,
1580 Abort,
1581 };
1582 Step currentStep = Step::Start;
1583
1584 auto thread0 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
1585 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
1586
1587 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start));
1588
1589 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
1590
1591 // Create a texture, and record a command that draws into it.
1592 GLTexture color;
1593 texture = color;
1594
1595 glBindTexture(GL_TEXTURE_2D, texture);
1596 glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
1597 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1598 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1599 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, kInitialData);
1600
1601 sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
1602 ASSERT_NE(sync, nullptr);
1603
1604 ANGLE_GL_PROGRAM(drawTexture, essl1_shaders::vs::Texture2D(),
1605 essl1_shaders::fs::Texture2D());
1606 drawQuad(drawTexture, essl1_shaders::PositionAttrib(), 0.0f);
1607
1608 // Don't flush yet; this leaves the descriptor set updates to the texture pending in the
1609 // Vulkan backend.
1610 threadSynchronization.nextStep(Step::Thread0CreateTextureAndDraw);
1611 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1DrawAndFlush));
1612
1613 // Flush after thread 1
1614 EXPECT_PIXEL_COLOR_NEAR(
1615 0, 0, GLColor(kInitialData[0], kInitialData[1], kInitialData[2], kInitialData[3]), 1);
1616
1617 // Clean up
1618 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1619
1620 threadSynchronization.nextStep(Step::Finish);
1621 };
1622
1623 auto thread1 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
1624 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
1625
1626 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
1627
1628 // Wait for thread 0 to set up
1629 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0CreateTextureAndDraw));
1630
1631 // Synchronize with the texture upload (but not the concurrent read)
1632 glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
1633
1634 // Draw with the same texture, in the same way as thread 0. This ensures that the
1635 // descriptor sets used in the Vulkan backend are identical.
1636 glBindTexture(GL_TEXTURE_2D, texture);
1637 ANGLE_GL_PROGRAM(drawTexture, essl1_shaders::vs::Texture2D(),
1638 essl1_shaders::fs::Texture2D());
1639 drawQuad(drawTexture, essl1_shaders::PositionAttrib(), 0.0f);
1640
1641 // Flush
1642 EXPECT_PIXEL_COLOR_NEAR(
1643 0, 0, GLColor(kInitialData[0], kInitialData[1], kInitialData[2], kInitialData[3]), 1);
1644
1645 threadSynchronization.nextStep(Step::Thread1DrawAndFlush);
1646
1647 // Clean up
1648 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1649
1650 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
1651 };
1652
1653 std::array<LockStepThreadFunc, 2> threadFuncs = {
1654 std::move(thread0),
1655 std::move(thread1),
1656 };
1657
1658 RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data());
1659
1660 ASSERT_NE(currentStep, Step::Abort);
1661 }
1662
1663 // Similar to UnsynchronizedTextureReads, but the texture update is done through framebuffer write.
TEST_P(MultithreadingTestES3,UnsynchronizedTextureReads2)1664 TEST_P(MultithreadingTestES3, UnsynchronizedTextureReads2)
1665 {
1666 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
1667 ANGLE_SKIP_TEST_IF(!hasFenceSyncExtension() || !hasGLSyncExtension());
1668
1669 GLsync sync = 0;
1670 GLuint texture = 0;
1671
1672 std::mutex mutex;
1673 std::condition_variable condVar;
1674
1675 enum class Step
1676 {
1677 Start,
1678 Thread0CreateTextureAndDraw,
1679 Thread1DrawAndFlush,
1680 Finish,
1681 Abort,
1682 };
1683 Step currentStep = Step::Start;
1684
1685 auto thread0 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
1686 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
1687
1688 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start));
1689
1690 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
1691
1692 // Create a texture, and record a command that draws into it.
1693 GLTexture color;
1694 texture = color;
1695
1696 glBindTexture(GL_TEXTURE_2D, texture);
1697 glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
1698 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1699 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1700
1701 GLFramebuffer fbo;
1702 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1703 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
1704
1705 glClearColor(1, 0, 0, 1);
1706 glClear(GL_COLOR_BUFFER_BIT);
1707 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
1708 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1709
1710 sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
1711 ASSERT_NE(sync, nullptr);
1712
1713 ANGLE_GL_PROGRAM(drawTexture, essl1_shaders::vs::Texture2D(),
1714 essl1_shaders::fs::Texture2D());
1715 drawQuad(drawTexture, essl1_shaders::PositionAttrib(), 0.0f);
1716
1717 // Don't flush yet; this leaves the descriptor set updates to the texture pending in the
1718 // Vulkan backend.
1719 threadSynchronization.nextStep(Step::Thread0CreateTextureAndDraw);
1720 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1DrawAndFlush));
1721
1722 // Flush after thread 1
1723 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
1724
1725 // Clean up
1726 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1727
1728 threadSynchronization.nextStep(Step::Finish);
1729 };
1730
1731 auto thread1 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
1732 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
1733
1734 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
1735
1736 // Wait for thread 0 to set up
1737 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0CreateTextureAndDraw));
1738
1739 // Synchronize with the texture update (but not the concurrent read)
1740 glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
1741
1742 // Draw with the same texture, in the same way as thread 0. This ensures that the
1743 // descriptor sets used in the Vulkan backend are identical.
1744 glBindTexture(GL_TEXTURE_2D, texture);
1745 ANGLE_GL_PROGRAM(drawTexture, essl1_shaders::vs::Texture2D(),
1746 essl1_shaders::fs::Texture2D());
1747 drawQuad(drawTexture, essl1_shaders::PositionAttrib(), 0.0f);
1748
1749 // Flush
1750 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
1751
1752 threadSynchronization.nextStep(Step::Thread1DrawAndFlush);
1753
1754 // Clean up
1755 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1756
1757 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
1758 };
1759
1760 std::array<LockStepThreadFunc, 2> threadFuncs = {
1761 std::move(thread0),
1762 std::move(thread1),
1763 };
1764
1765 RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data());
1766
1767 ASSERT_NE(currentStep, Step::Abort);
1768 }
1769
1770 // Similar to UnsynchronizedTextureReads, but the texture is used once. This is because
1771 // UnsynchronizedTextureRead hits a different bug than it intends to test. This test makes sure the
1772 // image is put in the right layout, by using it together with another texture (i.e. a different
1773 // descriptor set).
TEST_P(MultithreadingTestES3,UnsynchronizedTextureReads3)1774 TEST_P(MultithreadingTestES3, UnsynchronizedTextureReads3)
1775 {
1776 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
1777
1778 constexpr GLubyte kInitialData[4] = {127, 63, 191, 255};
1779
1780 GLuint texture = 0;
1781
1782 std::mutex mutex;
1783 std::condition_variable condVar;
1784
1785 enum class Step
1786 {
1787 Start,
1788 Thread0CreateTextureAndDraw,
1789 Thread1DrawAndFlush,
1790 Finish,
1791 Abort,
1792 };
1793 Step currentStep = Step::Start;
1794
1795 auto thread0 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
1796 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
1797
1798 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start));
1799
1800 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
1801
1802 // Create a texture, and record a command that draws into it.
1803 GLTexture color;
1804 texture = color;
1805
1806 glActiveTexture(GL_TEXTURE0);
1807 glBindTexture(GL_TEXTURE_2D, texture);
1808 glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
1809 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1810 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1811 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, kInitialData);
1812
1813 glActiveTexture(GL_TEXTURE1);
1814 GLTexture color2;
1815 glBindTexture(GL_TEXTURE_2D, color2);
1816 glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
1817 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1818 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1819 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, kInitialData);
1820
1821 ANGLE_GL_PROGRAM(setupTexture, essl1_shaders::vs::Texture2D(),
1822 R"(precision mediump float;
1823 uniform sampler2D tex2D;
1824 uniform sampler2D tex2D2;
1825 varying vec2 v_texCoord;
1826
1827 void main()
1828 {
1829 gl_FragColor = texture2D(tex2D, v_texCoord) + texture2D(tex2D2, v_texCoord);
1830 })");
1831 drawQuad(setupTexture, essl1_shaders::PositionAttrib(), 0.0f);
1832 ASSERT_GL_NO_ERROR();
1833
1834 glFinish();
1835
1836 ANGLE_GL_PROGRAM(drawTexture, essl1_shaders::vs::Texture2D(),
1837 essl1_shaders::fs::Texture2D());
1838 drawQuad(drawTexture, essl1_shaders::PositionAttrib(), 0.0f);
1839 ASSERT_GL_NO_ERROR();
1840
1841 // Don't flush yet; this leaves the descriptor set updates to the texture pending in the
1842 // Vulkan backend.
1843 threadSynchronization.nextStep(Step::Thread0CreateTextureAndDraw);
1844 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1DrawAndFlush));
1845
1846 // Flush after thread 1
1847 EXPECT_PIXEL_COLOR_NEAR(
1848 0, 0, GLColor(kInitialData[0], kInitialData[1], kInitialData[2], kInitialData[3]), 1);
1849
1850 // Clean up
1851 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1852
1853 threadSynchronization.nextStep(Step::Finish);
1854 };
1855
1856 auto thread1 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
1857 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
1858
1859 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
1860
1861 // Wait for thread 0 to set up
1862 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0CreateTextureAndDraw));
1863
1864 // Draw with the same texture, in the same way as thread 0. This ensures that the
1865 // descriptor sets used in the Vulkan backend are identical.
1866 glBindTexture(GL_TEXTURE_2D, texture);
1867 ANGLE_GL_PROGRAM(drawTexture, essl1_shaders::vs::Texture2D(),
1868 essl1_shaders::fs::Texture2D());
1869 drawQuad(drawTexture, essl1_shaders::PositionAttrib(), 0.0f);
1870 ASSERT_GL_NO_ERROR();
1871
1872 // Flush
1873 EXPECT_PIXEL_COLOR_NEAR(
1874 0, 0, GLColor(kInitialData[0], kInitialData[1], kInitialData[2], kInitialData[3]), 1);
1875
1876 threadSynchronization.nextStep(Step::Thread1DrawAndFlush);
1877
1878 // Clean up
1879 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1880
1881 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
1882 };
1883
1884 std::array<LockStepThreadFunc, 2> threadFuncs = {
1885 std::move(thread0),
1886 std::move(thread1),
1887 };
1888
1889 RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data());
1890
1891 ASSERT_NE(currentStep, Step::Abort);
1892 }
1893
1894 // Test framebuffer fetch program used between share groups.
testFramebufferFetch(DrawOrder drawOrder)1895 void MultithreadingTestES3::testFramebufferFetch(DrawOrder drawOrder)
1896 {
1897 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
1898 ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch_non_coherent"));
1899
1900 GLProgram framebufferFetchProgram;
1901
1902 constexpr char kFS[] = R"(#version 300 es
1903 #extension GL_EXT_shader_framebuffer_fetch_non_coherent : require
1904 layout(noncoherent, location = 0) inout highp vec4 o_color;
1905
1906 uniform highp vec4 u_color;
1907 void main (void)
1908 {
1909 o_color += u_color;
1910 })";
1911
1912 std::mutex mutex;
1913 std::condition_variable condVar;
1914
1915 enum class Step
1916 {
1917 Start,
1918 Thread0PreCreateProgram,
1919 Thread1CreateProgram,
1920 Finish,
1921 Abort,
1922 };
1923 Step currentStep = Step::Start;
1924
1925 auto thread0 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
1926 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
1927
1928 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start));
1929
1930 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
1931
1932 // Open a render pass, if requested.
1933 if (drawOrder == DrawOrder::Before)
1934 {
1935 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
1936 drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
1937 ASSERT_GL_NO_ERROR();
1938 }
1939
1940 threadSynchronization.nextStep(Step::Thread0PreCreateProgram);
1941 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1CreateProgram));
1942
1943 // Render using the framebuffer fetch program
1944 if (drawOrder == DrawOrder::After)
1945 {
1946 ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
1947 drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
1948 ASSERT_GL_NO_ERROR();
1949 }
1950
1951 glFramebufferFetchBarrierEXT();
1952
1953 glUseProgram(framebufferFetchProgram);
1954 GLint colorLocation = glGetUniformLocation(framebufferFetchProgram, "u_color");
1955 glUniform4f(colorLocation, 1, 0, 0, 0);
1956 drawQuad(framebufferFetchProgram, essl1_shaders::PositionAttrib(), 0.0f);
1957 ASSERT_GL_NO_ERROR();
1958
1959 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
1960
1961 threadSynchronization.nextStep(Step::Finish);
1962
1963 // Clean up
1964 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1965 };
1966
1967 auto thread1 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
1968 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
1969
1970 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
1971
1972 // Wait for thread 0 to set up
1973 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0PreCreateProgram));
1974
1975 // Create the framebuffer fetch program
1976 framebufferFetchProgram.makeRaster(essl3_shaders::vs::Simple(), kFS);
1977 glUseProgram(framebufferFetchProgram);
1978
1979 // Notify the other thread to use it
1980 threadSynchronization.nextStep(Step::Thread1CreateProgram);
1981 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
1982
1983 glClearColor(0, 0, 0, 1);
1984 glClear(GL_COLOR_BUFFER_BIT);
1985
1986 glFramebufferFetchBarrierEXT();
1987
1988 glUseProgram(framebufferFetchProgram);
1989 GLint colorLocation = glGetUniformLocation(framebufferFetchProgram, "u_color");
1990 glUniform4f(colorLocation, 0, 0, 1, 0);
1991 drawQuad(framebufferFetchProgram, essl1_shaders::PositionAttrib(), 0.0f);
1992 ASSERT_GL_NO_ERROR();
1993
1994 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
1995
1996 // Clean up
1997 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
1998 };
1999
2000 std::array<LockStepThreadFunc, 2> threadFuncs = {
2001 std::move(thread0),
2002 std::move(thread1),
2003 };
2004
2005 RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data());
2006
2007 ASSERT_NE(currentStep, Step::Abort);
2008 }
2009
2010 // Thread 1 creates the framebuffer fetch program. Thread 0 proceeds to use it.
TEST_P(MultithreadingTestES3,CreateFramebufferFetchBeforeRenderPass)2011 TEST_P(MultithreadingTestES3, CreateFramebufferFetchBeforeRenderPass)
2012 {
2013 testFramebufferFetch(DrawOrder::After);
2014 }
2015
2016 // Thread 1 creates the framebuffer fetch program while thread 0 is mid render pass. Thread 0
2017 // proceeds to use the framebuffer fetch program in the rest of its render pass.
TEST_P(MultithreadingTestES3,CreateFramebufferFetchMidRenderPass)2018 TEST_P(MultithreadingTestES3, CreateFramebufferFetchMidRenderPass)
2019 {
2020 testFramebufferFetch(DrawOrder::Before);
2021 }
2022
2023 // Test async monolithic pipeline creation in the Vulkan backend vs shared programs. This test
2024 // makes one context/thread create a set of programs, then has another context/thread use them a few
2025 // times, and then the original context destroys them.
TEST_P(MultithreadingTestES3,ProgramUseAndDestroyInTwoContexts)2026 TEST_P(MultithreadingTestES3, ProgramUseAndDestroyInTwoContexts)
2027 {
2028 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
2029
2030 GLProgram programs[6];
2031
2032 GLsync sync = 0;
2033
2034 std::mutex mutex;
2035 std::condition_variable condVar;
2036
2037 enum class Step
2038 {
2039 Start,
2040 Thread0CreatePrograms,
2041 Thread1UsePrograms,
2042 Finish,
2043 Abort,
2044 };
2045 Step currentStep = Step::Start;
2046
2047 auto thread0 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
2048 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
2049
2050 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start));
2051
2052 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
2053
2054 // Create the programs
2055 programs[0].makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
2056 programs[1].makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
2057 programs[2].makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
2058 programs[3].makeRaster(essl1_shaders::vs::Passthrough(), essl1_shaders::fs::Checkered());
2059 programs[4].makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
2060 programs[5].makeRaster(essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
2061
2062 EXPECT_TRUE(programs[0].valid());
2063 EXPECT_TRUE(programs[1].valid());
2064 EXPECT_TRUE(programs[2].valid());
2065 EXPECT_TRUE(programs[3].valid());
2066 EXPECT_TRUE(programs[4].valid());
2067 EXPECT_TRUE(programs[5].valid());
2068
2069 threadSynchronization.nextStep(Step::Thread0CreatePrograms);
2070 // Wait for the other thread to use the programs
2071 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1UsePrograms));
2072
2073 // Destroy them
2074 glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
2075 programs[0].reset();
2076 programs[1].reset();
2077 programs[2].reset();
2078 programs[3].reset();
2079 programs[4].reset();
2080 programs[5].reset();
2081
2082 threadSynchronization.nextStep(Step::Finish);
2083
2084 // Clean up
2085 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
2086 };
2087
2088 auto thread1 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
2089 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
2090
2091 // Wait for thread 0 to create the programs
2092 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0CreatePrograms));
2093
2094 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
2095
2096 // Use them a few times.
2097 drawQuad(programs[0], essl1_shaders::PositionAttrib(), 0.0f);
2098 drawQuad(programs[1], essl1_shaders::PositionAttrib(), 0.0f);
2099 drawQuad(programs[2], essl1_shaders::PositionAttrib(), 0.0f);
2100 drawQuad(programs[3], essl1_shaders::PositionAttrib(), 0.0f);
2101 drawQuad(programs[4], essl1_shaders::PositionAttrib(), 0.0f);
2102
2103 drawQuad(programs[0], essl1_shaders::PositionAttrib(), 0.0f);
2104 drawQuad(programs[1], essl1_shaders::PositionAttrib(), 0.0f);
2105 drawQuad(programs[2], essl1_shaders::PositionAttrib(), 0.0f);
2106
2107 drawQuad(programs[0], essl1_shaders::PositionAttrib(), 0.0f);
2108
2109 sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
2110 ASSERT_NE(sync, nullptr);
2111
2112 // Notify the other thread to destroy the programs.
2113 threadSynchronization.nextStep(Step::Thread1UsePrograms);
2114 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
2115
2116 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
2117
2118 // Clean up
2119 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
2120 };
2121
2122 std::array<LockStepThreadFunc, 2> threadFuncs = {
2123 std::move(thread0),
2124 std::move(thread1),
2125 };
2126
2127 RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data());
2128
2129 ASSERT_NE(currentStep, Step::Abort);
2130 }
2131
2132 // Tests that Context with High Priority will correctly sample Texture rendered by Share Context
2133 // with Low Priority.
TEST_P(MultithreadingTestES3,RenderThenSampleDifferentContextPriority)2134 TEST_P(MultithreadingTestES3, RenderThenSampleDifferentContextPriority)
2135 {
2136 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
2137
2138 constexpr size_t kIterationCountMax = 10;
2139
2140 const bool reduceLoad = isSwiftshader();
2141 const size_t iterationCount = reduceLoad ? 3 : kIterationCountMax;
2142 const size_t heavyDrawCount = reduceLoad ? 25 : 100;
2143
2144 // Initialize contexts
2145 EGLWindow *window = getEGLWindow();
2146 EGLDisplay dpy = window->getDisplay();
2147 EGLConfig config = window->getConfig();
2148
2149 // Large enough texture to catch timing problems.
2150 constexpr GLsizei kTexSize = 1024;
2151 constexpr size_t kThreadCount = 2;
2152 EGLSurface surface[kThreadCount] = {EGL_NO_SURFACE, EGL_NO_SURFACE};
2153 EGLContext ctx[kThreadCount] = {EGL_NO_CONTEXT, EGL_NO_CONTEXT};
2154
2155 EGLint priorities[kThreadCount] = {EGL_CONTEXT_PRIORITY_LOW_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG};
2156
2157 EGLint pbufferAttributes[kThreadCount][6] = {
2158 {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, EGL_NONE},
2159 {EGL_WIDTH, kTexSize, EGL_HEIGHT, kTexSize, EGL_NONE, EGL_NONE}};
2160
2161 EGLint attributes[] = {EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_NONE,
2162 EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE, EGL_NONE, EGL_NONE};
2163 EGLint *extraAttributes = attributes;
2164 if (!IsEGLDisplayExtensionEnabled(dpy, "EGL_ANGLE_context_virtualization"))
2165 {
2166 attributes[2] = EGL_NONE;
2167 }
2168 if (!IsEGLDisplayExtensionEnabled(dpy, "EGL_IMG_context_priority"))
2169 {
2170 // Run tests with single priority anyway.
2171 extraAttributes += 2;
2172 }
2173
2174 for (size_t t = 0; t < kThreadCount; ++t)
2175 {
2176 surface[t] = eglCreatePbufferSurface(dpy, config, pbufferAttributes[t]);
2177 EXPECT_EGL_SUCCESS();
2178
2179 attributes[1] = priorities[t];
2180 attributes[3] = mVirtualizationGroup++;
2181
2182 ctx[t] = window->createContext(t == 0 ? EGL_NO_CONTEXT : ctx[0], extraAttributes);
2183 EXPECT_NE(EGL_NO_CONTEXT, ctx[t]);
2184 }
2185
2186 // Synchronization tools to ensure the two threads are interleaved as designed by this test.
2187 std::mutex mutex;
2188 std::condition_variable condVar;
2189
2190 enum class Step
2191 {
2192 Start,
2193 Thread0Init,
2194 Thread1Init,
2195 Thread0Draw,
2196 Thread1Draw,
2197 Finish = Thread0Draw + kIterationCountMax * 2,
2198 Abort,
2199 };
2200 Step currentStep = Step::Start;
2201
2202 GLTexture texture;
2203 GLsync thread0DrawSyncObj;
2204
2205 auto calculateTestColor = [](size_t i) {
2206 return GLColor(i % 256, (i + 1) % 256, (i + 2) % 256, 255);
2207 };
2208 auto makeStep = [](Step base, size_t i) {
2209 return static_cast<Step>(static_cast<size_t>(base) + i * 2);
2210 };
2211
2212 // Render to the texture.
2213 std::thread thread0 = std::thread([&]() {
2214 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
2215
2216 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[0], surface[0], ctx[0]));
2217 EXPECT_EGL_SUCCESS();
2218
2219 glBindTexture(GL_TEXTURE_2D, texture);
2220 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
2221 nullptr);
2222 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
2223 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
2224
2225 GLFramebuffer fbo;
2226 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
2227 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
2228 glViewport(0, 0, kTexSize, kTexSize);
2229
2230 ANGLE_GL_PROGRAM(colorProgram, essl1_shaders::vs::Simple(),
2231 essl1_shaders::fs::UniformColor());
2232 GLint colorLocation =
2233 glGetUniformLocation(colorProgram, angle::essl1_shaders::ColorUniform());
2234 ASSERT_NE(-1, colorLocation);
2235 glUseProgram(colorProgram);
2236
2237 // Notify second thread that initialization is finished.
2238 threadSynchronization.nextStep(Step::Thread0Init);
2239 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Init));
2240
2241 for (size_t i = 0; i < iterationCount; ++i)
2242 {
2243 // Simulate heavy work...
2244 glUniform4f(colorLocation, 0.0f, 0.0f, 0.0f, 0.0f);
2245 for (size_t j = 0; j < heavyDrawCount; ++j)
2246 {
2247 drawQuad(colorProgram, essl1_shaders::PositionAttrib(), 0.5f);
2248 }
2249
2250 // Draw with test color.
2251 Vector4 color = calculateTestColor(i).toNormalizedVector();
2252 glUniform4f(colorLocation, color.x(), color.y(), color.z(), color.w());
2253 drawQuad(colorProgram, essl1_shaders::PositionAttrib(), 0.5f);
2254
2255 thread0DrawSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
2256 ASSERT_GL_NO_ERROR();
2257
2258 // Notify second thread that draw is finished.
2259 threadSynchronization.nextStep(makeStep(Step::Thread0Draw, i));
2260 ASSERT_TRUE(threadSynchronization.waitForStep(
2261 (i == iterationCount - 1) ? Step::Finish : makeStep(Step::Thread1Draw, i)));
2262 }
2263
2264 EXPECT_GL_NO_ERROR();
2265 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
2266 EXPECT_EGL_SUCCESS();
2267 });
2268
2269 // Sample texture
2270 std::thread thread1 = std::thread([&]() {
2271 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
2272
2273 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[1], surface[1], ctx[1]));
2274 EXPECT_EGL_SUCCESS();
2275
2276 glViewport(0, 0, kTexSize, kTexSize);
2277
2278 ANGLE_GL_PROGRAM(textureProgram, essl1_shaders::vs::Texture2D(),
2279 essl1_shaders::fs::Texture2D());
2280 glUseProgram(textureProgram);
2281
2282 // Wait for first thread to finish initializing.
2283 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Init));
2284
2285 glBindTexture(GL_TEXTURE_2D, texture);
2286
2287 // Wait for first thread to draw using the shared texture.
2288 threadSynchronization.nextStep(Step::Thread1Init);
2289
2290 for (size_t i = 0; i < iterationCount; ++i)
2291 {
2292 ASSERT_TRUE(threadSynchronization.waitForStep(makeStep(Step::Thread0Draw, i)));
2293
2294 ASSERT_TRUE(thread0DrawSyncObj != nullptr);
2295 glWaitSync(thread0DrawSyncObj, 0, GL_TIMEOUT_IGNORED);
2296 ASSERT_GL_NO_ERROR();
2297
2298 // Should draw test color.
2299 drawQuad(textureProgram, essl1_shaders::PositionAttrib(), 0.5f);
2300
2301 // Check test color in four corners.
2302 GLColor color = calculateTestColor(i);
2303 EXPECT_PIXEL_EQ(0, 0, color.R, color.G, color.B, color.A);
2304 EXPECT_PIXEL_EQ(0, kTexSize - 1, color.R, color.G, color.B, color.A);
2305 EXPECT_PIXEL_EQ(kTexSize - 1, 0, color.R, color.G, color.B, color.A);
2306 EXPECT_PIXEL_EQ(kTexSize - 1, kTexSize - 1, color.R, color.G, color.B, color.A);
2307
2308 glDeleteSync(thread0DrawSyncObj);
2309 ASSERT_GL_NO_ERROR();
2310 thread0DrawSyncObj = nullptr;
2311
2312 threadSynchronization.nextStep(
2313 (i == iterationCount - 1) ? Step::Finish : makeStep(Step::Thread1Draw, i));
2314 }
2315
2316 EXPECT_GL_NO_ERROR();
2317 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
2318 EXPECT_EGL_SUCCESS();
2319 });
2320
2321 thread0.join();
2322 thread1.join();
2323
2324 ASSERT_NE(currentStep, Step::Abort);
2325
2326 // Clean up
2327 for (size_t t = 0; t < kThreadCount; ++t)
2328 {
2329 eglDestroySurface(dpy, surface[t]);
2330 eglDestroyContext(dpy, ctx[t]);
2331 }
2332 }
2333
2334 // Tests that newly created Context with High Priority will correctly sample Texture already
2335 // rendered by Share Context with Low Priority.
TEST_P(MultithreadingTestES3,RenderThenSampleInNewContextWithDifferentPriority)2336 TEST_P(MultithreadingTestES3, RenderThenSampleInNewContextWithDifferentPriority)
2337 {
2338 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
2339 ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_disjoint_timer_query"));
2340
2341 const bool reduceLoad = isSwiftshader();
2342 const size_t heavyDrawCount = reduceLoad ? 75 : 1000;
2343
2344 // Initialize contexts
2345 EGLWindow *window = getEGLWindow();
2346 EGLDisplay dpy = window->getDisplay();
2347 EGLConfig config = window->getConfig();
2348
2349 // Large enough texture to catch timing problems.
2350 constexpr GLsizei kTexSize = 1024;
2351 constexpr size_t kThreadCount = 2;
2352 EGLSurface surface[kThreadCount] = {EGL_NO_SURFACE, EGL_NO_SURFACE};
2353 EGLContext ctx[kThreadCount] = {EGL_NO_CONTEXT, EGL_NO_CONTEXT};
2354
2355 EGLint priorities[kThreadCount] = {EGL_CONTEXT_PRIORITY_LOW_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG};
2356
2357 EGLint pbufferAttributes[kThreadCount][6] = {
2358 {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, EGL_NONE},
2359 {EGL_WIDTH, kTexSize, EGL_HEIGHT, kTexSize, EGL_NONE, EGL_NONE}};
2360
2361 EGLint attributes[] = {EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_NONE,
2362 EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE, EGL_NONE, EGL_NONE};
2363 EGLint *extraAttributes = attributes;
2364 if (!IsEGLDisplayExtensionEnabled(dpy, "EGL_ANGLE_context_virtualization"))
2365 {
2366 attributes[2] = EGL_NONE;
2367 }
2368 if (!IsEGLDisplayExtensionEnabled(dpy, "EGL_IMG_context_priority"))
2369 {
2370 // Run tests with single priority anyway.
2371 extraAttributes += 2;
2372 }
2373
2374 for (size_t t = 0; t < kThreadCount; ++t)
2375 {
2376 surface[t] = eglCreatePbufferSurface(dpy, config, pbufferAttributes[t]);
2377 EXPECT_EGL_SUCCESS();
2378
2379 attributes[1] = priorities[t];
2380 attributes[3] = mVirtualizationGroup++;
2381
2382 // Second context will be created in a thread 1
2383 if (t == 0)
2384 {
2385 ctx[t] = window->createContext(t == 0 ? EGL_NO_CONTEXT : ctx[0], extraAttributes);
2386 EXPECT_NE(EGL_NO_CONTEXT, ctx[t]);
2387 }
2388 }
2389
2390 // Synchronization tools to ensure the two threads are interleaved as designed by this test.
2391 std::mutex mutex;
2392 std::condition_variable condVar;
2393
2394 enum class Step
2395 {
2396 Start,
2397 Thread0Draw,
2398 Thread1Draw,
2399 Finish,
2400 Abort,
2401 };
2402 Step currentStep = Step::Start;
2403
2404 // Create shared resources before threads to minimize timing delays.
2405 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[0], surface[0], ctx[0]));
2406 EXPECT_EGL_SUCCESS();
2407
2408 ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
2409 ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
2410 ANGLE_GL_PROGRAM(blueProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
2411 ANGLE_GL_PROGRAM(textureProgram, essl1_shaders::vs::Texture2D(),
2412 essl1_shaders::fs::Texture2D());
2413
2414 GLTexture texture;
2415 glBindTexture(GL_TEXTURE_2D, texture);
2416 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
2417 nullptr);
2418 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
2419 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
2420
2421 GLFramebuffer fbo;
2422 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
2423 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
2424 glViewport(0, 0, kTexSize, kTexSize);
2425
2426 EXPECT_GL_NO_ERROR();
2427 EXPECT_EGL_TRUE(window->makeCurrent());
2428 EXPECT_EGL_SUCCESS();
2429
2430 GLsync thread0DrawSyncObj;
2431
2432 // Render to the texture.
2433 std::thread thread0 = std::thread([&]() {
2434 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
2435
2436 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[0], surface[0], ctx[0]));
2437 EXPECT_EGL_SUCCESS();
2438
2439 // Enable additive blend
2440 glEnable(GL_BLEND);
2441 glBlendFunc(GL_ONE, GL_ONE);
2442
2443 GLuint query;
2444 glGenQueries(1, &query);
2445 glBeginQuery(GL_TIME_ELAPSED_EXT, query);
2446 ASSERT_GL_NO_ERROR();
2447
2448 // Simulate heavy work...
2449 glUseProgram(redProgram);
2450 for (size_t j = 0; j < heavyDrawCount; ++j)
2451 {
2452 // Draw with Red color.
2453 drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.5f);
2454 }
2455
2456 // Draw with Green color.
2457 glUseProgram(greenProgram);
2458 drawQuad(greenProgram, essl1_shaders::PositionAttrib(), 0.5f);
2459
2460 // This should force "flushToPrimary()"
2461 glEndQuery(GL_TIME_ELAPSED_EXT);
2462 glDeleteQueries(1, &query);
2463 ASSERT_GL_NO_ERROR();
2464
2465 // Continue draw with Blue color after flush...
2466 glUseProgram(blueProgram);
2467 drawQuad(blueProgram, essl1_shaders::PositionAttrib(), 0.5f);
2468
2469 thread0DrawSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
2470 ASSERT_GL_NO_ERROR();
2471
2472 // Notify second thread that draw is finished.
2473 threadSynchronization.nextStep(Step::Thread0Draw);
2474 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
2475
2476 EXPECT_GL_NO_ERROR();
2477 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
2478 EXPECT_EGL_SUCCESS();
2479 });
2480
2481 // Sample texture
2482 std::thread thread1 = std::thread([&]() {
2483 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
2484
2485 // Wait for first thread to finish draw.
2486 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Draw));
2487
2488 // Create High priority Context when Low priority Context already rendered to the texture.
2489 ctx[1] = window->createContext(ctx[0], extraAttributes);
2490 EXPECT_NE(EGL_NO_CONTEXT, ctx[1]);
2491
2492 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[1], surface[1], ctx[1]));
2493 EXPECT_EGL_SUCCESS();
2494
2495 glViewport(0, 0, kTexSize, kTexSize);
2496
2497 ASSERT_TRUE(thread0DrawSyncObj != nullptr);
2498 glWaitSync(thread0DrawSyncObj, 0, GL_TIMEOUT_IGNORED);
2499 ASSERT_GL_NO_ERROR();
2500
2501 // Should draw test color.
2502 glUseProgram(textureProgram);
2503 glBindTexture(GL_TEXTURE_2D, texture);
2504 drawQuad(textureProgram, essl1_shaders::PositionAttrib(), 0.5f);
2505
2506 // Check test color in four corners.
2507 GLColor color = GLColor::white;
2508 EXPECT_PIXEL_EQ(0, 0, color.R, color.G, color.B, color.A);
2509 EXPECT_PIXEL_EQ(0, kTexSize - 1, color.R, color.G, color.B, color.A);
2510 EXPECT_PIXEL_EQ(kTexSize - 1, 0, color.R, color.G, color.B, color.A);
2511 EXPECT_PIXEL_EQ(kTexSize - 1, kTexSize - 1, color.R, color.G, color.B, color.A);
2512
2513 glDeleteSync(thread0DrawSyncObj);
2514 ASSERT_GL_NO_ERROR();
2515 thread0DrawSyncObj = nullptr;
2516
2517 threadSynchronization.nextStep(Step::Finish);
2518
2519 EXPECT_GL_NO_ERROR();
2520 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
2521 EXPECT_EGL_SUCCESS();
2522 });
2523
2524 thread0.join();
2525 thread1.join();
2526
2527 ASSERT_NE(currentStep, Step::Abort);
2528
2529 // Clean up
2530 for (size_t t = 0; t < kThreadCount; ++t)
2531 {
2532 eglDestroySurface(dpy, surface[t]);
2533 eglDestroyContext(dpy, ctx[t]);
2534 }
2535 }
2536
2537 // Tests that Context with High Priority will correctly sample EGLImage target Texture rendered by
2538 // other Context with Low Priority into EGLImage source texture.
TEST_P(MultithreadingTestES3,RenderThenSampleDifferentContextPriorityUsingEGLImage)2539 TEST_P(MultithreadingTestES3, RenderThenSampleDifferentContextPriorityUsingEGLImage)
2540 {
2541 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
2542 ANGLE_SKIP_TEST_IF(!hasWaitSyncExtension() || !hasGLSyncExtension());
2543 ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_EGL_image"));
2544 ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_disjoint_timer_query"));
2545
2546 const bool reduceLoad = isSwiftshader();
2547 const size_t heavyDrawCount = reduceLoad ? 75 : 1000;
2548
2549 // Initialize contexts
2550 EGLWindow *window = getEGLWindow();
2551 EGLDisplay dpy = window->getDisplay();
2552 EGLConfig config = window->getConfig();
2553
2554 ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(dpy, "EGL_KHR_image_base"));
2555 ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(dpy, "EGL_KHR_gl_texture_2D_image"));
2556
2557 // Large enough texture to catch timing problems.
2558 constexpr GLsizei kTexSize = 1024;
2559 constexpr size_t kThreadCount = 2;
2560 EGLSurface surface[kThreadCount] = {EGL_NO_SURFACE, EGL_NO_SURFACE};
2561 EGLContext ctx[kThreadCount] = {EGL_NO_CONTEXT, EGL_NO_CONTEXT};
2562
2563 EGLint priorities[kThreadCount] = {EGL_CONTEXT_PRIORITY_LOW_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG};
2564
2565 EGLint pbufferAttributes[kThreadCount][6] = {
2566 {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, EGL_NONE},
2567 {EGL_WIDTH, kTexSize, EGL_HEIGHT, kTexSize, EGL_NONE, EGL_NONE}};
2568
2569 EGLint attributes[] = {EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_NONE,
2570 EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE, EGL_NONE, EGL_NONE};
2571 EGLint *extraAttributes = attributes;
2572 if (!IsEGLDisplayExtensionEnabled(dpy, "EGL_ANGLE_context_virtualization"))
2573 {
2574 attributes[2] = EGL_NONE;
2575 }
2576 if (!IsEGLDisplayExtensionEnabled(dpy, "EGL_IMG_context_priority"))
2577 {
2578 // Run tests with single priority anyway.
2579 extraAttributes += 2;
2580 }
2581
2582 for (size_t t = 0; t < kThreadCount; ++t)
2583 {
2584 surface[t] = eglCreatePbufferSurface(dpy, config, pbufferAttributes[t]);
2585 EXPECT_EGL_SUCCESS();
2586
2587 attributes[1] = priorities[t];
2588 attributes[3] = mVirtualizationGroup++;
2589
2590 // Contexts not shared
2591 ctx[t] = window->createContext(EGL_NO_CONTEXT, extraAttributes);
2592 EXPECT_NE(EGL_NO_CONTEXT, ctx[t]);
2593 }
2594
2595 // Synchronization tools to ensure the two threads are interleaved as designed by this test.
2596 std::mutex mutex;
2597 std::condition_variable condVar;
2598
2599 enum class Step
2600 {
2601 Start,
2602 Thread1Init,
2603 Thread0Draw,
2604 Thread1Draw,
2605 Finish,
2606 Abort,
2607 };
2608 Step currentStep = Step::Start;
2609
2610 EGLImage image = EGL_NO_IMAGE_KHR;
2611 EGLSyncKHR sync = EGL_NO_SYNC_KHR;
2612
2613 // Render to the EGLImage source texture.
2614 std::thread thread0 = std::thread([&]() {
2615 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
2616
2617 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[0], surface[0], ctx[0]));
2618 EXPECT_EGL_SUCCESS();
2619
2620 // Create source texture.
2621 GLTexture texture;
2622 glBindTexture(GL_TEXTURE_2D, texture);
2623 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
2624 nullptr);
2625 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
2626 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
2627
2628 GLFramebuffer fbo;
2629 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
2630 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
2631 glViewport(0, 0, kTexSize, kTexSize);
2632
2633 ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
2634 ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
2635 ANGLE_GL_PROGRAM(blueProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
2636
2637 // Wait for second thread to finish initializing.
2638 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Init));
2639
2640 // Enable additive blend
2641 glEnable(GL_BLEND);
2642 glBlendFunc(GL_ONE, GL_ONE);
2643
2644 GLuint query;
2645 glGenQueries(1, &query);
2646 glBeginQuery(GL_TIME_ELAPSED_EXT, query);
2647 ASSERT_GL_NO_ERROR();
2648
2649 // Simulate heavy work...
2650 glUseProgram(redProgram);
2651 for (size_t j = 0; j < heavyDrawCount; ++j)
2652 {
2653 // Draw with Red color.
2654 drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.5f);
2655 }
2656
2657 // Draw with Green color.
2658 glUseProgram(greenProgram);
2659 drawQuad(greenProgram, essl1_shaders::PositionAttrib(), 0.5f);
2660
2661 // This should force "flushToPrimary()"
2662 glEndQuery(GL_TIME_ELAPSED_EXT);
2663 glDeleteQueries(1, &query);
2664 ASSERT_GL_NO_ERROR();
2665
2666 // Continue draw with Blue color after flush...
2667 glUseProgram(blueProgram);
2668 drawQuad(blueProgram, essl1_shaders::PositionAttrib(), 0.5f);
2669
2670 sync = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, nullptr);
2671 EXPECT_NE(sync, EGL_NO_SYNC_KHR);
2672
2673 // Create EGLImage.
2674 image = eglCreateImageKHR(
2675 dpy, ctx[0], EGL_GL_TEXTURE_2D_KHR,
2676 reinterpret_cast<EGLClientBuffer>(static_cast<uintptr_t>(texture)), nullptr);
2677 ASSERT_EGL_SUCCESS();
2678
2679 // Notify second thread that draw is finished.
2680 threadSynchronization.nextStep(Step::Thread0Draw);
2681 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
2682
2683 EXPECT_GL_NO_ERROR();
2684 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
2685 EXPECT_EGL_SUCCESS();
2686 });
2687
2688 // Sample texture
2689 std::thread thread1 = std::thread([&]() {
2690 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
2691
2692 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[1], surface[1], ctx[1]));
2693 EXPECT_EGL_SUCCESS();
2694
2695 glViewport(0, 0, kTexSize, kTexSize);
2696
2697 ANGLE_GL_PROGRAM(textureProgram, essl1_shaders::vs::Texture2D(),
2698 essl1_shaders::fs::Texture2D());
2699 glUseProgram(textureProgram);
2700
2701 // Create target texture.
2702 GLTexture texture;
2703 glBindTexture(GL_TEXTURE_2D, texture);
2704 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
2705 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
2706
2707 // Wait for first thread to draw into the source texture.
2708 threadSynchronization.nextStep(Step::Thread1Init);
2709 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Draw));
2710
2711 // Wait for draw to complete
2712 ASSERT_TRUE(eglWaitSyncKHR(dpy, sync, 0));
2713
2714 // Specify target texture from EGLImage.
2715 glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);
2716 ASSERT_GL_NO_ERROR();
2717
2718 // Should draw test color.
2719 drawQuad(textureProgram, essl1_shaders::PositionAttrib(), 0.5f);
2720 ASSERT_GL_NO_ERROR();
2721
2722 // Check test color in four corners.
2723 GLColor color = GLColor::white;
2724 EXPECT_PIXEL_EQ(0, 0, color.R, color.G, color.B, color.A);
2725 EXPECT_PIXEL_EQ(0, kTexSize - 1, color.R, color.G, color.B, color.A);
2726 EXPECT_PIXEL_EQ(kTexSize - 1, 0, color.R, color.G, color.B, color.A);
2727 EXPECT_PIXEL_EQ(kTexSize - 1, kTexSize - 1, color.R, color.G, color.B, color.A);
2728
2729 EXPECT_EGL_TRUE(eglDestroyImageKHR(dpy, image));
2730 image = EGL_NO_IMAGE_KHR;
2731
2732 EXPECT_EGL_TRUE(eglDestroySyncKHR(dpy, sync));
2733 sync = EGL_NO_SYNC_KHR;
2734
2735 threadSynchronization.nextStep(Step::Finish);
2736
2737 EXPECT_GL_NO_ERROR();
2738 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
2739 EXPECT_EGL_SUCCESS();
2740 });
2741
2742 thread0.join();
2743 thread1.join();
2744
2745 ASSERT_NE(currentStep, Step::Abort);
2746
2747 // Clean up
2748 for (size_t t = 0; t < kThreadCount; ++t)
2749 {
2750 eglDestroySurface(dpy, surface[t]);
2751 eglDestroyContext(dpy, ctx[t]);
2752 }
2753 }
2754
2755 // Tests mixing commands of Contexts with different Priorities in a single Command Buffers (Vulkan).
TEST_P(MultithreadingTestES3,ContextPriorityMixing)2756 TEST_P(MultithreadingTestES3, ContextPriorityMixing)
2757 {
2758 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
2759 ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_disjoint_timer_query"));
2760
2761 constexpr size_t kIterationCountMax = 10;
2762
2763 const bool reduceLoad = isSwiftshader();
2764 const size_t iterationCount = reduceLoad ? 3 : kIterationCountMax;
2765 const size_t heavyDrawCount = reduceLoad ? 25 : 100;
2766
2767 // Initialize contexts
2768 EGLWindow *window = getEGLWindow();
2769 EGLDisplay dpy = window->getDisplay();
2770 EGLConfig config = window->getConfig();
2771
2772 // Large enough texture to catch timing problems.
2773 constexpr GLsizei kTexSize = 1024;
2774 constexpr size_t kThreadCount = 2;
2775 EGLSurface surface[kThreadCount] = {EGL_NO_SURFACE, EGL_NO_SURFACE};
2776 EGLContext ctx[kThreadCount] = {EGL_NO_CONTEXT, EGL_NO_CONTEXT};
2777
2778 EGLint priorities[kThreadCount] = {EGL_CONTEXT_PRIORITY_LOW_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG};
2779
2780 EGLint pbufferAttributes[kThreadCount][6] = {
2781 {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, EGL_NONE},
2782 {EGL_WIDTH, kTexSize, EGL_HEIGHT, kTexSize, EGL_NONE, EGL_NONE}};
2783
2784 EGLint attributes[] = {EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_NONE,
2785 EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE, EGL_NONE, EGL_NONE};
2786 EGLint *extraAttributes = attributes;
2787 if (!IsEGLDisplayExtensionEnabled(dpy, "EGL_ANGLE_context_virtualization"))
2788 {
2789 attributes[2] = EGL_NONE;
2790 }
2791 if (!IsEGLDisplayExtensionEnabled(dpy, "EGL_IMG_context_priority"))
2792 {
2793 // Run tests with single priority anyway.
2794 extraAttributes += 2;
2795 }
2796
2797 for (size_t t = 0; t < kThreadCount; ++t)
2798 {
2799 surface[t] = eglCreatePbufferSurface(dpy, config, pbufferAttributes[t]);
2800 EXPECT_EGL_SUCCESS();
2801
2802 attributes[1] = priorities[t];
2803 attributes[3] = mVirtualizationGroup++;
2804
2805 // Contexts not shared
2806 ctx[t] = window->createContext(EGL_NO_CONTEXT, extraAttributes);
2807 EXPECT_NE(EGL_NO_CONTEXT, ctx[t]);
2808 }
2809
2810 // Synchronization tools to ensure the two threads are interleaved as designed by this test.
2811 std::mutex mutex;
2812 std::condition_variable condVar;
2813
2814 enum class Step
2815 {
2816 Start,
2817 Thread1DrawColor,
2818 Thread0Iterate,
2819 Finish = Thread1DrawColor + kIterationCountMax * 2,
2820 Abort,
2821 };
2822 Step currentStep = Step::Start;
2823
2824 auto makeStep = [](Step base, size_t i) {
2825 return static_cast<Step>(static_cast<size_t>(base) + i * 2);
2826 };
2827
2828 // Triggers commands submission.
2829 std::thread thread0 = std::thread([&]() {
2830 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
2831
2832 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[0], surface[0], ctx[0]));
2833 EXPECT_EGL_SUCCESS();
2834
2835 ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
2836
2837 for (size_t i = 0; i < iterationCount; ++i)
2838 {
2839 ASSERT_TRUE(threadSynchronization.waitForStep(makeStep(Step::Thread1DrawColor, i)));
2840
2841 glUseProgram(redProgram);
2842 drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.5f);
2843 ASSERT_GL_NO_ERROR();
2844
2845 // This should perform commands submission.
2846 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
2847 EXPECT_EGL_SUCCESS();
2848
2849 threadSynchronization.nextStep(makeStep(Step::Thread0Iterate, i));
2850
2851 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[0], surface[0], ctx[0]));
2852 EXPECT_EGL_SUCCESS();
2853 }
2854
2855 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
2856
2857 EXPECT_GL_NO_ERROR();
2858 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
2859 EXPECT_EGL_SUCCESS();
2860 });
2861
2862 // Render and then sample texture.
2863 std::thread thread1 = std::thread([&]() {
2864 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
2865
2866 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[1], surface[1], ctx[1]));
2867 EXPECT_EGL_SUCCESS();
2868
2869 glDisable(GL_DEPTH_TEST);
2870 glViewport(0, 0, kTexSize, kTexSize);
2871
2872 GLTexture texture;
2873 glBindTexture(GL_TEXTURE_2D, texture);
2874 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
2875 nullptr);
2876 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
2877 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
2878
2879 GLFramebuffer fbo;
2880 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
2881 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
2882
2883 ANGLE_GL_PROGRAM(colorProgram, essl1_shaders::vs::Simple(),
2884 essl1_shaders::fs::UniformColor());
2885 GLint colorLocation =
2886 glGetUniformLocation(colorProgram, angle::essl1_shaders::ColorUniform());
2887 ASSERT_NE(-1, colorLocation);
2888
2889 ANGLE_GL_PROGRAM(textureProgram, essl1_shaders::vs::Texture2D(),
2890 essl1_shaders::fs::Texture2D());
2891
2892 for (size_t i = 0; i < iterationCount; ++i)
2893 {
2894 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
2895 glUseProgram(colorProgram);
2896
2897 GLuint query;
2898 glGenQueries(1, &query);
2899 glBeginQuery(GL_TIME_ELAPSED_EXT, query);
2900 ASSERT_GL_NO_ERROR();
2901
2902 // Simulate heavy work...
2903 glUniform4f(colorLocation, 0.0f, 0.0f, 0.0f, 0.0f);
2904 for (size_t j = 0; j < heavyDrawCount; ++j)
2905 {
2906 drawQuad(colorProgram, essl1_shaders::PositionAttrib(), 0.5f);
2907 }
2908
2909 // Draw with test color.
2910 GLColor color(i % 256, (i + 1) % 256, (i + 2) % 256, 255);
2911 Vector4 colorF = color.toNormalizedVector();
2912 glUniform4f(colorLocation, colorF.x(), colorF.y(), colorF.z(), colorF.w());
2913 drawQuad(colorProgram, essl1_shaders::PositionAttrib(), 0.5f);
2914 ASSERT_GL_NO_ERROR();
2915
2916 // This should force "flushToPrimary()"
2917 glEndQuery(GL_TIME_ELAPSED_EXT);
2918 glDeleteQueries(1, &query);
2919 ASSERT_GL_NO_ERROR();
2920
2921 threadSynchronization.nextStep(makeStep(Step::Thread1DrawColor, i));
2922 ASSERT_TRUE(threadSynchronization.waitForStep(makeStep(Step::Thread0Iterate, i)));
2923
2924 glBindFramebuffer(GL_FRAMEBUFFER, 0);
2925 glUseProgram(textureProgram);
2926
2927 // Should draw test color.
2928 drawQuad(textureProgram, essl1_shaders::PositionAttrib(), 0.5f);
2929 ASSERT_GL_NO_ERROR();
2930
2931 // Check test color in four corners.
2932 EXPECT_PIXEL_EQ(0, 0, color.R, color.G, color.B, color.A);
2933 EXPECT_PIXEL_EQ(0, kTexSize - 1, color.R, color.G, color.B, color.A);
2934 EXPECT_PIXEL_EQ(kTexSize - 1, 0, color.R, color.G, color.B, color.A);
2935 EXPECT_PIXEL_EQ(kTexSize - 1, kTexSize - 1, color.R, color.G, color.B, color.A);
2936 }
2937
2938 threadSynchronization.nextStep(Step::Finish);
2939
2940 EXPECT_GL_NO_ERROR();
2941 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
2942 EXPECT_EGL_SUCCESS();
2943 });
2944
2945 thread0.join();
2946 thread1.join();
2947
2948 ASSERT_NE(currentStep, Step::Abort);
2949
2950 // Clean up
2951 for (size_t t = 0; t < kThreadCount; ++t)
2952 {
2953 eglDestroySurface(dpy, surface[t]);
2954 eglDestroyContext(dpy, ctx[t]);
2955 }
2956 }
2957
2958 // Test that it is possible to upload textures in one thread and use them in another with
2959 // synchronization.
TEST_P(MultithreadingTestES3,MultithreadedTextureUploadAndDraw)2960 TEST_P(MultithreadingTestES3, MultithreadedTextureUploadAndDraw)
2961 {
2962 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
2963
2964 constexpr size_t kTexSize = 4;
2965 GLTexture texture1;
2966 GLTexture texture2;
2967 std::vector<GLColor> textureColors1(kTexSize * kTexSize, GLColor::red);
2968 std::vector<GLColor> textureColors2(kTexSize * kTexSize, GLColor::green);
2969
2970 // Sync primitives
2971 GLsync sync = nullptr;
2972 std::mutex mutex;
2973 std::condition_variable condVar;
2974
2975 enum class Step
2976 {
2977 Start,
2978 Thread0UploadFinish,
2979 Finish,
2980 Abort,
2981 };
2982 Step currentStep = Step::Start;
2983
2984 // Threads to upload and draw with textures.
2985 auto thread0Upload = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
2986 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
2987 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start));
2988
2989 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
2990
2991 // Two mipmap textures are defined here. They are used for drawing in the other thread.
2992 glBindTexture(GL_TEXTURE_2D, texture1);
2993 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
2994 textureColors1.data());
2995 glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kTexSize / 2, kTexSize / 2, 0, GL_RGBA,
2996 GL_UNSIGNED_BYTE, textureColors1.data());
2997 glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, kTexSize / 4, kTexSize / 4, 0, GL_RGBA,
2998 GL_UNSIGNED_BYTE, textureColors1.data());
2999 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
3000 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
3001 ASSERT_GL_NO_ERROR();
3002
3003 glBindTexture(GL_TEXTURE_2D, texture2);
3004 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
3005 textureColors2.data());
3006 glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kTexSize / 2, kTexSize / 2, 0, GL_RGBA,
3007 GL_UNSIGNED_BYTE, textureColors2.data());
3008 glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, kTexSize / 4, kTexSize / 4, 0, GL_RGBA,
3009 GL_UNSIGNED_BYTE, textureColors2.data());
3010 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
3011 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
3012 ASSERT_GL_NO_ERROR();
3013
3014 // Create a sync object to be used for the draw thread.
3015 sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
3016 glFlush();
3017 ASSERT_NE(sync, nullptr);
3018
3019 threadSynchronization.nextStep(Step::Thread0UploadFinish);
3020 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
3021 };
3022
3023 auto thread1Draw = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
3024 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
3025 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0UploadFinish));
3026
3027 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
3028
3029 // Wait for the sync object to be signaled.
3030 glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
3031 ASSERT_GL_NO_ERROR();
3032
3033 // Draw using the textures from the texture upload thread.
3034 ANGLE_GL_PROGRAM(textureProgram, essl1_shaders::vs::Texture2D(),
3035 essl1_shaders::fs::Texture2D());
3036 glUseProgram(textureProgram);
3037
3038 glBindTexture(GL_TEXTURE_2D, texture1);
3039 drawQuad(textureProgram, essl1_shaders::PositionAttrib(), 0.5f);
3040 glFlush();
3041 ASSERT_GL_NO_ERROR();
3042 EXPECT_PIXEL_RECT_EQ(0, 0, kTexSize, kTexSize, GLColor::red);
3043
3044 glBindTexture(GL_TEXTURE_2D, texture2);
3045 drawQuad(textureProgram, essl1_shaders::PositionAttrib(), 0.5f);
3046 glFlush();
3047 ASSERT_GL_NO_ERROR();
3048 EXPECT_PIXEL_RECT_EQ(0, 0, kTexSize, kTexSize, GLColor::green);
3049
3050 threadSynchronization.nextStep(Step::Finish);
3051 };
3052
3053 std::array<LockStepThreadFunc, 2> threadFuncs = {
3054 std::move(thread0Upload),
3055 std::move(thread1Draw),
3056 };
3057
3058 RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data());
3059
3060 ASSERT_NE(currentStep, Step::Abort);
3061 }
3062
3063 // Test that it is possible to create a new context after uploading mutable mipmap textures in the
3064 // previous context, and use them in the new context.
TEST_P(MultithreadingTestES3,CreateNewContextAfterTextureUploadOnNewThread)3065 TEST_P(MultithreadingTestES3, CreateNewContextAfterTextureUploadOnNewThread)
3066 {
3067 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
3068
3069 constexpr size_t kTexSize = 4;
3070 GLTexture texture1;
3071 GLTexture texture2;
3072 std::vector<GLColor> textureColors1(kTexSize * kTexSize, GLColor::red);
3073 std::vector<GLColor> textureColors2(kTexSize * kTexSize, GLColor::green);
3074
3075 EGLWindow *window = getEGLWindow();
3076 EGLDisplay dpy = window->getDisplay();
3077
3078 std::thread thread = std::thread([&]() {
3079 // Create a context and upload the textures.
3080 EGLContext ctx1 = createMultithreadedContext(window, EGL_NO_CONTEXT);
3081 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx1));
3082
3083 glBindTexture(GL_TEXTURE_2D, texture1);
3084 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
3085 textureColors1.data());
3086 glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kTexSize / 2, kTexSize / 2, 0, GL_RGBA,
3087 GL_UNSIGNED_BYTE, textureColors1.data());
3088 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
3089 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
3090 ASSERT_GL_NO_ERROR();
3091
3092 glBindTexture(GL_TEXTURE_2D, texture2);
3093 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
3094 textureColors2.data());
3095 glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kTexSize / 2, kTexSize / 2, 0, GL_RGBA,
3096 GL_UNSIGNED_BYTE, textureColors2.data());
3097 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
3098 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
3099 ASSERT_GL_NO_ERROR();
3100
3101 // Create a new context and use the uploaded textures.
3102 EGLContext ctx2 = createMultithreadedContext(window, ctx1);
3103 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx2));
3104
3105 GLFramebuffer fbo;
3106 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
3107
3108 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture1, 0);
3109 EXPECT_PIXEL_RECT_EQ(0, 0, kTexSize, kTexSize, GLColor::red);
3110
3111 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture2, 0);
3112 EXPECT_PIXEL_RECT_EQ(0, 0, kTexSize, kTexSize, GLColor::green);
3113
3114 // Destroy the contexts.
3115 EXPECT_EGL_TRUE(eglDestroyContext(dpy, ctx2));
3116 EXPECT_EGL_TRUE(eglDestroyContext(dpy, ctx1));
3117 });
3118
3119 thread.join();
3120 }
3121
3122 // Test that it is possible to create a new context after uploading mutable mipmap textures in the
3123 // main thread, and use them in the new context.
TEST_P(MultithreadingTestES3,CreateNewContextAfterTextureUploadOnMainThread)3124 TEST_P(MultithreadingTestES3, CreateNewContextAfterTextureUploadOnMainThread)
3125 {
3126 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
3127
3128 EGLWindow *window = getEGLWindow();
3129 EGLDisplay dpy = window->getDisplay();
3130
3131 // Upload the textures.
3132 constexpr size_t kTexSize = 4;
3133 GLTexture texture1;
3134 GLTexture texture2;
3135 std::vector<GLColor> textureColors1(kTexSize * kTexSize, GLColor::red);
3136 std::vector<GLColor> textureColors2(kTexSize * kTexSize, GLColor::green);
3137
3138 glBindTexture(GL_TEXTURE_2D, texture1);
3139 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
3140 textureColors1.data());
3141 glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kTexSize / 2, kTexSize / 2, 0, GL_RGBA,
3142 GL_UNSIGNED_BYTE, textureColors1.data());
3143 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
3144 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
3145 ASSERT_GL_NO_ERROR();
3146
3147 glBindTexture(GL_TEXTURE_2D, texture2);
3148 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
3149 textureColors2.data());
3150 glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kTexSize / 2, kTexSize / 2, 0, GL_RGBA,
3151 GL_UNSIGNED_BYTE, textureColors2.data());
3152 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
3153 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
3154 ASSERT_GL_NO_ERROR();
3155
3156 GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
3157
3158 std::thread thread = std::thread([&]() {
3159 // Create a context.
3160 EGLContext ctx1 = createMultithreadedContext(window, window->getContext());
3161 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx1));
3162
3163 // Wait for the sync object to be signaled.
3164 glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
3165 ASSERT_GL_NO_ERROR();
3166
3167 // Use the uploaded textures in the main thread.
3168 GLFramebuffer fbo;
3169 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
3170
3171 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture1, 0);
3172 EXPECT_PIXEL_RECT_EQ(0, 0, kTexSize, kTexSize, GLColor::red);
3173
3174 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture2, 0);
3175 EXPECT_PIXEL_RECT_EQ(0, 0, kTexSize, kTexSize, GLColor::green);
3176
3177 // Destroy the context.
3178 EXPECT_EGL_TRUE(eglDestroyContext(dpy, ctx1));
3179 });
3180
3181 thread.join();
3182 }
3183
3184 // Test when lots of upload happens on a different thread at the same time as the main thread doing
3185 // draws.
TEST_P(MultithreadingTestES3,SimultaneousUploadAndDraw)3186 TEST_P(MultithreadingTestES3, SimultaneousUploadAndDraw)
3187 {
3188 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
3189
3190 // The following shader is used to create busy work while the worker thread is doing something.
3191 // It intentionally spreads its uniforms and inputs so the main thread has to make many GL
3192 // calls.
3193 constexpr char kBusyDrawVS[] = R"(#version 300 es
3194 uniform mediump float x0;
3195 uniform mediump float y0;
3196 uniform mediump float x1;
3197 uniform mediump float y1;
3198
3199 in mediump float r;
3200 in mediump float g;
3201 in mediump float b;
3202 in mediump float a;
3203
3204 out mediump vec4 color;
3205
3206 void main()
3207 {
3208 // gl_VertexID x y
3209 // 0 -1 -1
3210 // 1 1 -1
3211 // 2 -1 1
3212 // 3 1 1
3213 int bit0 = gl_VertexID & 1;
3214 int bit1 = gl_VertexID >> 1;
3215 gl_Position.x = bit0 == 0 ? x0 : x1;
3216 gl_Position.y = bit1 == 0 ? y0 : y1;
3217 gl_Position.z = 0.;
3218 gl_Position.w = 1.;
3219
3220 color = vec4(r, g, b, a);
3221 })";
3222 constexpr char kBusyDrawFS[] = R"(#version 300 es
3223
3224 in mediump vec4 color;
3225 out mediump vec4 colorOut;
3226
3227 void main()
3228 {
3229 colorOut = color;
3230 })";
3231
3232 // The following shader is used to consume the results of texture uploads, ensuring appropriate
3233 // synchronization.
3234 constexpr char kTextureDrawVS[] = R"(#version 300 es
3235 out mediump vec2 uv;
3236
3237 void main()
3238 {
3239 // gl_VertexID x y
3240 // 0 -1 -1
3241 // 1 1 -1
3242 // 2 -1 1
3243 // 3 1 1
3244 int bit0 = gl_VertexID & 1;
3245 int bit1 = gl_VertexID >> 1;
3246 gl_Position = vec4(bit0 * 2 - 1, bit1 * 2 - 1, 0, 1);
3247 uv = vec2(bit0, bit1);
3248 })";
3249 constexpr char kTextureDrawFS[] = R"(#version 300 es
3250
3251 uniform mediump sampler2D s0;
3252 uniform mediump sampler2D s1;
3253 uniform mediump sampler2D s2;
3254 uniform mediump sampler2D s3;
3255 uniform mediump sampler2D s4;
3256 uniform mediump sampler2D s5;
3257 uniform mediump sampler2D s6;
3258 uniform mediump sampler2D s7;
3259 uniform mediump sampler2D s8;
3260 uniform mediump sampler2D s9;
3261
3262 in mediump vec2 uv;
3263 out mediump vec4 colorOut;
3264
3265 void main()
3266 {
3267 highp vec4 result = texture(s0, uv) +
3268 texture(s1, uv) +
3269 texture(s2, uv) +
3270 texture(s3, uv) +
3271 texture(s4, uv) +
3272 texture(s5, uv) +
3273 texture(s6, uv) +
3274 texture(s7, uv) +
3275 texture(s8, uv) +
3276 texture(s9, uv);
3277 result /= 10.;
3278
3279 colorOut = result;
3280 })";
3281
3282 constexpr uint32_t kTextureCount = 10;
3283 GLuint textures[kTextureCount];
3284
3285 ASSERT(IsGLExtensionEnabled("GL_KHR_texture_compression_astc_ldr") ||
3286 IsGLExtensionEnabled("GL_EXT_texture_compression_bptc"));
3287 // Note ASTC may be emulated in ANGLE, so check for BPTC first
3288 const bool hasBPTC = IsGLExtensionEnabled("GL_EXT_texture_compression_bptc");
3289 const GLenum compressedFormat =
3290 hasBPTC ? GL_COMPRESSED_RGBA_BPTC_UNORM_EXT : GL_COMPRESSED_RGBA_ASTC_4x4_KHR;
3291
3292 std::vector<uint8_t> textureData[kTextureCount];
3293
3294 constexpr int kSurfaceWidth = 256;
3295 constexpr int kSurfaceHeight = 512;
3296 constexpr int kTexSize = 1024;
3297
3298 // Sync primitives
3299 GLsync sync = nullptr;
3300 std::mutex mutex;
3301 std::condition_variable condVar;
3302
3303 enum class Step
3304 {
3305 Start,
3306 Thread1Ready,
3307 Thread0UploadFinish,
3308 Finish,
3309 Abort,
3310 };
3311 Step currentStep = Step::Start;
3312
3313 // Threads to upload and draw with textures.
3314 auto thread0Upload = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
3315 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
3316 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
3317
3318 // Wait for the other thread to set everything up
3319 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Ready));
3320
3321 // Perform uploads while the other thread does draws
3322 for (uint32_t i = 0; i < kTextureCount; ++i)
3323 {
3324 glBindTexture(GL_TEXTURE_2D, textures[i]);
3325 glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTexSize, kTexSize, compressedFormat,
3326 static_cast<GLsizei>(textureData[i].size()),
3327 textureData[i].data());
3328 }
3329 ASSERT_GL_NO_ERROR();
3330
3331 // Create a sync object to be used for the draw thread.
3332 sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
3333 ASSERT_NE(sync, nullptr);
3334 ASSERT_GL_NO_ERROR();
3335
3336 threadSynchronization.nextStep(Step::Thread0UploadFinish);
3337 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
3338 };
3339
3340 auto thread1Draw = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
3341 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
3342 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
3343
3344 ANGLE_GL_PROGRAM(busyDrawProgram, kBusyDrawVS, kBusyDrawFS);
3345
3346 // Set up the test. Don't let the other thread work yet.
3347 glUseProgram(busyDrawProgram);
3348 GLuint busyDrawX0Loc = glGetUniformLocation(busyDrawProgram, "x0");
3349 GLuint busyDrawY0Loc = glGetUniformLocation(busyDrawProgram, "y0");
3350 GLuint busyDrawX1Loc = glGetUniformLocation(busyDrawProgram, "x1");
3351 GLuint busyDrawY1Loc = glGetUniformLocation(busyDrawProgram, "y1");
3352 GLuint busyDrawRLoc = glGetAttribLocation(busyDrawProgram, "r");
3353 GLuint busyDrawGLoc = glGetAttribLocation(busyDrawProgram, "g");
3354 GLuint busyDrawBLoc = glGetAttribLocation(busyDrawProgram, "b");
3355 GLuint busyDrawALoc = glGetAttribLocation(busyDrawProgram, "a");
3356
3357 ANGLE_GL_PROGRAM(textureDrawProgram, kTextureDrawVS, kTextureDrawFS);
3358 GLuint textureDrawSamplerLoc[kTextureCount] = {};
3359
3360 glUseProgram(textureDrawProgram);
3361 glGenTextures(kTextureCount, textures);
3362 for (uint32_t i = 0; i < kTextureCount; ++i)
3363 {
3364 std::ostringstream name;
3365 name << "s" << i;
3366
3367 textureDrawSamplerLoc[i] = glGetUniformLocation(textureDrawProgram, name.str().c_str());
3368
3369 glBindTexture(GL_TEXTURE_2D, textures[i]);
3370 glTexStorage2D(GL_TEXTURE_2D, 1, compressedFormat, kTexSize, kTexSize);
3371
3372 // Both ASTC 4x4 and BPTC have 1 byte per pixel. The textures' contents are arbitrary
3373 // but distinct.
3374 textureData[i].resize(kTexSize * kTexSize);
3375 for (int y = 0; y < kTexSize; ++y)
3376 {
3377 for (int x = 0; x < kTexSize; ++x)
3378 {
3379 textureData[i][y * kTexSize + x] = (i * 50 + y + x) % 255;
3380 }
3381 }
3382 }
3383 ASSERT_GL_NO_ERROR();
3384
3385 // Now that everything is set up, let the upload thread work while this thread does draws.
3386 threadSynchronization.nextStep(Step::Thread1Ready);
3387
3388 int w = kSurfaceWidth;
3389 int h = kSurfaceHeight;
3390
3391 glClear(GL_COLOR_BUFFER_BIT);
3392 glViewport(0, 0, w, h);
3393 glUseProgram(busyDrawProgram);
3394 for (uint32_t y = 0; y < 8; ++y)
3395 {
3396 for (uint32_t x = 0; x < 8; ++x)
3397 {
3398 float width = w / 4;
3399 float height = h / 8;
3400
3401 glUniform1f(busyDrawX0Loc, x * width / w - 1);
3402 glUniform1f(busyDrawY0Loc, y * height / h);
3403 glUniform1f(busyDrawX1Loc, (x + 1) * width / w - 1);
3404 glUniform1f(busyDrawY1Loc, (y + 1) * height / h);
3405
3406 glVertexAttrib1f(busyDrawRLoc, x / 8.0f);
3407 glVertexAttrib1f(busyDrawGLoc, y / 8.0f);
3408 glVertexAttrib1f(busyDrawBLoc, 0);
3409 glVertexAttrib1f(busyDrawALoc, 1);
3410
3411 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
3412 }
3413 }
3414 ASSERT_GL_NO_ERROR();
3415
3416 // Wait for the other thread to finish with uploads.
3417 threadSynchronization.waitForStep(Step::Thread0UploadFinish);
3418
3419 // Wait for fence and use all textures in a draw.
3420 glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
3421
3422 glUseProgram(textureDrawProgram);
3423 for (uint32_t i = 0; i < kTextureCount; ++i)
3424 {
3425 glActiveTexture(GL_TEXTURE0 + i);
3426 glBindTexture(GL_TEXTURE_2D, textures[i]);
3427 glUniform1i(textureDrawSamplerLoc[i], i);
3428 }
3429 glViewport(0, 0, w, h / 2);
3430 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
3431 ASSERT_GL_NO_ERROR();
3432
3433 threadSynchronization.nextStep(Step::Finish);
3434
3435 // Verify results
3436 for (uint32_t y = 0; y < 8; ++y)
3437 {
3438 for (uint32_t x = 0; x < 8; ++x)
3439 {
3440 int width = w / 8;
3441 int height = h / 16;
3442
3443 EXPECT_PIXEL_COLOR_NEAR(x * width + width / 2, h - (y * height + height / 2),
3444 GLColor(x * 255 / 8, (7 - y) * 255 / 8, 0, 255), 1);
3445 }
3446 }
3447 ASSERT_GL_NO_ERROR();
3448
3449 for (uint32_t x = 0; x < 8; ++x)
3450 {
3451 // The compressed data is gibberish, just ensure it's not black.
3452 EXPECT_PIXEL_COLOR_NEAR(x * w / 8, h / 4, GLColor(128, 128, 128, 128), 127);
3453 }
3454 ASSERT_GL_NO_ERROR();
3455 };
3456
3457 std::array<LockStepThreadFunc, 2> threadFuncs = {
3458 std::move(thread0Upload),
3459 std::move(thread1Draw),
3460 };
3461
3462 RunLockStepThreadsWithSize(getEGLWindow(), kSurfaceWidth, kSurfaceHeight, threadFuncs.size(),
3463 threadFuncs.data());
3464
3465 ASSERT_NE(currentStep, Step::Abort);
3466 }
3467
3468 // Test that calling glUniformBlockBinding on one context affects all contexts.
TEST_P(MultithreadingTestES3,UniformBlockBinding)3469 TEST_P(MultithreadingTestES3, UniformBlockBinding)
3470 {
3471 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
3472
3473 constexpr char kVS[] = R"(#version 300 es
3474 void main()
3475 {
3476 vec2 pos = vec2(0.0);
3477 switch (gl_VertexID) {
3478 case 0: pos = vec2(-1.0, -1.0); break;
3479 case 1: pos = vec2(3.0, -1.0); break;
3480 case 2: pos = vec2(-1.0, 3.0); break;
3481 };
3482 gl_Position = vec4(pos, 0.0, 1.0);
3483 })";
3484 constexpr char kFS[] = R"(#version 300 es
3485 out mediump vec4 colorOut;
3486
3487 layout(std140) uniform buffer { mediump vec4 color; };
3488
3489 void main()
3490 {
3491 colorOut = color;
3492 })";
3493
3494 GLProgram program;
3495 GLint uniformBufferIndex;
3496
3497 // Sync primitives
3498 std::mutex mutex;
3499 std::condition_variable condVar;
3500
3501 enum class Step
3502 {
3503 Start,
3504 Thread1Ready,
3505 Thread0BindingChanged,
3506 Thread1FinishedDrawing,
3507 Finish,
3508 Abort,
3509 };
3510 Step currentStep = Step::Start;
3511
3512 // Threads to create programs and draw with different uniform blocks.
3513 auto thread0 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
3514 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
3515 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
3516
3517 // Create buffers bound to bindings 1 and 2
3518 constexpr std::array<float, 4> kRed = {1, 0, 0, 1};
3519 constexpr std::array<float, 4> kTransparentGreen = {0, 1, 0, 0};
3520 GLBuffer red, transparentGreen;
3521 glBindBuffer(GL_UNIFORM_BUFFER, red);
3522 glBufferData(GL_UNIFORM_BUFFER, sizeof(kRed), kRed.data(), GL_STATIC_DRAW);
3523 glBindBuffer(GL_UNIFORM_BUFFER, transparentGreen);
3524 glBufferData(GL_UNIFORM_BUFFER, sizeof(kTransparentGreen), kTransparentGreen.data(),
3525 GL_STATIC_DRAW);
3526
3527 glBindBufferBase(GL_UNIFORM_BUFFER, 1, transparentGreen);
3528 glBindBufferBase(GL_UNIFORM_BUFFER, 2, red);
3529
3530 // Wait for the other thread to set everything up
3531 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Ready));
3532
3533 // Issue a draw call. The buffer should be transparent green now
3534 glClearColor(0, 0, 0, 0);
3535 glClear(GL_COLOR_BUFFER_BIT);
3536 glEnable(GL_BLEND);
3537 glBlendFunc(GL_ONE, GL_ONE);
3538
3539 glUseProgram(program);
3540 glDrawArrays(GL_TRIANGLES, 0, 3);
3541
3542 // Change the binding
3543 glUniformBlockBinding(program, uniformBufferIndex, 1);
3544 ASSERT_GL_NO_ERROR();
3545
3546 // Let the other thread work before any deferred operations for the binding change above are
3547 // processed in this context.
3548 threadSynchronization.nextStep(Step::Thread0BindingChanged);
3549 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1FinishedDrawing));
3550
3551 // Draw again, it should accumulate blue and the buffer should become magenta.
3552 glDrawArrays(GL_TRIANGLES, 0, 3);
3553
3554 // Verify results
3555 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
3556 ASSERT_GL_NO_ERROR();
3557
3558 threadSynchronization.nextStep(Step::Finish);
3559 };
3560
3561 auto thread1 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
3562 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
3563 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
3564
3565 // Create buffers bound to bindings 1 and 2
3566 constexpr std::array<float, 4> kBlue = {0, 0, 1, 1};
3567 constexpr std::array<float, 4> kTransparentRed = {1, 0, 0, 0};
3568 GLBuffer blue, transparentRed;
3569 glBindBuffer(GL_UNIFORM_BUFFER, blue);
3570 glBufferData(GL_UNIFORM_BUFFER, sizeof(kBlue), kBlue.data(), GL_STATIC_DRAW);
3571 glBindBuffer(GL_UNIFORM_BUFFER, transparentRed);
3572 glBufferData(GL_UNIFORM_BUFFER, sizeof(kTransparentRed), kTransparentRed.data(),
3573 GL_STATIC_DRAW);
3574
3575 glBindBufferBase(GL_UNIFORM_BUFFER, 1, blue);
3576 glBindBufferBase(GL_UNIFORM_BUFFER, 2, transparentRed);
3577
3578 // Create the program
3579 program.makeRaster(kVS, kFS);
3580 glUseProgram(program);
3581 uniformBufferIndex = glGetUniformBlockIndex(program, "buffer");
3582
3583 // Configure the buffer binding to binding 2
3584 glUniformBlockBinding(program, uniformBufferIndex, 2);
3585 ASSERT_GL_NO_ERROR();
3586
3587 // Issue a draw call. The buffer should be transparent red now
3588 glClearColor(0, 0, 0, 0);
3589 glClear(GL_COLOR_BUFFER_BIT);
3590 glEnable(GL_BLEND);
3591 glBlendFunc(GL_ONE, GL_ONE);
3592
3593 glDrawArrays(GL_TRIANGLES, 0, 3);
3594
3595 // Now that everything is set up, let the other thread continue
3596 threadSynchronization.nextStep(Step::Thread1Ready);
3597 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0BindingChanged));
3598
3599 // The other thread has changed the binding. Draw again, it should accumulate blue and the
3600 // buffer should become magenta.
3601 glDrawArrays(GL_TRIANGLES, 0, 3);
3602
3603 // Verify results
3604 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::magenta);
3605 ASSERT_GL_NO_ERROR();
3606
3607 // Tell the other thread to finish up.
3608 threadSynchronization.nextStep(Step::Thread1FinishedDrawing);
3609 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
3610 };
3611
3612 std::array<LockStepThreadFunc, 2> threadFuncs = {
3613 std::move(thread0),
3614 std::move(thread1),
3615 };
3616
3617 RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data());
3618
3619 ASSERT_NE(currentStep, Step::Abort);
3620 }
3621
3622 // Test that observers are notified of a change in foveation state of a texture
TEST_P(MultithreadingTestES3,SharedFoveatedTexture)3623 TEST_P(MultithreadingTestES3, SharedFoveatedTexture)
3624 {
3625 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
3626 ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_QCOM_texture_foveated"));
3627
3628 // Shared texture
3629 GLTexture texture;
3630
3631 // Sync primitives
3632 std::mutex mutex;
3633 std::condition_variable condVar;
3634
3635 enum class Step
3636 {
3637 Start,
3638 Thread0Draw,
3639 Thread1Draw,
3640 Thread0ConfiguredTextureFoveation,
3641 Finish,
3642 Abort,
3643 };
3644 Step currentStep = Step::Start;
3645
3646 // Thread to configure texture foveation.
3647 auto thread0 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
3648 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
3649 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
3650
3651 // Create non-foveated framebuffer and attach shared texture as color attachment
3652 GLFramebuffer framebuffer;
3653 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
3654 glActiveTexture(GL_TEXTURE0);
3655 glBindTexture(GL_TEXTURE_2D, texture);
3656 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
3657 GL_UNSIGNED_BYTE, nullptr);
3658 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
3659 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
3660 EXPECT_GL_NO_ERROR();
3661 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
3662 ASSERT_GL_NO_ERROR();
3663 EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
3664
3665 // Render before configuring foveation on the texture
3666 ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
3667 glUseProgram(greenProgram);
3668
3669 // Draw
3670 drawQuad(greenProgram, essl1_shaders::PositionAttrib(), 0.5f);
3671 EXPECT_GL_NO_ERROR();
3672
3673 // Verify results
3674 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
3675 ASSERT_GL_NO_ERROR();
3676
3677 threadSynchronization.nextStep(Step::Thread0Draw);
3678 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Draw));
3679
3680 // Configure foveation for the texture
3681 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_FOVEATED_FEATURE_BITS_QCOM,
3682 GL_FOVEATION_ENABLE_BIT_QCOM);
3683 EXPECT_GL_NO_ERROR();
3684 glTextureFoveationParametersQCOM(texture, 0, 0, 0.0f, 0.0f, 8.0f, 8.0f, 0.0f);
3685 EXPECT_GL_NO_ERROR();
3686
3687 threadSynchronization.nextStep(Step::Thread0ConfiguredTextureFoveation);
3688 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
3689 };
3690
3691 auto thread1 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
3692 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
3693 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
3694
3695 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Draw));
3696
3697 // Create non-foveated framebuffer and attach shared texture as color attachment
3698 GLFramebuffer framebuffer;
3699 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
3700 glActiveTexture(GL_TEXTURE0);
3701 glBindTexture(GL_TEXTURE_2D, texture);
3702 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
3703 ASSERT_GL_NO_ERROR();
3704 EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
3705
3706 // Render before configuring foveation on the texture
3707 ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
3708 glUseProgram(redProgram);
3709
3710 // Draw
3711 drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.5f);
3712 EXPECT_GL_NO_ERROR();
3713
3714 // Verify results
3715 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
3716 ASSERT_GL_NO_ERROR();
3717
3718 threadSynchronization.nextStep(Step::Thread1Draw);
3719 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0ConfiguredTextureFoveation));
3720
3721 // Render after texture foveation was configured
3722 ANGLE_GL_PROGRAM(blueProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
3723 glUseProgram(blueProgram);
3724
3725 // Draw
3726 drawQuad(blueProgram, essl1_shaders::PositionAttrib(), 0.5f);
3727 EXPECT_GL_NO_ERROR();
3728
3729 // Verify results
3730 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
3731 ASSERT_GL_NO_ERROR();
3732
3733 threadSynchronization.nextStep(Step::Finish);
3734 };
3735
3736 std::array<LockStepThreadFunc, 2> threadFuncs = {
3737 std::move(thread0),
3738 std::move(thread1),
3739 };
3740
3741 RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data());
3742
3743 ASSERT_NE(currentStep, Step::Abort);
3744 }
3745
3746 // Test that a program linked in one context can be bound in another context while link may be
3747 // happening in parallel.
TEST_P(MultithreadingTest,ProgramLinkAndBind)3748 TEST_P(MultithreadingTest, ProgramLinkAndBind)
3749 {
3750 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
3751
3752 GLuint vs;
3753 GLuint redfs;
3754 GLuint greenfs;
3755
3756 GLuint program;
3757
3758 // Sync primitives
3759 std::mutex mutex;
3760 std::condition_variable condVar;
3761
3762 enum class Step
3763 {
3764 Start,
3765 Thread1Ready,
3766 Thread0ProgramLinked,
3767 Thread1FinishedDrawing,
3768 Finish,
3769 Abort,
3770 };
3771 Step currentStep = Step::Start;
3772
3773 // Threads to create programs and draw.
3774 auto thread0 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
3775 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
3776 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
3777
3778 // Wait for thread 1 to bind the program before linking it
3779 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Ready));
3780
3781 glUseProgram(program);
3782
3783 // Link a program, but don't resolve link.
3784 glDetachShader(program, greenfs);
3785 glAttachShader(program, redfs);
3786 glLinkProgram(program);
3787
3788 // Let the other thread bind and use the program.
3789 threadSynchronization.nextStep(Step::Thread0ProgramLinked);
3790 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1FinishedDrawing));
3791
3792 // Draw in this context too
3793 drawQuad(program, essl1_shaders::PositionAttrib(), 0);
3794
3795 // Verify results
3796 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
3797 ASSERT_GL_NO_ERROR();
3798
3799 threadSynchronization.nextStep(Step::Finish);
3800 };
3801
3802 auto thread1 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
3803 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
3804 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
3805
3806 vs = CompileShader(GL_VERTEX_SHADER, essl1_shaders::vs::Simple());
3807 redfs = CompileShader(GL_FRAGMENT_SHADER, essl1_shaders::fs::Red());
3808 greenfs = CompileShader(GL_FRAGMENT_SHADER, essl1_shaders::fs::Green());
3809 program = glCreateProgram();
3810
3811 glAttachShader(program, vs);
3812 glAttachShader(program, greenfs);
3813 glLinkProgram(program);
3814 ASSERT_NE(CheckLinkStatusAndReturnProgram(program, true), 0u);
3815
3816 // Bind the program before it's relinked. Otherwise the program is resolved before the
3817 // binding happens.
3818 glUseProgram(program);
3819
3820 threadSynchronization.nextStep(Step::Thread1Ready);
3821 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0ProgramLinked));
3822
3823 // Unbind and rebind for extra testing
3824 glUseProgram(0);
3825 glUseProgram(program);
3826
3827 // Issue a draw call
3828 drawQuad(program, essl1_shaders::PositionAttrib(), 0);
3829
3830 // Verify results
3831 EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
3832 ASSERT_GL_NO_ERROR();
3833
3834 // Tell the other thread to finish up.
3835 threadSynchronization.nextStep(Step::Thread1FinishedDrawing);
3836 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
3837 };
3838
3839 std::array<LockStepThreadFunc, 2> threadFuncs = {
3840 std::move(thread0),
3841 std::move(thread1),
3842 };
3843
3844 RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data());
3845
3846 ASSERT_NE(currentStep, Step::Abort);
3847 }
3848
3849 // Test that two contexts in share group can generate, delete and bind buffers for themselves in
3850 // parallel.
TEST_P(MultithreadingTestES3,SimultaneousBufferBindAndGen)3851 TEST_P(MultithreadingTestES3, SimultaneousBufferBindAndGen)
3852 {
3853 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
3854
3855 constexpr char kFS[] = R"(#version 300 es
3856 precision mediump float;
3857
3858 layout(std140) uniform Block
3859 {
3860 vec4 colorIn;
3861 };
3862
3863 out vec4 color;
3864
3865 void main()
3866 {
3867 color = colorIn;
3868 })";
3869
3870 constexpr int kSurfaceWidth = 32;
3871 constexpr int kSurfaceHeight = 128;
3872
3873 // Sync primitives
3874 std::mutex mutex;
3875 std::condition_variable condVar;
3876
3877 enum class Step
3878 {
3879 Start,
3880 Thread0Ready,
3881 Thread1Ready,
3882 Finish,
3883 Abort,
3884 };
3885 Step currentStep = Step::Start;
3886
3887 auto threadFunc = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context, uint32_t index) {
3888 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
3889 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
3890
3891 ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
3892
3893 // Make sure the two threads start work around the same time
3894 if (index == 0)
3895 {
3896 threadSynchronization.nextStep(Step::Thread0Ready);
3897 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Ready));
3898 }
3899 else
3900 {
3901 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Ready));
3902 threadSynchronization.nextStep(Step::Thread1Ready);
3903 }
3904
3905 std::vector<GLuint> buffers(kSurfaceWidth * kSurfaceHeight);
3906
3907 glEnable(GL_SCISSOR_TEST);
3908 for (int y = 0; y < kSurfaceHeight; ++y)
3909 {
3910 for (int x = 0; x < kSurfaceWidth; ++x)
3911 {
3912 GLuint &buffer = buffers[y * kSurfaceWidth + x];
3913 const float bufferData[4] = {
3914 ((y * kSurfaceWidth + x + index * 100) % 255) / 255.0f,
3915 ((y * kSurfaceWidth + x + index * 100 + 1) % 255) / 255.0f,
3916 ((y * kSurfaceWidth + x + index * 100 + 2) % 255) / 255.0f,
3917 ((y * kSurfaceWidth + x + index * 100 + 3) % 255) / 255.0f,
3918 };
3919
3920 // Generate one buffer per pixel and shade the pixel with it.
3921 glGenBuffers(1, &buffer);
3922 glBindBuffer(GL_UNIFORM_BUFFER, buffers[y * kSurfaceWidth + x]);
3923 glBufferData(GL_UNIFORM_BUFFER, sizeof(bufferData), bufferData, GL_STATIC_DRAW);
3924 glBindBufferBase(GL_UNIFORM_BUFFER, 0, buffer);
3925
3926 glScissor(x, y, 1, 1);
3927 drawQuad(program, essl3_shaders::PositionAttrib(), 0);
3928
3929 if ((x + y) % 2 == 0)
3930 {
3931 glDeleteBuffers(1, &buffer);
3932 buffer = 0;
3933 }
3934 }
3935 }
3936
3937 // Verify the results
3938 auto verify = [&](int x, int y) {
3939 const GLColor expect((y * kSurfaceWidth + x + index * 100) % 255,
3940 (y * kSurfaceWidth + x + index * 100 + 1) % 255,
3941 (y * kSurfaceWidth + x + index * 100 + 2) % 255,
3942 (y * kSurfaceWidth + x + index * 100 + 3) % 255);
3943 EXPECT_PIXEL_COLOR_EQ(x, y, expect);
3944 };
3945
3946 verify(0, 0);
3947 verify(0, kSurfaceHeight - 1);
3948 verify(kSurfaceWidth - 1, 0);
3949 verify(kSurfaceWidth - 1, kSurfaceHeight - 1);
3950 verify(kSurfaceWidth / 2, kSurfaceHeight / 2);
3951 ASSERT_GL_NO_ERROR();
3952
3953 if (index == 0)
3954 {
3955 threadSynchronization.nextStep(Step::Finish);
3956 }
3957 else
3958 {
3959 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
3960 }
3961 };
3962
3963 auto thread0 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
3964 threadFunc(dpy, surface, context, 0);
3965 };
3966 auto thread1 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
3967 threadFunc(dpy, surface, context, 1);
3968 };
3969
3970 std::array<LockStepThreadFunc, 2> threadFuncs = {
3971 std::move(thread0),
3972 std::move(thread1),
3973 };
3974
3975 RunLockStepThreadsWithSize(getEGLWindow(), kSurfaceWidth, kSurfaceHeight, threadFuncs.size(),
3976 threadFuncs.data());
3977
3978 ASSERT_NE(currentStep, Step::Abort);
3979 }
3980
3981 // Test that ref counting is thread-safe when the same buffer is used in multiple threads.
TEST_P(MultithreadingTestES3,SimultaneousBufferBind)3982 TEST_P(MultithreadingTestES3, SimultaneousBufferBind)
3983 {
3984 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
3985
3986 constexpr char kFS[] = R"(#version 300 es
3987 precision mediump float;
3988
3989 layout(std140) uniform Block
3990 {
3991 vec4 colorIn;
3992 };
3993
3994 out vec4 color;
3995
3996 void main()
3997 {
3998 color = colorIn;
3999 })";
4000
4001 constexpr int kSurfaceWidth = 32;
4002 constexpr int kSurfaceHeight = 128;
4003
4004 GLuint buffer;
4005 GLsync sync = nullptr;
4006
4007 // Sync primitives
4008 std::mutex mutex;
4009 std::condition_variable condVar;
4010
4011 enum class Step
4012 {
4013 Start,
4014 Thread0Ready,
4015 Thread1Ready,
4016 Finish,
4017 Abort,
4018 };
4019 Step currentStep = Step::Start;
4020
4021 auto thread0 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
4022 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
4023 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
4024
4025 ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
4026
4027 // Create the buffer in this context
4028 glGenBuffers(1, &buffer);
4029
4030 constexpr float kBufferData[4] = {
4031 10.0f / 255.0f,
4032 50.0f / 255.0f,
4033 130.0f / 255.0f,
4034 220.0f / 255.0f,
4035 };
4036 glBindBuffer(GL_UNIFORM_BUFFER, buffer);
4037 glBufferData(GL_UNIFORM_BUFFER, sizeof(kBufferData), kBufferData, GL_STATIC_DRAW);
4038
4039 sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
4040
4041 // Make sure the two threads start work around the same time
4042 threadSynchronization.nextStep(Step::Thread0Ready);
4043 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Ready));
4044
4045 // Bind and unbind the buffer many times. If ref counting is not thread safe, chances are
4046 // the ref count would be incorrect in the end. This can result in the buffer prematurely
4047 // getting deleted.
4048 for (uint32_t i = 0; i < 8000; ++i)
4049 {
4050 glBindBuffer(GL_UNIFORM_BUFFER, i % 2 == 0 ? 0 : buffer);
4051 }
4052 ASSERT_GL_NO_ERROR();
4053
4054 threadSynchronization.nextStep(Step::Finish);
4055 };
4056 auto thread1 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
4057 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
4058 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));
4059
4060 ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
4061
4062 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Ready));
4063 threadSynchronization.nextStep(Step::Thread1Ready);
4064
4065 glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
4066
4067 // Bind and unbind the buffer many times.
4068 for (uint32_t i = 0; i < 4000; ++i)
4069 {
4070 glBindBuffer(GL_UNIFORM_BUFFER, i % 2 == 0 ? buffer : 0);
4071 }
4072
4073 // Draw with it to make sure buffer is still valid and not accidentally deleted due to bad
4074 // ref counting.
4075 glBindBuffer(GL_UNIFORM_BUFFER, buffer);
4076 glBindBufferBase(GL_UNIFORM_BUFFER, 0, buffer);
4077 drawQuad(program, essl3_shaders::PositionAttrib(), 0);
4078
4079 // Verify the results
4080 const GLColor expect(10, 50, 130, 220);
4081 EXPECT_PIXEL_RECT_EQ(0, 0, kSurfaceWidth, kSurfaceHeight, expect);
4082 ASSERT_GL_NO_ERROR();
4083
4084 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
4085 };
4086
4087 std::array<LockStepThreadFunc, 2> threadFuncs = {
4088 std::move(thread0),
4089 std::move(thread1),
4090 };
4091
4092 RunLockStepThreadsWithSize(getEGLWindow(), kSurfaceWidth, kSurfaceHeight, threadFuncs.size(),
4093 threadFuncs.data());
4094
4095 ASSERT_NE(currentStep, Step::Abort);
4096 }
4097 ANGLE_INSTANTIATE_TEST(
4098 MultithreadingTest,
4099 ES2_OPENGL(),
4100 ES3_OPENGL(),
4101 ES2_OPENGLES(),
4102 ES3_OPENGLES(),
4103 ES3_VULKAN(),
4104 ES3_VULKAN_SWIFTSHADER().enable(Feature::AsyncCommandQueue),
4105 ES3_VULKAN_SWIFTSHADER()
4106 .enable(Feature::AsyncCommandQueue)
4107 .enable(Feature::SlowAsyncCommandQueueForTesting),
4108 ES3_VULKAN_SWIFTSHADER().disable(Feature::PreferMonolithicPipelinesOverLibraries),
4109 ES3_VULKAN_SWIFTSHADER().enable(Feature::PreferMonolithicPipelinesOverLibraries),
4110 ES3_VULKAN_SWIFTSHADER()
4111 .enable(Feature::PreferMonolithicPipelinesOverLibraries)
4112 .enable(Feature::SlowDownMonolithicPipelineCreationForTesting),
4113 ES3_VULKAN_SWIFTSHADER()
4114 .enable(Feature::PreferMonolithicPipelinesOverLibraries)
4115 .disable(Feature::MergeProgramPipelineCachesToGlobalCache),
4116 ES3_VULKAN_SWIFTSHADER().enable(Feature::PermanentlySwitchToFramebufferFetchMode),
4117 ES3_VULKAN_SWIFTSHADER()
4118 .enable(Feature::PermanentlySwitchToFramebufferFetchMode)
4119 .enable(Feature::PreferMonolithicPipelinesOverLibraries),
4120 ES3_VULKAN_SWIFTSHADER()
4121 .enable(Feature::PermanentlySwitchToFramebufferFetchMode)
4122 .enable(Feature::PreferMonolithicPipelinesOverLibraries)
4123 .enable(Feature::SlowDownMonolithicPipelineCreationForTesting),
4124 ES2_D3D11(),
4125 ES3_D3D11());
4126
4127 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MultithreadingTestES3);
4128 ANGLE_INSTANTIATE_TEST(
4129 MultithreadingTestES3,
4130 ES3_OPENGL(),
4131 ES3_OPENGLES(),
4132 ES3_VULKAN(),
4133 ES3_VULKAN_SWIFTSHADER().enable(Feature::AsyncCommandQueue),
4134 ES3_VULKAN_SWIFTSHADER()
4135 .enable(Feature::AsyncCommandQueue)
4136 .enable(Feature::SlowAsyncCommandQueueForTesting),
4137 ES3_VULKAN_SWIFTSHADER().disable(Feature::PreferMonolithicPipelinesOverLibraries),
4138 ES3_VULKAN_SWIFTSHADER().enable(Feature::PreferMonolithicPipelinesOverLibraries),
4139 ES3_VULKAN_SWIFTSHADER()
4140 .enable(Feature::PreferMonolithicPipelinesOverLibraries)
4141 .enable(Feature::SlowDownMonolithicPipelineCreationForTesting),
4142 ES3_VULKAN_SWIFTSHADER()
4143 .enable(Feature::PreferMonolithicPipelinesOverLibraries)
4144 .disable(Feature::MergeProgramPipelineCachesToGlobalCache),
4145 ES3_VULKAN_SWIFTSHADER().enable(Feature::PermanentlySwitchToFramebufferFetchMode),
4146 ES3_VULKAN_SWIFTSHADER()
4147 .enable(Feature::PermanentlySwitchToFramebufferFetchMode)
4148 .enable(Feature::PreferMonolithicPipelinesOverLibraries),
4149 ES3_VULKAN_SWIFTSHADER()
4150 .enable(Feature::PermanentlySwitchToFramebufferFetchMode)
4151 .enable(Feature::PreferMonolithicPipelinesOverLibraries)
4152 .enable(Feature::SlowDownMonolithicPipelineCreationForTesting),
4153 ES3_D3D11());
4154
4155 } // namespace angle
4156