diff --git a/engine/src/flutter/impeller/display_list/canvas.cc b/engine/src/flutter/impeller/display_list/canvas.cc index 0f29cab864..133e8c5f03 100644 --- a/engine/src/flutter/impeller/display_list/canvas.cc +++ b/engine/src/flutter/impeller/display_list/canvas.cc @@ -1017,12 +1017,19 @@ void Canvas::SaveLayer(const Paint& paint, // sampling mode. ISize subpass_size; bool did_round_out = false; + Point coverage_origin_adjustment = Point{0, 0}; if (paint.image_filter) { subpass_size = ISize(subpass_coverage.GetSize()); } else { did_round_out = true; subpass_size = static_cast(IRect::RoundOut(subpass_coverage).GetSize()); + // If rounding out, adjust the coverage to account for the subpixel shift. + coverage_origin_adjustment = + Point(subpass_coverage.GetLeftTop().x - + std::floor(subpass_coverage.GetLeftTop().x), + subpass_coverage.GetLeftTop().y - + std::floor(subpass_coverage.GetLeftTop().y)); } if (subpass_size.IsEmpty()) { return SkipUntilMatchingRestore(total_content_depth); @@ -1156,7 +1163,8 @@ void Canvas::SaveLayer(const Paint& paint, subpass_size, // Color::BlackTransparent() // ))); - save_layer_state_.push_back(SaveLayerState{paint_copy, subpass_coverage}); + save_layer_state_.push_back(SaveLayerState{ + paint_copy, subpass_coverage.Shift(-coverage_origin_adjustment)}); CanvasStackEntry entry; entry.transform = transform_stack_.back().transform; diff --git a/engine/src/flutter/impeller/display_list/dl_golden_unittests.cc b/engine/src/flutter/impeller/display_list/dl_golden_unittests.cc index 377f77f12c..8dc953d21b 100644 --- a/engine/src/flutter/impeller/display_list/dl_golden_unittests.cc +++ b/engine/src/flutter/impeller/display_list/dl_golden_unittests.cc @@ -4,6 +4,9 @@ #include "impeller/display_list/dl_golden_unittests.h" +#include "display_list/dl_color.h" +#include "display_list/dl_paint.h" +#include "display_list/geometry/dl_geometry_types.h" #include "flutter/display_list/dl_builder.h" #include "flutter/impeller/geometry/path_builder.h" #include "flutter/testing/testing.h" @@ -307,5 +310,30 @@ TEST_P(DlGoldenTest, DashedLinesTest) { ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); } +TEST_P(DlGoldenTest, SaveLayerAtFractionalValue) { + // Draws a stroked rounded rect at a fractional pixel value. The coverage must + // be adjusted so that we still have room to draw it, even though it lies on + // the fractional bounds of the saveLayer. + DisplayListBuilder builder; + builder.DrawPaint(DlPaint().setColor(DlColor::kWhite())); + auto save_paint = DlPaint().setAlpha(100); + builder.SaveLayer(nullptr, &save_paint); + + builder.DrawRoundRect(DlRoundRect::MakeRectRadius( + DlRect::MakeLTRB(10.5, 10.5, 200.5, 200.5), 10), + DlPaint() + .setDrawStyle(DlDrawStyle::kStroke) + .setStrokeWidth(1.5) + .setColor(DlColor::kBlack())); + builder.DrawCircle(DlPoint::MakeXY(100, 100), 50.5, + DlPaint().setColor(DlColor::kAqua())); + builder.DrawCircle(DlPoint::MakeXY(110, 110), 50.5, + DlPaint().setColor(DlColor::kCyan())); + + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + } // namespace testing } // namespace flutter diff --git a/engine/src/flutter/testing/impeller_golden_tests_output.txt b/engine/src/flutter/testing/impeller_golden_tests_output.txt index 0ac59638b0..431408e7c1 100644 --- a/engine/src/flutter/testing/impeller_golden_tests_output.txt +++ b/engine/src/flutter/testing/impeller_golden_tests_output.txt @@ -981,6 +981,9 @@ impeller_Play_DlGoldenTest_GaussianVsRRectBlurScaled_Vulkan.png impeller_Play_DlGoldenTest_GaussianVsRRectBlur_Metal.png impeller_Play_DlGoldenTest_GaussianVsRRectBlur_OpenGLES.png impeller_Play_DlGoldenTest_GaussianVsRRectBlur_Vulkan.png +impeller_Play_DlGoldenTest_SaveLayerAtFractionalValue_Metal.png +impeller_Play_DlGoldenTest_SaveLayerAtFractionalValue_OpenGLES.png +impeller_Play_DlGoldenTest_SaveLayerAtFractionalValue_Vulkan.png impeller_Play_DlGoldenTest_StrokedRRectFastBlur_Metal.png impeller_Play_DlGoldenTest_StrokedRRectFastBlur_OpenGLES.png impeller_Play_DlGoldenTest_StrokedRRectFastBlur_Vulkan.png