From 80fd1f7fbf0d03e1523fda58ab94e1724b4865b7 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Wed, 24 Jul 2024 16:10:11 -0700 Subject: [PATCH] [iOS] Switch to FlutterMetalLayer by default. (flutter/engine#54086) For this to work, we need to provide our own capture scope otherwise the default scope won't capture our commands. This is required as part of the work to switch to unmerged threads for PVs (https://github.com/flutter/engine/pull/53826), as I can confirm @knopp 's observations that the performance is much worse with the default CAMetalLayer. Fixes https://github.com/flutter/flutter/issues/140901 --- .../renderer/backend/metal/context_mtl.h | 39 +++++++++++++++++-- .../renderer/backend/metal/context_mtl.mm | 33 ++++++++++++++++ .../renderer/backend/metal/surface_mtl.h | 5 +++ .../renderer/backend/metal/surface_mtl.mm | 3 ++ .../shell/gpu/gpu_surface_metal_impeller.mm | 9 +++++ .../gpu_surface_metal_impeller_unittests.mm | 32 ++++++++++++++- .../ios/framework/Source/FlutterMetalLayer.mm | 7 ++-- 7 files changed, 120 insertions(+), 8 deletions(-) diff --git a/engine/src/flutter/impeller/renderer/backend/metal/context_mtl.h b/engine/src/flutter/impeller/renderer/backend/metal/context_mtl.h index d1669206f0..b09933c752 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/context_mtl.h +++ b/engine/src/flutter/impeller/renderer/backend/metal/context_mtl.h @@ -31,6 +31,36 @@ namespace impeller { +/// @brief Creates and manages a Metal capture scope that supports frame capture +/// when using the FlutterMetalLayer backed drawable. +class ImpellerMetalCaptureManager { + public: + /// @brief Construct a new capture manager from the provided Metal device. + explicit ImpellerMetalCaptureManager(id device); + + ~ImpellerMetalCaptureManager() = default; + + /// Whether or not the Impeller capture scope is active. + /// + /// This is distinct from whether or not there is a session recording the + /// capture. That can be checked with `[[MTLCaptureManager + /// sharedCaptureManager] isCapturing].` + bool CaptureScopeActive() const; + + /// @brief Begin a new capture scope, no-op if the scope has already started. + void StartCapture(); + + /// @brief End the current capture scope. + void FinishCapture(); + + private: + id current_capture_scope_; + bool scope_active_ = false; + + ImpellerMetalCaptureManager(const ImpellerMetalCaptureManager&) = default; + ImpellerMetalCaptureManager(ImpellerMetalCaptureManager&&) = delete; +}; + class ContextMTL final : public Context, public BackendCast, public std::enable_shared_from_this { @@ -101,6 +131,8 @@ class ContextMTL final : public Context, #ifdef IMPELLER_DEBUG std::shared_ptr GetGPUTracer() const; + + const std::shared_ptr GetCaptureManager() const; #endif // IMPELLER_DEBUG // |Context| @@ -125,12 +157,13 @@ class ContextMTL final : public Context, std::shared_ptr resource_allocator_; std::shared_ptr device_capabilities_; std::shared_ptr is_gpu_disabled_sync_switch_; -#ifdef IMPELLER_DEBUG - std::shared_ptr gpu_tracer_; -#endif // IMPELLER_DEBUG std::deque> tasks_awaiting_gpu_; std::unique_ptr sync_switch_observer_; std::shared_ptr command_queue_ip_; +#ifdef IMPELLER_DEBUG + std::shared_ptr gpu_tracer_; + std::shared_ptr capture_manager_; +#endif // IMPELLER_DEBUG bool is_valid_ = false; ContextMTL(id device, diff --git a/engine/src/flutter/impeller/renderer/backend/metal/context_mtl.mm b/engine/src/flutter/impeller/renderer/backend/metal/context_mtl.mm index 22882ea0bd..03c6ea0739 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/context_mtl.mm +++ b/engine/src/flutter/impeller/renderer/backend/metal/context_mtl.mm @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "impeller/renderer/backend/metal/context_mtl.h" +#include #include @@ -134,6 +135,7 @@ ContextMTL::ContextMTL( command_queue_ip_ = std::make_shared(); #ifdef IMPELLER_DEBUG gpu_tracer_ = std::make_shared(); + capture_manager_ = std::make_shared(device_); #endif // IMPELLER_DEBUG is_valid_ = true; } @@ -404,4 +406,35 @@ std::shared_ptr ContextMTL::GetCommandQueue() const { return command_queue_ip_; } +#ifdef IMPELLER_DEBUG +const std::shared_ptr +ContextMTL::GetCaptureManager() const { + return capture_manager_; +} +#endif // IMPELLER_DEBUG + +ImpellerMetalCaptureManager::ImpellerMetalCaptureManager(id device) { + current_capture_scope_ = [[MTLCaptureManager sharedCaptureManager] + newCaptureScopeWithDevice:device]; + [current_capture_scope_ setLabel:@"Impeller Frame"]; +} + +bool ImpellerMetalCaptureManager::CaptureScopeActive() const { + return scope_active_; +} + +void ImpellerMetalCaptureManager::StartCapture() { + if (scope_active_) { + return; + } + scope_active_ = true; + [current_capture_scope_ beginScope]; +} + +void ImpellerMetalCaptureManager::FinishCapture() { + FML_DCHECK(scope_active_); + [current_capture_scope_ endScope]; + scope_active_ = false; +} + } // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/metal/surface_mtl.h b/engine/src/flutter/impeller/renderer/backend/metal/surface_mtl.h index 5ddc6973d0..dd028266a4 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/surface_mtl.h +++ b/engine/src/flutter/impeller/renderer/backend/metal/surface_mtl.h @@ -61,6 +61,10 @@ class SurfaceMTL final : public Surface { // |Surface| bool Present() const override; + void SetFrameBoundary(bool frame_boundary) { + frame_boundary_ = frame_boundary; + } + private: std::weak_ptr context_; std::shared_ptr resolve_texture_; @@ -69,6 +73,7 @@ class SurfaceMTL final : public Surface { std::shared_ptr destination_texture_; bool requires_blit_ = false; std::optional clip_rect_; + bool frame_boundary_ = false; static bool ShouldPerformPartialRepaint(std::optional damage_rect); diff --git a/engine/src/flutter/impeller/renderer/backend/metal/surface_mtl.mm b/engine/src/flutter/impeller/renderer/backend/metal/surface_mtl.mm index 9219aca941..e8cd572c5f 100644 --- a/engine/src/flutter/impeller/renderer/backend/metal/surface_mtl.mm +++ b/engine/src/flutter/impeller/renderer/backend/metal/surface_mtl.mm @@ -231,6 +231,9 @@ bool SurfaceMTL::Present() const { #ifdef IMPELLER_DEBUG context->GetResourceAllocator()->DebugTraceMemoryStatistics(); + if (frame_boundary_) { + ContextMTL::Cast(context.get())->GetCaptureManager()->FinishCapture(); + } #endif // IMPELLER_DEBUG if (requires_blit_) { diff --git a/engine/src/flutter/shell/gpu/gpu_surface_metal_impeller.mm b/engine/src/flutter/shell/gpu/gpu_surface_metal_impeller.mm index bc9bfd78d2..e14ac5c4a2 100644 --- a/engine/src/flutter/shell/gpu/gpu_surface_metal_impeller.mm +++ b/engine/src/flutter/shell/gpu/gpu_surface_metal_impeller.mm @@ -106,6 +106,10 @@ std::unique_ptr GPUSurfaceMetalImpeller::AcquireFrameFromCAMetalLa last_texture_.reset([drawable.texture retain]); } +#ifdef IMPELLER_DEBUG + impeller::ContextMTL::Cast(*impeller_renderer_->GetContext()).GetCaptureManager()->StartCapture(); +#endif // IMPELLER_DEBUG + id last_texture = static_cast>(last_texture_); SurfaceFrame::SubmitCallback submit_callback = fml::MakeCopyable([damage = damage_, @@ -186,6 +190,7 @@ std::unique_ptr GPUSurfaceMetalImpeller::AcquireFrameFromCAMetalLa display_list->Dispatch(impeller_dispatcher, sk_cull_rect); auto picture = impeller_dispatcher.EndRecordingAsPicture(); const bool reset_host_buffer = surface_frame.submit_info().frame_boundary; + surface->SetFrameBoundary(surface_frame.submit_info().frame_boundary); return renderer->Render( std::move(surface), @@ -233,6 +238,10 @@ std::unique_ptr GPUSurfaceMetalImpeller::AcquireFrameFromMTLTextur last_texture_.reset([mtl_texture retain]); } +#ifdef IMPELLER_DEBUG + impeller::ContextMTL::Cast(*impeller_renderer_->GetContext()).GetCaptureManager()->StartCapture(); +#endif // IMPELLER_DEBUG + SurfaceFrame::SubmitCallback submit_callback = fml::MakeCopyable([disable_partial_repaint = disable_partial_repaint_, // damage = damage_, diff --git a/engine/src/flutter/shell/gpu/gpu_surface_metal_impeller_unittests.mm b/engine/src/flutter/shell/gpu/gpu_surface_metal_impeller_unittests.mm index 13ccbba3f6..a5927ff035 100644 --- a/engine/src/flutter/shell/gpu/gpu_surface_metal_impeller_unittests.mm +++ b/engine/src/flutter/shell/gpu/gpu_surface_metal_impeller_unittests.mm @@ -103,7 +103,7 @@ TEST(GPUSurfaceMetalImpeller, ResetHostBufferBasedOnFrameBoundary) { auto context = CreateImpellerContext(); std::unique_ptr surface = - std::make_unique(delegate.get(), CreateImpellerContext()); + std::make_unique(delegate.get(), context); ASSERT_TRUE(surface->IsValid()); @@ -124,5 +124,35 @@ TEST(GPUSurfaceMetalImpeller, ResetHostBufferBasedOnFrameBoundary) { EXPECT_EQ(host_buffer.GetStateForTest().current_frame, 1u); } +#ifdef IMPELLER_DEBUG +TEST(GPUSurfaceMetalImpeller, CreatesImpellerCaptureScope) { + auto delegate = std::make_shared(); + delegate->SetDevice(); + + auto context = CreateImpellerContext(); + + EXPECT_FALSE(context->GetCaptureManager()->CaptureScopeActive()); + + std::unique_ptr surface = + std::make_unique(delegate.get(), context); + auto frame_1 = surface->AcquireFrame(SkISize::Make(100, 100)); + frame_1->set_submit_info({.frame_boundary = false}); + + EXPECT_TRUE(context->GetCaptureManager()->CaptureScopeActive()); + + std::unique_ptr surface_2 = + std::make_unique(delegate.get(), context); + auto frame_2 = surface->AcquireFrame(SkISize::Make(100, 100)); + frame_2->set_submit_info({.frame_boundary = true}); + + EXPECT_TRUE(context->GetCaptureManager()->CaptureScopeActive()); + + ASSERT_TRUE(frame_1->Submit()); + EXPECT_TRUE(context->GetCaptureManager()->CaptureScopeActive()); + ASSERT_TRUE(frame_2->Submit()); + EXPECT_FALSE(context->GetCaptureManager()->CaptureScopeActive()); +} +#endif // IMPELLER_DEBUG + } // namespace testing } // namespace flutter diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm index 7a3a7d99e2..8a7551dca4 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.mm @@ -424,15 +424,14 @@ extern CFTimeInterval display_link_target; } + (BOOL)enabled { - static BOOL enabled = NO; + static BOOL enabled = YES; static BOOL didCheckInfoPlist = NO; if (!didCheckInfoPlist) { didCheckInfoPlist = YES; NSNumber* use_flutter_metal_layer = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FLTUseFlutterMetalLayer"]; - if (use_flutter_metal_layer != nil && [use_flutter_metal_layer boolValue]) { - enabled = YES; - FML_LOG(WARNING) << "Using FlutterMetalLayer. This is an experimental feature."; + if (use_flutter_metal_layer != nil && ![use_flutter_metal_layer boolValue]) { + enabled = NO; } } return enabled;