[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
This commit is contained in:
Jonah Williams 2024-07-24 16:10:11 -07:00 committed by GitHub
parent 274cf5f243
commit 80fd1f7fbf
7 changed files with 120 additions and 8 deletions

View File

@ -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<MTLDevice> 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<MTLCaptureScope> current_capture_scope_;
bool scope_active_ = false;
ImpellerMetalCaptureManager(const ImpellerMetalCaptureManager&) = default;
ImpellerMetalCaptureManager(ImpellerMetalCaptureManager&&) = delete;
};
class ContextMTL final : public Context,
public BackendCast<ContextMTL, Context>,
public std::enable_shared_from_this<ContextMTL> {
@ -101,6 +131,8 @@ class ContextMTL final : public Context,
#ifdef IMPELLER_DEBUG
std::shared_ptr<GPUTracerMTL> GetGPUTracer() const;
const std::shared_ptr<ImpellerMetalCaptureManager> GetCaptureManager() const;
#endif // IMPELLER_DEBUG
// |Context|
@ -125,12 +157,13 @@ class ContextMTL final : public Context,
std::shared_ptr<AllocatorMTL> resource_allocator_;
std::shared_ptr<const Capabilities> device_capabilities_;
std::shared_ptr<const fml::SyncSwitch> is_gpu_disabled_sync_switch_;
#ifdef IMPELLER_DEBUG
std::shared_ptr<GPUTracerMTL> gpu_tracer_;
#endif // IMPELLER_DEBUG
std::deque<std::function<void()>> tasks_awaiting_gpu_;
std::unique_ptr<SyncSwitchObserver> sync_switch_observer_;
std::shared_ptr<CommandQueue> command_queue_ip_;
#ifdef IMPELLER_DEBUG
std::shared_ptr<GPUTracerMTL> gpu_tracer_;
std::shared_ptr<ImpellerMetalCaptureManager> capture_manager_;
#endif // IMPELLER_DEBUG
bool is_valid_ = false;
ContextMTL(id<MTLDevice> device,

View File

@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "impeller/renderer/backend/metal/context_mtl.h"
#include <Metal/Metal.h>
#include <memory>
@ -134,6 +135,7 @@ ContextMTL::ContextMTL(
command_queue_ip_ = std::make_shared<CommandQueue>();
#ifdef IMPELLER_DEBUG
gpu_tracer_ = std::make_shared<GPUTracerMTL>();
capture_manager_ = std::make_shared<ImpellerMetalCaptureManager>(device_);
#endif // IMPELLER_DEBUG
is_valid_ = true;
}
@ -404,4 +406,35 @@ std::shared_ptr<CommandQueue> ContextMTL::GetCommandQueue() const {
return command_queue_ip_;
}
#ifdef IMPELLER_DEBUG
const std::shared_ptr<ImpellerMetalCaptureManager>
ContextMTL::GetCaptureManager() const {
return capture_manager_;
}
#endif // IMPELLER_DEBUG
ImpellerMetalCaptureManager::ImpellerMetalCaptureManager(id<MTLDevice> 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

View File

@ -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> context_;
std::shared_ptr<Texture> resolve_texture_;
@ -69,6 +73,7 @@ class SurfaceMTL final : public Surface {
std::shared_ptr<Texture> destination_texture_;
bool requires_blit_ = false;
std::optional<IRect> clip_rect_;
bool frame_boundary_ = false;
static bool ShouldPerformPartialRepaint(std::optional<IRect> damage_rect);

View File

@ -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_) {

View File

@ -106,6 +106,10 @@ std::unique_ptr<SurfaceFrame> GPUSurfaceMetalImpeller::AcquireFrameFromCAMetalLa
last_texture_.reset([drawable.texture retain]);
}
#ifdef IMPELLER_DEBUG
impeller::ContextMTL::Cast(*impeller_renderer_->GetContext()).GetCaptureManager()->StartCapture();
#endif // IMPELLER_DEBUG
id<MTLTexture> last_texture = static_cast<id<MTLTexture>>(last_texture_);
SurfaceFrame::SubmitCallback submit_callback =
fml::MakeCopyable([damage = damage_,
@ -186,6 +190,7 @@ std::unique_ptr<SurfaceFrame> 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<SurfaceFrame> 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_,

View File

@ -103,7 +103,7 @@ TEST(GPUSurfaceMetalImpeller, ResetHostBufferBasedOnFrameBoundary) {
auto context = CreateImpellerContext();
std::unique_ptr<Surface> surface =
std::make_unique<GPUSurfaceMetalImpeller>(delegate.get(), CreateImpellerContext());
std::make_unique<GPUSurfaceMetalImpeller>(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<TestGPUSurfaceMetalDelegate>();
delegate->SetDevice();
auto context = CreateImpellerContext();
EXPECT_FALSE(context->GetCaptureManager()->CaptureScopeActive());
std::unique_ptr<Surface> surface =
std::make_unique<GPUSurfaceMetalImpeller>(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> surface_2 =
std::make_unique<GPUSurfaceMetalImpeller>(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

View File

@ -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;