1 //
2 // Copyright 2023 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 // ParallelLinkProgramPerfTest:
7 // Tests performance of compiling and linking many shaders and programs in sequence.
8 //
9
10 #include "ANGLEPerfTest.h"
11
12 #include <array>
13
14 #include "common/vector_utils.h"
15 #include "util/shader_utils.h"
16
17 using namespace angle;
18
19 namespace
20 {
21
22 enum class CompileLinkOrder
23 {
24 // Most optimal behavior; all compiles are done first, then all links.
25 AllCompilesFirst,
26 // Less optimal, but still decent; each program's shaders are first compiled, then the program
27 // is linked before moving on to the next program.
28 Interleaved,
29 // Worst behavior, which unfortunately is what the large majority of applications do; each
30 // program's shaders are compiled, the program is linked, and the link status is immediately
31 // queried (causing the main thread to block on the link task).
32 InterleavedAndImmediateQuery,
33
34 Unspecified,
35 };
36
37 struct ParallelLinkProgramParams final : public RenderTestParams
38 {
ParallelLinkProgramParams__anon97123a550111::ParallelLinkProgramParams39 ParallelLinkProgramParams(CompileLinkOrder order)
40 {
41 iterationsPerStep = 100;
42
43 majorVersion = 3;
44 minorVersion = 0;
45 windowWidth = 256;
46 windowHeight = 256;
47 compileLinkOrder = order;
48 }
49
story__anon97123a550111::ParallelLinkProgramParams50 std::string story() const override
51 {
52 std::stringstream strstr;
53 strstr << RenderTestParams::story();
54
55 if (compileLinkOrder == CompileLinkOrder::AllCompilesFirst)
56 {
57 strstr << "_all_compiles_first";
58 }
59 else if (compileLinkOrder == CompileLinkOrder::Interleaved)
60 {
61 strstr << "_interleaved_compile_and_link";
62 }
63 else if (compileLinkOrder == CompileLinkOrder::InterleavedAndImmediateQuery)
64 {
65 strstr << "_interleaved_compile_and_link_with_immediate_query";
66 }
67
68 if (std::find(eglParameters.disabledFeatureOverrides.begin(),
69 eglParameters.disabledFeatureOverrides.end(),
70 Feature::EnableParallelCompileAndLink) !=
71 eglParameters.disabledFeatureOverrides.end())
72 {
73 strstr << "_serial";
74 }
75
76 if (eglParameters.deviceType == EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE)
77 {
78 strstr << "_null";
79 }
80
81 return strstr.str();
82 }
83
84 CompileLinkOrder compileLinkOrder;
85 };
86
operator <<(std::ostream & os,const ParallelLinkProgramParams & params)87 std::ostream &operator<<(std::ostream &os, const ParallelLinkProgramParams ¶ms)
88 {
89 os << params.backendAndStory().substr(1);
90 return os;
91 }
92
93 class ParallelLinkProgramBenchmark : public ANGLERenderTest,
94 public ::testing::WithParamInterface<ParallelLinkProgramParams>
95 {
96 public:
97 ParallelLinkProgramBenchmark();
98
99 void initializeBenchmark() override;
100 void destroyBenchmark() override;
101 void drawBenchmark() override;
102
103 protected:
104 struct Program
105 {
106 std::string vertexShader;
107 std::string fragmentShader;
108
109 GLuint vs;
110 GLuint fs;
111 GLuint program;
112 };
113
114 std::vector<Program> mPrograms;
115 };
116
ParallelLinkProgramBenchmark()117 ParallelLinkProgramBenchmark::ParallelLinkProgramBenchmark()
118 : ANGLERenderTest("ParallelLinkProgram", GetParam())
119 {
120 if (IsWindows() && IsNVIDIA() &&
121 GetParam().eglParameters.renderer == EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE)
122 {
123 skipTest("http://anglebug.com/42266835 crashes the GL driver");
124 }
125
126 if (IsLinux() && IsNVIDIA() &&
127 GetParam().eglParameters.renderer == EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE)
128 {
129 skipTest("http://anglebug.com/42266835 flakily crashes the GL driver");
130 }
131 }
132
initializeBenchmark()133 void ParallelLinkProgramBenchmark::initializeBenchmark()
134 {
135 const ParallelLinkProgramParams ¶ms = GetParam();
136
137 // Generate N shaders and create the GL objects. DrawBenchmark would then _only_ do compilation
138 // and link.
139 mPrograms.resize(params.iterationsPerStep);
140 for (uint32_t i = 0; i < params.iterationsPerStep; ++i)
141 {
142 std::ostringstream vs;
143 vs << R"(#version 300 es
144 uniform UBO
145 {
146 vec4 data[)"
147 << params.iterationsPerStep << R"(];
148 };
149
150 uniform UBO2
151 {
152 mat4 m[2];
153 } ubo;
154
155 in mediump vec4 attr1;
156 in mediump uvec4 attr2;
157
158 out highp vec3 var1;
159 flat out lowp uvec2 var2;
160
161 void main()
162 {
163 vec4 a = attr1 + vec4(attr2);)";
164 for (uint32_t j = 0; j < i * 5; ++j)
165 {
166 vs << R"(
167 a *= ubo.m[0];)";
168 }
169 vs << R"(
170 a += ubo.m[1][2];
171 var1 = cross(a.zxy, data[)"
172 << i << R"(].xxw);
173 var2 = uvec2(1, 3) * uvec2(6, 3);
174 })";
175 mPrograms[i].vertexShader = vs.str();
176
177 std::ostringstream fs;
178 fs << R"(#version 300 es
179 uniform UBO
180 {
181 highp vec4 data[)"
182 << params.iterationsPerStep << R"(];
183 };
184
185 in highp vec3 var1;
186 flat in lowp uvec2 var2;
187
188 uniform mediump sampler2D s;
189 uniform mediump sampler2D s2;
190
191 layout(location = 0) out mediump vec4 color1;
192 layout(location = 2) out highp uvec2 color2;
193
194 void main()
195 {
196 color1 = var1.xyzx + texture(s, vec2(var2) / 9.);
197 mediump vec4 sum = vec4(0);
198 for (int i = 0; i < 10; ++i)
199 sum += texture(s2, vec2(float(i / 2) / 5., float(i % 5) / 5.));
200 uvec2 res = uvec2(sum.xz + sum.yw);)";
201 for (uint32_t j = 0; j < i * 5; ++j)
202 {
203 fs << R"(
204 res += uvec2(data[)"
205 << (j % 100) << R"(]);)";
206 }
207 fs << R"(
208 color2 = res;
209 })";
210 mPrograms[i].fragmentShader = fs.str();
211
212 mPrograms[i].vs = glCreateShader(GL_VERTEX_SHADER);
213 mPrograms[i].fs = glCreateShader(GL_FRAGMENT_SHADER);
214 mPrograms[i].program = glCreateProgram();
215
216 const char *vsCStr = mPrograms[i].vertexShader.c_str();
217 const char *fsCStr = mPrograms[i].fragmentShader.c_str();
218 glShaderSource(mPrograms[i].vs, 1, &vsCStr, 0);
219 glShaderSource(mPrograms[i].fs, 1, &fsCStr, 0);
220
221 glAttachShader(mPrograms[i].program, mPrograms[i].vs);
222 glAttachShader(mPrograms[i].program, mPrograms[i].fs);
223 }
224
225 ASSERT_GL_NO_ERROR();
226 }
227
destroyBenchmark()228 void ParallelLinkProgramBenchmark::destroyBenchmark()
229 {
230 const ParallelLinkProgramParams ¶ms = GetParam();
231
232 for (uint32_t i = 0; i < params.iterationsPerStep; ++i)
233 {
234 glDetachShader(mPrograms[i].program, mPrograms[i].vs);
235 glDetachShader(mPrograms[i].program, mPrograms[i].fs);
236 glDeleteShader(mPrograms[i].vs);
237 glDeleteShader(mPrograms[i].fs);
238 glDeleteProgram(mPrograms[i].program);
239 }
240 }
241
drawBenchmark()242 void ParallelLinkProgramBenchmark::drawBenchmark()
243 {
244 const ParallelLinkProgramParams ¶ms = GetParam();
245
246 for (uint32_t i = 0; i < params.iterationsPerStep; ++i)
247 {
248 // Compile the shaders, and if interleaved, link the corresponding programs.
249 glCompileShader(mPrograms[i].vs);
250 glCompileShader(mPrograms[i].fs);
251 if (params.compileLinkOrder != CompileLinkOrder::AllCompilesFirst)
252 {
253 glLinkProgram(mPrograms[i].program);
254
255 if (params.compileLinkOrder == CompileLinkOrder::InterleavedAndImmediateQuery)
256 {
257 GLint linkStatus = GL_TRUE;
258 glGetProgramiv(mPrograms[i].program, GL_LINK_STATUS, &linkStatus);
259 EXPECT_TRUE(linkStatus) << i;
260 }
261 }
262 }
263
264 // If asked to link after all shaders are compiled, link all the programs now
265 if (params.compileLinkOrder == CompileLinkOrder::AllCompilesFirst)
266 {
267 for (uint32_t i = 0; i < params.iterationsPerStep; ++i)
268 {
269 glLinkProgram(mPrograms[i].program);
270 }
271 }
272
273 // Now that all the compile and link jobs have been scheduled, wait for them all to finish.
274 for (uint32_t i = 0; i < params.iterationsPerStep; ++i)
275 {
276 GLint compileResult;
277 glGetShaderiv(mPrograms[i].vs, GL_COMPILE_STATUS, &compileResult);
278 EXPECT_NE(compileResult, 0) << i;
279 glGetShaderiv(mPrograms[i].fs, GL_COMPILE_STATUS, &compileResult);
280 EXPECT_NE(compileResult, 0) << i;
281
282 GLint linkStatus = GL_TRUE;
283 glGetProgramiv(mPrograms[i].program, GL_LINK_STATUS, &linkStatus);
284 EXPECT_TRUE(linkStatus) << i;
285 }
286
287 // ANGLE supports running some optional link subtasks beyond the actual end of the link. Ensure
288 // those are all finished by triggerring a wait on the jobs of the last program. Currently,
289 // detaching and attaching shaders does that (among other operations).
290 const uint32_t last = params.iterationsPerStep - 1;
291 glDetachShader(mPrograms[last].program, mPrograms[last].vs);
292 glAttachShader(mPrograms[last].program, mPrograms[last].vs);
293
294 ASSERT_GL_NO_ERROR();
295 }
296
297 using namespace egl_platform;
298
ParallelLinkProgramD3D11Params(CompileLinkOrder compileLinkOrder)299 ParallelLinkProgramParams ParallelLinkProgramD3D11Params(CompileLinkOrder compileLinkOrder)
300 {
301 ParallelLinkProgramParams params(compileLinkOrder);
302 params.eglParameters = D3D11();
303 return params;
304 }
305
ParallelLinkProgramMetalParams(CompileLinkOrder compileLinkOrder)306 ParallelLinkProgramParams ParallelLinkProgramMetalParams(CompileLinkOrder compileLinkOrder)
307 {
308 ParallelLinkProgramParams params(compileLinkOrder);
309 params.eglParameters = METAL();
310 return params;
311 }
312
ParallelLinkProgramOpenGLOrGLESParams(CompileLinkOrder compileLinkOrder)313 ParallelLinkProgramParams ParallelLinkProgramOpenGLOrGLESParams(CompileLinkOrder compileLinkOrder)
314 {
315 ParallelLinkProgramParams params(compileLinkOrder);
316 params.eglParameters = OPENGL_OR_GLES();
317 return params;
318 }
319
ParallelLinkProgramVulkanParams(CompileLinkOrder compileLinkOrder)320 ParallelLinkProgramParams ParallelLinkProgramVulkanParams(CompileLinkOrder compileLinkOrder)
321 {
322 ParallelLinkProgramParams params(compileLinkOrder);
323 params.eglParameters = VULKAN();
324 params.enable(Feature::EnableParallelCompileAndLink);
325 return params;
326 }
327
SerialLinkProgramVulkanParams(CompileLinkOrder compileLinkOrder)328 ParallelLinkProgramParams SerialLinkProgramVulkanParams(CompileLinkOrder compileLinkOrder)
329 {
330 ParallelLinkProgramParams params(compileLinkOrder);
331 params.eglParameters = VULKAN();
332 params.disable(Feature::EnableParallelCompileAndLink);
333 return params;
334 }
335
336 // Test parallel link performance
TEST_P(ParallelLinkProgramBenchmark,Run)337 TEST_P(ParallelLinkProgramBenchmark, Run)
338 {
339 run();
340 }
341
342 ANGLE_INSTANTIATE_TEST(
343 ParallelLinkProgramBenchmark,
344 ParallelLinkProgramD3D11Params(CompileLinkOrder::AllCompilesFirst),
345 ParallelLinkProgramD3D11Params(CompileLinkOrder::Interleaved),
346 ParallelLinkProgramMetalParams(CompileLinkOrder::AllCompilesFirst),
347 ParallelLinkProgramMetalParams(CompileLinkOrder::Interleaved),
348 ParallelLinkProgramOpenGLOrGLESParams(CompileLinkOrder::AllCompilesFirst),
349 ParallelLinkProgramOpenGLOrGLESParams(CompileLinkOrder::Interleaved),
350 ParallelLinkProgramVulkanParams(CompileLinkOrder::AllCompilesFirst),
351 ParallelLinkProgramVulkanParams(CompileLinkOrder::Interleaved),
352 ParallelLinkProgramVulkanParams(CompileLinkOrder::InterleavedAndImmediateQuery),
353 SerialLinkProgramVulkanParams(CompileLinkOrder::AllCompilesFirst),
354 SerialLinkProgramVulkanParams(CompileLinkOrder::Interleaved),
355 SerialLinkProgramVulkanParams(CompileLinkOrder::InterleavedAndImmediateQuery));
356
357 } // anonymous namespace
358