From d52f315f41f54c55039864d441fbbfcea7465375 Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Wed, 3 Jan 2024 12:46:00 -0800 Subject: [PATCH] [Impeller] new blur: limit uvs to blur region (flutter/engine#49299) This will run the blur passes on a subset of the texture passed into it that will actually be blurred. This is an optimization for backdrop filters. issue: https://github.com/flutter/flutter/issues/131580 test coverage: devicelab `backdrop_filter_perf_ios__timeline_summary` ## performance results This results in a 50% reduction in average GPU time in our benchmark, 79% reduction in the "99% percentile". ``` BEFORE "average_vsync_transitions_missed": 3.889423076923077, "90th_percentile_vsync_transitions_missed": 4.0, "99th_percentile_vsync_transitions_missed": 4.0, "average_vsync_frame_lag": 0.0, "90th_percentile_vsync_frame_lag": 0.0, "99th_percentile_vsync_frame_lag": 0.0, "average_layer_cache_count": 0.0, "90th_percentile_layer_cache_count": 0.0, "99th_percentile_layer_cache_count": 0.0, "average_frame_request_pending_latency": 16654.368333333332, "90th_percentile_frame_request_pending_latency": 16692.0, "99th_percentile_frame_request_pending_latency": 16749.0, "worst_layer_cache_count": 0.0, "average_layer_cache_memory": 0.0, "90th_percentile_layer_cache_memory": 0.0, "99th_percentile_layer_cache_memory": 0.0, "worst_layer_cache_memory": 0.0, "average_picture_cache_count": 0.0, "90th_percentile_picture_cache_count": 0.0, "99th_percentile_picture_cache_count": 0.0, "worst_picture_cache_count": 0.0, "average_picture_cache_memory": 0.0, "90th_percentile_picture_cache_memory": 0.0, "99th_percentile_picture_cache_memory": 0.0, "worst_picture_cache_memory": 0.0, "total_ui_gc_time": 0.659, "30hz_frame_percentage": 0.0, "60hz_frame_percentage": 100.0, "80hz_frame_percentage": 0.0, "90hz_frame_percentage": 0.0, "120hz_frame_percentage": 0.0, "illegal_refresh_rate_frame_count": 0, "average_gpu_frame_time": 52.13341346153846, "90th_percentile_gpu_frame_time": 62.5, "99th_percentile_gpu_frame_time": 62.5, "worst_gpu_frame_time": 62.5, "average_cpu_usage": 69.08979595918369, "90th_percentile_cpu_usage": 70.4, "99th_percentile_cpu_usage": 71.699999, "average_gpu_usage": 100.0, "90th_percentile_gpu_usage": 100.0, "99th_percentile_gpu_usage": 100.0, "average_memory_usage": 118.79942602040816, "90th_percentile_memory_usage": 138.125, "99th_percentile_memory_usage": 143.65625 AFTER "average_vsync_transitions_missed": 2.0, "90th_percentile_vsync_transitions_missed": 2.0, "99th_percentile_vsync_transitions_missed": 2.0, "average_vsync_frame_lag": 0.0, "90th_percentile_vsync_frame_lag": 0.0, "99th_percentile_vsync_frame_lag": 0.0, "average_layer_cache_count": 0.0, "90th_percentile_layer_cache_count": 0.0, "99th_percentile_layer_cache_count": 0.0, "average_frame_request_pending_latency": 16635.025, "90th_percentile_frame_request_pending_latency": 16715.0, "99th_percentile_frame_request_pending_latency": 16802.0, "worst_layer_cache_count": 0.0, "average_layer_cache_memory": 0.0, "90th_percentile_layer_cache_memory": 0.0, "99th_percentile_layer_cache_memory": 0.0, "worst_layer_cache_memory": 0.0, "average_picture_cache_count": 0.0, "90th_percentile_picture_cache_count": 0.0, "99th_percentile_picture_cache_count": 0.0, "worst_picture_cache_count": 0.0, "average_picture_cache_memory": 0.0, "90th_percentile_picture_cache_memory": 0.0, "99th_percentile_picture_cache_memory": 0.0, "worst_picture_cache_memory": 0.0, "total_ui_gc_time": 1.732, "30hz_frame_percentage": 0.0, "60hz_frame_percentage": 100.0, "80hz_frame_percentage": 0.0, "90hz_frame_percentage": 0.0, "120hz_frame_percentage": 0.0, "illegal_refresh_rate_frame_count": 0, "average_gpu_frame_time": 25.01558603491272, "90th_percentile_gpu_frame_time": 31.25, "99th_percentile_gpu_frame_time": 31.25, ``` [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style --- .../flutter/impeller/aiks/aiks_unittests.cc | 28 ++++++++++- .../filters/gaussian_blur_filter_contents.cc | 46 ++++++++++++++++--- 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/engine/src/flutter/impeller/aiks/aiks_unittests.cc b/engine/src/flutter/impeller/aiks/aiks_unittests.cc index bc9b3d6ccb4..aa667eca97f 100644 --- a/engine/src/flutter/impeller/aiks/aiks_unittests.cc +++ b/engine/src/flutter/impeller/aiks/aiks_unittests.cc @@ -3630,6 +3630,30 @@ TEST_P(AiksTest, GaussianBlurRotatedAndClipped) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } +TEST_P(AiksTest, GaussianBlurScaledAndClipped) { + Canvas canvas; + std::shared_ptr boston = CreateTextureForFixture("boston.jpg"); + Rect bounds = + Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height); + Vector2 image_center = Vector2(bounds.GetSize() / 2); + Paint paint = {.image_filter = + ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0), + FilterContents::BlurStyle::kNormal, + Entity::TileMode::kDecal)}; + Vector2 clip_size = {150, 75}; + Vector2 center = Vector2(1024, 768) / 2; + canvas.Scale(GetContentScale()); + canvas.ClipRect( + Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size)); + canvas.Translate({center.x, center.y, 0}); + canvas.Scale({0.6, 0.6, 1}); + + canvas.DrawImageRect(std::make_shared(boston), /*source=*/bounds, + /*dest=*/bounds.Shift(-image_center), paint); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + TEST_P(AiksTest, GaussianBlurRotatedAndClippedInteractive) { std::shared_ptr boston = CreateTextureForFixture("boston.jpg"); @@ -3640,11 +3664,13 @@ TEST_P(AiksTest, GaussianBlurRotatedAndClippedInteractive) { Entity::TileMode::kMirror, Entity::TileMode::kDecal}; static float rotation = 0; + static float scale = 0.6; static int selected_tile_mode = 3; ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); { ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180); + ImGui::SliderFloat("Scale", &scale, 0, 2.0); ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names, sizeof(tile_mode_names) / sizeof(char*)); } @@ -3665,7 +3691,7 @@ TEST_P(AiksTest, GaussianBlurRotatedAndClippedInteractive) { canvas.ClipRect( Rect::MakeLTRB(handle_a.x, handle_a.y, handle_b.x, handle_b.y)); canvas.Translate({center.x, center.y, 0}); - canvas.Scale({0.6, 0.6, 1}); + canvas.Scale({scale, scale, 1}); canvas.Rotate(Degrees(rotation)); canvas.DrawImageRect(std::make_shared(boston), /*source=*/bounds, diff --git a/engine/src/flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/engine/src/flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc index 453c37dd4b2..19aa2e46fa3 100644 --- a/engine/src/flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc @@ -121,7 +121,8 @@ fml::StatusOr MakeBlurSubpass( const SamplerDescriptor& sampler_descriptor, Entity::TileMode tile_mode, const GaussianBlurFragmentShader::BlurInfo& blur_info, - std::optional destination_target) { + std::optional destination_target, + const Quad& blur_uvs) { if (blur_info.blur_sigma < kEhCloseEnough) { return input_pass; } @@ -153,10 +154,10 @@ fml::StatusOr MakeBlurSubpass( BindVertices(cmd, host_buffer, { - {Point(0, 0), Point(0, 0)}, - {Point(1, 0), Point(1, 0)}, - {Point(0, 1), Point(0, 1)}, - {Point(1, 1), Point(1, 1)}, + {blur_uvs[0], blur_uvs[0]}, + {blur_uvs[1], blur_uvs[1]}, + {blur_uvs[2], blur_uvs[2]}, + {blur_uvs[3], blur_uvs[3]}, }); SamplerDescriptor linear_sampler_descriptor = sampler_descriptor; @@ -183,6 +184,14 @@ fml::StatusOr MakeBlurSubpass( } } +/// Returns `rect` relative to `reference`, where Rect::MakeXYWH(0,0,1,1) will +/// be returned when `rect` == `reference`. +Rect MakeReferenceUVs(const Rect& reference, const Rect& rect) { + Rect result = Rect::MakeOriginSize(rect.GetOrigin() - reference.GetOrigin(), + rect.GetSize()); + return result.Scale(1.0f / Vector2(reference.GetSize())); +} + } // namespace GaussianBlurFilterContents::GaussianBlurFilterContents( @@ -311,6 +320,29 @@ std::optional GaussianBlurFilterContents::RenderFilter( Vector2 pass1_pixel_size = 1.0 / Vector2(pass1_out.value().GetRenderTargetTexture()->GetSize()); + std::optional input_snapshot_coverage = input_snapshot->GetCoverage(); + Quad blur_uvs = {Point(0, 0), Point(1, 0), Point(0, 1), Point(1, 1)}; + if (expanded_coverage_hint.has_value() && + input_snapshot_coverage.has_value() && + // TODO(https://github.com/flutter/flutter/issues/140890): Remove this + // condition. There is some flaw in coverage stopping us from using this + // today. I attempted to use source coordinates to calculate the uvs, + // but that didn't work either. + input_snapshot.has_value() && + input_snapshot.value().transform.IsTranslationScaleOnly()) { + // Only process the uvs where the blur is happening, not the whole texture. + std::optional uvs = MakeReferenceUVs(input_snapshot_coverage.value(), + expanded_coverage_hint.value()) + .Intersection(Rect::MakeSize(Size(1, 1))); + FML_DCHECK(uvs.has_value()); + if (uvs.has_value()) { + blur_uvs[0] = uvs->GetLeftTop(); + blur_uvs[1] = uvs->GetRightTop(); + blur_uvs[2] = uvs->GetLeftBottom(); + blur_uvs[3] = uvs->GetRightBottom(); + } + } + fml::StatusOr pass2_out = MakeBlurSubpass(renderer, /*input_pass=*/pass1_out.value(), input_snapshot->sampler_descriptor, tile_mode_, @@ -320,7 +352,7 @@ std::optional GaussianBlurFilterContents::RenderFilter( .blur_radius = blur_radius.y * effective_scalar.y, .step_size = 1.0, }, - /*destination_target=*/std::nullopt); + /*destination_target=*/std::nullopt, blur_uvs); if (!pass2_out.ok()) { return std::nullopt; @@ -341,7 +373,7 @@ std::optional GaussianBlurFilterContents::RenderFilter( .blur_radius = blur_radius.x * effective_scalar.x, .step_size = 1.0, }, - pass3_destination); + pass3_destination, blur_uvs); if (!pass3_out.ok()) { return std::nullopt;