From 88b005388d1bd9fbde4280dda491f40b089a3746 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Thu, 1 Aug 2024 11:35:18 -0700 Subject: [PATCH] [Impeller] migrate more AIKS test to DL. (flutter/engine#54267) Part of https://github.com/flutter/flutter/issues/142054 --- .../flutter/impeller/aiks/aiks_unittests.cc | 226 ------------- .../display_list/aiks_dl_basic_unittests.cc | 309 +++++++++++++++++- 2 files changed, 307 insertions(+), 228 deletions(-) diff --git a/engine/src/flutter/impeller/aiks/aiks_unittests.cc b/engine/src/flutter/impeller/aiks/aiks_unittests.cc index ab73885e859..52ae55bb5c0 100644 --- a/engine/src/flutter/impeller/aiks/aiks_unittests.cc +++ b/engine/src/flutter/impeller/aiks/aiks_unittests.cc @@ -1437,152 +1437,6 @@ TEST_P(AiksTest, MatrixImageFilterMagnify) { ASSERT_TRUE(OpenPlaygroundHere(callback)); } -// Render a white circle at the top left corner of the screen. -TEST_P(AiksTest, MatrixImageFilterDoesntCullWhenTranslatedFromOffscreen) { - Canvas canvas; - canvas.Scale(GetContentScale()); - canvas.Translate({100, 100}); - // Draw a circle in a SaveLayer at -300, but move it back on-screen with a - // +300 translation applied by a SaveLayer image filter. - canvas.SaveLayer({ - .image_filter = std::make_shared( - Matrix::MakeTranslation({300, 0}), SamplerDescriptor{}), - }); - canvas.DrawCircle({-300, 0}, 100, {.color = Color::Green()}); - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -// Render a white circle at the top left corner of the screen. -TEST_P(AiksTest, - MatrixImageFilterDoesntCullWhenScaledAndTranslatedFromOffscreen) { - Canvas canvas; - canvas.Scale(GetContentScale()); - canvas.Translate({100, 100}); - // Draw a circle in a SaveLayer at -300, but move it back on-screen with a - // +300 translation applied by a SaveLayer image filter. - canvas.SaveLayer({ - .image_filter = std::make_shared( - Matrix::MakeTranslation({300, 0}) * Matrix::MakeScale({2, 2, 2}), - SamplerDescriptor{}), - }); - canvas.DrawCircle({-150, 0}, 50, {.color = Color::Green()}); - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -// This should be solid red, if you see a little red box this is broken. -TEST_P(AiksTest, ClearColorOptimizationWhenSubpassIsBiggerThanParentPass) { - SetWindowSize({400, 400}); - Canvas canvas; - canvas.Scale(GetContentScale()); - canvas.DrawRect(Rect::MakeLTRB(200, 200, 300, 300), {.color = Color::Red()}); - canvas.SaveLayer({ - .image_filter = std::make_shared( - Matrix::MakeScale({2, 2, 1}), SamplerDescriptor{}), - }); - // Draw a rectangle that would fully cover the parent pass size, but not - // the subpass that it is rendered in. - canvas.DrawRect(Rect::MakeLTRB(0, 0, 400, 400), {.color = Color::Green()}); - // Draw a bigger rectangle to force the subpass to be bigger. - canvas.DrawRect(Rect::MakeLTRB(0, 0, 800, 800), {.color = Color::Red()}); - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, EmptySaveLayerIgnoresPaint) { - Canvas canvas; - canvas.Scale(GetContentScale()); - canvas.DrawPaint(Paint{.color = Color::Red()}); - canvas.ClipRect(Rect::MakeXYWH(100, 100, 200, 200)); - canvas.SaveLayer(Paint{.color = Color::Blue()}); - canvas.Restore(); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, EmptySaveLayerRendersWithClear) { - Canvas canvas; - canvas.Scale(GetContentScale()); - auto image = std::make_shared(CreateTextureForFixture("airplane.jpg")); - canvas.DrawImage(image, {10, 10}, {}); - canvas.ClipRect(Rect::MakeXYWH(100, 100, 200, 200)); - canvas.SaveLayer(Paint{.blend_mode = BlendMode::kClear}); - canvas.Restore(); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, SubpassWithClearColorOptimization) { - Canvas canvas; - - // Use a non-srcOver blend mode to ensure that we don't detect this as an - // opacity peephole optimization. - canvas.SaveLayer( - {.color = Color::Blue().WithAlpha(0.5), .blend_mode = BlendMode::kSource}, - Rect::MakeLTRB(0, 0, 200, 200)); - canvas.DrawPaint( - {.color = Color::BlackTransparent(), .blend_mode = BlendMode::kSource}); - canvas.Restore(); - - canvas.SaveLayer( - {.color = Color::Blue(), .blend_mode = BlendMode::kDestinationOver}); - canvas.Restore(); - - // This playground should appear blank on CI since we are only drawing - // transparent black. If the clear color optimization is broken, the texture - // will be filled with NaNs and may produce a magenta texture on macOS or iOS. - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, ImageColorSourceEffectTransform) { - // Compare with https://fiddle.skia.org/c/6cdc5aefb291fda3833b806ca347a885 - - Canvas canvas; - auto texture = CreateTextureForFixture("monkey.png"); - - canvas.DrawPaint({.color = Color::White()}); - - // Translation - { - Paint paint; - paint.color_source = ColorSource::MakeImage( - texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, - Matrix::MakeTranslation({50, 50})); - canvas.DrawRect(Rect::MakeLTRB(0, 0, 100, 100), paint); - } - - // Rotation/skew - { - canvas.Save(); - canvas.Rotate(Degrees(45)); - Paint paint; - paint.color_source = ColorSource::MakeImage( - texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, - Matrix(1, -1, 0, 0, // - 1, 1, 0, 0, // - 0, 0, 1, 0, // - 0, 0, 0, 1) // - ); - canvas.DrawRect(Rect::MakeLTRB(100, 0, 200, 100), paint); - canvas.Restore(); - } - - // Scale - { - canvas.Translate(Vector2(100, 0)); - canvas.Scale(Vector2(100, 100)); - Paint paint; - paint.color_source = ColorSource::MakeImage( - texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, - Matrix::MakeScale(Vector2(0.005, 0.005))); - canvas.DrawRect(Rect::MakeLTRB(0, 0, 1, 1), paint); - } - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - TEST_P(AiksTest, CorrectClipDepthAssignedToEntities) { Canvas canvas; // Depth 1 (base pass) canvas.DrawRRect(Rect::MakeLTRB(0, 0, 100, 100), {10, 10}, {}); // Depth 2 @@ -1630,86 +1484,6 @@ TEST_P(AiksTest, CorrectClipDepthAssignedToEntities) { } } -TEST_P(AiksTest, CanDrawPerspectiveTransformWithClips) { - // Avoiding `GetSecondsElapsed()` to reduce risk of golden flakiness. - int time = 0; - auto callback = [&](AiksContext& renderer) -> std::optional { - Canvas canvas; - - canvas.Save(); - { - canvas.Translate({300, 300}); - - // 1. Draw/restore a clip before drawing the image, which will get drawn - // to the depth buffer behind the image. - canvas.Save(); - { - canvas.DrawPaint({.color = Color::Green()}); - canvas.ClipRect(Rect::MakeLTRB(-180, -180, 180, 180), - Entity::ClipOperation::kDifference); - canvas.DrawPaint({.color = Color::Black()}); - } - canvas.Restore(); // Restore rectangle difference clip. - - canvas.Save(); - { - // 2. Draw an oval clip that applies to the image, which will get drawn - // in front of the image on the depth buffer. - canvas.ClipOval(Rect::MakeLTRB(-200, -200, 200, 200)); - - // 3. Draw the rotating image with a perspective transform. - canvas.Transform( - Matrix(1.0, 0.0, 0.0, 0.0, // - 0.0, 1.0, 0.0, 0.0, // - 0.0, 0.0, 1.0, 0.003, // - 0.0, 0.0, 0.0, 1.0) * // - Matrix::MakeRotationY({Radians{-1.0f + (time++ / 60.0f)}})); - auto image = - std::make_shared(CreateTextureForFixture("airplane.jpg")); - canvas.DrawImage(image, -Point(image->GetSize()) / 2, {}); - } - canvas.Restore(); // Restore oval intersect clip. - - // 4. Draw a semi-translucent blue circle atop all previous draws. - canvas.DrawCircle({}, 230, {.color = Color::Blue().WithAlpha(0.4)}); - } - canvas.Restore(); // Restore translation. - - return canvas.EndRecordingAsPicture(); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(AiksTest, CanRenderClippedBackdropFilter) { - Canvas canvas; - Paint paint; - - canvas.Scale(GetContentScale()); - - // Draw something interesting in the background. - 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.color_source = ColorSource::MakeLinearGradient( - {0, 0}, {100, 100}, std::move(colors), std::move(stops), - Entity::TileMode::kRepeat, {}); - canvas.DrawPaint(paint); - - Rect clip_rect = Rect::MakeLTRB(50, 50, 400, 300); - - // Draw a clipped SaveLayer, where the clip coverage and SaveLayer size are - // the same. - canvas.ClipRRect(clip_rect, Size(100, 100), - Entity::ClipOperation::kIntersect); - canvas.SaveLayer({}, clip_rect, - ImageFilter::MakeFromColorFilter(*ColorFilter::MakeBlend( - BlendMode::kExclusion, Color::Red()))); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - TEST_P(AiksTest, MipmapGenerationWorksCorrectly) { TextureDescriptor texture_descriptor; texture_descriptor.size = ISize{1024, 1024}; diff --git a/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc b/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc index 02224c075c9..078276dc829 100644 --- a/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc +++ b/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc @@ -2,6 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#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_image_filter.h" #include "flutter/impeller/aiks/aiks_unittests.h" #include "flutter/display_list/dl_blend_mode.h" @@ -12,10 +18,17 @@ #include "flutter/impeller/geometry/scalar.h" #include "flutter/testing/display_list_testing.h" #include "flutter/testing/testing.h" +#include "include/core/SkMatrix.h" namespace impeller { namespace testing { +namespace { +SkM44 FromImpellerMatrix(const Matrix& matrix) { + return SkM44::ColMajor(matrix.m); +} +} // namespace + using namespace flutter; TEST_P(AiksTest, CanRenderColoredRect) { @@ -334,8 +347,14 @@ TEST_P(AiksTest, CanRenderDifferentShapesWithSameColorSource) { 1.0, }; - paint.setColorSource(DlColorSource::MakeLinear({0, 0}, {100, 100}, 2, colors, - stops, DlTileMode::kRepeat)); + paint.setColorSource(DlColorSource::MakeLinear( + /*start_point=*/{0, 0}, // + /*end_point=*/{100, 100}, // + /*stop_count=*/2, // + /*colors=*/colors, // + /*stops=*/stops, // + /*tile_mode=*/DlTileMode::kRepeat // + )); builder.Save(); builder.Translate(100, 100); @@ -762,5 +781,291 @@ TEST_P(AiksTest, SolidColorCirclesOvalsRRectsMaskBlurCorrectly) { ASSERT_TRUE(OpenPlaygroundHere(dl)); } +TEST_P(AiksTest, CanRenderClippedBackdropFilter) { + DisplayListBuilder builder; + + builder.Scale(GetContentScale().x, GetContentScale().y); + + // Draw something interesting in the background. + 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; + paint.setColorSource(DlColorSource::MakeLinear( + /*start_point=*/{0, 0}, // + /*end_point=*/{100, 100}, // + /*stop_count=*/2, // + /*colors=*/colors.data(), // + /*stops=*/stops.data(), // + /*tile_mode=*/DlTileMode::kRepeat // + )); + + builder.DrawPaint(paint); + + SkRect clip_rect = SkRect::MakeLTRB(50, 50, 400, 300); + SkRRect clip_rrect = SkRRect::MakeRectXY(clip_rect, 100, 100); + + // Draw a clipped SaveLayer, where the clip coverage and SaveLayer size are + // the same. + builder.ClipRRect(clip_rrect, DlCanvas::ClipOp::kIntersect); + + DlPaint save_paint; + auto backdrop_filter = std::make_shared( + DlBlendColorFilter::Make(DlColor::kRed(), DlBlendMode::kExclusion)); + builder.SaveLayer(&clip_rect, &save_paint, backdrop_filter.get()); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanDrawPerspectiveTransformWithClips) { + // Avoiding `GetSecondsElapsed()` to reduce risk of golden flakiness. + int time = 0; + auto callback = [&]() -> sk_sp { + DisplayListBuilder builder; + + builder.Save(); + { + builder.Translate(300, 300); + + // 1. Draw/restore a clip before drawing the image, which will get drawn + // to the depth buffer behind the image. + builder.Save(); + { + DlPaint paint; + paint.setColor(DlColor::kGreen()); + builder.DrawPaint(paint); + builder.ClipRect(SkRect::MakeLTRB(-180, -180, 180, 180), + DlCanvas::ClipOp::kDifference); + + paint.setColor(DlColor::kBlack()); + builder.DrawPaint(paint); + } + builder.Restore(); // Restore rectangle difference clip. + + builder.Save(); + { + // 2. Draw an oval clip that applies to the image, which will get drawn + // in front of the image on the depth buffer. + builder.ClipOval(SkRect::MakeLTRB(-200, -200, 200, 200)); + + Matrix result = + Matrix(1.0, 0.0, 0.0, 0.0, // + 0.0, 1.0, 0.0, 0.0, // + 0.0, 0.0, 1.0, 0.003, // + 0.0, 0.0, 0.0, 1.0) * + Matrix::MakeRotationY({Radians{-1.0f + (time++ / 60.0f)}}); + + // 3. Draw the rotating image with a perspective transform. + builder.Transform(FromImpellerMatrix(result)); + + auto image = + DlImageImpeller::Make(CreateTextureForFixture("airplane.jpg")); + auto position = -SkPoint::Make(image->dimensions().fWidth, + image->dimensions().fHeight) * + 0.5; + builder.DrawImage(image, position, {}); + } + builder.Restore(); // Restore oval intersect clip. + + // 4. Draw a semi-translucent blue circle atop all previous draws. + DlPaint paint; + paint.setColor(DlColor::kBlue().modulateOpacity(0.4)); + builder.DrawCircle({}, 230, paint); + } + builder.Restore(); // Restore translation. + + return builder.Build(); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(AiksTest, ImageColorSourceEffectTransform) { + // Compare with https://fiddle.skia.org/c/6cdc5aefb291fda3833b806ca347a885 + + DisplayListBuilder builder; + auto texture = DlImageImpeller::Make(CreateTextureForFixture("monkey.png")); + + DlPaint paint; + paint.setColor(DlColor::kWhite()); + builder.DrawPaint(paint); + + // Translation + { + SkMatrix matrix = SkMatrix::Translate(50, 50); + DlPaint paint; + paint.setColorSource(std::make_shared( + texture, DlTileMode::kRepeat, DlTileMode::kRepeat, + DlImageSampling::kNearestNeighbor, &matrix)); + + builder.DrawRect(SkRect::MakeLTRB(0, 0, 100, 100), paint); + } + + // Rotation/skew + { + builder.Save(); + builder.Rotate(45); + DlPaint paint; + + Matrix impeller_matrix(1, -1, 0, 0, // + 1, 1, 0, 0, // + 0, 0, 1, 0, // + 0, 0, 0, 1); + SkMatrix matrix = SkM44::ColMajor(impeller_matrix.m).asM33(); + paint.setColorSource(std::make_shared( + texture, DlTileMode::kRepeat, DlTileMode::kRepeat, + DlImageSampling::kNearestNeighbor, &matrix)); + builder.DrawRect(SkRect::MakeLTRB(100, 0, 200, 100), paint); + builder.Restore(); + } + + // Scale + { + builder.Translate(100, 0); + builder.Scale(100, 100); + DlPaint paint; + + SkMatrix matrix = SkMatrix::Scale(0.005, 0.005); + paint.setColorSource(std::make_shared( + texture, DlTileMode::kRepeat, DlTileMode::kRepeat, + DlImageSampling::kNearestNeighbor, &matrix)); + + builder.DrawRect(SkRect::MakeLTRB(0, 0, 1, 1), paint); + } + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, SubpassWithClearColorOptimization) { + DisplayListBuilder builder; + + // Use a non-srcOver blend mode to ensure that we don't detect this as an + // opacity peephole optimization. + DlPaint paint; + paint.setColor(DlColor::kBlue().modulateOpacity(0.5)); + paint.setBlendMode(DlBlendMode::kSrc); + + SkRect bounds = SkRect::MakeLTRB(0, 0, 200, 200); + builder.SaveLayer(&bounds, &paint); + + paint.setColor(DlColor::kTransparent()); + paint.setBlendMode(DlBlendMode::kSrc); + builder.DrawPaint(paint); + builder.Restore(); + + paint.setColor(DlColor::kBlue()); + paint.setBlendMode(DlBlendMode::kDstOver); + builder.SaveLayer(nullptr, &paint); + builder.Restore(); + + // This playground should appear blank on CI since we are only drawing + // transparent black. If the clear color optimization is broken, the texture + // will be filled with NaNs and may produce a magenta texture on macOS or iOS. + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +// Render a white circle at the top left corner of the screen. +TEST_P(AiksTest, MatrixImageFilterDoesntCullWhenTranslatedFromOffscreen) { + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + builder.Translate(100, 100); + // Draw a circle in a SaveLayer at -300, but move it back on-screen with a + // +300 translation applied by a SaveLayer image filter. + DlPaint paint; + SkMatrix translate = SkMatrix::Translate(300, 0); + paint.setImageFilter( + DlMatrixImageFilter::Make(translate, DlImageSampling::kLinear)); + builder.SaveLayer(nullptr, &paint); + + DlPaint circle_paint; + circle_paint.setColor(DlColor::kGreen()); + builder.DrawCircle({-300, 0}, 100, circle_paint); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +// Render a white circle at the top left corner of the screen. +TEST_P(AiksTest, + MatrixImageFilterDoesntCullWhenScaledAndTranslatedFromOffscreen) { + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + builder.Translate(100, 100); + // Draw a circle in a SaveLayer at -300, but move it back on-screen with a + // +300 translation applied by a SaveLayer image filter. + + DlPaint paint; + paint.setImageFilter(DlMatrixImageFilter::Make( + SkMatrix::Translate(300, 0) * SkMatrix::Scale(2, 2), + DlImageSampling::kNearestNeighbor)); + builder.SaveLayer(nullptr, &paint); + + DlPaint circle_paint; + circle_paint.setColor(DlColor::kGreen()); + builder.DrawCircle({-150, 0}, 50, circle_paint); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +// This should be solid red, if you see a little red box this is broken. +TEST_P(AiksTest, ClearColorOptimizationWhenSubpassIsBiggerThanParentPass) { + SetWindowSize({400, 400}); + DisplayListBuilder builder; + + builder.Scale(GetContentScale().x, GetContentScale().y); + + DlPaint paint; + paint.setColor(DlColor::kRed()); + builder.DrawRect(SkRect::MakeLTRB(200, 200, 300, 300), paint); + + paint.setImageFilter(DlMatrixImageFilter::Make(SkMatrix::Scale(2, 2), + DlImageSampling::kLinear)); + builder.SaveLayer(nullptr, &paint); + // Draw a rectangle that would fully cover the parent pass size, but not + // the subpass that it is rendered in. + paint.setColor(DlColor::kGreen()); + builder.DrawRect(SkRect::MakeLTRB(0, 0, 400, 400), paint); + // Draw a bigger rectangle to force the subpass to be bigger. + + paint.setColor(DlColor::kRed()); + builder.DrawRect(SkRect::MakeLTRB(0, 0, 800, 800), paint); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, EmptySaveLayerIgnoresPaint) { + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + + DlPaint paint; + paint.setColor(DlColor::kRed()); + builder.DrawPaint(paint); + builder.ClipRect(SkRect::MakeXYWH(100, 100, 200, 200)); + paint.setColor(DlColor::kBlue()); + builder.SaveLayer(nullptr, &paint); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, EmptySaveLayerRendersWithClear) { + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + auto image = DlImageImpeller::Make(CreateTextureForFixture("airplane.jpg")); + builder.DrawImage(image, {10, 10}, {}); + builder.ClipRect(SkRect::MakeXYWH(100, 100, 200, 200)); + + DlPaint paint; + paint.setBlendMode(DlBlendMode::kClear); + builder.SaveLayer(nullptr, &paint); + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + } // namespace testing } // namespace impeller