diff --git a/BUILD.gn b/BUILD.gn index 6d096a5aaff53..34acd32563caf 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -143,7 +143,6 @@ group("flutter") { "//flutter/display_list:display_list_region_benchmarks", "//flutter/display_list:display_list_transform_benchmarks", "//flutter/fml:fml_benchmarks", - "//flutter/impeller/aiks:canvas_benchmarks", "//flutter/impeller/geometry:geometry_benchmarks", "//flutter/lib/ui:ui_benchmarks", "//flutter/shell/common:shell_benchmarks", diff --git a/ci/builders/linux_host_engine.json b/ci/builders/linux_host_engine.json index 2c2644e49bd20..d8a3e3d238fef 100644 --- a/ci/builders/linux_host_engine.json +++ b/ci/builders/linux_host_engine.json @@ -168,7 +168,6 @@ "flutter/display_list:display_list_transform_benchmarks", "flutter/fml:fml_benchmarks", "flutter/impeller/geometry:geometry_benchmarks", - "flutter/impeller/aiks:canvas_benchmarks", "flutter/lib/ui:ui_benchmarks", "flutter/shell/common:shell_benchmarks", "flutter/shell/testing", diff --git a/ci/builders/standalone/linux_benchmarks.json b/ci/builders/standalone/linux_benchmarks.json index 9afed62f1d941..442cbf5ca9218 100644 --- a/ci/builders/standalone/linux_benchmarks.json +++ b/ci/builders/standalone/linux_benchmarks.json @@ -30,7 +30,6 @@ "flutter/display_list:display_list_transform_benchmarks", "flutter/fml:fml_benchmarks", "flutter/impeller/geometry:geometry_benchmarks", - "flutter/impeller/aiks:canvas_benchmarks", "flutter/lib/ui:ui_benchmarks", "flutter/shell/common:shell_benchmarks", "flutter/shell/testing", diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index 8f3f686127c1a..db5c887e828fc 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -128,11 +128,9 @@ ../../../flutter/impeller/.clang-format ../../../flutter/impeller/.gitignore ../../../flutter/impeller/README.md -../../../flutter/impeller/aiks/aiks_gradient_unittests.cc ../../../flutter/impeller/aiks/aiks_unittests.cc ../../../flutter/impeller/aiks/aiks_unittests.h ../../../flutter/impeller/aiks/canvas_unittests.cc -../../../flutter/impeller/aiks/testing ../../../flutter/impeller/base/README.md ../../../flutter/impeller/base/allocation_size_unittests.cc ../../../flutter/impeller/base/base_unittests.cc diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index f6cdc665d4706..3521d34d4acad 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -42624,21 +42624,14 @@ ORIGIN: ../../../flutter/impeller/aiks/aiks_playground.cc + ../../../flutter/LIC ORIGIN: ../../../flutter/impeller/aiks/aiks_playground.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/aiks/canvas.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/aiks/canvas.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/aiks/canvas_benchmarks.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/aiks/color_filter.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/aiks/color_filter.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/aiks/color_source.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/aiks/color_source.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/aiks/experimental_canvas.cc + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/aiks/experimental_canvas.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/aiks/image_filter.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/aiks/image_filter.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/aiks/paint.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/aiks/paint.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/aiks/paint_pass_delegate.cc + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/aiks/paint_pass_delegate.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/aiks/picture.cc + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/aiks/picture.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/base/allocation.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/base/allocation.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/base/allocation_size.cc + ../../../flutter/LICENSE @@ -42833,12 +42826,8 @@ ORIGIN: ../../../flutter/impeller/entity/draw_order_resolver.cc + ../../../flutt ORIGIN: ../../../flutter/impeller/entity/draw_order_resolver.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/entity/entity_pass.cc + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/entity/entity_pass.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity_pass_clip_stack.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity_pass_clip_stack.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/entity/entity_pass_delegate.cc + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/entity/entity_pass_delegate.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity_pass_target.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity_pass_target.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/entity_playground.cc + ../../../flutter/LICENSE @@ -45499,21 +45488,14 @@ FILE: ../../../flutter/impeller/aiks/aiks_playground.cc FILE: ../../../flutter/impeller/aiks/aiks_playground.h FILE: ../../../flutter/impeller/aiks/canvas.cc FILE: ../../../flutter/impeller/aiks/canvas.h -FILE: ../../../flutter/impeller/aiks/canvas_benchmarks.cc FILE: ../../../flutter/impeller/aiks/color_filter.cc FILE: ../../../flutter/impeller/aiks/color_filter.h FILE: ../../../flutter/impeller/aiks/color_source.cc FILE: ../../../flutter/impeller/aiks/color_source.h -FILE: ../../../flutter/impeller/aiks/experimental_canvas.cc -FILE: ../../../flutter/impeller/aiks/experimental_canvas.h FILE: ../../../flutter/impeller/aiks/image_filter.cc FILE: ../../../flutter/impeller/aiks/image_filter.h FILE: ../../../flutter/impeller/aiks/paint.cc FILE: ../../../flutter/impeller/aiks/paint.h -FILE: ../../../flutter/impeller/aiks/paint_pass_delegate.cc -FILE: ../../../flutter/impeller/aiks/paint_pass_delegate.h -FILE: ../../../flutter/impeller/aiks/picture.cc -FILE: ../../../flutter/impeller/aiks/picture.h FILE: ../../../flutter/impeller/base/allocation.cc FILE: ../../../flutter/impeller/base/allocation.h FILE: ../../../flutter/impeller/base/allocation_size.cc @@ -45708,12 +45690,8 @@ FILE: ../../../flutter/impeller/entity/draw_order_resolver.cc FILE: ../../../flutter/impeller/entity/draw_order_resolver.h FILE: ../../../flutter/impeller/entity/entity.cc FILE: ../../../flutter/impeller/entity/entity.h -FILE: ../../../flutter/impeller/entity/entity_pass.cc -FILE: ../../../flutter/impeller/entity/entity_pass.h FILE: ../../../flutter/impeller/entity/entity_pass_clip_stack.cc FILE: ../../../flutter/impeller/entity/entity_pass_clip_stack.h -FILE: ../../../flutter/impeller/entity/entity_pass_delegate.cc -FILE: ../../../flutter/impeller/entity/entity_pass_delegate.h FILE: ../../../flutter/impeller/entity/entity_pass_target.cc FILE: ../../../flutter/impeller/entity/entity_pass_target.h FILE: ../../../flutter/impeller/entity/entity_playground.cc diff --git a/common/config.gni b/common/config.gni index 6c2354e7c9128..0a5a95306c2fd 100644 --- a/common/config.gni +++ b/common/config.gni @@ -35,9 +35,6 @@ declare_args() { # See [go/slimpeller-dashboard](https://github.com/orgs/flutter/projects/21) # for details. slimpeller = false - - # Opt into new DL dispatcher that skips AIKS layer - experimental_canvas = true } # feature_defines_list --------------------------------------------------------- @@ -76,10 +73,6 @@ if (slimpeller) { feature_defines_list += [ "SLIMPELLER=1" ] } -if (experimental_canvas) { - feature_defines_list += [ "EXPERIMENTAL_CANVAS=1" ] -} - if (is_ios || is_mac) { flutter_cflags_objc = [ "-Werror=overriding-method-mismatch", diff --git a/display_list/testing/dl_test_surface_metal.cc b/display_list/testing/dl_test_surface_metal.cc index c80dab3a90f60..90ac9b7eb5637 100644 --- a/display_list/testing/dl_test_surface_metal.cc +++ b/display_list/testing/dl_test_surface_metal.cc @@ -87,21 +87,9 @@ sk_sp DlMetalSurfaceProvider::ImpellerSnapshot( const sk_sp& list, int width, int height) const { -#if EXPERIMENTAL_CANVAS auto texture = DisplayListToTexture(list, {width, height}, *aiks_context_); return sk_make_sp( snapshotter_->MakeScreenshot(*aiks_context_, texture)); -#else - InitScreenShotter(); - impeller::DlDispatcher dispatcher; - dispatcher.drawColor(flutter::DlColor::kTransparent(), - flutter::DlBlendMode::kSrc); - list->Dispatch(dispatcher); - auto picture = dispatcher.EndRecordingAsPicture(); - return sk_make_sp(snapshotter_->MakeScreenshot( - *aiks_context_, picture, {width, height}, false)); - -#endif // EXPERIMENTAL_CANVAS } sk_sp DlMetalSurfaceProvider::MakeImpellerImage( @@ -109,20 +97,8 @@ sk_sp DlMetalSurfaceProvider::MakeImpellerImage( int width, int height) const { InitScreenShotter(); -#if EXPERIMENTAL_CANVAS return impeller::DlImageImpeller::Make( DisplayListToTexture(list, {width, height}, *aiks_context_)); -#else - impeller::DlDispatcher dispatcher; - dispatcher.drawColor(flutter::DlColor::kTransparent(), - flutter::DlBlendMode::kSrc); - list->Dispatch(dispatcher); - auto picture = dispatcher.EndRecordingAsPicture(); - std::shared_ptr texture = - picture.ToImage(*aiks_context_, {width, height}); - return impeller::DlImageImpeller::Make(texture); - -#endif // EXPERIMENTAL_CANVAS } void DlMetalSurfaceProvider::InitScreenShotter() const { diff --git a/impeller/aiks/BUILD.gn b/impeller/aiks/BUILD.gn index c2c4f1f3d795a..c731fba787882 100644 --- a/impeller/aiks/BUILD.gn +++ b/impeller/aiks/BUILD.gn @@ -14,16 +14,10 @@ impeller_component("aiks") { "color_filter.h", "color_source.cc", "color_source.h", - "experimental_canvas.cc", - "experimental_canvas.h", "image_filter.cc", "image_filter.h", "paint.cc", "paint.h", - "paint_pass_delegate.cc", - "paint_pass_delegate.h", - "picture.cc", - "picture.h", ] public_deps = [ @@ -53,25 +47,9 @@ impeller_component("aiks_playground") { ] } -impeller_component("context_spy") { - testonly = true - - sources = [ - "testing/context_mock.h", - "testing/context_spy.cc", - "testing/context_spy.h", - ] - deps = [ - "//flutter/impeller/entity:entity_test_helpers", - "//flutter/impeller/renderer", - "//flutter/testing:testing_lib", - ] -} - template("aiks_unittests_component") { target_name = invoker.target_name predefined_sources = [ - "aiks_gradient_unittests.cc", "aiks_unittests.cc", "aiks_unittests.h", ] @@ -92,7 +70,6 @@ template("aiks_unittests_component") { deps = [ ":aiks", ":aiks_playground", - ":context_spy", "//flutter/impeller/geometry:geometry_asserts", "//flutter/impeller/golden_tests:golden_playground_test", "//flutter/impeller/playground:playground_test", @@ -116,12 +93,3 @@ aiks_unittests_component("aiks_unittests_golden") { "IMPELLER_ENABLE_VALIDATION=1", ] } - -executable("canvas_benchmarks") { - testonly = true - sources = [ "canvas_benchmarks.cc" ] - deps = [ - ":aiks", - "//flutter/benchmarking", - ] -} diff --git a/impeller/aiks/aiks_context.cc b/impeller/aiks/aiks_context.cc index d455681ca09d0..80f94b0b95d97 100644 --- a/impeller/aiks/aiks_context.cc +++ b/impeller/aiks/aiks_context.cc @@ -4,8 +4,6 @@ #include "impeller/aiks/aiks_context.h" -#include "fml/closure.h" -#include "impeller/aiks/picture.h" #include "impeller/typographer/typographer_context.h" namespace impeller { @@ -45,23 +43,4 @@ ContentContext& AiksContext::GetContentContext() const { return *content_context_; } -bool AiksContext::Render(const Picture& picture, - const RenderTarget& render_target, - bool reset_host_buffer) { - if (!IsValid()) { - return false; - } - - fml::ScopedCleanupClosure closure([&]() { - if (reset_host_buffer) { - content_context_->GetTransientsBuffer().Reset(); - } - }); - if (picture.pass) { - return picture.pass->Render(*content_context_, render_target); - } - - return true; -} - } // namespace impeller diff --git a/impeller/aiks/aiks_context.h b/impeller/aiks/aiks_context.h index da5273213095c..a28bf1340045f 100644 --- a/impeller/aiks/aiks_context.h +++ b/impeller/aiks/aiks_context.h @@ -42,10 +42,6 @@ class AiksContext { ContentContext& GetContentContext() const; - bool Render(const Picture& picture, - const RenderTarget& render_target, - bool reset_host_buffer); - private: std::shared_ptr context_; std::unique_ptr content_context_; diff --git a/impeller/aiks/aiks_gradient_unittests.cc b/impeller/aiks/aiks_gradient_unittests.cc deleted file mode 100644 index bf6abdad96cd3..0000000000000 --- a/impeller/aiks/aiks_gradient_unittests.cc +++ /dev/null @@ -1,45 +0,0 @@ -// 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 "flutter/impeller/aiks/aiks_unittests.h" - -#include "impeller/aiks/canvas.h" -#include "impeller/entity/contents/conical_gradient_contents.h" -#include "impeller/entity/contents/linear_gradient_contents.h" -#include "impeller/entity/contents/radial_gradient_contents.h" -#include "impeller/entity/contents/sweep_gradient_contents.h" -#include "impeller/geometry/geometry_asserts.h" -#include "impeller/geometry/path_builder.h" -#include "impeller/playground/widgets.h" -#include "third_party/imgui/imgui.h" - -//////////////////////////////////////////////////////////////////////////////// -// This is for tests of Canvas that are interested the results of rendering -// gradients. -//////////////////////////////////////////////////////////////////////////////// - -namespace impeller { -namespace testing { - -#define APPLY_COLOR_FILTER_GRADIENT_TEST(name) \ - TEST_P(AiksTest, name##GradientApplyColorFilter) { \ - auto contents = name##GradientContents(); \ - contents.SetColors({Color::CornflowerBlue().WithAlpha(0.75)}); \ - auto result = contents.ApplyColorFilter([](const Color& color) { \ - return color.Blend(Color::LimeGreen().WithAlpha(0.75), \ - BlendMode::kScreen); \ - }); \ - ASSERT_TRUE(result); \ - \ - std::vector expected = {Color(0.433247, 0.879523, 0.825324, 0.75)}; \ - ASSERT_COLORS_NEAR(contents.GetColors(), expected); \ - } - -APPLY_COLOR_FILTER_GRADIENT_TEST(Linear); -APPLY_COLOR_FILTER_GRADIENT_TEST(Radial); -APPLY_COLOR_FILTER_GRADIENT_TEST(Conical); -APPLY_COLOR_FILTER_GRADIENT_TEST(Sweep); - -} // namespace testing -} // namespace impeller \ No newline at end of file diff --git a/impeller/aiks/aiks_playground.cc b/impeller/aiks/aiks_playground.cc index 6ad968a27646f..d6c0faa2a09ed 100644 --- a/impeller/aiks/aiks_playground.cc +++ b/impeller/aiks/aiks_playground.cc @@ -5,7 +5,6 @@ #include "impeller/aiks/aiks_playground.h" #include -#include #include "impeller/aiks/aiks_context.h" #include "impeller/display_list/dl_dispatcher.h" @@ -28,44 +27,6 @@ void AiksPlayground::TearDown() { PlaygroundTest::TearDown(); } -bool AiksPlayground::OpenPlaygroundHere(Picture picture) { - if (!switches_.enable_playground) { - return true; - } - - AiksContext renderer(GetContext(), typographer_context_); - - if (!renderer.IsValid()) { - return false; - } - - return Playground::OpenPlaygroundHere( - [&renderer, &picture](RenderTarget& render_target) -> bool { - return renderer.Render(picture, render_target, true); - }); -} - -bool AiksPlayground::OpenPlaygroundHere(AiksPlaygroundCallback callback) { - if (!switches_.enable_playground) { - return true; - } - - AiksContext renderer(GetContext(), typographer_context_); - - if (!renderer.IsValid()) { - return false; - } - - return Playground::OpenPlaygroundHere( - [&renderer, &callback](RenderTarget& render_target) -> bool { - std::optional picture = callback(renderer); - if (!picture.has_value()) { - return false; - } - return renderer.Render(*picture, render_target, true); - }); -} - bool AiksPlayground::ImGuiBegin(const char* name, bool* p_open, ImGuiWindowFlags flags) { @@ -88,7 +49,6 @@ bool AiksPlayground::OpenPlaygroundHere( return Playground::OpenPlaygroundHere( [&renderer, &callback](RenderTarget& render_target) -> bool { -#if EXPERIMENTAL_CANVAS auto display_list = callback(); TextFrameDispatcher collector(renderer.GetContentContext(), // Matrix(), // @@ -96,7 +56,7 @@ bool AiksPlayground::OpenPlaygroundHere( ); display_list->Dispatch(collector); - ExperimentalDlDispatcher impeller_dispatcher( + CanvasDlDispatcher impeller_dispatcher( renderer.GetContentContext(), render_target, display_list->root_has_backdrop_filter(), display_list->max_root_blend_mode(), IRect::MakeMaximum()); @@ -105,14 +65,6 @@ bool AiksPlayground::OpenPlaygroundHere( renderer.GetContentContext().GetTransientsBuffer().Reset(); renderer.GetContentContext().GetLazyGlyphAtlas()->ResetTextFrames(); return true; -#else - auto display_list = callback(); - DlDispatcher dispatcher; - display_list->Dispatch(dispatcher); - Picture picture = dispatcher.EndRecordingAsPicture(); - - return renderer.Render(picture, render_target, true); -#endif // EXPERIMENTAL_CANVAS }); } diff --git a/impeller/aiks/aiks_playground.h b/impeller/aiks/aiks_playground.h index bb0fddfd1b5d9..89d331f8409eb 100644 --- a/impeller/aiks/aiks_playground.h +++ b/impeller/aiks/aiks_playground.h @@ -7,7 +7,6 @@ #include "flutter/display_list/display_list.h" #include "impeller/aiks/aiks_context.h" -#include "impeller/aiks/picture.h" #include "impeller/playground/playground_test.h" #include "impeller/typographer/typographer_context.h" #include "third_party/imgui/imgui.h" @@ -16,9 +15,6 @@ namespace impeller { class AiksPlayground : public PlaygroundTest { public: - using AiksPlaygroundCallback = - std::function(AiksContext& renderer)>; - using AiksDlPlaygroundCallback = std::function()>; AiksPlayground(); @@ -30,10 +26,6 @@ class AiksPlayground : public PlaygroundTest { void SetTypographerContext( std::shared_ptr typographer_context); - bool OpenPlaygroundHere(Picture picture); - - bool OpenPlaygroundHere(AiksPlaygroundCallback callback); - bool OpenPlaygroundHere(const AiksDlPlaygroundCallback& callback); bool OpenPlaygroundHere(const sk_sp& list); diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index 3f910a51f0b17..5233f1434084a 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -4,33 +4,8 @@ #include "flutter/impeller/aiks/aiks_unittests.h" -#include -#include -#include -#include -#include -#include -#include - #include "flutter/testing/testing.h" #include "gtest/gtest.h" -#include "impeller/aiks/canvas.h" -#include "impeller/aiks/color_filter.h" -#include "impeller/aiks/image_filter.h" -#include "impeller/aiks/testing/context_spy.h" -#include "impeller/core/device_buffer.h" -#include "impeller/entity/contents/solid_color_contents.h" -#include "impeller/geometry/color.h" -#include "impeller/geometry/constants.h" -#include "impeller/geometry/geometry_asserts.h" -#include "impeller/geometry/matrix.h" -#include "impeller/geometry/path.h" -#include "impeller/geometry/path_builder.h" -#include "impeller/geometry/rect.h" -#include "impeller/geometry/size.h" -#include "impeller/playground/widgets.h" -#include "impeller/renderer/command_buffer.h" -#include "impeller/renderer/snapshot.h" #include "third_party/imgui/imgui.h" namespace impeller { @@ -38,306 +13,5 @@ namespace testing { INSTANTIATE_PLAYGROUND_SUITE(AiksTest); -TEST_P(AiksTest, PaintWithFilters) { - // validate that a paint with a color filter "HasFilters", no other filters - // impact this setting. - Paint paint; - - ASSERT_FALSE(paint.HasColorFilter()); - - paint.color_filter = - ColorFilter::MakeBlend(BlendMode::kSourceOver, Color::Blue()); - - ASSERT_TRUE(paint.HasColorFilter()); - - paint.image_filter = ImageFilter::MakeBlur(Sigma(1.0), Sigma(1.0), - FilterContents::BlurStyle::kNormal, - Entity::TileMode::kClamp); - - ASSERT_TRUE(paint.HasColorFilter()); - - paint.mask_blur_descriptor = {}; - - ASSERT_TRUE(paint.HasColorFilter()); - - paint.color_filter = nullptr; - - ASSERT_FALSE(paint.HasColorFilter()); -} - -TEST_P(AiksTest, DrawPaintAbsorbsClears) { - Canvas canvas; - canvas.DrawPaint({.color = Color::Red(), .blend_mode = BlendMode::kSource}); - canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75), - .blend_mode = BlendMode::kSourceOver}); - - Picture picture = canvas.EndRecordingAsPicture(); - auto expected = Color::Red().Blend(Color::CornflowerBlue().WithAlpha(0.75), - BlendMode::kSourceOver); - ASSERT_EQ(picture.pass->GetClearColor(), expected); - - std::shared_ptr spy = ContextSpy::Make(); - std::shared_ptr real_context = GetContext(); - std::shared_ptr mock_context = spy->MakeContext(real_context); - AiksContext renderer(mock_context, nullptr); - std::shared_ptr image = picture.ToImage(renderer, {300, 300}); - - ASSERT_EQ(spy->render_passes_.size(), 1llu); - std::shared_ptr render_pass = spy->render_passes_[0]; - ASSERT_EQ(render_pass->GetCommands().size(), 0llu); -} - -// This is important to enforce with texture reuse, since cached textures need -// to be cleared before reuse. -TEST_P(AiksTest, - ParentSaveLayerCreatesRenderPassWhenChildBackdropFilterIsPresent) { - Canvas canvas; - canvas.SaveLayer({}, std::nullopt, ImageFilter::MakeMatrix(Matrix(), {})); - canvas.DrawPaint({.color = Color::Red(), .blend_mode = BlendMode::kSource}); - canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75), - .blend_mode = BlendMode::kSourceOver}); - canvas.Restore(); - - Picture picture = canvas.EndRecordingAsPicture(); - - std::shared_ptr spy = ContextSpy::Make(); - std::shared_ptr real_context = GetContext(); - std::shared_ptr mock_context = spy->MakeContext(real_context); - AiksContext renderer(mock_context, nullptr); - std::shared_ptr image = picture.ToImage(renderer, {300, 300}); - - ASSERT_EQ(spy->render_passes_.size(), - GetBackend() == PlaygroundBackend::kOpenGLES ? 4llu : 3llu); - std::shared_ptr render_pass = spy->render_passes_[0]; - ASSERT_EQ(render_pass->GetCommands().size(), 0llu); -} - -TEST_P(AiksTest, DrawRectAbsorbsClears) { - Canvas canvas; - canvas.DrawRect(Rect::MakeXYWH(0, 0, 300, 300), - {.color = Color::Red(), .blend_mode = BlendMode::kSource}); - canvas.DrawRect(Rect::MakeXYWH(0, 0, 300, 300), - {.color = Color::CornflowerBlue().WithAlpha(0.75), - .blend_mode = BlendMode::kSourceOver}); - - std::shared_ptr spy = ContextSpy::Make(); - Picture picture = canvas.EndRecordingAsPicture(); - std::shared_ptr real_context = GetContext(); - std::shared_ptr mock_context = spy->MakeContext(real_context); - AiksContext renderer(mock_context, nullptr); - std::shared_ptr image = picture.ToImage(renderer, {300, 300}); - - ASSERT_EQ(spy->render_passes_.size(), 1llu); - std::shared_ptr render_pass = spy->render_passes_[0]; - ASSERT_EQ(render_pass->GetCommands().size(), 0llu); -} - -TEST_P(AiksTest, DrawRectAbsorbsClearsNegativeRRect) { - Canvas canvas; - canvas.DrawRRect(Rect::MakeXYWH(0, 0, 300, 300), {5.0, 5.0}, - {.color = Color::Red(), .blend_mode = BlendMode::kSource}); - canvas.DrawRRect(Rect::MakeXYWH(0, 0, 300, 300), {5.0, 5.0}, - {.color = Color::CornflowerBlue().WithAlpha(0.75), - .blend_mode = BlendMode::kSourceOver}); - - std::shared_ptr spy = ContextSpy::Make(); - Picture picture = canvas.EndRecordingAsPicture(); - std::shared_ptr real_context = GetContext(); - std::shared_ptr mock_context = spy->MakeContext(real_context); - AiksContext renderer(mock_context, nullptr); - std::shared_ptr image = picture.ToImage(renderer, {300, 300}); - - ASSERT_EQ(spy->render_passes_.size(), 1llu); - std::shared_ptr render_pass = spy->render_passes_[0]; - ASSERT_EQ(render_pass->GetCommands().size(), 2llu); -} - -TEST_P(AiksTest, DrawRectAbsorbsClearsNegativeRotation) { - Canvas canvas; - canvas.Translate(Vector3(150.0, 150.0, 0.0)); - canvas.Rotate(Degrees(45.0)); - canvas.Translate(Vector3(-150.0, -150.0, 0.0)); - canvas.DrawRect(Rect::MakeXYWH(0, 0, 300, 300), - {.color = Color::Red(), .blend_mode = BlendMode::kSource}); - - std::shared_ptr spy = ContextSpy::Make(); - Picture picture = canvas.EndRecordingAsPicture(); - std::shared_ptr real_context = GetContext(); - std::shared_ptr mock_context = spy->MakeContext(real_context); - AiksContext renderer(mock_context, nullptr); - std::shared_ptr image = picture.ToImage(renderer, {300, 300}); - - ASSERT_EQ(spy->render_passes_.size(), 1llu); - std::shared_ptr render_pass = spy->render_passes_[0]; - ASSERT_EQ(render_pass->GetCommands().size(), 1llu); -} - -TEST_P(AiksTest, DrawRectAbsorbsClearsNegative) { - Canvas canvas; - canvas.DrawRect(Rect::MakeXYWH(0, 0, 300, 300), - {.color = Color::Red(), .blend_mode = BlendMode::kSource}); - canvas.DrawRect(Rect::MakeXYWH(0, 0, 300, 300), - {.color = Color::CornflowerBlue().WithAlpha(0.75), - .blend_mode = BlendMode::kSourceOver}); - - std::shared_ptr spy = ContextSpy::Make(); - Picture picture = canvas.EndRecordingAsPicture(); - std::shared_ptr real_context = GetContext(); - std::shared_ptr mock_context = spy->MakeContext(real_context); - AiksContext renderer(mock_context, nullptr); - std::shared_ptr image = picture.ToImage(renderer, {301, 301}); - - ASSERT_EQ(spy->render_passes_.size(), 1llu); - std::shared_ptr render_pass = spy->render_passes_[0]; - ASSERT_EQ(render_pass->GetCommands().size(), 2llu); -} - -TEST_P(AiksTest, ClipRectElidesNoOpClips) { - Canvas canvas(Rect::MakeXYWH(0, 0, 100, 100)); - canvas.ClipRect(Rect::MakeXYWH(0, 0, 100, 100)); - canvas.ClipRect(Rect::MakeXYWH(-100, -100, 300, 300)); - canvas.DrawPaint({.color = Color::Red(), .blend_mode = BlendMode::kSource}); - canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75), - .blend_mode = BlendMode::kSourceOver}); - - Picture picture = canvas.EndRecordingAsPicture(); - auto expected = Color::Red().Blend(Color::CornflowerBlue().WithAlpha(0.75), - BlendMode::kSourceOver); - ASSERT_EQ(picture.pass->GetClearColor(), expected); - - std::shared_ptr spy = ContextSpy::Make(); - std::shared_ptr real_context = GetContext(); - std::shared_ptr mock_context = spy->MakeContext(real_context); - AiksContext renderer(mock_context, nullptr); - std::shared_ptr image = picture.ToImage(renderer, {300, 300}); - - ASSERT_EQ(spy->render_passes_.size(), 1llu); - std::shared_ptr render_pass = spy->render_passes_[0]; - ASSERT_EQ(render_pass->GetCommands().size(), 0llu); -} - -TEST_P(AiksTest, ClearColorOptimizationDoesNotApplyForBackdropFilters) { - Canvas canvas; - canvas.SaveLayer({}, std::nullopt, - ImageFilter::MakeBlur(Sigma(3), Sigma(3), - FilterContents::BlurStyle::kNormal, - Entity::TileMode::kClamp)); - canvas.DrawPaint({.color = Color::Red(), .blend_mode = BlendMode::kSource}); - canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75), - .blend_mode = BlendMode::kSourceOver}); - canvas.Restore(); - - Picture picture = canvas.EndRecordingAsPicture(); - - std::optional actual_color; - bool found_subpass = false; - picture.pass->IterateAllElements([&](EntityPass::Element& element) -> bool { - if (auto subpass = std::get_if>(&element)) { - actual_color = subpass->get()->GetClearColor(); - found_subpass = true; - } - // Fail if the first element isn't a subpass. - return true; - }); - - EXPECT_TRUE(found_subpass); - EXPECT_FALSE(actual_color.has_value()); -} - -TEST_P(AiksTest, OpaqueEntitiesGetCoercedToSource) { - Canvas canvas; - canvas.Scale(Vector2(1.618, 1.618)); - canvas.DrawCircle(Point(), 10, - { - .color = Color::CornflowerBlue(), - .blend_mode = BlendMode::kSourceOver, - }); - Picture picture = canvas.EndRecordingAsPicture(); - - // Extract the SolidColorSource. - // Entity entity; - std::vector entity; - std::shared_ptr contents; - picture.pass->IterateAllEntities([e = &entity, &contents](Entity& entity) { - if (ScalarNearlyEqual(entity.GetTransform().GetScale().x, 1.618f)) { - contents = - std::static_pointer_cast(entity.GetContents()); - e->emplace_back(entity.Clone()); - return false; - } - return true; - }); - - ASSERT_TRUE(entity.size() >= 1); - ASSERT_TRUE(contents->IsOpaque({})); - ASSERT_EQ(entity[0].GetBlendMode(), BlendMode::kSource); -} - -TEST_P(AiksTest, SolidColorApplyColorFilter) { - auto contents = SolidColorContents(); - contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75)); - auto result = contents.ApplyColorFilter([](const Color& color) { - return color.Blend(Color::LimeGreen().WithAlpha(0.75), BlendMode::kScreen); - }); - ASSERT_TRUE(result); - ASSERT_COLOR_NEAR(contents.GetColor(), - Color(0.424452, 0.828743, 0.79105, 0.9375)); -} - -TEST_P(AiksTest, CorrectClipDepthAssignedToEntities) { - Canvas canvas; // Depth 1 (base pass) - canvas.DrawRRect(Rect::MakeLTRB(0, 0, 100, 100), {10, 10}, {}); // Depth 2 - canvas.Save(); - { - canvas.ClipRRect(Rect::MakeLTRB(0, 0, 50, 50), {10, 10}, {}); // Depth 4 - canvas.SaveLayer({}); // Depth 4 - { - canvas.DrawRRect(Rect::MakeLTRB(0, 0, 50, 50), {10, 10}, {}); // Depth 3 - } - canvas.Restore(); // Restore the savelayer. - } - canvas.Restore(); // Depth 5 -- this will no longer append a restore entity - // once we switch to the clip depth approach. - - auto picture = canvas.EndRecordingAsPicture(); - - std::vector expected = { - 2, // DrawRRect - 4, // ClipRRect -- Has a depth value equal to the max depth of all the - // content it affect. In this case, the SaveLayer and all - // its contents are affected. - 4, // SaveLayer -- The SaveLayer is drawn to the parent pass after its - // contents are rendered, so it should have a depth value - // greater than all its contents. - 3, // DrawRRect - 5, // Restore (no longer necessary when clipping on the depth buffer) - }; - - std::vector actual; - - picture.pass->IterateAllElements([&](EntityPass::Element& element) -> bool { - if (auto* subpass = std::get_if>(&element)) { - actual.push_back(subpass->get()->GetClipDepth()); - } - if (Entity* entity = std::get_if(&element)) { - actual.push_back(entity->GetClipDepth()); - } - return true; - }); - - ASSERT_EQ(actual.size(), expected.size()); - for (size_t i = 0; i < expected.size(); i++) { - EXPECT_EQ(expected[i], actual[i]) << "Index: " << i; - } -} - } // namespace testing } // namespace impeller - -// █████████████████████████████████████████████████████████████████████████████ -// █ NOTICE: Before adding new tests to this file consider adding it to one of -// █ the subdivisions of AiksTest to avoid having one massive file. -// █ -// █ Subdivisions: -// █ - aiks_gradient_unittests.cc -// █████████████████████████████████████████████████████████████████████████████ diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index 805a4d7b3066d..0a281215335fc 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -12,19 +12,19 @@ #include "flutter/fml/trace_event.h" #include "impeller/aiks/color_source.h" #include "impeller/aiks/image_filter.h" -#include "impeller/aiks/paint_pass_delegate.h" #include "impeller/entity/contents/atlas_contents.h" #include "impeller/entity/contents/clip_contents.h" #include "impeller/entity/contents/color_source_contents.h" #include "impeller/entity/contents/content_context.h" #include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/framebuffer_blend_contents.h" #include "impeller/entity/contents/solid_rrect_blur_contents.h" #include "impeller/entity/contents/text_contents.h" #include "impeller/entity/contents/texture_contents.h" #include "impeller/entity/contents/tiled_texture_contents.h" #include "impeller/entity/contents/vertices_contents.h" #include "impeller/entity/geometry/geometry.h" -#include "impeller/entity/geometry/superellipse_geometry.h" +#include "impeller/entity/save_layer_utils.h" #include "impeller/geometry/color.h" #include "impeller/geometry/constants.h" #include "impeller/geometry/path_builder.h" @@ -140,105 +140,258 @@ static std::shared_ptr CreateCoverContentsWithFilters( return CreateContentsForGeometryWithFilters(paint, Geometry::MakeCover()); } +static void SetClipScissor(std::optional clip_coverage, + RenderPass& pass, + Point global_pass_position) { + // Set the scissor to the clip coverage area. We do this prior to rendering + // the clip itself and all its contents. + IRect scissor; + if (clip_coverage.has_value()) { + clip_coverage = clip_coverage->Shift(-global_pass_position); + scissor = IRect::RoundOut(clip_coverage.value()); + // The scissor rect must not exceed the size of the render target. + scissor = scissor.Intersection(IRect::MakeSize(pass.GetRenderTargetSize())) + .value_or(IRect()); + } + pass.SetScissor(scissor); +} + +static void ApplyFramebufferBlend(Entity& entity) { + auto src_contents = entity.GetContents(); + auto contents = std::make_shared(); + contents->SetChildContents(src_contents); + contents->SetBlendMode(entity.GetBlendMode()); + entity.SetContents(std::move(contents)); + entity.SetBlendMode(BlendMode::kSource); +} + +/// End the current render pass, saving the result as a texture, and then +/// restart it with the backdrop cleared to the previous contents. +/// +/// This method is used to set up the input for emulated advanced blends and +/// backdrop filters. +/// +/// Returns the previous render pass stored as a texture, or nullptr if there +/// was a validation failure. +static std::shared_ptr FlipBackdrop( + std::vector& render_passes, + Point global_pass_position, + EntityPassClipStack& clip_coverage_stack, + ContentContext& renderer) { + auto rendering_config = std::move(render_passes.back()); + render_passes.pop_back(); + + // If the very first thing we render in this EntityPass is a subpass that + // happens to have a backdrop filter or advanced blend, than that backdrop + // filter/blend will sample from an uninitialized texture. + // + // By calling `pass_context.GetRenderPass` here, we force the texture to pass + // through at least one RenderPass with the correct clear configuration before + // any sampling occurs. + // + // In cases where there are no contents, we + // could instead check the clear color and initialize a 1x2 CPU texture + // instead of ending the pass. + rendering_config.inline_pass_context->GetRenderPass(0); + if (!rendering_config.inline_pass_context->EndPass()) { + VALIDATION_LOG + << "Failed to end the current render pass in order to read from " + "the backdrop texture and apply an advanced blend or backdrop " + "filter."; + // Note: adding this render pass ensures there are no later crashes from + // unbalanced save layers. Ideally, this method would return false and the + // renderer could handle that by terminating dispatch. + render_passes.push_back(LazyRenderingConfig( + renderer, std::move(rendering_config.entity_pass_target), + std::move(rendering_config.inline_pass_context))); + return nullptr; + } + + std::shared_ptr input_texture = + rendering_config.inline_pass_context->GetTexture(); + + if (!input_texture) { + VALIDATION_LOG << "Failed to fetch the color texture in order to " + "apply an advanced blend or backdrop filter."; + + // Note: see above. + render_passes.push_back(LazyRenderingConfig( + renderer, std::move(rendering_config.entity_pass_target), + std::move(rendering_config.inline_pass_context))); + return nullptr; + } + + render_passes.push_back(LazyRenderingConfig( + renderer, std::move(rendering_config.entity_pass_target), + std::move(rendering_config.inline_pass_context))); + // Eagerly restore the BDF contents. + + // If the pass context returns a backdrop texture, we need to draw it to the + // current pass. We do this because it's faster and takes significantly less + // memory than storing/loading large MSAA textures. Also, it's not possible + // to blit the non-MSAA resolve texture of the previous pass to MSAA + // textures (let alone a transient one). + Rect size_rect = Rect::MakeSize(input_texture->GetSize()); + auto msaa_backdrop_contents = TextureContents::MakeRect(size_rect); + msaa_backdrop_contents->SetStencilEnabled(false); + msaa_backdrop_contents->SetLabel("MSAA backdrop"); + msaa_backdrop_contents->SetSourceRect(size_rect); + msaa_backdrop_contents->SetTexture(input_texture); + + Entity msaa_backdrop_entity; + msaa_backdrop_entity.SetContents(std::move(msaa_backdrop_contents)); + msaa_backdrop_entity.SetBlendMode(BlendMode::kSource); + msaa_backdrop_entity.SetClipDepth(std::numeric_limits::max()); + if (!msaa_backdrop_entity.Render( + renderer, + *render_passes.back().inline_pass_context->GetRenderPass(0).pass)) { + VALIDATION_LOG << "Failed to render MSAA backdrop entity."; + return nullptr; + } + + // Restore any clips that were recorded before the backdrop filter was + // applied. + auto& replay_entities = clip_coverage_stack.GetReplayEntities(); + for (const auto& replay : replay_entities) { + SetClipScissor( + replay.clip_coverage, + *render_passes.back().inline_pass_context->GetRenderPass(0).pass, + global_pass_position); + if (!replay.entity.Render( + renderer, + *render_passes.back().inline_pass_context->GetRenderPass(0).pass)) { + VALIDATION_LOG << "Failed to render entity for clip restore."; + } + } + + return input_texture; +} + +/// @brief Create the subpass restore contents, appling any filters or opacity +/// from the provided paint object. +static std::shared_ptr CreateContentsForSubpassTarget( + const Paint& paint, + const std::shared_ptr& target, + const Matrix& effect_transform) { + auto contents = TextureContents::MakeRect(Rect::MakeSize(target->GetSize())); + contents->SetTexture(target); + contents->SetLabel("Subpass"); + contents->SetSourceRect(Rect::MakeSize(target->GetSize())); + contents->SetOpacity(paint.color.alpha); + contents->SetDeferApplyingOpacity(true); + + return paint.WithFiltersForSubpassTarget(std::move(contents), + effect_transform); +} + +static const constexpr RenderTarget::AttachmentConfig kDefaultStencilConfig = + RenderTarget::AttachmentConfig{ + .storage_mode = StorageMode::kDeviceTransient, + .load_action = LoadAction::kDontCare, + .store_action = StoreAction::kDontCare, + }; + +static std::unique_ptr CreateRenderTarget( + ContentContext& renderer, + ISize size, + const Color& clear_color) { + const std::shared_ptr& context = renderer.GetContext(); + + /// All of the load/store actions are managed by `InlinePassContext` when + /// `RenderPasses` are created, so we just set them to `kDontCare` here. + /// What's important is the `StorageMode` of the textures, which cannot be + /// changed for the lifetime of the textures. + + RenderTarget target; + if (context->GetCapabilities()->SupportsOffscreenMSAA()) { + target = renderer.GetRenderTargetCache()->CreateOffscreenMSAA( + /*context=*/*context, + /*size=*/size, + /*mip_count=*/1, + /*label=*/"EntityPass", + /*color_attachment_config=*/ + RenderTarget::AttachmentConfigMSAA{ + .storage_mode = StorageMode::kDeviceTransient, + .resolve_storage_mode = StorageMode::kDevicePrivate, + .load_action = LoadAction::kDontCare, + .store_action = StoreAction::kMultisampleResolve, + .clear_color = clear_color}, + /*stencil_attachment_config=*/kDefaultStencilConfig); + } else { + target = renderer.GetRenderTargetCache()->CreateOffscreen( + *context, // context + size, // size + /*mip_count=*/1, + "EntityPass", // label + RenderTarget::AttachmentConfig{ + .storage_mode = StorageMode::kDevicePrivate, + .load_action = LoadAction::kDontCare, + .store_action = StoreAction::kDontCare, + .clear_color = clear_color, + }, // color_attachment_config + kDefaultStencilConfig // + ); + } + + return std::make_unique( + target, renderer.GetDeviceCapabilities().SupportsReadFromResolve(), + renderer.GetDeviceCapabilities().SupportsImplicitResolvingMSAA()); +} + } // namespace -Canvas::Canvas() { +Canvas::Canvas(ContentContext& renderer, + RenderTarget& render_target, + bool requires_readback) + : renderer_(renderer), + render_target_(render_target), + requires_readback_(requires_readback), + clip_coverage_stack_(EntityPassClipStack( + Rect::MakeSize(render_target.GetRenderTargetSize()))) { Initialize(std::nullopt); + SetupRenderPass(); } -Canvas::Canvas(Rect cull_rect) { +Canvas::Canvas(ContentContext& renderer, + RenderTarget& render_target, + bool requires_readback, + Rect cull_rect) + : renderer_(renderer), + render_target_(render_target), + requires_readback_(requires_readback), + clip_coverage_stack_(EntityPassClipStack( + Rect::MakeSize(render_target.GetRenderTargetSize()))) { Initialize(cull_rect); + SetupRenderPass(); } -Canvas::Canvas(IRect cull_rect) { +Canvas::Canvas(ContentContext& renderer, + RenderTarget& render_target, + bool requires_readback, + IRect cull_rect) + : renderer_(renderer), + render_target_(render_target), + requires_readback_(requires_readback), + clip_coverage_stack_(EntityPassClipStack( + Rect::MakeSize(render_target.GetRenderTargetSize()))) { Initialize(Rect::MakeLTRB(cull_rect.GetLeft(), cull_rect.GetTop(), cull_rect.GetRight(), cull_rect.GetBottom())); + SetupRenderPass(); } -Canvas::~Canvas() = default; - void Canvas::Initialize(std::optional cull_rect) { initial_cull_rect_ = cull_rect; - base_pass_ = std::make_unique(); - base_pass_->SetClipDepth(++current_depth_); - current_pass_ = base_pass_.get(); transform_stack_.emplace_back(CanvasStackEntry{ - .cull_rect = cull_rect, .clip_depth = kMaxDepth, }); FML_DCHECK(GetSaveCount() == 1u); - FML_DCHECK(base_pass_->GetSubpassesDepth() == 1u); } void Canvas::Reset() { - base_pass_ = nullptr; - current_pass_ = nullptr; current_depth_ = 0u; transform_stack_ = {}; } -void Canvas::Save(uint32_t total_content_depth) { - Save(false, total_content_depth); -} - -void Canvas::Save(bool create_subpass, - uint32_t total_content_depth, - BlendMode blend_mode, - const std::shared_ptr& backdrop_filter) { - auto entry = CanvasStackEntry{}; - entry.transform = transform_stack_.back().transform; - entry.cull_rect = transform_stack_.back().cull_rect; - entry.clip_height = transform_stack_.back().clip_height; - entry.distributed_opacity = transform_stack_.back().distributed_opacity; - if (create_subpass) { - entry.rendering_mode = - Entity::RenderingMode::kSubpassAppendSnapshotTransform; - auto subpass = std::make_unique(); - if (backdrop_filter) { - EntityPass::BackdropFilterProc backdrop_filter_proc = - [backdrop_filter = backdrop_filter->Clone()]( - const FilterInput::Ref& input, const Matrix& effect_transform, - Entity::RenderingMode rendering_mode) { - auto filter = backdrop_filter->WrapInput(input); - filter->SetEffectTransform(effect_transform); - filter->SetRenderingMode(rendering_mode); - return filter; - }; - subpass->SetBackdropFilter(backdrop_filter_proc); - } - subpass->SetBlendMode(blend_mode); - current_pass_ = GetCurrentPass().AddSubpass(std::move(subpass)); - current_pass_->SetTransform(transform_stack_.back().transform); - current_pass_->SetClipHeight(transform_stack_.back().clip_height); - } - transform_stack_.emplace_back(entry); -} - -bool Canvas::Restore() { - FML_DCHECK(transform_stack_.size() > 0); - if (transform_stack_.size() == 1) { - return false; - } - size_t num_clips = transform_stack_.back().num_clips; - current_pass_->PopClips(num_clips, current_depth_); - - if (transform_stack_.back().rendering_mode == - Entity::RenderingMode::kSubpassAppendSnapshotTransform || - transform_stack_.back().rendering_mode == - Entity::RenderingMode::kSubpassPrependSnapshotTransform) { - current_pass_->SetClipDepth(++current_depth_); - current_pass_ = GetCurrentPass().GetSuperpass(); - FML_DCHECK(current_pass_); - } - - transform_stack_.pop_back(); - if (num_clips > 0) { - RestoreClip(); - } - - return true; -} - void Canvas::Concat(const Matrix& transform) { transform_stack_.back().transform = GetCurrentTransform() * transform; } @@ -259,15 +412,6 @@ const Matrix& Canvas::GetCurrentTransform() const { return transform_stack_.back().transform; } -const std::optional Canvas::GetCurrentLocalCullingBounds() const { - auto cull_rect = transform_stack_.back().cull_rect; - if (cull_rect.has_value()) { - Matrix inverse = transform_stack_.back().transform.Invert(); - cull_rect = cull_rect.value().TransformBounds(inverse); - } - return cull_rect; -} - void Canvas::Translate(const Vector3& offset) { Concat(Matrix::MakeTranslation(offset)); } @@ -288,10 +432,29 @@ void Canvas::Rotate(Radians radians) { Concat(Matrix::MakeRotationZ(radians)); } +Point Canvas::GetGlobalPassPosition() const { + if (save_layer_state_.empty()) { + return Point(0, 0); + } + return save_layer_state_.back().coverage.GetOrigin(); +} + +// clip depth of the previous save or 0. +size_t Canvas::GetClipHeightFloor() const { + if (transform_stack_.size() > 1) { + return transform_stack_[transform_stack_.size() - 2].clip_height; + } + return 0; +} + size_t Canvas::GetSaveCount() const { return transform_stack_.size(); } +bool Canvas::IsSkipping() const { + return transform_stack_.back().skipping; +} + void Canvas::RestoreToCount(size_t count) { while (GetSaveCount() > count) { if (!Restore()) { @@ -306,7 +469,7 @@ void Canvas::DrawPath(const Path& path, const Paint& paint) { entity.SetBlendMode(paint.blend_mode); entity.SetContents(CreatePathContentsWithFilters(paint, path)); - AddRenderEntityToCurrentPass(std::move(entity)); + AddRenderEntityToCurrentPass(entity); } void Canvas::DrawPaint(const Paint& paint) { @@ -315,7 +478,7 @@ void Canvas::DrawPaint(const Paint& paint) { entity.SetBlendMode(paint.blend_mode); entity.SetContents(CreateCoverContentsWithFilters(paint)); - AddRenderEntityToCurrentPass(std::move(entity)); + AddRenderEntityToCurrentPass(entity); } bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, @@ -405,7 +568,7 @@ bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, rrect_paint.mask_blur_descriptor = std::nullopt; blurred_rrect_entity.SetContents( rrect_paint.WithFilters(std::move(contents))); - AddRenderEntityToCurrentPass(std::move(blurred_rrect_entity)); + AddRenderEntityToCurrentPass(blurred_rrect_entity); }; switch (rrect_paint.mask_blur_descriptor->style) { @@ -422,7 +585,7 @@ bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, entity.SetBlendMode(rrect_paint.blend_mode); entity.SetContents(CreateContentsForGeometryWithFilters( rrect_paint, Geometry::MakeRoundRect(rect, corner_radii))); - AddRenderEntityToCurrentPass(std::move(entity), true); + AddRenderEntityToCurrentPass(entity, true); break; } case FilterContents::BlurStyle::kOuter: { @@ -449,7 +612,7 @@ void Canvas::DrawLine(const Point& p0, const Point& p1, const Paint& paint) { entity.SetContents(CreateContentsForGeometryWithFilters( paint, Geometry::MakeLine(p0, p1, paint.stroke_width, paint.stroke_cap))); - AddRenderEntityToCurrentPass(std::move(entity)); + AddRenderEntityToCurrentPass(entity); } void Canvas::DrawRect(const Rect& rect, const Paint& paint) { @@ -468,7 +631,7 @@ void Canvas::DrawRect(const Rect& rect, const Paint& paint) { entity.SetContents( CreateContentsForGeometryWithFilters(paint, Geometry::MakeRect(rect))); - AddRenderEntityToCurrentPass(std::move(entity)); + AddRenderEntityToCurrentPass(entity); } void Canvas::DrawOval(const Rect& rect, const Paint& paint) { @@ -499,7 +662,7 @@ void Canvas::DrawOval(const Rect& rect, const Paint& paint) { entity.SetContents( CreateContentsForGeometryWithFilters(paint, Geometry::MakeOval(rect))); - AddRenderEntityToCurrentPass(std::move(entity)); + AddRenderEntityToCurrentPass(entity); } void Canvas::DrawRRect(const Rect& rect, @@ -516,7 +679,7 @@ void Canvas::DrawRRect(const Rect& rect, entity.SetContents(CreateContentsForGeometryWithFilters( paint, Geometry::MakeRoundRect(rect, corner_radii))); - AddRenderEntityToCurrentPass(std::move(entity)); + AddRenderEntityToCurrentPass(entity); return; } @@ -548,98 +711,28 @@ void Canvas::DrawCircle(const Point& center, entity.SetContents( CreateContentsForGeometryWithFilters(paint, std::move(geometry))); - AddRenderEntityToCurrentPass(std::move(entity)); + AddRenderEntityToCurrentPass(entity); } void Canvas::ClipPath(const Path& path, Entity::ClipOperation clip_op) { - auto bounds = path.GetBoundingBox(); ClipGeometry(Geometry::MakeFillPath(path), clip_op); - if (clip_op == Entity::ClipOperation::kIntersect) { - if (bounds.has_value()) { - IntersectCulling(bounds.value()); - } - } } void Canvas::ClipRect(const Rect& rect, Entity::ClipOperation clip_op) { auto geometry = Geometry::MakeRect(rect); - auto& cull_rect = transform_stack_.back().cull_rect; - if (clip_op == Entity::ClipOperation::kIntersect && // - cull_rect.has_value() && // - geometry->CoversArea(transform_stack_.back().transform, *cull_rect) // - ) { - return; // This clip will do nothing, so skip it. - } - ClipGeometry(geometry, clip_op); - switch (clip_op) { - case Entity::ClipOperation::kIntersect: - IntersectCulling(rect); - break; - case Entity::ClipOperation::kDifference: - SubtractCulling(rect); - break; - } } void Canvas::ClipOval(const Rect& bounds, Entity::ClipOperation clip_op) { auto geometry = Geometry::MakeOval(bounds); - auto& cull_rect = transform_stack_.back().cull_rect; - if (clip_op == Entity::ClipOperation::kIntersect && // - cull_rect.has_value() && // - geometry->CoversArea(transform_stack_.back().transform, *cull_rect) // - ) { - return; // This clip will do nothing, so skip it. - } - ClipGeometry(geometry, clip_op); - switch (clip_op) { - case Entity::ClipOperation::kIntersect: - IntersectCulling(bounds); - break; - case Entity::ClipOperation::kDifference: - break; - } } void Canvas::ClipRRect(const Rect& rect, const Size& corner_radii, Entity::ClipOperation clip_op) { - // Does the rounded rect have a flat part on the top/bottom or left/right? - bool flat_on_TB = corner_radii.width * 2 < rect.GetWidth(); - bool flat_on_LR = corner_radii.height * 2 < rect.GetHeight(); auto geometry = Geometry::MakeRoundRect(rect, corner_radii); - auto& cull_rect = transform_stack_.back().cull_rect; - if (clip_op == Entity::ClipOperation::kIntersect && // - cull_rect.has_value() && // - geometry->CoversArea(transform_stack_.back().transform, *cull_rect) // - ) { - return; // This clip will do nothing, so skip it. - } - ClipGeometry(geometry, clip_op); - switch (clip_op) { - case Entity::ClipOperation::kIntersect: - IntersectCulling(rect); - break; - case Entity::ClipOperation::kDifference: - if (corner_radii.IsEmpty()) { - SubtractCulling(rect); - } else { - // We subtract the inner "tall" and "wide" rectangle pieces - // that fit inside the corners which cover the greatest area - // without involving the curved corners - // Since this is a subtract operation, we can subtract each - // rectangle piece individually without fear of interference. - if (flat_on_TB) { - SubtractCulling(rect.Expand(Size{-corner_radii.width, 0.0})); - } - if (flat_on_LR) { - SubtractCulling(rect.Expand(Size{0.0, -corner_radii.height})); - } - } - break; - } } void Canvas::ClipGeometry(const std::shared_ptr& geometry, @@ -652,37 +745,12 @@ void Canvas::ClipGeometry(const std::shared_ptr& geometry, entity.SetTransform(GetCurrentTransform()); entity.SetContents(std::move(contents)); - AddClipEntityToCurrentPass(std::move(entity)); + AddClipEntityToCurrentPass(entity); ++transform_stack_.back().clip_height; ++transform_stack_.back().num_clips; } -void Canvas::IntersectCulling(Rect clip_rect) { - clip_rect = clip_rect.TransformBounds(GetCurrentTransform()); - std::optional& cull_rect = transform_stack_.back().cull_rect; - if (cull_rect.has_value()) { - cull_rect = cull_rect - .value() // - .Intersection(clip_rect) // - .value_or(Rect{}); - } else { - cull_rect = clip_rect; - } -} - -void Canvas::SubtractCulling(Rect clip_rect) { - std::optional& cull_rect = transform_stack_.back().cull_rect; - if (cull_rect.has_value()) { - clip_rect = clip_rect.TransformBounds(GetCurrentTransform()); - cull_rect = cull_rect - .value() // - .Cutout(clip_rect) // - .value_or(Rect{}); - } - // else (no cull) diff (any clip) is non-rectangular -} - void Canvas::RestoreClip() { Entity entity; entity.SetTransform(GetCurrentTransform()); @@ -692,7 +760,7 @@ void Canvas::RestoreClip() { clip_restore->SetRestoreHeight(GetClipHeight()); entity.SetContents(std::move(clip_restore)); - AddRenderEntityToCurrentPass(std::move(entity)); + AddRenderEntityToCurrentPass(entity); } void Canvas::DrawPoints(std::vector points, @@ -711,7 +779,7 @@ void Canvas::DrawPoints(std::vector points, Geometry::MakePointField(std::move(points), radius, /*round=*/point_style == PointStyle::kRound))); - AddRenderEntityToCurrentPass(std::move(entity)); + AddRenderEntityToCurrentPass(entity); } void Canvas::DrawImage(const std::shared_ptr& image, @@ -763,119 +831,13 @@ void Canvas::DrawImageRect(const std::shared_ptr& image, entity.SetContents(paint.WithFilters(contents)); entity.SetTransform(GetCurrentTransform()); - AddRenderEntityToCurrentPass(std::move(entity)); -} - -Picture Canvas::EndRecordingAsPicture() { - // Assign clip depths to any outstanding clip entities. - while (current_pass_ != nullptr) { - current_pass_->PopAllClips(current_depth_); - current_pass_ = current_pass_->GetSuperpass(); - } - - Picture picture; - picture.pass = std::move(base_pass_); - - Reset(); - Initialize(initial_cull_rect_); - - return picture; -} - -EntityPass& Canvas::GetCurrentPass() { - FML_DCHECK(current_pass_ != nullptr); - return *current_pass_; + AddRenderEntityToCurrentPass(entity); } size_t Canvas::GetClipHeight() const { return transform_stack_.back().clip_height; } -void Canvas::AddRenderEntityToCurrentPass(Entity entity, bool reuse_depth) { - if (!reuse_depth) { - ++current_depth_; - } - entity.SetClipDepth(current_depth_); - entity.SetInheritedOpacity(transform_stack_.back().distributed_opacity); - GetCurrentPass().AddEntity(std::move(entity)); -} - -void Canvas::AddClipEntityToCurrentPass(Entity entity) { - GetCurrentPass().PushClip(std::move(entity)); -} - -void Canvas::SaveLayer(const Paint& paint, - std::optional bounds, - const std::shared_ptr& backdrop_filter, - ContentBoundsPromise bounds_promise, - uint32_t total_content_depth, - bool can_distribute_opacity) { - if (can_distribute_opacity && !backdrop_filter && - Paint::CanApplyOpacityPeephole(paint) && - bounds_promise != ContentBoundsPromise::kMayClipContents) { - Save(false, total_content_depth, paint.blend_mode, backdrop_filter); - transform_stack_.back().distributed_opacity *= paint.color.alpha; - return; - } - TRACE_EVENT0("flutter", "Canvas::saveLayer"); - - Save(true, total_content_depth, paint.blend_mode, backdrop_filter); - - // The DisplayList bounds/rtree doesn't account for filters applied to parent - // layers, and so sub-DisplayLists are getting culled as if no filters are - // applied. - // See also: https://github.com/flutter/flutter/issues/139294 - if (paint.image_filter) { - transform_stack_.back().cull_rect = std::nullopt; - } - - auto& new_layer_pass = GetCurrentPass(); - if (bounds) { - new_layer_pass.SetBoundsLimit(bounds); - } - - // When applying a save layer, absorb any pending distributed opacity. - Paint paint_copy = paint; - paint_copy.color.alpha *= transform_stack_.back().distributed_opacity; - transform_stack_.back().distributed_opacity = 1.0; - - new_layer_pass.SetDelegate(std::make_shared(paint_copy)); -} - -void Canvas::DrawTextFrame(const std::shared_ptr& text_frame, - Point position, - const Paint& paint) { - Entity entity; - entity.SetBlendMode(paint.blend_mode); - - auto text_contents = std::make_shared(); - text_contents->SetTextFrame(text_frame); - text_contents->SetForceTextColor(paint.mask_blur_descriptor.has_value()); - text_contents->SetOffset(position); - text_contents->SetColor(paint.color); - text_contents->SetTextProperties(paint.color, // - paint.style == Paint::Style::kStroke, // - paint.stroke_width, // - paint.stroke_cap, // - paint.stroke_join, // - paint.stroke_miter // - ); - - entity.SetTransform(GetCurrentTransform() * - Matrix::MakeTranslation(position)); - - // TODO(bdero): This mask blur application is a hack. It will always wind up - // doing a gaussian blur that affects the color source itself - // instead of just the mask. The color filter text support - // needs to be reworked in order to interact correctly with - // mask filters. - // https://github.com/flutter/flutter/issues/133297 - entity.SetContents(paint.WithFilters(paint.WithMaskBlur( - std::move(text_contents), true, GetCurrentTransform()))); - - AddRenderEntityToCurrentPass(std::move(entity)); -} - static bool UseColorSourceContents( const std::shared_ptr& vertices, const Paint& paint) { @@ -908,7 +870,7 @@ void Canvas::DrawVertices(const std::shared_ptr& vertices, // If there are no vertex colors. if (UseColorSourceContents(vertices, paint)) { entity.SetContents(CreateContentsForGeometryWithFilters(paint, vertices)); - AddRenderEntityToCurrentPass(std::move(entity)); + AddRenderEntityToCurrentPass(entity); return; } @@ -919,7 +881,7 @@ void Canvas::DrawVertices(const std::shared_ptr& vertices, contents->SetAlpha(paint.color.alpha); contents->SetGeometry(vertices); entity.SetContents(paint.WithFilters(std::move(contents))); - AddRenderEntityToCurrentPass(std::move(entity)); + AddRenderEntityToCurrentPass(entity); return; } @@ -937,7 +899,7 @@ void Canvas::DrawVertices(const std::shared_ptr& vertices, contents->SetTileMode(image_data.x_tile_mode, image_data.y_tile_mode); entity.SetContents(paint.WithFilters(std::move(contents))); - AddRenderEntityToCurrentPass(std::move(entity)); + AddRenderEntityToCurrentPass(entity); return; } @@ -982,7 +944,7 @@ void Canvas::DrawVertices(const std::shared_ptr& vertices, ->texture; }); entity.SetContents(paint.WithFilters(std::move(contents))); - AddRenderEntityToCurrentPass(std::move(entity)); + AddRenderEntityToCurrentPass(entity); } void Canvas::DrawAtlas(const std::shared_ptr& atlas, @@ -1012,7 +974,700 @@ void Canvas::DrawAtlas(const std::shared_ptr& atlas, entity.SetBlendMode(paint.blend_mode); entity.SetContents(paint.WithFilters(contents)); - AddRenderEntityToCurrentPass(std::move(entity)); + AddRenderEntityToCurrentPass(entity); +} + +/// Compositor Functionality +///////////////////////////////////////// + +void Canvas::SetupRenderPass() { + renderer_.GetRenderTargetCache()->Start(); + auto color0 = render_target_.GetColorAttachments().find(0u)->second; + + auto& stencil_attachment = render_target_.GetStencilAttachment(); + auto& depth_attachment = render_target_.GetDepthAttachment(); + if (!stencil_attachment.has_value() || !depth_attachment.has_value()) { + // Setup a new root stencil with an optimal configuration if one wasn't + // provided by the caller. + render_target_.SetupDepthStencilAttachments( + *renderer_.GetContext(), + *renderer_.GetContext()->GetResourceAllocator(), + color0.texture->GetSize(), + renderer_.GetContext()->GetCapabilities()->SupportsOffscreenMSAA(), + "ImpellerOnscreen", kDefaultStencilConfig); + } + + // Set up the clear color of the root pass. + color0.clear_color = Color::BlackTransparent(); + render_target_.SetColorAttachment(color0, 0); + + // If requires_readback is true, then there is a backdrop filter or emulated + // advanced blend in the first save layer. This requires a readback, which + // isn't supported by onscreen textures. To support this, we immediately begin + // a second save layer with the same dimensions as the onscreen. When + // rendering is completed, we must blit this saveLayer to the onscreen. + if (requires_readback_) { + auto entity_pass_target = + CreateRenderTarget(renderer_, // + color0.texture->GetSize(), // + /*clear_color=*/Color::BlackTransparent()); + render_passes_.push_back( + LazyRenderingConfig(renderer_, std::move(entity_pass_target))); + } else { + auto entity_pass_target = std::make_unique( + render_target_, // + renderer_.GetDeviceCapabilities().SupportsReadFromResolve(), // + renderer_.GetDeviceCapabilities().SupportsImplicitResolvingMSAA() // + ); + render_passes_.push_back( + LazyRenderingConfig(renderer_, std::move(entity_pass_target))); + } +} + +void Canvas::SkipUntilMatchingRestore(size_t total_content_depth) { + auto entry = CanvasStackEntry{}; + entry.skipping = true; + entry.clip_depth = current_depth_ + total_content_depth; + transform_stack_.push_back(entry); +} + +void Canvas::Save(uint32_t total_content_depth) { + if (IsSkipping()) { + return SkipUntilMatchingRestore(total_content_depth); + } + + auto entry = CanvasStackEntry{}; + entry.transform = transform_stack_.back().transform; + entry.clip_depth = current_depth_ + total_content_depth; + entry.distributed_opacity = transform_stack_.back().distributed_opacity; + FML_DCHECK(entry.clip_depth <= transform_stack_.back().clip_depth) + << entry.clip_depth << " <=? " << transform_stack_.back().clip_depth + << " after allocating " << total_content_depth; + entry.clip_height = transform_stack_.back().clip_height; + entry.rendering_mode = Entity::RenderingMode::kDirect; + transform_stack_.push_back(entry); +} + +std::optional Canvas::GetLocalCoverageLimit() const { + if (!clip_coverage_stack_.HasCoverage()) { + // The current clip is empty. This means the pass texture won't be + // visible, so skip it. + return std::nullopt; + } + + auto maybe_current_clip_coverage = clip_coverage_stack_.CurrentClipCoverage(); + if (!maybe_current_clip_coverage.has_value()) { + return std::nullopt; + } + + auto current_clip_coverage = maybe_current_clip_coverage.value(); + + // The maximum coverage of the subpass. Subpasses textures should never + // extend outside the parent pass texture or the current clip coverage. + std::optional maybe_coverage_limit = + Rect::MakeOriginSize(GetGlobalPassPosition(), + Size(render_passes_.back() + .inline_pass_context->GetTexture() + ->GetSize())) + .Intersection(current_clip_coverage); + + if (!maybe_coverage_limit.has_value() || maybe_coverage_limit->IsEmpty()) { + return std::nullopt; + } + + return maybe_coverage_limit->Intersection( + Rect::MakeSize(render_target_.GetRenderTargetSize())); +} + +void Canvas::SaveLayer(const Paint& paint, + std::optional bounds, + const std::shared_ptr& backdrop_filter, + ContentBoundsPromise bounds_promise, + uint32_t total_content_depth, + bool can_distribute_opacity) { + TRACE_EVENT0("flutter", "Canvas::saveLayer"); + if (IsSkipping()) { + return SkipUntilMatchingRestore(total_content_depth); + } + + auto maybe_coverage_limit = GetLocalCoverageLimit(); + if (!maybe_coverage_limit.has_value()) { + return SkipUntilMatchingRestore(total_content_depth); + } + auto coverage_limit = maybe_coverage_limit.value(); + + if (can_distribute_opacity && !backdrop_filter && + Paint::CanApplyOpacityPeephole(paint) && + bounds_promise != ContentBoundsPromise::kMayClipContents) { + Save(total_content_depth); + transform_stack_.back().distributed_opacity *= paint.color.alpha; + return; + } + + std::shared_ptr filter_contents = paint.WithImageFilter( + Rect(), transform_stack_.back().transform, + Entity::RenderingMode::kSubpassPrependSnapshotTransform); + + std::optional maybe_subpass_coverage = ComputeSaveLayerCoverage( + bounds.value_or(Rect::MakeMaximum()), + transform_stack_.back().transform, // + coverage_limit, // + filter_contents, // + /*flood_output_coverage=*/ + Entity::IsBlendModeDestructive(paint.blend_mode), // + /*flood_input_coverage=*/!!backdrop_filter // + ); + + if (!maybe_subpass_coverage.has_value()) { + return SkipUntilMatchingRestore(total_content_depth); + } + + auto subpass_coverage = maybe_subpass_coverage.value(); + + // When an image filter is present, clamp to avoid flicking due to nearest + // sampled image. For other cases, round out to ensure than any geometry is + // not cut off. + // + // See also this bug: https://github.com/flutter/flutter/issues/144213 + // + // TODO(jonahwilliams): this could still round out for filters that use decal + // sampling mode. + ISize subpass_size; + bool did_round_out = false; + if (paint.image_filter) { + subpass_size = ISize(subpass_coverage.GetSize()); + } else { + did_round_out = true; + subpass_size = ISize(IRect::RoundOut(subpass_coverage).GetSize()); + } + if (subpass_size.IsEmpty()) { + return SkipUntilMatchingRestore(total_content_depth); + } + + // Backdrop filter state, ignored if there is no BDF. + std::shared_ptr backdrop_filter_contents; + Point local_position = {0, 0}; + if (backdrop_filter) { + local_position = subpass_coverage.GetOrigin() - GetGlobalPassPosition(); + Canvas::BackdropFilterProc backdrop_filter_proc = + [backdrop_filter = backdrop_filter->Clone()]( + const FilterInput::Ref& input, const Matrix& effect_transform, + Entity::RenderingMode rendering_mode) { + auto filter = backdrop_filter->WrapInput(input); + filter->SetEffectTransform(effect_transform); + filter->SetRenderingMode(rendering_mode); + return filter; + }; + + auto input_texture = FlipBackdrop(render_passes_, // + GetGlobalPassPosition(), // + clip_coverage_stack_, // + renderer_ // + ); + if (!input_texture) { + // Validation failures are logged in FlipBackdrop. + return; + } + + backdrop_filter_contents = backdrop_filter_proc( + FilterInput::Make(std::move(input_texture)), + transform_stack_.back().transform.Basis(), + // When the subpass has a translation that means the math with + // the snapshot has to be different. + transform_stack_.back().transform.HasTranslation() + ? Entity::RenderingMode::kSubpassPrependSnapshotTransform + : Entity::RenderingMode::kSubpassAppendSnapshotTransform); + } + + // When applying a save layer, absorb any pending distributed opacity. + Paint paint_copy = paint; + paint_copy.color.alpha *= transform_stack_.back().distributed_opacity; + transform_stack_.back().distributed_opacity = 1.0; + + render_passes_.push_back( + LazyRenderingConfig(renderer_, // + CreateRenderTarget(renderer_, // + subpass_size, // + Color::BlackTransparent() // + ))); + save_layer_state_.push_back(SaveLayerState{paint_copy, subpass_coverage}); + + CanvasStackEntry entry; + entry.transform = transform_stack_.back().transform; + entry.clip_depth = current_depth_ + total_content_depth; + FML_DCHECK(entry.clip_depth <= transform_stack_.back().clip_depth) + << entry.clip_depth << " <=? " << transform_stack_.back().clip_depth + << " after allocating " << total_content_depth; + entry.clip_height = transform_stack_.back().clip_height; + entry.rendering_mode = Entity::RenderingMode::kSubpassAppendSnapshotTransform; + entry.did_round_out = did_round_out; + transform_stack_.emplace_back(entry); + + // Start non-collapsed subpasses with a fresh clip coverage stack limited by + // the subpass coverage. This is important because image filters applied to + // save layers may transform the subpass texture after it's rendered, + // causing parent clip coverage to get misaligned with the actual area that + // the subpass will affect in the parent pass. + clip_coverage_stack_.PushSubpass(subpass_coverage, GetClipHeight()); + + if (backdrop_filter_contents) { + // Render the backdrop entity. + Entity backdrop_entity; + backdrop_entity.SetContents(std::move(backdrop_filter_contents)); + backdrop_entity.SetTransform( + Matrix::MakeTranslation(Vector3(-local_position))); + backdrop_entity.SetClipDepth(std::numeric_limits::max()); + + backdrop_entity.Render( + renderer_, + *render_passes_.back().inline_pass_context->GetRenderPass(0).pass); + } +} + +bool Canvas::Restore() { + FML_DCHECK(transform_stack_.size() > 0); + if (transform_stack_.size() == 1) { + return false; + } + + // This check is important to make sure we didn't exceed the depth + // that the clips were rendered at while rendering any of the + // rendering ops. It is OK for the current depth to equal the + // outgoing clip depth because that means the clipping would have + // been successful up through the last rendering op, but it cannot + // be greater. + // Also, we bump the current rendering depth to the outgoing clip + // depth so that future rendering operations are not clipped by + // any of the pixels set by the expiring clips. It is OK for the + // estimates used to determine the clip depth in save/saveLayer + // to be overly conservative, but we need to jump the depth to + // the clip depth so that the next rendering op will get a + // larger depth (it will pre-increment the current_depth_ value). + FML_DCHECK(current_depth_ <= transform_stack_.back().clip_depth) + << current_depth_ << " <=? " << transform_stack_.back().clip_depth; + current_depth_ = transform_stack_.back().clip_depth; + + if (IsSkipping()) { + transform_stack_.pop_back(); + return true; + } + + if (transform_stack_.back().rendering_mode == + Entity::RenderingMode::kSubpassAppendSnapshotTransform || + transform_stack_.back().rendering_mode == + Entity::RenderingMode::kSubpassPrependSnapshotTransform) { + auto lazy_render_pass = std::move(render_passes_.back()); + render_passes_.pop_back(); + // Force the render pass to be constructed if it never was. + lazy_render_pass.inline_pass_context->GetRenderPass(0); + + SaveLayerState save_layer_state = save_layer_state_.back(); + save_layer_state_.pop_back(); + auto global_pass_position = GetGlobalPassPosition(); + + std::shared_ptr contents = CreateContentsForSubpassTarget( + save_layer_state.paint, // + lazy_render_pass.inline_pass_context->GetTexture(), // + Matrix::MakeTranslation(Vector3{-global_pass_position}) * // + transform_stack_.back().transform // + ); + + lazy_render_pass.inline_pass_context->EndPass(); + + // Round the subpass texture position for pixel alignment with the parent + // pass render target. By default, we draw subpass textures with nearest + // sampling, so aligning here is important for avoiding visual nearest + // sampling errors caused by limited floating point precision when + // straddling a half pixel boundary. + Point subpass_texture_position; + if (transform_stack_.back().did_round_out) { + // Subpass coverage was rounded out, origin potentially moved "down" by + // as much as a pixel. + subpass_texture_position = + (save_layer_state.coverage.GetOrigin() - global_pass_position) + .Floor(); + } else { + // Subpass coverage was truncated. Pick the closest phyiscal pixel. + subpass_texture_position = + (save_layer_state.coverage.GetOrigin() - global_pass_position) + .Round(); + } + + Entity element_entity; + element_entity.SetClipDepth(++current_depth_); + element_entity.SetContents(std::move(contents)); + element_entity.SetBlendMode(save_layer_state.paint.blend_mode); + element_entity.SetTransform( + Matrix::MakeTranslation(Vector3(subpass_texture_position))); + + if (element_entity.GetBlendMode() > Entity::kLastPipelineBlendMode) { + if (renderer_.GetDeviceCapabilities().SupportsFramebufferFetch()) { + ApplyFramebufferBlend(element_entity); + } else { + // End the active pass and flush the buffer before rendering "advanced" + // blends. Advanced blends work by binding the current render target + // texture as an input ("destination"), blending with a second texture + // input ("source"), writing the result to an intermediate texture, and + // finally copying the data from the intermediate texture back to the + // render target texture. And so all of the commands that have written + // to the render target texture so far need to execute before it's bound + // for blending (otherwise the blend pass will end up executing before + // all the previous commands in the active pass). + auto input_texture = + FlipBackdrop(render_passes_, GetGlobalPassPosition(), + clip_coverage_stack_, renderer_); + if (!input_texture) { + return false; + } + + FilterInput::Vector inputs = { + FilterInput::Make(input_texture, + element_entity.GetTransform().Invert()), + FilterInput::Make(element_entity.GetContents())}; + auto contents = ColorFilterContents::MakeBlend( + element_entity.GetBlendMode(), inputs); + contents->SetCoverageHint(element_entity.GetCoverage()); + element_entity.SetContents(std::move(contents)); + element_entity.SetBlendMode(BlendMode::kSource); + } + } + + element_entity.Render( + renderer_, // + *render_passes_.back().inline_pass_context->GetRenderPass(0).pass // + ); + clip_coverage_stack_.PopSubpass(); + transform_stack_.pop_back(); + + // We don't need to restore clips if a saveLayer was performed, as the clip + // state is per render target, and no more rendering operations will be + // performed as the render target workloaded is completed in the restore. + return true; + } + + size_t num_clips = transform_stack_.back().num_clips; + transform_stack_.pop_back(); + + if (num_clips > 0) { + Entity entity; + entity.SetTransform( + Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * + GetCurrentTransform()); + // This path is empty because ClipRestoreContents just generates a quad that + // takes up the full render target. + auto clip_restore = std::make_shared(); + clip_restore->SetRestoreHeight(GetClipHeight()); + entity.SetContents(std::move(clip_restore)); + + auto current_clip_coverage = clip_coverage_stack_.CurrentClipCoverage(); + if (current_clip_coverage.has_value()) { + // Entity transforms are relative to the current pass position, so we need + // to check clip coverage in the same space. + current_clip_coverage = + current_clip_coverage->Shift(-GetGlobalPassPosition()); + } + + auto clip_coverage = entity.GetClipCoverage(current_clip_coverage); + if (clip_coverage.coverage.has_value()) { + clip_coverage.coverage = + clip_coverage.coverage->Shift(GetGlobalPassPosition()); + } + + EntityPassClipStack::ClipStateResult clip_state_result = + clip_coverage_stack_.ApplyClipState(clip_coverage, entity, + GetClipHeightFloor(), + GetGlobalPassPosition()); + + if (clip_state_result.clip_did_change) { + // We only need to update the pass scissor if the clip state has changed. + SetClipScissor( + clip_coverage_stack_.CurrentClipCoverage(), // + *render_passes_.back().inline_pass_context->GetRenderPass(0).pass, // + GetGlobalPassPosition() // + ); + } + + if (!clip_state_result.should_render) { + return true; + } + + entity.Render( + renderer_, + *render_passes_.back().inline_pass_context->GetRenderPass(0).pass); + } + + return true; +} + +void Canvas::DrawTextFrame(const std::shared_ptr& text_frame, + Point position, + const Paint& paint) { + Entity entity; + entity.SetClipDepth(GetClipHeight()); + entity.SetBlendMode(paint.blend_mode); + + auto text_contents = std::make_shared(); + text_contents->SetTextFrame(text_frame); + text_contents->SetForceTextColor(paint.mask_blur_descriptor.has_value()); + text_contents->SetScale(GetCurrentTransform().GetMaxBasisLengthXY()); + text_contents->SetColor(paint.color); + text_contents->SetOffset(position); + text_contents->SetTextProperties(paint.color, // + paint.style == Paint::Style::kStroke, // + paint.stroke_width, // + paint.stroke_cap, // + paint.stroke_join, // + paint.stroke_miter // + ); + + entity.SetTransform(GetCurrentTransform() * + Matrix::MakeTranslation(position)); + + // TODO(bdero): This mask blur application is a hack. It will always wind up + // doing a gaussian blur that affects the color source itself + // instead of just the mask. The color filter text support + // needs to be reworked in order to interact correctly with + // mask filters. + // https://github.com/flutter/flutter/issues/133297 + entity.SetContents(paint.WithFilters(paint.WithMaskBlur( + std::move(text_contents), true, GetCurrentTransform()))); + + AddRenderEntityToCurrentPass(entity, false); +} + +void Canvas::AddRenderEntityToCurrentPass(Entity& entity, bool reuse_depth) { + if (IsSkipping()) { + return; + } + + entity.SetTransform( + Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * + entity.GetTransform()); + entity.SetInheritedOpacity(transform_stack_.back().distributed_opacity); + if (entity.GetBlendMode() == BlendMode::kSourceOver && + entity.GetContents()->IsOpaque(entity.GetTransform())) { + entity.SetBlendMode(BlendMode::kSource); + } + + // If the entity covers the current render target and is a solid color, then + // conditionally update the backdrop color to its solid color value blended + // with the current backdrop. + if (render_passes_.back().IsApplyingClearColor()) { + std::optional maybe_color = entity.AsBackgroundColor( + render_passes_.back().inline_pass_context->GetTexture()->GetSize()); + if (maybe_color.has_value()) { + Color color = maybe_color.value(); + RenderTarget& render_target = render_passes_.back() + .inline_pass_context->GetPassTarget() + .GetRenderTarget(); + ColorAttachment attachment = + render_target.GetColorAttachments().find(0u)->second; + // Attachment.clear color needs to be premultiplied at all times, but the + // Color::Blend function requires unpremultiplied colors. + attachment.clear_color = attachment.clear_color.Unpremultiply() + .Blend(color, entity.GetBlendMode()) + .Premultiply(); + render_target.SetColorAttachment(attachment, 0u); + return; + } + } + + if (!reuse_depth) { + ++current_depth_; + } + // We can render at a depth up to and including the depth of the currently + // active clips and we will still be clipped out, but we cannot render at + // a depth that is greater than the current clips or we will not be clipped. + FML_DCHECK(current_depth_ <= transform_stack_.back().clip_depth) + << current_depth_ << " <=? " << transform_stack_.back().clip_depth; + entity.SetClipDepth(current_depth_); + + if (entity.GetBlendMode() > Entity::kLastPipelineBlendMode) { + if (renderer_.GetDeviceCapabilities().SupportsFramebufferFetch()) { + ApplyFramebufferBlend(entity); + } else { + // End the active pass and flush the buffer before rendering "advanced" + // blends. Advanced blends work by binding the current render target + // texture as an input ("destination"), blending with a second texture + // input ("source"), writing the result to an intermediate texture, and + // finally copying the data from the intermediate texture back to the + // render target texture. And so all of the commands that have written + // to the render target texture so far need to execute before it's bound + // for blending (otherwise the blend pass will end up executing before + // all the previous commands in the active pass). + auto input_texture = FlipBackdrop(render_passes_, // + GetGlobalPassPosition(), // + clip_coverage_stack_, // + renderer_ // + ); + if (!input_texture) { + return; + } + + // The coverage hint tells the rendered Contents which portion of the + // rendered output will actually be used, and so we set this to the + // current clip coverage (which is the max clip bounds). The contents may + // optionally use this hint to avoid unnecessary rendering work. + auto element_coverage_hint = entity.GetContents()->GetCoverageHint(); + entity.GetContents()->SetCoverageHint(Rect::Intersection( + element_coverage_hint, clip_coverage_stack_.CurrentClipCoverage())); + + FilterInput::Vector inputs = { + FilterInput::Make(input_texture, entity.GetTransform().Invert()), + FilterInput::Make(entity.GetContents())}; + auto contents = + ColorFilterContents::MakeBlend(entity.GetBlendMode(), inputs); + entity.SetContents(std::move(contents)); + entity.SetBlendMode(BlendMode::kSource); + } + } + + InlinePassContext::RenderPassResult result = + render_passes_.back().inline_pass_context->GetRenderPass(0); + if (!result.pass) { + // Failure to produce a render pass should be explained by specific errors + // in `InlinePassContext::GetRenderPass()`, so avoid log spam and don't + // append a validation log here. + return; + } + + entity.Render(renderer_, *result.pass); +} + +void Canvas::AddClipEntityToCurrentPass(Entity& entity) { + if (IsSkipping()) { + return; + } + + auto transform = entity.GetTransform(); + entity.SetTransform( + Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * transform); + + // Ideally the clip depth would be greater than the current rendering + // depth because any rendering calls that follow this clip operation will + // pre-increment the depth and then be rendering above our clip depth, + // but that case will be caught by the CHECK in AddRenderEntity above. + // In practice we sometimes have a clip set with no rendering after it + // and in such cases the current depth will equal the clip depth. + // Eventually the DisplayList should optimize these out, but it is hard + // to know if a clip will actually be used in advance of storing it in + // the DisplayList buffer. + // See https://github.com/flutter/flutter/issues/147021 + FML_DCHECK(current_depth_ <= transform_stack_.back().clip_depth) + << current_depth_ << " <=? " << transform_stack_.back().clip_depth; + entity.SetClipDepth(transform_stack_.back().clip_depth); + + auto current_clip_coverage = clip_coverage_stack_.CurrentClipCoverage(); + if (current_clip_coverage.has_value()) { + // Entity transforms are relative to the current pass position, so we need + // to check clip coverage in the same space. + current_clip_coverage = + current_clip_coverage->Shift(-GetGlobalPassPosition()); + } + + auto clip_coverage = entity.GetClipCoverage(current_clip_coverage); + if (clip_coverage.coverage.has_value()) { + clip_coverage.coverage = + clip_coverage.coverage->Shift(GetGlobalPassPosition()); + } + + EntityPassClipStack::ClipStateResult clip_state_result = + clip_coverage_stack_.ApplyClipState( + clip_coverage, entity, GetClipHeightFloor(), GetGlobalPassPosition()); + + if (clip_state_result.clip_did_change) { + // We only need to update the pass scissor if the clip state has changed. + SetClipScissor( + clip_coverage_stack_.CurrentClipCoverage(), + *render_passes_.back().inline_pass_context->GetRenderPass(0).pass, + GetGlobalPassPosition()); + } + + if (!clip_state_result.should_render) { + return; + } + + entity.Render( + renderer_, + *render_passes_.back().inline_pass_context->GetRenderPass(0).pass); +} + +bool Canvas::BlitToOnscreen() { + auto command_buffer = renderer_.GetContext()->CreateCommandBuffer(); + command_buffer->SetLabel("EntityPass Root Command Buffer"); + auto offscreen_target = render_passes_.back() + .inline_pass_context->GetPassTarget() + .GetRenderTarget(); + + if (renderer_.GetContext() + ->GetCapabilities() + ->SupportsTextureToTextureBlits()) { + auto blit_pass = command_buffer->CreateBlitPass(); + blit_pass->AddCopy(offscreen_target.GetRenderTargetTexture(), + render_target_.GetRenderTargetTexture()); + if (!blit_pass->EncodeCommands( + renderer_.GetContext()->GetResourceAllocator())) { + VALIDATION_LOG << "Failed to encode root pass blit command."; + return false; + } + if (!renderer_.GetContext() + ->GetCommandQueue() + ->Submit({command_buffer}) + .ok()) { + return false; + } + } else { + auto render_pass = command_buffer->CreateRenderPass(render_target_); + render_pass->SetLabel("EntityPass Root Render Pass"); + + { + auto size_rect = Rect::MakeSize(offscreen_target.GetRenderTargetSize()); + auto contents = TextureContents::MakeRect(size_rect); + contents->SetTexture(offscreen_target.GetRenderTargetTexture()); + contents->SetSourceRect(size_rect); + contents->SetLabel("Root pass blit"); + + Entity entity; + entity.SetContents(contents); + entity.SetBlendMode(BlendMode::kSource); + + if (!entity.Render(renderer_, *render_pass)) { + VALIDATION_LOG << "Failed to render EntityPass root blit."; + return false; + } + } + + if (!render_pass->EncodeCommands()) { + VALIDATION_LOG << "Failed to encode root pass command buffer."; + return false; + } + if (!renderer_.GetContext() + ->GetCommandQueue() + ->Submit({command_buffer}) + .ok()) { + return false; + } + } + return true; +} + +void Canvas::EndReplay() { + FML_DCHECK(render_passes_.size() == 1u); + render_passes_.back().inline_pass_context->GetRenderPass(0); + render_passes_.back().inline_pass_context->EndPass(); + + // If requires_readback_ was true, then we rendered to an offscreen texture + // instead of to the onscreen provided in the render target. Now we need to + // draw or blit the offscreen back to the onscreen. + if (requires_readback_) { + BlitToOnscreen(); + } + + render_passes_.clear(); + renderer_.GetRenderTargetCache()->End(); + + Reset(); + Initialize(initial_cull_rect_); } } // namespace impeller diff --git a/impeller/aiks/canvas.h b/impeller/aiks/canvas.h index 0b0c3554c451d..983e2f994090c 100644 --- a/impeller/aiks/canvas.h +++ b/impeller/aiks/canvas.h @@ -13,12 +13,12 @@ #include "impeller/aiks/image_filter.h" #include "impeller/aiks/paint.h" -#include "impeller/aiks/picture.h" #include "impeller/core/sampler_descriptor.h" #include "impeller/entity/entity.h" -#include "impeller/entity/entity_pass.h" +#include "impeller/entity/entity_pass_clip_stack.h" #include "impeller/entity/geometry/geometry.h" #include "impeller/entity/geometry/vertices_geometry.h" +#include "impeller/entity/inline_pass_context.h" #include "impeller/geometry/matrix.h" #include "impeller/geometry/path.h" #include "impeller/geometry/point.h" @@ -29,8 +29,6 @@ namespace impeller { struct CanvasStackEntry { Matrix transform; - // |cull_rect| is conservative screen-space bounds of the clipped output area - std::optional cull_rect; uint32_t clip_depth = 0u; size_t clip_height = 0u; // The number of clips tracked for this canvas stack entry. @@ -61,21 +59,76 @@ enum class SourceRectConstraint { kStrict, }; +/// Specifies how much to trust the bounds rectangle provided for a list +/// of contents. Used by both |EntityPass| and |Canvas::SaveLayer|. +enum class ContentBoundsPromise { + /// @brief The caller makes no claims related to the size of the bounds. + kUnknown, + + /// @brief The caller claims the bounds are a reasonably tight estimate + /// of the coverage of the contents and should contain all of the + /// contents. + kContainsContents, + + /// @brief The caller claims the bounds are a subset of an estimate of + /// the reasonably tight bounds but likely clips off some of the + /// contents. + kMayClipContents, +}; + +struct LazyRenderingConfig { + std::unique_ptr entity_pass_target; + std::unique_ptr inline_pass_context; + + /// Whether or not the clear color texture can still be updated. + bool IsApplyingClearColor() const { return !inline_pass_context->IsActive(); } + + LazyRenderingConfig(ContentContext& renderer, + std::unique_ptr p_entity_pass_target) + : entity_pass_target(std::move(p_entity_pass_target)) { + inline_pass_context = + std::make_unique(renderer, *entity_pass_target, 0); + } + + LazyRenderingConfig(ContentContext& renderer, + std::unique_ptr entity_pass_target, + std::unique_ptr inline_pass_context) + : entity_pass_target(std::move(entity_pass_target)), + inline_pass_context(std::move(inline_pass_context)) {} +}; + class Canvas { public: static constexpr uint32_t kMaxDepth = 1 << 24; - Canvas(); + using BackdropFilterProc = std::function( + FilterInput::Ref, + const Matrix& effect_transform, + Entity::RenderingMode rendering_mode)>; + + Canvas(ContentContext& renderer, + RenderTarget& render_target, + bool requires_readback); + + explicit Canvas(ContentContext& renderer, + RenderTarget& render_target, + bool requires_readback, + Rect cull_rect); - explicit Canvas(Rect cull_rect); + explicit Canvas(ContentContext& renderer, + RenderTarget& render_target, + bool requires_readback, + IRect cull_rect); - explicit Canvas(IRect cull_rect); + ~Canvas() = default; - virtual ~Canvas(); + /// @brief Return the culling bounds of the current render target, or nullopt + /// if there is no coverage. + std::optional GetLocalCoverageLimit() const; - virtual void Save(uint32_t total_content_depth = kMaxDepth); + void Save(uint32_t total_content_depth = kMaxDepth); - virtual void SaveLayer( + void SaveLayer( const Paint& paint, std::optional bounds = std::nullopt, const std::shared_ptr& backdrop_filter = nullptr, @@ -83,7 +136,7 @@ class Canvas { uint32_t total_content_depth = kMaxDepth, bool can_distribute_opacity = false); - virtual bool Restore(); + bool Restore(); size_t GetSaveCount() const; @@ -91,8 +144,6 @@ class Canvas { const Matrix& GetCurrentTransform() const; - const std::optional GetCurrentLocalCullingBounds() const; - void ResetTransform(); void Transform(const Matrix& transform); @@ -162,9 +213,9 @@ class Canvas { const Size& corner_radii, Entity::ClipOperation clip_op = Entity::ClipOperation::kIntersect); - virtual void DrawTextFrame(const std::shared_ptr& text_frame, - Point position, - const Paint& paint); + void DrawTextFrame(const std::shared_ptr& text_frame, + Point position, + const Paint& paint); void DrawVertices(const std::shared_ptr& vertices, BlendMode blend_mode, @@ -179,44 +230,59 @@ class Canvas { std::optional cull_rect, const Paint& paint); - Picture EndRecordingAsPicture(); + void EndReplay(); uint64_t GetOpDepth() const { return current_depth_; } + uint64_t GetMaxOpDepth() const { return transform_stack_.back().clip_depth; } - protected: + struct SaveLayerState { + Paint paint; + Rect coverage; + }; + + private: + ContentContext& renderer_; + RenderTarget& render_target_; + const bool requires_readback_; + EntityPassClipStack clip_coverage_stack_; + std::deque transform_stack_; std::optional initial_cull_rect_; + std::vector render_passes_; + std::vector save_layer_state_; + uint64_t current_depth_ = 0u; + Point GetGlobalPassPosition() const; + + // clip depth of the previous save or 0. + size_t GetClipHeightFloor() const; + + /// @brief Whether all entites should be skipped until a corresponding + /// restore. + bool IsSkipping() const; + + /// @brief Skip all rendering/clipping entities until next restore. + void SkipUntilMatchingRestore(size_t total_content_depth); + + void SetupRenderPass(); + + bool BlitToOnscreen(); + size_t GetClipHeight() const; void Initialize(std::optional cull_rect); void Reset(); - private: - std::unique_ptr base_pass_; - EntityPass* current_pass_ = nullptr; - - EntityPass& GetCurrentPass(); + void AddRenderEntityToCurrentPass(Entity& entity, bool reuse_depth = false); - virtual void AddRenderEntityToCurrentPass(Entity entity, - bool reuse_depth = false); - virtual void AddClipEntityToCurrentPass(Entity entity); + void AddClipEntityToCurrentPass(Entity& entity); void ClipGeometry(const std::shared_ptr& geometry, Entity::ClipOperation clip_op); - void IntersectCulling(Rect clip_bounds); - void SubtractCulling(Rect clip_bounds); - - virtual void Save( - bool create_subpass, - uint32_t total_content_depth, - BlendMode = BlendMode::kSourceOver, - const std::shared_ptr& backdrop_filter = nullptr); - void RestoreClip(); bool AttemptDrawBlurredRRect(const Rect& rect, diff --git a/impeller/aiks/canvas_benchmarks.cc b/impeller/aiks/canvas_benchmarks.cc deleted file mode 100644 index 8f4a310e0ceb7..0000000000000 --- a/impeller/aiks/canvas_benchmarks.cc +++ /dev/null @@ -1,63 +0,0 @@ -// 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 "flutter/benchmarking/benchmarking.h" - -#include "impeller/aiks/canvas.h" - -namespace impeller { - -namespace { - -using CanvasCallback = size_t (*)(Canvas&); - -size_t DrawRect(Canvas& canvas) { - for (auto i = 0; i < 500; i++) { - canvas.DrawRect(Rect::MakeLTRB(0, 0, 100, 100), - {.color = Color::DarkKhaki()}); - } - return 500; -} - -size_t DrawCircle(Canvas& canvas) { - for (auto i = 0; i < 500; i++) { - canvas.DrawCircle({100, 100}, 5, {.color = Color::DarkKhaki()}); - } - return 500; -} - -size_t DrawLine(Canvas& canvas) { - for (auto i = 0; i < 500; i++) { - canvas.DrawLine({0, 0}, {100, 100}, {.color = Color::DarkKhaki()}); - } - return 500; -} -} // namespace - -// A set of benchmarks that measures the CPU cost of encoding canvas operations. -// These benchmarks do not measure the cost of conversion through the HAL, no -// do they measure the GPU side cost of executing the required shader programs. -template -static void BM_CanvasRecord(benchmark::State& state, Args&&... args) { - auto args_tuple = std::make_tuple(std::move(args)...); - auto test_proc = std::get(args_tuple); - - size_t op_count = 0u; - size_t canvas_count = 0u; - while (state.KeepRunning()) { - // A new canvas is allocated for each iteration to avoid the benchmark - // becoming a measurement of only the entity vector re-allocation time. - Canvas canvas; - op_count += test_proc(canvas); - canvas_count++; - } - state.counters["TotalOpCount"] = op_count; - state.counters["TotalCanvasCount"] = canvas_count; -} - -BENCHMARK_CAPTURE(BM_CanvasRecord, draw_rect, &DrawRect); -BENCHMARK_CAPTURE(BM_CanvasRecord, draw_circle, &DrawCircle); -BENCHMARK_CAPTURE(BM_CanvasRecord, draw_line, &DrawLine); - -} // namespace impeller diff --git a/impeller/aiks/canvas_unittests.cc b/impeller/aiks/canvas_unittests.cc index 33623e356326d..2fb3acefb2144 100644 --- a/impeller/aiks/canvas_unittests.cc +++ b/impeller/aiks/canvas_unittests.cc @@ -5,7 +5,7 @@ #include "flutter/testing/testing.h" #include "impeller/aiks/aiks_context.h" #include "impeller/aiks/aiks_unittests.h" -#include "impeller/aiks/experimental_canvas.h" +#include "impeller/aiks/canvas.h" #include "impeller/geometry/geometry_asserts.h" #include "impeller/geometry/path_builder.h" @@ -15,17 +15,17 @@ namespace impeller { namespace testing { -std::unique_ptr CreateTestCanvas( +std::unique_ptr CreateTestCanvas( ContentContext& context, std::optional cull_rect = std::nullopt) { RenderTarget render_target = context.GetRenderTargetCache()->CreateOffscreen( *context.GetContext(), {1, 1}, 1); if (cull_rect.has_value()) { - return std::make_unique(context, render_target, false, - cull_rect.value()); + return std::make_unique(context, render_target, false, + cull_rect.value()); } - return std::make_unique(context, render_target, false); + return std::make_unique(context, render_target, false); } TEST_P(AiksTest, TransformMultipliesCorrectly) { @@ -98,353 +98,31 @@ TEST_P(AiksTest, CanvasCTMCanBeUpdated) { Matrix::MakeTranslation({100.0, 100.0, 0.0})); } -TEST_P(AiksTest, EmptyCullRect) { - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context); - - ASSERT_FALSE(canvas->GetCurrentLocalCullingBounds().has_value()); -} - -TEST_P(AiksTest, InitialCullRect) { - Rect initial_cull = Rect::MakeXYWH(0, 0, 10, 10); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), initial_cull); -} - -TEST_P(AiksTest, TranslatedCullRect) { - Rect initial_cull = Rect::MakeXYWH(5, 5, 10, 10); - Rect translated_cull = Rect::MakeXYWH(0, 0, 10, 10); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->Translate(Vector3(5, 5, 0)); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), translated_cull); -} - -TEST_P(AiksTest, ScaledCullRect) { - Rect initial_cull = Rect::MakeXYWH(5, 5, 10, 10); - Rect scaled_cull = Rect::MakeXYWH(10, 10, 20, 20); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->Scale(Vector2(0.5, 0.5)); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), scaled_cull); -} - -TEST_P(AiksTest, RectClipIntersectAgainstEmptyCullRect) { - Rect rect_clip = Rect::MakeXYWH(5, 5, 10, 10); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context); - canvas->ClipRect(rect_clip, Entity::ClipOperation::kIntersect); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), rect_clip); -} - -TEST_P(AiksTest, RectClipDiffAgainstEmptyCullRect) { - Rect rect_clip = Rect::MakeXYWH(5, 5, 10, 10); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context); - canvas->ClipRect(rect_clip, Entity::ClipOperation::kDifference); - - ASSERT_FALSE(canvas->GetCurrentLocalCullingBounds().has_value()); -} - -TEST_P(AiksTest, RectClipIntersectAgainstCullRect) { - Rect initial_cull = Rect::MakeXYWH(0, 0, 10, 10); - Rect rect_clip = Rect::MakeXYWH(5, 5, 10, 10); - Rect result_cull = Rect::MakeXYWH(5, 5, 5, 5); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipRect(rect_clip, Entity::ClipOperation::kIntersect); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); -} - -TEST_P(AiksTest, RectClipDiffAgainstNonCoveredCullRect) { - Rect initial_cull = Rect::MakeXYWH(0, 0, 10, 10); - Rect rect_clip = Rect::MakeXYWH(5, 5, 10, 10); - Rect result_cull = Rect::MakeXYWH(0, 0, 10, 10); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipRect(rect_clip, Entity::ClipOperation::kDifference); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); -} - -TEST_P(AiksTest, RectClipDiffAboveCullRect) { - Rect initial_cull = Rect::MakeXYWH(5, 5, 10, 10); - Rect rect_clip = Rect::MakeXYWH(0, 0, 20, 4); - Rect result_cull = Rect::MakeXYWH(5, 5, 10, 10); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipRect(rect_clip, Entity::ClipOperation::kDifference); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); -} - -TEST_P(AiksTest, RectClipDiffBelowCullRect) { - Rect initial_cull = Rect::MakeXYWH(5, 5, 10, 10); - Rect rect_clip = Rect::MakeXYWH(0, 16, 20, 4); - Rect result_cull = Rect::MakeXYWH(5, 5, 10, 10); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipRect(rect_clip, Entity::ClipOperation::kDifference); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); -} - -TEST_P(AiksTest, RectClipDiffLeftOfCullRect) { - Rect initial_cull = Rect::MakeXYWH(5, 5, 10, 10); - Rect rect_clip = Rect::MakeXYWH(0, 0, 4, 20); - Rect result_cull = Rect::MakeXYWH(5, 5, 10, 10); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipRect(rect_clip, Entity::ClipOperation::kDifference); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); -} - -TEST_P(AiksTest, RectClipDiffRightOfCullRect) { - Rect initial_cull = Rect::MakeXYWH(5, 5, 10, 10); - Rect rect_clip = Rect::MakeXYWH(16, 0, 4, 20); - Rect result_cull = Rect::MakeXYWH(5, 5, 10, 10); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipRect(rect_clip, Entity::ClipOperation::kDifference); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); -} - -TEST_P(AiksTest, RectClipDiffAgainstVCoveredCullRect) { - Rect initial_cull = Rect::MakeXYWH(0, 0, 10, 10); - Rect rect_clip = Rect::MakeXYWH(5, 0, 10, 10); - Rect result_cull = Rect::MakeXYWH(0, 0, 5, 10); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipRect(rect_clip, Entity::ClipOperation::kDifference); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); -} +TEST_P(AiksTest, PaintWithFilters) { + // validate that a paint with a color filter "HasFilters", no other filters + // impact this setting. + Paint paint; -TEST_P(AiksTest, RectClipDiffAgainstHCoveredCullRect) { - Rect initial_cull = Rect::MakeXYWH(0, 0, 10, 10); - Rect rect_clip = Rect::MakeXYWH(0, 5, 10, 10); - Rect result_cull = Rect::MakeXYWH(0, 0, 10, 5); + ASSERT_FALSE(paint.HasColorFilter()); - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipRect(rect_clip, Entity::ClipOperation::kDifference); + paint.color_filter = + ColorFilter::MakeBlend(BlendMode::kSourceOver, Color::Blue()); - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); -} + ASSERT_TRUE(paint.HasColorFilter()); -TEST_P(AiksTest, RRectClipIntersectAgainstEmptyCullRect) { - Rect rect_clip = Rect::MakeXYWH(5, 5, 10, 10); + paint.image_filter = ImageFilter::MakeBlur(Sigma(1.0), Sigma(1.0), + FilterContents::BlurStyle::kNormal, + Entity::TileMode::kClamp); - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context); - canvas->ClipRRect(rect_clip, {1, 1}, Entity::ClipOperation::kIntersect); + ASSERT_TRUE(paint.HasColorFilter()); - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), rect_clip); -} - -TEST_P(AiksTest, RRectClipDiffAgainstEmptyCullRect) { - Rect rect_clip = Rect::MakeXYWH(5, 5, 10, 10); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context); - canvas->ClipRRect(rect_clip, {1, 1}, Entity::ClipOperation::kDifference); - - ASSERT_FALSE(canvas->GetCurrentLocalCullingBounds().has_value()); -} - -TEST_P(AiksTest, RRectClipIntersectAgainstCullRect) { - Rect initial_cull = Rect::MakeXYWH(0, 0, 10, 10); - Rect rect_clip = Rect::MakeXYWH(5, 5, 10, 10); - Rect result_cull = Rect::MakeXYWH(5, 5, 5, 5); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipRRect(rect_clip, {1, 1}, Entity::ClipOperation::kIntersect); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); -} + paint.mask_blur_descriptor = {}; -TEST_P(AiksTest, RRectClipDiffAgainstNonCoveredCullRect) { - Rect initial_cull = Rect::MakeXYWH(0, 0, 10, 10); - Rect rect_clip = Rect::MakeXYWH(5, 5, 10, 10); - Rect result_cull = Rect::MakeXYWH(0, 0, 10, 10); + ASSERT_TRUE(paint.HasColorFilter()); - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipRRect(rect_clip, {1, 1}, Entity::ClipOperation::kDifference); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); -} - -TEST_P(AiksTest, RRectClipDiffAgainstVPartiallyCoveredCullRect) { - Rect initial_cull = Rect::MakeXYWH(0, 0, 10, 10); - Rect rect_clip = Rect::MakeXYWH(5, 0, 10, 10); - Rect result_cull = Rect::MakeXYWH(0, 0, 6, 10); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipRRect(rect_clip, {1, 1}, Entity::ClipOperation::kDifference); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); -} - -TEST_P(AiksTest, RRectClipDiffAgainstVFullyCoveredCullRect) { - Rect initial_cull = Rect::MakeXYWH(0, 0, 10, 10); - Rect rect_clip = Rect::MakeXYWH(5, -2, 10, 14); - Rect result_cull = Rect::MakeXYWH(0, 0, 5, 10); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipRRect(rect_clip, {1, 1}, Entity::ClipOperation::kDifference); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); -} - -TEST_P(AiksTest, RRectClipDiffAgainstHPartiallyCoveredCullRect) { - Rect initial_cull = Rect::MakeXYWH(0, 0, 10, 10); - Rect rect_clip = Rect::MakeXYWH(0, 5, 10, 10); - Rect result_cull = Rect::MakeXYWH(0, 0, 10, 6); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipRRect(rect_clip, {1, 1}, Entity::ClipOperation::kDifference); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); -} - -TEST_P(AiksTest, RRectClipDiffAgainstHFullyCoveredCullRect) { - Rect initial_cull = Rect::MakeXYWH(0, 0, 10, 10); - Rect rect_clip = Rect::MakeXYWH(-2, 5, 14, 10); - Rect result_cull = Rect::MakeXYWH(0, 0, 10, 5); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipRRect(rect_clip, {1, 1}, Entity::ClipOperation::kDifference); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); -} - -TEST_P(AiksTest, PathClipIntersectAgainstEmptyCullRect) { - PathBuilder builder; - builder.AddRect(Rect::MakeXYWH(5, 5, 1, 1)); - builder.AddRect(Rect::MakeXYWH(5, 14, 1, 1)); - builder.AddRect(Rect::MakeXYWH(14, 5, 1, 1)); - builder.AddRect(Rect::MakeXYWH(14, 14, 1, 1)); - Path path = builder.TakePath(); - Rect rect_clip = Rect::MakeXYWH(5, 5, 10, 10); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context); - canvas->ClipPath(path, Entity::ClipOperation::kIntersect); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), rect_clip); -} - -TEST_P(AiksTest, PathClipDiffAgainstEmptyCullRect) { - PathBuilder builder; - builder.AddRect(Rect::MakeXYWH(5, 5, 1, 1)); - builder.AddRect(Rect::MakeXYWH(5, 14, 1, 1)); - builder.AddRect(Rect::MakeXYWH(14, 5, 1, 1)); - builder.AddRect(Rect::MakeXYWH(14, 14, 1, 1)); - Path path = builder.TakePath(); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context); - canvas->ClipPath(path, Entity::ClipOperation::kDifference); - - ASSERT_FALSE(canvas->GetCurrentLocalCullingBounds().has_value()); -} - -TEST_P(AiksTest, PathClipIntersectAgainstCullRect) { - Rect initial_cull = Rect::MakeXYWH(0, 0, 10, 10); - PathBuilder builder; - builder.AddRect(Rect::MakeXYWH(5, 5, 1, 1)); - builder.AddRect(Rect::MakeXYWH(5, 14, 1, 1)); - builder.AddRect(Rect::MakeXYWH(14, 5, 1, 1)); - builder.AddRect(Rect::MakeXYWH(14, 14, 1, 1)); - Path path = builder.TakePath(); - Rect result_cull = Rect::MakeXYWH(5, 5, 5, 5); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipPath(path, Entity::ClipOperation::kIntersect); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); -} - -TEST_P(AiksTest, PathClipDiffAgainstNonCoveredCullRect) { - Rect initial_cull = Rect::MakeXYWH(0, 0, 10, 10); - PathBuilder builder; - builder.AddRect(Rect::MakeXYWH(5, 5, 1, 1)); - builder.AddRect(Rect::MakeXYWH(5, 14, 1, 1)); - builder.AddRect(Rect::MakeXYWH(14, 5, 1, 1)); - builder.AddRect(Rect::MakeXYWH(14, 14, 1, 1)); - Path path = builder.TakePath(); - Rect result_cull = Rect::MakeXYWH(0, 0, 10, 10); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipPath(path, Entity::ClipOperation::kDifference); - - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); -} - -TEST_P(AiksTest, PathClipDiffAgainstFullyCoveredCullRect) { - Rect initial_cull = Rect::MakeXYWH(5, 5, 10, 10); - PathBuilder builder; - builder.AddRect(Rect::MakeXYWH(0, 0, 100, 100)); - Path path = builder.TakePath(); - // Diff clip of Paths is ignored due to complexity - Rect result_cull = Rect::MakeXYWH(5, 5, 10, 10); - - ContentContext context(GetContext(), nullptr); - auto canvas = CreateTestCanvas(context, initial_cull); - canvas->ClipPath(path, Entity::ClipOperation::kDifference); + paint.color_filter = nullptr; - ASSERT_TRUE(canvas->GetCurrentLocalCullingBounds().has_value()); - ASSERT_EQ(canvas->GetCurrentLocalCullingBounds().value(), result_cull); + ASSERT_FALSE(paint.HasColorFilter()); } } // namespace testing diff --git a/impeller/aiks/experimental_canvas.cc b/impeller/aiks/experimental_canvas.cc deleted file mode 100644 index b262e8dbb3a77..0000000000000 --- a/impeller/aiks/experimental_canvas.cc +++ /dev/null @@ -1,950 +0,0 @@ -// 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 "impeller/aiks/experimental_canvas.h" - -#include -#include - -#include "fml/logging.h" -#include "fml/trace_event.h" -#include "impeller/aiks/canvas.h" -#include "impeller/aiks/paint_pass_delegate.h" -#include "impeller/base/validation.h" -#include "impeller/core/allocator.h" -#include "impeller/core/formats.h" -#include "impeller/entity/contents/clip_contents.h" -#include "impeller/entity/contents/filters/filter_contents.h" -#include "impeller/entity/contents/framebuffer_blend_contents.h" -#include "impeller/entity/contents/text_contents.h" -#include "impeller/entity/entity.h" -#include "impeller/entity/entity_pass_clip_stack.h" -#include "impeller/entity/save_layer_utils.h" -#include "impeller/geometry/color.h" -#include "impeller/renderer/render_target.h" - -namespace impeller { - -namespace { - -static void SetClipScissor(std::optional clip_coverage, - RenderPass& pass, - Point global_pass_position) { - // Set the scissor to the clip coverage area. We do this prior to rendering - // the clip itself and all its contents. - IRect scissor; - if (clip_coverage.has_value()) { - clip_coverage = clip_coverage->Shift(-global_pass_position); - scissor = IRect::RoundOut(clip_coverage.value()); - // The scissor rect must not exceed the size of the render target. - scissor = scissor.Intersection(IRect::MakeSize(pass.GetRenderTargetSize())) - .value_or(IRect()); - } - pass.SetScissor(scissor); -} - -static void ApplyFramebufferBlend(Entity& entity) { - auto src_contents = entity.GetContents(); - auto contents = std::make_shared(); - contents->SetChildContents(src_contents); - contents->SetBlendMode(entity.GetBlendMode()); - entity.SetContents(std::move(contents)); - entity.SetBlendMode(BlendMode::kSource); -} - -/// End the current render pass, saving the result as a texture, and then -/// restart it with the backdrop cleared to the previous contents. -/// -/// This method is used to set up the input for emulated advanced blends and -/// backdrop filters. -/// -/// Returns the previous render pass stored as a texture, or nullptr if there -/// was a validation failure. -static std::shared_ptr FlipBackdrop( - std::vector& render_passes, - Point global_pass_position, - EntityPassClipStack& clip_coverage_stack, - ContentContext& renderer) { - auto rendering_config = std::move(render_passes.back()); - render_passes.pop_back(); - - // If the very first thing we render in this EntityPass is a subpass that - // happens to have a backdrop filter or advanced blend, than that backdrop - // filter/blend will sample from an uninitialized texture. - // - // By calling `pass_context.GetRenderPass` here, we force the texture to pass - // through at least one RenderPass with the correct clear configuration before - // any sampling occurs. - // - // In cases where there are no contents, we - // could instead check the clear color and initialize a 1x2 CPU texture - // instead of ending the pass. - rendering_config.inline_pass_context->GetRenderPass(0); - if (!rendering_config.inline_pass_context->EndPass()) { - VALIDATION_LOG - << "Failed to end the current render pass in order to read from " - "the backdrop texture and apply an advanced blend or backdrop " - "filter."; - // Note: adding this render pass ensures there are no later crashes from - // unbalanced save layers. Ideally, this method would return false and the - // renderer could handle that by terminating dispatch. - render_passes.push_back(LazyRenderingConfig( - renderer, std::move(rendering_config.entity_pass_target), - std::move(rendering_config.inline_pass_context))); - return nullptr; - } - - std::shared_ptr input_texture = - rendering_config.inline_pass_context->GetTexture(); - - if (!input_texture) { - VALIDATION_LOG << "Failed to fetch the color texture in order to " - "apply an advanced blend or backdrop filter."; - - // Note: see above. - render_passes.push_back(LazyRenderingConfig( - renderer, std::move(rendering_config.entity_pass_target), - std::move(rendering_config.inline_pass_context))); - return nullptr; - } - - render_passes.push_back(LazyRenderingConfig( - renderer, std::move(rendering_config.entity_pass_target), - std::move(rendering_config.inline_pass_context))); - // Eagerly restore the BDF contents. - - // If the pass context returns a backdrop texture, we need to draw it to the - // current pass. We do this because it's faster and takes significantly less - // memory than storing/loading large MSAA textures. Also, it's not possible - // to blit the non-MSAA resolve texture of the previous pass to MSAA - // textures (let alone a transient one). - Rect size_rect = Rect::MakeSize(input_texture->GetSize()); - auto msaa_backdrop_contents = TextureContents::MakeRect(size_rect); - msaa_backdrop_contents->SetStencilEnabled(false); - msaa_backdrop_contents->SetLabel("MSAA backdrop"); - msaa_backdrop_contents->SetSourceRect(size_rect); - msaa_backdrop_contents->SetTexture(input_texture); - - Entity msaa_backdrop_entity; - msaa_backdrop_entity.SetContents(std::move(msaa_backdrop_contents)); - msaa_backdrop_entity.SetBlendMode(BlendMode::kSource); - msaa_backdrop_entity.SetClipDepth(std::numeric_limits::max()); - if (!msaa_backdrop_entity.Render( - renderer, - *render_passes.back().inline_pass_context->GetRenderPass(0).pass)) { - VALIDATION_LOG << "Failed to render MSAA backdrop entity."; - return nullptr; - } - - // Restore any clips that were recorded before the backdrop filter was - // applied. - auto& replay_entities = clip_coverage_stack.GetReplayEntities(); - for (const auto& replay : replay_entities) { - SetClipScissor( - replay.clip_coverage, - *render_passes.back().inline_pass_context->GetRenderPass(0).pass, - global_pass_position); - if (!replay.entity.Render( - renderer, - *render_passes.back().inline_pass_context->GetRenderPass(0).pass)) { - VALIDATION_LOG << "Failed to render entity for clip restore."; - } - } - - return input_texture; -} - -} // namespace - -static const constexpr RenderTarget::AttachmentConfig kDefaultStencilConfig = - RenderTarget::AttachmentConfig{ - .storage_mode = StorageMode::kDeviceTransient, - .load_action = LoadAction::kDontCare, - .store_action = StoreAction::kDontCare, - }; - -static std::unique_ptr CreateRenderTarget( - ContentContext& renderer, - ISize size, - const Color& clear_color) { - const std::shared_ptr& context = renderer.GetContext(); - - /// All of the load/store actions are managed by `InlinePassContext` when - /// `RenderPasses` are created, so we just set them to `kDontCare` here. - /// What's important is the `StorageMode` of the textures, which cannot be - /// changed for the lifetime of the textures. - - RenderTarget target; - if (context->GetCapabilities()->SupportsOffscreenMSAA()) { - target = renderer.GetRenderTargetCache()->CreateOffscreenMSAA( - /*context=*/*context, - /*size=*/size, - /*mip_count=*/1, - /*label=*/"EntityPass", - /*color_attachment_config=*/ - RenderTarget::AttachmentConfigMSAA{ - .storage_mode = StorageMode::kDeviceTransient, - .resolve_storage_mode = StorageMode::kDevicePrivate, - .load_action = LoadAction::kDontCare, - .store_action = StoreAction::kMultisampleResolve, - .clear_color = clear_color}, - /*stencil_attachment_config=*/kDefaultStencilConfig); - } else { - target = renderer.GetRenderTargetCache()->CreateOffscreen( - *context, // context - size, // size - /*mip_count=*/1, - "EntityPass", // label - RenderTarget::AttachmentConfig{ - .storage_mode = StorageMode::kDevicePrivate, - .load_action = LoadAction::kDontCare, - .store_action = StoreAction::kDontCare, - .clear_color = clear_color, - }, // color_attachment_config - kDefaultStencilConfig // - ); - } - - return std::make_unique( - target, renderer.GetDeviceCapabilities().SupportsReadFromResolve(), - renderer.GetDeviceCapabilities().SupportsImplicitResolvingMSAA()); -} - -ExperimentalCanvas::ExperimentalCanvas(ContentContext& renderer, - RenderTarget& render_target, - bool requires_readback) - : Canvas(), - renderer_(renderer), - render_target_(render_target), - requires_readback_(requires_readback), - clip_coverage_stack_(EntityPassClipStack( - Rect::MakeSize(render_target.GetRenderTargetSize()))) { - SetupRenderPass(); -} - -ExperimentalCanvas::ExperimentalCanvas(ContentContext& renderer, - RenderTarget& render_target, - bool requires_readback, - Rect cull_rect) - : Canvas(cull_rect), - renderer_(renderer), - render_target_(render_target), - requires_readback_(requires_readback), - clip_coverage_stack_(EntityPassClipStack( - Rect::MakeSize(render_target.GetRenderTargetSize()))) { - SetupRenderPass(); -} - -ExperimentalCanvas::ExperimentalCanvas(ContentContext& renderer, - RenderTarget& render_target, - bool requires_readback, - IRect cull_rect) - : Canvas(cull_rect), - renderer_(renderer), - render_target_(render_target), - requires_readback_(requires_readback), - clip_coverage_stack_(EntityPassClipStack( - Rect::MakeSize(render_target.GetRenderTargetSize()))) { - SetupRenderPass(); -} - -void ExperimentalCanvas::SetupRenderPass() { - renderer_.GetRenderTargetCache()->Start(); - auto color0 = render_target_.GetColorAttachments().find(0u)->second; - - auto& stencil_attachment = render_target_.GetStencilAttachment(); - auto& depth_attachment = render_target_.GetDepthAttachment(); - if (!stencil_attachment.has_value() || !depth_attachment.has_value()) { - // Setup a new root stencil with an optimal configuration if one wasn't - // provided by the caller. - render_target_.SetupDepthStencilAttachments( - *renderer_.GetContext(), - *renderer_.GetContext()->GetResourceAllocator(), - color0.texture->GetSize(), - renderer_.GetContext()->GetCapabilities()->SupportsOffscreenMSAA(), - "ImpellerOnscreen", kDefaultStencilConfig); - } - - // Set up the clear color of the root pass. - color0.clear_color = Color::BlackTransparent(); - render_target_.SetColorAttachment(color0, 0); - - // If requires_readback is true, then there is a backdrop filter or emulated - // advanced blend in the first save layer. This requires a readback, which - // isn't supported by onscreen textures. To support this, we immediately begin - // a second save layer with the same dimensions as the onscreen. When - // rendering is completed, we must blit this saveLayer to the onscreen. - if (requires_readback_) { - auto entity_pass_target = - CreateRenderTarget(renderer_, // - color0.texture->GetSize(), // - /*clear_color=*/Color::BlackTransparent()); - render_passes_.push_back( - LazyRenderingConfig(renderer_, std::move(entity_pass_target))); - } else { - auto entity_pass_target = std::make_unique( - render_target_, // - renderer_.GetDeviceCapabilities().SupportsReadFromResolve(), // - renderer_.GetDeviceCapabilities().SupportsImplicitResolvingMSAA() // - ); - render_passes_.push_back( - LazyRenderingConfig(renderer_, std::move(entity_pass_target))); - } -} - -void ExperimentalCanvas::SkipUntilMatchingRestore(size_t total_content_depth) { - auto entry = CanvasStackEntry{}; - entry.skipping = true; - entry.clip_depth = current_depth_ + total_content_depth; - transform_stack_.push_back(entry); -} - -void ExperimentalCanvas::Save(uint32_t total_content_depth) { - if (IsSkipping()) { - return SkipUntilMatchingRestore(total_content_depth); - } - - auto entry = CanvasStackEntry{}; - entry.transform = transform_stack_.back().transform; - entry.cull_rect = transform_stack_.back().cull_rect; - entry.clip_depth = current_depth_ + total_content_depth; - entry.distributed_opacity = transform_stack_.back().distributed_opacity; - FML_DCHECK(entry.clip_depth <= transform_stack_.back().clip_depth) - << entry.clip_depth << " <=? " << transform_stack_.back().clip_depth - << " after allocating " << total_content_depth; - entry.clip_height = transform_stack_.back().clip_height; - entry.rendering_mode = Entity::RenderingMode::kDirect; - transform_stack_.push_back(entry); -} - -std::optional ExperimentalCanvas::ComputeCoverageLimit() const { - if (!clip_coverage_stack_.HasCoverage()) { - // The current clip is empty. This means the pass texture won't be - // visible, so skip it. - return std::nullopt; - } - - auto maybe_current_clip_coverage = clip_coverage_stack_.CurrentClipCoverage(); - if (!maybe_current_clip_coverage.has_value()) { - return std::nullopt; - } - - auto current_clip_coverage = maybe_current_clip_coverage.value(); - - // The maximum coverage of the subpass. Subpasses textures should never - // extend outside the parent pass texture or the current clip coverage. - std::optional maybe_coverage_limit = - Rect::MakeOriginSize(GetGlobalPassPosition(), - Size(render_passes_.back() - .inline_pass_context->GetTexture() - ->GetSize())) - .Intersection(current_clip_coverage); - - if (!maybe_coverage_limit.has_value() || maybe_coverage_limit->IsEmpty()) { - return std::nullopt; - } - - return maybe_coverage_limit->Intersection( - Rect::MakeSize(render_target_.GetRenderTargetSize())); -} - -void ExperimentalCanvas::SaveLayer( - const Paint& paint, - std::optional bounds, - const std::shared_ptr& backdrop_filter, - ContentBoundsPromise bounds_promise, - uint32_t total_content_depth, - bool can_distribute_opacity) { - TRACE_EVENT0("flutter", "Canvas::saveLayer"); - if (IsSkipping()) { - return SkipUntilMatchingRestore(total_content_depth); - } - - auto maybe_coverage_limit = ComputeCoverageLimit(); - if (!maybe_coverage_limit.has_value()) { - return SkipUntilMatchingRestore(total_content_depth); - } - auto coverage_limit = maybe_coverage_limit.value(); - - if (can_distribute_opacity && !backdrop_filter && - Paint::CanApplyOpacityPeephole(paint) && - bounds_promise != ContentBoundsPromise::kMayClipContents) { - Save(total_content_depth); - transform_stack_.back().distributed_opacity *= paint.color.alpha; - return; - } - - std::shared_ptr filter_contents = paint.WithImageFilter( - Rect(), transform_stack_.back().transform, - Entity::RenderingMode::kSubpassPrependSnapshotTransform); - - std::optional maybe_subpass_coverage = ComputeSaveLayerCoverage( - bounds.value_or(Rect::MakeMaximum()), - transform_stack_.back().transform, // - coverage_limit, // - filter_contents, // - /*flood_output_coverage=*/ - Entity::IsBlendModeDestructive(paint.blend_mode), // - /*flood_input_coverage=*/!!backdrop_filter // - ); - - if (!maybe_subpass_coverage.has_value()) { - return SkipUntilMatchingRestore(total_content_depth); - } - - auto subpass_coverage = maybe_subpass_coverage.value(); - - // When an image filter is present, clamp to avoid flicking due to nearest - // sampled image. For other cases, round out to ensure than any geometry is - // not cut off. - // - // See also this bug: https://github.com/flutter/flutter/issues/144213 - // - // TODO(jonahwilliams): this could still round out for filters that use decal - // sampling mode. - ISize subpass_size; - bool did_round_out = false; - if (paint.image_filter) { - subpass_size = ISize(subpass_coverage.GetSize()); - } else { - did_round_out = true; - subpass_size = ISize(IRect::RoundOut(subpass_coverage).GetSize()); - } - if (subpass_size.IsEmpty()) { - return SkipUntilMatchingRestore(total_content_depth); - } - - // Backdrop filter state, ignored if there is no BDF. - std::shared_ptr backdrop_filter_contents; - Point local_position = {0, 0}; - if (backdrop_filter) { - local_position = subpass_coverage.GetOrigin() - GetGlobalPassPosition(); - EntityPass::BackdropFilterProc backdrop_filter_proc = - [backdrop_filter = backdrop_filter->Clone()]( - const FilterInput::Ref& input, const Matrix& effect_transform, - Entity::RenderingMode rendering_mode) { - auto filter = backdrop_filter->WrapInput(input); - filter->SetEffectTransform(effect_transform); - filter->SetRenderingMode(rendering_mode); - return filter; - }; - - auto input_texture = FlipBackdrop(render_passes_, // - GetGlobalPassPosition(), // - clip_coverage_stack_, // - renderer_ // - ); - if (!input_texture) { - // Validation failures are logged in FlipBackdrop. - return; - } - - backdrop_filter_contents = backdrop_filter_proc( - FilterInput::Make(std::move(input_texture)), - transform_stack_.back().transform.Basis(), - // When the subpass has a translation that means the math with - // the snapshot has to be different. - transform_stack_.back().transform.HasTranslation() - ? Entity::RenderingMode::kSubpassPrependSnapshotTransform - : Entity::RenderingMode::kSubpassAppendSnapshotTransform); - } - - // When applying a save layer, absorb any pending distributed opacity. - Paint paint_copy = paint; - paint_copy.color.alpha *= transform_stack_.back().distributed_opacity; - transform_stack_.back().distributed_opacity = 1.0; - - render_passes_.push_back( - LazyRenderingConfig(renderer_, // - CreateRenderTarget(renderer_, // - subpass_size, // - Color::BlackTransparent() // - ))); - save_layer_state_.push_back(SaveLayerState{paint_copy, subpass_coverage}); - - CanvasStackEntry entry; - entry.transform = transform_stack_.back().transform; - entry.cull_rect = transform_stack_.back().cull_rect; - entry.clip_depth = current_depth_ + total_content_depth; - FML_DCHECK(entry.clip_depth <= transform_stack_.back().clip_depth) - << entry.clip_depth << " <=? " << transform_stack_.back().clip_depth - << " after allocating " << total_content_depth; - entry.clip_height = transform_stack_.back().clip_height; - entry.rendering_mode = Entity::RenderingMode::kSubpassAppendSnapshotTransform; - entry.did_round_out = did_round_out; - transform_stack_.emplace_back(entry); - - // The current clip aiks clip culling can not handle image filters. - // Remove this once we've migrated to exp canvas and removed it. - if (paint.image_filter) { - transform_stack_.back().cull_rect = std::nullopt; - } - - // Start non-collapsed subpasses with a fresh clip coverage stack limited by - // the subpass coverage. This is important because image filters applied to - // save layers may transform the subpass texture after it's rendered, - // causing parent clip coverage to get misaligned with the actual area that - // the subpass will affect in the parent pass. - clip_coverage_stack_.PushSubpass(subpass_coverage, GetClipHeight()); - - if (backdrop_filter_contents) { - // Render the backdrop entity. - Entity backdrop_entity; - backdrop_entity.SetContents(std::move(backdrop_filter_contents)); - backdrop_entity.SetTransform( - Matrix::MakeTranslation(Vector3(-local_position))); - backdrop_entity.SetClipDepth(std::numeric_limits::max()); - - backdrop_entity.Render( - renderer_, - *render_passes_.back().inline_pass_context->GetRenderPass(0).pass); - } -} - -bool ExperimentalCanvas::Restore() { - FML_DCHECK(transform_stack_.size() > 0); - if (transform_stack_.size() == 1) { - return false; - } - - // This check is important to make sure we didn't exceed the depth - // that the clips were rendered at while rendering any of the - // rendering ops. It is OK for the current depth to equal the - // outgoing clip depth because that means the clipping would have - // been successful up through the last rendering op, but it cannot - // be greater. - // Also, we bump the current rendering depth to the outgoing clip - // depth so that future rendering operations are not clipped by - // any of the pixels set by the expiring clips. It is OK for the - // estimates used to determine the clip depth in save/saveLayer - // to be overly conservative, but we need to jump the depth to - // the clip depth so that the next rendering op will get a - // larger depth (it will pre-increment the current_depth_ value). - FML_DCHECK(current_depth_ <= transform_stack_.back().clip_depth) - << current_depth_ << " <=? " << transform_stack_.back().clip_depth; - current_depth_ = transform_stack_.back().clip_depth; - - if (IsSkipping()) { - transform_stack_.pop_back(); - return true; - } - - if (transform_stack_.back().rendering_mode == - Entity::RenderingMode::kSubpassAppendSnapshotTransform || - transform_stack_.back().rendering_mode == - Entity::RenderingMode::kSubpassPrependSnapshotTransform) { - auto lazy_render_pass = std::move(render_passes_.back()); - render_passes_.pop_back(); - // Force the render pass to be constructed if it never was. - lazy_render_pass.inline_pass_context->GetRenderPass(0); - - SaveLayerState save_layer_state = save_layer_state_.back(); - save_layer_state_.pop_back(); - auto global_pass_position = GetGlobalPassPosition(); - - std::shared_ptr contents = - PaintPassDelegate(save_layer_state.paint) - .CreateContentsForSubpassTarget( - lazy_render_pass.inline_pass_context->GetTexture(), - Matrix::MakeTranslation(Vector3{-global_pass_position}) * - transform_stack_.back().transform); - - lazy_render_pass.inline_pass_context->EndPass(); - - // Round the subpass texture position for pixel alignment with the parent - // pass render target. By default, we draw subpass textures with nearest - // sampling, so aligning here is important for avoiding visual nearest - // sampling errors caused by limited floating point precision when - // straddling a half pixel boundary. - Point subpass_texture_position; - if (transform_stack_.back().did_round_out) { - // Subpass coverage was rounded out, origin potentially moved "down" by - // as much as a pixel. - subpass_texture_position = - (save_layer_state.coverage.GetOrigin() - global_pass_position) - .Floor(); - } else { - // Subpass coverage was truncated. Pick the closest phyiscal pixel. - subpass_texture_position = - (save_layer_state.coverage.GetOrigin() - global_pass_position) - .Round(); - } - - Entity element_entity; - element_entity.SetClipDepth(++current_depth_); - element_entity.SetContents(std::move(contents)); - element_entity.SetBlendMode(save_layer_state.paint.blend_mode); - element_entity.SetTransform( - Matrix::MakeTranslation(Vector3(subpass_texture_position))); - - if (element_entity.GetBlendMode() > Entity::kLastPipelineBlendMode) { - if (renderer_.GetDeviceCapabilities().SupportsFramebufferFetch()) { - ApplyFramebufferBlend(element_entity); - } else { - // End the active pass and flush the buffer before rendering "advanced" - // blends. Advanced blends work by binding the current render target - // texture as an input ("destination"), blending with a second texture - // input ("source"), writing the result to an intermediate texture, and - // finally copying the data from the intermediate texture back to the - // render target texture. And so all of the commands that have written - // to the render target texture so far need to execute before it's bound - // for blending (otherwise the blend pass will end up executing before - // all the previous commands in the active pass). - auto input_texture = - FlipBackdrop(render_passes_, GetGlobalPassPosition(), - clip_coverage_stack_, renderer_); - if (!input_texture) { - return false; - } - - FilterInput::Vector inputs = { - FilterInput::Make(input_texture, - element_entity.GetTransform().Invert()), - FilterInput::Make(element_entity.GetContents())}; - auto contents = ColorFilterContents::MakeBlend( - element_entity.GetBlendMode(), inputs); - contents->SetCoverageHint(element_entity.GetCoverage()); - element_entity.SetContents(std::move(contents)); - element_entity.SetBlendMode(BlendMode::kSource); - } - } - - element_entity.Render( - renderer_, // - *render_passes_.back().inline_pass_context->GetRenderPass(0).pass // - ); - clip_coverage_stack_.PopSubpass(); - transform_stack_.pop_back(); - - // We don't need to restore clips if a saveLayer was performed, as the clip - // state is per render target, and no more rendering operations will be - // performed as the render target workloaded is completed in the restore. - return true; - } - - size_t num_clips = transform_stack_.back().num_clips; - transform_stack_.pop_back(); - - if (num_clips > 0) { - Entity entity; - entity.SetTransform( - Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * - GetCurrentTransform()); - // This path is empty because ClipRestoreContents just generates a quad that - // takes up the full render target. - auto clip_restore = std::make_shared(); - clip_restore->SetRestoreHeight(GetClipHeight()); - entity.SetContents(std::move(clip_restore)); - - auto current_clip_coverage = clip_coverage_stack_.CurrentClipCoverage(); - if (current_clip_coverage.has_value()) { - // Entity transforms are relative to the current pass position, so we need - // to check clip coverage in the same space. - current_clip_coverage = - current_clip_coverage->Shift(-GetGlobalPassPosition()); - } - - auto clip_coverage = entity.GetClipCoverage(current_clip_coverage); - if (clip_coverage.coverage.has_value()) { - clip_coverage.coverage = - clip_coverage.coverage->Shift(GetGlobalPassPosition()); - } - - EntityPassClipStack::ClipStateResult clip_state_result = - clip_coverage_stack_.ApplyClipState(clip_coverage, entity, - GetClipHeightFloor(), - GetGlobalPassPosition()); - - if (clip_state_result.clip_did_change) { - // We only need to update the pass scissor if the clip state has changed. - SetClipScissor( - clip_coverage_stack_.CurrentClipCoverage(), // - *render_passes_.back().inline_pass_context->GetRenderPass(0).pass, // - GetGlobalPassPosition() // - ); - } - - if (!clip_state_result.should_render) { - return true; - } - - entity.Render( - renderer_, - *render_passes_.back().inline_pass_context->GetRenderPass(0).pass); - } - - return true; -} - -void ExperimentalCanvas::DrawTextFrame( - const std::shared_ptr& text_frame, - Point position, - const Paint& paint) { - Entity entity; - entity.SetClipDepth(GetClipHeight()); - entity.SetBlendMode(paint.blend_mode); - - auto text_contents = std::make_shared(); - text_contents->SetTextFrame(text_frame); - text_contents->SetForceTextColor(paint.mask_blur_descriptor.has_value()); - text_contents->SetScale(GetCurrentTransform().GetMaxBasisLengthXY()); - text_contents->SetColor(paint.color); - text_contents->SetOffset(position); - text_contents->SetTextProperties(paint.color, // - paint.style == Paint::Style::kStroke, // - paint.stroke_width, // - paint.stroke_cap, // - paint.stroke_join, // - paint.stroke_miter // - ); - - entity.SetTransform(GetCurrentTransform() * - Matrix::MakeTranslation(position)); - - // TODO(bdero): This mask blur application is a hack. It will always wind up - // doing a gaussian blur that affects the color source itself - // instead of just the mask. The color filter text support - // needs to be reworked in order to interact correctly with - // mask filters. - // https://github.com/flutter/flutter/issues/133297 - entity.SetContents(paint.WithFilters(paint.WithMaskBlur( - std::move(text_contents), true, GetCurrentTransform()))); - - AddRenderEntityToCurrentPass(std::move(entity), false); -} - -void ExperimentalCanvas::AddRenderEntityToCurrentPass(Entity entity, - bool reuse_depth) { - if (IsSkipping()) { - return; - } - - entity.SetTransform( - Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * - entity.GetTransform()); - entity.SetInheritedOpacity(transform_stack_.back().distributed_opacity); - if (entity.GetBlendMode() == BlendMode::kSourceOver && - entity.GetContents()->IsOpaque(entity.GetTransform())) { - entity.SetBlendMode(BlendMode::kSource); - } - - // If the entity covers the current render target and is a solid color, then - // conditionally update the backdrop color to its solid color value blended - // with the current backdrop. - if (render_passes_.back().IsApplyingClearColor()) { - std::optional maybe_color = entity.AsBackgroundColor( - render_passes_.back().inline_pass_context->GetTexture()->GetSize()); - if (maybe_color.has_value()) { - Color color = maybe_color.value(); - RenderTarget& render_target = render_passes_.back() - .inline_pass_context->GetPassTarget() - .GetRenderTarget(); - ColorAttachment attachment = - render_target.GetColorAttachments().find(0u)->second; - // Attachment.clear color needs to be premultiplied at all times, but the - // Color::Blend function requires unpremultiplied colors. - attachment.clear_color = attachment.clear_color.Unpremultiply() - .Blend(color, entity.GetBlendMode()) - .Premultiply(); - render_target.SetColorAttachment(attachment, 0u); - return; - } - } - - if (!reuse_depth) { - ++current_depth_; - } - // We can render at a depth up to and including the depth of the currently - // active clips and we will still be clipped out, but we cannot render at - // a depth that is greater than the current clips or we will not be clipped. - FML_DCHECK(current_depth_ <= transform_stack_.back().clip_depth) - << current_depth_ << " <=? " << transform_stack_.back().clip_depth; - entity.SetClipDepth(current_depth_); - - if (entity.GetBlendMode() > Entity::kLastPipelineBlendMode) { - if (renderer_.GetDeviceCapabilities().SupportsFramebufferFetch()) { - ApplyFramebufferBlend(entity); - } else { - // End the active pass and flush the buffer before rendering "advanced" - // blends. Advanced blends work by binding the current render target - // texture as an input ("destination"), blending with a second texture - // input ("source"), writing the result to an intermediate texture, and - // finally copying the data from the intermediate texture back to the - // render target texture. And so all of the commands that have written - // to the render target texture so far need to execute before it's bound - // for blending (otherwise the blend pass will end up executing before - // all the previous commands in the active pass). - auto input_texture = FlipBackdrop(render_passes_, GetGlobalPassPosition(), - clip_coverage_stack_, renderer_); - if (!input_texture) { - return; - } - - // The coverage hint tells the rendered Contents which portion of the - // rendered output will actually be used, and so we set this to the - // current clip coverage (which is the max clip bounds). The contents may - // optionally use this hint to avoid unnecessary rendering work. - auto element_coverage_hint = entity.GetContents()->GetCoverageHint(); - entity.GetContents()->SetCoverageHint(Rect::Intersection( - element_coverage_hint, clip_coverage_stack_.CurrentClipCoverage())); - - FilterInput::Vector inputs = { - FilterInput::Make(input_texture, entity.GetTransform().Invert()), - FilterInput::Make(entity.GetContents())}; - auto contents = - ColorFilterContents::MakeBlend(entity.GetBlendMode(), inputs); - entity.SetContents(std::move(contents)); - entity.SetBlendMode(BlendMode::kSource); - } - } - - InlinePassContext::RenderPassResult result = - render_passes_.back().inline_pass_context->GetRenderPass(0); - if (!result.pass) { - // Failure to produce a render pass should be explained by specific errors - // in `InlinePassContext::GetRenderPass()`, so avoid log spam and don't - // append a validation log here. - return; - } - - entity.Render(renderer_, *result.pass); -} - -void ExperimentalCanvas::AddClipEntityToCurrentPass(Entity entity) { - if (IsSkipping()) { - return; - } - - auto transform = entity.GetTransform(); - entity.SetTransform( - Matrix::MakeTranslation(Vector3(-GetGlobalPassPosition())) * transform); - - // Ideally the clip depth would be greater than the current rendering - // depth because any rendering calls that follow this clip operation will - // pre-increment the depth and then be rendering above our clip depth, - // but that case will be caught by the CHECK in AddRenderEntity above. - // In practice we sometimes have a clip set with no rendering after it - // and in such cases the current depth will equal the clip depth. - // Eventually the DisplayList should optimize these out, but it is hard - // to know if a clip will actually be used in advance of storing it in - // the DisplayList buffer. - // See https://github.com/flutter/flutter/issues/147021 - FML_DCHECK(current_depth_ <= transform_stack_.back().clip_depth) - << current_depth_ << " <=? " << transform_stack_.back().clip_depth; - entity.SetClipDepth(transform_stack_.back().clip_depth); - - auto current_clip_coverage = clip_coverage_stack_.CurrentClipCoverage(); - if (current_clip_coverage.has_value()) { - // Entity transforms are relative to the current pass position, so we need - // to check clip coverage in the same space. - current_clip_coverage = - current_clip_coverage->Shift(-GetGlobalPassPosition()); - } - - auto clip_coverage = entity.GetClipCoverage(current_clip_coverage); - if (clip_coverage.coverage.has_value()) { - clip_coverage.coverage = - clip_coverage.coverage->Shift(GetGlobalPassPosition()); - } - - EntityPassClipStack::ClipStateResult clip_state_result = - clip_coverage_stack_.ApplyClipState( - clip_coverage, entity, GetClipHeightFloor(), GetGlobalPassPosition()); - - if (clip_state_result.clip_did_change) { - // We only need to update the pass scissor if the clip state has changed. - SetClipScissor( - clip_coverage_stack_.CurrentClipCoverage(), - *render_passes_.back().inline_pass_context->GetRenderPass(0).pass, - GetGlobalPassPosition()); - } - - if (!clip_state_result.should_render) { - return; - } - - entity.Render( - renderer_, - *render_passes_.back().inline_pass_context->GetRenderPass(0).pass); -} - -bool ExperimentalCanvas::BlitToOnscreen() { - auto command_buffer = renderer_.GetContext()->CreateCommandBuffer(); - command_buffer->SetLabel("EntityPass Root Command Buffer"); - auto offscreen_target = render_passes_.back() - .inline_pass_context->GetPassTarget() - .GetRenderTarget(); - - if (renderer_.GetContext() - ->GetCapabilities() - ->SupportsTextureToTextureBlits()) { - auto blit_pass = command_buffer->CreateBlitPass(); - blit_pass->AddCopy(offscreen_target.GetRenderTargetTexture(), - render_target_.GetRenderTargetTexture()); - if (!blit_pass->EncodeCommands( - renderer_.GetContext()->GetResourceAllocator())) { - VALIDATION_LOG << "Failed to encode root pass blit command."; - return false; - } - if (!renderer_.GetContext() - ->GetCommandQueue() - ->Submit({command_buffer}) - .ok()) { - return false; - } - } else { - auto render_pass = command_buffer->CreateRenderPass(render_target_); - render_pass->SetLabel("EntityPass Root Render Pass"); - - { - auto size_rect = Rect::MakeSize(offscreen_target.GetRenderTargetSize()); - auto contents = TextureContents::MakeRect(size_rect); - contents->SetTexture(offscreen_target.GetRenderTargetTexture()); - contents->SetSourceRect(size_rect); - contents->SetLabel("Root pass blit"); - - Entity entity; - entity.SetContents(contents); - entity.SetBlendMode(BlendMode::kSource); - - if (!entity.Render(renderer_, *render_pass)) { - VALIDATION_LOG << "Failed to render EntityPass root blit."; - return false; - } - } - - if (!render_pass->EncodeCommands()) { - VALIDATION_LOG << "Failed to encode root pass command buffer."; - return false; - } - if (!renderer_.GetContext() - ->GetCommandQueue() - ->Submit({command_buffer}) - .ok()) { - return false; - } - } - return true; -} - -void ExperimentalCanvas::EndReplay() { - FML_DCHECK(render_passes_.size() == 1u); - render_passes_.back().inline_pass_context->GetRenderPass(0); - render_passes_.back().inline_pass_context->EndPass(); - - // If requires_readback_ was true, then we rendered to an offscreen texture - // instead of to the onscreen provided in the render target. Now we need to - // draw or blit the offscreen back to the onscreen. - if (requires_readback_) { - BlitToOnscreen(); - } - - render_passes_.clear(); - renderer_.GetRenderTargetCache()->End(); - - Reset(); - Initialize(initial_cull_rect_); -} - -} // namespace impeller diff --git a/impeller/aiks/experimental_canvas.h b/impeller/aiks/experimental_canvas.h deleted file mode 100644 index 282c71b536410..0000000000000 --- a/impeller/aiks/experimental_canvas.h +++ /dev/null @@ -1,136 +0,0 @@ -// 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. - -#ifndef FLUTTER_IMPELLER_AIKS_EXPERIMENTAL_CANVAS_H_ -#define FLUTTER_IMPELLER_AIKS_EXPERIMENTAL_CANVAS_H_ - -#include -#include -#include - -#include "impeller/aiks/canvas.h" -#include "impeller/aiks/image_filter.h" -#include "impeller/aiks/paint.h" -#include "impeller/entity/entity.h" -#include "impeller/entity/entity_pass.h" -#include "impeller/entity/entity_pass_clip_stack.h" - -namespace impeller { - -struct LazyRenderingConfig { - std::unique_ptr entity_pass_target; - std::unique_ptr inline_pass_context; - - /// Whether or not the clear color texture can still be updated. - bool IsApplyingClearColor() const { return !inline_pass_context->IsActive(); } - - LazyRenderingConfig(ContentContext& renderer, - std::unique_ptr p_entity_pass_target) - : entity_pass_target(std::move(p_entity_pass_target)) { - inline_pass_context = - std::make_unique(renderer, *entity_pass_target, 0); - } - - LazyRenderingConfig(ContentContext& renderer, - std::unique_ptr entity_pass_target, - std::unique_ptr inline_pass_context) - : entity_pass_target(std::move(entity_pass_target)), - inline_pass_context(std::move(inline_pass_context)) {} -}; - -/// This Canvas attempts to translate from display lists to draw calls directly. -/// -/// It's not fully implemented yet but if successful it will be replacing the -/// aiks Canvas functionality. -/// -/// See also: -/// - go/impeller-canvas-efficiency -class ExperimentalCanvas : public Canvas { - public: - ExperimentalCanvas(ContentContext& renderer, - RenderTarget& render_target, - bool requires_readback); - - ExperimentalCanvas(ContentContext& renderer, - RenderTarget& render_target, - bool requires_readback, - Rect cull_rect); - - ExperimentalCanvas(ContentContext& renderer, - RenderTarget& render_target, - bool requires_readback, - IRect cull_rect); - - ~ExperimentalCanvas() override = default; - - void Save(uint32_t total_content_depth) override; - - void SaveLayer(const Paint& paint, - std::optional bounds, - const std::shared_ptr& backdrop_filter, - ContentBoundsPromise bounds_promise, - uint32_t total_content_depth, - bool can_distribute_opacity) override; - - bool Restore() override; - - void EndReplay(); - - void DrawTextFrame(const std::shared_ptr& text_frame, - Point position, - const Paint& paint) override; - - struct SaveLayerState { - Paint paint; - Rect coverage; - }; - - private: - /// @brief Compute the current coverage limit in screen space, or - /// std::nullopt. - std::optional ComputeCoverageLimit() const; - - // clip depth of the previous save or 0. - size_t GetClipHeightFloor() const { - if (transform_stack_.size() > 1) { - return transform_stack_[transform_stack_.size() - 2].clip_height; - } - return 0; - } - - /// @brief Whether all entites should be skipped until a corresponding - /// restore. - bool IsSkipping() { return transform_stack_.back().skipping; } - - /// @brief Skip all rendering/clipping entities until next restore. - void SkipUntilMatchingRestore(size_t total_content_depth); - - ContentContext& renderer_; - RenderTarget& render_target_; - const bool requires_readback_; - EntityPassClipStack clip_coverage_stack_; - std::vector render_passes_; - std::vector save_layer_state_; - - void SetupRenderPass(); - - void AddRenderEntityToCurrentPass(Entity entity, bool reuse_depth) override; - void AddClipEntityToCurrentPass(Entity entity) override; - bool BlitToOnscreen(); - - Point GetGlobalPassPosition() const { - if (save_layer_state_.empty()) { - return Point(0, 0); - } - return save_layer_state_.back().coverage.GetOrigin(); - } - - ExperimentalCanvas(const ExperimentalCanvas&) = delete; - - ExperimentalCanvas& operator=(const ExperimentalCanvas&) = delete; -}; - -} // namespace impeller - -#endif // FLUTTER_IMPELLER_AIKS_EXPERIMENTAL_CANVAS_H_ diff --git a/impeller/aiks/image_filter.h b/impeller/aiks/image_filter.h index 23911cad878d0..b2d2b7609791e 100644 --- a/impeller/aiks/image_filter.h +++ b/impeller/aiks/image_filter.h @@ -16,25 +16,6 @@ namespace impeller { struct Paint; -class LocalMatrixImageFilter; -class BlurImageFilter; -class DilateImageFilter; -class ErodeImageFilter; -class MatrixImageFilter; -class ComposeImageFilter; -class ColorImageFilter; - -class ImageFilterVisitor { - public: - virtual void Visit(const BlurImageFilter& filter) = 0; - virtual void Visit(const LocalMatrixImageFilter& filter) = 0; - virtual void Visit(const DilateImageFilter& filter) = 0; - virtual void Visit(const ErodeImageFilter& filter) = 0; - virtual void Visit(const MatrixImageFilter& filter) = 0; - virtual void Visit(const ComposeImageFilter& filter) = 0; - virtual void Visit(const ColorImageFilter& filter) = 0; -}; - /******************************************************************************* ******* ImageFilter ******************************************************************************/ @@ -86,8 +67,6 @@ class ImageFilter { virtual std::shared_ptr Clone() const = 0; - virtual void Visit(ImageFilterVisitor& visitor) = 0; - virtual int GetRequiredMipCount() const { return 1; } }; @@ -111,9 +90,6 @@ class BlurImageFilter : public ImageFilter { // |ImageFilter| std::shared_ptr Clone() const override; - // |ImageFilter| - void Visit(ImageFilterVisitor& visitor) override { visitor.Visit(*this); } - int GetRequiredMipCount() const override { return 4; } private: @@ -140,9 +116,6 @@ class DilateImageFilter : public ImageFilter { // |ImageFilter| std::shared_ptr Clone() const override; - // |ImageFilter| - void Visit(ImageFilterVisitor& visitor) override { visitor.Visit(*this); } - private: Radius radius_x_; Radius radius_y_; @@ -165,9 +138,6 @@ class ErodeImageFilter : public ImageFilter { // |ImageFilter| std::shared_ptr Clone() const override; - // |ImageFilter| - void Visit(ImageFilterVisitor& visitor) override { visitor.Visit(*this); } - private: Radius radius_x_; Radius radius_y_; @@ -190,9 +160,6 @@ class MatrixImageFilter : public ImageFilter { // |ImageFilter| std::shared_ptr Clone() const override; - // |ImageFilter| - void Visit(ImageFilterVisitor& visitor) override { visitor.Visit(*this); } - const Matrix& GetMatrix() const { return matrix_; } private: @@ -217,9 +184,6 @@ class ComposeImageFilter : public ImageFilter { // |ImageFilter| std::shared_ptr Clone() const override; - // |ImageFilter| - void Visit(ImageFilterVisitor& visitor) override { visitor.Visit(*this); } - private: std::shared_ptr inner_; std::shared_ptr outer_; @@ -242,9 +206,6 @@ class ColorImageFilter : public ImageFilter { // |ImageFilter| std::shared_ptr Clone() const override; - // |ImageFilter| - void Visit(ImageFilterVisitor& visitor) override { visitor.Visit(*this); } - private: std::shared_ptr color_filter_; }; @@ -267,9 +228,6 @@ class LocalMatrixImageFilter : public ImageFilter { // |ImageFilter| std::shared_ptr Clone() const override; - // |ImageFilter| - void Visit(ImageFilterVisitor& visitor) override { visitor.Visit(*this); } - private: Matrix matrix_; std::shared_ptr internal_filter_; diff --git a/impeller/aiks/paint_pass_delegate.cc b/impeller/aiks/paint_pass_delegate.cc deleted file mode 100644 index 941baffc93f6c..0000000000000 --- a/impeller/aiks/paint_pass_delegate.cc +++ /dev/null @@ -1,57 +0,0 @@ -// 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 "impeller/aiks/paint_pass_delegate.h" - -#include "impeller/core/formats.h" -#include "impeller/entity/contents/contents.h" -#include "impeller/entity/contents/texture_contents.h" -#include "impeller/entity/entity_pass.h" -#include "impeller/geometry/color.h" - -namespace impeller { - -/// PaintPassDelegate -/// ---------------------------------------------- - -PaintPassDelegate::PaintPassDelegate(Paint paint) : paint_(std::move(paint)) {} - -// |EntityPassDelgate| -PaintPassDelegate::~PaintPassDelegate() = default; - -// |EntityPassDelgate| -bool PaintPassDelegate::CanElide() { - return paint_.blend_mode == BlendMode::kDestination; -} - -// |EntityPassDelgate| -bool PaintPassDelegate::CanCollapseIntoParentPass(EntityPass* entity_pass) { - return false; -} - -// |EntityPassDelgate| -std::shared_ptr PaintPassDelegate::CreateContentsForSubpassTarget( - std::shared_ptr target, - const Matrix& effect_transform) { - auto contents = TextureContents::MakeRect(Rect::MakeSize(target->GetSize())); - contents->SetTexture(target); - contents->SetLabel("Subpass"); - contents->SetSourceRect(Rect::MakeSize(target->GetSize())); - contents->SetOpacity(paint_.color.alpha); - contents->SetDeferApplyingOpacity(true); - - return paint_.WithFiltersForSubpassTarget(std::move(contents), - effect_transform); -} - -// |EntityPassDelgate| -std::shared_ptr PaintPassDelegate::WithImageFilter( - const FilterInput::Variant& input, - const Matrix& effect_transform) const { - return paint_.WithImageFilter( - input, effect_transform, - Entity::RenderingMode::kSubpassPrependSnapshotTransform); -} - -} // namespace impeller diff --git a/impeller/aiks/paint_pass_delegate.h b/impeller/aiks/paint_pass_delegate.h deleted file mode 100644 index 72e71d61403d3..0000000000000 --- a/impeller/aiks/paint_pass_delegate.h +++ /dev/null @@ -1,48 +0,0 @@ -// 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. - -#ifndef FLUTTER_IMPELLER_AIKS_PAINT_PASS_DELEGATE_H_ -#define FLUTTER_IMPELLER_AIKS_PAINT_PASS_DELEGATE_H_ - -#include - -#include "impeller/aiks/paint.h" -#include "impeller/entity/entity_pass_delegate.h" - -namespace impeller { - -class PaintPassDelegate final : public EntityPassDelegate { - public: - explicit PaintPassDelegate(Paint paint); - - // |EntityPassDelgate| - ~PaintPassDelegate() override; - - // |EntityPassDelgate| - bool CanElide() override; - - // |EntityPassDelgate| - bool CanCollapseIntoParentPass(EntityPass* entity_pass) override; - - // |EntityPassDelgate| - std::shared_ptr CreateContentsForSubpassTarget( - std::shared_ptr target, - const Matrix& effect_transform) override; - - // |EntityPassDelgate| - std::shared_ptr WithImageFilter( - const FilterInput::Variant& input, - const Matrix& effect_transform) const override; - - private: - const Paint paint_; - - PaintPassDelegate(const PaintPassDelegate&) = delete; - - PaintPassDelegate& operator=(const PaintPassDelegate&) = delete; -}; - -} // namespace impeller - -#endif // FLUTTER_IMPELLER_AIKS_PAINT_PASS_DELEGATE_H_ diff --git a/impeller/aiks/picture.cc b/impeller/aiks/picture.cc deleted file mode 100644 index ba4793a0ad3eb..0000000000000 --- a/impeller/aiks/picture.cc +++ /dev/null @@ -1,82 +0,0 @@ -// 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 "impeller/aiks/picture.h" - -#include -#include - -#include "impeller/base/validation.h" -#include "impeller/renderer/render_target.h" - -namespace impeller { - -std::shared_ptr Picture::ToImage(AiksContext& context, - ISize size) const { - if (size.IsEmpty()) { - return nullptr; - } - return RenderToTexture(context, size); -} - -std::shared_ptr Picture::RenderToTexture( - AiksContext& context, - ISize size, - std::optional translate) const { - FML_DCHECK(!size.IsEmpty()); - - pass->IterateAllEntities([&translate](auto& entity) -> bool { - auto matrix = translate.has_value() - ? translate.value() * entity.GetTransform() - : entity.GetTransform(); - entity.SetTransform(matrix); - return true; - }); - - // This texture isn't host visible, but we might want to add host visible - // features to Image someday. - const std::shared_ptr& impeller_context = context.GetContext(); - // Do not use the render target cache as the lifecycle of this texture - // will outlive a particular frame. - RenderTargetAllocator render_target_allocator = - RenderTargetAllocator(impeller_context->GetResourceAllocator()); - RenderTarget target; - if (impeller_context->GetCapabilities()->SupportsOffscreenMSAA()) { - target = render_target_allocator.CreateOffscreenMSAA( - *impeller_context, // context - size, // size - /*mip_count=*/1, - "Picture Snapshot MSAA", // label - RenderTarget:: - kDefaultColorAttachmentConfigMSAA // color_attachment_config - ); - } else { - target = render_target_allocator.CreateOffscreen( - *impeller_context, // context - size, // size - /*mip_count=*/1, - "Picture Snapshot", // label - RenderTarget::kDefaultColorAttachmentConfig // color_attachment_config - ); - } - if (!target.IsValid()) { - VALIDATION_LOG << "Could not create valid RenderTarget."; - return nullptr; - } - - if (!context.Render(*this, target, false)) { - VALIDATION_LOG << "Could not render Picture to Texture."; - return nullptr; - } - - auto texture = target.GetRenderTargetTexture(); - if (!texture) { - VALIDATION_LOG << "RenderTarget has no target texture."; - return nullptr; - } - - return texture; -} - -} // namespace impeller diff --git a/impeller/aiks/picture.h b/impeller/aiks/picture.h deleted file mode 100644 index 2d783cffeed05..0000000000000 --- a/impeller/aiks/picture.h +++ /dev/null @@ -1,30 +0,0 @@ -// 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. - -#ifndef FLUTTER_IMPELLER_AIKS_PICTURE_H_ -#define FLUTTER_IMPELLER_AIKS_PICTURE_H_ - -#include -#include - -#include "impeller/aiks/aiks_context.h" -#include "impeller/entity/entity_pass.h" - -namespace impeller { - -struct Picture { - std::unique_ptr pass; - - std::shared_ptr ToImage(AiksContext& context, ISize size) const; - - private: - std::shared_ptr RenderToTexture( - AiksContext& context, - ISize size, - std::optional translate = std::nullopt) const; -}; - -} // namespace impeller - -#endif // FLUTTER_IMPELLER_AIKS_PICTURE_H_ diff --git a/impeller/aiks/testing/context_mock.h b/impeller/aiks/testing/context_mock.h deleted file mode 100644 index 424090295c011..0000000000000 --- a/impeller/aiks/testing/context_mock.h +++ /dev/null @@ -1,130 +0,0 @@ -// 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. - -#ifndef FLUTTER_IMPELLER_AIKS_TESTING_CONTEXT_MOCK_H_ -#define FLUTTER_IMPELLER_AIKS_TESTING_CONTEXT_MOCK_H_ - -#include -#include -#include - -#include "gmock/gmock-function-mocker.h" -#include "gmock/gmock.h" -#include "impeller/renderer/command_buffer.h" -#include "impeller/renderer/context.h" -#include "impeller/renderer/render_target.h" - -namespace impeller { -namespace testing { - -class CommandBufferMock : public CommandBuffer { - public: - explicit CommandBufferMock(std::weak_ptr context) - : CommandBuffer(std::move(context)) {} - - MOCK_METHOD(bool, IsValid, (), (const, override)); - - MOCK_METHOD(void, SetLabel, (const std::string& label), (const, override)); - - MOCK_METHOD(std::shared_ptr, - OnCreateRenderPass, - (RenderTarget render_target), - (override)); - - static std::shared_ptr ForwardOnCreateRenderPass( - CommandBuffer* command_buffer, - const RenderTarget& render_target) { - return command_buffer->OnCreateRenderPass(render_target); - } - - MOCK_METHOD(std::shared_ptr, OnCreateBlitPass, (), (override)); - static std::shared_ptr ForwardOnCreateBlitPass( - CommandBuffer* command_buffer) { - return command_buffer->OnCreateBlitPass(); - } - - MOCK_METHOD(bool, - OnSubmitCommands, - (CompletionCallback callback), - (override)); - static bool ForwardOnSubmitCommands(CommandBuffer* command_buffer, - CompletionCallback callback) { - return command_buffer->OnSubmitCommands(std::move(callback)); - } - - MOCK_METHOD(void, OnWaitUntilScheduled, (), (override)); - static void ForwardOnWaitUntilScheduled(CommandBuffer* command_buffer) { - return command_buffer->OnWaitUntilScheduled(); - } - - MOCK_METHOD(std::shared_ptr, - OnCreateComputePass, - (), - (override)); - static std::shared_ptr ForwardOnCreateComputePass( - CommandBuffer* command_buffer) { - return command_buffer->OnCreateComputePass(); - } -}; - -class ContextMock : public Context { - public: - MOCK_METHOD(std::string, DescribeGpuModel, (), (const, override)); - - MOCK_METHOD(Context::BackendType, GetBackendType, (), (const, override)); - - MOCK_METHOD(bool, IsValid, (), (const, override)); - - MOCK_METHOD(const std::shared_ptr&, - GetCapabilities, - (), - (const, override)); - - MOCK_METHOD(bool, - UpdateOffscreenLayerPixelFormat, - (PixelFormat format), - (override)); - - MOCK_METHOD(std::shared_ptr, - GetResourceAllocator, - (), - (const, override)); - - MOCK_METHOD(std::shared_ptr, - GetShaderLibrary, - (), - (const, override)); - - MOCK_METHOD(std::shared_ptr, - GetSamplerLibrary, - (), - (const, override)); - - MOCK_METHOD(std::shared_ptr, - GetPipelineLibrary, - (), - (const, override)); - - MOCK_METHOD(std::shared_ptr, - CreateCommandBuffer, - (), - (const, override)); - - MOCK_METHOD(std::shared_ptr, - GetCommandQueue, - (), - (const override)); - - MOCK_METHOD(void, Shutdown, (), (override)); - - MOCK_METHOD(void, - InitializeCommonlyUsedShadersIfNeeded, - (), - (const, override)); -}; - -} // namespace testing -} // namespace impeller - -#endif // FLUTTER_IMPELLER_AIKS_TESTING_CONTEXT_MOCK_H_ diff --git a/impeller/aiks/testing/context_spy.cc b/impeller/aiks/testing/context_spy.cc deleted file mode 100644 index bb3d528eea144..0000000000000 --- a/impeller/aiks/testing/context_spy.cc +++ /dev/null @@ -1,130 +0,0 @@ -// 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 -#include "impeller/renderer/command_buffer.h" -#include "impeller/renderer/command_queue.h" - -#include "impeller/aiks/testing/context_spy.h" - -namespace impeller { -namespace testing { - -fml::Status NoopCommandQueue::Submit( - const std::vector>& buffers, - const CompletionCallback& completion_callback) { - if (completion_callback) { - completion_callback(CommandBuffer::Status::kCompleted); - } - return fml::Status(); -} - -std::shared_ptr ContextSpy::Make() { - return std::shared_ptr(new ContextSpy()); -} - -std::shared_ptr ContextSpy::MakeContext( - const std::shared_ptr& real_context) { - std::shared_ptr mock_context = - std::make_shared<::testing::NiceMock>(); - std::shared_ptr shared_this = shared_from_this(); - - ON_CALL(*mock_context, IsValid).WillByDefault([real_context]() { - return real_context->IsValid(); - }); - - ON_CALL(*mock_context, GetBackendType) - .WillByDefault([real_context]() -> Context::BackendType { - return real_context->GetBackendType(); - }); - - ON_CALL(*mock_context, GetCapabilities) - .WillByDefault( - [real_context]() -> const std::shared_ptr& { - return real_context->GetCapabilities(); - }); - - ON_CALL(*mock_context, UpdateOffscreenLayerPixelFormat) - .WillByDefault([real_context](PixelFormat format) { - return real_context->UpdateOffscreenLayerPixelFormat(format); - }); - - ON_CALL(*mock_context, GetResourceAllocator).WillByDefault([real_context]() { - return real_context->GetResourceAllocator(); - }); - - ON_CALL(*mock_context, GetShaderLibrary).WillByDefault([real_context]() { - return real_context->GetShaderLibrary(); - }); - - ON_CALL(*mock_context, GetSamplerLibrary).WillByDefault([real_context]() { - return real_context->GetSamplerLibrary(); - }); - - ON_CALL(*mock_context, GetPipelineLibrary).WillByDefault([real_context]() { - return real_context->GetPipelineLibrary(); - }); - - ON_CALL(*mock_context, GetCommandQueue).WillByDefault([shared_this]() { - return shared_this->command_queue_; - }); - - ON_CALL(*mock_context, CreateCommandBuffer) - .WillByDefault([real_context, shared_this]() { - auto real_buffer = real_context->CreateCommandBuffer(); - auto spy = std::make_shared<::testing::NiceMock>( - real_context); - - ON_CALL(*spy, IsValid).WillByDefault([real_buffer]() { - return real_buffer->IsValid(); - }); - - ON_CALL(*spy, SetLabel) - .WillByDefault([real_buffer](const std::string& label) { - return real_buffer->SetLabel(label); - }); - - ON_CALL(*spy, OnCreateRenderPass) - .WillByDefault([real_buffer, shared_this, - real_context](const RenderTarget& render_target) { - std::shared_ptr result = - CommandBufferMock::ForwardOnCreateRenderPass( - real_buffer.get(), render_target); - std::shared_ptr recorder = - std::make_shared(result, real_context, - render_target); - shared_this->render_passes_.push_back(recorder); - return recorder; - }); - - ON_CALL(*spy, OnCreateBlitPass).WillByDefault([real_buffer]() { - return CommandBufferMock::ForwardOnCreateBlitPass(real_buffer.get()); - }); - - ON_CALL(*spy, OnSubmitCommands) - .WillByDefault( - [real_buffer](CommandBuffer::CompletionCallback callback) { - return CommandBufferMock::ForwardOnSubmitCommands( - real_buffer.get(), std::move(callback)); - }); - - ON_CALL(*spy, OnWaitUntilScheduled).WillByDefault([real_buffer]() { - return CommandBufferMock::ForwardOnWaitUntilScheduled( - real_buffer.get()); - }); - - ON_CALL(*spy, OnCreateComputePass).WillByDefault([real_buffer]() { - return CommandBufferMock::ForwardOnCreateComputePass( - real_buffer.get()); - }); - - return spy; - }); - - return mock_context; -} - -} // namespace testing - -} // namespace impeller diff --git a/impeller/aiks/testing/context_spy.h b/impeller/aiks/testing/context_spy.h deleted file mode 100644 index c4fb6c84fa947..0000000000000 --- a/impeller/aiks/testing/context_spy.h +++ /dev/null @@ -1,45 +0,0 @@ -// 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. - -#ifndef FLUTTER_IMPELLER_AIKS_TESTING_CONTEXT_SPY_H_ -#define FLUTTER_IMPELLER_AIKS_TESTING_CONTEXT_SPY_H_ - -#include - -#include "impeller/aiks/testing/context_mock.h" -#include "impeller/entity/contents/test/recording_render_pass.h" -#include "impeller/renderer/command_queue.h" - -namespace impeller { -namespace testing { - -class NoopCommandQueue : public CommandQueue { - public: - fml::Status Submit( - const std::vector>& buffers, - const CompletionCallback& completion_callback = {}) override; -}; - -/// Forwards calls to a real Context but can store information about how -/// the Context was used. -class ContextSpy : public std::enable_shared_from_this { - public: - static std::shared_ptr Make(); - - std::shared_ptr MakeContext( - const std::shared_ptr& real_context); - - std::vector> render_passes_; - std::shared_ptr command_queue_ = - std::make_shared(); - - private: - ContextSpy() = default; -}; - -} // namespace testing - -} // namespace impeller - -#endif // FLUTTER_IMPELLER_AIKS_TESTING_CONTEXT_SPY_H_ diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index 2e1870da19376..1a544011aa4bb 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -12,7 +12,7 @@ #include #include "flutter/fml/logging.h" -#include "flutter/fml/trace_event.h" +#include "impeller/aiks/aiks_context.h" #include "impeller/aiks/color_filter.h" #include "impeller/core/formats.h" #include "impeller/display_list/dl_vertices_geometry.h" @@ -32,11 +32,11 @@ namespace impeller { -#if EXPERIMENTAL_CANVAS && !defined(NDEBUG) +#if !defined(NDEBUG) #define USE_DEPTH_WATCHER true -#else // EXPERIMENTAL_CANVAS && !defined(NDEBUG) +#else #define USE_DEPTH_WATCHER false -#endif // EXPERIMENTAL_CANVAS && !defined(NDEBUG) +#endif // !defined(NDEBUG) #if USE_DEPTH_WATCHER @@ -1080,17 +1080,6 @@ void DlDispatcherBase::drawVertices( const std::shared_ptr& vertices, flutter::DlBlendMode dl_mode) {} -// |flutter::DlOpReceiver| -void ExperimentalDlDispatcher::drawVertices( - const std::shared_ptr& vertices, - flutter::DlBlendMode dl_mode) { - AUTO_DEPTH_WATCHER(1u); - - GetCanvas().DrawVertices( - std::make_shared(vertices, renderer_), - ToBlendMode(dl_mode), paint_); -} - // |flutter::DlOpReceiver| void DlDispatcherBase::drawImage(const sk_sp image, const DlPoint& point, @@ -1165,7 +1154,6 @@ void DlDispatcherBase::drawAtlas(const sk_sp atlas, const DlRect* cull_rect, bool render_with_attributes) { AUTO_DEPTH_WATCHER(1u); - GetCanvas().DrawAtlas( atlas->impeller_texture(), skia_conversions::ToRSXForms(xform, count), skia_conversions::ToRects(tex, count), ToColors(colors, count), @@ -1217,14 +1205,16 @@ void DlDispatcherBase::drawDisplayList( // the ops based on a rectangle expressed in its "destination bounds" // so we need the canvas to transform those into the current local // coordinate space into which the DisplayList will be rendered. - auto cull_bounds = GetCanvas().GetCurrentLocalCullingBounds(); - if (cull_bounds.has_value()) { - Rect cull_rect = cull_bounds.value(); + auto global_culling_bounds = GetCanvas().GetLocalCoverageLimit(); + if (global_culling_bounds.has_value()) { + Rect cull_rect = global_culling_bounds->TransformBounds( + GetCanvas().GetCurrentTransform().Invert()); display_list->Dispatch( *this, SkRect::MakeLTRB(cull_rect.GetLeft(), cull_rect.GetTop(), cull_rect.GetRight(), cull_rect.GetBottom())); } else { - display_list->Dispatch(*this); + // If the culling bounds are empty, this display list can be skipped + // entirely. } } else { display_list->Dispatch(*this); @@ -1322,25 +1312,8 @@ void DlDispatcherBase::drawShadow(const DlPath& path, GetCanvas().Restore(); } -Picture DlDispatcherBase::EndRecordingAsPicture() { - TRACE_EVENT0("impeller", "DisplayListDispatcher::EndRecordingAsPicture"); - return GetCanvas().EndRecordingAsPicture(); -} - /// Subclasses -#if !EXPERIMENTAL_CANVAS -DlDispatcher::DlDispatcher() = default; - -DlDispatcher::DlDispatcher(IRect cull_rect) : canvas_(cull_rect) {} - -DlDispatcher::DlDispatcher(Rect cull_rect) : canvas_(cull_rect) {} - -Canvas& DlDispatcher::GetCanvas() { - return canvas_; -} -#endif // !EXPERIMENTAL_CANVAS - static bool RequiresReadbackForBlends( const ContentContext& renderer, flutter::DlBlendMode max_root_blend_mode) { @@ -1348,23 +1321,32 @@ static bool RequiresReadbackForBlends( ToBlendMode(max_root_blend_mode) > Entity::kLastPipelineBlendMode; } -ExperimentalDlDispatcher::ExperimentalDlDispatcher( - ContentContext& renderer, - RenderTarget& render_target, - bool has_root_backdrop_filter, - flutter::DlBlendMode max_root_blend_mode, - IRect cull_rect) - : renderer_(renderer), - canvas_(renderer, +CanvasDlDispatcher::CanvasDlDispatcher(ContentContext& renderer, + RenderTarget& render_target, + bool has_root_backdrop_filter, + flutter::DlBlendMode max_root_blend_mode, + IRect cull_rect) + : canvas_(renderer, render_target, has_root_backdrop_filter || RequiresReadbackForBlends(renderer, max_root_blend_mode), - cull_rect) {} + cull_rect), + renderer_(renderer) {} -Canvas& ExperimentalDlDispatcher::GetCanvas() { +Canvas& CanvasDlDispatcher::GetCanvas() { return canvas_; } +void CanvasDlDispatcher::drawVertices( + const std::shared_ptr& vertices, + flutter::DlBlendMode dl_mode) { + AUTO_DEPTH_WATCHER(1u); + + GetCanvas().DrawVertices( + std::make_shared(vertices, renderer_), + ToBlendMode(dl_mode), paint_); +} + //// Text Frame Dispatcher TextFrameDispatcher::TextFrameDispatcher(const ContentContext& renderer, @@ -1582,7 +1564,8 @@ void TextFrameDispatcher::setImageFilter(const flutter::DlImageFilter* filter) { std::shared_ptr DisplayListToTexture( const sk_sp& display_list, ISize size, - AiksContext& context) { + AiksContext& context, + bool reset_host_buffer) { // Do not use the render target cache as the lifecycle of this texture // will outlive a particular frame. impeller::RenderTargetAllocator render_target_allocator = @@ -1613,17 +1596,49 @@ std::shared_ptr DisplayListToTexture( impeller::TextFrameDispatcher collector( context.GetContentContext(), impeller::Matrix(), Rect::MakeSize(size)); display_list->Dispatch(collector, sk_cull_rect); - impeller::ExperimentalDlDispatcher impeller_dispatcher( - context.GetContentContext(), target, - display_list->root_has_backdrop_filter(), - display_list->max_root_blend_mode(), impeller::IRect::MakeSize(size)); + impeller::CanvasDlDispatcher impeller_dispatcher( + context.GetContentContext(), // + target, // + display_list->root_has_backdrop_filter(), // + display_list->max_root_blend_mode(), // + impeller::IRect::MakeSize(size) // + ); display_list->Dispatch(impeller_dispatcher, sk_cull_rect); impeller_dispatcher.FinishRecording(); - context.GetContentContext().GetTransientsBuffer().Reset(); + if (reset_host_buffer) { + context.GetContentContext().GetTransientsBuffer().Reset(); + } context.GetContentContext().GetLazyGlyphAtlas()->ResetTextFrames(); return target.GetRenderTargetTexture(); } +bool RenderToOnscreen(ContentContext& context, + RenderTarget render_target, + const sk_sp& display_list, + SkIRect cull_rect, + bool reset_host_buffer) { + Rect ip_cull_rect = Rect::MakeLTRB(cull_rect.left(), cull_rect.top(), + cull_rect.right(), cull_rect.bottom()); + TextFrameDispatcher collector(context, impeller::Matrix(), ip_cull_rect); + display_list->Dispatch(collector, cull_rect); + + impeller::CanvasDlDispatcher impeller_dispatcher( + context, // + render_target, // + display_list->root_has_backdrop_filter(), // + display_list->max_root_blend_mode(), // + IRect::RoundOut(ip_cull_rect) // + ); + display_list->Dispatch(impeller_dispatcher, cull_rect); + impeller_dispatcher.FinishRecording(); + if (reset_host_buffer) { + context.GetTransientsBuffer().Reset(); + } + context.GetLazyGlyphAtlas()->ResetTextFrames(); + + return true; +} + } // namespace impeller diff --git a/impeller/display_list/dl_dispatcher.h b/impeller/display_list/dl_dispatcher.h index 61f8efd5a9dca..204b938c074cf 100644 --- a/impeller/display_list/dl_dispatcher.h +++ b/impeller/display_list/dl_dispatcher.h @@ -10,11 +10,10 @@ #include "flutter/display_list/geometry/dl_path.h" #include "flutter/display_list/utils/dl_receiver_utils.h" #include "fml/logging.h" +#include "impeller/aiks/aiks_context.h" #include "impeller/aiks/canvas.h" -#include "impeller/aiks/experimental_canvas.h" #include "impeller/aiks/paint.h" #include "impeller/entity/contents/content_context.h" -#include "impeller/geometry/color.h" #include "impeller/geometry/rect.h" namespace impeller { @@ -27,8 +26,6 @@ using DlPath = flutter::DlPath; class DlDispatcherBase : public flutter::DlOpReceiver { public: - Picture EndRecordingAsPicture(); - // |flutter::DlOpReceiver| void setAntiAlias(bool aa) override; @@ -246,54 +243,15 @@ class DlDispatcherBase : public flutter::DlOpReceiver { const Paint& paint); }; -#if !EXPERIMENTAL_CANVAS -class DlDispatcher : public DlDispatcherBase { +class CanvasDlDispatcher : public DlDispatcherBase { public: - DlDispatcher(); - - explicit DlDispatcher(IRect cull_rect); + CanvasDlDispatcher(ContentContext& renderer, + RenderTarget& render_target, + bool has_root_backdrop_filter, + flutter::DlBlendMode max_root_blend_mode, + IRect cull_rect); - explicit DlDispatcher(Rect cull_rect); - - ~DlDispatcher() = default; - - // |flutter::DlOpReceiver| - void save() override { - // This dispatcher is used from test cases that might not supply - // a content_depth parameter. Since this dispatcher doesn't use - // the value, we just pass through a 0. - DlDispatcherBase::save(0u); - } - using DlDispatcherBase::save; - - // |flutter::DlOpReceiver| - void saveLayer(const DlRect& bounds, - const flutter::SaveLayerOptions options, - const flutter::DlImageFilter* backdrop) override { - // This dispatcher is used from test cases that might not supply - // a content_depth parameter. Since this dispatcher doesn't use - // the value, we just pass through a 0. - DlDispatcherBase::saveLayer(bounds, options, 0u, - flutter::DlBlendMode::kLastMode, backdrop); - } - using DlDispatcherBase::saveLayer; - - private: - Canvas canvas_; - - Canvas& GetCanvas() override; -}; -#endif // !EXPERIMENTAL_CANVAS - -class ExperimentalDlDispatcher : public DlDispatcherBase { - public: - ExperimentalDlDispatcher(ContentContext& renderer, - RenderTarget& render_target, - bool has_root_backdrop_filter, - flutter::DlBlendMode max_root_blend_mode, - IRect cull_rect); - - ~ExperimentalDlDispatcher() = default; + ~CanvasDlDispatcher() = default; // |flutter::DlOpReceiver| void save() override { @@ -320,8 +278,8 @@ class ExperimentalDlDispatcher : public DlDispatcherBase { flutter::DlBlendMode dl_mode) override; private: + Canvas canvas_; const ContentContext& renderer_; - ExperimentalCanvas canvas_; Canvas& GetCanvas() override; }; @@ -411,7 +369,14 @@ class TextFrameDispatcher : public flutter::IgnoreAttributeDispatchHelper, std::shared_ptr DisplayListToTexture( const sk_sp& display_list, ISize size, - AiksContext& context); + AiksContext& context, + bool reset_host_buffer = true); + +/// Render the provided display list to the render target. +bool RenderToOnscreen(ContentContext& context, RenderTarget render_target, + const sk_sp& display_list, + SkIRect cull_rect, + bool reset_host_buffer); } // namespace impeller diff --git a/impeller/display_list/dl_playground.cc b/impeller/display_list/dl_playground.cc index ee3ee438028ce..79f741fc72e6c 100644 --- a/impeller/display_list/dl_playground.cc +++ b/impeller/display_list/dl_playground.cc @@ -47,15 +47,11 @@ bool DlPlayground::OpenPlaygroundHere(DisplayListPlaygroundCallback callback) { } auto list = callback(); - -#if EXPERIMENTAL_CANVAS - TextFrameDispatcher collector(context.GetContentContext(), // - Matrix(), // - Rect::MakeMaximum() // - ); + TextFrameDispatcher collector(context.GetContentContext(), Matrix(), + Rect::MakeMaximum()); list->Dispatch(collector); - ExperimentalDlDispatcher impeller_dispatcher( + CanvasDlDispatcher impeller_dispatcher( context.GetContentContext(), render_target, list->root_has_backdrop_filter(), list->max_root_blend_mode(), IRect::MakeMaximum()); @@ -64,13 +60,6 @@ bool DlPlayground::OpenPlaygroundHere(DisplayListPlaygroundCallback callback) { context.GetContentContext().GetTransientsBuffer().Reset(); context.GetContentContext().GetLazyGlyphAtlas()->ResetTextFrames(); return true; -#else - DlDispatcher dispatcher; - list->Dispatch(dispatcher); - auto picture = dispatcher.EndRecordingAsPicture(); - - return context.Render(picture, render_target, true); -#endif }); } diff --git a/impeller/display_list/dl_unittests.cc b/impeller/display_list/dl_unittests.cc index 36b0cb4aa4df6..e90a2774b2eac 100644 --- a/impeller/display_list/dl_unittests.cc +++ b/impeller/display_list/dl_unittests.cc @@ -1480,21 +1480,6 @@ TEST_P(DisplayListTest, DrawVerticesBlendModes) { ASSERT_TRUE(OpenPlaygroundHere(callback)); } -template -static std::optional GetCoverageOfFirstEntity(const Picture& picture) { - std::optional coverage; - picture.pass->IterateAllEntities([&coverage](Entity& entity) { - if (std::static_pointer_cast(entity.GetContents())) { - auto contents = std::static_pointer_cast(entity.GetContents()); - Entity entity; - coverage = contents->GetCoverage(entity); - return false; - } - return true; - }); - return coverage; -} - TEST_P(DisplayListTest, DrawPaintIgnoresMaskFilter) { flutter::DisplayListBuilder builder; builder.DrawPaint(flutter::DlPaint().setColor(flutter::DlColor::kWhite())); diff --git a/impeller/display_list/skia_conversions.h b/impeller/display_list/skia_conversions.h index fa0c9f6917b1a..8aa3604bad7f5 100644 --- a/impeller/display_list/skia_conversions.h +++ b/impeller/display_list/skia_conversions.h @@ -13,7 +13,6 @@ #include "impeller/geometry/path_builder.h" #include "impeller/geometry/point.h" #include "impeller/geometry/rect.h" -#include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkColorType.h" #include "third_party/skia/include/core/SkPath.h" #include "third_party/skia/include/core/SkPoint.h" diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index 1b609e57d1601..9f966ec4c6a91 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -170,12 +170,8 @@ impeller_component("entity") { "draw_order_resolver.h", "entity.cc", "entity.h", - "entity_pass.cc", - "entity_pass.h", "entity_pass_clip_stack.cc", "entity_pass_clip_stack.h", - "entity_pass_delegate.cc", - "entity_pass_delegate.h", "entity_pass_target.cc", "entity_pass_target.h", "geometry/circle_geometry.cc", diff --git a/impeller/entity/contents/clip_contents.cc b/impeller/entity/contents/clip_contents.cc index 80beb53c8ca20..1eb51b8ee299c 100644 --- a/impeller/entity/contents/clip_contents.cc +++ b/impeller/entity/contents/clip_contents.cc @@ -76,15 +76,6 @@ Contents::ClipCoverage ClipContents::GetClipCoverage( FML_UNREACHABLE(); } -bool ClipContents::ShouldRender(const Entity& entity, - const std::optional clip_coverage) const { - return true; -} - -bool ClipContents::CanInheritOpacity(const Entity& entity) const { - return true; -} - void ClipContents::SetInheritedOpacity(Scalar opacity) {} bool ClipContents::Render(const ContentContext& renderer, @@ -204,16 +195,6 @@ Contents::ClipCoverage ClipRestoreContents::GetClipCoverage( return {.type = ClipCoverage::Type::kRestore, .coverage = std::nullopt}; } -bool ClipRestoreContents::ShouldRender( - const Entity& entity, - const std::optional clip_coverage) const { - return true; -} - -bool ClipRestoreContents::CanInheritOpacity(const Entity& entity) const { - return true; -} - void ClipRestoreContents::SetInheritedOpacity(Scalar opacity) {} bool ClipRestoreContents::Render(const ContentContext& renderer, diff --git a/impeller/entity/contents/clip_contents.h b/impeller/entity/contents/clip_contents.h index 5481298a2a8b2..eae784ab251c5 100644 --- a/impeller/entity/contents/clip_contents.h +++ b/impeller/entity/contents/clip_contents.h @@ -33,16 +33,10 @@ class ClipContents final : public Contents { const Entity& entity, const std::optional& current_clip_coverage) const override; - // |Contents| - bool ShouldRender(const Entity& entity, - const std::optional clip_coverage) const override; - // |Contents| bool Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const override; - // |Contents| - bool CanInheritOpacity(const Entity& entity) const override; // |Contents| void SetInheritedOpacity(Scalar opacity) override; @@ -80,18 +74,11 @@ class ClipRestoreContents final : public Contents { const Entity& entity, const std::optional& current_clip_coverage) const override; - // |Contents| - bool ShouldRender(const Entity& entity, - const std::optional clip_coverage) const override; - // |Contents| bool Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const override; - // |Contents| - bool CanInheritOpacity(const Entity& entity) const override; - // |Contents| void SetInheritedOpacity(Scalar opacity) override; diff --git a/impeller/entity/contents/color_source_contents.cc b/impeller/entity/contents/color_source_contents.cc index 9048af516d9dc..009f3146076be 100644 --- a/impeller/entity/contents/color_source_contents.cc +++ b/impeller/entity/contents/color_source_contents.cc @@ -46,10 +46,6 @@ std::optional ColorSourceContents::GetCoverage( return geometry_->GetCoverage(entity.GetTransform()); }; -bool ColorSourceContents::CanInheritOpacity(const Entity& entity) const { - return true; -} - void ColorSourceContents::SetInheritedOpacity(Scalar opacity) { inherited_opacity_ = opacity; } diff --git a/impeller/entity/contents/color_source_contents.h b/impeller/entity/contents/color_source_contents.h index 3b548f146f0e4..1bade2c369b48 100644 --- a/impeller/entity/contents/color_source_contents.h +++ b/impeller/entity/contents/color_source_contents.h @@ -89,9 +89,6 @@ class ColorSourceContents : public Contents { /// /// @note If set, the output of this method factors factors in the inherited /// opacity of this `Contents`. - /// - /// @see `Contents::CanInheritOpacity` - /// Scalar GetOpacityFactor() const; virtual bool IsSolidColor() const; @@ -99,9 +96,6 @@ class ColorSourceContents : public Contents { // |Contents| std::optional GetCoverage(const Entity& entity) const override; - // |Contents| - bool CanInheritOpacity(const Entity& entity) const override; - // |Contents| void SetInheritedOpacity(Scalar opacity) override; diff --git a/impeller/entity/contents/contents.cc b/impeller/entity/contents/contents.cc index 4ab73d78d6e3b..df79c42307bef 100644 --- a/impeller/entity/contents/contents.cc +++ b/impeller/entity/contents/contents.cc @@ -128,10 +128,6 @@ std::optional Contents::RenderToSnapshot( return snapshot; } -bool Contents::CanInheritOpacity(const Entity& entity) const { - return false; -} - void Contents::SetInheritedOpacity(Scalar opacity) { VALIDATION_LOG << "Contents::SetInheritedOpacity should never be called when " "Contents::CanAcceptOpacity returns false."; @@ -151,21 +147,6 @@ bool Contents::ApplyColorFilter( return false; } -bool Contents::ShouldRender(const Entity& entity, - const std::optional clip_coverage) const { - if (!clip_coverage.has_value()) { - return false; - } - auto coverage = GetCoverage(entity); - if (!coverage.has_value()) { - return false; - } - if (coverage == Rect::MakeMaximum()) { - return true; - } - return clip_coverage->IntersectsWithRect(coverage.value()); -} - void Contents::SetCoverageHint(std::optional coverage_hint) { coverage_hint_ = coverage_hint; } diff --git a/impeller/entity/contents/contents.h b/impeller/entity/contents/contents.h index 6ee63e1bc7217..0de4926b31d2c 100644 --- a/impeller/entity/contents/contents.h +++ b/impeller/entity/contents/contents.h @@ -64,12 +64,6 @@ class Contents { virtual ~Contents(); - /// @brief Add any text data to the specified lazy atlas. The scale parameter - /// must be used again later when drawing the text. - virtual void PopulateGlyphAtlas( - const std::shared_ptr& lazy_glyph_atlas, - Scalar scale) {} - virtual bool Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const = 0; @@ -134,9 +128,6 @@ class Contents { int32_t mip_count = 1, const std::string& label = "Snapshot") const; - virtual bool ShouldRender(const Entity& entity, - const std::optional clip_coverage) const; - //---------------------------------------------------------------------------- /// @brief Return the color source's intrinsic size, if available. /// @@ -148,18 +139,6 @@ class Contents { void SetColorSourceSize(Size size); - //---------------------------------------------------------------------------- - /// @brief Whether or not this contents can accept the opacity peephole - /// optimization. - /// - /// By default all contents return false. Contents are responsible - /// for determining whether or not their own geometries intersect in - /// a way that makes accepting opacity impossible. It is always safe - /// to return false, especially if computing overlap would be - /// computationally expensive. - /// - virtual bool CanInheritOpacity(const Entity& entity) const; - //---------------------------------------------------------------------------- /// @brief Inherit the provided opacity. /// diff --git a/impeller/entity/contents/filters/filter_contents.cc b/impeller/entity/contents/filters/filter_contents.cc index 46cfab8c874d9..36af904dd14b9 100644 --- a/impeller/entity/contents/filters/filter_contents.cc +++ b/impeller/entity/contents/filters/filter_contents.cc @@ -166,14 +166,6 @@ std::optional FilterContents::GetCoverage(const Entity& entity) const { return GetLocalCoverage(entity_with_local_transform); } -void FilterContents::PopulateGlyphAtlas( - const std::shared_ptr& lazy_glyph_atlas, - Scalar scale) { - for (auto& input : inputs_) { - input->PopulateGlyphAtlas(lazy_glyph_atlas, scale); - } -} - std::optional FilterContents::GetFilterCoverage( const FilterInput::Vector& inputs, const Entity& entity, diff --git a/impeller/entity/contents/filters/filter_contents.h b/impeller/entity/contents/filters/filter_contents.h index 27ecd6c520ed2..6ffdf13e2a661 100644 --- a/impeller/entity/contents/filters/filter_contents.h +++ b/impeller/entity/contents/filters/filter_contents.h @@ -110,11 +110,6 @@ class FilterContents : public Contents { // |Contents| std::optional GetCoverage(const Entity& entity) const override; - // |Contents| - void PopulateGlyphAtlas( - const std::shared_ptr& lazy_glyph_atlas, - Scalar scale) override; - // |Contents| std::optional RenderToSnapshot( const ContentContext& renderer, diff --git a/impeller/entity/contents/filters/inputs/contents_filter_input.cc b/impeller/entity/contents/filters/inputs/contents_filter_input.cc index f6e95a1af23ec..c8755112e61c5 100644 --- a/impeller/entity/contents/filters/inputs/contents_filter_input.cc +++ b/impeller/entity/contents/filters/inputs/contents_filter_input.cc @@ -48,10 +48,4 @@ std::optional ContentsFilterInput::GetCoverage( return contents_->GetCoverage(entity); } -void ContentsFilterInput::PopulateGlyphAtlas( - const std::shared_ptr& lazy_glyph_atlas, - Scalar scale) { - contents_->PopulateGlyphAtlas(lazy_glyph_atlas, scale); -} - } // namespace impeller diff --git a/impeller/entity/contents/filters/inputs/contents_filter_input.h b/impeller/entity/contents/filters/inputs/contents_filter_input.h index 374e10123efc4..af9eb2acd03e5 100644 --- a/impeller/entity/contents/filters/inputs/contents_filter_input.h +++ b/impeller/entity/contents/filters/inputs/contents_filter_input.h @@ -26,11 +26,6 @@ class ContentsFilterInput final : public FilterInput { // |FilterInput| std::optional GetCoverage(const Entity& entity) const override; - // |FilterInput| - void PopulateGlyphAtlas( - const std::shared_ptr& lazy_glyph_atlas, - Scalar scale) override; - private: ContentsFilterInput(std::shared_ptr contents, bool msaa_enabled); diff --git a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc index 2dabc08e61c06..00135b6c8cd52 100644 --- a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc +++ b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc @@ -60,12 +60,6 @@ Matrix FilterContentsFilterInput::GetTransform(const Entity& entity) const { return filter_->GetTransform(entity.GetTransform()); } -void FilterContentsFilterInput::PopulateGlyphAtlas( - const std::shared_ptr& lazy_glyph_atlas, - Scalar scale) { - filter_->PopulateGlyphAtlas(lazy_glyph_atlas, scale); -} - bool FilterContentsFilterInput::IsTranslationOnly() const { return filter_->IsTranslationOnly(); } diff --git a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h index 8d5f26693be9b..76826475061cb 100644 --- a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h +++ b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h @@ -37,11 +37,6 @@ class FilterContentsFilterInput final : public FilterInput { // |FilterInput| Matrix GetTransform(const Entity& entity) const override; - // |FilterInput| - void PopulateGlyphAtlas( - const std::shared_ptr& lazy_glyph_atlas, - Scalar scale) override; - // |FilterInput| bool IsTranslationOnly() const override; diff --git a/impeller/entity/contents/filters/inputs/filter_input.cc b/impeller/entity/contents/filters/inputs/filter_input.cc index b1459b7e200ee..d0280009ac17e 100644 --- a/impeller/entity/contents/filters/inputs/filter_input.cc +++ b/impeller/entity/contents/filters/inputs/filter_input.cc @@ -76,10 +76,6 @@ Matrix FilterInput::GetTransform(const Entity& entity) const { return entity.GetTransform() * GetLocalTransform(entity); } -void FilterInput::PopulateGlyphAtlas( - const std::shared_ptr& lazy_glyph_atlas, - Scalar scale) {} - FilterInput::~FilterInput() = default; bool FilterInput::IsTranslationOnly() const { diff --git a/impeller/entity/contents/filters/inputs/filter_input.h b/impeller/entity/contents/filters/inputs/filter_input.h index 070b05c688daf..d31927437afbb 100644 --- a/impeller/entity/contents/filters/inputs/filter_input.h +++ b/impeller/entity/contents/filters/inputs/filter_input.h @@ -69,11 +69,6 @@ class FilterInput { /// calling `entity.GetTransform() * GetLocalTransform()`. virtual Matrix GetTransform(const Entity& entity) const; - /// @see `Contents::PopulateGlyphAtlas` - virtual void PopulateGlyphAtlas( - const std::shared_ptr& lazy_glyph_atlas, - Scalar scale); - /// @see `FilterContents::HasBasisTransforms` virtual bool IsTranslationOnly() const; diff --git a/impeller/entity/contents/filters/inputs/placeholder_filter_input.cc b/impeller/entity/contents/filters/inputs/placeholder_filter_input.cc index 5341b287f0756..d4daf6d95176d 100644 --- a/impeller/entity/contents/filters/inputs/placeholder_filter_input.cc +++ b/impeller/entity/contents/filters/inputs/placeholder_filter_input.cc @@ -34,8 +34,4 @@ std::optional PlaceholderFilterInput::GetCoverage( return coverage_rect_; } -void PlaceholderFilterInput::PopulateGlyphAtlas( - const std::shared_ptr& lazy_glyph_atlas, - Scalar scale) {} - } // namespace impeller diff --git a/impeller/entity/contents/filters/inputs/placeholder_filter_input.h b/impeller/entity/contents/filters/inputs/placeholder_filter_input.h index 3641176a51dd4..321c164ce7130 100644 --- a/impeller/entity/contents/filters/inputs/placeholder_filter_input.h +++ b/impeller/entity/contents/filters/inputs/placeholder_filter_input.h @@ -28,11 +28,6 @@ class PlaceholderFilterInput final : public FilterInput { // |FilterInput| std::optional GetCoverage(const Entity& entity) const override; - // |FilterInput| - void PopulateGlyphAtlas( - const std::shared_ptr& lazy_glyph_atlas, - Scalar scale) override; - private: Rect coverage_rect_; diff --git a/impeller/entity/contents/runtime_effect_contents.cc b/impeller/entity/contents/runtime_effect_contents.cc index d28f64e1f291b..d690b66924f78 100644 --- a/impeller/entity/contents/runtime_effect_contents.cc +++ b/impeller/entity/contents/runtime_effect_contents.cc @@ -39,10 +39,6 @@ void RuntimeEffectContents::SetTextureInputs( texture_inputs_ = std::move(texture_inputs); } -bool RuntimeEffectContents::CanInheritOpacity(const Entity& entity) const { - return false; -} - static ShaderType GetShaderType(RuntimeUniformType type) { switch (type) { case kSampledImage: diff --git a/impeller/entity/contents/runtime_effect_contents.h b/impeller/entity/contents/runtime_effect_contents.h index 3602e82ee3517..151bb315f9ef2 100644 --- a/impeller/entity/contents/runtime_effect_contents.h +++ b/impeller/entity/contents/runtime_effect_contents.h @@ -27,9 +27,6 @@ class RuntimeEffectContents final : public ColorSourceContents { void SetTextureInputs(std::vector texture_inputs); - // | Contents| - bool CanInheritOpacity(const Entity& entity) const override; - // |Contents| bool Render(const ContentContext& renderer, const Entity& entity, diff --git a/impeller/entity/contents/text_contents.cc b/impeller/entity/contents/text_contents.cc index 8254902e8df09..3c5db239301bd 100644 --- a/impeller/entity/contents/text_contents.cc +++ b/impeller/entity/contents/text_contents.cc @@ -37,16 +37,6 @@ Color TextContents::GetColor() const { return color_.WithAlpha(color_.alpha * inherited_opacity_); } -bool TextContents::CanInheritOpacity(const Entity& entity) const { - // Computing whether or not opacity can be inherited requires determining if - // any glyphs can overlap exactly. While this was previously implemented - // via TextFrame::MaybeHasOverlapping, this code relied on scaling up text - // bounds for a size specified at 1.0 DPR, which was not accurate at - // higher or lower DPRs. Rather than re-implement the checks to compute exact - // glyph bounds, for now this optimization has been disabled for Text. - return false; -} - void TextContents::SetInheritedOpacity(Scalar opacity) { inherited_opacity_ = opacity; } @@ -63,13 +53,6 @@ std::optional TextContents::GetCoverage(const Entity& entity) const { return frame_->GetBounds().TransformBounds(entity.GetTransform()); } -void TextContents::PopulateGlyphAtlas( - const std::shared_ptr& lazy_glyph_atlas, - Scalar scale) { - lazy_glyph_atlas->AddTextFrame(*frame_, scale, offset_, properties_); - scale_ = scale; -} - void TextContents::SetTextProperties(Color color, bool stroke, Scalar stroke_width, diff --git a/impeller/entity/contents/text_contents.h b/impeller/entity/contents/text_contents.h index 807b02267e502..f379f1836e371 100644 --- a/impeller/entity/contents/text_contents.h +++ b/impeller/entity/contents/text_contents.h @@ -43,9 +43,6 @@ class TextContents final : public Contents { Color GetColor() const; - // |Contents| - bool CanInheritOpacity(const Entity& entity) const override; - // |Contents| void SetInheritedOpacity(Scalar opacity) override; @@ -57,11 +54,6 @@ class TextContents final : public Contents { // |Contents| std::optional GetCoverage(const Entity& entity) const override; - // |Contents| - void PopulateGlyphAtlas( - const std::shared_ptr& lazy_glyph_atlas, - Scalar scale) override; - void SetScale(Scalar scale) { scale_ = scale; } // |Contents| diff --git a/impeller/entity/contents/texture_contents.cc b/impeller/entity/contents/texture_contents.cc index 83454365a6ad1..f23d39f5eae1b 100644 --- a/impeller/entity/contents/texture_contents.cc +++ b/impeller/entity/contents/texture_contents.cc @@ -55,10 +55,6 @@ void TextureContents::SetStencilEnabled(bool enabled) { stencil_enabled_ = enabled; } -bool TextureContents::CanInheritOpacity(const Entity& entity) const { - return true; -} - void TextureContents::SetInheritedOpacity(Scalar opacity) { inherited_opacity_ = opacity; } diff --git a/impeller/entity/contents/texture_contents.h b/impeller/entity/contents/texture_contents.h index 5ea2b31e281fa..2526eabf3d9bb 100644 --- a/impeller/entity/contents/texture_contents.h +++ b/impeller/entity/contents/texture_contents.h @@ -69,9 +69,6 @@ class TextureContents final : public Contents { const Entity& entity, RenderPass& pass) const override; - // |Contents| - bool CanInheritOpacity(const Entity& entity) const override; - // |Contents| void SetInheritedOpacity(Scalar opacity) override; diff --git a/impeller/entity/entity.cc b/impeller/entity/entity.cc index f6a969f64e80a..5f06a309a7e8f 100644 --- a/impeller/entity/entity.cc +++ b/impeller/entity/entity.cc @@ -12,7 +12,6 @@ #include "impeller/entity/contents/content_context.h" #include "impeller/entity/contents/filters/filter_contents.h" #include "impeller/entity/contents/texture_contents.h" -#include "impeller/entity/entity_pass.h" #include "impeller/geometry/color.h" #include "impeller/geometry/vector.h" #include "impeller/renderer/render_pass.h" @@ -81,14 +80,6 @@ Contents::ClipCoverage Entity::GetClipCoverage( return contents_->GetClipCoverage(*this, current_clip_coverage); } -bool Entity::ShouldRender(const std::optional& clip_coverage) const { -#ifdef IMPELLER_CONTENT_CULLING - return contents_->ShouldRender(*this, clip_coverage); -#else - return true; -#endif // IMPELLER_CONTENT_CULLING -} - void Entity::SetContents(std::shared_ptr contents) { contents_ = std::move(contents); } @@ -122,21 +113,9 @@ BlendMode Entity::GetBlendMode() const { return blend_mode_; } -bool Entity::CanInheritOpacity() const { - if (!contents_) { - return false; - } - if (!((blend_mode_ == BlendMode::kSource && - contents_->IsOpaque(GetTransform())) || - blend_mode_ == BlendMode::kSourceOver)) { - return false; - } - return contents_->CanInheritOpacity(*this); -} - bool Entity::SetInheritedOpacity(Scalar alpha) { - if (!CanInheritOpacity()) { - return false; + if (alpha >= 1.0) { + return true; } if (blend_mode_ == BlendMode::kSource && contents_->IsOpaque(GetTransform())) { @@ -188,10 +167,6 @@ bool Entity::Render(const ContentContext& renderer, return contents_->Render(renderer, *this, parent_pass); } -Scalar Entity::DeriveTextScale() const { - return GetTransform().GetMaxBasisLengthXY(); -} - Entity Entity::Clone() const { return Entity(*this); } diff --git a/impeller/entity/entity.h b/impeller/entity/entity.h index daaac1420a420..86d2e0b3296d9 100644 --- a/impeller/entity/entity.h +++ b/impeller/entity/entity.h @@ -95,8 +95,6 @@ class Entity { Contents::ClipCoverage GetClipCoverage( const std::optional& current_clip_coverage) const; - bool ShouldRender(const std::optional& clip_coverage) const; - void SetContents(std::shared_ptr contents); const std::shared_ptr& GetContents() const; @@ -117,14 +115,10 @@ class Entity { static bool IsBlendModeDestructive(BlendMode blend_mode); - bool CanInheritOpacity() const; - bool SetInheritedOpacity(Scalar alpha); std::optional AsBackgroundColor(ISize target_size) const; - Scalar DeriveTextScale() const; - Entity Clone() const; private: diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc deleted file mode 100644 index 01c8d4b11dd25..0000000000000 --- a/impeller/entity/entity_pass.cc +++ /dev/null @@ -1,1126 +0,0 @@ -// 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 "impeller/entity/entity_pass.h" - -#include -#include -#include -#include - -#include "flutter/fml/closure.h" -#include "flutter/fml/logging.h" -#include "flutter/fml/trace_event.h" -#include "impeller/base/strings.h" -#include "impeller/base/validation.h" -#include "impeller/core/formats.h" -#include "impeller/entity/contents/content_context.h" -#include "impeller/entity/contents/filters/color_filter_contents.h" -#include "impeller/entity/contents/filters/inputs/filter_input.h" -#include "impeller/entity/contents/framebuffer_blend_contents.h" -#include "impeller/entity/contents/texture_contents.h" -#include "impeller/entity/draw_order_resolver.h" -#include "impeller/entity/entity.h" -#include "impeller/entity/entity_pass_clip_stack.h" -#include "impeller/entity/inline_pass_context.h" -#include "impeller/entity/save_layer_utils.h" -#include "impeller/geometry/color.h" -#include "impeller/geometry/rect.h" -#include "impeller/geometry/size.h" -#include "impeller/renderer/command_buffer.h" - -namespace impeller { - -namespace { -std::tuple, BlendMode> ElementAsBackgroundColor( - const EntityPass::Element& element, - ISize target_size) { - if (const Entity* entity = std::get_if(&element)) { - std::optional entity_color = entity->AsBackgroundColor(target_size); - if (entity_color.has_value()) { - return {entity_color.value(), entity->GetBlendMode()}; - } - } - return {}; -} -} // namespace - -bool EntityPass::IsSubpass(const Element& element) { - return std::holds_alternative>(element); -} - -EntityPass::EntityPass() = default; - -EntityPass::~EntityPass() = default; - -void EntityPass::SetDelegate(std::shared_ptr delegate) { - if (!delegate) { - return; - } - delegate_ = std::move(delegate); -} - -void EntityPass::SetBoundsLimit(std::optional bounds_limit) { - bounds_limit_ = bounds_limit; -} - -std::optional EntityPass::GetBoundsLimit() const { - return bounds_limit_; -} - -void EntityPass::AddEntity(Entity entity) { - if (entity.GetBlendMode() == BlendMode::kSourceOver && - entity.GetContents()->IsOpaque(entity.GetTransform())) { - entity.SetBlendMode(BlendMode::kSource); - } - - if (entity.GetBlendMode() > Entity::kLastPipelineBlendMode) { - advanced_blend_reads_from_pass_texture_ = true; - } - draw_order_resolver_.AddElement(elements_.size(), - entity.GetBlendMode() == BlendMode::kSource); - elements_.emplace_back(std::move(entity)); -} - -void EntityPass::PushClip(Entity entity) { - elements_.emplace_back(std::move(entity)); - draw_order_resolver_.PushClip(elements_.size() - 1); - active_clips_.emplace_back(elements_.size() - 1); -} - -void EntityPass::PopClips(size_t num_clips, uint64_t depth) { - if (num_clips > active_clips_.size()) { - VALIDATION_LOG - << "Attempted to pop more clips than are currently active. Active: " - << active_clips_.size() << ", Popped: " << num_clips - << ", Depth: " << depth; - } - - size_t max = std::min(num_clips, active_clips_.size()); - for (size_t i = 0; i < max; i++) { - FML_DCHECK(active_clips_.back() < elements_.size()); - Entity* element = std::get_if(&elements_[active_clips_.back()]); - FML_DCHECK(element); - element->SetClipDepth(depth); - active_clips_.pop_back(); - draw_order_resolver_.PopClip(); - } -} - -void EntityPass::PopAllClips(uint64_t depth) { - PopClips(active_clips_.size(), depth); -} - -void EntityPass::SetElements(std::vector elements) { - elements_ = std::move(elements); -} - -size_t EntityPass::GetSubpassesDepth() const { - size_t max_subpass_depth = 0u; - for (const auto& element : elements_) { - if (auto subpass = std::get_if>(&element)) { - max_subpass_depth = - std::max(max_subpass_depth, subpass->get()->GetSubpassesDepth()); - } - } - return max_subpass_depth + 1u; -} - -EntityPass* EntityPass::GetSuperpass() const { - return superpass_; -} - -EntityPass* EntityPass::AddSubpass(std::unique_ptr pass) { - if (!pass) { - return nullptr; - } - FML_DCHECK(pass->superpass_ == nullptr); - pass->superpass_ = this; - - bool has_backdrop_filter = pass->backdrop_filter_proc_ != nullptr; - if (has_backdrop_filter) { - backdrop_filter_reads_from_pass_texture_ = true; - - // Since backdrop filters trigger the RenderPass to end and lose all depth - // information for opaque draws, this is a hard barrier for the draw order - // optimization. Flush all sorted draws accumulated up to this point. - draw_order_resolver_.Flush(); - } - if (pass->blend_mode_ > Entity::kLastPipelineBlendMode) { - advanced_blend_reads_from_pass_texture_ = true; - } - - auto subpass_pointer = pass.get(); - elements_.emplace_back(std::move(pass)); - - draw_order_resolver_.AddElement(elements_.size() - 1, false); - if (has_backdrop_filter) { - draw_order_resolver_.Flush(); - } - - return subpass_pointer; -} - -static const constexpr RenderTarget::AttachmentConfig kDefaultStencilConfig = - RenderTarget::AttachmentConfig{ - .storage_mode = StorageMode::kDeviceTransient, - .load_action = LoadAction::kDontCare, - .store_action = StoreAction::kDontCare, - }; - -static EntityPassTarget CreateRenderTarget(ContentContext& renderer, - ISize size, - int mip_count, - const Color& clear_color) { - const std::shared_ptr& context = renderer.GetContext(); - - /// All of the load/store actions are managed by `InlinePassContext` when - /// `RenderPasses` are created, so we just set them to `kDontCare` here. - /// What's important is the `StorageMode` of the textures, which cannot be - /// changed for the lifetime of the textures. - - if (context->GetBackendType() == Context::BackendType::kOpenGLES) { - // TODO(141732): Implement mip map generation on opengles. - mip_count = 1; - } - - RenderTarget target; - if (context->GetCapabilities()->SupportsOffscreenMSAA()) { - target = renderer.GetRenderTargetCache()->CreateOffscreenMSAA( - /*context=*/*context, - /*size=*/size, - /*mip_count=*/mip_count, - /*label=*/"EntityPass", - /*color_attachment_config=*/ - RenderTarget::AttachmentConfigMSAA{ - .storage_mode = StorageMode::kDeviceTransient, - .resolve_storage_mode = StorageMode::kDevicePrivate, - .load_action = LoadAction::kDontCare, - .store_action = StoreAction::kMultisampleResolve, - .clear_color = clear_color}, - /*stencil_attachment_config=*/ - kDefaultStencilConfig); - } else { - target = renderer.GetRenderTargetCache()->CreateOffscreen( - *context, // context - size, // size - /*mip_count=*/mip_count, - "EntityPass", // label - RenderTarget::AttachmentConfig{ - .storage_mode = StorageMode::kDevicePrivate, - .load_action = LoadAction::kDontCare, - .store_action = StoreAction::kDontCare, - .clear_color = clear_color, - }, // color_attachment_config - kDefaultStencilConfig // stencil_attachment_config - ); - } - - return EntityPassTarget( - target, renderer.GetDeviceCapabilities().SupportsReadFromResolve(), - renderer.GetDeviceCapabilities().SupportsImplicitResolvingMSAA()); -} - -bool EntityPass::DoesBackdropGetRead(ContentContext& renderer) const { - return renderer.GetDeviceCapabilities().SupportsFramebufferFetch() - ? backdrop_filter_reads_from_pass_texture_ - : backdrop_filter_reads_from_pass_texture_ || - advanced_blend_reads_from_pass_texture_; -} - -bool EntityPass::Render(ContentContext& renderer, - const RenderTarget& render_target) const { - renderer.GetRenderTargetCache()->Start(); - fml::ScopedCleanupClosure reset_state([&renderer]() { - renderer.GetLazyGlyphAtlas()->ResetTextFrames(); - renderer.GetRenderTargetCache()->End(); - }); - - auto root_render_target = render_target; - - if (root_render_target.GetColorAttachments().find(0u) == - root_render_target.GetColorAttachments().end()) { - VALIDATION_LOG << "The root RenderTarget must have a color attachment."; - return false; - } - if (root_render_target.GetDepthAttachment().has_value() != - root_render_target.GetStencilAttachment().has_value()) { - VALIDATION_LOG << "The root RenderTarget should have a stencil attachment " - "iff it has a depth attachment."; - return false; - } - - const auto& lazy_glyph_atlas = renderer.GetLazyGlyphAtlas(); - IterateAllEntities([&lazy_glyph_atlas](const Entity& entity) { - if (const auto& contents = entity.GetContents()) { - contents->PopulateGlyphAtlas(lazy_glyph_atlas, entity.DeriveTextScale()); - } - return true; - }); - - EntityPassClipStack clip_stack = EntityPassClipStack( - Rect::MakeSize(root_render_target.GetRenderTargetSize())); - - // In this branch path, we need to render everything to an offscreen texture - // and then blit the results onto the onscreen texture. If using this branch, - // there's no need to set up a stencil attachment on the root render target. - if (DoesBackdropGetRead(renderer)) { - EntityPassTarget offscreen_target = CreateRenderTarget( - renderer, root_render_target.GetRenderTargetSize(), - GetRequiredMipCount(), - GetClearColorOrDefault(render_target.GetRenderTargetSize())); - - if (!OnRender(renderer, // renderer - offscreen_target.GetRenderTarget() - .GetRenderTargetSize(), // root_pass_size - offscreen_target, // pass_target - Point(), // global_pass_position - Point(), // local_pass_position - 0, // pass_depth - clip_stack // clip_coverage_stack - )) { - // Validation error messages are triggered for all `OnRender()` failure - // cases. - return false; - } - - auto command_buffer = renderer.GetContext()->CreateCommandBuffer(); - command_buffer->SetLabel("EntityPass Root Command Buffer"); - - // If the context supports blitting, blit the offscreen texture to the - // onscreen texture. Otherwise, draw it to the parent texture using a - // pipeline (slower). - if (renderer.GetContext() - ->GetCapabilities() - ->SupportsTextureToTextureBlits()) { - auto blit_pass = command_buffer->CreateBlitPass(); - blit_pass->AddCopy( - offscreen_target.GetRenderTarget().GetRenderTargetTexture(), - root_render_target.GetRenderTargetTexture()); - if (!blit_pass->EncodeCommands( - renderer.GetContext()->GetResourceAllocator())) { - VALIDATION_LOG << "Failed to encode root pass blit command."; - return false; - } - if (!renderer.GetContext() - ->GetCommandQueue() - ->Submit({command_buffer}) - .ok()) { - return false; - } - } else { - auto render_pass = command_buffer->CreateRenderPass(root_render_target); - render_pass->SetLabel("EntityPass Root Render Pass"); - - { - auto size_rect = Rect::MakeSize( - offscreen_target.GetRenderTarget().GetRenderTargetSize()); - auto contents = TextureContents::MakeRect(size_rect); - contents->SetTexture( - offscreen_target.GetRenderTarget().GetRenderTargetTexture()); - contents->SetSourceRect(size_rect); - contents->SetLabel("Root pass blit"); - - Entity entity; - entity.SetContents(contents); - entity.SetBlendMode(BlendMode::kSource); - - if (!entity.Render(renderer, *render_pass)) { - VALIDATION_LOG << "Failed to render EntityPass root blit."; - return false; - } - } - - if (!render_pass->EncodeCommands()) { - VALIDATION_LOG << "Failed to encode root pass command buffer."; - return false; - } - if (!renderer.GetContext() - ->GetCommandQueue() - ->Submit({command_buffer}) - .ok()) { - return false; - } - } - - return true; - } - - // If we make it this far, that means the context is capable of rendering - // everything directly to the onscreen texture. - - // The safety check for fetching this color attachment is at the beginning of - // this method. - auto color0 = root_render_target.GetColorAttachments().find(0u)->second; - - auto stencil_attachment = root_render_target.GetStencilAttachment(); - auto depth_attachment = root_render_target.GetDepthAttachment(); - if (!stencil_attachment.has_value() || !depth_attachment.has_value()) { - // Setup a new root stencil with an optimal configuration if one wasn't - // provided by the caller. - root_render_target.SetupDepthStencilAttachments( - *renderer.GetContext(), *renderer.GetContext()->GetResourceAllocator(), - color0.texture->GetSize(), - renderer.GetContext()->GetCapabilities()->SupportsOffscreenMSAA(), - "ImpellerOnscreen", kDefaultStencilConfig); - } - - // Set up the clear color of the root pass. - color0.clear_color = - GetClearColorOrDefault(render_target.GetRenderTargetSize()); - root_render_target.SetColorAttachment(color0, 0); - - EntityPassTarget pass_target( - root_render_target, - renderer.GetDeviceCapabilities().SupportsReadFromResolve(), - renderer.GetDeviceCapabilities().SupportsImplicitResolvingMSAA()); - - return OnRender( // - renderer, // renderer - root_render_target.GetRenderTargetSize(), // root_pass_size - pass_target, // pass_target - Point(), // global_pass_position - Point(), // local_pass_position - 0, // pass_depth - clip_stack); // clip_coverage_stack -} - -EntityPass::EntityResult EntityPass::GetEntityForElement( - const EntityPass::Element& element, - ContentContext& renderer, - InlinePassContext& pass_context, - ISize root_pass_size, - Point global_pass_position, - uint32_t pass_depth, - EntityPassClipStack& clip_coverage_stack, - size_t clip_height_floor) const { - //-------------------------------------------------------------------------- - /// Setup entity element. - /// - if (const auto& entity = std::get_if(&element)) { - Entity element_entity = entity->Clone(); - - if (!global_pass_position.IsZero()) { - // If the pass image is going to be rendered with a non-zero position, - // apply the negative translation to entity copies before rendering them - // so that they'll end up rendering to the correct on-screen position. - element_entity.SetTransform( - Matrix::MakeTranslation(Vector3(-global_pass_position)) * - element_entity.GetTransform()); - } - return EntityPass::EntityResult::Success(std::move(element_entity)); - } - - //-------------------------------------------------------------------------- - /// Setup subpass element. - /// - if (const auto& subpass_ptr = - std::get_if>(&element)) { - auto subpass = subpass_ptr->get(); - if (subpass->delegate_->CanElide()) { - return EntityPass::EntityResult::Skip(); - } - - if (!subpass->backdrop_filter_proc_ && - subpass->delegate_->CanCollapseIntoParentPass(subpass)) { - // Directly render into the parent target and move on. - if (!subpass->OnRender( - renderer, // renderer - root_pass_size, // root_pass_size - pass_context.GetPassTarget(), // pass_target - global_pass_position, // global_pass_position - Point(), // local_pass_position - pass_depth, // pass_depth - clip_coverage_stack, // clip_coverage_stack - clip_height_, // clip_height_floor - nullptr, // backdrop_filter_contents - pass_context.GetRenderPass(pass_depth) // collapsed_parent_pass - )) { - // Validation error messages are triggered for all `OnRender()` failure - // cases. - return EntityPass::EntityResult::Failure(); - } - return EntityPass::EntityResult::Skip(); - } - - std::shared_ptr subpass_backdrop_filter_contents = nullptr; - if (subpass->backdrop_filter_proc_) { - auto texture = pass_context.GetTexture(); - // Render the backdrop texture before any of the pass elements. - const auto& proc = subpass->backdrop_filter_proc_; - - subpass_backdrop_filter_contents = proc( - FilterInput::Make(std::move(texture)), subpass->transform_.Basis(), - // When the subpass has a translation that means the math with - // the snapshot has to be different. - subpass->transform_.HasTranslation() - ? Entity::RenderingMode::kSubpassPrependSnapshotTransform - : Entity::RenderingMode::kSubpassAppendSnapshotTransform); - - // If the very first thing we render in this EntityPass is a subpass that - // happens to have a backdrop filter, than that backdrop filter will end - // may wind up sampling from the raw, uncleared texture that came straight - // out of the texture cache. By calling `pass_context.GetRenderPass` here, - // we force the texture to pass through at least one RenderPass with the - // correct clear configuration before any sampling occurs. - pass_context.GetRenderPass(pass_depth); - - // The subpass will need to read from the current pass texture when - // rendering the backdrop, so if there's an active pass, end it prior to - // rendering the subpass. - pass_context.EndPass(); - } - - if (!clip_coverage_stack.HasCoverage()) { - // The current clip is empty. This means the pass texture won't be - // visible, so skip it. - return EntityPass::EntityResult::Skip(); - } - auto clip_coverage_back = clip_coverage_stack.CurrentClipCoverage(); - if (!clip_coverage_back.has_value()) { - return EntityPass::EntityResult::Skip(); - } - - // The maximum coverage of the subpass. Subpasses textures should never - // extend outside the parent pass texture or the current clip coverage. - auto coverage_limit = Rect::MakeOriginSize(global_pass_position, - Size(pass_context.GetPassTarget() - .GetRenderTarget() - .GetRenderTargetSize())) - .Intersection(clip_coverage_back.value()); - if (!coverage_limit.has_value()) { - return EntityPass::EntityResult::Skip(); - } - - coverage_limit = - coverage_limit->Intersection(Rect::MakeSize(root_pass_size)); - if (!coverage_limit.has_value()) { - return EntityPass::EntityResult::Skip(); - } - - std::shared_ptr image_filter = - subpass->delegate_->WithImageFilter(Rect(), subpass->transform_); - - auto subpass_coverage = ComputeSaveLayerCoverage( - subpass->bounds_limit_.value_or(Rect::MakeMaximum()), // - subpass->transform_, // - coverage_limit.value(), // - image_filter, // - /*flood_output_coverage=*/subpass->flood_clip_, // - /*flood_input_coverage=*/!!subpass_backdrop_filter_contents // - ); - - if (!subpass_coverage.has_value()) { - return EntityPass::EntityResult::Skip(); - } - - auto subpass_size = ISize(subpass_coverage->GetSize()); - if (subpass_size.IsEmpty()) { - return EntityPass::EntityResult::Skip(); - } - - auto subpass_target = CreateRenderTarget( - renderer, // renderer - subpass_size, // size - subpass->GetRequiredMipCount(), - subpass->GetClearColorOrDefault(subpass_size)); // clear_color - - if (!subpass_target.IsValid()) { - VALIDATION_LOG << "Subpass render target is invalid."; - return EntityPass::EntityResult::Failure(); - } - - // Start non-collapsed subpasses with a fresh clip coverage stack limited by - // the subpass coverage. This is important because image filters applied to - // save layers may transform the subpass texture after it's rendered, - // causing parent clip coverage to get misaligned with the actual area that - // the subpass will affect in the parent pass. - clip_coverage_stack.PushSubpass(subpass_coverage, subpass->clip_height_); - - // Stencil textures aren't shared between EntityPasses (as much of the - // time they are transient). - if (!subpass->OnRender( - renderer, // renderer - root_pass_size, // root_pass_size - subpass_target, // pass_target - subpass_coverage->GetOrigin(), // global_pass_position - subpass_coverage->GetOrigin() - - global_pass_position, // local_pass_position - ++pass_depth, // pass_depth - clip_coverage_stack, // clip_coverage_stack - subpass->clip_height_, // clip_height_floor - subpass_backdrop_filter_contents // backdrop_filter_contents - )) { - // Validation error messages are triggered for all `OnRender()` failure - // cases. - return EntityPass::EntityResult::Failure(); - } - - clip_coverage_stack.PopSubpass(); - - // The subpass target's texture may have changed during OnRender. - auto subpass_texture = - subpass_target.GetRenderTarget().GetRenderTargetTexture(); - - auto offscreen_texture_contents = - subpass->delegate_->CreateContentsForSubpassTarget( - subpass_texture, - Matrix::MakeTranslation(Vector3{-global_pass_position}) * - subpass->transform_); - - if (!offscreen_texture_contents) { - // This is an error because the subpass delegate said the pass couldn't - // be collapsed into its parent. Yet, when asked how it want's to - // postprocess the offscreen texture, it couldn't give us an answer. - // - // Theoretically, we could collapse the pass now. But that would be - // wasteful as we already have the offscreen texture and we don't want - // to discard it without ever using it. Just make the delegate do the - // right thing. - return EntityPass::EntityResult::Failure(); - } - - // Round the subpass texture position for pixel alignment with the parent - // pass render target. By default, we draw subpass textures with nearest - // sampling, so aligning here is important for avoiding visual nearest - // sampling errors caused by limited floating point precision when - // straddling a half pixel boundary. - // - // We do this in lieu of expanding/rounding out the subpass coverage in - // order to keep the bounds wrapping consistently tight around subpass - // elements. Which is necessary to avoid intense flickering in cases - // where a subpass texture has a large blur filter with clamp sampling. - // - // See also this bug: https://github.com/flutter/flutter/issues/144213 - Point subpass_texture_position = - (subpass_coverage->GetOrigin() - global_pass_position).Round(); - - Entity element_entity; - element_entity.SetClipDepth(subpass->clip_depth_); - element_entity.SetContents(std::move(offscreen_texture_contents)); - element_entity.SetBlendMode(subpass->blend_mode_); - element_entity.SetTransform( - Matrix::MakeTranslation(Vector3(subpass_texture_position))); - - return EntityPass::EntityResult::Success(std::move(element_entity)); - } - FML_UNREACHABLE(); -} - -static void SetClipScissor(std::optional clip_coverage, - RenderPass& pass, - Point global_pass_position) { - // Set the scissor to the clip coverage area. We do this prior to rendering - // the clip itself and all its contents. - IRect scissor; - if (clip_coverage.has_value()) { - clip_coverage = clip_coverage->Shift(-global_pass_position); - scissor = IRect::RoundOut(clip_coverage.value()); - // The scissor rect must not exceed the size of the render target. - scissor = scissor.Intersection(IRect::MakeSize(pass.GetRenderTargetSize())) - .value_or(IRect()); - } - pass.SetScissor(scissor); -} - -bool EntityPass::RenderElement(Entity& element_entity, - size_t clip_height_floor, - InlinePassContext& pass_context, - int32_t pass_depth, - ContentContext& renderer, - EntityPassClipStack& clip_coverage_stack, - Point global_pass_position) const { - // Setup advanced blends. - if (element_entity.GetBlendMode() > Entity::kLastPipelineBlendMode) { - if (renderer.GetDeviceCapabilities().SupportsFramebufferFetch()) { - auto src_contents = element_entity.GetContents(); - auto contents = std::make_shared(); - contents->SetChildContents(src_contents); - contents->SetBlendMode(element_entity.GetBlendMode()); - element_entity.SetContents(std::move(contents)); - element_entity.SetBlendMode(BlendMode::kSource); - } else { - // End the active pass and flush the buffer before rendering - // "advanced" blends. Advanced blends work by binding the current - // render target texture as an input ("destination"), blending with a - // second texture input ("source"), writing the result to an - // intermediate texture, and finally copying the data from the - // intermediate texture back to the render target texture. And so all - // of the commands that have written to the render target texture so - // far need to execute before it's bound for blending (otherwise the - // blend pass will end up executing before all the previous commands - // in the active pass). - - if (!pass_context.EndPass()) { - VALIDATION_LOG - << "Failed to end the current render pass in order to read " - "from " - "the backdrop texture and apply an advanced blend."; - return false; - } - - // Amend an advanced blend filter to the contents, attaching the pass - // texture. - auto texture = pass_context.GetTexture(); - if (!texture) { - VALIDATION_LOG << "Failed to fetch the color texture in order to " - "apply an advanced blend."; - return false; - } - - FilterInput::Vector inputs = { - FilterInput::Make(texture, element_entity.GetTransform().Invert()), - FilterInput::Make(element_entity.GetContents())}; - auto contents = - ColorFilterContents::MakeBlend(element_entity.GetBlendMode(), inputs); - contents->SetCoverageHint(element_entity.GetCoverage()); - element_entity.SetContents(std::move(contents)); - element_entity.SetBlendMode(BlendMode::kSource); - } - } - - auto result = pass_context.GetRenderPass(pass_depth); - if (!result.pass) { - // Failure to produce a render pass should be explained by specific errors - // in `InlinePassContext::GetRenderPass()`, so avoid log spam and don't - // append a validation log here. - return false; - } - - // If the pass context returns a backdrop texture, we need to draw it to the - // current pass. We do this because it's faster and takes significantly less - // memory than storing/loading large MSAA textures. Also, it's not possible to - // blit the non-MSAA resolve texture of the previous pass to MSAA textures - // (let alone a transient one). - if (result.backdrop_texture) { - auto size_rect = Rect::MakeSize(result.pass->GetRenderTargetSize()); - auto msaa_backdrop_contents = TextureContents::MakeRect(size_rect); - msaa_backdrop_contents->SetStencilEnabled(false); - msaa_backdrop_contents->SetLabel("MSAA backdrop"); - msaa_backdrop_contents->SetSourceRect(size_rect); - msaa_backdrop_contents->SetTexture(result.backdrop_texture); - - Entity msaa_backdrop_entity; - msaa_backdrop_entity.SetContents(std::move(msaa_backdrop_contents)); - msaa_backdrop_entity.SetBlendMode(BlendMode::kSource); - msaa_backdrop_entity.SetClipDepth(std::numeric_limits::max()); - if (!msaa_backdrop_entity.Render(renderer, *result.pass)) { - VALIDATION_LOG << "Failed to render MSAA backdrop filter entity."; - return false; - } - } - - if (result.just_created) { - clip_coverage_stack.ActivateClipReplay(); - } - - // If there are any pending clips to replay, render any that may affect - // the entity we're about to render. - while (const EntityPassClipStack::ReplayResult* next_replay_clip = - clip_coverage_stack.GetNextReplayResult( - element_entity.GetClipDepth())) { - auto& replay_entity = next_replay_clip->entity; - SetClipScissor(next_replay_clip->clip_coverage, *result.pass, - global_pass_position); - if (!replay_entity.Render(renderer, *result.pass)) { - VALIDATION_LOG << "Failed to render entity for clip replay."; - return false; - } - } - - auto current_clip_coverage = clip_coverage_stack.CurrentClipCoverage(); - if (current_clip_coverage.has_value()) { - // Entity transforms are relative to the current pass position, so we need - // to check clip coverage in the same space. - current_clip_coverage = current_clip_coverage->Shift(-global_pass_position); - } - - if (!element_entity.ShouldRender(current_clip_coverage)) { - return true; // Nothing to render. - } - - auto clip_coverage = element_entity.GetClipCoverage(current_clip_coverage); - if (clip_coverage.coverage.has_value()) { - clip_coverage.coverage = - clip_coverage.coverage->Shift(global_pass_position); - } - - // The coverage hint tells the rendered Contents which portion of the - // rendered output will actually be used, and so we set this to the current - // clip coverage (which is the max clip bounds). The contents may - // optionally use this hint to avoid unnecessary rendering work. - auto element_coverage_hint = element_entity.GetContents()->GetCoverageHint(); - element_entity.GetContents()->SetCoverageHint( - Rect::Intersection(element_coverage_hint, current_clip_coverage)); - - EntityPassClipStack::ClipStateResult clip_state_result = - clip_coverage_stack.ApplyClipState(clip_coverage, element_entity, - clip_height_floor, - global_pass_position); - - if (clip_state_result.clip_did_change) { - // We only need to update the pass scissor if the clip state has changed. - SetClipScissor(clip_coverage_stack.CurrentClipCoverage(), *result.pass, - global_pass_position); - } - - if (!clip_state_result.should_render) { - return true; - } - - if (!element_entity.Render(renderer, *result.pass)) { - VALIDATION_LOG << "Failed to render entity."; - return false; - } - return true; -} - -bool EntityPass::OnRender( - ContentContext& renderer, - ISize root_pass_size, - EntityPassTarget& pass_target, - Point global_pass_position, - Point local_pass_position, - uint32_t pass_depth, - EntityPassClipStack& clip_coverage_stack, - size_t clip_height_floor, - std::shared_ptr backdrop_filter_contents, - const std::optional& - collapsed_parent_pass) const { - TRACE_EVENT0("impeller", "EntityPass::OnRender"); - - if (!active_clips_.empty()) { - VALIDATION_LOG << SPrintF( - "EntityPass (Depth=%d) contains one or more clips with an unresolved " - "depth value.", - pass_depth); - } - - InlinePassContext pass_context(renderer, pass_target, GetElementCount(), - collapsed_parent_pass); - if (!pass_context.IsValid()) { - VALIDATION_LOG << SPrintF("Pass context invalid (Depth=%d)", pass_depth); - return false; - } - auto clear_color_size = pass_target.GetRenderTarget().GetRenderTargetSize(); - - if (!collapsed_parent_pass) { - // Always force the pass to construct the render pass object, even if there - // is not a clear color. This ensures that the attachment textures are - // cleared/transitioned to the right state. - pass_context.GetRenderPass(pass_depth); - } - - if (backdrop_filter_proc_) { - if (!backdrop_filter_contents) { - VALIDATION_LOG - << "EntityPass contains a backdrop filter, but no backdrop filter " - "contents was supplied by the parent pass at render time. This is " - "a bug in EntityPass. Parent passes are responsible for setting " - "up backdrop filters for their children."; - return false; - } - - Entity backdrop_entity; - backdrop_entity.SetContents(std::move(backdrop_filter_contents)); - backdrop_entity.SetTransform( - Matrix::MakeTranslation(Vector3(-local_pass_position))); - backdrop_entity.SetClipDepth(std::numeric_limits::max()); - - RenderElement(backdrop_entity, clip_height_floor, pass_context, pass_depth, - renderer, clip_coverage_stack, global_pass_position); - } - - bool should_collapse_clear_colors = - !collapsed_parent_pass && - // Backdrop filters act as a entity before - // everything and disrupt the optimization. - !backdrop_filter_proc_; - - // Count the number of elements eaten by the clear color optimization. Break - // it down in terms of opaque and translucent elements so that we can skip - // over these entities when applying the clear color optimization. - size_t opaque_clear_entity_count = 0; - size_t translucent_clear_entity_count = 0; - if (should_collapse_clear_colors) { - for (const auto& element : elements_) { - if (const Entity* entity = std::get_if(&element)) { - std::optional entity_color = - entity->AsBackgroundColor(clear_color_size); - if (entity_color.has_value()) { - if (entity->GetBlendMode() == BlendMode::kSource) { - opaque_clear_entity_count++; - } else { - translucent_clear_entity_count++; - } - // We've found an entity that replaces the whole background color of - // this layer, so continue counting. - continue; - } - } - // We came across an element that doesn't replace the background color of - // this layer, so stop counting. - break; - } - } - - using ElementCallback = std::function; - using ElementIterator = std::function; - - ElementIterator element_iterator; - - if (renderer.GetDeviceCapabilities().SupportsFramebufferFetch()) { - element_iterator = - [this, &opaque_clear_entity_count, - &translucent_clear_entity_count](const ElementCallback& callback) { - const auto& sorted_elements = draw_order_resolver_.GetSortedDraws( - opaque_clear_entity_count, translucent_clear_entity_count); - - for (const auto& element_ref : sorted_elements) { - const Element& element = elements_[element_ref]; - if (!callback(element)) { - return false; - } - } - return true; - }; - } else { - // If framebuffer fetch isn't supported, just disable the draw order - // optimization. We could technically make it work by flushing each time - // we encounter an advanced blend at recording time down the road. - element_iterator = [this, &opaque_clear_entity_count, - &translucent_clear_entity_count]( - const ElementCallback& callback) { - size_t skips = opaque_clear_entity_count + translucent_clear_entity_count; - for (const auto& element : elements_) { - if (skips > 0) { - skips--; - continue; - } - if (!callback(element)) { - return false; - } - } - return true; - }; - } - - const auto& render_element = [&, this](Entity& entity) { - return RenderElement(entity, clip_height_floor, pass_context, pass_depth, - renderer, clip_coverage_stack, global_pass_position); - }; - - std::optional deferred_entity; - bool result = element_iterator([&](const Element& element) { - EntityResult result = - GetEntityForElement(element, // element - renderer, // renderer - pass_context, // pass_context - root_pass_size, // root_pass_size - global_pass_position, // global_pass_position - pass_depth, // pass_depth - clip_coverage_stack, // clip_coverage_stack - clip_height_floor); // clip_height_floor - - switch (result.status) { - case EntityResult::kSuccess: - break; - case EntityResult::kFailure: - // All failure cases should be covered by specific validation messages - // in `GetEntityForElement()`. - return false; - case EntityResult::kSkip: - return true; - }; - - if (deferred_entity.has_value() && - result.entity.GetBlendMode() != BlendMode::kSource) { - if (!render_element(*deferred_entity)) { - return false; - } - deferred_entity.reset(); - } - - if (IsSubpass(element)) { - if (deferred_entity.has_value()) { - if (!render_element(*deferred_entity)) { - return false; - } - } - deferred_entity = std::move(result.entity); - return true; - } - - return render_element(result.entity); - }); - if (!result) { - return false; - } - - if (deferred_entity.has_value() && !render_element(*deferred_entity)) { - return false; - } - return true; -} - -void EntityPass::IterateAllElements( - const std::function& iterator) { - if (!iterator) { - return; - } - - for (auto& element : elements_) { - if (!iterator(element)) { - return; - } - if (auto subpass = std::get_if>(&element)) { - subpass->get()->IterateAllElements(iterator); - } - } -} - -void EntityPass::IterateAllElements( - const std::function& iterator) const { - /// TODO(gaaclarke): Remove duplication here between const and non-const - /// versions. - if (!iterator) { - return; - } - - for (auto& element : elements_) { - if (!iterator(element)) { - return; - } - if (auto subpass = std::get_if>(&element)) { - const EntityPass* entity_pass = subpass->get(); - entity_pass->IterateAllElements(iterator); - } - } -} - -void EntityPass::IterateAllEntities( - const std::function& iterator) { - if (!iterator) { - return; - } - - for (auto& element : elements_) { - if (auto entity = std::get_if(&element)) { - if (!iterator(*entity)) { - return; - } - continue; - } - if (auto subpass = std::get_if>(&element)) { - subpass->get()->IterateAllEntities(iterator); - continue; - } - FML_UNREACHABLE(); - } -} - -void EntityPass::IterateAllEntities( - const std::function& iterator) const { - if (!iterator) { - return; - } - - for (const auto& element : elements_) { - if (auto entity = std::get_if(&element)) { - if (!iterator(*entity)) { - return; - } - continue; - } - if (auto subpass = std::get_if>(&element)) { - const EntityPass* entity_pass = subpass->get(); - entity_pass->IterateAllEntities(iterator); - continue; - } - FML_UNREACHABLE(); - } -} - -bool EntityPass::IterateUntilSubpass( - const std::function& iterator) { - if (!iterator) { - return true; - } - - for (auto& element : elements_) { - if (auto entity = std::get_if(&element)) { - if (!iterator(*entity)) { - return false; - } - continue; - } - return true; - } - return false; -} - -size_t EntityPass::GetElementCount() const { - return elements_.size(); -} - -void EntityPass::SetTransform(Matrix transform) { - transform_ = transform; -} - -void EntityPass::SetClipHeight(size_t clip_height) { - clip_height_ = clip_height; -} - -size_t EntityPass::GetClipHeight() const { - return clip_height_; -} - -void EntityPass::SetClipDepth(size_t clip_depth) { - clip_depth_ = clip_depth; -} - -uint32_t EntityPass::GetClipDepth() const { - return clip_depth_; -} - -void EntityPass::SetBlendMode(BlendMode blend_mode) { - blend_mode_ = blend_mode; - flood_clip_ = Entity::IsBlendModeDestructive(blend_mode); -} - -Color EntityPass::GetClearColorOrDefault(ISize size) const { - return GetClearColor(size).value_or(Color::BlackTransparent()); -} - -std::optional EntityPass::GetClearColor(ISize target_size) const { - if (backdrop_filter_proc_) { - return std::nullopt; - } - - std::optional result = std::nullopt; - for (const Element& element : elements_) { - auto [entity_color, blend_mode] = - ElementAsBackgroundColor(element, target_size); - if (!entity_color.has_value()) { - break; - } - result = result.value_or(Color::BlackTransparent()) - .Blend(entity_color.value(), blend_mode); - } - if (result.has_value()) { - return result->Premultiply(); - } - return result; -} - -void EntityPass::SetBackdropFilter(BackdropFilterProc proc) { - if (superpass_) { - VALIDATION_LOG << "Backdrop filters cannot be set on EntityPasses that " - "have already been appended to another pass."; - } - - backdrop_filter_proc_ = std::move(proc); -} - -} // namespace impeller diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h deleted file mode 100644 index 8df8f39e21a30..0000000000000 --- a/impeller/entity/entity_pass.h +++ /dev/null @@ -1,319 +0,0 @@ -// 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. - -#ifndef FLUTTER_IMPELLER_ENTITY_ENTITY_PASS_H_ -#define FLUTTER_IMPELLER_ENTITY_ENTITY_PASS_H_ - -#include -#include -#include -#include -#include - -#include "impeller/entity/contents/contents.h" -#include "impeller/entity/contents/filters/filter_contents.h" -#include "impeller/entity/draw_order_resolver.h" -#include "impeller/entity/entity.h" -#include "impeller/entity/entity_pass_clip_stack.h" -#include "impeller/entity/entity_pass_delegate.h" -#include "impeller/entity/inline_pass_context.h" -#include "impeller/renderer/render_target.h" - -namespace impeller { - -class ContentContext; - -/// Specifies how much to trust the bounds rectangle provided for a list -/// of contents. Used by both |EntityPass| and |Canvas::SaveLayer|. -enum class ContentBoundsPromise { - /// @brief The caller makes no claims related to the size of the bounds. - kUnknown, - - /// @brief The caller claims the bounds are a reasonably tight estimate - /// of the coverage of the contents and should contain all of the - /// contents. - kContainsContents, - - /// @brief The caller claims the bounds are a subset of an estimate of - /// the reasonably tight bounds but likely clips off some of the - /// contents. - kMayClipContents, -}; - -class EntityPass { - public: - /// Elements are renderable items in the `EntityPass`. Each can either be an - /// `Entity` or a child `EntityPass`. - /// - /// When the element is a child `EntityPass`, it may be rendered to an - /// offscreen texture and converted into an `Entity` that draws the texture - /// into the current pass, or its children may be collapsed into the current - /// - /// `EntityPass`. Elements are converted to Entities in - /// `GetEntityForElement()`. - using Element = std::variant>; - - static bool IsSubpass(const Element& element); - - using BackdropFilterProc = std::function( - FilterInput::Ref, - const Matrix& effect_transform, - Entity::RenderingMode rendering_mode)>; - - EntityPass(); - - ~EntityPass(); - - void SetDelegate(std::shared_ptr delgate); - - /// @brief Set the computed content bounds, or std::nullopt if the contents - /// are unbounded. - void SetBoundsLimit(std::optional content_bounds); - - /// @brief Get the bounds limit. - std::optional GetBoundsLimit() const; - - size_t GetSubpassesDepth() const; - - /// @brief Add an entity to the current entity pass. - void AddEntity(Entity entity); - - void PushClip(Entity entity); - - void PopClips(size_t num_clips, uint64_t depth); - - void PopAllClips(uint64_t depth); - - void SetElements(std::vector elements); - - //---------------------------------------------------------------------------- - /// @brief Appends a given pass as a subpass. - /// - EntityPass* AddSubpass(std::unique_ptr pass); - - EntityPass* GetSuperpass() const; - - bool Render(ContentContext& renderer, - const RenderTarget& render_target) const; - - /// @brief Iterate all elements (entities and subpasses) in this pass, - /// recursively including elements of child passes. The iteration - /// order is depth-first. Whenever a subpass elements is encountered, - /// it's included in the stream before its children. - void IterateAllElements(const std::function& iterator); - - void IterateAllElements( - const std::function& iterator) const; - - //---------------------------------------------------------------------------- - /// @brief Iterate all entities in this pass, recursively including entities - /// of child passes. The iteration order is depth-first. - /// - void IterateAllEntities(const std::function& iterator); - - //---------------------------------------------------------------------------- - /// @brief Iterate all entities in this pass, recursively including entities - /// of child passes. The iteration order is depth-first and does not - /// allow modification of the entities. - /// - void IterateAllEntities( - const std::function& iterator) const; - - //---------------------------------------------------------------------------- - /// @brief Iterate entities in this pass up until the first subpass is found. - /// This is useful for limiting look-ahead optimizations. - /// - /// @return Returns whether a subpass was encountered. - /// - bool IterateUntilSubpass(const std::function& iterator); - - //---------------------------------------------------------------------------- - /// @brief Return the number of elements on this pass. - /// - size_t GetElementCount() const; - - void SetTransform(Matrix transform); - - void SetClipHeight(size_t clip_height); - - size_t GetClipHeight() const; - - void SetClipDepth(size_t clip_depth); - - uint32_t GetClipDepth() const; - - void SetBlendMode(BlendMode blend_mode); - - /// @brief Return the premultiplied clear color of the pass entities, if any. - std::optional GetClearColor(ISize size = ISize::Infinite()) const; - - /// @brief Return the premultiplied clear color of the pass entities. - /// - /// If the entity pass has no clear color, this will return transparent black. - Color GetClearColorOrDefault(ISize size = ISize::Infinite()) const; - - void SetBackdropFilter(BackdropFilterProc proc); - - int32_t GetRequiredMipCount() const { return required_mip_count_; } - - void SetRequiredMipCount(int32_t mip_count) { - required_mip_count_ = mip_count; - } - - private: - struct EntityResult { - enum Status { - /// The entity was successfully resolved and can be rendered. - kSuccess, - /// An unexpected rendering error occurred while resolving the Entity. - kFailure, - /// The entity should be skipped because rendering it will contribute - /// nothing to the frame. - kSkip, - }; - - /// @brief The resulting entity that should be rendered. - Entity entity; - /// @brief This is set to `false` if there was an unexpected rendering - /// error while resolving the Entity. - Status status = kFailure; - - static EntityResult Success(Entity e) { return {std::move(e), kSuccess}; } - static EntityResult Failure() { return {{}, kFailure}; } - static EntityResult Skip() { return {{}, kSkip}; } - }; - - bool RenderElement(Entity& element_entity, - size_t clip_height_floor, - InlinePassContext& pass_context, - int32_t pass_depth, - ContentContext& renderer, - EntityPassClipStack& clip_coverage_stack, - Point global_pass_position) const; - - EntityResult GetEntityForElement(const EntityPass::Element& element, - ContentContext& renderer, - InlinePassContext& pass_context, - ISize root_pass_size, - Point global_pass_position, - uint32_t pass_depth, - EntityPassClipStack& clip_coverage_stack, - size_t clip_height_floor) const; - - //---------------------------------------------------------------------------- - /// @brief OnRender is the internal command recording routine for - /// `EntityPass`. Its job is to walk through each `Element` which - /// was appended to the scene (either an `Entity` via `AddEntity()` - /// or a child `EntityPass` via `AddSubpass()`) and render them to - /// the given `pass_target`. - /// @param[in] renderer The Contents context, which manages - /// pipeline state. - /// @param[in] root_pass_size The size of the texture being - /// rendered into at the root of the - /// `EntityPass` tree. This is the size - /// of the `RenderTarget` color - /// attachment passed to the public - /// `EntityPass::Render` method. - /// @param[out] pass_target Stores the render target that should - /// be used for rendering. - /// @param[in] global_pass_position The position that this `EntityPass` - /// will be drawn to the parent pass - /// relative to the root pass origin. - /// Used for offsetting drawn `Element`s, - /// whose origins are all in root - /// pass/screen space, - /// @param[in] local_pass_position The position that this `EntityPass` - /// will be drawn to the parent pass - /// relative to the parent pass origin. - /// Used for positioning backdrop - /// filters. - /// @param[in] pass_depth The tree depth of the `EntityPass` at - /// render time. Only used for labeling - /// and debugging purposes. This can vary - /// depending on whether passes are - /// collapsed or not. - /// @param[in] clip_coverage_stack A global stack of coverage rectangles - /// for the clip buffer at each depth. - /// Higher depths are more restrictive. - /// Used to cull Elements that we - /// know won't result in a visible - /// change. - /// @param[in] clip_height_floor The clip depth that a value of - /// zero corresponds to in the given - /// `pass_target` clip buffer. - /// When new `pass_target`s are created - /// for subpasses, their clip buffers are - /// initialized at zero, and so this - /// value is used to offset Entity clip - /// depths to match the clip buffer. - /// @param[in] backdrop_filter_contents Optional. Is supplied, this contents - /// is rendered prior to anything else in - /// the `EntityPass`, offset by the - /// `local_pass_position`. - /// @param[in] collapsed_parent_pass Optional. If supplied, this - /// `InlinePassContext` state is used to - /// begin rendering elements instead of - /// creating a new `RenderPass`. This - /// "collapses" the Elements into the - /// parent pass. - /// - bool OnRender(ContentContext& renderer, - ISize root_pass_size, - EntityPassTarget& pass_target, - Point global_pass_position, - Point local_pass_position, - uint32_t pass_depth, - EntityPassClipStack& clip_coverage_stack, - size_t clip_height_floor = 0, - std::shared_ptr backdrop_filter_contents = nullptr, - const std::optional& - collapsed_parent_pass = std::nullopt) const; - - /// The list of renderable items in the scene. Each of these items is - /// evaluated and recorded to an `EntityPassTarget` by the `OnRender` method. - std::vector elements_; - - DrawOrderResolver draw_order_resolver_; - - /// The stack of currently active clips (during Aiks recording time). Each - /// entry is an index into the `elements_` list. The depth value of a clip - /// is the max of all the entities it affects, so assignment of the depth - /// value is deferred until clip restore or end of the EntityPass. - std::vector active_clips_; - - EntityPass* superpass_ = nullptr; - Matrix transform_; - size_t clip_height_ = 0u; - uint32_t clip_depth_ = 1u; - BlendMode blend_mode_ = BlendMode::kSourceOver; - bool flood_clip_ = false; - std::optional bounds_limit_; - int32_t required_mip_count_ = 1; - - /// These values indicate whether something has been added to the EntityPass - /// that requires reading from the backdrop texture. Currently, this can - /// happen in the following scenarios: - /// 1. An entity with an "advanced blend" is added to the pass. - /// 2. A subpass with a backdrop filter is added to the pass. - /// These are tracked as separate values because we may ignore - /// `blend_reads_from_pass_texture_` if the device supports framebuffer - /// based advanced blends. - bool advanced_blend_reads_from_pass_texture_ = false; - bool backdrop_filter_reads_from_pass_texture_ = false; - - bool DoesBackdropGetRead(ContentContext& renderer) const; - - BackdropFilterProc backdrop_filter_proc_ = nullptr; - - std::shared_ptr delegate_ = - EntityPassDelegate::MakeDefault(); - - EntityPass(const EntityPass&) = delete; - - EntityPass& operator=(const EntityPass&) = delete; -}; - -} // namespace impeller - -#endif // FLUTTER_IMPELLER_ENTITY_ENTITY_PASS_H_ diff --git a/impeller/entity/entity_pass_delegate.cc b/impeller/entity/entity_pass_delegate.cc deleted file mode 100644 index 01d75c1924bb0..0000000000000 --- a/impeller/entity/entity_pass_delegate.cc +++ /dev/null @@ -1,55 +0,0 @@ -// 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 "impeller/entity/entity_pass_delegate.h" -#include "impeller/entity/entity_pass.h" - -namespace impeller { - -EntityPassDelegate::EntityPassDelegate() = default; - -EntityPassDelegate::~EntityPassDelegate() = default; - -class DefaultEntityPassDelegate final : public EntityPassDelegate { - public: - DefaultEntityPassDelegate() = default; - - // |EntityPassDelegate| - ~DefaultEntityPassDelegate() override = default; - - // |EntityPassDelegate| - bool CanElide() override { return false; } - - // |EntityPassDelegate| - bool CanCollapseIntoParentPass(EntityPass* entity_pass) override { - return true; - } - - // |EntityPassDelegate| - std::shared_ptr CreateContentsForSubpassTarget( - std::shared_ptr target, - const Matrix& effect_transform) override { - // Not possible since this pass always collapses into its parent. - FML_UNREACHABLE(); - } - - // |EntityPassDelgate| - std::shared_ptr WithImageFilter( - const FilterInput::Variant& input, - const Matrix& effect_transform) const override { - return nullptr; - } - - private: - DefaultEntityPassDelegate(const DefaultEntityPassDelegate&) = delete; - - DefaultEntityPassDelegate& operator=(const DefaultEntityPassDelegate&) = - delete; -}; - -std::unique_ptr EntityPassDelegate::MakeDefault() { - return std::make_unique(); -} - -} // namespace impeller diff --git a/impeller/entity/entity_pass_delegate.h b/impeller/entity/entity_pass_delegate.h deleted file mode 100644 index 04b96ba00782f..0000000000000 --- a/impeller/entity/entity_pass_delegate.h +++ /dev/null @@ -1,49 +0,0 @@ -// 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. - -#ifndef FLUTTER_IMPELLER_ENTITY_ENTITY_PASS_DELEGATE_H_ -#define FLUTTER_IMPELLER_ENTITY_ENTITY_PASS_DELEGATE_H_ - -#include - -#include "impeller/core/texture.h" -#include "impeller/entity/contents/contents.h" -#include "impeller/entity/contents/filters/filter_contents.h" -#include "impeller/entity/contents/filters/inputs/filter_input.h" - -namespace impeller { - -class EntityPass; - -class EntityPassDelegate { - public: - static std::unique_ptr MakeDefault(); - - EntityPassDelegate(); - - virtual ~EntityPassDelegate(); - - virtual bool CanElide() = 0; - - /// @brief Whether or not this entity pass can be collapsed into the parent. - /// If true, this method may modify the entities for the current pass. - virtual bool CanCollapseIntoParentPass(EntityPass* entity_pass) = 0; - - virtual std::shared_ptr CreateContentsForSubpassTarget( - std::shared_ptr target, - const Matrix& effect_transform) = 0; - - virtual std::shared_ptr WithImageFilter( - const FilterInput::Variant& input, - const Matrix& effect_transform) const = 0; - - private: - EntityPassDelegate(const EntityPassDelegate&) = delete; - - EntityPassDelegate& operator=(const EntityPassDelegate&) = delete; -}; - -} // namespace impeller - -#endif // FLUTTER_IMPELLER_ENTITY_ENTITY_PASS_DELEGATE_H_ diff --git a/impeller/entity/entity_playground.cc b/impeller/entity/entity_playground.cc index 294141b995821..9feb8c3fb6e7d 100644 --- a/impeller/entity/entity_playground.cc +++ b/impeller/entity/entity_playground.cc @@ -20,28 +20,6 @@ void EntityPlayground::SetTypographerContext( typographer_context_ = std::move(typographer_context); } -bool EntityPlayground::OpenPlaygroundHere(EntityPass& entity_pass) { - if (!switches_.enable_playground) { - return true; - } - - ContentContext content_context(GetContext(), typographer_context_); - if (!content_context.IsValid()) { - return false; - } - - // Resolve any lingering tracked clips by assigning an arbitrarily high - // number. The number to assign just needs to be at least as high as larger - // any previously assigned clip depth in the scene. Normally, Aiks handles - // this correctly when wrapping up the base pass as an `impeller::Picture`. - entity_pass.PopAllClips(99999); - - auto callback = [&](RenderTarget& render_target) -> bool { - return entity_pass.Render(content_context, render_target); - }; - return Playground::OpenPlaygroundHere(callback); -} - std::shared_ptr EntityPlayground::GetContentContext() const { return std::make_shared(GetContext(), typographer_context_); } diff --git a/impeller/entity/entity_playground.h b/impeller/entity/entity_playground.h index ac2f8a432a7bd..6e23102de5642 100644 --- a/impeller/entity/entity_playground.h +++ b/impeller/entity/entity_playground.h @@ -9,7 +9,6 @@ #include "impeller/entity/contents/content_context.h" #include "impeller/entity/entity.h" -#include "impeller/entity/entity_pass.h" #include "impeller/typographer/typographer_context.h" namespace impeller { @@ -28,8 +27,6 @@ class EntityPlayground : public PlaygroundTest { bool OpenPlaygroundHere(Entity entity); - bool OpenPlaygroundHere(EntityPass& entity_pass); - bool OpenPlaygroundHere(EntityPlaygroundCallback callback); std::shared_ptr GetContentContext() const; diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 34d5b35ae88f4..b3f9b9940b4f8 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -15,7 +15,6 @@ #include "impeller/core/device_buffer.h" #include "impeller/core/formats.h" #include "impeller/core/texture_descriptor.h" -#include "impeller/entity/contents/atlas_contents.h" #include "impeller/entity/contents/clip_contents.h" #include "impeller/entity/contents/conical_gradient_contents.h" #include "impeller/entity/contents/content_context.h" @@ -29,18 +28,15 @@ #include "impeller/entity/contents/runtime_effect_contents.h" #include "impeller/entity/contents/solid_color_contents.h" #include "impeller/entity/contents/solid_rrect_blur_contents.h" +#include "impeller/entity/contents/sweep_gradient_contents.h" #include "impeller/entity/contents/text_contents.h" #include "impeller/entity/contents/texture_contents.h" #include "impeller/entity/contents/tiled_texture_contents.h" #include "impeller/entity/entity.h" -#include "impeller/entity/entity_pass.h" -#include "impeller/entity/entity_pass_delegate.h" #include "impeller/entity/entity_playground.h" #include "impeller/entity/geometry/geometry.h" -#include "impeller/entity/geometry/point_field_geometry.h" #include "impeller/entity/geometry/stroke_path_geometry.h" #include "impeller/entity/geometry/superellipse_geometry.h" -#include "impeller/entity/render_target_cache.h" #include "impeller/geometry/color.h" #include "impeller/geometry/geometry_asserts.h" #include "impeller/geometry/path_builder.h" @@ -74,77 +70,6 @@ TEST_P(EntityTest, CanCreateEntity) { ASSERT_TRUE(entity.GetTransform().IsIdentity()); } -class TestPassDelegate final : public EntityPassDelegate { - public: - explicit TestPassDelegate(bool collapse = false) : collapse_(collapse) {} - - // |EntityPassDelegate| - ~TestPassDelegate() override = default; - - // |EntityPassDelgate| - bool CanElide() override { return false; } - - // |EntityPassDelgate| - bool CanCollapseIntoParentPass(EntityPass* entity_pass) override { - return collapse_; - } - - // |EntityPassDelgate| - std::shared_ptr CreateContentsForSubpassTarget( - std::shared_ptr target, - const Matrix& transform) override { - return nullptr; - } - - // |EntityPassDelegate| - std::shared_ptr WithImageFilter( - const FilterInput::Variant& input, - const Matrix& effect_transform) const override { - return nullptr; - } - - private: - const std::optional coverage_; - const bool collapse_; -}; - -auto CreatePassWithRectPath( - Rect rect, - std::optional bounds_hint, - ContentBoundsPromise bounds_promise = ContentBoundsPromise::kUnknown, - bool collapse = false) { - auto subpass = std::make_unique(); - Entity entity; - entity.SetContents(SolidColorContents::Make( - PathBuilder{}.AddRect(rect).TakePath(), Color::Red())); - subpass->AddEntity(std::move(entity)); - subpass->SetDelegate(std::make_unique(collapse)); - subpass->SetBoundsLimit(bounds_hint); - return subpass; -} - -TEST_P(EntityTest, EntityPassCanMergeSubpassIntoParent) { - // Both a red and a blue box should appear if the pass merging has worked - // correctly. - - EntityPass pass; - auto subpass = CreatePassWithRectPath(Rect::MakeLTRB(0, 0, 100, 100), - Rect::MakeLTRB(50, 50, 150, 150), - ContentBoundsPromise::kUnknown, true); - pass.AddSubpass(std::move(subpass)); - - Entity entity; - entity.SetTransform(Matrix::MakeScale(GetContentScale())); - auto contents = std::make_unique(); - contents->SetGeometry(Geometry::MakeRect(Rect::MakeLTRB(100, 100, 200, 200))); - contents->SetColor(Color::Blue()); - entity.SetContents(std::move(contents)); - - pass.AddEntity(std::move(entity)); - - ASSERT_TRUE(OpenPlaygroundHere(pass)); -} - TEST_P(EntityTest, FilterCoverageRespectsCropRect) { auto image = CreateTextureForFixture("boston.jpg"); auto filter = ColorFilterContents::MakeBlend(BlendMode::kSoftLight, @@ -1369,76 +1294,6 @@ TEST_P(EntityTest, SolidFillCoverageIsCorrect) { } } -TEST_P(EntityTest, SolidFillShouldRenderIsCorrect) { - // No path. - { - auto fill = std::make_shared(); - fill->SetColor(Color::CornflowerBlue()); - ASSERT_FALSE(fill->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100}))); - ASSERT_FALSE( - fill->ShouldRender(Entity{}, Rect::MakeLTRB(-100, -100, -50, -50))); - } - - // With path. - { - auto fill = std::make_shared(); - fill->SetColor(Color::CornflowerBlue()); - fill->SetGeometry(Geometry::MakeFillPath( - PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 100, 100)).TakePath())); - ASSERT_TRUE(fill->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100}))); - ASSERT_FALSE( - fill->ShouldRender(Entity{}, Rect::MakeLTRB(-100, -100, -50, -50))); - } - - // With paint cover. - { - auto fill = std::make_shared(); - fill->SetColor(Color::CornflowerBlue()); - fill->SetGeometry(Geometry::MakeCover()); - ASSERT_TRUE(fill->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100}))); - ASSERT_TRUE( - fill->ShouldRender(Entity{}, Rect::MakeLTRB(-100, -100, -50, -50))); - } -} - -TEST_P(EntityTest, DoesNotCullEntitiesByDefault) { - auto fill = std::make_shared(); - fill->SetColor(Color::CornflowerBlue()); - fill->SetGeometry( - Geometry::MakeRect(Rect::MakeLTRB(-1000, -1000, -900, -900))); - - Entity entity; - entity.SetContents(fill); - - // Even though the entity is offscreen, this should still render because we do - // not compute the coverage intersection by default. - EXPECT_TRUE(entity.ShouldRender(Rect::MakeLTRB(0, 0, 100, 100))); -} - -TEST_P(EntityTest, ClipContentsShouldRenderIsCorrect) { - // For clip ops, `ShouldRender` should always return true. - - // Clip. - { - auto clip = std::make_shared(); - ASSERT_TRUE(clip->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100}))); - clip->SetGeometry(Geometry::MakeFillPath( - PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 100, 100)).TakePath())); - ASSERT_TRUE(clip->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100}))); - ASSERT_TRUE( - clip->ShouldRender(Entity{}, Rect::MakeLTRB(-100, -100, -50, -50))); - } - - // Clip restore. - { - auto restore = std::make_shared(); - ASSERT_TRUE( - restore->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100}))); - ASSERT_TRUE( - restore->ShouldRender(Entity{}, Rect::MakeLTRB(-100, -100, -50, -50))); - } -} - TEST_P(EntityTest, ClipContentsGetClipCoverageIsCorrect) { // Intersection: No stencil coverage, no geometry. { @@ -2037,7 +1892,6 @@ TEST_P(EntityTest, InheritOpacityTest) { // Texture contents can always accept opacity. auto texture_contents = std::make_shared(); texture_contents->SetOpacity(0.5); - ASSERT_TRUE(texture_contents->CanInheritOpacity(entity)); texture_contents->SetInheritedOpacity(0.5); ASSERT_EQ(texture_contents->GetOpacity(), 0.25); @@ -2051,8 +1905,6 @@ TEST_P(EntityTest, InheritOpacityTest) { Geometry::MakeRect(Rect::MakeLTRB(100, 100, 200, 200))); solid_color->SetColor(Color::Blue().WithAlpha(0.5)); - ASSERT_TRUE(solid_color->CanInheritOpacity(entity)); - solid_color->SetInheritedOpacity(0.5); ASSERT_EQ(solid_color->GetColor().alpha, 0.25); solid_color->SetInheritedOpacity(0.5); @@ -2065,20 +1917,10 @@ TEST_P(EntityTest, InheritOpacityTest) { Geometry::MakeRect(Rect::MakeLTRB(100, 100, 200, 200))); tiled_texture->SetOpacityFactor(0.5); - ASSERT_TRUE(tiled_texture->CanInheritOpacity(entity)); - tiled_texture->SetInheritedOpacity(0.5); ASSERT_EQ(tiled_texture->GetOpacityFactor(), 0.25); tiled_texture->SetInheritedOpacity(0.5); ASSERT_EQ(tiled_texture->GetOpacityFactor(), 0.25); - - // Clips and restores trivially accept opacity. - ASSERT_TRUE(ClipContents().CanInheritOpacity(entity)); - ASSERT_TRUE(ClipRestoreContents().CanInheritOpacity(entity)); - - // Runtime effect contents can't accept opacity. - auto runtime_effect = std::make_shared(); - ASSERT_FALSE(runtime_effect->CanInheritOpacity(entity)); } TEST_P(EntityTest, ColorFilterWithForegroundColorAdvancedBlend) { @@ -2332,56 +2174,6 @@ TEST_P(EntityTest, TextContentsCeilsGlyphScaleToDecimal) { ASSERT_EQ(TextFrame::RoundScaledFontSize(100000000.0f, 12), 48.0f); } -TEST_P(EntityTest, AdvancedBlendCoverageHintIsNotResetByEntityPass) { - if (GetContext()->GetCapabilities()->SupportsFramebufferFetch()) { - GTEST_SKIP() << "Backends that support framebuffer fetch dont use coverage " - "for advanced blends."; - } - - auto contents = std::make_shared(); - contents->SetGeometry(Geometry::MakeRect(Rect::MakeXYWH(100, 100, 100, 100))); - contents->SetColor(Color::Red()); - - Entity entity; - entity.SetTransform(Matrix::MakeScale(Vector3(2, 2, 1))); - entity.SetBlendMode(BlendMode::kColorBurn); - entity.SetContents(contents); - - auto coverage = entity.GetCoverage(); - EXPECT_TRUE(coverage.has_value()); - - auto pass = std::make_unique(); - std::shared_ptr render_target_allocator = - std::make_shared(GetContext()->GetResourceAllocator()); - auto stencil_config = RenderTarget::AttachmentConfig{ - .storage_mode = StorageMode::kDevicePrivate, - .load_action = LoadAction::kClear, - .store_action = StoreAction::kDontCare, - .clear_color = Color::BlackTransparent()}; - auto rt = render_target_allocator->CreateOffscreen( - *GetContext(), ISize::MakeWH(1000, 1000), - /*mip_count=*/1, "Offscreen", RenderTarget::kDefaultColorAttachmentConfig, - stencil_config); - auto content_context = ContentContext( - GetContext(), TypographerContextSkia::Make(), render_target_allocator); - pass->AddEntity(std::move(entity)); - - EXPECT_TRUE(pass->Render(content_context, rt)); - - auto contains_size = [&render_target_allocator](ISize size) -> bool { - return std::find_if(render_target_allocator->GetRenderTargetDataBegin(), - render_target_allocator->GetRenderTargetDataEnd(), - [&size](const auto& data) { - return data.config.size == size; - }) != render_target_allocator->GetRenderTargetDataEnd(); - }; - - EXPECT_TRUE(contains_size(ISize(1000, 1000))) - << "The root texture wasn't allocated"; - EXPECT_TRUE(contains_size(ISize(200, 200))) - << "The ColorBurned texture wasn't allocated (100x100 scales up 2x)"; -} - TEST_P(EntityTest, SpecializationConstantsAreAppliedToVariants) { auto content_context = GetContentContext(); @@ -2582,6 +2374,36 @@ TEST_P(EntityTest, DrawSuperEllipse) { ASSERT_TRUE(OpenPlaygroundHere(callback)); } +TEST_P(EntityTest, SolidColorApplyColorFilter) { + auto contents = SolidColorContents(); + contents.SetColor(Color::CornflowerBlue().WithAlpha(0.75)); + auto result = contents.ApplyColorFilter([](const Color& color) { + return color.Blend(Color::LimeGreen().WithAlpha(0.75), BlendMode::kScreen); + }); + ASSERT_TRUE(result); + ASSERT_COLOR_NEAR(contents.GetColor(), + Color(0.424452, 0.828743, 0.79105, 0.9375)); +} + +#define APPLY_COLOR_FILTER_GRADIENT_TEST(name) \ + TEST_P(EntityTest, name##GradientApplyColorFilter) { \ + auto contents = name##GradientContents(); \ + contents.SetColors({Color::CornflowerBlue().WithAlpha(0.75)}); \ + auto result = contents.ApplyColorFilter([](const Color& color) { \ + return color.Blend(Color::LimeGreen().WithAlpha(0.75), \ + BlendMode::kScreen); \ + }); \ + ASSERT_TRUE(result); \ + \ + std::vector expected = {Color(0.433247, 0.879523, 0.825324, 0.75)}; \ + ASSERT_COLORS_NEAR(contents.GetColors(), expected); \ + } + +APPLY_COLOR_FILTER_GRADIENT_TEST(Linear); +APPLY_COLOR_FILTER_GRADIENT_TEST(Radial); +APPLY_COLOR_FILTER_GRADIENT_TEST(Conical); +APPLY_COLOR_FILTER_GRADIENT_TEST(Sweep); + } // namespace testing } // namespace impeller diff --git a/impeller/golden_tests/BUILD.gn b/impeller/golden_tests/BUILD.gn index 56713ad075638..3b3378e0801e7 100644 --- a/impeller/golden_tests/BUILD.gn +++ b/impeller/golden_tests/BUILD.gn @@ -13,6 +13,7 @@ impeller_component("golden_playground_test") { deps = [ ":digest", ":screenshot", + "//flutter/display_list", "//flutter/fml", "//flutter/impeller/aiks", "//flutter/impeller/display_list:display_list", @@ -66,6 +67,7 @@ if (is_mac) { deps = [ ":screenshot", + "//flutter/display_list", "//flutter/impeller/aiks", "//flutter/impeller/display_list", "//flutter/impeller/playground", @@ -87,6 +89,7 @@ if (is_mac) { deps = [ ":digest", ":metal_screenshot", + "//flutter/display_list", "//flutter/impeller/aiks", "//flutter/impeller/aiks:aiks_unittests_golden", "//flutter/impeller/display_list:display_list_unittests_golden", diff --git a/impeller/golden_tests/golden_playground_test_mac.cc b/impeller/golden_tests/golden_playground_test_mac.cc index 7294cead6cb65..95c6413d697aa 100644 --- a/impeller/golden_tests/golden_playground_test_mac.cc +++ b/impeller/golden_tests/golden_playground_test_mac.cc @@ -9,7 +9,6 @@ #include "display_list/display_list.h" #include "flutter/impeller/golden_tests/golden_playground_test.h" -#include "flutter/impeller/aiks/picture.h" #include "flutter/impeller/golden_tests/golden_digest.h" #include "flutter/impeller/golden_tests/metal_screenshotter.h" #include "flutter/impeller/golden_tests/vulkan_screenshotter.h" @@ -206,57 +205,17 @@ PlaygroundBackend GoldenPlaygroundTest::GetBackend() const { return GetParam(); } -bool GoldenPlaygroundTest::OpenPlaygroundHere(Picture picture) { - AiksContext renderer(GetContext(), typographer_context_); - - auto screenshot = pimpl_->screenshotter->MakeScreenshot(renderer, picture, - pimpl_->window_size); - return SaveScreenshot(std::move(screenshot)); -} - bool GoldenPlaygroundTest::OpenPlaygroundHere( const AiksDlPlaygroundCallback& callback) { AiksContext renderer(GetContext(), typographer_context_); std::unique_ptr screenshot; -#if EXPERIMENTAL_CANVAS for (int i = 0; i < 2; ++i) { auto display_list = callback(); auto texture = DisplayListToTexture(display_list, pimpl_->window_size, renderer); screenshot = pimpl_->screenshotter->MakeScreenshot(renderer, texture); } -#else - std::optional picture; - for (int i = 0; i < 2; ++i) { - auto display_list = callback(); - DlDispatcher dispatcher; - display_list->Dispatch(dispatcher); - Picture picture = dispatcher.EndRecordingAsPicture(); - - screenshot = pimpl_->screenshotter->MakeScreenshot(renderer, picture, - pimpl_->window_size); - } -#endif // EXPERIMENTAL_CANVAS - return SaveScreenshot(std::move(screenshot)); -} - -bool GoldenPlaygroundTest::OpenPlaygroundHere( - AiksPlaygroundCallback - callback) { // NOLINT(performance-unnecessary-value-param) - AiksContext renderer(GetContext(), typographer_context_); - - std::optional picture; - std::unique_ptr screenshot; - for (int i = 0; i < 2; ++i) { - picture = callback(renderer); - if (!picture.has_value()) { - return false; - } - screenshot = pimpl_->screenshotter->MakeScreenshot( - renderer, picture.value(), pimpl_->window_size); - } - return SaveScreenshot(std::move(screenshot)); } @@ -350,20 +309,8 @@ std::unique_ptr GoldenPlaygroundTest::MakeScreenshot( const sk_sp& list) { AiksContext renderer(GetContext(), typographer_context_); -#if EXPERIMENTAL_CANVAS return pimpl_->screenshotter->MakeScreenshot( renderer, DisplayListToTexture(list, pimpl_->window_size, renderer)); -#else - DlDispatcher dispatcher; - list->Dispatch(dispatcher); - Picture picture = dispatcher.EndRecordingAsPicture(); - - std::unique_ptr screenshot = - pimpl_->screenshotter->MakeScreenshot(renderer, picture, - pimpl_->window_size); - - return screenshot; -#endif // EXPERIMENTAL_CANVAS } } // namespace impeller diff --git a/impeller/golden_tests/golden_playground_test_stub.cc b/impeller/golden_tests/golden_playground_test_stub.cc index 022609c4f628a..fbe32a7fa471d 100644 --- a/impeller/golden_tests/golden_playground_test_stub.cc +++ b/impeller/golden_tests/golden_playground_test_stub.cc @@ -4,8 +4,6 @@ #include "flutter/impeller/golden_tests/golden_playground_test.h" -#include "impeller/aiks/picture.h" - namespace impeller { GoldenPlaygroundTest::GoldenPlaygroundTest() = default; @@ -27,16 +25,6 @@ PlaygroundBackend GoldenPlaygroundTest::GetBackend() const { return GetParam(); } -bool GoldenPlaygroundTest::OpenPlaygroundHere(Picture picture) { - return false; -} - -bool GoldenPlaygroundTest::OpenPlaygroundHere( - AiksPlaygroundCallback - callback) { // NOLINT(performance-unnecessary-value-param) - return false; -} - bool GoldenPlaygroundTest::OpenPlaygroundHere( const AiksDlPlaygroundCallback& callback) { return false; diff --git a/impeller/golden_tests/golden_tests.cc b/impeller/golden_tests/golden_tests.cc index 40a79cd173703..fbe0ccac86b39 100644 --- a/impeller/golden_tests/golden_tests.cc +++ b/impeller/golden_tests/golden_tests.cc @@ -2,19 +2,23 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "display_list/dl_color.h" +#include "display_list/dl_tile_mode.h" #include "gtest/gtest.h" #include +#include "display_list/dl_builder.h" +#include "display_list/dl_paint.h" +#include "display_list/effects/dl_color_source.h" #include "flutter/fml/platform/darwin/scoped_nsautorelease_pool.h" #include "impeller/aiks/aiks_context.h" -#include "impeller/aiks/canvas.h" -#include "impeller/entity/contents/conical_gradient_contents.h" -#include "impeller/geometry/path_builder.h" +#include "impeller/display_list/dl_dispatcher.h" // nogncheck #include "impeller/golden_tests/golden_digest.h" #include "impeller/golden_tests/metal_screenshot.h" #include "impeller/golden_tests/metal_screenshotter.h" #include "impeller/golden_tests/working_directory.h" +#include "third_party/skia/include/core/SkPaint.h" namespace impeller { namespace testing { @@ -70,21 +74,31 @@ class GoldenTests : public ::testing::Test { }; TEST_F(GoldenTests, ConicalGradient) { - Canvas canvas; - Paint paint; - - paint.color_source = ColorSource::MakeConicalGradient( - {125, 125}, 125, {Color(1.0, 0.0, 0.0, 1.0), Color(0.0, 0.0, 1.0, 1.0)}, - {0, 1}, {180, 180}, 0, Entity::TileMode::kClamp, {}); - - paint.stroke_width = 0.0; - paint.style = Paint::Style::kFill; - canvas.DrawRect(Rect::MakeXYWH(10, 10, 250, 250), paint); - Picture picture = canvas.EndRecordingAsPicture(); + flutter::DisplayListBuilder builder; + flutter::DlPaint paint; + paint.setDrawStyle(flutter::DlDrawStyle::kFill); + + flutter::DlColor colors[2] = {flutter::DlColor::RGBA(1, 0, 0, 1), + flutter::DlColor::RGBA(0, 0, 1, 1)}; + Scalar stops[2] = {0, 1}; + + paint.setColorSource(flutter::DlConicalGradientColorSource::MakeConical( + /*start_center=*/{125, 125}, // + /*start_radius=*/125, {180, 180}, // + /*end_radius=*/0, // + /*stop_count=*/2, // + /*colors=*/colors, // + /*stops=*/stops, // + /*tile_mode=*/flutter::DlTileMode::kClamp // + )); + + builder.DrawRect(SkRect::MakeXYWH(10, 10, 250, 250), paint); auto aiks_context = AiksContext(Screenshotter().GetPlayground().GetContext(), nullptr); - auto screenshot = Screenshotter().MakeScreenshot(aiks_context, picture); + auto screenshot = Screenshotter().MakeScreenshot( + aiks_context, + DisplayListToTexture(builder.Build(), {240, 240}, aiks_context)); ASSERT_TRUE(SaveScreenshot(std::move(screenshot))); } } // namespace testing diff --git a/impeller/golden_tests/metal_screenshotter.h b/impeller/golden_tests/metal_screenshotter.h index 57bd8fc11598f..a72df74ef353d 100644 --- a/impeller/golden_tests/metal_screenshotter.h +++ b/impeller/golden_tests/metal_screenshotter.h @@ -5,10 +5,10 @@ #ifndef FLUTTER_IMPELLER_GOLDEN_TESTS_METAL_SCREENSHOTTER_H_ #define FLUTTER_IMPELLER_GOLDEN_TESTS_METAL_SCREENSHOTTER_H_ -#include "flutter/impeller/aiks/picture.h" #include "flutter/impeller/golden_tests/metal_screenshot.h" #include "flutter/impeller/golden_tests/screenshotter.h" #include "flutter/impeller/playground/playground_impl.h" +#include "impeller/aiks/aiks_context.h" namespace impeller { namespace testing { @@ -19,12 +19,6 @@ class MetalScreenshotter : public Screenshotter { public: explicit MetalScreenshotter(bool enable_wide_gamut); - std::unique_ptr MakeScreenshot( - AiksContext& aiks_context, - const Picture& picture, - const ISize& size = {300, 300}, - bool scale_content = true) override; - std::unique_ptr MakeScreenshot( AiksContext& aiks_context, const std::shared_ptr texture) override; diff --git a/impeller/golden_tests/metal_screenshotter.mm b/impeller/golden_tests/metal_screenshotter.mm index e836e45a8d3c2..b09e2891eec0d 100644 --- a/impeller/golden_tests/metal_screenshotter.mm +++ b/impeller/golden_tests/metal_screenshotter.mm @@ -20,19 +20,6 @@ playground_ = PlaygroundImpl::Create(PlaygroundBackend::kMetal, switches); } -std::unique_ptr MetalScreenshotter::MakeScreenshot( - AiksContext& aiks_context, - const Picture& picture, - const ISize& size, - bool scale_content) { - Vector2 content_scale = - scale_content ? playground_->GetContentScale() : Vector2{1, 1}; - std::shared_ptr image = picture.ToImage( - aiks_context, - ISize(size.width * content_scale.x, size.height * content_scale.y)); - return MakeScreenshot(aiks_context, image); -} - std::unique_ptr MetalScreenshotter::MakeScreenshot( AiksContext& aiks_context, const std::shared_ptr texture) { diff --git a/impeller/golden_tests/screenshotter.h b/impeller/golden_tests/screenshotter.h index 1fbf478c8b572..88e8b89a22b42 100644 --- a/impeller/golden_tests/screenshotter.h +++ b/impeller/golden_tests/screenshotter.h @@ -5,9 +5,9 @@ #ifndef FLUTTER_IMPELLER_GOLDEN_TESTS_SCREENSHOTTER_H_ #define FLUTTER_IMPELLER_GOLDEN_TESTS_SCREENSHOTTER_H_ -#include "flutter/impeller/aiks/picture.h" #include "flutter/impeller/golden_tests/screenshot.h" #include "flutter/impeller/playground/playground_impl.h" +#include "impeller/aiks/aiks_context.h" namespace impeller { namespace testing { @@ -18,12 +18,6 @@ class Screenshotter { public: virtual ~Screenshotter() = default; - virtual std::unique_ptr MakeScreenshot( - AiksContext& aiks_context, - const Picture& picture, - const ISize& size = {300, 300}, - bool scale_content = true) = 0; - virtual std::unique_ptr MakeScreenshot( AiksContext& aiks_context, const std::shared_ptr texture) = 0; diff --git a/impeller/golden_tests/vulkan_screenshotter.h b/impeller/golden_tests/vulkan_screenshotter.h index 14347ee0ec3ce..8099661f62655 100644 --- a/impeller/golden_tests/vulkan_screenshotter.h +++ b/impeller/golden_tests/vulkan_screenshotter.h @@ -5,7 +5,6 @@ #ifndef FLUTTER_IMPELLER_GOLDEN_TESTS_VULKAN_SCREENSHOTTER_H_ #define FLUTTER_IMPELLER_GOLDEN_TESTS_VULKAN_SCREENSHOTTER_H_ -#include "flutter/impeller/aiks/picture.h" #include "flutter/impeller/golden_tests/metal_screenshot.h" #include "flutter/impeller/golden_tests/screenshotter.h" #include "flutter/impeller/playground/playground_impl.h" @@ -20,12 +19,6 @@ class VulkanScreenshotter : public Screenshotter { explicit VulkanScreenshotter( const std::unique_ptr& playground); - std::unique_ptr MakeScreenshot( - AiksContext& aiks_context, - const Picture& picture, - const ISize& size = {300, 300}, - bool scale_content = true) override; - std::unique_ptr MakeScreenshot( AiksContext& aiks_context, const std::shared_ptr texture) override; diff --git a/impeller/golden_tests/vulkan_screenshotter.mm b/impeller/golden_tests/vulkan_screenshotter.mm index 843db4b5d76fe..a13bf752c2159 100644 --- a/impeller/golden_tests/vulkan_screenshotter.mm +++ b/impeller/golden_tests/vulkan_screenshotter.mm @@ -105,19 +105,6 @@ CGImagePtr flipped_image(CGBitmapContextCreateImage(flipped_context.get()), FML_CHECK(playground_); } -std::unique_ptr VulkanScreenshotter::MakeScreenshot( - AiksContext& aiks_context, - const Picture& picture, - const ISize& size, - bool scale_content) { - Vector2 content_scale = - scale_content ? playground_->GetContentScale() : Vector2{1, 1}; - std::shared_ptr image = picture.ToImage( - aiks_context, - ISize(size.width * content_scale.x, size.height * content_scale.y)); - return ReadTexture(aiks_context.GetContext(), image); -} - std::unique_ptr VulkanScreenshotter::MakeScreenshot( AiksContext& aiks_context, const std::shared_ptr texture) { diff --git a/impeller/toolkit/interop/surface.cc b/impeller/toolkit/interop/surface.cc index d37fad66407f3..6982af36a6c2d 100644 --- a/impeller/toolkit/interop/surface.cc +++ b/impeller/toolkit/interop/surface.cc @@ -61,22 +61,9 @@ bool Surface::DrawDisplayList(const DisplayList& dl) const { const auto cull_rect = IRect::MakeSize(surface_->GetSize()); auto skia_cull_rect = SkIRect::MakeWH(cull_rect.GetWidth(), cull_rect.GetHeight()); - impeller::TextFrameDispatcher collector(content_context, impeller::Matrix{}, - Rect::MakeSize(surface_->GetSize())); - display_list->Dispatch(collector, skia_cull_rect); - impeller::ExperimentalDlDispatcher impeller_dispatcher( - content_context, // - render_target, // - display_list->root_has_backdrop_filter(), // - display_list->max_root_blend_mode(), // - cull_rect // - ); - display_list->Dispatch(impeller_dispatcher, skia_cull_rect); - impeller_dispatcher.FinishRecording(); - content_context.GetLazyGlyphAtlas()->ResetTextFrames(); - content_context.GetTransientsBuffer().Reset(); - return true; + return RenderToOnscreen(content_context, render_target, display_list, + skia_cull_rect, /*reset_host_buffer=*/true); } } // namespace impeller::interop diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index c187e5e31c9cc..eab9199142334 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -925,20 +925,14 @@ ScreenshotLayerTreeAsImageImpeller( RenderFrameForScreenshot(compositor_context, &builder, tree, nullptr, aiks_context); - std::shared_ptr texture; -#if EXPERIMENTAL_CANVAS - texture = impeller::DisplayListToTexture( + std::shared_ptr texture = impeller::DisplayListToTexture( builder.Build(), impeller::ISize(tree->frame_size().fWidth, tree->frame_size().fHeight), *aiks_context); -#else - impeller::DlDispatcher dispatcher; - builder.Build()->Dispatch(dispatcher); - const auto& picture = dispatcher.EndRecordingAsPicture(); - texture = picture.ToImage( - *aiks_context, - impeller::ISize(tree->frame_size().fWidth, tree->frame_size().fHeight)); -#endif // EXPERIMENTAL_CANVAS + if (!texture) { + FML_LOG(ERROR) << "Failed to render to texture"; + return {nullptr, Rasterizer::ScreenshotFormat::kUnknown}; + } impeller::DeviceBufferDescriptor buffer_desc; buffer_desc.storage_mode = impeller::StorageMode::kHostVisible; diff --git a/shell/common/snapshot_controller_impeller.cc b/shell/common/snapshot_controller_impeller.cc index 89583f9eafcc0..dcf0702dbca1e 100644 --- a/shell/common/snapshot_controller_impeller.cc +++ b/shell/common/snapshot_controller_impeller.cc @@ -46,65 +46,10 @@ sk_sp DoMakeRasterSnapshot( render_target_size.height *= scale_factor; } -#if EXPERIMENTAL_CANVAS - // Do not use the render target cache as the lifecycle of this texture - // will outlive a particular frame. - impeller::RenderTargetAllocator render_target_allocator = - impeller::RenderTargetAllocator( - context->GetContext()->GetResourceAllocator()); - impeller::RenderTarget target; - if (context->GetContext()->GetCapabilities()->SupportsOffscreenMSAA()) { - target = render_target_allocator.CreateOffscreenMSAA( - *context->GetContext(), // context - render_target_size, // size - /*mip_count=*/1, - "Picture Snapshot MSAA", // label - impeller::RenderTarget:: - kDefaultColorAttachmentConfigMSAA // color_attachment_config - ); - } else { - target = render_target_allocator.CreateOffscreen( - *context->GetContext(), // context - render_target_size, // size - /*mip_count=*/1, - "Picture Snapshot", // label - impeller::RenderTarget:: - kDefaultColorAttachmentConfig // color_attachment_config - ); - } - - impeller::TextFrameDispatcher collector( - context->GetContentContext(), // - impeller::Matrix(), // - impeller::Rect::MakeSize(render_target_size) // - ); - display_list->Dispatch(collector, SkIRect::MakeSize(size)); - impeller::ExperimentalDlDispatcher impeller_dispatcher( - context->GetContentContext(), target, - display_list->root_has_backdrop_filter(), - display_list->max_root_blend_mode(), - impeller::IRect::MakeSize(render_target_size)); - display_list->Dispatch(impeller_dispatcher, SkIRect::MakeSize(size)); - impeller_dispatcher.FinishRecording(); - - context->GetContentContext().GetLazyGlyphAtlas()->ResetTextFrames(); - - return impeller::DlImageImpeller::Make(target.GetRenderTargetTexture(), - DlImage::OwningContext::kRaster); -#else - impeller::DlDispatcher dispatcher; - display_list->Dispatch(dispatcher); - impeller::Picture picture = dispatcher.EndRecordingAsPicture(); - - std::shared_ptr image = - picture.ToImage(*context, render_target_size); - if (image) { - return impeller::DlImageImpeller::Make(image, - DlImage::OwningContext::kRaster); - } -#endif // EXPERIMENTAL_CANVAS - - return nullptr; + return impeller::DlImageImpeller::Make( + impeller::DisplayListToTexture(display_list, render_target_size, *context, + /*reset_host_buffer=*/false), + DlImage::OwningContext::kRaster); } sk_sp DoMakeRasterSnapshot( diff --git a/shell/gpu/gpu_surface_gl_impeller.cc b/shell/gpu/gpu_surface_gl_impeller.cc index 7542d040fb946..679bd3b2f92af 100644 --- a/shell/gpu/gpu_surface_gl_impeller.cc +++ b/shell/gpu/gpu_surface_gl_impeller.cc @@ -116,35 +116,14 @@ std::unique_ptr GPUSurfaceGLImpeller::AcquireFrame( } auto cull_rect = render_target.GetRenderTargetSize(); - impeller::Rect dl_cull_rect = impeller::Rect::MakeSize(cull_rect); - -#if EXPERIMENTAL_CANVAS - auto skia_cull_rect = SkIRect::MakeWH(cull_rect.width, cull_rect.height); - impeller::TextFrameDispatcher collector( - aiks_context->GetContentContext(), impeller::Matrix(), - impeller::Rect::MakeSize(cull_rect)); - display_list->Dispatch(collector, skia_cull_rect); - - impeller::ExperimentalDlDispatcher impeller_dispatcher( - aiks_context->GetContentContext(), render_target, - display_list->root_has_backdrop_filter(), - display_list->max_root_blend_mode(), - impeller::IRect::MakeSize(cull_rect)); - display_list->Dispatch(impeller_dispatcher, skia_cull_rect); - impeller_dispatcher.FinishRecording(); - aiks_context->GetContentContext().GetLazyGlyphAtlas()->ResetTextFrames(); - aiks_context->GetContentContext().GetTransientsBuffer().Reset(); + SkIRect sk_cull_rect = SkIRect::MakeWH(cull_rect.width, cull_rect.height); + return impeller::RenderToOnscreen(aiks_context->GetContentContext(), // + render_target, // + display_list, // + sk_cull_rect, // + /*reset_host_buffer=*/true // + ); return true; -#else - impeller::DlDispatcher impeller_dispatcher(dl_cull_rect); - display_list->Dispatch(impeller_dispatcher, - SkIRect::MakeWH(cull_rect.width, cull_rect.height)); - auto picture = impeller_dispatcher.EndRecordingAsPicture(); - - return aiks_context->Render(picture, render_target, - /*reset_host_buffer=*/true); - -#endif // EXPERIMENTAL_CANVAS }; return std::make_unique( diff --git a/shell/gpu/gpu_surface_metal_impeller.mm b/shell/gpu/gpu_surface_metal_impeller.mm index a842e50a7ebc8..ff765afc21d8d 100644 --- a/shell/gpu/gpu_surface_metal_impeller.mm +++ b/shell/gpu/gpu_surface_metal_impeller.mm @@ -166,43 +166,23 @@ impeller::IRect cull_rect = surface->coverage(); SkIRect sk_cull_rect = SkIRect::MakeWH(cull_rect.GetWidth(), cull_rect.GetHeight()); - - impeller::RenderTarget render_target = surface->GetTargetRenderPassDescriptor(); surface->SetFrameBoundary(surface_frame.submit_info().frame_boundary); - -#if EXPERIMENTAL_CANVAS - impeller::TextFrameDispatcher collector(aiks_context->GetContentContext(), // - impeller::Matrix(), // - impeller::Rect::MakeSize(cull_rect.GetSize()) // - ); - display_list->Dispatch(collector, sk_cull_rect); - - impeller::ExperimentalDlDispatcher impeller_dispatcher( - aiks_context->GetContentContext(), render_target, - display_list->root_has_backdrop_filter(), display_list->max_root_blend_mode(), - cull_rect); - display_list->Dispatch(impeller_dispatcher, sk_cull_rect); - impeller_dispatcher.FinishRecording(); - aiks_context->GetContentContext().GetLazyGlyphAtlas()->ResetTextFrames(); - aiks_context->GetContentContext().GetTransientsBuffer().Reset(); - - if (!surface->PreparePresent()) { + auto render_result = + impeller::RenderToOnscreen(aiks_context->GetContentContext(), // + surface->GetTargetRenderPassDescriptor(), // + display_list, // + sk_cull_rect, // + /*reset_host_buffer=*/true // + ); + if (!render_result) { return false; } - surface_frame.set_user_data(std::move(surface)); - return true; -#else - impeller::DlDispatcher impeller_dispatcher(cull_rect); - display_list->Dispatch(impeller_dispatcher, sk_cull_rect); - auto picture = impeller_dispatcher.EndRecordingAsPicture(); - auto result = aiks_context->Render(picture, render_target, /*reset_host_buffer=*/true); if (!surface->PreparePresent()) { return false; } surface_frame.set_user_data(std::move(surface)); - return result; -#endif + return true; }); SurfaceFrame::FramebufferInfo framebuffer_info; @@ -300,38 +280,20 @@ impeller::IRect cull_rect = surface->coverage(); SkIRect sk_cull_rect = SkIRect::MakeWH(cull_rect.GetWidth(), cull_rect.GetHeight()); -#if EXPERIMENTAL_CANVAS - impeller::TextFrameDispatcher collector(aiks_context->GetContentContext(), // - impeller::Matrix(), // - impeller::Rect::MakeSize(cull_rect.GetSize()) // - ); - display_list->Dispatch(collector, sk_cull_rect); - impeller::RenderTarget render_target = surface->GetTargetRenderPassDescriptor(); - impeller::ExperimentalDlDispatcher impeller_dispatcher( - aiks_context->GetContentContext(), render_target, - display_list->root_has_backdrop_filter(), display_list->max_root_blend_mode(), - cull_rect); - display_list->Dispatch(impeller_dispatcher, sk_cull_rect); - impeller_dispatcher.FinishRecording(); - aiks_context->GetContentContext().GetTransientsBuffer().Reset(); - aiks_context->GetContentContext().GetLazyGlyphAtlas()->ResetTextFrames(); - if (!surface->PreparePresent()) { - return false; - } - bool render_result = true; -#else - impeller::DlDispatcher impeller_dispatcher(cull_rect); - display_list->Dispatch(impeller_dispatcher, sk_cull_rect); - auto picture = impeller_dispatcher.EndRecordingAsPicture(); - - const impeller::RenderTarget& render_target = surface->GetTargetRenderPassDescriptor(); - bool render_result = - aiks_context->Render(picture, render_target, /*reset_host_buffer=*/true); -#endif + auto render_result = + impeller::RenderToOnscreen(aiks_context->GetContentContext(), // + surface->GetTargetRenderPassDescriptor(), // + display_list, // + sk_cull_rect, // + /*reset_host_buffer=*/true // + ); if (!render_result) { FML_LOG(ERROR) << "Failed to render Impeller frame"; return false; } + if (!surface->PreparePresent()) { + return false; + } return surface->PreparePresent(); }); diff --git a/shell/gpu/gpu_surface_vulkan_impeller.cc b/shell/gpu/gpu_surface_vulkan_impeller.cc index d04937edc6b81..99e4d1dfeebc3 100644 --- a/shell/gpu/gpu_surface_vulkan_impeller.cc +++ b/shell/gpu/gpu_surface_vulkan_impeller.cc @@ -80,32 +80,13 @@ std::unique_ptr GPUSurfaceVulkanImpeller::AcquireFrame( return false; } -#if EXPERIMENTAL_CANVAS - impeller::TextFrameDispatcher collector( - aiks_context->GetContentContext(), impeller::Matrix(), - impeller::Rect::MakeSize(cull_rect)); - display_list->Dispatch(collector, - SkIRect::MakeWH(cull_rect.width, cull_rect.height)); - impeller::ExperimentalDlDispatcher impeller_dispatcher( - aiks_context->GetContentContext(), render_target, - display_list->root_has_backdrop_filter(), - display_list->max_root_blend_mode(), - impeller::IRect::RoundOut(impeller::Rect::MakeSize(cull_rect))); - display_list->Dispatch(impeller_dispatcher, - SkIRect::MakeWH(cull_rect.width, cull_rect.height)); - impeller_dispatcher.FinishRecording(); - aiks_context->GetContentContext().GetLazyGlyphAtlas()->ResetTextFrames(); - aiks_context->GetContentContext().GetTransientsBuffer().Reset(); - return true; -#else - impeller::Rect dl_cull_rect = impeller::Rect::MakeSize(cull_rect); - impeller::DlDispatcher impeller_dispatcher(dl_cull_rect); - display_list->Dispatch(impeller_dispatcher, - SkIRect::MakeWH(cull_rect.width, cull_rect.height)); - auto picture = impeller_dispatcher.EndRecordingAsPicture(); - return aiks_context->Render(picture, render_target, - /*reset_host_buffer=*/true); -#endif + SkIRect sk_cull_rect = SkIRect::MakeWH(cull_rect.width, cull_rect.height); + return impeller::RenderToOnscreen(aiks_context->GetContentContext(), // + render_target, // + display_list, // + sk_cull_rect, // + /*reset_host_buffer=*/true // + ); }; return std::make_unique( diff --git a/shell/platform/embedder/embedder_external_view.cc b/shell/platform/embedder/embedder_external_view.cc index 9019a417f09e5..99ce249597568 100644 --- a/shell/platform/embedder/embedder_external_view.cc +++ b/shell/platform/embedder/embedder_external_view.cc @@ -12,8 +12,7 @@ #ifdef IMPELLER_SUPPORTS_RENDERING #include "impeller/display_list/dl_dispatcher.h" // nogncheck -#define ENABLE_EXPERIMENTAL_CANVAS false -#endif // IMPELLER_SUPPORTS_RENDERING +#endif // IMPELLER_SUPPORTS_RENDERING namespace flutter { @@ -129,34 +128,17 @@ bool EmbedderExternalView::Render(const EmbedderRenderTarget& render_target, slice_->render_into(&dl_builder); auto display_list = dl_builder.Build(); -#if EXPERIMENTAL_CANVAS auto cull_rect = impeller::IRect::MakeSize(impeller_target->GetRenderTargetSize()); SkIRect sk_cull_rect = SkIRect::MakeWH(cull_rect.GetWidth(), cull_rect.GetHeight()); - impeller::TextFrameDispatcher collector( - aiks_context->GetContentContext(), // - impeller::Matrix(), // - impeller::Rect::MakeSize(cull_rect.GetSize()) // + + return impeller::RenderToOnscreen(aiks_context->GetContentContext(), // + *impeller_target, // + display_list, // + sk_cull_rect, // + /*reset_host_buffer=*/true // ); - display_list->Dispatch(collector, sk_cull_rect); - - impeller::ExperimentalDlDispatcher impeller_dispatcher( - aiks_context->GetContentContext(), *impeller_target, - display_list->root_has_backdrop_filter(), - display_list->max_root_blend_mode(), cull_rect); - display_list->Dispatch(impeller_dispatcher, sk_cull_rect); - impeller_dispatcher.FinishRecording(); - aiks_context->GetContentContext().GetTransientsBuffer().Reset(); - aiks_context->GetContentContext().GetLazyGlyphAtlas()->ResetTextFrames(); - - return true; -#else - auto dispatcher = impeller::DlDispatcher(); - dispatcher.drawDisplayList(display_list, 1); - return aiks_context->Render(dispatcher.EndRecordingAsPicture(), - *impeller_target, /*reset_host_buffer=*/true); -#endif } #endif // IMPELLER_SUPPORTS_RENDERING diff --git a/testing/benchmark/generate_metrics.sh b/testing/benchmark/generate_metrics.sh index eddd11e9bfe43..bcd52e506aecd 100644 --- a/testing/benchmark/generate_metrics.sh +++ b/testing/benchmark/generate_metrics.sh @@ -19,4 +19,3 @@ ${ENGINE_PATH}/src/out/${VARIANT}/display_list_builder_benchmarks --benchmark_fo ${ENGINE_PATH}/src/out/${VARIANT}/display_list_region_benchmarks --benchmark_format=json > ${ENGINE_PATH}/src/out/${VARIANT}/display_list_region_benchmarks.json ${ENGINE_PATH}/src/out/${VARIANT}/display_list_transform_benchmarks --benchmark_format=json > ${ENGINE_PATH}/src/out/${VARIANT}/display_list_transform_benchmarks.json ${ENGINE_PATH}/src/out/${VARIANT}/geometry_benchmarks --benchmark_format=json > ${ENGINE_PATH}/src/out/${VARIANT}/geometry_benchmarks.json -${ENGINE_PATH}/src/out/${VARIANT}/canvas_benchmarks --benchmark_format=json > ${ENGINE_PATH}/src/out/${VARIANT}/canvas_benchmarks.json diff --git a/testing/benchmark/upload_metrics.sh b/testing/benchmark/upload_metrics.sh index 5d7a871a37c9f..ad9fb7e134fe3 100644 --- a/testing/benchmark/upload_metrics.sh +++ b/testing/benchmark/upload_metrics.sh @@ -63,5 +63,3 @@ cd "$SCRIPT_DIR" --json $ENGINE_PATH/src/out/${VARIANT}/display_list_transform_benchmarks.json "$@" "$DART" bin/parse_and_send.dart \ --json $ENGINE_PATH/src/out/${VARIANT}/geometry_benchmarks.json "$@" -"$DART" bin/parse_and_send.dart \ - --json $ENGINE_PATH/src/out/${VARIANT}/canvas_benchmarks.json "$@" diff --git a/testing/run_tests.py b/testing/run_tests.py index 0f27467683cbc..d66e4e98dc87a 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -590,8 +590,6 @@ def run_engine_benchmarks(build_dir, executable_filter): run_engine_executable(build_dir, 'geometry_benchmarks', executable_filter, icu_flags) - run_engine_executable(build_dir, 'canvas_benchmarks', executable_filter, icu_flags) - if is_linux(): run_engine_executable(build_dir, 'txt_benchmarks', executable_filter, icu_flags)