• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright 2016 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 // EGLContextSharingTest.cpp:
7 //   Tests relating to shared Contexts.
8 
9 #include <gtest/gtest.h>
10 
11 #include "test_utils/ANGLETest.h"
12 #include "test_utils/MultiThreadSteps.h"
13 #include "test_utils/angle_test_configs.h"
14 #include "test_utils/gl_raii.h"
15 #include "util/EGLWindow.h"
16 #include "util/OSWindow.h"
17 
18 using namespace angle;
19 
20 namespace
21 {
22 
SafeDestroyContext(EGLDisplay display,EGLContext & context)23 EGLBoolean SafeDestroyContext(EGLDisplay display, EGLContext &context)
24 {
25     EGLBoolean result = EGL_TRUE;
26     if (context != EGL_NO_CONTEXT)
27     {
28         result  = eglDestroyContext(display, context);
29         context = EGL_NO_CONTEXT;
30     }
31     return result;
32 }
33 
34 class EGLContextSharingTest : public ANGLETest
35 {
36   public:
EGLContextSharingTest()37     EGLContextSharingTest() : mContexts{EGL_NO_CONTEXT, EGL_NO_CONTEXT}, mTexture(0) {}
38 
testTearDown()39     void testTearDown() override
40     {
41         glDeleteTextures(1, &mTexture);
42 
43         EGLDisplay display = getEGLWindow()->getDisplay();
44 
45         if (display != EGL_NO_DISPLAY)
46         {
47             for (auto &context : mContexts)
48             {
49                 SafeDestroyContext(display, context);
50             }
51         }
52 
53         // Set default test state to not give an error on shutdown.
54         getEGLWindow()->makeCurrent();
55     }
56 
57     EGLContext mContexts[2] = {EGL_NO_CONTEXT, EGL_NO_CONTEXT};
58     GLuint mTexture;
59 };
60 
61 class EGLContextSharingTestNoFixture : public EGLContextSharingTest
62 {
63   public:
EGLContextSharingTestNoFixture()64     EGLContextSharingTestNoFixture() : EGLContextSharingTest() {}
65 
testSetUp()66     void testSetUp() override
67     {
68         mOsWindow     = OSWindow::New();
69         mMajorVersion = GetParam().majorVersion;
70     }
71 
testTearDown()72     void testTearDown() override
73     {
74         if (mDisplay != EGL_NO_DISPLAY)
75         {
76             eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
77 
78             if (mSurface != EGL_NO_SURFACE)
79             {
80                 eglDestroySurface(mDisplay, mSurface);
81                 ASSERT_EGL_SUCCESS();
82                 mSurface = EGL_NO_SURFACE;
83             }
84 
85             for (auto &context : mContexts)
86             {
87                 SafeDestroyContext(mDisplay, context);
88             }
89 
90             eglTerminate(mDisplay);
91             mDisplay = EGL_NO_DISPLAY;
92             ASSERT_EGL_SUCCESS();
93             eglReleaseThread();
94             ASSERT_EGL_SUCCESS();
95         }
96 
97         mOsWindow->destroy();
98         OSWindow::Delete(&mOsWindow);
99         ASSERT_EGL_SUCCESS() << "Error during test TearDown";
100     }
101 
chooseConfig(EGLConfig * config) const102     bool chooseConfig(EGLConfig *config) const
103     {
104         bool result          = false;
105         EGLint count         = 0;
106         EGLint clientVersion = mMajorVersion == 3 ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_ES2_BIT;
107         EGLint attribs[]     = {EGL_RED_SIZE,
108                             8,
109                             EGL_GREEN_SIZE,
110                             8,
111                             EGL_BLUE_SIZE,
112                             8,
113                             EGL_ALPHA_SIZE,
114                             0,
115                             EGL_RENDERABLE_TYPE,
116                             clientVersion,
117                             EGL_SURFACE_TYPE,
118                             EGL_WINDOW_BIT,
119                             EGL_NONE};
120 
121         result = eglChooseConfig(mDisplay, attribs, config, 1, &count);
122         EXPECT_EGL_TRUE(result && (count > 0));
123         return result;
124     }
125 
createContext(EGLConfig config,EGLContext * context)126     bool createContext(EGLConfig config, EGLContext *context)
127     {
128         bool result      = false;
129         EGLint attribs[] = {EGL_CONTEXT_MAJOR_VERSION, mMajorVersion, EGL_NONE};
130 
131         *context = eglCreateContext(mDisplay, config, nullptr, attribs);
132         result   = (*context != EGL_NO_CONTEXT);
133         EXPECT_TRUE(result);
134         return result;
135     }
136 
createWindowSurface(EGLConfig config,EGLNativeWindowType win,EGLSurface * surface)137     bool createWindowSurface(EGLConfig config, EGLNativeWindowType win, EGLSurface *surface)
138     {
139         bool result      = false;
140         EGLint attribs[] = {EGL_NONE};
141 
142         *surface = eglCreateWindowSurface(mDisplay, config, win, attribs);
143         result   = (*surface != EGL_NO_SURFACE);
144         EXPECT_TRUE(result);
145         return result;
146     }
147 
148     OSWindow *mOsWindow;
149     EGLDisplay mDisplay  = EGL_NO_DISPLAY;
150     EGLSurface mSurface  = EGL_NO_SURFACE;
151     const EGLint kWidth  = 64;
152     const EGLint kHeight = 64;
153     EGLint mMajorVersion = 0;
154 };
155 
156 // Tests that creating resources works after freeing the share context.
TEST_P(EGLContextSharingTest,BindTextureAfterShareContextFree)157 TEST_P(EGLContextSharingTest, BindTextureAfterShareContextFree)
158 {
159     EGLDisplay display = getEGLWindow()->getDisplay();
160     EGLConfig config   = getEGLWindow()->getConfig();
161     EGLSurface surface = getEGLWindow()->getSurface();
162 
163     const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION,
164                                      getEGLWindow()->getClientMajorVersion(), EGL_NONE};
165 
166     mContexts[0] = eglCreateContext(display, config, nullptr, contextAttribs);
167     ASSERT_EGL_SUCCESS();
168     ASSERT_TRUE(mContexts[0] != EGL_NO_CONTEXT);
169     mContexts[1] = eglCreateContext(display, config, mContexts[1], contextAttribs);
170     ASSERT_EGL_SUCCESS();
171     ASSERT_TRUE(mContexts[1] != EGL_NO_CONTEXT);
172 
173     ASSERT_EGL_TRUE(SafeDestroyContext(display, mContexts[0]));
174     ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[1]));
175     ASSERT_EGL_SUCCESS();
176 
177     glGenTextures(1, &mTexture);
178     glBindTexture(GL_TEXTURE_2D, mTexture);
179     ASSERT_GL_NO_ERROR();
180 }
181 
182 // Tests the creation of contexts using EGL_ANGLE_display_texture_share_group
TEST_P(EGLContextSharingTest,DisplayShareGroupContextCreation)183 TEST_P(EGLContextSharingTest, DisplayShareGroupContextCreation)
184 {
185     EGLDisplay display = getEGLWindow()->getDisplay();
186     EGLConfig config   = getEGLWindow()->getConfig();
187 
188     const EGLint inShareGroupContextAttribs[] = {
189         EGL_CONTEXT_CLIENT_VERSION, 2, EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE, EGL_TRUE, EGL_NONE};
190 
191     // Check whether extension's supported to avoid clearing the EGL error state
192     // after failed context creation.
193     bool extensionEnabled =
194         IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_display_texture_share_group");
195 
196     // Test creating two contexts in the global share group
197     mContexts[0] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs);
198     mContexts[1] = eglCreateContext(display, config, mContexts[1], inShareGroupContextAttribs);
199 
200     if (!extensionEnabled)
201     {
202         // Make sure an error is generated and early-exit
203         ASSERT_EGL_ERROR(EGL_BAD_ATTRIBUTE);
204         ASSERT_EQ(EGL_NO_CONTEXT, mContexts[0]);
205         return;
206     }
207 
208     ASSERT_EGL_SUCCESS();
209 
210     ASSERT_NE(EGL_NO_CONTEXT, mContexts[0]);
211     ASSERT_NE(EGL_NO_CONTEXT, mContexts[1]);
212     eglDestroyContext(display, mContexts[0]);
213     mContexts[0] = EGL_NO_CONTEXT;
214 
215     // Try creating a context that is not in the global share group but tries to share with a
216     // context that is
217     const EGLint notInShareGroupContextAttribs[] = {
218         EGL_CONTEXT_CLIENT_VERSION, 2, EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE, EGL_FALSE, EGL_NONE};
219     mContexts[0] = eglCreateContext(display, config, mContexts[1], notInShareGroupContextAttribs);
220     ASSERT_EGL_ERROR(EGL_BAD_ATTRIBUTE);
221     ASSERT_TRUE(mContexts[0] == EGL_NO_CONTEXT);
222 }
223 
224 // Tests the sharing of textures using EGL_ANGLE_display_texture_share_group
TEST_P(EGLContextSharingTest,DisplayShareGroupObjectSharing)225 TEST_P(EGLContextSharingTest, DisplayShareGroupObjectSharing)
226 {
227     EGLDisplay display = getEGLWindow()->getDisplay();
228     if (!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_display_texture_share_group"))
229     {
230         std::cout << "Test skipped because EGL_ANGLE_display_texture_share_group is not present."
231                   << std::endl;
232         return;
233     }
234 
235     EGLConfig config   = getEGLWindow()->getConfig();
236     EGLSurface surface = getEGLWindow()->getSurface();
237 
238     const EGLint inShareGroupContextAttribs[] = {
239         EGL_CONTEXT_CLIENT_VERSION, 2, EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE, EGL_TRUE, EGL_NONE};
240 
241     // Create two contexts in the global share group but not in the same context share group
242     mContexts[0] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs);
243     mContexts[1] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs);
244 
245     ASSERT_EGL_SUCCESS();
246 
247     ASSERT_NE(EGL_NO_CONTEXT, mContexts[0]);
248     ASSERT_NE(EGL_NO_CONTEXT, mContexts[1]);
249 
250     ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0]));
251     ASSERT_EGL_SUCCESS();
252 
253     // Create a texture and buffer in ctx 0
254     GLTexture textureFromCtx0;
255     glBindTexture(GL_TEXTURE_2D, textureFromCtx0);
256     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
257     glBindTexture(GL_TEXTURE_2D, 0);
258     ASSERT_GL_TRUE(glIsTexture(textureFromCtx0));
259 
260     GLBuffer bufferFromCtx0;
261     glBindBuffer(GL_ARRAY_BUFFER, bufferFromCtx0);
262     glBufferData(GL_ARRAY_BUFFER, 1, nullptr, GL_STATIC_DRAW);
263     glBindBuffer(GL_ARRAY_BUFFER, 0);
264     ASSERT_GL_TRUE(glIsBuffer(bufferFromCtx0));
265 
266     ASSERT_GL_NO_ERROR();
267 
268     // Switch to context 1 and verify that the texture is accessible but the buffer is not
269     ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[1]));
270     ASSERT_EGL_SUCCESS();
271 
272     ASSERT_GL_TRUE(glIsTexture(textureFromCtx0));
273 
274     ASSERT_GL_FALSE(glIsBuffer(bufferFromCtx0));
275     ASSERT_GL_NO_ERROR();
276 
277     // Call readpixels on the texture to verify that the backend has proper support
278     GLFramebuffer fbo;
279     glBindFramebuffer(GL_FRAMEBUFFER, fbo);
280     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureFromCtx0, 0);
281 
282     GLubyte pixel[4];
283     glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
284     ASSERT_GL_NO_ERROR();
285 
286     // Switch back to context 0 and delete the buffer
287     ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0]));
288     ASSERT_EGL_SUCCESS();
289 }
290 
291 // Tests that shared textures using EGL_ANGLE_display_texture_share_group are released when the last
292 // context is destroyed
TEST_P(EGLContextSharingTest,DisplayShareGroupReleasedWithLastContext)293 TEST_P(EGLContextSharingTest, DisplayShareGroupReleasedWithLastContext)
294 {
295     EGLDisplay display = getEGLWindow()->getDisplay();
296     if (!IsEGLDisplayExtensionEnabled(display, "EGL_ANGLE_display_texture_share_group"))
297     {
298         std::cout << "Test skipped because EGL_ANGLE_display_texture_share_group is not present."
299                   << std::endl;
300         return;
301     }
302 
303     EGLConfig config   = getEGLWindow()->getConfig();
304     EGLSurface surface = getEGLWindow()->getSurface();
305 
306     const EGLint inShareGroupContextAttribs[] = {
307         EGL_CONTEXT_CLIENT_VERSION, 2, EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE, EGL_TRUE, EGL_NONE};
308 
309     // Create two contexts in the global share group but not in the same context share group
310     mContexts[0] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs);
311     mContexts[1] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs);
312 
313     // Create a texture and buffer in ctx 0
314     ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0]));
315     GLTexture textureFromCtx0;
316     glBindTexture(GL_TEXTURE_2D, textureFromCtx0);
317     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
318     glBindTexture(GL_TEXTURE_2D, 0);
319     ASSERT_GL_TRUE(glIsTexture(textureFromCtx0));
320 
321     // Switch to context 1 and verify that the texture is accessible
322     ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[1]));
323     ASSERT_GL_TRUE(glIsTexture(textureFromCtx0));
324 
325     // Destroy both contexts, the texture should be cleaned up automatically
326     ASSERT_EGL_TRUE(eglDestroyContext(display, mContexts[0]));
327     mContexts[0] = EGL_NO_CONTEXT;
328     ASSERT_EGL_TRUE(eglDestroyContext(display, mContexts[1]));
329     mContexts[1] = EGL_NO_CONTEXT;
330 
331     // Unmake current, so the context can be released.
332     ASSERT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
333 
334     // Create a new context and verify it cannot access the texture previously created
335     mContexts[0] = eglCreateContext(display, config, nullptr, inShareGroupContextAttribs);
336     ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0]));
337 
338     ASSERT_GL_FALSE(glIsTexture(textureFromCtx0));
339 }
340 
341 // Tests that deleting an object on one Context doesn't destroy it ahead-of-time. Mostly focused
342 // on the Vulkan back-end where we manage object lifetime manually.
TEST_P(EGLContextSharingTest,TextureLifetime)343 TEST_P(EGLContextSharingTest, TextureLifetime)
344 {
345     EGLWindow *eglWindow = getEGLWindow();
346     EGLConfig config     = getEGLWindow()->getConfig();
347     EGLDisplay display   = getEGLWindow()->getDisplay();
348 
349     // Create a pbuffer surface for use with a shared context.
350     EGLSurface surface     = eglWindow->getSurface();
351     EGLContext mainContext = eglWindow->getContext();
352 
353     // Initialize a shared context.
354     mContexts[0] = eglCreateContext(display, config, mainContext, nullptr);
355     ASSERT_NE(mContexts[0], EGL_NO_CONTEXT);
356 
357     // Create a Texture on the shared context.
358     ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0]));
359 
360     constexpr GLsizei kTexSize                  = 2;
361     const GLColor kTexData[kTexSize * kTexSize] = {GLColor::red, GLColor::green, GLColor::blue,
362                                                    GLColor::yellow};
363     GLTexture tex;
364     glBindTexture(GL_TEXTURE_2D, tex);
365     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
366                  kTexData);
367     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
368     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
369 
370     // Make the main Context current and draw with the texture.
371     ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mainContext));
372 
373     glBindTexture(GL_TEXTURE_2D, tex);
374     ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
375     glUseProgram(program);
376 
377     // No uniform update because the update seems to hide the error on Vulkan.
378 
379     // Enqueue the draw call.
380     drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
381     EXPECT_GL_NO_ERROR();
382 
383     // Delete the texture in the main context to orphan it.
384     // Do not read back the data to keep the commands in the graph.
385     tex.reset();
386 
387     // Bind and delete the test context. This should trigger texture garbage collection.
388     ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0]));
389     SafeDestroyContext(display, mContexts[0]);
390 
391     // Bind the main context to clean up the test.
392     ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mainContext));
393 }
394 
395 // Tests that deleting an object on one Context doesn't destroy it ahead-of-time. Mostly focused
396 // on the Vulkan back-end where we manage object lifetime manually.
TEST_P(EGLContextSharingTest,SamplerLifetime)397 TEST_P(EGLContextSharingTest, SamplerLifetime)
398 {
399     EGLWindow *eglWindow = getEGLWindow();
400     EGLConfig config     = getEGLWindow()->getConfig();
401     EGLDisplay display   = getEGLWindow()->getDisplay();
402 
403     ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
404     ANGLE_SKIP_TEST_IF(!IsEGLDisplayExtensionEnabled(display, "EGL_KHR_create_context"));
405 
406     // Create a pbuffer surface for use with a shared context.
407     EGLSurface surface     = eglWindow->getSurface();
408     EGLContext mainContext = eglWindow->getContext();
409 
410     std::vector<EGLint> contextAttributes;
411     contextAttributes.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR);
412     contextAttributes.push_back(getClientMajorVersion());
413     contextAttributes.push_back(EGL_NONE);
414 
415     // Initialize a shared context.
416     mContexts[0] = eglCreateContext(display, config, mainContext, contextAttributes.data());
417     ASSERT_NE(mContexts[0], EGL_NO_CONTEXT);
418 
419     // Create a Texture on the shared context. Also create a Sampler object.
420     ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0]));
421 
422     constexpr GLsizei kTexSize                  = 2;
423     const GLColor kTexData[kTexSize * kTexSize] = {GLColor::red, GLColor::green, GLColor::blue,
424                                                    GLColor::yellow};
425     GLTexture tex;
426     glBindTexture(GL_TEXTURE_2D, tex);
427     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
428                  kTexData);
429 
430     GLSampler sampler;
431     glBindSampler(0, sampler);
432     glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
433     glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
434 
435     // Make the main Context current and draw with the texture and sampler.
436     ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mainContext));
437 
438     glBindTexture(GL_TEXTURE_2D, tex);
439     glBindSampler(0, sampler);
440     ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
441     glUseProgram(program);
442 
443     // No uniform update because the update seems to hide the error on Vulkan.
444 
445     // Enqueue the draw call.
446     drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
447     EXPECT_GL_NO_ERROR();
448 
449     // Delete the texture and sampler in the main context to orphan them.
450     // Do not read back the data to keep the commands in the graph.
451     tex.reset();
452     sampler.reset();
453 
454     // Bind and delete the test context. This should trigger texture garbage collection.
455     ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mContexts[0]));
456     SafeDestroyContext(display, mContexts[0]);
457 
458     // Bind the main context to clean up the test.
459     ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mainContext));
460 }
461 
462 // Test that deleting an object reading from a shared object in one context doesn't cause the other
463 // context to crash.  Mostly focused on the Vulkan back-end where we track resource dependencies in
464 // a graph.
TEST_P(EGLContextSharingTest,DeleteReaderOfSharedTexture)465 TEST_P(EGLContextSharingTest, DeleteReaderOfSharedTexture)
466 {
467     ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
468     // GL Fences require GLES 3.0+
469     ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
470 
471     // Initialize contexts
472     EGLWindow *window = getEGLWindow();
473     EGLDisplay dpy    = window->getDisplay();
474     EGLConfig config  = window->getConfig();
475 
476     constexpr size_t kThreadCount    = 2;
477     EGLSurface surface[kThreadCount] = {EGL_NO_SURFACE, EGL_NO_SURFACE};
478     EGLContext ctx[kThreadCount]     = {EGL_NO_CONTEXT, EGL_NO_CONTEXT};
479 
480     EGLint pbufferAttributes[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, EGL_NONE};
481 
482     for (size_t t = 0; t < kThreadCount; ++t)
483     {
484         surface[t] = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
485         EXPECT_EGL_SUCCESS();
486 
487         ctx[t] = window->createContext(t == 0 ? EGL_NO_CONTEXT : ctx[0], nullptr);
488         EXPECT_NE(EGL_NO_CONTEXT, ctx[t]);
489     }
490 
491     // Initialize test resources.  They are done outside the threads to reduce the sources of
492     // errors and thus deadlock-free teardown.
493     ASSERT_EGL_TRUE(eglMakeCurrent(dpy, surface[1], surface[1], ctx[1]));
494 
495     // Shared texture to read from.
496     constexpr GLsizei kTexSize = 1;
497     const GLColor kTexData     = GLColor::red;
498 
499     GLTexture sharedTex;
500     glBindTexture(GL_TEXTURE_2D, sharedTex);
501     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
502                  &kTexData);
503     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
504     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
505 
506     // Resources for each context.
507     GLRenderbuffer renderbuffer[kThreadCount];
508     GLFramebuffer fbo[kThreadCount];
509     GLProgram program[kThreadCount];
510 
511     for (size_t t = 0; t < kThreadCount; ++t)
512     {
513         ASSERT_EGL_TRUE(eglMakeCurrent(dpy, surface[t], surface[t], ctx[t]));
514 
515         glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer[t]);
516         constexpr int kRenderbufferSize = 4;
517         glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kRenderbufferSize, kRenderbufferSize);
518 
519         glBindFramebuffer(GL_FRAMEBUFFER, fbo[t]);
520         glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
521                                   renderbuffer[t]);
522 
523         glBindTexture(GL_TEXTURE_2D, sharedTex);
524         program[t].makeRaster(essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
525         ASSERT_TRUE(program[t].valid());
526     }
527 
528     EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
529 
530     // Synchronization tools to ensure the two threads are interleaved as designed by this test.
531     std::mutex mutex;
532     std::condition_variable condVar;
533     std::atomic<GLsync> deletingThreadSyncObj;
534     std::atomic<GLsync> continuingThreadSyncObj;
535 
536     enum class Step
537     {
538         Start,
539         Thread0Draw,
540         Thread1Draw,
541         Thread0Delete,
542         Finish,
543         Abort,
544     };
545     Step currentStep = Step::Start;
546 
547     std::thread deletingThread = std::thread([&]() {
548         ThreadSynchronization<Step> threadSynchronization(&currentStep, &mutex, &condVar);
549 
550         EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[0], surface[0], ctx[0]));
551         EXPECT_EGL_SUCCESS();
552 
553         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start));
554 
555         // Draw using the shared texture.
556         drawQuad(program[0].get(), essl1_shaders::PositionAttrib(), 0.5f);
557 
558         deletingThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
559         ASSERT_GL_NO_ERROR();
560         // Force the fence to be created
561         glFlush();
562 
563         // Wait for the other thread to also draw using the shared texture.
564         threadSynchronization.nextStep(Step::Thread0Draw);
565         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Draw));
566 
567         ASSERT_TRUE(continuingThreadSyncObj != nullptr);
568         glWaitSync(continuingThreadSyncObj, 0, GL_TIMEOUT_IGNORED);
569         ASSERT_GL_NO_ERROR();
570         glDeleteSync(continuingThreadSyncObj);
571         ASSERT_GL_NO_ERROR();
572         continuingThreadSyncObj = nullptr;
573 
574         // Delete this thread's framebuffer (reader of the shared texture).
575         fbo[0].reset();
576 
577         // Wait for the other thread to use the shared texture again before unbinding the
578         // context (so no implicit flush happens).
579         threadSynchronization.nextStep(Step::Thread0Delete);
580         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
581 
582         EXPECT_GL_NO_ERROR();
583         EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
584         EXPECT_EGL_SUCCESS();
585     });
586 
587     std::thread continuingThread = std::thread([&]() {
588         ThreadSynchronization<Step> threadSynchronization(&currentStep, &mutex, &condVar);
589 
590         EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[1], surface[1], ctx[1]));
591         EXPECT_EGL_SUCCESS();
592 
593         // Wait for first thread to draw using the shared texture.
594         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Draw));
595 
596         ASSERT_TRUE(deletingThreadSyncObj != nullptr);
597         glWaitSync(deletingThreadSyncObj, 0, GL_TIMEOUT_IGNORED);
598         ASSERT_GL_NO_ERROR();
599         glDeleteSync(deletingThreadSyncObj);
600         ASSERT_GL_NO_ERROR();
601         deletingThreadSyncObj = nullptr;
602 
603         // Draw using the shared texture.
604         drawQuad(program[0].get(), essl1_shaders::PositionAttrib(), 0.5f);
605 
606         continuingThreadSyncObj = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
607         ASSERT_GL_NO_ERROR();
608         // Force the fence to be created
609         glFlush();
610 
611         // Wait for the other thread to delete its framebuffer.
612         threadSynchronization.nextStep(Step::Thread1Draw);
613         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Delete));
614 
615         // Write to the shared texture differently, so a dependency is created from the previous
616         // readers of the shared texture (the two framebuffers of the two threads) to the new
617         // commands being recorded for the shared texture.
618         //
619         // If the backend attempts to create a dependency from nodes associated with the
620         // previous readers of the texture to the new node that will contain the following
621         // commands, there will be a use-after-free error.
622         const GLColor kTexData2 = GLColor::green;
623         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
624                      &kTexData2);
625         drawQuad(program[0].get(), essl1_shaders::PositionAttrib(), 0.5f);
626 
627         threadSynchronization.nextStep(Step::Finish);
628 
629         EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
630         EXPECT_EGL_SUCCESS();
631     });
632 
633     deletingThread.join();
634     continuingThread.join();
635 
636     ASSERT_NE(currentStep, Step::Abort);
637 
638     // Clean up
639     for (size_t t = 0; t < kThreadCount; ++t)
640     {
641         eglDestroySurface(dpy, surface[t]);
642         eglDestroyContext(dpy, ctx[t]);
643     }
644 }
645 
646 // Test that eglTerminate() with a thread doesn't cause other threads to crash.
TEST_P(EGLContextSharingTestNoFixture,EglTerminateMultiThreaded)647 TEST_P(EGLContextSharingTestNoFixture, EglTerminateMultiThreaded)
648 {
649     // http://anglebug.com/6208
650     // The following EGL calls led to a crash in eglMakeCurrent():
651     //
652     // Thread A: eglMakeCurrent(context A)
653     // Thread B: eglDestroyContext(context A)
654     //        B: eglTerminate() <<--- this release context A
655     // Thread A: eglMakeCurrent(context B)
656 
657     EGLint dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), EGL_NONE};
658     mDisplay           = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,
659                                         reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs);
660     EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY);
661     EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr));
662 
663     EGLConfig config = EGL_NO_CONFIG_KHR;
664     EXPECT_TRUE(chooseConfig(&config));
665 
666     mOsWindow->initialize("EGLContextSharingTestNoFixture", kWidth, kHeight);
667     EXPECT_TRUE(createWindowSurface(config, mOsWindow->getNativeWindow(), &mSurface));
668     ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";
669 
670     EXPECT_TRUE(createContext(config, &mContexts[0]));
671     EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[0]));
672 
673     // Must be after the eglMakeCurrent() so renderer string is initialized.
674     ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
675     // TODO(http://www.anglebug.com/6304): Fails with OpenGL ES backend.
676     ANGLE_SKIP_TEST_IF(IsOpenGLES());
677 
678     // Synchronization tools to ensure the two threads are interleaved as designed by this test.
679     std::mutex mutex;
680     std::condition_variable condVar;
681 
682     enum class Step
683     {
684         Start,
685         Thread0Clear,
686         Thread1Terminate,
687         Thread0MakeCurrentContext1,
688         Finish,
689         Abort,
690     };
691     Step currentStep = Step::Start;
692 
693     std::thread thread0 = std::thread([&]() {
694         ThreadSynchronization<Step> threadSynchronization(&currentStep, &mutex, &condVar);
695 
696         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start));
697 
698         EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[0]));
699 
700         // Clear and read back to make sure thread 0 uses context 0.
701         glClearColor(1.0, 0.0, 0.0, 1.0);
702         glClear(GL_COLOR_BUFFER_BIT);
703         EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255);
704 
705         // Wait for thread 1 to clear.
706         threadSynchronization.nextStep(Step::Thread0Clear);
707         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Terminate));
708 
709         // First Display was terminated, so we need to create a new one to create a new Context.
710         mDisplay = eglGetPlatformDisplayEXT(
711             EGL_PLATFORM_ANGLE_ANGLE, reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs);
712         EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY);
713         EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr));
714         config = EGL_NO_CONFIG_KHR;
715         EXPECT_TRUE(chooseConfig(&config));
716         EXPECT_TRUE(createContext(config, &mContexts[1]));
717         EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[1]));
718 
719         // Clear and read back to make sure thread 0 uses context 1.
720         glClearColor(1.0, 1.0, 0.0, 1.0);
721         glClear(GL_COLOR_BUFFER_BIT);
722         EXPECT_PIXEL_EQ(0, 0, 255, 255, 0, 255);
723 
724         // Cleanup
725         EXPECT_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
726         EXPECT_TRUE(SafeDestroyContext(mDisplay, mContexts[1]));
727         eglDestroySurface(mDisplay, mSurface);
728         mSurface = EGL_NO_SURFACE;
729         eglTerminate(mDisplay);
730         mDisplay = EGL_NO_DISPLAY;
731         EXPECT_EGL_SUCCESS();
732 
733         threadSynchronization.nextStep(Step::Thread0MakeCurrentContext1);
734         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
735     });
736 
737     std::thread thread1 = std::thread([&]() {
738         ThreadSynchronization<Step> threadSynchronization(&currentStep, &mutex, &condVar);
739 
740         // Wait for thread 0 to clear.
741         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Clear));
742 
743         // Destroy context 0 while thread1 has it current.
744         EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
745         EXPECT_TRUE(SafeDestroyContext(mDisplay, mContexts[0]));
746         EXPECT_EGL_SUCCESS();
747         eglTerminate(mDisplay);
748         mDisplay = EGL_NO_DISPLAY;
749         EXPECT_EGL_SUCCESS();
750 
751         // Wait for the thread 0 to make a new context and glClear().
752         threadSynchronization.nextStep(Step::Thread1Terminate);
753         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0MakeCurrentContext1));
754 
755         threadSynchronization.nextStep(Step::Finish);
756     });
757 
758     thread0.join();
759     thread1.join();
760 
761     ASSERT_NE(currentStep, Step::Abort);
762 }
763 
764 // Test that eglDestoryContext() can be called multiple times on the same Context without causing
765 // errors.
TEST_P(EGLContextSharingTestNoFixture,EglDestoryContextManyTimesSameContext)766 TEST_P(EGLContextSharingTestNoFixture, EglDestoryContextManyTimesSameContext)
767 {
768     EGLint dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), EGL_NONE};
769     mDisplay           = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,
770                                         reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs);
771     EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY);
772     EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr));
773 
774     EGLConfig config = EGL_NO_CONFIG_KHR;
775     EXPECT_TRUE(chooseConfig(&config));
776 
777     mOsWindow->initialize("EGLContextSharingTestNoFixture", kWidth, kHeight);
778     EXPECT_TRUE(createWindowSurface(config, mOsWindow->getNativeWindow(), &mSurface));
779     ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";
780 
781     EXPECT_TRUE(createContext(config, &mContexts[0]));
782     EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[0]));
783 
784     // Must be after the eglMakeCurrent() so renderer string is initialized.
785     ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
786     // TODO(http://www.anglebug.com/6304): Fails with OpenGL ES backend.
787     ANGLE_SKIP_TEST_IF(IsOpenGLES());
788 
789     // Synchronization tools to ensure the two threads are interleaved as designed by this test.
790     std::mutex mutex;
791     std::condition_variable condVar;
792 
793     enum class Step
794     {
795         Start,
796         Thread0Clear,
797         Thread1Terminate,
798         Thread0MakeCurrentContext1,
799         Finish,
800         Abort,
801     };
802     Step currentStep = Step::Start;
803 
804     std::thread thread0 = std::thread([&]() {
805         ThreadSynchronization<Step> threadSynchronization(&currentStep, &mutex, &condVar);
806 
807         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Start));
808 
809         EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[0]));
810 
811         // Clear and read back to make sure thread 0 uses context 0.
812         glClearColor(1.0, 0.0, 0.0, 1.0);
813         glClear(GL_COLOR_BUFFER_BIT);
814         EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255);
815 
816         // Wait for thread 1 to clear.
817         threadSynchronization.nextStep(Step::Thread0Clear);
818         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Terminate));
819 
820         // First Display was terminated, so we need to create a new one to create a new Context.
821         mDisplay = eglGetPlatformDisplayEXT(
822             EGL_PLATFORM_ANGLE_ANGLE, reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs);
823         EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY);
824         EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr));
825         config = EGL_NO_CONFIG_KHR;
826         EXPECT_TRUE(chooseConfig(&config));
827         EXPECT_TRUE(createContext(config, &mContexts[1]));
828         EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[1]));
829 
830         // Clear and read back to make sure thread 0 uses context 1.
831         glClearColor(1.0, 1.0, 0.0, 1.0);
832         glClear(GL_COLOR_BUFFER_BIT);
833         EXPECT_PIXEL_EQ(0, 0, 255, 255, 0, 255);
834 
835         // Cleanup
836         EXPECT_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
837         EXPECT_TRUE(SafeDestroyContext(mDisplay, mContexts[1]));
838         eglDestroySurface(mDisplay, mSurface);
839         mSurface = EGL_NO_SURFACE;
840         eglTerminate(mDisplay);
841         mDisplay = EGL_NO_DISPLAY;
842         EXPECT_EGL_SUCCESS();
843 
844         threadSynchronization.nextStep(Step::Thread0MakeCurrentContext1);
845         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
846     });
847 
848     std::thread thread1 = std::thread([&]() {
849         ThreadSynchronization<Step> threadSynchronization(&currentStep, &mutex, &condVar);
850 
851         // Wait for thread 0 to clear.
852         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Clear));
853 
854         EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
855 
856         // Destroy context 0 5 times while thread1 has it current.
857         EXPECT_EGL_TRUE(eglDestroyContext(mDisplay, mContexts[0]));
858         EXPECT_EGL_TRUE(eglDestroyContext(mDisplay, mContexts[0]));
859         EXPECT_EGL_TRUE(eglDestroyContext(mDisplay, mContexts[0]));
860         EXPECT_EGL_TRUE(eglDestroyContext(mDisplay, mContexts[0]));
861         EXPECT_EGL_TRUE(eglDestroyContext(mDisplay, mContexts[0]));
862         mContexts[0] = EGL_NO_CONTEXT;
863 
864         eglTerminate(mDisplay);
865         mDisplay = EGL_NO_DISPLAY;
866         EXPECT_EGL_SUCCESS();
867 
868         // Wait for the thread 0 to make a new context and glClear().
869         threadSynchronization.nextStep(Step::Thread1Terminate);
870         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0MakeCurrentContext1));
871 
872         threadSynchronization.nextStep(Step::Finish);
873     });
874 
875     thread0.join();
876     thread1.join();
877 
878     ASSERT_NE(currentStep, Step::Abort);
879 }
880 
881 // Test that eglTerminate() can be called multiple times on the same Display while Contexts are
882 // still current without causing errors.
TEST_P(EGLContextSharingTestNoFixture,EglTerminateMultipleTimes)883 TEST_P(EGLContextSharingTestNoFixture, EglTerminateMultipleTimes)
884 {
885     // https://bugs.chromium.org/p/skia/issues/detail?id=12413#c4
886     // The following sequence caused a crash with the D3D backend in the Skia infra:
887     //   eglDestroyContext(ctx0)
888     //   eglDestroySurface(srf0)
889     //   eglTerminate(shared-display)
890     //   eglDestroyContext(ctx1) // completes the cleanup from the above terminate
891     //   eglDestroySurface(srf1)
892     //   eglTerminate(shared-display)
893 
894     EGLint dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), EGL_NONE};
895     mDisplay           = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE,
896                                         reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs);
897     EXPECT_TRUE(mDisplay != EGL_NO_DISPLAY);
898     EXPECT_EGL_TRUE(eglInitialize(mDisplay, nullptr, nullptr));
899 
900     EGLConfig config = EGL_NO_CONFIG_KHR;
901     EXPECT_TRUE(chooseConfig(&config));
902 
903     mOsWindow->initialize("EGLContextSharingTestNoFixture", kWidth, kHeight);
904     EXPECT_TRUE(createWindowSurface(config, mOsWindow->getNativeWindow(), &mSurface));
905     EXPECT_TRUE(mSurface != EGL_NO_SURFACE);
906     ASSERT_EGL_SUCCESS() << "eglCreateWindowSurface failed.";
907 
908     EXPECT_TRUE(createContext(config, &mContexts[0]));
909     EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[0]));
910     EXPECT_TRUE(createContext(config, &mContexts[1]));
911     EXPECT_EGL_TRUE(eglMakeCurrent(mDisplay, mSurface, mSurface, mContexts[1]));
912 
913     // Must be after the eglMakeCurrent() so renderer string is initialized.
914     // TODO(http://www.anglebug.com/6304): Fails with Mac + OpenGL backend.
915     ANGLE_SKIP_TEST_IF(IsOSX() && IsOpenGL());
916 
917     EXPECT_TRUE(eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
918 
919     eglDestroySurface(mDisplay, mSurface);
920     mSurface = EGL_NO_SURFACE;
921     EXPECT_EGL_TRUE(eglDestroyContext(mDisplay, mContexts[0]));
922     mContexts[0] = EGL_NO_CONTEXT;
923     eglTerminate(mDisplay);
924     EXPECT_EGL_SUCCESS();
925 
926     eglDestroyContext(mDisplay, mContexts[1]);
927     mContexts[1] = EGL_NO_CONTEXT;
928     ASSERT_EGL_ERROR(EGL_NOT_INITIALIZED);
929     eglTerminate(mDisplay);
930     EXPECT_EGL_SUCCESS();
931     mDisplay = EGL_NO_DISPLAY;
932 }
933 }  // anonymous namespace
934 
935 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLContextSharingTest);
936 ANGLE_INSTANTIATE_TEST(EGLContextSharingTest,
937                        ES2_D3D9(),
938                        ES2_D3D11(),
939                        ES3_D3D11(),
940                        ES2_METAL(),
941                        ES2_OPENGL(),
942                        ES3_OPENGL(),
943                        ES2_VULKAN(),
944                        ES3_VULKAN());
945 
946 ANGLE_INSTANTIATE_TEST(EGLContextSharingTestNoFixture,
947                        WithNoFixture(ES2_OPENGLES()),
948                        WithNoFixture(ES3_OPENGLES()),
949                        WithNoFixture(ES2_OPENGL()),
950                        WithNoFixture(ES3_OPENGL()),
951                        WithNoFixture(ES2_VULKAN()),
952                        WithNoFixture(ES3_VULKAN()));
953