@@ -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,176 @@ 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, const DlPaint& paint_both,
3304
+ bool same, bool rev_same, const std::string& desc1,
3305
+ const std::string& desc2) {
3306
+ // clang-format on
3307
+ DisplayListBuilder nested_builder;
3308
+ nested_builder.saveLayer (&kTestBounds , &paint1);
3309
+ nested_builder.saveLayer (&kTestBounds , &paint2);
3310
+ render_content (nested_builder);
3311
+ auto nested_surface = env.MakeSurface ();
3312
+ nested_builder.Build ()->RenderTo (nested_surface->canvas ());
3313
+
3314
+ DisplayListBuilder reverse_builder;
3315
+ reverse_builder.saveLayer (&kTestBounds , &paint2);
3316
+ reverse_builder.saveLayer (&kTestBounds , &paint1);
3317
+ render_content (reverse_builder);
3318
+ auto reverse_surface = env.MakeSurface ();
3319
+ reverse_builder.Build ()->RenderTo (reverse_surface->canvas ());
3320
+
3321
+ DisplayListBuilder combined_builder;
3322
+ combined_builder.saveLayer (&kTestBounds , &paint_both);
3323
+ render_content (combined_builder);
3324
+ auto combined_surface = env.MakeSurface ();
3325
+ combined_builder.Build ()->RenderTo (combined_surface->canvas ());
3326
+
3327
+ // Set this boolean to true to test if combinations that are marked
3328
+ // as incompatible actually are compatible despite our predictions.
3329
+ // Some of the combinations that we treat as incompatible actually
3330
+ // are compatible with swapping the order of the operations, but
3331
+ // it would take a bit of new infrastructure to really identify
3332
+ // those combinations. The only hard constraint to test here is
3333
+ // when we claim that they are compatible and they aren't.
3334
+ const bool always = false ;
3335
+
3336
+ if (always || same) {
3337
+ CanvasCompareTester::quickCompareToReference (
3338
+ nested_surface->pixmap (), combined_surface->pixmap (), same,
3339
+ " nested " + desc1 + " then " + desc2);
3340
+ }
3341
+ if (always || rev_same) {
3342
+ CanvasCompareTester::quickCompareToReference (
3343
+ reverse_surface->pixmap (), combined_surface->pixmap (), rev_same,
3344
+ " nested " + desc2 + " then " + desc1);
3345
+ }
3346
+ };
3347
+
3348
+ // CF then Opacity should always work.
3349
+ // The reverse sometimes works.
3350
+ for (size_t cfi = 0 ; cfi < color_filters.size (); cfi++) {
3351
+ auto color_filter = color_filters[cfi];
3352
+ std::string cf_desc = " color filter #" + std::to_string (cfi + 1 );
3353
+ DlPaint nested_paint1 = DlPaint ().setColorFilter (color_filter);
3354
+
3355
+ for (size_t oi = 0 ; oi < opacities.size (); oi++) {
3356
+ SkScalar opacity = opacities[oi];
3357
+ std::string op_desc = " opacity " + std::to_string (opacity);
3358
+ DlPaint nested_paint2 = DlPaint ().setOpacity (opacity);
3359
+
3360
+ DlPaint combined_paint = nested_paint1;
3361
+ combined_paint.setOpacity (opacity);
3362
+
3363
+ bool op_then_cf_works = opacity <= 0.0 || opacity >= 1.0 ||
3364
+ color_filter->can_commute_with_opacity ();
3365
+
3366
+ test_attributes (nested_paint1, nested_paint2, combined_paint, true ,
3367
+ op_then_cf_works, cf_desc, op_desc);
3368
+ }
3369
+ }
3370
+
3371
+ // Opacity then IF should always work.
3372
+ // The reverse can also work for some values of opacity.
3373
+ // The reverse should also theoretically work for some IFs, but we
3374
+ // get some rounding errors that are more than just trivial.
3375
+ for (size_t oi = 0 ; oi < opacities.size (); oi++) {
3376
+ SkScalar opacity = opacities[oi];
3377
+ std::string op_desc = " opacity " + std::to_string (opacity);
3378
+ DlPaint nested_paint1 = DlPaint ().setOpacity (opacity);
3379
+
3380
+ for (size_t ifi = 0 ; ifi < image_filters.size (); ifi++) {
3381
+ auto image_filter = image_filters[ifi];
3382
+ std::string if_desc = " image filter #" + std::to_string (ifi + 1 );
3383
+ DlPaint nested_paint2 = DlPaint ().setImageFilter (image_filter);
3384
+
3385
+ DlPaint combined_paint = nested_paint1;
3386
+ combined_paint.setImageFilter (image_filter);
3387
+
3388
+ bool if_then_op_works = opacity <= 0.0 || opacity >= 1.0 ;
3389
+ test_attributes (nested_paint1, nested_paint2, combined_paint, true ,
3390
+ if_then_op_works, op_desc, if_desc);
3391
+ }
3392
+ }
3393
+
3394
+ // CF then IF should always work.
3395
+ // The reverse might work, but we lack the infrastructure to check it.
3396
+ for (size_t cfi = 0 ; cfi < color_filters.size (); cfi++) {
3397
+ auto color_filter = color_filters[cfi];
3398
+ std::string cf_desc = " color filter #" + std::to_string (cfi + 1 );
3399
+ DlPaint nested_paint1 = DlPaint ().setColorFilter (color_filter);
3400
+
3401
+ for (size_t ifi = 0 ; ifi < image_filters.size (); ifi++) {
3402
+ auto image_filter = image_filters[ifi];
3403
+ std::string if_desc = " image filter #" + std::to_string (ifi + 1 );
3404
+ DlPaint nested_paint2 = DlPaint ().setImageFilter (image_filter);
3405
+
3406
+ DlPaint combined_paint = nested_paint1;
3407
+ combined_paint.setImageFilter (image_filter);
3408
+
3409
+ test_attributes (nested_paint1, nested_paint2, combined_paint, true , false ,
3410
+ cf_desc, if_desc);
3411
+ }
3412
+ }
3413
+ }
3414
+
3241
3415
} // namespace testing
3242
3416
} // namespace flutter
0 commit comments