[Impeller] migrate blend tests to DL. (flutter/engine#54457)

Part of https://github.com/flutter/flutter/issues/142054
This commit is contained in:
Jonah Williams 2024-08-10 09:49:19 -07:00 committed by GitHub
parent c96be54305
commit 720cae8ed3
11 changed files with 918 additions and 728 deletions

View File

@ -144,6 +144,7 @@
../../../flutter/impeller/core/allocator_unittests.cc ../../../flutter/impeller/core/allocator_unittests.cc
../../../flutter/impeller/display_list/aiks_dl_atlas_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_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_clip_unittests.cc
../../../flutter/impeller/display_list/aiks_dl_gradient_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_gradient_unittests.cc
../../../flutter/impeller/display_list/aiks_dl_opacity_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_opacity_unittests.cc

View File

@ -665,7 +665,7 @@ class DlColorFilterImageFilter final : public DlImageFilter {
class DlLocalMatrixImageFilter final : public DlImageFilter { class DlLocalMatrixImageFilter final : public DlImageFilter {
public: public:
explicit DlLocalMatrixImageFilter(const SkMatrix& matrix, explicit DlLocalMatrixImageFilter(const SkMatrix& matrix,
std::shared_ptr<DlImageFilter> filter) std::shared_ptr<const DlImageFilter> filter)
: matrix_(matrix), image_filter_(std::move(filter)) {} : matrix_(matrix), image_filter_(std::move(filter)) {}
explicit DlLocalMatrixImageFilter(const DlLocalMatrixImageFilter* filter) explicit DlLocalMatrixImageFilter(const DlLocalMatrixImageFilter* filter)
: DlLocalMatrixImageFilter(filter->matrix_, filter->image_filter_) {} : DlLocalMatrixImageFilter(filter->matrix_, filter->image_filter_) {}
@ -682,7 +682,7 @@ class DlLocalMatrixImageFilter final : public DlImageFilter {
const SkMatrix& matrix() const { return matrix_; } const SkMatrix& matrix() const { return matrix_; }
const std::shared_ptr<DlImageFilter> image_filter() const { const std::shared_ptr<const DlImageFilter> image_filter() const {
return image_filter_; return image_filter_;
} }
@ -738,7 +738,7 @@ class DlLocalMatrixImageFilter final : public DlImageFilter {
private: private:
SkMatrix matrix_; SkMatrix matrix_;
std::shared_ptr<DlImageFilter> image_filter_; std::shared_ptr<const DlImageFilter> image_filter_;
}; };
} // namespace flutter } // namespace flutter

View File

@ -18,166 +18,6 @@
namespace impeller { namespace impeller {
namespace testing { 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<Color> colors = {Color{0.9568, 0.2627, 0.2118, 1.0},
Color{0.1294, 0.5882, 0.9529, 1.0}};
std::vector<Scalar> 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<Image>(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<Image>(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}, #define BLEND_MODE_TUPLE(blend_mode) {#blend_mode, BlendMode::k##blend_mode},
struct BlendModeSelection { struct BlendModeSelection {
@ -338,258 +178,5 @@ TEST_P(AiksTest, ColorWheel) {
ASSERT_TRUE(OpenPlaygroundHere(callback)); 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<Image>& src_image,
const std::shared_ptr<Image>& 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>({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<Image>( \
CreateTextureForFixture("blend_mode_src.png")); \
auto dst_image = std::make_shared<Image>( \
CreateTextureForFixture("blend_mode_dst.png")); \
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> { \
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<Image>( \
CreateTextureForFixture("blend_mode_src.png")); \
auto dst_image = std::make_shared<Image>( \
CreateTextureForFixture("blend_mode_dst.png")); \
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> { \
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<Picture> {
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<float*>(&background));
ImGui::ColorEdit4("Foreground", reinterpret_cast<float*>(&foreground));
ImGui::ListBox("Blend mode", &current_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<BlendMode>(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<Image>(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<Image>(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<Image>(texture), {20, 20},
{.blend_mode = BlendMode::kMultiply});
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
} // namespace testing } // namespace testing
} // namespace impeller } // namespace impeller

View File

@ -354,95 +354,6 @@ TEST_P(AiksTest, ClearColorOptimizationDoesNotApplyForBackdropFilters) {
EXPECT_FALSE(actual_color.has_value()); EXPECT_FALSE(actual_color.has_value());
} }
TEST_P(AiksTest, ImageFilteredSaveLayerWithUnboundedContents) {
Canvas canvas;
canvas.Scale(GetContentScale());
auto test = [&canvas](const std::shared_ptr<ImageFilter>& 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) { TEST_P(AiksTest, OpaqueEntitiesGetCoercedToSource) {
Canvas canvas; Canvas canvas;
canvas.Scale(Vector2(1.618, 1.618)); canvas.Scale(Vector2(1.618, 1.618));
@ -472,57 +383,6 @@ TEST_P(AiksTest, OpaqueEntitiesGetCoercedToSource) {
ASSERT_EQ(entity[0].GetBlendMode(), BlendMode::kSource); 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) { TEST_P(AiksTest, SolidColorApplyColorFilter) {
auto contents = SolidColorContents(); auto contents = SolidColorContents();
contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75)); contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75));
@ -534,65 +394,6 @@ TEST_P(AiksTest, SolidColorApplyColorFilter) {
Color(0.424452, 0.828743, 0.79105, 0.9375)); 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<Texture> 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<Picture> {
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<Image>(CreateTextureForFixture("airplane.jpg"));
canvas.Translate({600, -200});
canvas.SaveLayer({
.image_filter = std::make_shared<MatrixImageFilter>(
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) { TEST_P(AiksTest, CorrectClipDepthAssignedToEntities) {
Canvas canvas; // Depth 1 (base pass) Canvas canvas; // Depth 1 (base pass)
canvas.DrawRRect(Rect::MakeLTRB(0, 0, 100, 100), {10, 10}, {}); // Depth 2 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<uint8_t> 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<fml::NonOwnedMapping>(
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<Image>(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<uint8_t> 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<fml::NonOwnedMapping>(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<Image>(bridge);
Canvas canvas;
canvas.DrawImage(image, {0, 0}, {});
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
} // namespace testing } // namespace testing
} // namespace impeller } // namespace impeller

View File

@ -51,6 +51,7 @@ template("display_list_unittests_component") {
predefined_sources = [ predefined_sources = [
"aiks_dl_atlas_unittests.cc", "aiks_dl_atlas_unittests.cc",
"aiks_dl_basic_unittests.cc", "aiks_dl_basic_unittests.cc",
"aiks_dl_blend_unittests.cc",
"aiks_dl_clip_unittests.cc", "aiks_dl_clip_unittests.cc",
"aiks_dl_gradient_unittests.cc", "aiks_dl_gradient_unittests.cc",
"aiks_dl_opacity_unittests.cc", "aiks_dl_opacity_unittests.cc",

View File

@ -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 <memory>
#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<const char*> blend_mode_names;
std::vector<BlendMode> blend_mode_values;
};
static BlendModeSelection GetBlendModeSelection() {
std::vector<const char*> blend_mode_names;
std::vector<BlendMode> blend_mode_values;
{
const std::vector<std::tuple<const char*, BlendMode>> blends = {
IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TUPLE)};
assert(blends.size() ==
static_cast<size_t>(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<DlColor> colors = {DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0),
DlColor::RGBA(0.1294, 0.5882, 0.9529, 1.0)};
std::vector<Scalar> 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<DisplayList> BlendModeTest(Vector2 content_scale,
BlendMode blend_mode,
const sk_sp<DlImageImpeller>& src_image,
const sk_sp<DlImageImpeller>& 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>({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<DlBlendMode>(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<DlBlendMode>(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<DlBlendMode>(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<DisplayList> { \
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<DisplayList> { \
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<DisplayList> {
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<float*>(&background));
ImGui::ColorEdit4("Foreground", reinterpret_cast<float*>(&foreground));
ImGui::ListBox("Blend mode", &current_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<DlBlendMode>(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

View File

@ -20,6 +20,7 @@
#include "imgui.h" #include "imgui.h"
#include "impeller/display_list/dl_image_impeller.h" #include "impeller/display_list/dl_image_impeller.h"
#include "impeller/geometry/scalar.h" #include "impeller/geometry/scalar.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkRSXform.h" #include "include/core/SkRSXform.h"
#include "include/core/SkRefCnt.h" #include "include/core/SkRefCnt.h"
@ -467,5 +468,337 @@ TEST_P(AiksTest, CanDrawPointsWithTextureMap) {
ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); 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<uint8_t> 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<fml::NonOwnedMapping>(
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<uint8_t> 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<fml::NonOwnedMapping>(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<Texture> 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<DlImageColorSource>(
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<DisplayList> {
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<const DlImageFilter>& 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<DlBlurImageFilter>(10.0, 10.0, DlTileMode::kDecal));
builder.Translate(200.0, 0.0);
test(std::make_shared<DlDilateImageFilter>(10.0, 10.0));
builder.Translate(200.0, 0.0);
test(std::make_shared<DlErodeImageFilter>(10.0, 10.0));
builder.Translate(-400.0, 200.0);
SkMatrix sk_matrix = SkMatrix::RotateDeg(10);
auto rotate_filter = std::make_shared<DlMatrixImageFilter>(
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<DlColorFilterImageFilter>(
std::make_shared<DlMatrixColorFilter>(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<DlLocalMatrixImageFilter>(
SkMatrix::Translate(25.0, 25.0), rotate_filter));
builder.Translate(200.0, 0.0);
test(std::make_shared<DlLocalMatrixImageFilter>(
SkMatrix::Translate(25.0, 25.0), rgb_swap_filter));
builder.Translate(200.0, 0.0);
test(std::make_shared<DlLocalMatrixImageFilter>(
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 testing
} // namespace impeller } // namespace impeller

View File

@ -600,7 +600,7 @@ void DlDispatcherBase::saveLayer(const SkRect& bounds,
? ContentBoundsPromise::kMayClipContents ? ContentBoundsPromise::kMayClipContents
: ContentBoundsPromise::kContainsContents; : ContentBoundsPromise::kContainsContents;
std::optional<Rect> impeller_bounds; std::optional<Rect> impeller_bounds;
if (!options.content_is_unbounded()) { if (!options.content_is_unbounded() || options.bounds_from_caller()) {
impeller_bounds = skia_conversions::ToRect(bounds); impeller_bounds = skia_conversions::ToRect(bounds);
} }

View File

@ -66,7 +66,7 @@ class SurfaceMTL final : public Surface {
} }
/// @brief Perform the final blit and trigger end of frame workloads. /// @brief Perform the final blit and trigger end of frame workloads.
bool PreparePresent(); bool PreparePresent() const;
// |Surface| // |Surface|
bool Present() const override; bool Present() const override;
@ -85,7 +85,7 @@ class SurfaceMTL final : public Surface {
std::optional<IRect> clip_rect_; std::optional<IRect> clip_rect_;
bool frame_boundary_ = false; bool frame_boundary_ = false;
bool present_with_transaction_ = false; bool present_with_transaction_ = false;
bool prepared_ = false; mutable bool prepared_ = false;
static bool ShouldPerformPartialRepaint(std::optional<IRect> damage_rect); static bool ShouldPerformPartialRepaint(std::optional<IRect> damage_rect);

View File

@ -222,7 +222,7 @@ IRect SurfaceMTL::coverage() const {
return IRect::MakeSize(resolve_texture_->GetSize()); return IRect::MakeSize(resolve_texture_->GetSize());
} }
bool SurfaceMTL::PreparePresent() { bool SurfaceMTL::PreparePresent() const {
auto context = context_.lock(); auto context = context_.lock();
if (!context) { if (!context) {
return false; return false;
@ -265,7 +265,9 @@ bool SurfaceMTL::PreparePresent() {
// |Surface| // |Surface|
bool SurfaceMTL::Present() const { bool SurfaceMTL::Present() const {
FML_CHECK(prepared_); if (!prepared_) {
PreparePresent();
}
auto context = context_.lock(); auto context = context_.lock();
if (!context) { if (!context) {
return false; return false;

View File

@ -493,6 +493,9 @@ impeller_Play_AiksTest_CanSaveLayerStandalone_Vulkan.png
impeller_Play_AiksTest_ClearBlendWithBlur_Metal.png impeller_Play_AiksTest_ClearBlendWithBlur_Metal.png
impeller_Play_AiksTest_ClearBlendWithBlur_OpenGLES.png impeller_Play_AiksTest_ClearBlendWithBlur_OpenGLES.png
impeller_Play_AiksTest_ClearBlendWithBlur_Vulkan.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_Metal.png
impeller_Play_AiksTest_ClearColorOptimizationWhenSubpassIsBiggerThanParentPass_OpenGLES.png impeller_Play_AiksTest_ClearColorOptimizationWhenSubpassIsBiggerThanParentPass_OpenGLES.png
impeller_Play_AiksTest_ClearColorOptimizationWhenSubpassIsBiggerThanParentPass_Vulkan.png impeller_Play_AiksTest_ClearColorOptimizationWhenSubpassIsBiggerThanParentPass_Vulkan.png