• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright 2021 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 // EGLMultiContextTest.cpp:
7 //   Tests relating to multiple non-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 
17 using namespace angle;
18 
19 namespace
20 {
21 
SafeDestroyContext(EGLDisplay display,EGLContext & context)22 EGLBoolean SafeDestroyContext(EGLDisplay display, EGLContext &context)
23 {
24     EGLBoolean result = EGL_TRUE;
25     if (context != EGL_NO_CONTEXT)
26     {
27         result  = eglDestroyContext(display, context);
28         context = EGL_NO_CONTEXT;
29     }
30     return result;
31 }
32 
33 class EGLMultiContextTest : public ANGLETest
34 {
35   public:
EGLMultiContextTest()36     EGLMultiContextTest() : mContexts{EGL_NO_CONTEXT, EGL_NO_CONTEXT}, mTexture(0) {}
37 
testTearDown()38     void testTearDown() override
39     {
40         glDeleteTextures(1, &mTexture);
41 
42         EGLDisplay display = getEGLWindow()->getDisplay();
43 
44         if (display != EGL_NO_DISPLAY)
45         {
46             for (auto &context : mContexts)
47             {
48                 SafeDestroyContext(display, context);
49             }
50         }
51 
52         // Set default test state to not give an error on shutdown.
53         getEGLWindow()->makeCurrent();
54     }
55 
chooseConfig(EGLDisplay dpy,EGLConfig * config) const56     bool chooseConfig(EGLDisplay dpy, EGLConfig *config) const
57     {
58         bool result          = false;
59         EGLint count         = 0;
60         EGLint clientVersion = EGL_OPENGL_ES3_BIT;
61         EGLint attribs[]     = {EGL_RED_SIZE,
62                             8,
63                             EGL_GREEN_SIZE,
64                             8,
65                             EGL_BLUE_SIZE,
66                             8,
67                             EGL_ALPHA_SIZE,
68                             8,
69                             EGL_RENDERABLE_TYPE,
70                             clientVersion,
71                             EGL_SURFACE_TYPE,
72                             EGL_WINDOW_BIT,
73                             EGL_NONE};
74 
75         result = eglChooseConfig(dpy, attribs, config, 1, &count);
76         EXPECT_EGL_TRUE(result && (count > 0));
77         return result;
78     }
79 
createContext(EGLDisplay dpy,EGLConfig config,EGLContext * context)80     bool createContext(EGLDisplay dpy, EGLConfig config, EGLContext *context)
81     {
82         bool result      = false;
83         EGLint attribs[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE};
84 
85         *context = eglCreateContext(dpy, config, nullptr, attribs);
86         result   = (*context != EGL_NO_CONTEXT);
87         EXPECT_TRUE(result);
88         return result;
89     }
90 
createPbufferSurface(EGLDisplay dpy,EGLConfig config,EGLint width,EGLint height,EGLSurface * surface)91     bool createPbufferSurface(EGLDisplay dpy,
92                               EGLConfig config,
93                               EGLint width,
94                               EGLint height,
95                               EGLSurface *surface)
96     {
97         bool result      = false;
98         EGLint attribs[] = {EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE};
99 
100         *surface = eglCreatePbufferSurface(dpy, config, attribs);
101         result   = (*surface != EGL_NO_SURFACE);
102         EXPECT_TRUE(result);
103         return result;
104     }
105 
106     EGLContext mContexts[2];
107     GLuint mTexture;
108 };
109 
110 // Test that calling eglDeleteContext on a context that is not current succeeds.
TEST_P(EGLMultiContextTest,TestContextDestroySimple)111 TEST_P(EGLMultiContextTest, TestContextDestroySimple)
112 {
113     EGLWindow *window = getEGLWindow();
114     EGLDisplay dpy    = window->getDisplay();
115 
116     EGLContext context1 = window->createContext(EGL_NO_CONTEXT, nullptr);
117     EGLContext context2 = window->createContext(EGL_NO_CONTEXT, nullptr);
118 
119     EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, context1));
120     EXPECT_EGL_TRUE(eglDestroyContext(dpy, context2));
121     EXPECT_EGL_SUCCESS();
122 
123     // Cleanup
124     EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
125     EXPECT_EGL_TRUE(eglDestroyContext(dpy, context1));
126     EXPECT_EGL_SUCCESS();
127 }
128 
129 // Test that a compute shader running in one thread will still work when rendering is happening in
130 // another thread (with non-shared contexts).  The non-shared context will still share a Vulkan
131 // command buffer.
TEST_P(EGLMultiContextTest,ComputeShaderOkayWithRendering)132 TEST_P(EGLMultiContextTest, ComputeShaderOkayWithRendering)
133 {
134     ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
135     ANGLE_SKIP_TEST_IF(!isVulkanRenderer());
136     ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 || getClientMinorVersion() < 1);
137 
138     // Initialize contexts
139     EGLWindow *window = getEGLWindow();
140     EGLDisplay dpy    = window->getDisplay();
141     EGLConfig config  = window->getConfig();
142 
143     constexpr size_t kThreadCount    = 2;
144     EGLSurface surface[kThreadCount] = {EGL_NO_SURFACE, EGL_NO_SURFACE};
145     EGLContext ctx[kThreadCount]     = {EGL_NO_CONTEXT, EGL_NO_CONTEXT};
146 
147     EGLint pbufferAttributes[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, EGL_NONE};
148 
149     for (size_t t = 0; t < kThreadCount; ++t)
150     {
151         surface[t] = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
152         EXPECT_EGL_SUCCESS();
153 
154         ctx[t] = window->createContext(EGL_NO_CONTEXT, nullptr);
155         EXPECT_NE(EGL_NO_CONTEXT, ctx[t]);
156     }
157 
158     // Synchronization tools to ensure the two threads are interleaved as designed by this test.
159     std::mutex mutex;
160     std::condition_variable condVar;
161 
162     enum class Step
163     {
164         Thread0Start,
165         Thread0DispatchedCompute,
166         Thread1Drew,
167         Thread0DispatchedComputeAgain,
168         Finish,
169         Abort,
170     };
171     Step currentStep = Step::Thread0Start;
172 
173     // This first thread dispatches a compute shader.  It immediately starts.
174     std::thread deletingThread = std::thread([&]() {
175         ThreadSynchronization<Step> threadSynchronization(&currentStep, &mutex, &condVar);
176 
177         EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[0], surface[0], ctx[0]));
178         EXPECT_EGL_SUCCESS();
179 
180         // Potentially wait to be signalled to start.
181         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Start));
182 
183         // Wake up and do next step: Create, detach, and dispatch a compute shader program.
184         constexpr char kCS[]  = R"(#version 310 es
185 layout(local_size_x=1) in;
186 void main()
187 {
188 })";
189         GLuint computeProgram = glCreateProgram();
190         GLuint cs             = CompileShader(GL_COMPUTE_SHADER, kCS);
191         EXPECT_NE(0u, cs);
192 
193         glAttachShader(computeProgram, cs);
194         glDeleteShader(cs);
195         glLinkProgram(computeProgram);
196         GLint linkStatus;
197         glGetProgramiv(computeProgram, GL_LINK_STATUS, &linkStatus);
198         EXPECT_GL_TRUE(linkStatus);
199         glDetachShader(computeProgram, cs);
200         EXPECT_GL_NO_ERROR();
201         glUseProgram(computeProgram);
202 
203         glDispatchCompute(8, 4, 2);
204         EXPECT_GL_NO_ERROR();
205 
206         // Signal the second thread and wait for it to draw and flush.
207         threadSynchronization.nextStep(Step::Thread0DispatchedCompute);
208         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Drew));
209 
210         // Wake up and do next step: Dispatch the same compute shader again.
211         glDispatchCompute(8, 4, 2);
212 
213         // Signal the second thread and wait for it to draw and flush again.
214         threadSynchronization.nextStep(Step::Thread0DispatchedComputeAgain);
215         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
216 
217         // Wake up and do next step: Dispatch the same compute shader again, and force flush the
218         // underlying command buffer.
219         glDispatchCompute(8, 4, 2);
220         glFinish();
221 
222         // Clean-up and exit this thread.
223         EXPECT_GL_NO_ERROR();
224         EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
225         EXPECT_EGL_SUCCESS();
226     });
227 
228     // This second thread renders.  It starts once the other thread does its first nextStep()
229     std::thread continuingThread = std::thread([&]() {
230         ThreadSynchronization<Step> threadSynchronization(&currentStep, &mutex, &condVar);
231 
232         EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[1], surface[1], ctx[1]));
233         EXPECT_EGL_SUCCESS();
234 
235         // Wait for first thread to create and dispatch a compute shader.
236         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0DispatchedCompute));
237 
238         // Wake up and do next step: Create graphics resources, draw, and force flush the
239         // underlying command buffer.
240         GLTexture texture;
241         glBindTexture(GL_TEXTURE_2D, texture);
242         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
243         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
244         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
245 
246         GLRenderbuffer renderbuffer;
247         GLFramebuffer fbo;
248         glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
249         constexpr int kRenderbufferSize = 4;
250         glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kRenderbufferSize, kRenderbufferSize);
251         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
252         glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
253                                   renderbuffer);
254         glBindTexture(GL_TEXTURE_2D, texture);
255 
256         GLProgram graphicsProgram;
257         graphicsProgram.makeRaster(essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
258         ASSERT_TRUE(graphicsProgram.valid());
259 
260         drawQuad(graphicsProgram.get(), essl1_shaders::PositionAttrib(), 0.5f);
261         glFinish();
262 
263         // Signal the first thread and wait for it to dispatch a compute shader again.
264         threadSynchronization.nextStep(Step::Thread1Drew);
265         ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0DispatchedComputeAgain));
266 
267         // Wake up and do next step: Draw and force flush the underlying command buffer again.
268         drawQuad(graphicsProgram.get(), essl1_shaders::PositionAttrib(), 0.5f);
269         glFinish();
270 
271         // Signal the first thread and wait exit this thread.
272         threadSynchronization.nextStep(Step::Finish);
273 
274         EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
275         EXPECT_EGL_SUCCESS();
276     });
277 
278     deletingThread.join();
279     continuingThread.join();
280 
281     ASSERT_NE(currentStep, Step::Abort);
282 
283     // Clean up
284     EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
285     for (size_t t = 0; t < kThreadCount; ++t)
286     {
287         eglDestroySurface(dpy, surface[t]);
288         eglDestroyContext(dpy, ctx[t]);
289     }
290 }
291 
292 // Test that repeated EGL init + terminate with improper cleanup doesn't cause an OOM crash.
293 // To reproduce the memleak issue changes need to be made to "EGLWindow::destroyGL" as shown here ->
294 // https://chromium-review.googlesource.com/c/angle/angle/+/3294581/5/util/EGLWindow.cpp
TEST_P(EGLMultiContextTest,RepeatedEglInitAndTerminate)295 TEST_P(EGLMultiContextTest, RepeatedEglInitAndTerminate)
296 {
297     ANGLE_SKIP_TEST_IF(!IsAndroid() || !IsVulkan());
298 
299     EGLDisplay dpy;
300     EGLSurface srf;
301     EGLContext ctx;
302     EGLint dispattrs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, GetParam().getRenderer(), EGL_NONE};
303 
304     for (int i = 0; i < 100; i++)
305     {
306         std::thread thread = std::thread([&]() {
307             dpy = eglGetPlatformDisplayEXT(
308                 EGL_PLATFORM_ANGLE_ANGLE, reinterpret_cast<void *>(EGL_DEFAULT_DISPLAY), dispattrs);
309             EXPECT_TRUE(dpy != EGL_NO_DISPLAY);
310             EXPECT_EGL_TRUE(eglInitialize(dpy, nullptr, nullptr));
311 
312             EGLConfig config = EGL_NO_CONFIG_KHR;
313             EXPECT_TRUE(chooseConfig(dpy, &config));
314 
315             EXPECT_TRUE(createPbufferSurface(dpy, config, 2560, 1080, &srf));
316             ASSERT_EGL_SUCCESS() << "eglCreatePbufferSurface failed.";
317 
318             EXPECT_TRUE(createContext(dpy, config, &ctx));
319             EXPECT_EGL_TRUE(eglMakeCurrent(dpy, srf, srf, ctx));
320 
321             // Clear and read back to make sure thread uses context.
322             glClearColor(1.0, 0.0, 0.0, 1.0);
323             glClear(GL_COLOR_BUFFER_BIT);
324             EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255);
325 
326             eglTerminate(dpy);
327             EXPECT_EGL_SUCCESS();
328             dpy = EGL_NO_DISPLAY;
329             srf = EGL_NO_SURFACE;
330             ctx = EGL_NO_CONTEXT;
331         });
332 
333         thread.join();
334     }
335 }
336 }  // anonymous namespace
337 
338 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLMultiContextTest);
339 ANGLE_INSTANTIATE_TEST_ES31(EGLMultiContextTest);
340