Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[impeller] adds test for catching shimmer in gaussian blur #54116

Merged
merged 19 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions impeller/display_list/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
116 changes: 116 additions & 0 deletions impeller/display_list/dl_golden_blur_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<double>(left[0]) - right[0], //
static_cast<double>(left[1]) - right[1], //
static_cast<double>(left[2]) - right[2], //
static_cast<double>(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<double>(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<sk_sp<DlImage>>& 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<sk_sp<DlImage>> images;
images.emplace_back(CreateDlImageForFixture("boston.jpg"));

auto make_screenshot = [&](float sigma) {
DisplayListBuilder builder;
draw(&builder, images, sigma);

std::unique_ptr<impeller::testing::Screenshot> screenshot =
MakeScreenshot(builder.Build());
return screenshot;
};

float start_sigma = 10.0f;
std::unique_ptr<impeller::testing::Screenshot> 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<impeller::testing::Screenshot> 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
5 changes: 5 additions & 0 deletions impeller/display_list/dl_playground.cc
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ bool DlPlayground::OpenPlaygroundHere(DisplayListPlaygroundCallback callback) {
});
}

std::unique_ptr<testing::Screenshot> DlPlayground::MakeScreenshot(
const sk_sp<flutter::DisplayList>& list) {
return nullptr;
}

SkFont DlPlayground::CreateTestFontOfSize(SkScalar scalar) {
static constexpr const char* kTestFontFixture = "Roboto-Regular.ttf";
auto mapping = flutter::testing::OpenFixtureAsSkData(kTestFontFixture);
Expand Down
4 changes: 4 additions & 0 deletions impeller/display_list/dl_playground.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -27,6 +28,9 @@ class DlPlayground : public PlaygroundTest {

bool OpenPlaygroundHere(DisplayListPlaygroundCallback callback);

std::unique_ptr<testing::Screenshot> MakeScreenshot(
const sk_sp<flutter::DisplayList>& list);

SkFont CreateTestFontOfSize(SkScalar scalar);

SkFont CreateTestFont();
Expand Down
8 changes: 7 additions & 1 deletion impeller/golden_tests/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ impeller_component("golden_playground_test") {

deps = [
":digest",
":screenshot",
"//flutter/fml",
"//flutter/impeller/aiks",
"//flutter/impeller/display_list:display_list",
Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand Down
7 changes: 7 additions & 0 deletions impeller/golden_tests/golden_playground_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -51,6 +52,12 @@ class GoldenPlaygroundTest

bool OpenPlaygroundHere(const sk_sp<flutter::DisplayList>& list);

std::unique_ptr<testing::Screenshot> MakeScreenshot(
const sk_sp<flutter::DisplayList>& list);

static bool SaveScreenshot(std::unique_ptr<testing::Screenshot> screenshot,
const std::string& postfix = "");

static bool ImGuiBegin(const char* name,
bool* p_open,
ImGuiWindowFlags flags);
Expand Down
28 changes: 22 additions & 6 deletions impeller/golden_tests/golden_playground_test_mac.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<testing::Screenshot> screenshot) {
bool GoldenPlaygroundTest::SaveScreenshot(
std::unique_ptr<testing::Screenshot> 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(
Expand All @@ -161,8 +164,6 @@ bool SaveScreenshot(std::unique_ptr<testing::Screenshot> screenshot) {
return true;
}

} // namespace

struct GoldenPlaygroundTest::GoldenPlaygroundTestImpl {
std::unique_ptr<PlaygroundImpl> test_vulkan_playground;
std::unique_ptr<PlaygroundImpl> test_opengl_playground;
Expand Down Expand Up @@ -396,4 +397,19 @@ fml::Status GoldenPlaygroundTest::SetCapabilities(
return pimpl_->screenshotter->GetPlayground().SetCapabilities(capabilities);
}

std::unique_ptr<testing::Screenshot> GoldenPlaygroundTest::MakeScreenshot(
const sk_sp<flutter::DisplayList>& list) {
AiksContext renderer(GetContext(), typographer_context_);

DlDispatcher dispatcher;
list->Dispatch(dispatcher);
Picture picture = dispatcher.EndRecordingAsPicture();

std::unique_ptr<testing::Screenshot> screenshot =
pimpl_->screenshotter->MakeScreenshot(renderer, picture,
pimpl_->window_size);

return screenshot;
}

} // namespace impeller
5 changes: 5 additions & 0 deletions impeller/golden_tests/golden_playground_test_stub.cc
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,9 @@ fml::Status GoldenPlaygroundTest::SetCapabilities(
"GoldenPlaygroundTest-Stub doesn't support SetCapabilities.");
}

std::unique_ptr<testing::Screenshot> GoldenPlaygroundTest::MakeScreenshot(
const sk_sp<flutter::DisplayList>& list) {
return nullptr;
}

} // namespace impeller
40 changes: 21 additions & 19 deletions impeller/golden_tests/metal_screenshotter.mm
Original file line number Diff line number Diff line change
Expand Up @@ -37,30 +37,32 @@
std::unique_ptr<Screenshot> MetalScreenshotter::MakeScreenshot(
AiksContext& aiks_context,
const std::shared_ptr<Texture> texture) {
id<MTLTexture> metal_texture =
std::static_pointer_cast<TextureMTL>(texture)->GetMTLTexture();
@autoreleasepool {
id<MTLTexture> metal_texture =
std::static_pointer_cast<TextureMTL>(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> context = playground_->GetContext();
std::shared_ptr<ContextMTL> context_mtl =
std::static_pointer_cast<ContextMTL>(context);
CIContext* cicontext =
[CIContext contextWithMTLDevice:context_mtl->GetMTLDevice()];
FML_CHECK(context);
std::shared_ptr<Context> context = playground_->GetContext();
std::shared_ptr<ContextMTL> context_mtl =
std::static_pointer_cast<ContextMTL>(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<MetalScreenshot>(new MetalScreenshot(cgImage));
return std::unique_ptr<MetalScreenshot>(new MetalScreenshot(cgImage));
}
}

} // namespace testing
Expand Down