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
7 // ParallelShaderCompileTest.cpp : Tests of the GL_KHR_parallel_shader_compile extension.
8
9 #include "test_utils/ANGLETest.h"
10 #include "test_utils/gl_raii.h"
11
12 #include "util/random_utils.h"
13 #include "util/test_utils.h"
14
15 using namespace angle;
16
17 namespace
18 {
19
20 namespace
21 {
22
23 constexpr int kTaskCount = 32;
24 constexpr unsigned int kPollInterval = 100;
25
26 } // anonymous namespace
27
28 class ParallelShaderCompileTest : public ANGLETest<>
29 {
30 protected:
ParallelShaderCompileTest()31 ParallelShaderCompileTest()
32 {
33 setWindowWidth(128);
34 setWindowHeight(128);
35 setConfigRedBits(8);
36 setConfigGreenBits(8);
37 setConfigBlueBits(8);
38 setConfigAlphaBits(8);
39 }
40
ensureParallelShaderCompileExtensionAvailable()41 bool ensureParallelShaderCompileExtensionAvailable()
42 {
43 if (IsGLExtensionRequestable("GL_KHR_parallel_shader_compile"))
44 {
45 glRequestExtensionANGLE("GL_KHR_parallel_shader_compile");
46 }
47
48 if (!IsGLExtensionEnabled("GL_KHR_parallel_shader_compile"))
49 {
50 return false;
51 }
52 return true;
53 }
54
55 class Task
56 {
57 public:
Task(int id)58 Task(int id) : mID(id) {}
~Task()59 virtual ~Task() {}
60
61 virtual bool compile() = 0;
62 virtual bool isCompileCompleted() = 0;
63 virtual bool link() = 0;
postLink()64 virtual void postLink() {}
65 virtual void runAndVerify(ParallelShaderCompileTest *test) = 0;
66
isLinkCompleted()67 bool isLinkCompleted()
68 {
69 GLint status;
70 glGetProgramiv(mProgram, GL_COMPLETION_STATUS_KHR, &status);
71 return (status == GL_TRUE);
72 }
73
74 protected:
InsertRandomString(const std::string & source)75 static std::string InsertRandomString(const std::string &source)
76 {
77 RNG rng;
78 std::ostringstream ostream;
79 ostream << source << "\n// Random string to fool program cache: " << rng.randomInt()
80 << "\n";
81 return ostream.str();
82 }
83
CompileShader(GLenum type,const std::string & source)84 static GLuint CompileShader(GLenum type, const std::string &source)
85 {
86 GLuint shader = glCreateShader(type);
87
88 const char *sourceArray[1] = {source.c_str()};
89 glShaderSource(shader, 1, sourceArray, nullptr);
90 glCompileShader(shader);
91 return shader;
92 }
93
RecompileShader(GLuint shader,const std::string & source)94 static void RecompileShader(GLuint shader, const std::string &source)
95 {
96 const char *sourceArray[1] = {source.c_str()};
97 glShaderSource(shader, 1, sourceArray, nullptr);
98 glCompileShader(shader);
99 }
100
CheckShader(GLuint shader)101 static bool CheckShader(GLuint shader)
102 {
103 GLint compileResult;
104 glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);
105
106 if (compileResult == 0)
107 {
108 GLint infoLogLength;
109 glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
110
111 // Info log length includes the null terminator, so 1 means that the info log is an
112 // empty string.
113 if (infoLogLength > 1)
114 {
115 std::vector<GLchar> infoLog(infoLogLength);
116 glGetShaderInfoLog(shader, static_cast<GLsizei>(infoLog.size()), nullptr,
117 &infoLog[0]);
118 std::cerr << "shader compilation failed: " << &infoLog[0];
119 }
120 else
121 {
122 std::cerr << "shader compilation failed. <Empty log message>";
123 }
124 std::cerr << std::endl;
125 }
126 return (compileResult == GL_TRUE);
127 }
128
129 GLuint mProgram;
130 int mID;
131 };
132
133 template <typename T>
134 class TaskRunner
135 {
136 public:
TaskRunner()137 TaskRunner() {}
~TaskRunner()138 ~TaskRunner() {}
139
run(ParallelShaderCompileTest * test,unsigned int pollInterval)140 void run(ParallelShaderCompileTest *test, unsigned int pollInterval)
141 {
142
143 std::vector<std::unique_ptr<T>> compileTasks;
144 for (int i = 0; i < kTaskCount; ++i)
145 {
146 std::unique_ptr<T> task(new T(i));
147 bool isCompiling = task->compile();
148 ASSERT_TRUE(isCompiling);
149 compileTasks.push_back(std::move(task));
150 }
151
152 std::vector<std::unique_ptr<T>> linkTasks;
153 while (!compileTasks.empty())
154 {
155 for (unsigned int i = 0; i < compileTasks.size();)
156 {
157 auto &task = compileTasks[i];
158
159 if (task->isCompileCompleted())
160 {
161 bool isLinking = task->link();
162 task->postLink();
163 ASSERT_TRUE(isLinking);
164 linkTasks.push_back(std::move(task));
165 compileTasks.erase(compileTasks.begin() + i);
166 continue;
167 }
168 ++i;
169 }
170 if (pollInterval != 0)
171 {
172 angle::Sleep(pollInterval);
173 }
174 }
175
176 while (!linkTasks.empty())
177 {
178 for (unsigned int i = 0; i < linkTasks.size();)
179 {
180 auto &task = linkTasks[i];
181
182 if (task->isLinkCompleted())
183 {
184 task->runAndVerify(test);
185 linkTasks.erase(linkTasks.begin() + i);
186 continue;
187 }
188 else
189 {
190 task->postLink();
191 }
192 ++i;
193 }
194 if (pollInterval != 0)
195 {
196 angle::Sleep(pollInterval);
197 }
198 }
199 }
200 };
201
202 class ClearColorWithDraw : public Task
203 {
204 public:
ClearColorWithDraw(int taskID)205 ClearColorWithDraw(int taskID) : Task(taskID)
206 {
207 auto color = static_cast<GLubyte>(taskID * 255 / kTaskCount);
208 mColor = {color, color, color, 255};
209 }
210
compile()211 bool compile() override
212 {
213 mVertexShader =
214 CompileShader(GL_VERTEX_SHADER, InsertRandomString(essl1_shaders::vs::Simple()));
215 mFragmentShader = CompileShader(GL_FRAGMENT_SHADER,
216 InsertRandomString(essl1_shaders::fs::UniformColor()));
217 return (mVertexShader != 0 && mFragmentShader != 0);
218 }
219
isCompileCompleted()220 bool isCompileCompleted() override
221 {
222 GLint status;
223 glGetShaderiv(mVertexShader, GL_COMPLETION_STATUS_KHR, &status);
224 if (status == GL_TRUE)
225 {
226 glGetShaderiv(mFragmentShader, GL_COMPLETION_STATUS_KHR, &status);
227 return (status == GL_TRUE);
228 }
229 return false;
230 }
231
link()232 bool link() override
233 {
234 mProgram = 0;
235 if (CheckShader(mVertexShader) && CheckShader(mFragmentShader))
236 {
237 mProgram = glCreateProgram();
238 glAttachShader(mProgram, mVertexShader);
239 glAttachShader(mProgram, mFragmentShader);
240 glLinkProgram(mProgram);
241 }
242 glDeleteShader(mVertexShader);
243 glDeleteShader(mFragmentShader);
244 return (mProgram != 0);
245 }
246
runAndVerify(ParallelShaderCompileTest * test)247 void runAndVerify(ParallelShaderCompileTest *test) override
248 {
249 glClearColor(0, 0, 0, 0);
250 glClear(GL_COLOR_BUFFER_BIT);
251 glDisable(GL_DEPTH_TEST);
252 glUseProgram(mProgram);
253 ASSERT_GL_NO_ERROR();
254 GLint colorUniformLocation =
255 glGetUniformLocation(mProgram, essl1_shaders::ColorUniform());
256 ASSERT_NE(colorUniformLocation, -1);
257 auto normalizeColor = mColor.toNormalizedVector();
258 glUniform4fv(colorUniformLocation, 1, normalizeColor.data());
259 test->drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f);
260 EXPECT_PIXEL_COLOR_EQ(test->getWindowWidth() / 2, test->getWindowHeight() / 2, mColor);
261 glUseProgram(0);
262 glDeleteProgram(mProgram);
263 ASSERT_GL_NO_ERROR();
264 }
265
266 protected:
recompile()267 void recompile()
268 {
269 RecompileShader(mVertexShader, essl1_shaders::vs::Simple());
270 RecompileShader(mFragmentShader, essl1_shaders::fs::UniformColor());
271 }
272
273 private:
274 GLuint mVertexShader;
275 GLuint mFragmentShader;
276 GLColor mColor;
277 };
278
279 class ClearColorWithDrawRecompile : public ClearColorWithDraw
280 {
281 public:
ClearColorWithDrawRecompile(int taskID)282 ClearColorWithDrawRecompile(int taskID) : ClearColorWithDraw(taskID) {}
283
postLink()284 void postLink() override { recompile(); }
285 };
286
287 class ImageLoadStore : public Task
288 {
289 public:
ImageLoadStore(int taskID)290 ImageLoadStore(int taskID) : Task(taskID) {}
~ImageLoadStore()291 ~ImageLoadStore() {}
292
compile()293 bool compile() override
294 {
295 const char kCSSource[] = R"(#version 310 es
296 layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
297 layout(r32ui, binding = 0) readonly uniform highp uimage2D uImage_1;
298 layout(r32ui, binding = 1) writeonly uniform highp uimage2D uImage_2;
299 void main()
300 {
301 uvec4 value = imageLoad(uImage_1, ivec2(gl_LocalInvocationID.xy));
302 imageStore(uImage_2, ivec2(gl_LocalInvocationID.xy), value);
303 })";
304
305 mShader = CompileShader(GL_COMPUTE_SHADER, InsertRandomString(kCSSource));
306 return mShader != 0;
307 }
308
isCompileCompleted()309 bool isCompileCompleted() override
310 {
311 GLint status;
312 glGetShaderiv(mShader, GL_COMPLETION_STATUS_KHR, &status);
313 return status == GL_TRUE;
314 }
315
link()316 bool link() override
317 {
318 mProgram = 0;
319 if (CheckShader(mShader))
320 {
321 mProgram = glCreateProgram();
322 glAttachShader(mProgram, mShader);
323 glLinkProgram(mProgram);
324 }
325 glDeleteShader(mShader);
326 return mProgram != 0;
327 }
328
runAndVerify(ParallelShaderCompileTest * test)329 void runAndVerify(ParallelShaderCompileTest *test) override
330 {
331 // Taken from ComputeShaderTest.StoreImageThenLoad.
332 constexpr GLuint kInputValues[3][1] = {{300}, {200}, {100}};
333 GLTexture texture[3];
334 glBindTexture(GL_TEXTURE_2D, texture[0]);
335 glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
336 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT,
337 kInputValues[0]);
338 EXPECT_GL_NO_ERROR();
339
340 glBindTexture(GL_TEXTURE_2D, texture[1]);
341 glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
342 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT,
343 kInputValues[1]);
344 EXPECT_GL_NO_ERROR();
345
346 glBindTexture(GL_TEXTURE_2D, texture[2]);
347 glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
348 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT,
349 kInputValues[2]);
350 EXPECT_GL_NO_ERROR();
351
352 glUseProgram(mProgram);
353
354 glBindImageTexture(0, texture[0], 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
355 glBindImageTexture(1, texture[1], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
356
357 glDispatchCompute(1, 1, 1);
358 glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
359 EXPECT_GL_NO_ERROR();
360
361 glBindImageTexture(0, texture[1], 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
362 glBindImageTexture(1, texture[2], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
363
364 glDispatchCompute(1, 1, 1);
365 glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
366 EXPECT_GL_NO_ERROR();
367
368 GLuint outputValue;
369 GLFramebuffer framebuffer;
370 glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
371 glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
372 texture[2], 0);
373 glReadPixels(0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &outputValue);
374 EXPECT_GL_NO_ERROR();
375
376 EXPECT_EQ(300u, outputValue);
377
378 glUseProgram(0);
379 glDeleteProgram(mProgram);
380 ASSERT_GL_NO_ERROR();
381 }
382
383 private:
384 GLuint mShader;
385 };
386 };
387
388 // Test basic functionality of GL_KHR_parallel_shader_compile
TEST_P(ParallelShaderCompileTest,Basic)389 TEST_P(ParallelShaderCompileTest, Basic)
390 {
391 ANGLE_SKIP_TEST_IF(!ensureParallelShaderCompileExtensionAvailable());
392
393 GLint count = 0;
394 glMaxShaderCompilerThreadsKHR(8);
395 EXPECT_GL_NO_ERROR();
396 glGetIntegerv(GL_MAX_SHADER_COMPILER_THREADS_KHR, &count);
397 EXPECT_GL_NO_ERROR();
398 EXPECT_EQ(8, count);
399 }
400
401 // Test to compile and link many programs in parallel.
TEST_P(ParallelShaderCompileTest,LinkAndDrawManyPrograms)402 TEST_P(ParallelShaderCompileTest, LinkAndDrawManyPrograms)
403 {
404 ANGLE_SKIP_TEST_IF(!ensureParallelShaderCompileExtensionAvailable());
405
406 TaskRunner<ClearColorWithDraw> runner;
407 runner.run(this, kPollInterval);
408 }
409
410 // Tests no crash in case that the Shader starts another compile while the Program being attached
411 // to is still linking.
412 // crbug.com/1317673
TEST_P(ParallelShaderCompileTest,LinkProgramAndRecompileShader)413 TEST_P(ParallelShaderCompileTest, LinkProgramAndRecompileShader)
414 {
415 ANGLE_SKIP_TEST_IF(!ensureParallelShaderCompileExtensionAvailable());
416
417 TaskRunner<ClearColorWithDrawRecompile> runner;
418 runner.run(this, 0);
419 }
420
421 class ParallelShaderCompileTestES31 : public ParallelShaderCompileTest
422 {};
423
424 // Test to compile and link many computing programs in parallel.
TEST_P(ParallelShaderCompileTestES31,LinkAndDispatchManyPrograms)425 TEST_P(ParallelShaderCompileTestES31, LinkAndDispatchManyPrograms)
426 {
427 // Flaky on Win NVIDIA D3D11. http://anglebug.com/3359
428 // Suspectable to the flakyness of http://anglebug.com/3349.
429 ANGLE_SKIP_TEST_IF(IsWindows() && IsD3D11());
430
431 // TODO(http://anglebug.com/5656): Fails on Linux+Intel+OpenGL
432 ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel() && IsOpenGL());
433
434 ANGLE_SKIP_TEST_IF(!ensureParallelShaderCompileExtensionAvailable());
435
436 TaskRunner<ImageLoadStore> runner;
437 runner.run(this, kPollInterval);
438 }
439
440 ANGLE_INSTANTIATE_TEST_ES2(ParallelShaderCompileTest);
441
442 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ParallelShaderCompileTestES31);
443 ANGLE_INSTANTIATE_TEST_ES31(ParallelShaderCompileTestES31);
444
445 } // namespace
446