From 98e1c7de6d376922c74763d1811d64063e8ab7df Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Sat, 10 Aug 2024 09:49:19 -0700 Subject: [PATCH] [Impeller] migrate blend tests to DL. (#54457) Part of https://github.com/flutter/flutter/issues/142054 --- ci/licenses_golden/excluded_files | 1 + display_list/effects/dl_image_filter.h | 6 +- impeller/aiks/aiks_blend_unittests.cc | 413 ------------- impeller/aiks/aiks_unittests.cc | 307 ---------- impeller/display_list/BUILD.gn | 1 + .../display_list/aiks_dl_blend_unittests.cc | 570 ++++++++++++++++++ impeller/display_list/aiks_dl_unittests.cc | 333 ++++++++++ impeller/display_list/dl_dispatcher.cc | 2 +- impeller/renderer/backend/metal/surface_mtl.h | 4 +- .../renderer/backend/metal/surface_mtl.mm | 6 +- testing/impeller_golden_tests_output.txt | 3 + 11 files changed, 918 insertions(+), 728 deletions(-) create mode 100644 impeller/display_list/aiks_dl_blend_unittests.cc diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index 06e681b694e91..5d5333443dfcc 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -144,6 +144,7 @@ ../../../flutter/impeller/core/allocator_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_atlas_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_basic_unittests.cc +../../../flutter/impeller/display_list/aiks_dl_blend_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_clip_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_gradient_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_opacity_unittests.cc diff --git a/display_list/effects/dl_image_filter.h b/display_list/effects/dl_image_filter.h index bcb89fd69d887..f98010f56f0bc 100644 --- a/display_list/effects/dl_image_filter.h +++ b/display_list/effects/dl_image_filter.h @@ -665,7 +665,7 @@ class DlColorFilterImageFilter final : public DlImageFilter { class DlLocalMatrixImageFilter final : public DlImageFilter { public: explicit DlLocalMatrixImageFilter(const SkMatrix& matrix, - std::shared_ptr filter) + std::shared_ptr filter) : matrix_(matrix), image_filter_(std::move(filter)) {} explicit DlLocalMatrixImageFilter(const DlLocalMatrixImageFilter* filter) : DlLocalMatrixImageFilter(filter->matrix_, filter->image_filter_) {} @@ -682,7 +682,7 @@ class DlLocalMatrixImageFilter final : public DlImageFilter { const SkMatrix& matrix() const { return matrix_; } - const std::shared_ptr image_filter() const { + const std::shared_ptr image_filter() const { return image_filter_; } @@ -738,7 +738,7 @@ class DlLocalMatrixImageFilter final : public DlImageFilter { private: SkMatrix matrix_; - std::shared_ptr image_filter_; + std::shared_ptr image_filter_; }; } // namespace flutter diff --git a/impeller/aiks/aiks_blend_unittests.cc b/impeller/aiks/aiks_blend_unittests.cc index ebf129851d298..d731c642edd19 100644 --- a/impeller/aiks/aiks_blend_unittests.cc +++ b/impeller/aiks/aiks_blend_unittests.cc @@ -18,166 +18,6 @@ namespace impeller { namespace testing { -TEST_P(AiksTest, CanRenderAdvancedBlendColorFilterWithSaveLayer) { - Canvas canvas; - - Rect layer_rect = Rect::MakeXYWH(0, 0, 500, 500); - canvas.ClipRect(layer_rect); - - canvas.SaveLayer( - { - .color_filter = ColorFilter::MakeBlend(BlendMode::kDifference, - Color(0, 1, 0, 0.5)), - }, - layer_rect); - - Paint paint; - canvas.DrawPaint({.color = Color::Black()}); - canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), - {.color = Color::White()}); - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, BlendModeShouldCoverWholeScreen) { - Canvas canvas; - Paint paint; - - paint.color = Color::Red(); - canvas.DrawPaint(paint); - - paint.blend_mode = BlendMode::kSourceOver; - canvas.SaveLayer(paint); - - paint.color = Color::White(); - canvas.DrawRect(Rect::MakeXYWH(100, 100, 400, 400), paint); - - paint.blend_mode = BlendMode::kSource; - canvas.SaveLayer(paint); - - paint.color = Color::Blue(); - canvas.DrawRect(Rect::MakeXYWH(200, 200, 200, 200), paint); - - canvas.Restore(); - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, CanDrawPaintWithAdvancedBlend) { - Canvas canvas; - canvas.Scale(Vector2(0.2, 0.2)); - canvas.DrawPaint({.color = Color::MediumTurquoise()}); - canvas.DrawPaint({.color = Color::Color::OrangeRed().WithAlpha(0.5), - .blend_mode = BlendMode::kHue}); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, DrawPaintWithAdvancedBlendOverFilter) { - Paint filtered = { - .color = Color::Black(), - .mask_blur_descriptor = - Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kNormal, - .sigma = Sigma(60), - }, - }; - - Canvas canvas; - canvas.DrawPaint({.color = Color::White()}); - canvas.DrawCircle({300, 300}, 200, filtered); - canvas.DrawPaint({.color = Color::Green(), .blend_mode = BlendMode::kScreen}); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, DrawAdvancedBlendPartlyOffscreen) { - std::vector colors = {Color{0.9568, 0.2627, 0.2118, 1.0}, - Color{0.1294, 0.5882, 0.9529, 1.0}}; - std::vector stops = {0.0, 1.0}; - - Paint paint = { - .color_source = ColorSource::MakeLinearGradient( - {0, 0}, {100, 100}, std::move(colors), std::move(stops), - Entity::TileMode::kRepeat, Matrix::MakeScale(Vector3(0.3, 0.3, 0.3))), - .blend_mode = BlendMode::kLighten, - }; - - Canvas canvas; - canvas.DrawPaint({.color = Color::Blue()}); - canvas.Scale(Vector2(2, 2)); - canvas.ClipRect(Rect::MakeLTRB(0, 0, 200, 200)); - canvas.DrawCircle({100, 100}, 100, paint); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, PaintBlendModeIsRespected) { - Paint paint; - Canvas canvas; - // Default is kSourceOver. - paint.color = Color(1, 0, 0, 0.5); - canvas.DrawCircle(Point(150, 200), 100, paint); - paint.color = Color(0, 1, 0, 0.5); - canvas.DrawCircle(Point(250, 200), 100, paint); - - paint.blend_mode = BlendMode::kPlus; - paint.color = Color::Red(); - canvas.DrawCircle(Point(450, 250), 100, paint); - paint.color = Color::Green(); - canvas.DrawCircle(Point(550, 250), 100, paint); - paint.color = Color::Blue(); - canvas.DrawCircle(Point(500, 150), 100, paint); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -// Bug: https://github.com/flutter/flutter/issues/142549 -TEST_P(AiksTest, BlendModePlusAlphaWideGamut) { - EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(), - PixelFormat::kB10G10R10A10XR); - auto texture = CreateTextureForFixture("airplane.jpg", - /*enable_mipmapping=*/true); - - Canvas canvas; - canvas.Scale(GetContentScale()); - canvas.DrawPaint({.color = Color(0.9, 1.0, 0.9, 1.0)}); - canvas.SaveLayer({}); - Paint paint; - paint.blend_mode = BlendMode::kPlus; - paint.color = Color::Red(); - canvas.DrawRect(Rect::MakeXYWH(100, 100, 400, 400), paint); - paint.color = Color::White(); - canvas.DrawImageRect( - std::make_shared(texture), Rect::MakeSize(texture->GetSize()), - Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100), paint); - canvas.Restore(); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -// Bug: https://github.com/flutter/flutter/issues/142549 -TEST_P(AiksTest, BlendModePlusAlphaColorFilterWideGamut) { - EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(), - PixelFormat::kB10G10R10A10XR); - auto texture = CreateTextureForFixture("airplane.jpg", - /*enable_mipmapping=*/true); - - Canvas canvas; - canvas.Scale(GetContentScale()); - canvas.DrawPaint({.color = Color(0.1, 0.2, 0.1, 1.0)}); - canvas.SaveLayer({ - .color_filter = - ColorFilter::MakeBlend(BlendMode::kPlus, Color(Vector4{1, 0, 0, 1})), - }); - Paint paint; - paint.color = Color::Red(); - canvas.DrawRect(Rect::MakeXYWH(100, 100, 400, 400), paint); - paint.color = Color::White(); - canvas.DrawImageRect( - std::make_shared(texture), Rect::MakeSize(texture->GetSize()), - Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100), paint); - canvas.Restore(); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - #define BLEND_MODE_TUPLE(blend_mode) {#blend_mode, BlendMode::k##blend_mode}, struct BlendModeSelection { @@ -338,258 +178,5 @@ TEST_P(AiksTest, ColorWheel) { ASSERT_TRUE(OpenPlaygroundHere(callback)); } -TEST_P(AiksTest, ForegroundBlendSubpassCollapseOptimization) { - Canvas canvas; - - canvas.SaveLayer({ - .color_filter = - ColorFilter::MakeBlend(BlendMode::kColorDodge, Color::Red()), - }); - - canvas.Translate({500, 300, 0}); - canvas.Rotate(Radians(2 * kPi / 3)); - canvas.DrawRect(Rect::MakeXYWH(100, 100, 200, 200), {.color = Color::Blue()}); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, ClearBlend) { - Canvas canvas; - Paint white; - white.color = Color::Blue(); - canvas.DrawRect(Rect::MakeXYWH(0, 0, 600.0, 600.0), white); - - Paint clear; - clear.blend_mode = BlendMode::kClear; - - canvas.DrawCircle(Point::MakeXY(300.0, 300.0), 200.0, clear); -} - -static Picture BlendModeTest(Vector2 content_scale, - BlendMode blend_mode, - const std::shared_ptr& src_image, - const std::shared_ptr& dst_image, - Scalar src_alpha) { - if (AiksTest::ImGuiBegin("Controls", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1); - ImGui::End(); - } - - Color destination_color = Color::CornflowerBlue().WithAlpha(0.75); - auto source_colors = std::vector({Color::White().WithAlpha(0.75), - Color::LimeGreen().WithAlpha(0.75), - Color::Black().WithAlpha(0.75)}); - - Canvas canvas; - canvas.DrawPaint({.color = Color::Black()}); - // TODO(bdero): Why does this cause the left image to double scale on high DPI - // displays. - // canvas.Scale(content_scale); - - //---------------------------------------------------------------------------- - /// 1. Save layer blending (top squares). - /// - - canvas.Save(); - for (const auto& color : source_colors) { - canvas.Save(); - { - canvas.ClipRect(Rect::MakeXYWH(25, 25, 100, 100)); - // Perform the blend in a SaveLayer so that the initial backdrop color is - // fully transparent black. SourceOver blend the result onto the parent - // pass. - canvas.SaveLayer({}); - { - canvas.DrawPaint({.color = destination_color}); - // Draw the source color in an offscreen pass and blend it to the parent - // pass. - canvas.SaveLayer({.blend_mode = blend_mode}); - { // - canvas.DrawRect(Rect::MakeXYWH(25, 25, 100, 100), {.color = color}); - } - canvas.Restore(); - } - canvas.Restore(); - } - canvas.Restore(); - canvas.Translate(Vector2(100, 0)); - } - canvas.RestoreToCount(0); - - //---------------------------------------------------------------------------- - /// 2. CPU blend modes (bottom squares). - /// - - canvas.Save(); - canvas.Translate({0, 100}); - // Perform the blend in a SaveLayer so that the initial backdrop color is - // fully transparent black. SourceOver blend the result onto the parent pass. - canvas.SaveLayer({}); - for (const auto& color : source_colors) { - // Simply write the CPU blended color to the pass. - canvas.DrawRect(Rect::MakeXYWH(25, 25, 100, 100), - {.color = destination_color.Blend(color, blend_mode), - .blend_mode = BlendMode::kSourceOver}); - canvas.Translate(Vector2(100, 0)); - } - canvas.Restore(); - canvas.Restore(); - - //---------------------------------------------------------------------------- - /// 3. Image blending (bottom images). - /// - /// Compare these results with the images in the Flutter blend mode - /// documentation: https://api.flutter.dev/flutter/dart-ui/BlendMode.html - /// - - canvas.Translate({0, 250}); - - // Draw grid behind the images. - canvas.DrawRect(Rect::MakeLTRB(0, 0, 800, 400), - {.color = Color::MakeRGBA8(41, 41, 41, 255)}); - Paint square_paint = {.color = Color::MakeRGBA8(15, 15, 15, 255)}; - for (int y = 0; y < 400 / 8; y++) { - for (int x = 0; x < 800 / 16; x++) { - canvas.DrawRect(Rect::MakeXYWH(x * 16 + (y % 2) * 8, y * 8, 8, 8), - square_paint); - } - } - - // Uploaded image source (left image). - canvas.Save(); - canvas.SaveLayer({.blend_mode = BlendMode::kSourceOver}); - { - canvas.DrawImage(dst_image, {0, 0}, - { - .blend_mode = BlendMode::kSourceOver, - }); - canvas.DrawImage(src_image, {0, 0}, - { - .color = Color::White().WithAlpha(src_alpha), - .blend_mode = blend_mode, - }); - } - canvas.Restore(); - canvas.Restore(); - - // Rendered image source (right image). - canvas.Save(); - canvas.SaveLayer({.blend_mode = BlendMode::kSourceOver}); - { - canvas.DrawImage(dst_image, {400, 0}, - {.blend_mode = BlendMode::kSourceOver}); - canvas.SaveLayer({.color = Color::White().WithAlpha(src_alpha), - .blend_mode = blend_mode}); - { - canvas.DrawImage(src_image, {400, 0}, - {.blend_mode = BlendMode::kSourceOver}); - } - canvas.Restore(); - } - canvas.Restore(); - canvas.Restore(); - - return canvas.EndRecordingAsPicture(); -} - -#define BLEND_MODE_TEST(blend_mode) \ - TEST_P(AiksTest, BlendMode##blend_mode) { \ - auto src_image = std::make_shared( \ - CreateTextureForFixture("blend_mode_src.png")); \ - auto dst_image = std::make_shared( \ - CreateTextureForFixture("blend_mode_dst.png")); \ - auto callback = [&](AiksContext& renderer) -> std::optional { \ - return BlendModeTest(GetContentScale(), BlendMode::k##blend_mode, \ - src_image, dst_image, /*src_alpha=*/1.0); \ - }; \ - OpenPlaygroundHere(callback); \ - } -IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TEST) - -#define BLEND_MODE_SRC_ALPHA_TEST(blend_mode) \ - TEST_P(AiksTest, BlendModeSrcAlpha##blend_mode) { \ - auto src_image = std::make_shared( \ - CreateTextureForFixture("blend_mode_src.png")); \ - auto dst_image = std::make_shared( \ - CreateTextureForFixture("blend_mode_dst.png")); \ - auto callback = [&](AiksContext& renderer) -> std::optional { \ - return BlendModeTest(GetContentScale(), BlendMode::k##blend_mode, \ - src_image, dst_image, /*src_alpha=*/0.5); \ - }; \ - OpenPlaygroundHere(callback); \ - } -IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_SRC_ALPHA_TEST) - -TEST_P(AiksTest, CanDrawPaintMultipleTimesInteractive) { - auto modes = GetBlendModeSelection(); - - auto callback = [&](AiksContext& renderer) -> std::optional { - static Color background = Color::MediumTurquoise(); - static Color foreground = Color::Color::OrangeRed().WithAlpha(0.5); - static int current_blend_index = 3; - - if (AiksTest::ImGuiBegin("Controls", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::ColorEdit4("Background", reinterpret_cast(&background)); - ImGui::ColorEdit4("Foreground", reinterpret_cast(&foreground)); - ImGui::ListBox("Blend mode", ¤t_blend_index, - modes.blend_mode_names.data(), - modes.blend_mode_names.size()); - ImGui::End(); - } - - Canvas canvas; - canvas.Scale(Vector2(0.2, 0.2)); - canvas.DrawPaint({.color = background}); - canvas.DrawPaint( - {.color = foreground, - .blend_mode = static_cast(current_blend_index)}); - return canvas.EndRecordingAsPicture(); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(AiksTest, ForegroundPipelineBlendAppliesTransformCorrectly) { - auto texture = CreateTextureForFixture("airplane.jpg", - /*enable_mipmapping=*/true); - - Canvas canvas; - canvas.Rotate(Degrees(30)); - canvas.DrawImage(std::make_shared(texture), {200, 200}, - {.color_filter = ColorFilter::MakeBlend(BlendMode::kSourceIn, - Color::Orange())}); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, ForegroundAdvancedBlendAppliesTransformCorrectly) { - auto texture = CreateTextureForFixture("airplane.jpg", - /*enable_mipmapping=*/true); - - Canvas canvas; - canvas.Rotate(Degrees(30)); - canvas.DrawImage(std::make_shared(texture), {200, 200}, - {.color_filter = ColorFilter::MakeBlend( - BlendMode::kColorDodge, Color::Orange())}); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, FramebufferAdvancedBlendCoverage) { - auto texture = CreateTextureForFixture("airplane.jpg", - /*enable_mipmapping=*/true); - - // Draw with an advanced blend that can use FramebufferBlendContents and - // verify that the scale transform is correctly applied to the image. - Canvas canvas; - canvas.DrawPaint({.color = Color::DarkGray()}); - canvas.Scale(Vector2(0.4, 0.4)); - canvas.DrawImage(std::make_shared(texture), {20, 20}, - {.blend_mode = BlendMode::kMultiply}); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - } // namespace testing } // namespace impeller diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index ddc04b0a517dc..2919c78604e4a 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -354,95 +354,6 @@ TEST_P(AiksTest, ClearColorOptimizationDoesNotApplyForBackdropFilters) { EXPECT_FALSE(actual_color.has_value()); } -TEST_P(AiksTest, ImageFilteredSaveLayerWithUnboundedContents) { - Canvas canvas; - canvas.Scale(GetContentScale()); - - auto test = [&canvas](const std::shared_ptr& filter) { - auto DrawLine = [&canvas](const Point& p0, const Point& p1, - const Paint& p) { - auto path = PathBuilder{} - .AddLine(p0, p1) - .SetConvexity(Convexity::kConvex) - .TakePath(); - Paint paint = p; - paint.style = Paint::Style::kStroke; - canvas.DrawPath(path, paint); - }; - // Registration marks for the edge of the SaveLayer - DrawLine(Point(75, 100), Point(225, 100), {.color = Color::White()}); - DrawLine(Point(75, 200), Point(225, 200), {.color = Color::White()}); - DrawLine(Point(100, 75), Point(100, 225), {.color = Color::White()}); - DrawLine(Point(200, 75), Point(200, 225), {.color = Color::White()}); - - canvas.SaveLayer({.image_filter = filter}, - Rect::MakeLTRB(100, 100, 200, 200)); - { - // DrawPaint to verify correct behavior when the contents are unbounded. - canvas.DrawPaint({.color = Color::Yellow()}); - - // Contrasting rectangle to see interior blurring - canvas.DrawRect(Rect::MakeLTRB(125, 125, 175, 175), - {.color = Color::Blue()}); - } - canvas.Restore(); - }; - - test(ImageFilter::MakeBlur(Sigma{10.0}, Sigma{10.0}, - FilterContents::BlurStyle::kNormal, - Entity::TileMode::kDecal)); - - canvas.Translate({200.0, 0.0}); - - test(ImageFilter::MakeDilate(Radius{10.0}, Radius{10.0})); - - canvas.Translate({200.0, 0.0}); - - test(ImageFilter::MakeErode(Radius{10.0}, Radius{10.0})); - - canvas.Translate({-400.0, 200.0}); - - auto rotate_filter = - ImageFilter::MakeMatrix(Matrix::MakeTranslation({150, 150}) * - Matrix::MakeRotationZ(Degrees{10.0}) * - Matrix::MakeTranslation({-150, -150}), - SamplerDescriptor{}); - test(rotate_filter); - - canvas.Translate({200.0, 0.0}); - - auto rgb_swap_filter = ImageFilter::MakeFromColorFilter( - *ColorFilter::MakeMatrix({.array = { - 0, 1, 0, 0, 0, // - 0, 0, 1, 0, 0, // - 1, 0, 0, 0, 0, // - 0, 0, 0, 1, 0 // - }})); - test(rgb_swap_filter); - - canvas.Translate({200.0, 0.0}); - - test(ImageFilter::MakeCompose(*rotate_filter, *rgb_swap_filter)); - - canvas.Translate({-400.0, 200.0}); - - test(ImageFilter::MakeLocalMatrix(Matrix::MakeTranslation({25.0, 25.0}), - *rotate_filter)); - - canvas.Translate({200.0, 0.0}); - - test(ImageFilter::MakeLocalMatrix(Matrix::MakeTranslation({25.0, 25.0}), - *rgb_swap_filter)); - - canvas.Translate({200.0, 0.0}); - - test(ImageFilter::MakeLocalMatrix( - Matrix::MakeTranslation({25.0, 25.0}), - *ImageFilter::MakeCompose(*rotate_filter, *rgb_swap_filter))); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - TEST_P(AiksTest, OpaqueEntitiesGetCoercedToSource) { Canvas canvas; canvas.Scale(Vector2(1.618, 1.618)); @@ -472,57 +383,6 @@ TEST_P(AiksTest, OpaqueEntitiesGetCoercedToSource) { ASSERT_EQ(entity[0].GetBlendMode(), BlendMode::kSource); } -TEST_P(AiksTest, MatrixSaveLayerFilter) { - Canvas canvas; - canvas.DrawPaint({.color = Color::Black()}); - canvas.SaveLayer({}, std::nullopt); - { - canvas.DrawCircle(Point(200, 200), 100, - {.color = Color::Green().WithAlpha(0.5), - .blend_mode = BlendMode::kPlus}); - // Should render a second circle, centered on the bottom-right-most edge of - // the circle. - canvas.SaveLayer({.image_filter = ImageFilter::MakeMatrix( - Matrix::MakeTranslation(Vector2(1, 1) * - (200 + 100 * k1OverSqrt2)) * - Matrix::MakeScale(Vector2(1, 1) * 0.5) * - Matrix::MakeTranslation(Vector2(-200, -200)), - SamplerDescriptor{})}, - std::nullopt); - canvas.DrawCircle(Point(200, 200), 100, - {.color = Color::Green().WithAlpha(0.5), - .blend_mode = BlendMode::kPlus}); - canvas.Restore(); - } - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, MatrixBackdropFilter) { - Canvas canvas; - canvas.DrawPaint({.color = Color::Black()}); - canvas.SaveLayer({}, std::nullopt); - { - canvas.DrawCircle(Point(200, 200), 100, - {.color = Color::Green().WithAlpha(0.5), - .blend_mode = BlendMode::kPlus}); - // Should render a second circle, centered on the bottom-right-most edge of - // the circle. - canvas.SaveLayer( - {}, std::nullopt, - ImageFilter::MakeMatrix( - Matrix::MakeTranslation(Vector2(1, 1) * (100 + 100 * k1OverSqrt2)) * - Matrix::MakeScale(Vector2(1, 1) * 0.5) * - Matrix::MakeTranslation(Vector2(-100, -100)), - SamplerDescriptor{})); - canvas.Restore(); - } - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - TEST_P(AiksTest, SolidColorApplyColorFilter) { auto contents = SolidColorContents(); contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75)); @@ -534,65 +394,6 @@ TEST_P(AiksTest, SolidColorApplyColorFilter) { Color(0.424452, 0.828743, 0.79105, 0.9375)); } -// Regression test for https://github.com/flutter/flutter/issues/134678. -TEST_P(AiksTest, ReleasesTextureOnTeardown) { - auto context = MakeContext(); - std::weak_ptr weak_texture; - - { - auto texture = CreateTextureForFixture("table_mountain_nx.png"); - - Canvas canvas; - canvas.Scale(GetContentScale()); - canvas.Translate({100.0f, 100.0f, 0}); - - Paint paint; - paint.color_source = ColorSource::MakeImage( - texture, Entity::TileMode::kClamp, Entity::TileMode::kClamp, {}, {}); - canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); - } - - // See https://github.com/flutter/flutter/issues/134751. - // - // If the fence waiter was working this may not be released by the end of the - // scope above. Adding a manual shutdown so that future changes to the fence - // waiter will not flake this test. - context->Shutdown(); - - // The texture should be released by now. - ASSERT_TRUE(weak_texture.expired()) << "When the texture is no longer in use " - "by the backend, it should be " - "released."; -} - -TEST_P(AiksTest, MatrixImageFilterMagnify) { - Scalar scale = 2.0; - auto callback = [&](AiksContext& renderer) -> std::optional { - if (AiksTest::ImGuiBegin("Controls", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::SliderFloat("Scale", &scale, 1, 2); - ImGui::End(); - } - Canvas canvas; - canvas.Scale(GetContentScale()); - auto image = - std::make_shared(CreateTextureForFixture("airplane.jpg")); - canvas.Translate({600, -200}); - canvas.SaveLayer({ - .image_filter = std::make_shared( - Matrix::MakeScale({scale, scale, 1}), SamplerDescriptor{}), - }); - canvas.DrawImage(image, {0, 0}, - Paint{.color = Color::White().WithAlpha(0.5)}); - canvas.Restore(); - return canvas.EndRecordingAsPicture(); - }; - - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - TEST_P(AiksTest, CorrectClipDepthAssignedToEntities) { Canvas canvas; // Depth 1 (base pass) canvas.DrawRRect(Rect::MakeLTRB(0, 0, 100, 100), {10, 10}, {}); // Depth 2 @@ -640,114 +441,6 @@ TEST_P(AiksTest, CorrectClipDepthAssignedToEntities) { } } -TEST_P(AiksTest, MipmapGenerationWorksCorrectly) { - TextureDescriptor texture_descriptor; - texture_descriptor.size = ISize{1024, 1024}; - texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt; - texture_descriptor.storage_mode = StorageMode::kHostVisible; - texture_descriptor.mip_count = texture_descriptor.size.MipCount(); - - std::vector bytes(4194304); - bool alternate = false; - for (auto i = 0u; i < 4194304; i += 4) { - if (alternate) { - bytes[i] = 255; - bytes[i + 1] = 0; - bytes[i + 2] = 0; - bytes[i + 3] = 255; - } else { - bytes[i] = 0; - bytes[i + 1] = 255; - bytes[i + 2] = 0; - bytes[i + 3] = 255; - } - alternate = !alternate; - } - - ASSERT_EQ(texture_descriptor.GetByteSizeOfBaseMipLevel(), bytes.size()); - auto mapping = std::make_shared( - bytes.data(), // data - texture_descriptor.GetByteSizeOfBaseMipLevel() // size - ); - auto texture = - GetContext()->GetResourceAllocator()->CreateTexture(texture_descriptor); - - auto device_buffer = - GetContext()->GetResourceAllocator()->CreateBufferWithCopy(*mapping); - auto command_buffer = GetContext()->CreateCommandBuffer(); - auto blit_pass = command_buffer->CreateBlitPass(); - - blit_pass->AddCopy(DeviceBuffer::AsBufferView(std::move(device_buffer)), - texture); - blit_pass->GenerateMipmap(texture); - EXPECT_TRUE(blit_pass->EncodeCommands(GetContext()->GetResourceAllocator())); - EXPECT_TRUE(GetContext()->GetCommandQueue()->Submit({command_buffer}).ok()); - - auto image = std::make_shared(texture); - - Canvas canvas; - canvas.DrawImageRect(image, Rect::MakeSize(texture->GetSize()), - Rect::MakeLTRB(0, 0, 100, 100), {}); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -// https://github.com/flutter/flutter/issues/146648 -TEST_P(AiksTest, StrokedPathWithMoveToThenCloseDrawnCorrectly) { - Path path = PathBuilder{} - .MoveTo({0, 400}) - .LineTo({0, 0}) - .LineTo({400, 0}) - // MoveTo implicitly adds a contour, ensure that close doesn't - // add another nearly-empty contour. - .MoveTo({0, 400}) - .Close() - .TakePath(); - - Canvas canvas; - canvas.Translate({50, 50, 0}); - canvas.DrawPath(path, { - .color = Color::Blue(), - .stroke_width = 10, - .stroke_cap = Cap::kRound, - .style = Paint::Style::kStroke, - }); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, SetContentsWithRegion) { - auto bridge = CreateTextureForFixture("bay_bridge.jpg"); - - // Replace part of the texture with a red rectangle. - std::vector bytes(100 * 100 * 4); - for (auto i = 0u; i < bytes.size(); i += 4) { - bytes[i] = 255; - bytes[i + 1] = 0; - bytes[i + 2] = 0; - bytes[i + 3] = 255; - } - auto mapping = - std::make_shared(bytes.data(), bytes.size()); - auto device_buffer = - GetContext()->GetResourceAllocator()->CreateBufferWithCopy(*mapping); - auto cmd_buffer = GetContext()->CreateCommandBuffer(); - auto blit_pass = cmd_buffer->CreateBlitPass(); - blit_pass->AddCopy(DeviceBuffer::AsBufferView(device_buffer), bridge, - IRect::MakeLTRB(50, 50, 150, 150)); - - auto did_submit = - blit_pass->EncodeCommands(GetContext()->GetResourceAllocator()) && - GetContext()->GetCommandQueue()->Submit({std::move(cmd_buffer)}).ok(); - ASSERT_TRUE(did_submit); - - auto image = std::make_shared(bridge); - - Canvas canvas; - canvas.DrawImage(image, {0, 0}, {}); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - } // namespace testing } // namespace impeller diff --git a/impeller/display_list/BUILD.gn b/impeller/display_list/BUILD.gn index e6dac762f204b..391a0d6a05a00 100644 --- a/impeller/display_list/BUILD.gn +++ b/impeller/display_list/BUILD.gn @@ -51,6 +51,7 @@ template("display_list_unittests_component") { predefined_sources = [ "aiks_dl_atlas_unittests.cc", "aiks_dl_basic_unittests.cc", + "aiks_dl_blend_unittests.cc", "aiks_dl_clip_unittests.cc", "aiks_dl_gradient_unittests.cc", "aiks_dl_opacity_unittests.cc", diff --git a/impeller/display_list/aiks_dl_blend_unittests.cc b/impeller/display_list/aiks_dl_blend_unittests.cc new file mode 100644 index 0000000000000..bb06db2dd51ee --- /dev/null +++ b/impeller/display_list/aiks_dl_blend_unittests.cc @@ -0,0 +1,570 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "display_list/display_list.h" +#include "display_list/dl_sampling_options.h" +#include "display_list/dl_tile_mode.h" +#include "display_list/effects/dl_color_filter.h" +#include "display_list/effects/dl_color_source.h" +#include "display_list/effects/dl_mask_filter.h" +#include "flutter/impeller/aiks/aiks_unittests.h" + +#include "flutter/display_list/dl_blend_mode.h" +#include "flutter/display_list/dl_builder.h" +#include "flutter/display_list/dl_color.h" +#include "flutter/display_list/dl_paint.h" +#include "flutter/impeller/display_list/dl_image_impeller.h" +#include "flutter/impeller/geometry/scalar.h" +#include "include/core/SkMatrix.h" + +//////////////////////////////////////////////////////////////////////////////// +// This is for tests of Canvas that are interested the results of rendering +// blends. +//////////////////////////////////////////////////////////////////////////////// + +namespace impeller { +namespace testing { + +using namespace flutter; + +#define BLEND_MODE_TUPLE(blend_mode) {#blend_mode, BlendMode::k##blend_mode}, + +struct BlendModeSelection { + std::vector blend_mode_names; + std::vector blend_mode_values; +}; + +static BlendModeSelection GetBlendModeSelection() { + std::vector blend_mode_names; + std::vector blend_mode_values; + { + const std::vector> blends = { + IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TUPLE)}; + assert(blends.size() == + static_cast(Entity::kLastAdvancedBlendMode) + 1); + for (const auto& [name, mode] : blends) { + blend_mode_names.push_back(name); + blend_mode_values.push_back(mode); + } + } + + return {blend_mode_names, blend_mode_values}; +} + +TEST_P(AiksTest, CanRenderAdvancedBlendColorFilterWithSaveLayer) { + DisplayListBuilder builder; + + SkRect layer_rect = SkRect::MakeXYWH(0, 0, 500, 500); + builder.ClipRect(layer_rect); + + DlPaint save_paint; + save_paint.setColorFilter(DlBlendColorFilter::Make( + DlColor::RGBA(0, 1, 0, 0.5), DlBlendMode::kDifference)); + builder.SaveLayer(&layer_rect, &save_paint); + + DlPaint paint; + paint.setColor(DlColor::kBlack()); + builder.DrawPaint(paint); + paint.setColor(DlColor::kWhite()); + builder.DrawRect(SkRect::MakeXYWH(100, 100, 300, 300), paint); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, BlendModeShouldCoverWholeScreen) { + DisplayListBuilder builder; + DlPaint paint; + + paint.setColor(DlColor::kRed()); + builder.DrawPaint(paint); + + paint.setBlendMode(DlBlendMode::kSrcOver); + builder.SaveLayer(nullptr, &paint); + + paint.setColor(DlColor::kWhite()); + builder.DrawRect(SkRect::MakeXYWH(100, 100, 400, 400), paint); + + paint.setBlendMode(DlBlendMode::kSrc); + builder.SaveLayer(nullptr, &paint); + + paint.setColor(DlColor::kBlue()); + builder.DrawRect(SkRect::MakeXYWH(200, 200, 200, 200), paint); + + builder.Restore(); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanDrawPaintWithAdvancedBlend) { + DisplayListBuilder builder; + + builder.Scale(0.2, 0.2); + DlPaint paint; + paint.setColor(DlColor::RGBA( + Color::MediumTurquoise().red, Color::MediumTurquoise().green, + Color::MediumTurquoise().blue, Color::MediumTurquoise().alpha)); + builder.DrawPaint(paint); + + paint.setColor(DlColor::RGBA(Color::OrangeRed().red, Color::OrangeRed().green, + Color::OrangeRed().blue, 0.5)); + paint.setBlendMode(DlBlendMode::kHue); + builder.DrawPaint(paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, DrawPaintWithAdvancedBlendOverFilter) { + DlPaint paint; + paint.setColor(DlColor::kBlack()); + paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 60)); + + DisplayListBuilder builder; + paint.setColor(DlColor::kWhite()); + builder.DrawPaint(paint); + paint.setColor(DlColor::kBlack()); + builder.DrawCircle({300, 300}, 200, paint); + paint.setColor(DlColor::kGreen()); + paint.setBlendMode(DlBlendMode::kScreen); + builder.DrawPaint(paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, DrawAdvancedBlendPartlyOffscreen) { + DisplayListBuilder builder; + + DlPaint draw_paint; + draw_paint.setColor(DlColor::kBlue()); + builder.DrawPaint(draw_paint); + builder.Scale(2, 2); + builder.ClipRect(SkRect::MakeLTRB(0, 0, 200, 200)); + + std::vector colors = {DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0), + DlColor::RGBA(0.1294, 0.5882, 0.9529, 1.0)}; + std::vector stops = {0.0, 1.0}; + + DlPaint paint; + SkMatrix matrix = SkMatrix::Scale(0.3, 0.3); + paint.setColorSource(DlColorSource::MakeLinear( + /*start_point=*/{0, 0}, // + /*end_point=*/{100, 100}, // + /*stop_count=*/colors.size(), // + /*colors=*/colors.data(), // + /*stops=*/stops.data(), // + /*tile_mode=*/DlTileMode::kRepeat, // + /*matrix=*/&matrix // + )); + paint.setBlendMode(DlBlendMode::kLighten); + + builder.DrawCircle({100, 100}, 100, paint); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, PaintBlendModeIsRespected) { + DlPaint paint; + DisplayListBuilder builder; + // Default is kSourceOver. + + paint.setColor(DlColor::RGBA(1, 0, 0, 0.5)); + builder.DrawCircle({150, 200}, 100, paint); + + paint.setColor(DlColor::RGBA(0, 1, 0, 0.5)); + builder.DrawCircle({250, 200}, 100, paint); + + paint.setBlendMode(DlBlendMode::kPlus); + + paint.setColor(DlColor::kRed()); + builder.DrawCircle({450, 250}, 100, paint); + + paint.setColor(DlColor::kGreen()); + builder.DrawCircle({550, 250}, 100, paint); + + paint.setColor(DlColor::kBlue()); + builder.DrawCircle({500, 150}, 100, paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +// Bug: https://github.com/flutter/flutter/issues/142549 +TEST_P(AiksTest, BlendModePlusAlphaWideGamut) { + EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(), + PixelFormat::kB10G10R10A10XR); + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + + DisplayListBuilder builder; + DlPaint paint; + builder.Scale(GetContentScale().x, GetContentScale().y); + + paint.setColor(DlColor::RGBA(0.9, 1, 0.9, 1.0)); + builder.DrawPaint(paint); + builder.SaveLayer(nullptr); + + paint.setBlendMode(DlBlendMode::kPlus); + paint.setColor(DlColor::kRed()); + + builder.DrawRect(SkRect::MakeXYWH(100, 100, 400, 400), paint); + paint.setColor(DlColor::kWhite()); + + auto rect = Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100); + builder.DrawImageRect( + DlImageImpeller::Make(texture), + SkRect::MakeSize( + SkSize::Make(texture->GetSize().width, texture->GetSize().height)), + SkRect::MakeLTRB(rect.GetLeft(), rect.GetTop(), rect.GetRight(), + rect.GetBottom()), + DlImageSampling::kMipmapLinear, &paint); + builder.Restore(); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +// Bug: https://github.com/flutter/flutter/issues/142549 +TEST_P(AiksTest, BlendModePlusAlphaColorFilterWideGamut) { + EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(), + PixelFormat::kB10G10R10A10XR); + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + + DlPaint paint; + paint.setColor(DlColor::RGBA(0.1, 0.2, 0.1, 1.0)); + builder.DrawPaint(paint); + + DlPaint save_paint; + save_paint.setColorFilter( + DlBlendColorFilter::Make(DlColor::RGBA(1, 0, 0, 1), DlBlendMode::kPlus)); + builder.SaveLayer(nullptr, &save_paint); + + paint.setColor(DlColor::kRed()); + builder.DrawRect(SkRect::MakeXYWH(100, 100, 400, 400), paint); + + paint.setColor(DlColor::kWhite()); + + auto rect = Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100); + builder.DrawImageRect( + DlImageImpeller::Make(texture), + SkRect::MakeSize( + SkSize::Make(texture->GetSize().width, texture->GetSize().height)), + SkRect::MakeLTRB(rect.GetLeft(), rect.GetTop(), rect.GetRight(), + rect.GetBottom()), + DlImageSampling::kMipmapLinear, &paint); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, ForegroundBlendSubpassCollapseOptimization) { + DisplayListBuilder builder; + + DlPaint save_paint; + save_paint.setColorFilter( + DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kColorDodge)); + builder.SaveLayer(nullptr, &save_paint); + + builder.Translate(500, 300); + builder.Rotate(120); + + DlPaint paint; + paint.setColor(DlColor::kBlue()); + builder.DrawRect(SkRect::MakeXYWH(100, 100, 200, 200), paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, ClearBlend) { + DisplayListBuilder builder; + + DlPaint blue; + blue.setColor(DlColor::kBlue()); + builder.DrawRect(SkRect::MakeXYWH(0, 0, 600.0, 600.0), blue); + + DlPaint clear; + clear.setBlendMode(DlBlendMode::kClear); + + builder.DrawCircle({300.0, 300.0}, 200.0, clear); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +static sk_sp BlendModeTest(Vector2 content_scale, + BlendMode blend_mode, + const sk_sp& src_image, + const sk_sp& dst_image, + Scalar src_alpha) { + if (AiksTest::ImGuiBegin("Controls", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1); + ImGui::End(); + } + + Color destination_color = Color::CornflowerBlue().WithAlpha(0.75); + auto source_colors = std::vector({Color::White().WithAlpha(0.75), + Color::LimeGreen().WithAlpha(0.75), + Color::Black().WithAlpha(0.75)}); + + DisplayListBuilder builder; + { + DlPaint paint; + paint.setColor(DlColor::kBlack()); + builder.DrawPaint(paint); + } + // TODO(bdero): Why does this cause the left image to double scale on high DPI + // displays. + // builder.Scale(content_scale); + + //---------------------------------------------------------------------------- + /// 1. Save layer blending (top squares). + /// + + builder.Save(); + for (const auto& color : source_colors) { + builder.Save(); + { + builder.ClipRect(SkRect::MakeXYWH(25, 25, 100, 100)); + // Perform the blend in a SaveLayer so that the initial backdrop color is + // fully transparent black. SourceOver blend the result onto the parent + // pass. + builder.SaveLayer({}); + { + DlPaint draw_paint; + draw_paint.setColor( + DlColor::RGBA(destination_color.red, destination_color.green, + destination_color.blue, destination_color.alpha)); + builder.DrawPaint(draw_paint); + + // Draw the source color in an offscreen pass and blend it to the parent + // pass. + DlPaint save_paint; + save_paint.setBlendMode(static_cast(blend_mode)); + builder.SaveLayer(nullptr, &save_paint); + { // + DlPaint paint; + paint.setColor( + DlColor::RGBA(color.red, color.green, color.blue, color.alpha)); + builder.DrawRect(SkRect::MakeXYWH(25, 25, 100, 100), paint); + } + builder.Restore(); + } + builder.Restore(); + } + builder.Restore(); + builder.Translate(100, 0); + } + builder.RestoreToCount(0); + + //---------------------------------------------------------------------------- + /// 2. CPU blend modes (bottom squares). + /// + + builder.Save(); + builder.Translate(0, 100); + // Perform the blend in a SaveLayer so that the initial backdrop color is + // fully transparent black. SourceOver blend the result onto the parent pass. + builder.SaveLayer({}); + for (const auto& color : source_colors) { + // Simply write the CPU blended color to the pass. + DlPaint paint; + auto dest = destination_color.Blend(color, blend_mode); + paint.setColor(DlColor::RGBA(dest.red, dest.green, dest.blue, dest.alpha)); + paint.setBlendMode(DlBlendMode::kSrcOver); + builder.DrawRect(SkRect::MakeXYWH(25, 25, 100, 100), paint); + builder.Translate(100, 0); + } + builder.Restore(); + builder.Restore(); + + //---------------------------------------------------------------------------- + /// 3. Image blending (bottom images). + /// + /// Compare these results with the images in the Flutter blend mode + /// documentation: https://api.flutter.dev/flutter/dart-ui/BlendMode.html + /// + + builder.Translate(0, 250); + + // Draw grid behind the images. + { + DlPaint paint; + paint.setColor(DlColor::RGBA(41 / 255.0, 41 / 255.0, 41 / 255.0, 255)); + builder.DrawRect(SkRect::MakeLTRB(0, 0, 800, 400), paint); + } + + DlPaint square_paint; + square_paint.setColor(DlColor::RGBA(15 / 255.0, 15 / 255.0, 15 / 255.0, 1)); + for (int y = 0; y < 400 / 8; y++) { + for (int x = 0; x < 800 / 16; x++) { + builder.DrawRect(SkRect::MakeXYWH(x * 16 + (y % 2) * 8, y * 8, 8, 8), + square_paint); + } + } + + // Uploaded image source (left image). + DlPaint paint; + paint.setBlendMode(DlBlendMode::kSrcOver); + builder.Save(); + builder.SaveLayer(nullptr, &paint); + { + builder.DrawImage(dst_image, {0, 0}, DlImageSampling::kMipmapLinear, + &paint); + + paint.setColor(DlColor::kWhite().withAlpha(src_alpha * 255)); + paint.setBlendMode(static_cast(blend_mode)); + builder.DrawImage(src_image, {0, 0}, DlImageSampling::kMipmapLinear, + &paint); + } + builder.Restore(); + builder.Restore(); + + // Rendered image source (right image). + builder.Save(); + + DlPaint save_paint; + builder.SaveLayer(nullptr, &save_paint); + { + builder.DrawImage(dst_image, {400, 0}, DlImageSampling::kMipmapLinear, + nullptr); + + DlPaint save_paint; + save_paint.setColor(DlColor::kWhite().withAlpha(src_alpha * 255)); + save_paint.setBlendMode(static_cast(blend_mode)); + builder.SaveLayer(nullptr, &save_paint); + { + builder.DrawImage(src_image, {400, 0}, DlImageSampling::kMipmapLinear, + nullptr); + } + builder.Restore(); + } + builder.Restore(); + builder.Restore(); + + return builder.Build(); +} + +#define BLEND_MODE_TEST(blend_mode) \ + TEST_P(AiksTest, BlendMode##blend_mode) { \ + auto src_image = \ + DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png")); \ + auto dst_image = \ + DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png")); \ + auto callback = [&]() -> sk_sp { \ + return BlendModeTest(GetContentScale(), BlendMode::k##blend_mode, \ + src_image, dst_image, /*src_alpha=*/1.0); \ + }; \ + OpenPlaygroundHere(callback); \ + } +IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TEST) + +#define BLEND_MODE_SRC_ALPHA_TEST(blend_mode) \ + TEST_P(AiksTest, BlendModeSrcAlpha##blend_mode) { \ + auto src_image = \ + DlImageImpeller::Make(CreateTextureForFixture("blend_mode_src.png")); \ + auto dst_image = \ + DlImageImpeller::Make(CreateTextureForFixture("blend_mode_dst.png")); \ + auto callback = [&]() -> sk_sp { \ + return BlendModeTest(GetContentScale(), BlendMode::k##blend_mode, \ + src_image, dst_image, /*src_alpha=*/0.5); \ + }; \ + OpenPlaygroundHere(callback); \ + } +IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_SRC_ALPHA_TEST) + +TEST_P(AiksTest, CanDrawPaintMultipleTimesInteractive) { + auto modes = GetBlendModeSelection(); + + auto callback = [&]() -> sk_sp { + static Color background = Color::MediumTurquoise(); + static Color foreground = Color::Color::OrangeRed().WithAlpha(0.5); + static int current_blend_index = 3; + + if (AiksTest::ImGuiBegin("Controls", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::ColorEdit4("Background", reinterpret_cast(&background)); + ImGui::ColorEdit4("Foreground", reinterpret_cast(&foreground)); + ImGui::ListBox("Blend mode", ¤t_blend_index, + modes.blend_mode_names.data(), + modes.blend_mode_names.size()); + ImGui::End(); + } + + DisplayListBuilder builder; + builder.Scale(0.2, 0.2); + DlPaint paint; + paint.setColor(DlColor(background.ToARGB())); + builder.DrawPaint(paint); + + paint.setColor(DlColor(foreground.ToARGB())); + paint.setBlendMode(static_cast(current_blend_index)); + builder.DrawPaint(paint); + return builder.Build(); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(AiksTest, ForegroundPipelineBlendAppliesTransformCorrectly) { + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + + DisplayListBuilder builder; + builder.Rotate(30); + + DlPaint image_paint; + image_paint.setColorFilter(DlBlendColorFilter::Make( + DlColor::RGBA(255.0f / 255.0f, 165.0f / 255.0f, 0.0f / 255.0f, 1.0f), + DlBlendMode::kSrcIn)); + + builder.DrawImage(DlImageImpeller::Make(texture), {200, 200}, + DlImageSampling::kMipmapLinear, &image_paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, ForegroundAdvancedBlendAppliesTransformCorrectly) { + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + + DisplayListBuilder builder; + builder.Rotate(30); + + DlPaint image_paint; + image_paint.setColorFilter(DlBlendColorFilter::Make( + DlColor::RGBA(255.0f / 255.0f, 165.0f / 255.0f, 0.0f / 255.0f, 1.0f), + DlBlendMode::kColorDodge)); + + builder.DrawImage(DlImageImpeller::Make(texture), {200, 200}, + DlImageSampling::kMipmapLinear, &image_paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, FramebufferAdvancedBlendCoverage) { + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + + // Draw with an advanced blend that can use FramebufferBlendContents and + // verify that the scale transform is correctly applied to the image. + DisplayListBuilder builder; + + DlPaint paint; + paint.setColor( + DlColor::RGBA(169.0f / 255.0f, 169.0f / 255.0f, 169.0f / 255.0f, 1.0f)); + builder.DrawPaint(paint); + builder.Scale(0.4, 0.4); + + DlPaint image_paint; + image_paint.setBlendMode(DlBlendMode::kMultiply); + + builder.DrawImage(DlImageImpeller::Make(texture), {20, 20}, + DlImageSampling::kMipmapLinear, &image_paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/display_list/aiks_dl_unittests.cc b/impeller/display_list/aiks_dl_unittests.cc index 3d6082962bb7b..e8cf7326bcd90 100644 --- a/impeller/display_list/aiks_dl_unittests.cc +++ b/impeller/display_list/aiks_dl_unittests.cc @@ -20,6 +20,7 @@ #include "imgui.h" #include "impeller/display_list/dl_image_impeller.h" #include "impeller/geometry/scalar.h" +#include "include/core/SkMatrix.h" #include "include/core/SkRSXform.h" #include "include/core/SkRefCnt.h" @@ -467,5 +468,337 @@ TEST_P(AiksTest, CanDrawPointsWithTextureMap) { ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); } +TEST_P(AiksTest, MipmapGenerationWorksCorrectly) { + TextureDescriptor texture_descriptor; + texture_descriptor.size = ISize{1024, 1024}; + texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt; + texture_descriptor.storage_mode = StorageMode::kHostVisible; + texture_descriptor.mip_count = texture_descriptor.size.MipCount(); + + std::vector bytes(4194304); + bool alternate = false; + for (auto i = 0u; i < 4194304; i += 4) { + if (alternate) { + bytes[i] = 255; + bytes[i + 1] = 0; + bytes[i + 2] = 0; + bytes[i + 3] = 255; + } else { + bytes[i] = 0; + bytes[i + 1] = 255; + bytes[i + 2] = 0; + bytes[i + 3] = 255; + } + alternate = !alternate; + } + + ASSERT_EQ(texture_descriptor.GetByteSizeOfBaseMipLevel(), bytes.size()); + auto mapping = std::make_shared( + bytes.data(), // data + texture_descriptor.GetByteSizeOfBaseMipLevel() // size + ); + auto texture = + GetContext()->GetResourceAllocator()->CreateTexture(texture_descriptor); + + auto device_buffer = + GetContext()->GetResourceAllocator()->CreateBufferWithCopy(*mapping); + auto command_buffer = GetContext()->CreateCommandBuffer(); + auto blit_pass = command_buffer->CreateBlitPass(); + + blit_pass->AddCopy(DeviceBuffer::AsBufferView(std::move(device_buffer)), + texture); + blit_pass->GenerateMipmap(texture); + EXPECT_TRUE(blit_pass->EncodeCommands(GetContext()->GetResourceAllocator())); + EXPECT_TRUE(GetContext()->GetCommandQueue()->Submit({command_buffer}).ok()); + + auto image = DlImageImpeller::Make(texture); + + DisplayListBuilder builder; + builder.DrawImageRect( + image, + SkRect::MakeSize( + SkSize::Make(texture->GetSize().width, texture->GetSize().height)), + SkRect::MakeLTRB(0, 0, 100, 100), DlImageSampling::kMipmapLinear); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +// https://github.com/flutter/flutter/issues/146648 +TEST_P(AiksTest, StrokedPathWithMoveToThenCloseDrawnCorrectly) { + SkPath path; + path.moveTo(0, 400) + .lineTo(0, 0) + .lineTo(400, 0) + // MoveTo implicitly adds a contour, ensure that close doesn't + // add another nearly-empty contour. + .moveTo(0, 400) + .close(); + + DisplayListBuilder builder; + builder.Translate(50, 50); + + DlPaint paint; + paint.setColor(DlColor::kBlue()); + paint.setStrokeCap(DlStrokeCap::kRound); + paint.setStrokeWidth(10); + paint.setDrawStyle(DlDrawStyle::kStroke); + builder.DrawPath(path, paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, SetContentsWithRegion) { + auto bridge = CreateTextureForFixture("bay_bridge.jpg"); + + // Replace part of the texture with a red rectangle. + std::vector bytes(100 * 100 * 4); + for (auto i = 0u; i < bytes.size(); i += 4) { + bytes[i] = 255; + bytes[i + 1] = 0; + bytes[i + 2] = 0; + bytes[i + 3] = 255; + } + auto mapping = + std::make_shared(bytes.data(), bytes.size()); + auto device_buffer = + GetContext()->GetResourceAllocator()->CreateBufferWithCopy(*mapping); + auto cmd_buffer = GetContext()->CreateCommandBuffer(); + auto blit_pass = cmd_buffer->CreateBlitPass(); + blit_pass->AddCopy(DeviceBuffer::AsBufferView(device_buffer), bridge, + IRect::MakeLTRB(50, 50, 150, 150)); + + auto did_submit = + blit_pass->EncodeCommands(GetContext()->GetResourceAllocator()) && + GetContext()->GetCommandQueue()->Submit({std::move(cmd_buffer)}).ok(); + ASSERT_TRUE(did_submit); + + auto image = DlImageImpeller::Make(bridge); + + DisplayListBuilder builder; + builder.DrawImage(image, {0, 0}, {}); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +// Regression test for https://github.com/flutter/flutter/issues/134678. +TEST_P(AiksTest, ReleasesTextureOnTeardown) { + auto context = MakeContext(); + std::weak_ptr weak_texture; + + { + auto texture = CreateTextureForFixture("table_mountain_nx.png"); + weak_texture = texture; + + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + builder.Translate(100.0f, 100.0f); + + DlPaint paint; + paint.setColorSource(std::make_shared( + DlImageImpeller::Make(texture), DlTileMode::kClamp, DlTileMode::kClamp, + DlImageSampling::kLinear, nullptr)); + + builder.DrawRect(SkRect::MakeXYWH(0, 0, 600, 600), paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); + } + + // See https://github.com/flutter/flutter/issues/134751. + // + // If the fence waiter was working this may not be released by the end of the + // scope above. Adding a manual shutdown so that future changes to the fence + // waiter will not flake this test. + context->Shutdown(); + + // The texture should be released by now. + ASSERT_TRUE(weak_texture.expired()) << "When the texture is no longer in use " + "by the backend, it should be " + "released."; +} + +TEST_P(AiksTest, MatrixImageFilterMagnify) { + Scalar scale = 2.0; + auto callback = [&]() -> sk_sp { + if (AiksTest::ImGuiBegin("Controls", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::SliderFloat("Scale", &scale, 1, 2); + ImGui::End(); + } + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + auto image = DlImageImpeller::Make(CreateTextureForFixture("airplane.jpg")); + + builder.Translate(600, -200); + + SkMatrix matrix = SkMatrix::Scale(scale, scale); + DlPaint paint; + paint.setImageFilter( + DlMatrixImageFilter::Make(matrix, DlImageSampling::kLinear)); + builder.SaveLayer(nullptr, &paint); + + DlPaint rect_paint; + rect_paint.setAlpha(0.5 * 255); + builder.DrawImage(image, {0, 0}, DlImageSampling::kLinear, &rect_paint); + builder.Restore(); + + return builder.Build(); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(AiksTest, ImageFilteredSaveLayerWithUnboundedContents) { + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + + auto test = [&builder](const std::shared_ptr& filter) { + auto DrawLine = [&builder](const SkPoint& p0, const SkPoint& p1, + const DlPaint& p) { + DlPaint paint = p; + paint.setDrawStyle(DlDrawStyle::kStroke); + builder.DrawPath(SkPath::Line(p0, p1), paint); + }; + // Registration marks for the edge of the SaveLayer + DlPaint paint; + paint.setColor(DlColor::kWhite()); + DrawLine(SkPoint::Make(75, 100), SkPoint::Make(225, 100), paint); + DrawLine(SkPoint::Make(75, 200), SkPoint::Make(225, 200), paint); + DrawLine(SkPoint::Make(100, 75), SkPoint::Make(100, 225), paint); + DrawLine(SkPoint::Make(200, 75), SkPoint::Make(200, 225), paint); + + DlPaint save_paint; + save_paint.setImageFilter(filter); + SkRect bounds = SkRect::MakeLTRB(100, 100, 200, 200); + builder.SaveLayer(&bounds, &save_paint); + + { + // DrawPaint to verify correct behavior when the contents are unbounded. + DlPaint paint; + paint.setColor(DlColor::kYellow()); + builder.DrawPaint(paint); + + // Contrasting rectangle to see interior blurring + paint.setColor(DlColor::kBlue()); + builder.DrawRect(SkRect::MakeLTRB(125, 125, 175, 175), paint); + } + builder.Restore(); + }; + + test(std::make_shared(10.0, 10.0, DlTileMode::kDecal)); + + builder.Translate(200.0, 0.0); + + test(std::make_shared(10.0, 10.0)); + + builder.Translate(200.0, 0.0); + + test(std::make_shared(10.0, 10.0)); + + builder.Translate(-400.0, 200.0); + + SkMatrix sk_matrix = SkMatrix::RotateDeg(10); + + auto rotate_filter = std::make_shared( + sk_matrix, DlImageSampling::kLinear); + test(rotate_filter); + + builder.Translate(200.0, 0.0); + + const float m[20] = { + 0, 1, 0, 0, 0, // + 0, 0, 1, 0, 0, // + 1, 0, 0, 0, 0, // + 0, 0, 0, 1, 0 // + }; + auto rgb_swap_filter = std::make_shared( + std::make_shared(m)); + test(rgb_swap_filter); + + builder.Translate(200.0, 0.0); + + test(DlComposeImageFilter::Make(rotate_filter, rgb_swap_filter)); + + builder.Translate(-400.0, 200.0); + + test(std::make_shared( + SkMatrix::Translate(25.0, 25.0), rotate_filter)); + + builder.Translate(200.0, 0.0); + + test(std::make_shared( + SkMatrix::Translate(25.0, 25.0), rgb_swap_filter)); + + builder.Translate(200.0, 0.0); + + test(std::make_shared( + SkMatrix::Translate(25.0, 25.0), + DlComposeImageFilter::Make(rotate_filter, rgb_swap_filter))); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, MatrixBackdropFilter) { + DisplayListBuilder builder; + + DlPaint paint; + paint.setColor(DlColor::kBlack()); + builder.DrawPaint(paint); + builder.SaveLayer(nullptr, nullptr); + { + DlPaint paint; + paint.setColor(DlColor::kGreen().withAlpha(0.5 * 255)); + paint.setBlendMode(DlBlendMode::kPlus); + builder.DrawCircle(SkPoint::Make(200, 200), 100, paint); + // Should render a second circle, centered on the bottom-right-most edge of + // the circle. + SkMatrix matrix = SkMatrix::Translate((100 + 100 * k1OverSqrt2), + (100 + 100 * k1OverSqrt2)) * + SkMatrix::Scale(0.5, 0.5) * + SkMatrix::Translate(-100, -100); + auto backdrop_filter = + DlMatrixImageFilter::Make(matrix, DlImageSampling::kLinear); + builder.SaveLayer(nullptr, nullptr, backdrop_filter.get()); + builder.Restore(); + } + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, MatrixSaveLayerFilter) { + DisplayListBuilder builder; + + DlPaint paint; + paint.setColor(DlColor::kBlack()); + builder.DrawPaint(paint); + builder.SaveLayer({}, nullptr); + { + paint.setColor(DlColor::kGreen().withAlpha(255 * 0.5)); + paint.setBlendMode(DlBlendMode::kPlus); + builder.DrawCircle({200, 200}, 100, paint); + // Should render a second circle, centered on the bottom-right-most edge of + // the circle. + + SkMatrix matrix = SkMatrix::Translate((200 + 100 * k1OverSqrt2), + (200 + 100 * k1OverSqrt2)) * + SkMatrix::Scale(0.5, 0.5) * + SkMatrix::Translate(-200, -200); + DlPaint save_paint; + save_paint.setImageFilter( + DlMatrixImageFilter::Make(matrix, DlImageSampling::kLinear)); + + builder.SaveLayer(nullptr, &save_paint); + + DlPaint circle_paint; + circle_paint.setColor(DlColor::kGreen().withAlpha(255 * 0.5)); + circle_paint.setBlendMode(DlBlendMode::kPlus); + builder.DrawCircle({200, 200}, 100, circle_paint); + builder.Restore(); + } + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + } // namespace testing } // namespace impeller diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index 72c96acf8d6f0..06e91b69b3576 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -600,7 +600,7 @@ void DlDispatcherBase::saveLayer(const SkRect& bounds, ? ContentBoundsPromise::kMayClipContents : ContentBoundsPromise::kContainsContents; std::optional impeller_bounds; - if (!options.content_is_unbounded()) { + if (!options.content_is_unbounded() || options.bounds_from_caller()) { impeller_bounds = skia_conversions::ToRect(bounds); } diff --git a/impeller/renderer/backend/metal/surface_mtl.h b/impeller/renderer/backend/metal/surface_mtl.h index bff5072f8fb8b..a9c9c8ec6c0d5 100644 --- a/impeller/renderer/backend/metal/surface_mtl.h +++ b/impeller/renderer/backend/metal/surface_mtl.h @@ -66,7 +66,7 @@ class SurfaceMTL final : public Surface { } /// @brief Perform the final blit and trigger end of frame workloads. - bool PreparePresent(); + bool PreparePresent() const; // |Surface| bool Present() const override; @@ -85,7 +85,7 @@ class SurfaceMTL final : public Surface { std::optional clip_rect_; bool frame_boundary_ = false; bool present_with_transaction_ = false; - bool prepared_ = false; + mutable bool prepared_ = false; static bool ShouldPerformPartialRepaint(std::optional damage_rect); diff --git a/impeller/renderer/backend/metal/surface_mtl.mm b/impeller/renderer/backend/metal/surface_mtl.mm index caa6804c8a42a..6299f4524f4f8 100644 --- a/impeller/renderer/backend/metal/surface_mtl.mm +++ b/impeller/renderer/backend/metal/surface_mtl.mm @@ -222,7 +222,7 @@ - (void)flutterPrepareForPresent:(nonnull id)commandBuffer; return IRect::MakeSize(resolve_texture_->GetSize()); } -bool SurfaceMTL::PreparePresent() { +bool SurfaceMTL::PreparePresent() const { auto context = context_.lock(); if (!context) { return false; @@ -265,7 +265,9 @@ - (void)flutterPrepareForPresent:(nonnull id)commandBuffer; // |Surface| bool SurfaceMTL::Present() const { - FML_CHECK(prepared_); + if (!prepared_) { + PreparePresent(); + } auto context = context_.lock(); if (!context) { return false; diff --git a/testing/impeller_golden_tests_output.txt b/testing/impeller_golden_tests_output.txt index a7e803cc170f2..8feb287d4003c 100644 --- a/testing/impeller_golden_tests_output.txt +++ b/testing/impeller_golden_tests_output.txt @@ -493,6 +493,9 @@ impeller_Play_AiksTest_CanSaveLayerStandalone_Vulkan.png impeller_Play_AiksTest_ClearBlendWithBlur_Metal.png impeller_Play_AiksTest_ClearBlendWithBlur_OpenGLES.png impeller_Play_AiksTest_ClearBlendWithBlur_Vulkan.png +impeller_Play_AiksTest_ClearBlend_Metal.png +impeller_Play_AiksTest_ClearBlend_OpenGLES.png +impeller_Play_AiksTest_ClearBlend_Vulkan.png impeller_Play_AiksTest_ClearColorOptimizationWhenSubpassIsBiggerThanParentPass_Metal.png impeller_Play_AiksTest_ClearColorOptimizationWhenSubpassIsBiggerThanParentPass_OpenGLES.png impeller_Play_AiksTest_ClearColorOptimizationWhenSubpassIsBiggerThanParentPass_Vulkan.png