diff --git a/impeller/display_list/BUILD.gn b/impeller/display_list/BUILD.gn index 67c0620302d86..a468e13a55e97 100644 --- a/impeller/display_list/BUILD.gn +++ b/impeller/display_list/BUILD.gn @@ -91,6 +91,7 @@ template("display_list_unittests_component") { deps += [ ":display_list", "../playground:playground_test", + "//flutter/impeller/golden_tests:screenshot", "//flutter/impeller/scene", "//flutter/impeller/typographer/backends/stb:typographer_stb_backend", "//flutter/third_party/txt", diff --git a/impeller/display_list/dl_golden_blur_unittests.cc b/impeller/display_list/dl_golden_blur_unittests.cc index bd9e293080c8f..4dd070682bf8c 100644 --- a/impeller/display_list/dl_golden_blur_unittests.cc +++ b/impeller/display_list/dl_golden_blur_unittests.cc @@ -6,6 +6,7 @@ #include "flutter/display_list/dl_builder.h" #include "flutter/display_list/effects/dl_mask_filter.h" +#include "flutter/impeller/golden_tests/screenshot.h" #include "flutter/testing/testing.h" #include "gtest/gtest.h" #include "impeller/typographer/backends/skia/text_frame_skia.h" @@ -110,5 +111,120 @@ TEST_P(DlGoldenTest, TextBlurMaskFilterDisrespectCTM) { ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); } +namespace { +double CalculateDistance(const uint8_t* left, const uint8_t* right) { + double diff[4] = { + static_cast(left[0]) - right[0], // + static_cast(left[1]) - right[1], // + static_cast(left[2]) - right[2], // + static_cast(left[3]) - right[3] // + }; + return sqrt((diff[0] * diff[0]) + // + (diff[1] * diff[1]) + // + (diff[2] * diff[2]) + // + (diff[3] * diff[3])); +} + +double RMSE(const impeller::testing::Screenshot* left, + const impeller::testing::Screenshot* right) { + FML_CHECK(left); + FML_CHECK(right); + FML_CHECK(left->GetWidth() == right->GetWidth()); + FML_CHECK(left->GetHeight() == right->GetHeight()); + + int64_t samples = left->GetWidth() * left->GetHeight(); + double tally = 0; + + const uint8_t* left_ptr = left->GetBytes(); + const uint8_t* right_ptr = right->GetBytes(); + for (int64_t i = 0; i < samples; ++i, left_ptr += 4, right_ptr += 4) { + double distance = CalculateDistance(left_ptr, right_ptr); + tally += distance * distance; + } + + return sqrt(tally / static_cast(samples)); +} +} // namespace + +// This is a test to make sure that we don't regress "shimmering" in the +// gaussian blur. Shimmering is abrupt changes in signal when making tiny +// changes to the blur parameters. +// +// See also: +// - https://github.com/flutter/flutter/issues/152195 +TEST_P(DlGoldenTest, ShimmerTest) { + impeller::Point content_scale = GetContentScale(); + auto draw = [&](DlCanvas* canvas, const std::vector>& images, + float sigma) { + canvas->DrawColor(DlColor(0xff111111)); + canvas->Scale(content_scale.x, content_scale.y); + + DlPaint paint; + canvas->DrawImage(images[0], SkPoint::Make(10.135, 10.36334), + DlImageSampling::kLinear, &paint); + + SkRect save_layer_bounds = SkRect::MakeLTRB(0, 0, 1024, 768); + DlBlurImageFilter blur(sigma, sigma, DlTileMode::kDecal); + canvas->ClipRect(SkRect::MakeLTRB(11.125, 10.3737, 911.25, 755.3333)); + canvas->SaveLayer(&save_layer_bounds, /*paint=*/nullptr, &blur); + canvas->Restore(); + }; + + std::vector> images; + images.emplace_back(CreateDlImageForFixture("boston.jpg")); + + auto make_screenshot = [&](float sigma) { + DisplayListBuilder builder; + draw(&builder, images, sigma); + + std::unique_ptr screenshot = + MakeScreenshot(builder.Build()); + return screenshot; + }; + + float start_sigma = 10.0f; + std::unique_ptr left = + make_screenshot(start_sigma); + if (!left) { + GTEST_SKIP() << "making screenshots not supported."; + } + + double average_rmse = 0.0; + const int32_t sample_count = 200; + for (int i = 1; i <= sample_count; ++i) { + float sigma = start_sigma + (i / 2.f); + std::unique_ptr right = + make_screenshot(sigma); + double rmse = RMSE(left.get(), right.get()); + average_rmse += rmse; + + // To debug this output the frames can be written out to disk then + // transformed to a video with ffmpeg. + // + // ## save images command + // std::stringstream ss; + // ss << "_" << std::setw(3) << std::setfill('0') << (i - 1); + // SaveScreenshot(std::move(left), ss.str()); + // + // ## ffmpeg command + // ``` + // ffmpeg -framerate 30 -pattern_type glob -i '*.png' \ + // -c:v libx264 -pix_fmt yuv420p out.mp4 + // ``` + left = std::move(right); + } + + average_rmse = average_rmse / sample_count; + + // This is a somewhat arbitrary threshold. It could be increased if we wanted. + // In the problematic cases previously we should values like 28. Before + // increasing this you should manually inspect the behavior in + // `AiksTest.GaussianBlurAnimatedBackdrop`. Average RMSE is a able to catch + // shimmer but it isn't perfect. + EXPECT_TRUE(average_rmse < 1.0) << "average_rmse: " << average_rmse; + // An average rmse of 0 would mean that the blur isn't blurring. + EXPECT_TRUE(average_rmse >= 0.0) << "average_rmse: " << average_rmse; +} + } // namespace testing } // namespace flutter diff --git a/impeller/display_list/dl_playground.cc b/impeller/display_list/dl_playground.cc index 7d9712640912b..cf74a095465cd 100644 --- a/impeller/display_list/dl_playground.cc +++ b/impeller/display_list/dl_playground.cc @@ -71,6 +71,11 @@ bool DlPlayground::OpenPlaygroundHere(DisplayListPlaygroundCallback callback) { }); } +std::unique_ptr DlPlayground::MakeScreenshot( + const sk_sp& list) { + return nullptr; +} + SkFont DlPlayground::CreateTestFontOfSize(SkScalar scalar) { static constexpr const char* kTestFontFixture = "Roboto-Regular.ttf"; auto mapping = flutter::testing::OpenFixtureAsSkData(kTestFontFixture); diff --git a/impeller/display_list/dl_playground.h b/impeller/display_list/dl_playground.h index 3bc9e48d97733..3159a44194b45 100644 --- a/impeller/display_list/dl_playground.h +++ b/impeller/display_list/dl_playground.h @@ -7,6 +7,7 @@ #include "flutter/display_list/display_list.h" #include "flutter/display_list/dl_builder.h" +#include "flutter/impeller/golden_tests/screenshot.h" #include "impeller/playground/playground_test.h" #include "third_party/skia/include/core/SkFont.h" @@ -27,6 +28,9 @@ class DlPlayground : public PlaygroundTest { bool OpenPlaygroundHere(DisplayListPlaygroundCallback callback); + std::unique_ptr MakeScreenshot( + const sk_sp& list); + SkFont CreateTestFontOfSize(SkScalar scalar); SkFont CreateTestFont(); diff --git a/impeller/golden_tests/BUILD.gn b/impeller/golden_tests/BUILD.gn index cd1cae7cb8d58..56713ad075638 100644 --- a/impeller/golden_tests/BUILD.gn +++ b/impeller/golden_tests/BUILD.gn @@ -12,6 +12,7 @@ impeller_component("golden_playground_test") { deps = [ ":digest", + ":screenshot", "//flutter/fml", "//flutter/impeller/aiks", "//flutter/impeller/display_list:display_list", @@ -44,6 +45,11 @@ impeller_component("digest") { deps = [ "//flutter/fml" ] } +impeller_component("screenshot") { + testonly = true + sources = [ "screenshot.h" ] +} + if (is_mac) { impeller_component("metal_screenshot") { testonly = true @@ -53,13 +59,13 @@ if (is_mac) { "metal_screenshot.mm", "metal_screenshotter.h", "metal_screenshotter.mm", - "screenshot.h", "screenshotter.h", "vulkan_screenshotter.h", "vulkan_screenshotter.mm", ] deps = [ + ":screenshot", "//flutter/impeller/aiks", "//flutter/impeller/display_list", "//flutter/impeller/playground", diff --git a/impeller/golden_tests/golden_playground_test.h b/impeller/golden_tests/golden_playground_test.h index b38bd184c2184..23489572e4220 100644 --- a/impeller/golden_tests/golden_playground_test.h +++ b/impeller/golden_tests/golden_playground_test.h @@ -10,6 +10,7 @@ #include "flutter/display_list/display_list.h" #include "flutter/display_list/image/dl_image.h" #include "flutter/impeller/aiks/aiks_context.h" +#include "flutter/impeller/golden_tests/screenshot.h" #include "flutter/impeller/playground/playground.h" #include "flutter/impeller/renderer/render_target.h" #include "flutter/testing/testing.h" @@ -51,6 +52,12 @@ class GoldenPlaygroundTest bool OpenPlaygroundHere(const sk_sp& list); + std::unique_ptr MakeScreenshot( + const sk_sp& list); + + static bool SaveScreenshot(std::unique_ptr screenshot, + const std::string& postfix = ""); + static bool ImGuiBegin(const char* name, bool* p_open, ImGuiWindowFlags flags); diff --git a/impeller/golden_tests/golden_playground_test_mac.cc b/impeller/golden_tests/golden_playground_test_mac.cc index 0803f42f22798..4d61cf74cc78f 100644 --- a/impeller/golden_tests/golden_playground_test_mac.cc +++ b/impeller/golden_tests/golden_playground_test_mac.cc @@ -140,17 +140,20 @@ std::string GetTestName() { return result; } -std::string GetGoldenFilename() { - return GetTestName() + ".png"; +std::string GetGoldenFilename(const std::string& postfix) { + return GetTestName() + postfix + ".png"; } +} // namespace -bool SaveScreenshot(std::unique_ptr screenshot) { +bool GoldenPlaygroundTest::SaveScreenshot( + std::unique_ptr screenshot, + const std::string& postfix) { if (!screenshot || !screenshot->GetBytes()) { FML_LOG(ERROR) << "Failed to collect screenshot for test " << GetTestName(); return false; } std::string test_name = GetTestName(); - std::string filename = GetGoldenFilename(); + std::string filename = GetGoldenFilename(postfix); testing::GoldenDigest::Instance()->AddImage( test_name, filename, screenshot->GetWidth(), screenshot->GetHeight()); if (!screenshot->WriteToPNG( @@ -161,8 +164,6 @@ bool SaveScreenshot(std::unique_ptr screenshot) { return true; } -} // namespace - struct GoldenPlaygroundTest::GoldenPlaygroundTestImpl { std::unique_ptr test_vulkan_playground; std::unique_ptr test_opengl_playground; @@ -396,4 +397,19 @@ fml::Status GoldenPlaygroundTest::SetCapabilities( return pimpl_->screenshotter->GetPlayground().SetCapabilities(capabilities); } +std::unique_ptr GoldenPlaygroundTest::MakeScreenshot( + const sk_sp& list) { + AiksContext renderer(GetContext(), typographer_context_); + + DlDispatcher dispatcher; + list->Dispatch(dispatcher); + Picture picture = dispatcher.EndRecordingAsPicture(); + + std::unique_ptr screenshot = + pimpl_->screenshotter->MakeScreenshot(renderer, picture, + pimpl_->window_size); + + return screenshot; +} + } // namespace impeller diff --git a/impeller/golden_tests/golden_playground_test_stub.cc b/impeller/golden_tests/golden_playground_test_stub.cc index 58f1951b87f83..022609c4f628a 100644 --- a/impeller/golden_tests/golden_playground_test_stub.cc +++ b/impeller/golden_tests/golden_playground_test_stub.cc @@ -89,4 +89,9 @@ fml::Status GoldenPlaygroundTest::SetCapabilities( "GoldenPlaygroundTest-Stub doesn't support SetCapabilities."); } +std::unique_ptr GoldenPlaygroundTest::MakeScreenshot( + const sk_sp& list) { + return nullptr; +} + } // namespace impeller diff --git a/impeller/golden_tests/metal_screenshotter.mm b/impeller/golden_tests/metal_screenshotter.mm index 9ab6f422e7d98..71058c86512ad 100644 --- a/impeller/golden_tests/metal_screenshotter.mm +++ b/impeller/golden_tests/metal_screenshotter.mm @@ -37,30 +37,32 @@ std::unique_ptr MetalScreenshotter::MakeScreenshot( AiksContext& aiks_context, const std::shared_ptr texture) { - id metal_texture = - std::static_pointer_cast(texture)->GetMTLTexture(); + @autoreleasepool { + id metal_texture = + std::static_pointer_cast(texture)->GetMTLTexture(); - CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); - CIImage* ciImage = [[CIImage alloc] - initWithMTLTexture:metal_texture - options:@{kCIImageColorSpace : (__bridge id)color_space}]; - CGColorSpaceRelease(color_space); - FML_CHECK(ciImage); + CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); + CIImage* ciImage = [[CIImage alloc] + initWithMTLTexture:metal_texture + options:@{kCIImageColorSpace : (__bridge id)color_space}]; + CGColorSpaceRelease(color_space); + FML_CHECK(ciImage); - std::shared_ptr context = playground_->GetContext(); - std::shared_ptr context_mtl = - std::static_pointer_cast(context); - CIContext* cicontext = - [CIContext contextWithMTLDevice:context_mtl->GetMTLDevice()]; - FML_CHECK(context); + std::shared_ptr context = playground_->GetContext(); + std::shared_ptr context_mtl = + std::static_pointer_cast(context); + CIContext* cicontext = + [CIContext contextWithMTLDevice:context_mtl->GetMTLDevice()]; + FML_CHECK(context); - CIImage* flipped = [ciImage - imageByApplyingOrientation:kCGImagePropertyOrientationDownMirrored]; + CIImage* flipped = [ciImage + imageByApplyingOrientation:kCGImagePropertyOrientationDownMirrored]; - CGImageRef cgImage = [cicontext createCGImage:flipped - fromRect:[ciImage extent]]; + CGImageRef cgImage = [cicontext createCGImage:flipped + fromRect:[ciImage extent]]; - return std::unique_ptr(new MetalScreenshot(cgImage)); + return std::unique_ptr(new MetalScreenshot(cgImage)); + } } } // namespace testing