diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 9e05118e041a0..4b32abab211b7 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -599,6 +599,9 @@ FILE: ../../../flutter/shell/common/animator_unittests.cc FILE: ../../../flutter/shell/common/canvas_spy.cc FILE: ../../../flutter/shell/common/canvas_spy.h FILE: ../../../flutter/shell/common/canvas_spy_unittests.cc +FILE: ../../../flutter/shell/common/display.h +FILE: ../../../flutter/shell/common/display_manager.cc +FILE: ../../../flutter/shell/common/display_manager.h FILE: ../../../flutter/shell/common/engine.cc FILE: ../../../flutter/shell/common/engine.h FILE: ../../../flutter/shell/common/engine_unittests.cc diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index a3093d4f44c58..ca69f2f353185 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -64,6 +64,9 @@ source_set("common") { "animator.h", "canvas_spy.cc", "canvas_spy.h", + "display.h", + "display_manager.cc", + "display_manager.h", "engine.cc", "engine.h", "isolate_configuration.cc", diff --git a/shell/common/animator.cc b/shell/common/animator.cc index cae6df1666a35..8cc8926ca4073 100644 --- a/shell/common/animator.cc +++ b/shell/common/animator.cc @@ -53,10 +53,6 @@ Animator::Animator(Delegate& delegate, Animator::~Animator() = default; -float Animator::GetDisplayRefreshRate() const { - return waiter_->GetDisplayRefreshRate(); -} - void Animator::Stop() { paused_ = true; } diff --git a/shell/common/animator.h b/shell/common/animator.h index a6508fe24fa1a..effc01111001d 100644 --- a/shell/common/animator.h +++ b/shell/common/animator.h @@ -47,8 +47,6 @@ class Animator final { ~Animator(); - float GetDisplayRefreshRate() const; - void RequestFrame(bool regenerate_layer_tree = true); void Render(std::unique_ptr layer_tree); diff --git a/shell/common/display.h b/shell/common/display.h new file mode 100644 index 0000000000000..2c7e0fa88ff73 --- /dev/null +++ b/shell/common/display.h @@ -0,0 +1,55 @@ +// 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_SHELL_COMMON_DISPLAY_H_ +#define FLUTTER_SHELL_COMMON_DISPLAY_H_ + +#include + +namespace flutter { + +/// Unique ID per display that is stable until the Flutter application restarts. +/// See also: `flutter::Display` +typedef uint64_t DisplayId; + +/// To be used when the display refresh rate is unknown. +static constexpr double kUnknownDisplayRefreshRate = 0; + +/// Display refers to a graphics hardware system consisting of a framebuffer, +/// typically a monitor or a screen. This class holds the various display +/// settings. +class Display { + public: + //------------------------------------------------------------------------------ + /// @brief Construct a new Display object in case where the display id of the + /// display is known. In cases where there is more than one display, every + /// display is expected to have a display id. + /// + Display(DisplayId display_id, double refresh_rate) + : display_id_(display_id), refresh_rate_(refresh_rate) {} + + //------------------------------------------------------------------------------ + /// @brief Construct a new Display object when there is only a single display. + /// When there are multiple displays, every display must have a display id. + /// + explicit Display(double refresh_rate) + : display_id_({}), refresh_rate_(refresh_rate) {} + + ~Display() = default; + + // Get the display's maximum refresh rate in the unit of frame per second. + // Return `kUnknownDisplayRefreshRate` if the refresh rate is unknown. + double GetRefreshRate() const { return refresh_rate_; } + + /// Returns the `DisplayId` of the display. + std::optional GetDisplayId() const { return display_id_; } + + private: + std::optional display_id_; + double refresh_rate_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_COMMON_DISPLAY_H_ diff --git a/shell/common/display_manager.cc b/shell/common/display_manager.cc new file mode 100644 index 0000000000000..a30c80b1a31e8 --- /dev/null +++ b/shell/common/display_manager.cc @@ -0,0 +1,49 @@ +// 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/shell/common/display_manager.h" + +#include "flutter/fml/logging.h" +#include "flutter/fml/macros.h" + +namespace flutter { + +DisplayManager::DisplayManager() = default; + +DisplayManager::~DisplayManager() = default; + +double DisplayManager::GetMainDisplayRefreshRate() const { + std::scoped_lock lock(displays_mutex_); + if (displays_.empty()) { + return kUnknownDisplayRefreshRate; + } else { + return displays_[0].GetRefreshRate(); + } +} + +void DisplayManager::HandleDisplayUpdates(DisplayUpdateType update_type, + std::vector displays) { + std::scoped_lock lock(displays_mutex_); + CheckDisplayConfiguration(displays); + switch (update_type) { + case DisplayUpdateType::kStartup: + FML_CHECK(displays_.empty()); + displays_ = displays; + return; + default: + FML_CHECK(false) << "Unknown DisplayUpdateType."; + } +} + +void DisplayManager::CheckDisplayConfiguration( + std::vector displays) const { + FML_CHECK(!displays.empty()); + if (displays.size() > 1) { + for (auto& display : displays) { + FML_CHECK(display.GetDisplayId().has_value()); + } + } +} + +} // namespace flutter diff --git a/shell/common/display_manager.h b/shell/common/display_manager.h new file mode 100644 index 0000000000000..aa4bbadbb8618 --- /dev/null +++ b/shell/common/display_manager.h @@ -0,0 +1,59 @@ +// 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_SHELL_COMMON_DISPLAY_MANAGER_H_ +#define FLUTTER_SHELL_COMMON_DISPLAY_MANAGER_H_ + +#include +#include + +#include "flutter/shell/common/display.h" + +namespace flutter { + +/// The update type parameter that is passed to +/// `DisplayManager::HandleDisplayUpdates`. +enum class DisplayUpdateType { + /// `flutter::Display`s that were active during start-up. A display is + /// considered active if: + /// 1. The frame buffer hardware is connected. + /// 2. The display is drawable, e.g. it isn't being mirrored from another + /// connected display or sleeping. + kStartup +}; + +/// Manages lifecycle of the connected displays. This class is thread-safe. +class DisplayManager { + public: + DisplayManager(); + + ~DisplayManager(); + + /// Returns the display refresh rate of the main display. In cases where there + /// is only one display connected, it will return that. We do not yet support + /// cases where there are multiple displays. + /// + /// When there are no registered displays, it returns + /// `kUnknownDisplayRefreshRate`. + double GetMainDisplayRefreshRate() const; + + /// Handles the display updates. + void HandleDisplayUpdates(DisplayUpdateType update_type, + std::vector displays); + + private: + /// Guards `displays_` vector. + mutable std::mutex displays_mutex_; + std::vector displays_; + + /// Checks that the provided display configuration is valid. Currently this + /// ensures that all the displays have an id in the case there are multiple + /// displays. In case where there is a single display, it is valid for the + /// display to not have an id. + void CheckDisplayConfiguration(std::vector displays) const; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_COMMON_DISPLAY_MANAGER_H_ diff --git a/shell/common/engine.cc b/shell/common/engine.cc index f7171b2fe6c1f..d2384c03579cf 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -97,10 +97,6 @@ Engine::Engine(Delegate& delegate, Engine::~Engine() = default; -float Engine::GetDisplayRefreshRate() const { - return animator_->GetDisplayRefreshRate(); -} - fml::WeakPtr Engine::GetWeakPtr() const { return weak_factory_.GetWeakPtr(); } diff --git a/shell/common/engine.h b/shell/common/engine.h index e4de94846e62f..b8d98aa766291 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -24,6 +24,7 @@ #include "flutter/runtime/runtime_controller.h" #include "flutter/runtime/runtime_delegate.h" #include "flutter/shell/common/animator.h" +#include "flutter/shell/common/display_manager.h" #include "flutter/shell/common/platform_view.h" #include "flutter/shell/common/pointer_data_dispatcher.h" #include "flutter/shell/common/rasterizer.h" @@ -328,30 +329,6 @@ class Engine final : public RuntimeDelegate, /// ~Engine() override; - //---------------------------------------------------------------------------- - /// @brief Gets the refresh rate in frames per second of the vsync waiter - /// used by the animator managed by this engine. This information - /// is purely advisory and is not used by any component. It is - /// only used by the tooling to visualize frame performance. - /// - /// @attention The display refresh rate is useless for frame scheduling - /// because it can vary and more accurate frame specific - /// information is given to the engine by the vsync waiter - /// already. However, this call is used by the tooling to ask very - /// high level questions about display refresh rate. For example, - /// "Is the display 60 or 120Hz?". This information is quite - /// unreliable (not available immediately on launch on some - /// platforms), variable and advisory. It must not be used by any - /// component that claims to use it to perform accurate frame - /// scheduling. - /// - /// @return The display refresh rate in frames per second. This may change - /// from frame to frame, throughout the lifecycle of the - /// application, and, may not be available immediately upon - /// application launch. - /// - float GetDisplayRefreshRate() const; - //---------------------------------------------------------------------------- /// @return The pointer to this instance of the engine. The engine may /// only be accessed safely on the UI task runner. diff --git a/shell/common/rasterizer.h b/shell/common/rasterizer.h index faf0f5ee9b895..b658f18e0674c 100644 --- a/shell/common/rasterizer.h +++ b/shell/common/rasterizer.h @@ -68,7 +68,9 @@ class Rasterizer final : public SnapshotDelegate { /// virtual void OnFrameRasterized(const FrameTiming& frame_timing) = 0; - /// Time limit for a smooth frame. See `Engine::GetDisplayRefreshRate`. + /// Time limit for a smooth frame. + /// + /// See: `DisplayManager::GetMainDisplayRefreshRate`. virtual fml::Milliseconds GetFrameBudget() = 0; /// Target time for the latest frame. See also `Shell::OnAnimatorBeginFrame` diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 6bd27c260e82d..322a8e0935cb4 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -333,6 +333,8 @@ Shell::Shell(DartVMRef vm, TaskRunners task_runners, Settings settings) FML_DCHECK(task_runners_.IsValid()); FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); + display_manager_ = std::make_unique(); + // Generate a WeakPtrFactory for use with the raster thread. This does not // need to wait on a latch because it can only ever be used from the raster // thread from this class, so we have ordering guarantees. @@ -574,14 +576,6 @@ bool Shell::Setup(std::unique_ptr platform_view, PersistentCache::GetCacheForProcess()->Purge(); } - // TODO(gw280): The WeakPtr here asserts that we are derefing it on the - // same thread as it was created on. Shell is constructed on the platform - // thread but we need to call into the Engine on the UI thread, so we need - // to use getUnsafe() here to avoid failing the assertion. - // - // https://github.com/flutter/flutter/issues/42947 - display_refresh_rate_ = weak_engine_.getUnsafe()->GetDisplayRefreshRate(); - return true; } @@ -1250,8 +1244,9 @@ void Shell::OnFrameRasterized(const FrameTiming& timing) { } fml::Milliseconds Shell::GetFrameBudget() { - if (display_refresh_rate_ > 0) { - return fml::RefreshRateToFrameBudget(display_refresh_rate_.load()); + double display_refresh_rate = display_manager_->GetMainDisplayRefreshRate(); + if (display_refresh_rate > 0) { + return fml::RefreshRateToFrameBudget(display_refresh_rate); } else { return fml::kDefaultFrameBudget; } @@ -1442,11 +1437,15 @@ bool Shell::OnServiceProtocolGetDisplayRefreshRate( FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); response->SetObject(); response->AddMember("type", "DisplayRefreshRate", response->GetAllocator()); - response->AddMember("fps", engine_->GetDisplayRefreshRate(), + response->AddMember("fps", display_manager_->GetMainDisplayRefreshRate(), response->GetAllocator()); return true; } +double Shell::GetMainDisplayRefreshRate() { + return display_manager_->GetMainDisplayRefreshRate(); +} + bool Shell::OnServiceProtocolGetSkSLs( const ServiceProtocol::Handler::ServiceProtocolMap& params, rapidjson::Document* response) { @@ -1608,4 +1607,9 @@ std::shared_ptr Shell::GetIsGpuDisabledSyncSwitch() const { return is_gpu_disabled_sync_switch_; } +void Shell::OnDisplayUpdates(DisplayUpdateType update_type, + std::vector displays) { + display_manager_->HandleDisplayUpdates(update_type, displays); +} + } // namespace flutter diff --git a/shell/common/shell.h b/shell/common/shell.h index 368e55116778b..74ad1c280e3ae 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -30,6 +30,7 @@ #include "flutter/runtime/dart_vm_lifecycle.h" #include "flutter/runtime/service_protocol.h" #include "flutter/shell/common/animator.h" +#include "flutter/shell/common/display_manager.h" #include "flutter/shell/common/engine.h" #include "flutter/shell/common/platform_view.h" #include "flutter/shell/common/rasterizer.h" @@ -369,6 +370,17 @@ class Shell final : public PlatformView::Delegate, /// DartVM* GetDartVM(); + //---------------------------------------------------------------------------- + /// @brief Notifies the display manager of the updates. + /// + void OnDisplayUpdates(DisplayUpdateType update_type, + std::vector displays); + + //---------------------------------------------------------------------------- + /// @brief Queries the `DisplayManager` for the main display refresh rate. + /// + double GetMainDisplayRefreshRate(); + private: using ServiceProtocolHandler = std::function unreported_timings_; - // A cache of `Engine::GetDisplayRefreshRate` (only callable in the UI thread) - // so we can access it from `Rasterizer` (in the raster thread). - // - // The atomic is for extra thread safety as this is written in the UI thread - // and read from the raster thread. - std::atomic display_refresh_rate_ = 0.0f; + /// Manages the displays. This class is thread safe, can be accessed from any + /// of the threads. + std::unique_ptr display_manager_; // protects expected_frame_size_ which is set on platform thread and read on // raster thread diff --git a/shell/common/vsync_waiter.cc b/shell/common/vsync_waiter.cc index 7947d42cf6125..1bd8fdfaae658 100644 --- a/shell/common/vsync_waiter.cc +++ b/shell/common/vsync_waiter.cc @@ -134,8 +134,4 @@ void VsyncWaiter::FireCallback(fml::TimePoint frame_start_time, } } -float VsyncWaiter::GetDisplayRefreshRate() const { - return kUnknownRefreshRateFPS; -} - } // namespace flutter diff --git a/shell/common/vsync_waiter.h b/shell/common/vsync_waiter.h index ab2d857415652..b248449268c28 100644 --- a/shell/common/vsync_waiter.h +++ b/shell/common/vsync_waiter.h @@ -30,12 +30,6 @@ class VsyncWaiter : public std::enable_shared_from_this { /// See also |PointerDataDispatcher::ScheduleSecondaryVsyncCallback|. void ScheduleSecondaryCallback(const fml::closure& callback); - static constexpr float kUnknownRefreshRateFPS = 0.0; - - // Get the display's maximum refresh rate in the unit of frame per second. - // Return kUnknownRefreshRateFPS if the refresh rate is unknown. - virtual float GetDisplayRefreshRate() const; - protected: // On some backends, the |FireCallback| needs to be made from a static C // method. diff --git a/shell/platform/android/android_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index 8e1448c13d89c..cc81849549f05 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/shell/platform/android/android_shell_holder.cc @@ -75,6 +75,8 @@ AndroidShellHolder::AndroidShellHolder( ); } weak_platform_view = platform_view_android->GetWeakPtr(); + shell.OnDisplayUpdates(DisplayUpdateType::kStartup, + {Display(jni_facade->GetDisplayRefreshRate())}); return platform_view_android; }; diff --git a/shell/platform/android/jni/jni_mock.h b/shell/platform/android/jni/jni_mock.h index f4310f6beeefc..65f790d972ee8 100644 --- a/shell/platform/android/jni/jni_mock.h +++ b/shell/platform/android/jni/jni_mock.h @@ -93,6 +93,8 @@ class JNIMock final : public PlatformViewAndroidJNI { FlutterViewComputePlatformResolvedLocale, (std::vector supported_locales_data), (override)); + + MOCK_METHOD(double, GetDisplayRefreshRate, (), (override)); }; } // namespace flutter diff --git a/shell/platform/android/jni/platform_view_android_jni.h b/shell/platform/android/jni/platform_view_android_jni.h index 7b58439bc5e36..e06079c1f5c4f 100644 --- a/shell/platform/android/jni/platform_view_android_jni.h +++ b/shell/platform/android/jni/platform_view_android_jni.h @@ -193,6 +193,8 @@ class PlatformViewAndroidJNI { virtual std::unique_ptr> FlutterViewComputePlatformResolvedLocale( std::vector supported_locales_data) = 0; + + virtual double GetDisplayRefreshRate() = 0; }; } // namespace flutter diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index e641e8bf919c1..3b8a61bf39c0c 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -1315,4 +1315,21 @@ PlatformViewAndroidJNIImpl::FlutterViewComputePlatformResolvedLocale( return out; } +double PlatformViewAndroidJNIImpl::GetDisplayRefreshRate() { + JNIEnv* env = fml::jni::AttachCurrentThread(); + + auto java_object = java_object_.get(env); + if (java_object.is_null()) { + return kUnknownDisplayRefreshRate; + } + + jclass clazz = env->GetObjectClass(java_object.obj()); + if (clazz == nullptr) { + return kUnknownDisplayRefreshRate; + } + + jfieldID fid = env->GetStaticFieldID(clazz, "refreshRateFPS", "F"); + return static_cast(env->GetStaticFloatField(clazz, fid)); +} + } // namespace flutter diff --git a/shell/platform/android/platform_view_android_jni_impl.h b/shell/platform/android/platform_view_android_jni_impl.h index e26c891960488..313a9ee921e6c 100644 --- a/shell/platform/android/platform_view_android_jni_impl.h +++ b/shell/platform/android/platform_view_android_jni_impl.h @@ -78,6 +78,8 @@ class PlatformViewAndroidJNIImpl final : public PlatformViewAndroidJNI { FlutterViewComputePlatformResolvedLocale( std::vector supported_locales_data) override; + double GetDisplayRefreshRate() override; + private: // Reference to FlutterJNI object. const fml::jni::JavaObjectWeakGlobalRef java_object_; diff --git a/shell/platform/android/vsync_waiter_android.cc b/shell/platform/android/vsync_waiter_android.cc index b38fd64548fba..2851eec5f58e5 100644 --- a/shell/platform/android/vsync_waiter_android.cc +++ b/shell/platform/android/vsync_waiter_android.cc @@ -38,19 +38,6 @@ void VsyncWaiterAndroid::AwaitVSync() { }); } -float VsyncWaiterAndroid::GetDisplayRefreshRate() const { - JNIEnv* env = fml::jni::AttachCurrentThread(); - if (g_vsync_waiter_class == nullptr) { - return kUnknownRefreshRateFPS; - } - jclass clazz = g_vsync_waiter_class->obj(); - if (clazz == nullptr) { - return kUnknownRefreshRateFPS; - } - jfieldID fid = env->GetStaticFieldID(clazz, "refreshRateFPS", "F"); - return env->GetStaticFloatField(clazz, fid); -} - // static void VsyncWaiterAndroid::OnNativeVsync(JNIEnv* env, jclass jcaller, diff --git a/shell/platform/android/vsync_waiter_android.h b/shell/platform/android/vsync_waiter_android.h index be086581bd506..96fcc8c0e16ec 100644 --- a/shell/platform/android/vsync_waiter_android.h +++ b/shell/platform/android/vsync_waiter_android.h @@ -22,8 +22,6 @@ class VsyncWaiterAndroid final : public VsyncWaiter { ~VsyncWaiterAndroid() override; - float GetDisplayRefreshRate() const override; - private: // |VsyncWaiter| void AwaitVSync() override; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 61966fd85be37..347b853270e89 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -27,6 +27,7 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/connection_collection.h" #import "flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/profiler_metrics_ios.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h" #import "flutter/shell/platform/darwin/ios/ios_surface.h" #import "flutter/shell/platform/darwin/ios/platform_view_ios.h" #import "flutter/shell/platform/darwin/ios/rendering_api_selection.h" @@ -539,6 +540,7 @@ - (BOOL)createShell:(NSString*)entrypoint } else { [self setupChannels]; [self onLocaleUpdated:nil]; + [self initializeDisplays]; if (!_platformViewsController) { _platformViewsController.reset(new flutter::FlutterPlatformViewsController()); } @@ -553,6 +555,12 @@ - (BOOL)createShell:(NSString*)entrypoint return _shell != nullptr; } +- (void)initializeDisplays { + double refresh_rate = [[[DisplayLinkManager alloc] init] displayRefreshRate]; + auto display = flutter::Display(refresh_rate); + _shell->OnDisplayUpdates(flutter::DisplayUpdateType::kStartup, {display}); +} + - (BOOL)run { return [self runWithEntrypoint:FlutterDefaultDartEntrypoint libraryURI:nil diff --git a/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h b/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h index 7cd01d2dfad5f..cd34590e11db0 100644 --- a/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h +++ b/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h @@ -10,7 +10,33 @@ #include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "flutter/shell/common/vsync_waiter.h" -@class VSyncClient; +@interface DisplayLinkManager : NSObject + +- (instancetype)init; + +//------------------------------------------------------------------------------ +/// @brief The display refresh rate used for reporting purposes. The engine does not care +/// about this for frame scheduling. It is only used by tools for instrumentation. The +/// engine uses the duration field of the link per frame for frame scheduling. +/// +/// @attention Do not use the this call in frame scheduling. It is only meant for reporting. +/// +/// @return The refresh rate in frames per second. +/// +- (double)displayRefreshRate; + +@end + +@interface VSyncClient : NSObject + +- (instancetype)initWithTaskRunner:(fml::RefPtr)task_runner + callback:(flutter::VsyncWaiter::Callback)callback; + +- (void)await; + +- (void)invalidate; + +@end namespace flutter { @@ -26,9 +52,6 @@ class VsyncWaiterIOS final : public VsyncWaiter { // |VsyncWaiter| void AwaitVSync() override; - // |VsyncWaiter| - float GetDisplayRefreshRate() const override; - FML_DISALLOW_COPY_AND_ASSIGN(VsyncWaiterIOS); }; diff --git a/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm b/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm index 8d5d8138d3fdb..e94fbd6063219 100644 --- a/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm +++ b/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm @@ -15,28 +15,6 @@ #include "flutter/fml/logging.h" #include "flutter/fml/trace_event.h" -@interface VSyncClient : NSObject - -- (instancetype)initWithTaskRunner:(fml::RefPtr)task_runner - callback:(flutter::VsyncWaiter::Callback)callback; - -- (void)await; - -- (void)invalidate; - -//------------------------------------------------------------------------------ -/// @brief The display refresh rate used for reporting purposes. The engine does not care -/// about this for frame scheduling. It is only used by tools for instrumentation. The -/// engine uses the duration field of the link per frame for frame scheduling. -/// -/// @attention Do not use the this call in frame scheduling. It is only meant for reporting. -/// -/// @return The refresh rate in frames per second. -/// -- (float)displayRefreshRate; - -@end - namespace flutter { VsyncWaiterIOS::VsyncWaiterIOS(flutter::TaskRunners task_runners) @@ -57,16 +35,11 @@ - (float)displayRefreshRate; [client_.get() await]; } -// |VsyncWaiter| -float VsyncWaiterIOS::GetDisplayRefreshRate() const { - return [client_.get() displayRefreshRate]; -} - } // namespace flutter @implementation VSyncClient { flutter::VsyncWaiter::Callback callback_; - fml::scoped_nsobject display_link_; + CADisplayLink* display_link_; } - (instancetype)initWithTaskRunner:(fml::RefPtr)task_runner @@ -75,14 +48,11 @@ - (instancetype)initWithTaskRunner:(fml::RefPtr)task_runner if (self) { callback_ = std::move(callback); - display_link_ = fml::scoped_nsobject { - [[CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLink:)] retain] - }; - display_link_.get().paused = YES; + display_link_ = [CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLink:)]; + display_link_.paused = YES; task_runner->PostTask([client = [self retain]]() { - [client->display_link_.get() addToRunLoop:[NSRunLoop currentRunLoop] - forMode:NSRunLoopCommonModes]; + [client->display_link_ addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; [client release]; }); } @@ -90,9 +60,52 @@ - (instancetype)initWithTaskRunner:(fml::RefPtr)task_runner return self; } -- (float)displayRefreshRate { +- (void)await { + display_link_.paused = NO; +} + +- (void)onDisplayLink:(CADisplayLink*)link { + TRACE_EVENT0("flutter", "VSYNC"); + + CFTimeInterval delay = CACurrentMediaTime() - link.timestamp; + fml::TimePoint frame_start_time = fml::TimePoint::Now() - fml::TimeDelta::FromSecondsF(delay); + fml::TimePoint frame_target_time = frame_start_time + fml::TimeDelta::FromSecondsF(link.duration); + + display_link_.paused = YES; + + callback_(frame_start_time, frame_target_time); +} + +- (void)invalidate { + [display_link_ invalidate]; +} + +- (void)dealloc { + [self invalidate]; + + [super dealloc]; +} + +@end + +@implementation DisplayLinkManager { + CADisplayLink* display_link_; +} + +- (instancetype)init { + self = [super init]; + + if (self) { + display_link_ = [CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLink:)]; + display_link_.paused = YES; + } + + return self; +} + +- (double)displayRefreshRate { if (@available(iOS 10.3, *)) { - auto preferredFPS = display_link_.get().preferredFramesPerSecond; // iOS 10.0 + auto preferredFPS = display_link_.preferredFramesPerSecond; // iOS 10.0 // From Docs: // The default value for preferredFramesPerSecond is 0. When this value is 0, the preferred @@ -109,29 +122,12 @@ - (float)displayRefreshRate { } } -- (void)await { - display_link_.get().paused = NO; -} - - (void)onDisplayLink:(CADisplayLink*)link { - TRACE_EVENT0("flutter", "VSYNC"); - - CFTimeInterval delay = CACurrentMediaTime() - link.timestamp; - fml::TimePoint frame_start_time = fml::TimePoint::Now() - fml::TimeDelta::FromSecondsF(delay); - fml::TimePoint frame_target_time = frame_start_time + fml::TimeDelta::FromSecondsF(link.duration); - - display_link_.get().paused = YES; - - callback_(frame_start_time, frame_target_time); -} - -- (void)invalidate { - // [CADisplayLink invalidate] is thread-safe. - [display_link_.get() invalidate]; + // no-op. } - (void)dealloc { - [self invalidate]; + [display_link_ invalidate]; [super dealloc]; } diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index bf2c1b9d44831..03fe2e90d8d4a 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -1955,3 +1956,58 @@ FlutterEngineResult FlutterEnginePostCallbackOnAllNativeThreads( "Internal error while attempting to post " "tasks to all threads."); } + +namespace { +static bool ValidDisplayConfiguration(const FlutterEngineDisplay* displays, + size_t display_count) { + std::set display_ids; + for (size_t i = 0; i < display_count; i++) { + if (displays[i].single_display && display_count != 1) { + return false; + } + display_ids.insert(displays[i].display_id); + } + + return display_ids.size() == display_count; +} +} // namespace + +FlutterEngineResult FlutterEngineNotifyDisplayUpdate( + FLUTTER_API_SYMBOL(FlutterEngine) raw_engine, + const FlutterEngineDisplaysUpdateType update_type, + const FlutterEngineDisplay* embedder_displays, + size_t display_count) { + if (raw_engine == nullptr) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); + } + + if (!ValidDisplayConfiguration(embedder_displays, display_count)) { + return LOG_EMBEDDER_ERROR( + kInvalidArguments, + "Invalid FlutterEngineDisplay configuration specified."); + } + + auto engine = reinterpret_cast(raw_engine); + + switch (update_type) { + case kFlutterEngineDisplaysUpdateTypeStartup: { + std::vector displays; + for (size_t i = 0; i < display_count; i++) { + flutter::Display display = + flutter::Display(embedder_displays[i].refresh_rate); + if (!embedder_displays[i].single_display) { + display = flutter::Display(embedder_displays[i].display_id, + embedder_displays[i].refresh_rate); + } + displays.push_back(display); + } + engine->GetShell().OnDisplayUpdates(flutter::DisplayUpdateType::kStartup, + displays); + return kSuccess; + } + default: + return LOG_EMBEDDER_ERROR( + kInvalidArguments, + "Invalid FlutterEngineDisplaysUpdateType type specified."); + } +} diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 97df3d2468308..8ed2dadfac37f 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -963,6 +963,56 @@ typedef const FlutterLocale* (*FlutterComputePlatformResolvedLocaleCallback)( const FlutterLocale** /* supported_locales*/, size_t /* Number of locales*/); +/// Display refers to a graphics hardware system consisting of a framebuffer, +/// typically a monitor or a screen. This ID is unique per display and is +/// stable until the Flutter application restarts. +typedef uint64_t FlutterEngineDisplayId; + +typedef struct { + /// This size of this struct. Must be sizeof(FlutterDisplay). + size_t struct_size; + + FlutterEngineDisplayId display_id; + + /// This is set to true if the embedder only has one display. In cases where + /// this is set to true, the value of display_id is ignored. In cases where + /// this is not set to true, it is expected that a valid display_id be + /// provided. + bool single_display; + + /// This represents the refresh period in frames per second. This value may be + /// zero if the device is not running or unavaliable or unknown. + double refresh_rate; +} FlutterEngineDisplay; + +/// The update type parameter that is passed to +/// `FlutterEngineNotifyDisplayUpdate`. +typedef enum { + /// `FlutterEngineDisplay`s that were active during start-up. A display is + /// considered active if: + /// 1. The frame buffer hardware is connected. + /// 2. The display is drawable, e.g. it isn't being mirrored from another + /// connected display or sleeping. + kFlutterEngineDisplaysUpdateTypeStartup, + kFlutterEngineDisplaysUpdateTypeCount, +} FlutterEngineDisplaysUpdateType; + +//------------------------------------------------------------------------------ +/// @brief Posts updates corresponding to display changes to a running engine +/// instance. +/// +/// @param[in] update_type The type of update pushed to the engine. +/// @param[in] displays The displays affected by this update. +/// @param[in] display_count Size of the displays array, must be at least 1. +/// +/// @return the result of the call made to the engine. +/// +FlutterEngineResult FlutterEngineNotifyDisplayUpdate( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterEngineDisplaysUpdateType update_type, + const FlutterEngineDisplay* displays, + size_t display_count); + typedef int64_t FlutterEngineDartPort; typedef enum { diff --git a/shell/platform/embedder/tests/embedder_unittests_gl.cc b/shell/platform/embedder/tests/embedder_unittests_gl.cc index d376522ce27b0..9a93e448f98ae 100644 --- a/shell/platform/embedder/tests/embedder_unittests_gl.cc +++ b/shell/platform/embedder/tests/embedder_unittests_gl.cc @@ -3127,5 +3127,230 @@ TEST_F(EmbedderTest, PresentInfoContainsValidFBOId) { frame_latch.Wait(); } +TEST_F(EmbedderTest, SetSingleDisplayConfigurationWithDisplayId) { + auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetCompositor(); + builder.SetDartEntrypoint("empty_scene"); + fml::AutoResetWaitableEvent latch; + context.AddNativeCallback( + "SignalNativeTest", + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); })); + + auto engine = builder.LaunchEngine(); + + ASSERT_TRUE(engine.is_valid()); + + FlutterEngineDisplay display; + display.struct_size = sizeof(FlutterEngineDisplay); + display.display_id = 1; + display.refresh_rate = 20; + + std::vector displays = {display}; + + const FlutterEngineResult result = FlutterEngineNotifyDisplayUpdate( + engine.get(), kFlutterEngineDisplaysUpdateTypeStartup, displays.data(), + displays.size()); + ASSERT_EQ(result, kSuccess); + + flutter::Shell& shell = ToEmbedderEngine(engine.get())->GetShell(); + ASSERT_EQ(shell.GetMainDisplayRefreshRate(), display.refresh_rate); + + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + event.pixel_ratio = 1.0; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + latch.Wait(); +} + +TEST_F(EmbedderTest, SetSingleDisplayConfigurationWithoutDisplayId) { + auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetCompositor(); + builder.SetDartEntrypoint("empty_scene"); + fml::AutoResetWaitableEvent latch; + context.AddNativeCallback( + "SignalNativeTest", + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); })); + + auto engine = builder.LaunchEngine(); + + ASSERT_TRUE(engine.is_valid()); + + FlutterEngineDisplay display; + display.struct_size = sizeof(FlutterEngineDisplay); + display.single_display = true; + display.refresh_rate = 20; + + std::vector displays = {display}; + + const FlutterEngineResult result = FlutterEngineNotifyDisplayUpdate( + engine.get(), kFlutterEngineDisplaysUpdateTypeStartup, displays.data(), + displays.size()); + ASSERT_EQ(result, kSuccess); + + flutter::Shell& shell = ToEmbedderEngine(engine.get())->GetShell(); + ASSERT_EQ(shell.GetMainDisplayRefreshRate(), display.refresh_rate); + + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + event.pixel_ratio = 1.0; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + latch.Wait(); +} + +TEST_F(EmbedderTest, SetValidMultiDisplayConfiguration) { + auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetCompositor(); + builder.SetDartEntrypoint("empty_scene"); + fml::AutoResetWaitableEvent latch; + context.AddNativeCallback( + "SignalNativeTest", + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); })); + + auto engine = builder.LaunchEngine(); + + ASSERT_TRUE(engine.is_valid()); + + FlutterEngineDisplay display_1; + display_1.struct_size = sizeof(FlutterEngineDisplay); + display_1.display_id = 1; + display_1.single_display = false; + display_1.refresh_rate = 20; + + FlutterEngineDisplay display_2; + display_2.struct_size = sizeof(FlutterEngineDisplay); + display_2.display_id = 2; + display_2.single_display = false; + display_2.refresh_rate = 60; + + std::vector displays = {display_1, display_2}; + + const FlutterEngineResult result = FlutterEngineNotifyDisplayUpdate( + engine.get(), kFlutterEngineDisplaysUpdateTypeStartup, displays.data(), + displays.size()); + ASSERT_EQ(result, kSuccess); + + flutter::Shell& shell = ToEmbedderEngine(engine.get())->GetShell(); + ASSERT_EQ(shell.GetMainDisplayRefreshRate(), display_1.refresh_rate); + + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + event.pixel_ratio = 1.0; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + latch.Wait(); +} + +TEST_F(EmbedderTest, MultipleDisplaysWithSingleDisplayTrueIsInvalid) { + auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetCompositor(); + builder.SetDartEntrypoint("empty_scene"); + fml::AutoResetWaitableEvent latch; + context.AddNativeCallback( + "SignalNativeTest", + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); })); + + auto engine = builder.LaunchEngine(); + + ASSERT_TRUE(engine.is_valid()); + + FlutterEngineDisplay display_1; + display_1.struct_size = sizeof(FlutterEngineDisplay); + display_1.display_id = 1; + display_1.single_display = true; + display_1.refresh_rate = 20; + + FlutterEngineDisplay display_2; + display_2.struct_size = sizeof(FlutterEngineDisplay); + display_2.display_id = 2; + display_2.single_display = true; + display_2.refresh_rate = 60; + + std::vector displays = {display_1, display_2}; + + const FlutterEngineResult result = FlutterEngineNotifyDisplayUpdate( + engine.get(), kFlutterEngineDisplaysUpdateTypeStartup, displays.data(), + displays.size()); + ASSERT_NE(result, kSuccess); + + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + event.pixel_ratio = 1.0; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + latch.Wait(); +} + +TEST_F(EmbedderTest, MultipleDisplaysWithSameDisplayIdIsInvalid) { + auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetCompositor(); + builder.SetDartEntrypoint("empty_scene"); + fml::AutoResetWaitableEvent latch; + context.AddNativeCallback( + "SignalNativeTest", + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); })); + + auto engine = builder.LaunchEngine(); + + ASSERT_TRUE(engine.is_valid()); + + FlutterEngineDisplay display_1; + display_1.struct_size = sizeof(FlutterEngineDisplay); + display_1.display_id = 1; + display_1.single_display = false; + display_1.refresh_rate = 20; + + FlutterEngineDisplay display_2; + display_2.struct_size = sizeof(FlutterEngineDisplay); + display_2.display_id = 1; + display_2.single_display = false; + display_2.refresh_rate = 60; + + std::vector displays = {display_1, display_2}; + + const FlutterEngineResult result = FlutterEngineNotifyDisplayUpdate( + engine.get(), kFlutterEngineDisplaysUpdateTypeStartup, displays.data(), + displays.size()); + ASSERT_NE(result, kSuccess); + + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + event.pixel_ratio = 1.0; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + latch.Wait(); +} + } // namespace testing } // namespace flutter