From c14a5fde0ab4e9dffc34139408d66fe7d60fbb02 Mon Sep 17 00:00:00 2001 From: Loic Sharma Date: Mon, 8 Aug 2022 14:34:21 -0700 Subject: [PATCH 1/4] Add embedder API for next frame callback --- shell/common/rasterizer.cc | 2 +- shell/platform/embedder/embedder.cc | 31 +++++++++++++++ shell/platform/embedder/embedder.h | 24 ++++++++++++ .../embedder/tests/embedder_unittests.cc | 38 +++++++++++++++++++ .../include/flutter/flutter_engine.h | 3 +- 5 files changed, 96 insertions(+), 2 deletions(-) diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index d121b90527fab..db5ef54055cc8 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -641,7 +641,7 @@ RasterStatus Rasterizer::DrawToSurfaceUnsafe( frame_timings_recorder.RecordRasterStart(fml::TimePoint::Now()); std::unique_ptr damage; - // when leaf layer tracing is enabled we wish to repaing the whole frame for + // when leaf layer tracing is enabled we wish to repaint the whole frame for // accurate performance metrics. if (frame->framebuffer_info().supports_partial_repaint && !layer_tree.is_leaf_layer_tracing_enabled()) { diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index d69507b5f4c24..b09c6f059dbb3 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -2683,6 +2683,36 @@ FlutterEngineResult FlutterEngineScheduleFrame(FLUTTER_API_SYMBOL(FlutterEngine) "Could not schedule frame."); } +FlutterEngineResult FlutterEngineSetNextFrameCallback( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + VoidCallback callback, + void* user_data) { + if (engine == nullptr) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); + } + + if (callback == nullptr) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, + "Next frame callback was null."); + } + + flutter::EmbedderEngine* embedder_engine = + reinterpret_cast(engine); + + fml::WeakPtr weakPlatformView = + embedder_engine->GetShell().GetPlatformView(); + + if (!weakPlatformView) { + return LOG_EMBEDDER_ERROR(kInternalInconsistency, + "Platform view unavailable."); + } + + weakPlatformView->SetNextFrameCallback( + [callback, user_data]() { callback(user_data); }); + + return kSuccess; +} + FlutterEngineResult FlutterEngineGetProcAddresses( FlutterEngineProcTable* table) { if (!table) { @@ -2734,6 +2764,7 @@ FlutterEngineResult FlutterEngineGetProcAddresses( FlutterEnginePostCallbackOnAllNativeThreads); SET_PROC(NotifyDisplayUpdate, FlutterEngineNotifyDisplayUpdate); SET_PROC(ScheduleFrame, FlutterEngineScheduleFrame); + SET_PROC(SetNextFrameCallback, FlutterEngineSetNextFrameCallback); #undef SET_PROC return kSuccess; diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 43fd5bd5b1cde..25a65c6e42135 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -2510,6 +2510,25 @@ FLUTTER_EXPORT FlutterEngineResult FlutterEngineScheduleFrame(FLUTTER_API_SYMBOL(FlutterEngine) engine); +//------------------------------------------------------------------------------ +/// @brief Schedule a callback to be called after the next frame is drawn. +/// This callback is made on an internal engine managed thread and +/// embedders must re-thread if necessary. Performing blocking calls +/// in this callback may introduce application jank. +/// +/// @param[in] engine A running engine instance. +/// @param[in] callback The callback to execute. +/// @param[in] user_data A baton passed by the engine to the callback. This +/// baton is not interpreted by the engine in any way. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineSetNextFrameCallback( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + VoidCallback callback, + void* user_data); + #endif // !FLUTTER_ENGINE_NO_PROTOTYPES // Typedefs for the function pointers in FlutterEngineProcTable. @@ -2628,6 +2647,10 @@ typedef FlutterEngineResult (*FlutterEngineNotifyDisplayUpdateFnPtr)( size_t display_count); typedef FlutterEngineResult (*FlutterEngineScheduleFrameFnPtr)( FLUTTER_API_SYMBOL(FlutterEngine) engine); +typedef FlutterEngineResult (*FlutterEngineSetNextFrameCallbackFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + VoidCallback callback, + void* user_data); /// Function-pointer-based versions of the APIs above. typedef struct { @@ -2673,6 +2696,7 @@ typedef struct { PostCallbackOnAllNativeThreads; FlutterEngineNotifyDisplayUpdateFnPtr NotifyDisplayUpdate; FlutterEngineScheduleFrameFnPtr ScheduleFrame; + FlutterEngineSetNextFrameCallbackFnPtr SetNextFrameCallback; } FlutterEngineProcTable; //------------------------------------------------------------------------------ diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index d2b0c96b3c99b..4d1ac722bb87b 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -2126,6 +2126,44 @@ TEST_F(EmbedderTest, CanScheduleFrame) { check_latch.Wait(); } +TEST_F(EmbedderTest, CanSetNextFrameCallback) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(); + builder.SetDartEntrypoint("empty_scene"); + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + + // Register the callback that is executed once the next frame is drawn. + fml::AutoResetWaitableEvent callback_latch; + VoidCallback callback = [](void* user_data) { + fml::AutoResetWaitableEvent* callback_latch = + static_cast(user_data); + + callback_latch->Signal(); + }; + + auto result = FlutterEngineSetNextFrameCallback(engine.get(), callback, + &callback_latch); + ASSERT_EQ(result, kSuccess); + + // Send a window metrics events so frames may be scheduled. + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + event.pixel_ratio = 1.0; + event.physical_view_inset_top = 0.0; + event.physical_view_inset_right = 0.0; + event.physical_view_inset_bottom = 0.0; + event.physical_view_inset_left = 0.0; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + callback_latch.Wait(); +} + #if defined(FML_OS_MACOSX) static void MockThreadConfigSetter(const fml::Thread::ThreadConfig& config) { diff --git a/shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h b/shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h index 464c233fa9bfa..5978684eb0b7e 100644 --- a/shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h +++ b/shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h @@ -22,7 +22,8 @@ namespace flutter { // // In the future, this will be the API surface used for all interactions with // the engine, rather than having them duplicated on FlutterViewController. -// For now it is only used in the rare where you need a headless Flutter engine. +// For now it is only used in the rare where case you need a headless Flutter +// engine. class FlutterEngine : public PluginRegistry { public: // Creates a new engine for running the given project. From 58c5b419c271bbf399421122d60dbbea9d4aad24 Mon Sep 17 00:00:00 2001 From: Loic Sharma Date: Mon, 8 Aug 2022 15:12:22 -0700 Subject: [PATCH 2/4] Use better Dart entry point --- shell/platform/embedder/tests/embedder_unittests.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index 4d1ac722bb87b..74c3782c037fc 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -2130,7 +2130,7 @@ TEST_F(EmbedderTest, CanSetNextFrameCallback) { auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetSoftwareRendererConfig(); - builder.SetDartEntrypoint("empty_scene"); + builder.SetDartEntrypoint("draw_solid_red"); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); From 5ed989b9efaa68ac72243d5de096b35a0d50a509 Mon Sep 17 00:00:00 2001 From: Loic Sharma Date: Mon, 8 Aug 2022 15:18:58 -0700 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=A4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../windows/client_wrapper/include/flutter/flutter_engine.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h b/shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h index 5978684eb0b7e..688361740ebc8 100644 --- a/shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h +++ b/shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h @@ -22,7 +22,7 @@ namespace flutter { // // In the future, this will be the API surface used for all interactions with // the engine, rather than having them duplicated on FlutterViewController. -// For now it is only used in the rare where case you need a headless Flutter +// For now it is only used in the rare case where you need a headless Flutter // engine. class FlutterEngine : public PluginRegistry { public: From cc5091a53e0a4acb82f3d172991c381021828612 Mon Sep 17 00:00:00 2001 From: Loic Sharma Date: Mon, 8 Aug 2022 15:29:40 -0700 Subject: [PATCH 4/4] Address feedback --- shell/platform/embedder/embedder.cc | 6 +++--- shell/platform/embedder/embedder.h | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index b09c6f059dbb3..0868901fce3d6 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -2699,15 +2699,15 @@ FlutterEngineResult FlutterEngineSetNextFrameCallback( flutter::EmbedderEngine* embedder_engine = reinterpret_cast(engine); - fml::WeakPtr weakPlatformView = + fml::WeakPtr weak_platform_view = embedder_engine->GetShell().GetPlatformView(); - if (!weakPlatformView) { + if (!weak_platform_view) { return LOG_EMBEDDER_ERROR(kInternalInconsistency, "Platform view unavailable."); } - weakPlatformView->SetNextFrameCallback( + weak_platform_view->SetNextFrameCallback( [callback, user_data]() { callback(user_data); }); return kSuccess; diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 25a65c6e42135..88a3014f0218f 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -2512,8 +2512,9 @@ FlutterEngineResult FlutterEngineScheduleFrame(FLUTTER_API_SYMBOL(FlutterEngine) //------------------------------------------------------------------------------ /// @brief Schedule a callback to be called after the next frame is drawn. -/// This callback is made on an internal engine managed thread and -/// embedders must re-thread if necessary. Performing blocking calls +/// This must be called from the platform thread. The callback is +/// executed only once from the raster thread; embedders must +/// re-thread if necessary. Performing blocking calls /// in this callback may introduce application jank. /// /// @param[in] engine A running engine instance.