Flutter Engine
The Flutter Engine
CombinationBuilderTest.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2022 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
8#include "tests/Test.h"
9
10#if defined(SK_GRAPHITE)
11
25
26#include <array>
27
28using namespace::skgpu::graphite;
29
30namespace {
31
32// colorfilters
33static constexpr int kExpectedBlendColorFilterCombos = 1;
34
35// shaders
36static constexpr int kExpectedSolidColorCombos = 1;
37static constexpr int kExpectedGradientCombos = 3;
38static constexpr int kExpectedImageCombos = 6;
39static constexpr int kExpectedPerlinNoiseCombos = 1;
40static constexpr int kExpectedPictureCombos = 12;
41static constexpr int kExpectedRawImageCombos = 3;
42
43
44// A default kSrcOver blend mode will be supplied if no other blend options are added
45void no_blend_mode_option_test(const KeyContext& keyContext,
46 PipelineDataGatherer* gatherer,
48 PaintOptions paintOptions;
49 paintOptions.setShaders({ PrecompileShaders::Color() });
50
51 REPORTER_ASSERT(reporter, paintOptions.priv().numCombinations() == 1);
52
53 std::vector<UniquePaintParamsID> precompileIDs;
54 paintOptions.priv().buildCombinations(keyContext,
55 gatherer,
57 /* withPrimitiveBlender= */ false,
59 [&precompileIDs](UniquePaintParamsID id,
61 bool /* withPrimitiveBlender */,
62 Coverage) {
63 precompileIDs.push_back(id);
64 });
65
66 SkASSERT(precompileIDs.size() == 1);
67}
68
69// This test checks that the 'PaintOptions::numCombinations' method and the number actually
70// generated by 'buildCombinations' agree with the expected number of combinations.
71void run_test(const KeyContext& keyContext,
72 PipelineDataGatherer* gatherer,
74 const PaintOptions& paintOptions,
75 int expectedNumOptions) {
76
77 REPORTER_ASSERT(reporter, paintOptions.priv().numCombinations() == expectedNumOptions);
78
79 std::vector<UniquePaintParamsID> precompileIDs;
80 paintOptions.priv().buildCombinations(keyContext,
81 gatherer,
83 /* withPrimitiveBlender= */ false,
85 [&precompileIDs](UniquePaintParamsID id,
87 bool /* withPrimitiveBlender */,
88 Coverage) {
89 precompileIDs.push_back(id);
90 });
91
92 SkASSERT(static_cast<int>(precompileIDs.size()) == expectedNumOptions);
93}
94
95void big_test(const KeyContext& keyContext,
96 PipelineDataGatherer* gatherer,
98
99 static constexpr int kNumExpected = 444;
100 // paintOptions (444 = 4*111)
101 // |- (111 = 3+108) sweepGrad_0 (3) |
102 // | blendShader_0 (108 = 1*4*27)
103 // | |- 0: (1) kSrc (1)
104 // | |- 1: (4=3+1) (dsts) linearGrad_0 (3) | solid_0 (1)
105 // | |- 2: (27=3+24) (srcs) linearGrad_1 (3) |
106 // | blendShader_1 (24=1*4*6)
107 // | |- 0: (1) kDst (1)
108 // | |- 1: (4=3+1) (dsts) radGrad_0 (3) | solid_1 (1)
109 // | |- 2: (6) (srcs) imageShader_0 (6)
110 // |
111 // |- (4) 4-built-in-blend-modes
112
113 PaintOptions paintOptions;
114
115 // first, shaders. First top-level option (sweepGrad_0)
117
118 std::array<SkBlendMode, 1> blendModes{ SkBlendMode::kSrc };
119
120 std::vector<SkBlendMode> moreBlendModes{ SkBlendMode::kDst };
121
122 // Second top-level option (blendShader_0)
123 auto blendShader_0 = PrecompileShaders::Blend(
124 SkSpan<const SkBlendMode>(blendModes), // std::array
125 { // initializer_list
128 },
129 {
132 SkSpan<const SkBlendMode>(moreBlendModes),// std::vector
133 {
136 },
137 {
139 })
140 });
141
142 paintOptions.setShaders({ sweepGrad_0, blendShader_0 });
143
144 static const SkBlendMode kEvenMoreBlendModes[] = {
149 };
150
151 // now, blend modes
152 paintOptions.setBlendModes(kEvenMoreBlendModes); // c array
153
154 REPORTER_ASSERT(reporter, paintOptions.priv().numCombinations() == kNumExpected,
155 "Actual # of combinations %d", paintOptions.priv().numCombinations());
156
157 std::vector<UniquePaintParamsID> precompileIDs;
158 paintOptions.priv().buildCombinations(keyContext,
159 gatherer,
161 /* withPrimitiveBlender= */ false,
163 [&precompileIDs](UniquePaintParamsID id,
165 bool /* withPrimitiveBlender */,
166 Coverage) {
167 precompileIDs.push_back(id);
168 });
169
170 SkASSERT(precompileIDs.size() == kNumExpected);
171}
172
173template <typename T>
174std::vector<sk_sp<T>> create_runtime_combos(
176 SkRuntimeEffect::Result effectFactory(SkString),
177 sk_sp<T> precompileFactory(sk_sp<SkRuntimeEffect>,
179 const char* redCode,
180 const char* greenCode,
181 const char* combineCode) {
182 auto [redEffect, error1] = effectFactory(SkString(redCode));
183 REPORTER_ASSERT(reporter, redEffect, "%s", error1.c_str());
184 auto [greenEffect, error2] = effectFactory(SkString(greenCode));
185 REPORTER_ASSERT(reporter, greenEffect, "%s", error2.c_str());
186 auto [combineEffect, error3] = effectFactory(SkString(combineCode));
187 REPORTER_ASSERT(reporter, combineEffect, "%s", error3.c_str());
188
189 sk_sp<T> red = precompileFactory(redEffect, {});
191
192 sk_sp<T> green = precompileFactory(greenEffect, {});
194
195 sk_sp<T> combine = precompileFactory(combineEffect, { { red, green }, { green, red } });
196 REPORTER_ASSERT(reporter, combine);
197
198 return { combine };
199}
200
201void runtime_effect_test(const KeyContext& keyContext,
202 PipelineDataGatherer* gatherer,
204 // paintOptions (8 = 2*2*2)
205 // |- combineShader (2)
206 // | 0: redShader | greenShader
207 // | 1: greenShader | redShader
208 // |
209 // |- combineColorFilter (2)
210 // | 0: redColorFilter | greenColorFilter
211 // | 1: greenColorFilter | redColorFilter
212 // |
213 // |- combineBlender (2)
214 // | 0: redBlender | greenBlender
215 // | 1: greenBlender | redBlender
216
217 PaintOptions paintOptions;
218
219 // shaders
220 {
221 static const char* kRedS = R"(
222 half4 main(vec2 fragcoord) { return half4(.5, 0, 0, .5); }
223 )";
224 static const char* kGreenS = R"(
225 half4 main(vec2 fragcoord) { return half4(0, .5, 0, .5); }
226 )";
227
228 static const char* kCombineS = R"(
229 uniform shader first;
230 uniform shader second;
231 half4 main(vec2 fragcoords) {
232 return first.eval(fragcoords) + second.eval(fragcoords);
233 }
234 )";
235
236 std::vector<sk_sp<PrecompileShader>> combinations =
237 create_runtime_combos<PrecompileShader>(reporter,
240 kRedS,
241 kGreenS,
242 kCombineS);
243 paintOptions.setShaders(combinations);
244 }
245
246 // color filters
247 {
248 static const char* kRedCF = R"(
249 half4 main(half4 color) { return half4(.5, 0, 0, .5); }
250 )";
251 static const char* kGreenCF = R"(
252 half4 main(half4 color) { return half4(0, .5, 0, .5); }
253 )";
254
255 static const char* kCombineCF = R"(
256 uniform colorFilter first;
257 uniform colorFilter second;
258 half4 main(half4 color) { return first.eval(color) + second.eval(color); }
259 )";
260
261 std::vector<sk_sp<PrecompileColorFilter>> combinations =
262 create_runtime_combos<PrecompileColorFilter>(reporter,
265 kRedCF,
266 kGreenCF,
267 kCombineCF);
268 paintOptions.setColorFilters(combinations);
269 }
270
271 // blenders
272 {
273 static const char* kRedB = R"(
274 half4 main(half4 src, half4 dst) { return half4(.5, 0, 0, .5); }
275 )";
276 static const char* kGreenB = R"(
277 half4 main(half4 src, half4 dst) { return half4(0, .5, 0, .5); }
278 )";
279
280 static const char* kCombineB = R"(
281 uniform blender first;
282 uniform blender second;
283 half4 main(half4 src, half4 dst) {
284 return first.eval(src, dst) + second.eval(src, dst);
285 }
286 )";
287
288 std::vector<sk_sp<PrecompileBlender>> combinations =
289 create_runtime_combos<PrecompileBlender>(reporter,
292 kRedB,
293 kGreenB,
294 kCombineB);
295 paintOptions.setBlenders(combinations);
296 }
297
298 REPORTER_ASSERT(reporter, paintOptions.priv().numCombinations() == 8);
299
300 std::vector<UniquePaintParamsID> precompileIDs;
301 paintOptions.priv().buildCombinations(keyContext,
302 gatherer,
304 /* withPrimitiveBlender= */ false,
306 [&precompileIDs](UniquePaintParamsID id,
308 bool /* withPrimitiveBlender */,
309 Coverage) {
310 precompileIDs.push_back(id);
311 });
312
313 SkASSERT(precompileIDs.size() == 8);
314}
315
316// Exercise all the PrecompileBlenders factories
317void blend_subtest(const KeyContext& keyContext,
318 PipelineDataGatherer* gatherer,
320 // The BlendMode PrecompileBlender only ever has 1 combination
321 {
322 PaintOptions paintOptions;
323 paintOptions.setBlenders({ PrecompileBlenders::Mode(SkBlendMode::kColorDodge) });
324
325 run_test(keyContext, gatherer, reporter, paintOptions, /* expectedNumOptions= */ 1);
326 }
327
328 // Specifying the BlendMode PrecompileBlender by SkBlendMode should also only ever
329 // yield 1 combination.
330 {
331 PaintOptions paintOptions;
332 paintOptions.setBlendModes({ SkBlendMode::kSrcOver });
333
334 run_test(keyContext, gatherer, reporter, paintOptions, /* expectedNumOptions= */ 1);
335 }
336
337 // The Arithmetic PrecompileBlender only ever has 1 combination
338 {
339 PaintOptions paintOptions;
340 paintOptions.setBlenders({ PrecompileBlenders::Arithmetic() });
341
342 run_test(keyContext, gatherer, reporter, paintOptions, /* expectedNumOptions= */ 1);
343 }
344}
345
346// Exercise all the PrecompileShaders factories
347void shader_subtest(const KeyContext& keyContext,
348 PipelineDataGatherer* gatherer,
350 {
351 PaintOptions paintOptions;
352 paintOptions.setShaders({ PrecompileShaders::Empty() });
353
354 run_test(keyContext, gatherer, reporter, paintOptions, /* expectedNumOptions= */ 1);
355 }
356
357 // The solid color shader only ever generates one combination. Because it is constant
358 // everywhere it can cause other shaders to be elided (e.g., the LocalMatrix shader -
359 // see the LocalMatrix test(s) below).
360 {
361 PaintOptions paintOptions;
362 paintOptions.setShaders({ PrecompileShaders::Color() });
363
364 run_test(keyContext, gatherer, reporter, paintOptions,
365 /* expectedNumOptions= */ kExpectedSolidColorCombos);
366 }
367
368 // In general, the blend shader generates the product of the options in each of its slots.
369 // The rules for how many combinations the SkBlendModes yield are:
370 // all Porter-Duff SkBlendModes collapse to one option (see SkBlendMode::kLastCoeffMode)
371 // all non-Porter-Duff SkBlendModes collapse to a second option
372 {
373 const SkBlendMode kBlendModes[] = {
374 SkBlendMode::kScreen, // Porter-Duff
375 SkBlendMode::kSrcOut, // Porter-Duff
376 SkBlendMode::kDarken, // non-Porter-Duff
377 SkBlendMode::kHardLight, // non-Porter-Duff
378 };
379 PaintOptions paintOptions;
380 paintOptions.setShaders(
384
385 run_test(keyContext, gatherer, reporter, paintOptions,
386 /* expectedNumOptions= */ 2 * // both Porter-Duff and non-Porter-Duff
387 kExpectedSolidColorCombos *
388 kExpectedPerlinNoiseCombos);
389 }
390
391 // The ImageShaders have 6 combinations (3 sampling/tiling x 2 alpha/non-alpha)
392 // The CoordClamp shader doesn't add any additional combinations to its wrapped shader.
393 {
394 PaintOptions paintOptions;
395 paintOptions.setShaders({ PrecompileShaders::CoordClamp({ PrecompileShaders::Image() }) });
396
397 run_test(keyContext, gatherer, reporter, paintOptions,
398 /* expectedNumOptions= */ kExpectedImageCombos);
399 }
400
401 // RawImageShaders only have 3 combinations (since they never incorporate alpha)
402 {
403 PaintOptions paintOptions;
404 paintOptions.setShaders({ PrecompileShaders::RawImage() });
405
406 run_test(keyContext, gatherer, reporter, paintOptions,
407 /* expectedNumOptions= */ kExpectedRawImageCombos);
408 }
409
410 // Each Perlin noise shader only has one combination
411 {
412 PaintOptions paintOptions;
413 paintOptions.setShaders({ PrecompileShaders::MakeFractalNoise(),
415
416 run_test(keyContext, gatherer, reporter, paintOptions,
417 /* expectedNumOptions= */ kExpectedPerlinNoiseCombos + kExpectedPerlinNoiseCombos);
418 }
419
420 // Each gradient shader generates 3 combinations
421 {
422 PaintOptions paintOptions;
423 paintOptions.setShaders({ PrecompileShaders::LinearGradient(),
427
428 run_test(keyContext, gatherer, reporter, paintOptions,
429 /* expectedNumOptions= */ kExpectedGradientCombos + kExpectedGradientCombos +
430 kExpectedGradientCombos + kExpectedGradientCombos);
431 }
432
433 // Each picture shader generates 12 combinations:
434 // 2 (pictureShader LM) x 6 (imageShader variations)
435 {
436 PaintOptions paintOptions;
437 paintOptions.setShaders({ PrecompileShaders::Picture() });
438
439 run_test(keyContext, gatherer, reporter, paintOptions,
440 /* expectedNumOptions= */ kExpectedPictureCombos);
441 }
442
443 // In general, the local matrix shader just generates however many options its wrapped
444 // shader generates.
445 {
446 PaintOptions paintOptions;
447 paintOptions.setShaders(
449
450 run_test(keyContext, gatherer, reporter, paintOptions,
451 /* expectedNumOptions= */ kExpectedGradientCombos);
452 }
453
454 // The ColorFilter shader just creates the cross product of its child options
455 {
456 PaintOptions paintOptions;
457 paintOptions.setShaders(
460
461 run_test(keyContext, gatherer, reporter, paintOptions,
462 /* expectedNumOptions= */ kExpectedGradientCombos *
463 kExpectedBlendColorFilterCombos);
464 }
465
466 {
467 PaintOptions paintOptions;
468 paintOptions.setShaders(
471
472 run_test(keyContext, gatherer, reporter, paintOptions,
473 /* expectedNumOptions= */ kExpectedGradientCombos *
474 1 /* only one colorSpace */);
475 }
476}
477
478} // anonymous namespace
479
480DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(CombinationBuilderTest, reporter, context,
482 ShaderCodeDictionary* dict = context->priv().shaderCodeDictionary();
483
484 auto rtEffectDict = std::make_unique<RuntimeEffectDictionary>();
485
487 KeyContext keyContext(context->priv().caps(),
488 dict,
489 rtEffectDict.get(),
490 ci,
491 /* dstTexture= */ nullptr,
492 /* dstOffset= */ {0, 0});
493
494 PipelineDataGatherer gatherer(Layout::kMetal);
495
496 // The default PaintOptions should create a single combination with a solid color shader and
497 // kSrcOver blending
498 {
499 PaintOptions paintOptions;
500
501 run_test(keyContext, &gatherer, reporter, paintOptions, /* expectedNumOptions= */ 1);
502 }
503
504 blend_subtest(keyContext, &gatherer, reporter);
505 shader_subtest(keyContext, &gatherer, reporter);
506
507 no_blend_mode_option_test(keyContext, &gatherer, reporter);
508 big_test(keyContext, &gatherer, reporter);
509 runtime_effect_test(keyContext, &gatherer, reporter);
510}
511
512#endif // SK_GRAPHITE
static void run_test(GrDirectContext *dContext, skiatest::Reporter *reporter, BulkRectTest test)
reporter
Definition: FontMgrTest.cpp:39
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition: SkAlphaType.h:29
#define SkASSERT(cond)
Definition: SkAssert.h:116
SkBlendMode
Definition: SkBlendMode.h:38
@ kSrcOut
r = s * (1-da)
@ kColorDodge
brighten destination to reflect source
@ kScreen
r = s + d - s*d
@ kSrcOver
r = s + (1-sa)*d
@ kDstOver
r = d + (1-da)*s
@ kHardLight
multiply or screen, depending on source
@ kDarken
rc = s + d - max(s*da, d*sa), ra = kSrcOver
@ kRGBA_8888_SkColorType
pixel with 8 bits for red, green, blue, alpha; in 32-bit word
Definition: SkColorType.h:24
#define DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(name, reporter, graphite_ctx, ctsEnforcement)
Definition: Test.h:373
#define REPORTER_ASSERT(r, cond,...)
Definition: Test.h:286
static sk_sp< SkColorSpace > MakeSRGBLinear()
static Result MakeForColorFilter(SkString sksl, const Options &)
static Result MakeForBlender(SkString sksl, const Options &)
static Result MakeForShader(SkString sksl, const Options &)
@ kMetal
Definition: embedder.h:85
SK_API sk_sp< SkShader > Color(SkColor)
SK_API sk_sp< SkShader > MakeTurbulence(SkScalar baseFrequencyX, SkScalar baseFrequencyY, int numOctaves, SkScalar seed, const SkISize *tileSize=nullptr)
SK_API sk_sp< SkShader > CoordClamp(sk_sp< SkShader >, const SkRect &subset)
SK_API sk_sp< SkShader > RawImage(sk_sp< SkImage > image, SkTileMode tmx, SkTileMode tmy, const SkSamplingOptions &options, const SkMatrix *localMatrix=nullptr)
SK_API sk_sp< SkShader > MakeFractalNoise(SkScalar baseFrequencyX, SkScalar baseFrequencyY, int numOctaves, SkScalar seed, const SkISize *tileSize=nullptr)
SK_API sk_sp< SkShader > Empty()
@ kNone
Definition: layer.h:53
CanvasImage Image
Definition: dart_ui.cc:55
SK_API sk_sp< PrecompileBlender > Arithmetic()
SK_API sk_sp< PrecompileShader > ColorFilter(SkSpan< const sk_sp< PrecompileShader > > shaders, SkSpan< const sk_sp< PrecompileColorFilter > > colorFilters)
SK_API sk_sp< PrecompileShader > LocalMatrix(SkSpan< const sk_sp< PrecompileShader > > wrapped)
SK_API sk_sp< PrecompileShader > WorkingColorSpace(SkSpan< const sk_sp< PrecompileShader > > shaders, SkSpan< const sk_sp< SkColorSpace > > colorSpaces)
SK_API sk_sp< PrecompileShader > LinearGradient()
SK_API sk_sp< PrecompileShader > RadialGradient()
SK_API sk_sp< PrecompileShader > Picture()
SK_API sk_sp< PrecompileShader > SweepGradient()
SK_API sk_sp< PrecompileShader > TwoPointConicalGradient()
sk_sp< PrecompileBlender > MakePrecompileBlender(sk_sp< SkRuntimeEffect > effect, SkSpan< const PrecompileChildOptions > childOptions)
sk_sp< PrecompileShader > MakePrecompileShader(sk_sp< SkRuntimeEffect > effect, SkSpan< const PrecompileChildOptions > childOptions)
sk_sp< PrecompileColorFilter > MakePrecompileColorFilter(sk_sp< SkRuntimeEffect > effect, SkSpan< const PrecompileChildOptions > childOptions)