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(std::string name,const char * src,bool optimize,Output output)56 SkSLCompileBench(std::string name, const char* src, bool optimize, Output output)
57 : fName(std::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 std::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 std::string fName;
100 std::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 uniform float3x3 umatrix_S1_c0;
122 uniform half4 uthresholds_S1_c1_c0_c0[1];
123 uniform float4 uscale_S1_c1_c0_c0[4];
124 uniform float4 ubias_S1_c1_c0_c0[4];
125 uniform half uinvR1_S1_c1_c0_c1_c0;
126 uniform half ufx_S1_c1_c0_c1_c0;
127 uniform float3x3 umatrix_S1_c1_c0_c1;
128 uniform half4 uleftBorderColor_S1_c1_c0;
129 uniform half4 urightBorderColor_S1_c1_c0;
130 uniform half urange_S1;
131 uniform sampler2D uTextureSampler_0_S1;
132 flat in half4 vcolor_S0;
133 noperspective in float2 vTransformedCoords_8_S0;
134 out half4 sk_FragColor;
135 half4 TextureEffect_S1_c0_c0(half4 _input, float2 _coords)
136 {
137 return sample(uTextureSampler_0_S1, _coords).000r;
138 }
139 half4 MatrixEffect_S1_c0(half4 _input, float2 _coords)
140 {
141 return TextureEffect_S1_c0_c0(_input, float3x2(umatrix_S1_c0) * _coords.xy1);
142 }
143 half4 LoopingBinaryColorizer_S1_c1_c0_c0(half4 _input, float2 _coords)
144 {
145 half4 _tmp_0_inColor = _input;
146 float2 _tmp_1_coords = _coords;
147 half t = half(_tmp_1_coords.x);
148 ;
149 ;
150 int chunk = 0;
151 ;
152 int pos;
153 if (t < uthresholds_S1_c1_c0_c0[chunk].y)
154 {
155 pos = int(t < uthresholds_S1_c1_c0_c0[chunk].x ? 0 : 1);
156 }
157 else
158 {
159 pos = int(t < uthresholds_S1_c1_c0_c0[chunk].z ? 2 : 3);
160 }
161 ;
162 return half4(half4(float(t) * uscale_S1_c1_c0_c0[pos] + ubias_S1_c1_c0_c0[pos]));
163 }
164 half4 TwoPointConicalFocalLayout_S1_c1_c0_c1_c0(half4 _input)
165 {
166 half4 _tmp_2_inColor = _input;
167 float2 _tmp_3_coords = vTransformedCoords_8_S0;
168 float t = -1.0;
169 half v = 1.0;
170 float x_t = -1.0;
171 if (bool(int(0)))
172 {
173 x_t = dot(_tmp_3_coords, _tmp_3_coords) / _tmp_3_coords.x;
174 }
175 else if (bool(int(0)))
176 {
177 x_t = length(_tmp_3_coords) - _tmp_3_coords.x * float(uinvR1_S1_c1_c0_c1_c0);
178 }
179 else
180 {
181 float temp = _tmp_3_coords.x * _tmp_3_coords.x - _tmp_3_coords.y * _tmp_3_coords.y;
182 if (temp >= 0.0)
183 {
184 if (bool(int(0)) || !bool(int(1)))
185 {
186 x_t = -sqrt(temp) - _tmp_3_coords.x * float(uinvR1_S1_c1_c0_c1_c0);
187 }
188 else
189 {
190 x_t = sqrt(temp) - _tmp_3_coords.x * float(uinvR1_S1_c1_c0_c1_c0);
191 }
192 }
193 }
194 if (!bool(int(0)))
195 {
196 if (x_t <= 0.0)
197 {
198 v = -1.0;
199 }
200 }
201 if (bool(int(1)))
202 {
203 if (bool(int(0)))
204 {
205 t = x_t;
206 }
207 else
208 {
209 t = x_t + float(ufx_S1_c1_c0_c1_c0);
210 }
211 }
212 else
213 {
214 if (bool(int(0)))
215 {
216 t = -x_t;
217 }
218 else
219 {
220 t = -x_t + float(ufx_S1_c1_c0_c1_c0);
221 }
222 }
223 if (bool(int(0)))
224 {
225 t = 1.0 - t;
226 }
227 return half4(half4(half(t), v, 0.0, 0.0));
228 }
229 half4 MatrixEffect_S1_c1_c0_c1(half4 _input)
230 {
231 return TwoPointConicalFocalLayout_S1_c1_c0_c1_c0(_input);
232 }
233 half4 ClampedGradient_S1_c1_c0(half4 _input)
234 {
235 half4 _tmp_4_inColor = _input;
236 half4 t = MatrixEffect_S1_c1_c0_c1(_tmp_4_inColor);
237 half4 outColor;
238 if (!bool(int(0)) && t.y < 0.0)
239 {
240 outColor = half4(0.0);
241 }
242 else if (t.x < 0.0)
243 {
244 outColor = uleftBorderColor_S1_c1_c0;
245 }
246 else if (t.x > 1.0)
247 {
248 outColor = urightBorderColor_S1_c1_c0;
249 }
250 else
251 {
252 outColor = LoopingBinaryColorizer_S1_c1_c0_c0(_tmp_4_inColor, float2(half2(t.x, 0.0)));
253 }
254 if (bool(int(0)))
255 {
256 outColor.xyz *= outColor.w;
257 }
258 return half4(outColor);
259 }
260 half4 DisableCoverageAsAlpha_S1_c1(half4 _input)
261 {
262 _input = ClampedGradient_S1_c1_c0(_input);
263 half4 _tmp_5_inColor = _input;
264 return half4(_input);
265 }
266 half4 Dither_S1(half4 _input)
267 {
268 _input = DisableCoverageAsAlpha_S1_c1(_input);
269 half4 _tmp_6_inColor = _input;
270 half value = MatrixEffect_S1_c0(_tmp_6_inColor, sk_FragCoord.xy).w - 0.5;
271 return half4(half4(clamp(_input.xyz + value * urange_S1, 0.0, _input.w), _input.w));
272 }
273 void main()
274 {
275 // Stage 0, QuadPerEdgeAAGeometryProcessor
276 half4 outputColor_S0;
277 outputColor_S0 = vcolor_S0;
278 const half4 outputCoverage_S0 = half4(1);
279 half4 output_S1;
280 output_S1 = Dither_S1(outputColor_S0);
281 {
282 // Xfer Processor: Porter Duff
283 sk_FragColor = output_S1 * outputCoverage_S0;
284 }
285 }
286 )");
287
288 // This fragment shader is taken from GM_BlurDrawImage.
289 COMPILER_BENCH(medium, R"(
290 uniform float3x3 umatrix_S1_c0;
291 uniform float3x3 umatrix_S2_c0_c0;
292 uniform float4 urect_S2_c0;
293 uniform sampler2D uTextureSampler_0_S1;
294 uniform sampler2D uTextureSampler_0_S2;
295 flat in half4 vcolor_S0;
296 noperspective in float2 vTransformedCoords_3_S0;
297 out half4 sk_FragColor;
298 half4 TextureEffect_S1_c0_c0(half4 _input)
299 {
300 return sample(uTextureSampler_0_S1, vTransformedCoords_3_S0);
301 }
302 half4 MatrixEffect_S1_c0(half4 _input)
303 {
304 return TextureEffect_S1_c0_c0(_input);
305 }
306 half4 DisableCoverageAsAlpha_S1(half4 _input)
307 {
308 _input = MatrixEffect_S1_c0(_input);
309 half4 _tmp_0_inColor = _input;
310 return half4(_input);
311 }
312 half4 TextureEffect_S2_c0_c0_c0(half4 _input, float2 _coords)
313 {
314 return sample(uTextureSampler_0_S2, _coords).000r;
315 }
316 half4 MatrixEffect_S2_c0_c0(half4 _input, float2 _coords)
317 {
318 return TextureEffect_S2_c0_c0_c0(_input, float3x2(umatrix_S2_c0_c0) * _coords.xy1);
319 }
320 half4 RectBlur_S2_c0(half4 _input, float2 _coords)
321 {
322 half4 _tmp_1_inColor = _input;
323 float2 _tmp_2_coords = _coords;
324 half xCoverage;
325 half yCoverage;
326 if (bool(int(1)))
327 {
328 half2 xy = max(half2(urect_S2_c0.xy - _tmp_2_coords), half2(_tmp_2_coords - urect_S2_c0.zw));
329 xCoverage = MatrixEffect_S2_c0_c0(_tmp_1_inColor, float2(half2(xy.x, 0.5))).w;
330 yCoverage = MatrixEffect_S2_c0_c0(_tmp_1_inColor, float2(half2(xy.y, 0.5))).w;
331 }
332 else
333 {
334 half4 rect = half4(half2(urect_S2_c0.xy - _tmp_2_coords), half2(_tmp_2_coords - urect_S2_c0.zw));
335 xCoverage = (1.0 - MatrixEffect_S2_c0_c0(_tmp_1_inColor, float2(half2(rect.x, 0.5))).w) - MatrixEffect_S2_c0_c0(_tmp_1_inColor, float2(half2(rect.z, 0.5))).w;
336 yCoverage = (1.0 - MatrixEffect_S2_c0_c0(_tmp_1_inColor, float2(half2(rect.y, 0.5))).w) - MatrixEffect_S2_c0_c0(_tmp_1_inColor, float2(half2(rect.w, 0.5))).w;
337 }
338 return half4((_input * xCoverage) * yCoverage);
339 }
340 half4 DeviceSpace_S2(half4 _input)
341 {
342 return RectBlur_S2_c0(_input, sk_FragCoord.xy);
343 }
344 void main()
345 {
346 // Stage 0, QuadPerEdgeAAGeometryProcessor
347 half4 outputColor_S0;
348 outputColor_S0 = vcolor_S0;
349 const half4 outputCoverage_S0 = half4(1);
350 half4 output_S1;
351 output_S1 = DisableCoverageAsAlpha_S1(outputColor_S0);
352 half4 output_S2;
353 output_S2 = DeviceSpace_S2(outputCoverage_S0);
354 {
355 // Xfer Processor: Porter Duff
356 sk_FragColor = output_S1 * output_S2;
357 }
358 }
359 )");
360
361 // This is the fragment shader used to blit the Viewer window when running the software rasterizer.
362 COMPILER_BENCH(small, R"(
363 uniform float3x3 umatrix_S1_c0;
364 uniform sampler2D uTextureSampler_0_S1;
365 flat in half4 vcolor_S0;
366 noperspective in float2 vTransformedCoords_3_S0;
367 out half4 sk_FragColor;
368 half4 TextureEffect_S1_c0_c0(half4 _input)
369 {
370 return sample(uTextureSampler_0_S1, vTransformedCoords_3_S0);
371 }
372 half4 MatrixEffect_S1_c0(half4 _input)
373 {
374 return TextureEffect_S1_c0_c0(_input);
375 }
376 half4 DisableCoverageAsAlpha_S1(half4 _input)
377 {
378 _input = MatrixEffect_S1_c0(_input);
379 half4 _tmp_0_inColor = _input;
380 return half4(_input);
381 }
382 void main()
383 {
384 // Stage 0, QuadPerEdgeAAGeometryProcessor
385 half4 outputColor_S0;
386 outputColor_S0 = vcolor_S0;
387 const half4 outputCoverage_S0 = half4(1);
388 half4 output_S1;
389 output_S1 = DisableCoverageAsAlpha_S1(outputColor_S0);
390 {
391 // Xfer Processor: Porter Duff
392 sk_FragColor = output_S1 * outputCoverage_S0;
393 }
394 }
395 )");
396
397 COMPILER_BENCH(tiny, "void main() { sk_FragColor = half4(1); }");
398
399 #if defined(SK_BUILD_FOR_UNIX)
400
401 #include <malloc.h>
402
403 // These benchmarks aren't timed, they produce memory usage statistics. They run standalone, and
404 // directly add their results to the nanobench log.
RunSkSLMemoryBenchmarks(NanoJSONResultsWriter * log)405 void RunSkSLMemoryBenchmarks(NanoJSONResultsWriter* log) {
406 auto heap_bytes_used = []() { return mallinfo().uordblks; };
407 auto bench = [log](const char* name, int bytes) {
408 log->beginObject(name); // test
409 log->beginObject("meta"); // config
410 log->appendS32("bytes", bytes); // sub_result
411 log->endObject(); // config
412 log->endObject(); // test
413 };
414
415 // Heap used by a default compiler (with no modules loaded)
416 {
417 int before = heap_bytes_used();
418 GrShaderCaps caps;
419 SkSL::Compiler compiler(&caps);
420 int after = heap_bytes_used();
421 bench("sksl_compiler_baseline", after - before);
422 }
423
424 // Heap used by a compiler with the two main GPU modules (fragment + vertex) loaded
425 {
426 int before = heap_bytes_used();
427 GrShaderCaps caps;
428 SkSL::Compiler compiler(&caps);
429 compiler.moduleForProgramKind(SkSL::ProgramKind::kVertex);
430 compiler.moduleForProgramKind(SkSL::ProgramKind::kFragment);
431 int after = heap_bytes_used();
432 bench("sksl_compiler_gpu", after - before);
433 }
434
435 // Heap used by a compiler with the runtime shader, color filter and blending modules loaded
436 {
437 int before = heap_bytes_used();
438 GrShaderCaps caps;
439 SkSL::Compiler compiler(&caps);
440 compiler.moduleForProgramKind(SkSL::ProgramKind::kRuntimeColorFilter);
441 compiler.moduleForProgramKind(SkSL::ProgramKind::kRuntimeShader);
442 compiler.moduleForProgramKind(SkSL::ProgramKind::kRuntimeBlender);
443 int after = heap_bytes_used();
444 bench("sksl_compiler_runtimeeffect", after - before);
445 }
446 }
447
448 #else
449
RunSkSLMemoryBenchmarks(NanoJSONResultsWriter *)450 void RunSkSLMemoryBenchmarks(NanoJSONResultsWriter*) {}
451
452 #endif
453