@@ -2040,6 +2040,9 @@ class CanvasCompareTester {
20402040 const uint32_t * test_row = test_pixels->addr32 (0 , y);
20412041 for (int x = 0 ; x < test_pixels->width (); x++) {
20422042 if (ref_row[x] != test_row[x]) {
2043+ if (should_match) {
2044+ FML_LOG (ERROR) << std::hex << ref_row[x] << " != " << test_row[x];
2045+ }
20432046 pixels_different++;
20442047 }
20452048 }
@@ -3238,5 +3241,175 @@ TEST_F(DisplayListCanvas, DrawShadowDpr) {
32383241 CanvasCompareTester::DefaultTolerance.addBoundsPadding (3 , 3 ));
32393242}
32403243
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+
32413414} // namespace testing
32423415} // namespace flutter
0 commit comments