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(¤tStep, &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(¤tStep, &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