1 /*
2 * Copyright 2019 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7 #include "bench/Benchmark.h"
8 #include "bench/ResultsWriter.h"
9 #include "bench/SkSLBench.h"
10 #include "include/core/SkCanvas.h"
11 #include "src/gpu/GrCaps.h"
12 #include "src/gpu/GrRecordingContextPriv.h"
13 #include "src/gpu/mock/GrMockCaps.h"
14 #include "src/sksl/SkSLCompiler.h"
15 #include "src/sksl/SkSLDSLParser.h"
16
17 class SkSLCompilerStartupBench : public Benchmark {
18 protected:
onGetName()19 const char* onGetName() override {
20 return "sksl_compiler_startup";
21 }
22
isSuitableFor(Backend backend)23 bool isSuitableFor(Backend backend) override {
24 return backend == kNonRendering_Backend;
25 }
26
onDraw(int loops,SkCanvas *)27 void onDraw(int loops, SkCanvas*) override {
28 GrShaderCaps caps;
29 for (int i = 0; i < loops; i++) {
30 SkSL::Compiler compiler(&caps);
31 }
32 }
33 };
34
35 DEF_BENCH(return new SkSLCompilerStartupBench();)
36
37 enum class Output {
38 kNone,
39 kGLSL,
40 kMetal,
41 kSPIRV
42 };
43
44 class SkSLCompileBench : public Benchmark {
45 public:
output_string(Output output)46 static const char* output_string(Output output) {
47 switch (output) {
48 case Output::kNone: return "";
49 case Output::kGLSL: return "glsl_";
50 case Output::kMetal: return "metal_";
51 case Output::kSPIRV: return "spirv_";
52 }
53 SkUNREACHABLE;
54 }
55
SkSLCompileBench(SkSL::String name,const char * src,bool optimize,Output output)56 SkSLCompileBench(SkSL::String name, const char* src, bool optimize, Output output)
57 : fName(SkSL::String("sksl_") + (optimize ? "" : "unoptimized_") + output_string(output) +
58 name)
59 , fSrc(src)
60 , fCaps(GrContextOptions(), GrMockOptions())
61 , fCompiler(fCaps.shaderCaps())
62 , fOutput(output) {
63 fSettings.fOptimize = optimize;
64 fSettings.fDSLMangling = false;
65 // The test programs we compile don't follow Vulkan rules and thus produce invalid
66 // SPIR-V. This is harmless, so long as we don't try to validate them.
67 fSettings.fValidateSPIRV = false;
68 }
69
70 protected:
onGetName()71 const char* onGetName() override {
72 return fName.c_str();
73 }
74
isSuitableFor(Backend backend)75 bool isSuitableFor(Backend backend) override {
76 return backend == kNonRendering_Backend;
77 }
78
onDraw(int loops,SkCanvas * canvas)79 void onDraw(int loops, SkCanvas* canvas) override {
80 for (int i = 0; i < loops; i++) {
81 std::unique_ptr<SkSL::Program> program = SkSL::DSLParser(&fCompiler,
82 fSettings,
83 SkSL::ProgramKind::kFragment,
84 fSrc).program();
85 if (fCompiler.errorCount()) {
86 SK_ABORT("shader compilation failed: %s\n", fCompiler.errorText().c_str());
87 }
88 SkSL::String result;
89 switch (fOutput) {
90 case Output::kNone: break;
91 case Output::kGLSL: SkAssertResult(fCompiler.toGLSL(*program, &result)); break;
92 case Output::kMetal: SkAssertResult(fCompiler.toMetal(*program, &result)); break;
93 case Output::kSPIRV: SkAssertResult(fCompiler.toSPIRV(*program, &result)); break;
94 }
95 }
96 }
97
98 private:
99 SkSL::String fName;
100 SkSL::String fSrc;
101 GrMockCaps fCaps;
102 SkSL::Compiler fCompiler;
103 SkSL::Program::Settings fSettings;
104 Output fOutput;
105
106 using INHERITED = Benchmark;
107 };
108
109 ///////////////////////////////////////////////////////////////////////////////
110
111 #define COMPILER_BENCH(name, text) \
112 static constexpr char name ## _SRC[] = text; \
113 DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/false, Output::kNone);) \
114 DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/true, Output::kNone);) \
115 DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/true, Output::kGLSL);) \
116 DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/true, Output::kMetal);) \
117 DEF_BENCH(return new SkSLCompileBench(#name, name ## _SRC, /*optimize=*/true, Output::kSPIRV);)
118
119 // This fragment shader is from the third tile on the top row of GM_gradients_2pt_conical_outside.
120 COMPILER_BENCH(large, R"(
121 layout(set=0, binding=0) uniform half urange_Stage1_c0;
122 layout(set=0, binding=0) uniform half4 uleftBorderColor_Stage1_c0_c0_c0;
123 layout(set=0, binding=0) uniform half4 urightBorderColor_Stage1_c0_c0_c0;
124 layout(set=0, binding=0) uniform float3x3 umatrix_Stage1_c0_c0_c0_c0;
125 layout(set=0, binding=0) uniform half2 ufocalParams_Stage1_c0_c0_c0_c0_c0;
126 layout(set=0, binding=0) uniform float4 uscale0_1_Stage1_c0_c0_c0_c1;
127 layout(set=0, binding=0) uniform float4 uscale2_3_Stage1_c0_c0_c0_c1;
128 layout(set=0, binding=0) uniform float4 uscale4_5_Stage1_c0_c0_c0_c1;
129 layout(set=0, binding=0) uniform float4 uscale6_7_Stage1_c0_c0_c0_c1;
130 layout(set=0, binding=0) uniform float4 ubias0_1_Stage1_c0_c0_c0_c1;
131 layout(set=0, binding=0) uniform float4 ubias2_3_Stage1_c0_c0_c0_c1;
132 layout(set=0, binding=0) uniform float4 ubias4_5_Stage1_c0_c0_c0_c1;
133 layout(set=0, binding=0) uniform float4 ubias6_7_Stage1_c0_c0_c0_c1;
134 layout(set=0, binding=0) uniform half4 uthresholds1_7_Stage1_c0_c0_c0_c1;
135 layout(set=0, binding=0) uniform half4 uthresholds9_13_Stage1_c0_c0_c0_c1;
136 flat in half4 vcolor_Stage0;
137 noperspective in float2 vTransformedCoords_0_Stage0;
138 out half4 sk_FragColor;
139 half4 TwoPointConicalGradientLayout_Stage1_c0_c0_c0_c0_c0(half4 _input)
140 {
141 float t = -1.0;
142 half v = 1.0;
143 @switch (2)
144 {
145 case 1:
146 {
147 half r0_2 = ufocalParams_Stage1_c0_c0_c0_c0_c0.y;
148 t = float(r0_2) - vTransformedCoords_0_Stage0.y * vTransformedCoords_0_Stage0.y;
149 if (t >= 0.0)
150 {
151 t = vTransformedCoords_0_Stage0.x + sqrt(t);
152 }
153 else
154 {
155 v = -1.0;
156 }
157 }
158 break;
159 case 0:
160 {
161 half r0 = ufocalParams_Stage1_c0_c0_c0_c0_c0.x;
162 @if (true)
163 {
164 t = length(vTransformedCoords_0_Stage0) - float(r0);
165 }
166 else
167 {
168 t = -length(vTransformedCoords_0_Stage0) - float(r0);
169 }
170 }
171 break;
172 case 2:
173 {
174 half invR1 = ufocalParams_Stage1_c0_c0_c0_c0_c0.x;
175 half fx = ufocalParams_Stage1_c0_c0_c0_c0_c0.y;
176 float x_t = -1.0;
177 @if (false)
178 {
179 x_t = dot(vTransformedCoords_0_Stage0, vTransformedCoords_0_Stage0) / vTransformedCoords_0_Stage0.x;
180 }
181 else if (false)
182 {
183 x_t = length(vTransformedCoords_0_Stage0) - vTransformedCoords_0_Stage0.x * float(invR1);
184 }
185 else
186 {
187 float temp = vTransformedCoords_0_Stage0.x * vTransformedCoords_0_Stage0.x - vTransformedCoords_0_Stage0.y * vTransformedCoords_0_Stage0.y;
188 if (temp >= 0.0)
189 {
190 @if (false || !true)
191 {
192 x_t = -sqrt(temp) - vTransformedCoords_0_Stage0.x * float(invR1);
193 }
194 else
195 {
196 x_t = sqrt(temp) - vTransformedCoords_0_Stage0.x * float(invR1);
197 }
198 }
199 }
200 @if (!false)
201 {
202 if (x_t <= 0.0)
203 {
204 v = -1.0;
205 }
206 }
207 @if (true)
208 {
209 @if (false)
210 {
211 t = x_t;
212 }
213 else
214 {
215 t = x_t + float(fx);
216 }
217 }
218 else
219 {
220 @if (false)
221 {
222 t = -x_t;
223 }
224 else
225 {
226 t = -x_t + float(fx);
227 }
228 }
229 @if (false)
230 {
231 t = 1.0 - t;
232 }
233 }
234 break;
235 }
236 return half4(half(t), v, 0.0, 0.0);
237 }
238 half4 MatrixEffect_Stage1_c0_c0_c0_c0(half4 _input)
239 {
240 return TwoPointConicalGradientLayout_Stage1_c0_c0_c0_c0_c0(_input);
241 }
242 half4 UnrolledBinaryGradientColorizer_Stage1_c0_c0_c0_c1(half4 _input, float2 _coords)
243 {
244 half t = half(_coords.x);
245 float4 scale;
246 float4 bias;
247 if (4 <= 4 || t < uthresholds1_7_Stage1_c0_c0_c0_c1.w)
248 {
249 if (4 <= 2 || t < uthresholds1_7_Stage1_c0_c0_c0_c1.y)
250 {
251 if (4 <= 1 || t < uthresholds1_7_Stage1_c0_c0_c0_c1.x)
252 {
253 scale = uscale0_1_Stage1_c0_c0_c0_c1;
254 bias = ubias0_1_Stage1_c0_c0_c0_c1;
255 }
256 else
257 {
258 scale = uscale2_3_Stage1_c0_c0_c0_c1;
259 bias = ubias2_3_Stage1_c0_c0_c0_c1;
260 }
261 }
262 else
263 {
264 if (4 <= 3 || t < uthresholds1_7_Stage1_c0_c0_c0_c1.z)
265 {
266 scale = uscale4_5_Stage1_c0_c0_c0_c1;
267 bias = ubias4_5_Stage1_c0_c0_c0_c1;
268 }
269 else
270 {
271 scale = uscale6_7_Stage1_c0_c0_c0_c1;
272 bias = ubias6_7_Stage1_c0_c0_c0_c1;
273 }
274 }
275 }
276 else
277 {
278 if (4 <= 6 || t < uthresholds9_13_Stage1_c0_c0_c0_c1.y)
279 {
280 if (4 <= 5 || t < uthresholds9_13_Stage1_c0_c0_c0_c1.x)
281 {
282 scale = float4(0);
283 bias = float4(0);
284 }
285 else
286 {
287 scale = float4(0);
288 bias = float4(0);
289 }
290 }
291 else
292 {
293 if (4 <= 7 || t < uthresholds9_13_Stage1_c0_c0_c0_c1.z)
294 {
295 scale = float4(0);
296 bias = float4(0);
297 }
298 else
299 {
300 scale = float4(0);
301 bias = float4(0);
302 }
303 }
304 }
305 return half4(float(t) * scale + bias);
306 }
307 half4 ClampedGradientEffect_Stage1_c0_c0_c0(half4 _input)
308 {
309 half4 t = MatrixEffect_Stage1_c0_c0_c0_c0(_input);
310 half4 outColor;
311 if (!false && t.y < 0.0)
312 {
313 outColor = half4(0.0);
314 }
315 else if (t.x < 0.0)
316 {
317 outColor = uleftBorderColor_Stage1_c0_c0_c0;
318 }
319 else if (t.x > 1.0)
320 {
321 outColor = urightBorderColor_Stage1_c0_c0_c0;
322 }
323 else
324 {
325 outColor = UnrolledBinaryGradientColorizer_Stage1_c0_c0_c0_c1(_input, float2(half2(t.x, 0.0)));
326 }
327 @if (false)
328 {
329 outColor.xyz *= outColor.w;
330 }
331 return outColor;
332 }
333 half4 OverrideInputFragmentProcessor_Stage1_c0_c0(half4 _input)
334 {
335 return ClampedGradientEffect_Stage1_c0_c0_c0(false ? half4(0) : half4(1.000000, 1.000000, 1.000000, 1.000000));
336 }
337 half4 DitherEffect_Stage1_c0(half4 _input)
338 {
339 half4 color = OverrideInputFragmentProcessor_Stage1_c0_c0(_input);
340 half value;
341 @if (sk_Caps.integerSupport)
342 {
343 uint x = uint(sk_FragCoord.x);
344 uint y = uint(sk_FragCoord.y) ^ x;
345 uint m = (((((y & 1) << 5 | (x & 1) << 4) | (y & 2) << 2) | (x & 2) << 1) | (y & 4) >> 1) | (x & 4) >> 2;
346 value = half(m) / 64.0 - 0.4921875;
347 }
348 else
349 {
350 half4 bits = mod(half4(sk_FragCoord.yxyx), half4(2.0, 2.0, 4.0, 4.0));
351 bits.zw = step(2.0, bits.zw);
352 bits.xz = abs(bits.xz - bits.yw);
353 value = dot(bits, half4(0.5, 0.25, 0.125, 0.0625)) - 0.46875;
354 }
355 return half4(clamp(color.xyz + value * urange_Stage1_c0, 0.0, color.w), color.w);
356 }
357 void main()
358 {
359 // Stage 0, QuadPerEdgeAAGeometryProcessor
360 half4 outputColor_Stage0;
361 outputColor_Stage0 = vcolor_Stage0;
362 const half4 outputCoverage_Stage0 = half4(1);
363 half4 output_Stage1;
364 output_Stage1 = DitherEffect_Stage1_c0(outputColor_Stage0);
365 {
366 // Xfer Processor: Porter Duff
367 sk_FragColor = output_Stage1 * outputCoverage_Stage0;
368 }
369 }
370 )");
371
372 // This fragment shader is taken from GM_BlurDrawImage.
373 COMPILER_BENCH(medium, R"(
374 layout(set=0, binding=0) uniform float3x3 umatrix_Stage1_c0_c0_c0;
375 layout(set=0, binding=0) uniform half4 urectH_Stage2_c1;
376 layout(set=0, binding=0) uniform float3x3 umatrix_Stage2_c1_c0;
377 layout(set=0, binding=0) uniform sampler2D uTextureSampler_0_Stage1;
378 layout(set=0, binding=0) uniform sampler2D uTextureSampler_0_Stage2;
379 flat in half4 vcolor_Stage0;
380 noperspective in float2 vTransformedCoords_0_Stage0;
381 out half4 sk_FragColor;
382 half4 TextureEffect_Stage1_c0_c0_c0_c0(half4 _input)
383 {
384 return sample(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
385 }
386 half4 MatrixEffect_Stage1_c0_c0_c0(half4 _input)
387 {
388 return TextureEffect_Stage1_c0_c0_c0_c0(_input);
389 }
390 half4 Blend_Stage1_c0_c0(half4 _input)
391 {
392 // Blend mode: SrcIn (Compose-One behavior)
393 return blend_src_in(MatrixEffect_Stage1_c0_c0_c0(half4(1)), _input);
394 }
395 half4 OverrideInputFragmentProcessor_Stage1_c0(half4 _input)
396 {
397 return Blend_Stage1_c0_c0(false ? half4(0) : half4(1.000000, 1.000000, 1.000000, 1.000000));
398 }
399 half4 TextureEffect_Stage2_c1_c0_c0(half4 _input, float2 _coords)
400 {
401 return sample(uTextureSampler_0_Stage2, _coords).000r;
402 }
403 half4 MatrixEffect_Stage2_c1_c0(half4 _input, float2 _coords)
404 {
405 return TextureEffect_Stage2_c1_c0_c0(_input, ((umatrix_Stage2_c1_c0) * _coords.xy1).xy);
406 }
407 half4 RectBlurEffect_Stage2_c1(half4 _input)
408 {
409 /* key */ const bool highPrecision = false;
410 half xCoverage;
411 half yCoverage;
412 float2 pos = sk_FragCoord.xy;
413 @if (false)
414 {
415 pos = (float3x3(1) * float3(pos, 1.0)).xy;
416 }
417 @if (true)
418 {
419 half2 xy;
420 @if (highPrecision)
421 {
422 xy = max(half2(float4(0).xy - pos), half2(pos - float4(0).zw));
423 }
424 else
425 {
426 xy = max(half2(float2(urectH_Stage2_c1.xy) - pos), half2(pos - float2(urectH_Stage2_c1.zw)));
427 }
428 xCoverage = MatrixEffect_Stage2_c1_c0(_input, float2(half2(xy.x, 0.5))).w;
429 yCoverage = MatrixEffect_Stage2_c1_c0(_input, float2(half2(xy.y, 0.5))).w;
430 }
431 else
432 {
433 half4 rect;
434 @if (highPrecision)
435 {
436 rect.xy = half2(float4(0).xy - pos);
437 rect.zw = half2(pos - float4(0).zw);
438 }
439 else
440 {
441 rect.xy = half2(float2(urectH_Stage2_c1.xy) - pos);
442 rect.zw = half2(pos - float2(urectH_Stage2_c1.zw));
443 }
444 xCoverage = (1.0 - MatrixEffect_Stage2_c1_c0(_input, float2(half2(rect.x, 0.5))).w) - MatrixEffect_Stage2_c1_c0(_input, float2(half2(rect.z, 0.5))).w;
445 yCoverage = (1.0 - MatrixEffect_Stage2_c1_c0(_input, float2(half2(rect.y, 0.5))).w) - MatrixEffect_Stage2_c1_c0(_input, float2(half2(rect.w, 0.5))).w;
446 }
447 return (_input * xCoverage) * yCoverage;
448 }
449 void main()
450 {
451 // Stage 0, QuadPerEdgeAAGeometryProcessor
452 half4 outputColor_Stage0;
453 outputColor_Stage0 = vcolor_Stage0;
454 const half4 outputCoverage_Stage0 = half4(1);
455 half4 output_Stage1;
456 output_Stage1 = OverrideInputFragmentProcessor_Stage1_c0(outputColor_Stage0);
457 half4 output_Stage2;
458 output_Stage2 = RectBlurEffect_Stage2_c1(outputCoverage_Stage0);
459 {
460 // Xfer Processor: Porter Duff
461 sk_FragColor = output_Stage1 * output_Stage2;
462 }
463 }
464 )");
465
466 // This is the fragment shader used to blit the Viewer window when running the software rasterizer.
467 COMPILER_BENCH(small, R"(
468 layout(set=0, binding=0) uniform float3x3 umatrix_Stage1_c0_c0;
469 layout(set=0, binding=0) uniform sampler2D uTextureSampler_0_Stage1;
470 noperspective in float2 vTransformedCoords_0_Stage0;
471 out half4 sk_FragColor;
472 half4 TextureEffect_Stage1_c0_c0_c0(half4 _input)
473 {
474 return sample(uTextureSampler_0_Stage1, vTransformedCoords_0_Stage0);
475 }
476 half4 MatrixEffect_Stage1_c0_c0(half4 _input)
477 {
478 return TextureEffect_Stage1_c0_c0_c0(_input);
479 }
480 half4 Blend_Stage1_c0(half4 _input)
481 {
482 // Blend mode: Modulate (Compose-One behavior)
483 return blend_modulate(MatrixEffect_Stage1_c0_c0(half4(1)), _input);
484 }
485 void main()
486 {
487 // Stage 0, QuadPerEdgeAAGeometryProcessor
488 half4 outputColor_Stage0 = half4(1);
489 const half4 outputCoverage_Stage0 = half4(1);
490 half4 output_Stage1;
491 output_Stage1 = Blend_Stage1_c0(outputColor_Stage0);
492 {
493 // Xfer Processor: Porter Duff
494 sk_FragColor = output_Stage1 * outputCoverage_Stage0;
495 }
496 }
497 )");
498
499 COMPILER_BENCH(tiny, "void main() { sk_FragColor = half4(1); }");
500
501 #if defined(SK_BUILD_FOR_UNIX)
502
503 #include <malloc.h>
504
505 // These benchmarks aren't timed, they produce memory usage statistics. They run standalone, and
506 // directly add their results to the nanobench log.
RunSkSLMemoryBenchmarks(NanoJSONResultsWriter * log)507 void RunSkSLMemoryBenchmarks(NanoJSONResultsWriter* log) {
508 auto heap_bytes_used = []() { return mallinfo().uordblks; };
509 auto bench = [log](const char* name, int bytes) {
510 log->beginObject(name); // test
511 log->beginObject("meta"); // config
512 log->appendS32("bytes", bytes); // sub_result
513 log->endObject(); // config
514 log->endObject(); // test
515 };
516
517 // Heap used by a default compiler (with no modules loaded)
518 {
519 int before = heap_bytes_used();
520 GrShaderCaps caps;
521 SkSL::Compiler compiler(&caps);
522 int after = heap_bytes_used();
523 bench("sksl_compiler_baseline", after - before);
524 }
525
526 // Heap used by a compiler with the two main GPU modules (fragment + vertex) loaded
527 {
528 int before = heap_bytes_used();
529 GrShaderCaps caps;
530 SkSL::Compiler compiler(&caps);
531 compiler.moduleForProgramKind(SkSL::ProgramKind::kVertex);
532 compiler.moduleForProgramKind(SkSL::ProgramKind::kFragment);
533 int after = heap_bytes_used();
534 bench("sksl_compiler_gpu", after - before);
535 }
536
537 // Heap used by a compiler with the runtime shader, color filter and blending modules loaded
538 {
539 int before = heap_bytes_used();
540 GrShaderCaps caps;
541 SkSL::Compiler compiler(&caps);
542 compiler.moduleForProgramKind(SkSL::ProgramKind::kRuntimeColorFilter);
543 compiler.moduleForProgramKind(SkSL::ProgramKind::kRuntimeShader);
544 compiler.moduleForProgramKind(SkSL::ProgramKind::kRuntimeBlender);
545 int after = heap_bytes_used();
546 bench("sksl_compiler_runtimeeffect", after - before);
547 }
548 }
549
550 #else
551
RunSkSLMemoryBenchmarks(NanoJSONResultsWriter *)552 void RunSkSLMemoryBenchmarks(NanoJSONResultsWriter*) {}
553
554 #endif
555