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 "EGLMultiThreadSteps.h"
12 #include "test_utils/ANGLETest.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
56 EGLContext mContexts[2];
57 GLuint mTexture;
58 };
59
60 // Test that calling eglDeleteContext on a context that is not current succeeds.
TEST_P(EGLMultiContextTest,TestContextDestroySimple)61 TEST_P(EGLMultiContextTest, TestContextDestroySimple)
62 {
63 EGLWindow *window = getEGLWindow();
64 EGLDisplay dpy = window->getDisplay();
65
66 EGLContext context1 = window->createContext(EGL_NO_CONTEXT);
67 EGLContext context2 = window->createContext(EGL_NO_CONTEXT);
68
69 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, context1));
70 EXPECT_EGL_TRUE(eglDestroyContext(dpy, context2));
71 EXPECT_EGL_SUCCESS();
72 }
73
74 // Test that a compute shader running in one thread will still work when rendering is happening in
75 // another thread (with non-shared contexts). The non-shared context will still share a Vulkan
76 // command buffer.
TEST_P(EGLMultiContextTest,ComputeShaderOkayWithRendering)77 TEST_P(EGLMultiContextTest, ComputeShaderOkayWithRendering)
78 {
79 ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
80 ANGLE_SKIP_TEST_IF(!isVulkanRenderer());
81 ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 || getClientMinorVersion() < 1);
82
83 // Initialize contexts
84 EGLWindow *window = getEGLWindow();
85 EGLDisplay dpy = window->getDisplay();
86 EGLConfig config = window->getConfig();
87
88 constexpr size_t kThreadCount = 2;
89 EGLSurface surface[kThreadCount] = {EGL_NO_SURFACE, EGL_NO_SURFACE};
90 EGLContext ctx[kThreadCount] = {EGL_NO_CONTEXT, EGL_NO_CONTEXT};
91
92 EGLint pbufferAttributes[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, EGL_NONE};
93
94 for (size_t t = 0; t < kThreadCount; ++t)
95 {
96 surface[t] = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
97 EXPECT_EGL_SUCCESS();
98
99 ctx[t] = window->createContext(EGL_NO_CONTEXT);
100 EXPECT_NE(EGL_NO_CONTEXT, ctx[t]);
101 }
102
103 // Synchronization tools to ensure the two threads are interleaved as designed by this test.
104 std::mutex mutex;
105 std::condition_variable condVar;
106
107 enum class Step
108 {
109 Thread0Start,
110 Thread0DispatchedCompute,
111 Thread1Drew,
112 Thread0DispatchedComputeAgain,
113 Finish,
114 Abort,
115 };
116 Step currentStep = Step::Thread0Start;
117
118 // This first thread dispatches a compute shader. It immediately starts.
119 std::thread deletingThread = std::thread([&]() {
120 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
121
122 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[0], surface[0], ctx[0]));
123 EXPECT_EGL_SUCCESS();
124
125 // Potentially wait to be signalled to start.
126 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Start));
127
128 // Wake up and do next step: Create, detach, and dispatch a compute shader program.
129 constexpr char kCS[] = R"(#version 310 es
130 layout(local_size_x=1) in;
131 void main()
132 {
133 })";
134 GLuint computeProgram = glCreateProgram();
135 GLuint cs = CompileShader(GL_COMPUTE_SHADER, kCS);
136 EXPECT_NE(0u, cs);
137
138 glAttachShader(computeProgram, cs);
139 glDeleteShader(cs);
140 glLinkProgram(computeProgram);
141 GLint linkStatus;
142 glGetProgramiv(computeProgram, GL_LINK_STATUS, &linkStatus);
143 EXPECT_GL_TRUE(linkStatus);
144 glDetachShader(computeProgram, cs);
145 EXPECT_GL_NO_ERROR();
146 glUseProgram(computeProgram);
147
148 glDispatchCompute(8, 4, 2);
149 EXPECT_GL_NO_ERROR();
150
151 // Signal the second thread and wait for it to draw and flush.
152 threadSynchronization.nextStep(Step::Thread0DispatchedCompute);
153 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1Drew));
154
155 // Wake up and do next step: Dispatch the same compute shader again.
156 glDispatchCompute(8, 4, 2);
157
158 // Signal the second thread and wait for it to draw and flush again.
159 threadSynchronization.nextStep(Step::Thread0DispatchedComputeAgain);
160 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
161
162 // Wake up and do next step: Dispatch the same compute shader again, and force flush the
163 // underlying command buffer.
164 glDispatchCompute(8, 4, 2);
165 glFinish();
166
167 // Clean-up and exit this thread.
168 EXPECT_GL_NO_ERROR();
169 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
170 EXPECT_EGL_SUCCESS();
171 });
172
173 // This second thread renders. It starts once the other thread does its first nextStep()
174 std::thread continuingThread = std::thread([&]() {
175 ThreadSynchronization<Step> threadSynchronization(¤tStep, &mutex, &condVar);
176
177 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[1], surface[1], ctx[1]));
178 EXPECT_EGL_SUCCESS();
179
180 // Wait for first thread to create and dispatch a compute shader.
181 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0DispatchedCompute));
182
183 // Wake up and do next step: Create graphics resources, draw, and force flush the
184 // underlying command buffer.
185 GLTexture texture;
186 glBindTexture(GL_TEXTURE_2D, texture);
187 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
188 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
189 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
190
191 GLRenderbuffer renderbuffer;
192 GLFramebuffer fbo;
193 glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
194 constexpr int kRenderbufferSize = 4;
195 glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kRenderbufferSize, kRenderbufferSize);
196 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
197 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
198 renderbuffer);
199 glBindTexture(GL_TEXTURE_2D, texture);
200
201 GLProgram graphicsProgram;
202 graphicsProgram.makeRaster(essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
203 ASSERT_TRUE(graphicsProgram.valid());
204
205 drawQuad(graphicsProgram.get(), essl1_shaders::PositionAttrib(), 0.5f);
206 glFinish();
207
208 // Signal the first thread and wait for it to dispatch a compute shader again.
209 threadSynchronization.nextStep(Step::Thread1Drew);
210 ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0DispatchedComputeAgain));
211
212 // Wake up and do next step: Draw and force flush the underlying command buffer again.
213 drawQuad(graphicsProgram.get(), essl1_shaders::PositionAttrib(), 0.5f);
214 glFinish();
215
216 // Signal the first thread and wait exit this thread.
217 threadSynchronization.nextStep(Step::Finish);
218
219 EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
220 EXPECT_EGL_SUCCESS();
221 });
222
223 deletingThread.join();
224 continuingThread.join();
225
226 ASSERT_NE(currentStep, Step::Abort);
227
228 // Clean up
229 for (size_t t = 0; t < kThreadCount; ++t)
230 {
231 eglDestroySurface(dpy, surface[t]);
232 eglDestroyContext(dpy, ctx[t]);
233 }
234 }
235 } // anonymous namespace
236
237 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLMultiContextTest);
238 ANGLE_INSTANTIATE_TEST_ES31(EGLMultiContextTest);
239