@@ -2040,6 +2040,9 @@ class CanvasCompareTester {
2040
2040
const uint32_t * test_row = test_pixels->addr32 (0 , y);
2041
2041
for (int x = 0 ; x < test_pixels->width (); x++) {
2042
2042
if (ref_row[x] != test_row[x]) {
2043
+ if (should_match) {
2044
+ FML_LOG (ERROR) << std::hex << ref_row[x] << " != " << test_row[x];
2045
+ }
2043
2046
pixels_different++;
2044
2047
}
2045
2048
}
@@ -3238,5 +3241,175 @@ TEST_F(DisplayListCanvas, DrawShadowDpr) {
3238
3241
CanvasCompareTester::DefaultTolerance.addBoundsPadding (3 , 3 ));
3239
3242
}
3240
3243
3244
+ TEST_F (DisplayListCanvas, SaveLayerConsolidation) {
3245
+ float commutable_color_matrix[]{
3246
+ // clang-format off
3247
+ 0 , 1 , 0 , 0 , 0 ,
3248
+ 0 , 0 , 1 , 0 , 0 ,
3249
+ 1 , 0 , 0 , 0 , 0 ,
3250
+ 0 , 0 , 0 , 1 , 0 ,
3251
+ // clang-format on
3252
+ };
3253
+ float non_commutable_color_matrix[]{
3254
+ // clang-format off
3255
+ 0 , 1 , 0 , .1 , 0 ,
3256
+ 0 , 0 , 1 , .1 , 0 ,
3257
+ 1 , 0 , 0 , .1 , 0 ,
3258
+ 0 , 0 , 0 , .7 , 0 ,
3259
+ // clang-format on
3260
+ };
3261
+ SkMatrix contract_matrix;
3262
+ contract_matrix.setScale (0 .9f , 0 .9f , kRenderCenterX , kRenderCenterY );
3263
+
3264
+ std::vector<SkScalar> opacities = {
3265
+ 0 ,
3266
+ 0 .5f ,
3267
+ SK_Scalar1,
3268
+ };
3269
+ std::vector<std::shared_ptr<DlColorFilter>> color_filters = {
3270
+ std::make_shared<DlBlendColorFilter>(DlColor::kCyan (),
3271
+ DlBlendMode::kSrcATop ),
3272
+ std::make_shared<DlMatrixColorFilter>(commutable_color_matrix),
3273
+ std::make_shared<DlMatrixColorFilter>(non_commutable_color_matrix),
3274
+ DlSrgbToLinearGammaColorFilter::instance,
3275
+ DlLinearToSrgbGammaColorFilter::instance,
3276
+ };
3277
+ std::vector<std::shared_ptr<DlImageFilter>> image_filters = {
3278
+ std::make_shared<DlBlurImageFilter>(5 .0f , 5 .0f , DlTileMode::kDecal ),
3279
+ std::make_shared<DlDilateImageFilter>(5 .0f , 5 .0f ),
3280
+ std::make_shared<DlErodeImageFilter>(5 .0f , 5 .0f ),
3281
+ std::make_shared<DlMatrixImageFilter>(contract_matrix,
3282
+ DlImageSampling::kLinear ),
3283
+ };
3284
+ RenderEnvironment env = RenderEnvironment::MakeN32 ();
3285
+
3286
+ auto render_content = [](DisplayListBuilder& builder) {
3287
+ builder.drawRect (
3288
+ SkRect{kRenderLeft , kRenderTop , kRenderCenterX , kRenderCenterY },
3289
+ DlPaint ().setColor (DlColor::kYellow ()));
3290
+ builder.drawRect (
3291
+ SkRect{kRenderCenterX , kRenderTop , kRenderRight , kRenderCenterY },
3292
+ DlPaint ().setColor (DlColor::kRed ()));
3293
+ builder.drawRect (
3294
+ SkRect{kRenderLeft , kRenderCenterY , kRenderCenterX , kRenderBottom },
3295
+ DlPaint ().setColor (DlColor::kBlue ()));
3296
+ builder.drawRect (
3297
+ SkRect{kRenderCenterX , kRenderCenterY , kRenderRight , kRenderBottom },
3298
+ DlPaint ().setColor (DlColor::kRed ().modulateOpacity (0 .5f )));
3299
+ };
3300
+
3301
+ // clang-format off
3302
+ auto test_attributes = [&env, render_content]
3303
+ (DlPaint& paint1, DlPaint& paint2, DlPaint paint_both,
3304
+ bool same, bool rev_same, std::string desc1, std::string desc2) {
3305
+ // clang-format on
3306
+ DisplayListBuilder nested_builder;
3307
+ nested_builder.saveLayer (&kTestBounds , &paint1);
3308
+ nested_builder.saveLayer (&kTestBounds , &paint2);
3309
+ render_content (nested_builder);
3310
+ auto nested_surface = env.MakeSurface ();
3311
+ nested_builder.Build ()->RenderTo (nested_surface->canvas ());
3312
+
3313
+ DisplayListBuilder reverse_builder;
3314
+ reverse_builder.saveLayer (&kTestBounds , &paint2);
3315
+ reverse_builder.saveLayer (&kTestBounds , &paint1);
3316
+ render_content (reverse_builder);
3317
+ auto reverse_surface = env.MakeSurface ();
3318
+ reverse_builder.Build ()->RenderTo (reverse_surface->canvas ());
3319
+
3320
+ DisplayListBuilder combined_builder;
3321
+ combined_builder.saveLayer (&kTestBounds , &paint_both);
3322
+ render_content (combined_builder);
3323
+ auto combined_surface = env.MakeSurface ();
3324
+ combined_builder.Build ()->RenderTo (combined_surface->canvas ());
3325
+
3326
+ // Set this boolean to true to test if combinations that are marked
3327
+ // as incompatible actually are compatible despite our predictions.
3328
+ // Some of the combinations that we treat as incompatible actually
3329
+ // are compatible with swapping the order of the operations, but
3330
+ // it would take a bit of new infrastructure to really identify
3331
+ // those combinations. The only hard constraint to test here is
3332
+ // when we claim that they are compatible and they aren't.
3333
+ const bool always = false ;
3334
+
3335
+ if (always || same) {
3336
+ CanvasCompareTester::quickCompareToReference (
3337
+ nested_surface->pixmap (), combined_surface->pixmap (), same,
3338
+ " nested " + desc1 + " then " + desc2);
3339
+ }
3340
+ if (always || rev_same) {
3341
+ CanvasCompareTester::quickCompareToReference (
3342
+ reverse_surface->pixmap (), combined_surface->pixmap (), rev_same,
3343
+ " nested " + desc2 + " then " + desc1);
3344
+ }
3345
+ };
3346
+
3347
+ // CF then Opacity should always work.
3348
+ // The reverse sometimes works.
3349
+ for (size_t cfi = 0 ; cfi < color_filters.size (); cfi++) {
3350
+ auto color_filter = color_filters[cfi];
3351
+ std::string cf_desc = " color filter #" + std::to_string (cfi + 1 );
3352
+ DlPaint nested_paint1 = DlPaint ().setColorFilter (color_filter);
3353
+
3354
+ for (size_t oi = 0 ; oi < opacities.size (); oi++) {
3355
+ SkScalar opacity = opacities[oi];
3356
+ std::string op_desc = " opacity " + std::to_string (opacity);
3357
+ DlPaint nested_paint2 = DlPaint ().setOpacity (opacity);
3358
+
3359
+ DlPaint combined_paint = nested_paint1;
3360
+ combined_paint.setOpacity (opacity);
3361
+
3362
+ bool op_then_cf_works = opacity <= 0.0 || opacity >= 1.0 ||
3363
+ color_filter->can_commute_with_opacity ();
3364
+
3365
+ test_attributes (nested_paint1, nested_paint2, combined_paint, true ,
3366
+ op_then_cf_works, cf_desc, op_desc);
3367
+ }
3368
+ }
3369
+
3370
+ // Opacity then IF should always work.
3371
+ // The reverse can also work for some values of opacity.
3372
+ // The reverse should also theoretically work for some IFs, but we
3373
+ // get some rounding errors that are more than just trivial.
3374
+ for (size_t oi = 0 ; oi < opacities.size (); oi++) {
3375
+ SkScalar opacity = opacities[oi];
3376
+ std::string op_desc = " opacity " + std::to_string (opacity);
3377
+ DlPaint nested_paint1 = DlPaint ().setOpacity (opacity);
3378
+
3379
+ for (size_t ifi = 0 ; ifi < image_filters.size (); ifi++) {
3380
+ auto image_filter = image_filters[ifi];
3381
+ std::string if_desc = " image filter #" + std::to_string (ifi + 1 );
3382
+ DlPaint nested_paint2 = DlPaint ().setImageFilter (image_filter);
3383
+
3384
+ DlPaint combined_paint = nested_paint1;
3385
+ combined_paint.setImageFilter (image_filter);
3386
+
3387
+ bool if_then_op_works = opacity <= 0.0 || opacity >= 1.0 ;
3388
+ test_attributes (nested_paint1, nested_paint2, combined_paint, true ,
3389
+ if_then_op_works, op_desc, if_desc);
3390
+ }
3391
+ }
3392
+
3393
+ // CF then IF should always work.
3394
+ // The reverse might work, but we lack the infrastructure to check it.
3395
+ for (size_t cfi = 0 ; cfi < color_filters.size (); cfi++) {
3396
+ auto color_filter = color_filters[cfi];
3397
+ std::string cf_desc = " color filter #" + std::to_string (cfi + 1 );
3398
+ DlPaint nested_paint1 = DlPaint ().setColorFilter (color_filter);
3399
+
3400
+ for (size_t ifi = 0 ; ifi < image_filters.size (); ifi++) {
3401
+ auto image_filter = image_filters[ifi];
3402
+ std::string if_desc = " image filter #" + std::to_string (ifi + 1 );
3403
+ DlPaint nested_paint2 = DlPaint ().setImageFilter (image_filter);
3404
+
3405
+ DlPaint combined_paint = nested_paint1;
3406
+ combined_paint.setImageFilter (image_filter);
3407
+
3408
+ test_attributes (nested_paint1, nested_paint2, combined_paint, true , false ,
3409
+ cf_desc, if_desc);
3410
+ }
3411
+ }
3412
+ }
3413
+
3241
3414
} // namespace testing
3242
3415
} // namespace flutter
0 commit comments