diff --git a/cmake/build.cmake b/cmake/build.cmake index c75a299a..35608e04 100644 --- a/cmake/build.cmake +++ b/cmake/build.cmake @@ -53,11 +53,17 @@ else() CODE_FILE ${_wayland_protocols_src_dir}/text-input-unstable-v3-protocol.c HEADER_FILE ${_wayland_protocols_src_dir}/text-input-unstable-v3-client-protocol.h) + generate_wayland_client_protocol( + PROTOCOL_FILE ${_wayland_protocols_xml_dir}/stable/presentation-time/presentation-time.xml + CODE_FILE ${_wayland_protocols_src_dir}/presentation-time-protocol.c + HEADER_FILE ${_wayland_protocols_src_dir}/presentation-time-protocol.h) + add_definitions(-DDISPLAY_BACKEND_TYPE_WAYLAND) set(DISPLAY_BACKEND_SRC ${_wayland_protocols_src_dir}/xdg-shell-protocol.c ${_wayland_protocols_src_dir}/text-input-unstable-v1-protocol.c ${_wayland_protocols_src_dir}/text-input-unstable-v3-protocol.c + ${_wayland_protocols_src_dir}/presentation-time-protocol.c src/flutter/shell/platform/linux_embedded/window/linuxes_window_wayland.cc src/flutter/shell/platform/linux_embedded/window/native_window_wayland.cc) endif() @@ -105,6 +111,7 @@ add_executable(${TARGET} src/flutter/shell/platform/linux_embedded/system_utils.cc src/flutter/shell/platform/linux_embedded/logger.cc src/flutter/shell/platform/linux_embedded/external_texture_gl.cc + src/flutter/shell/platform/linux_embedded/vsync_waiter.cc src/flutter/shell/platform/linux_embedded/flutter_linuxes_texture_registrar.cc src/flutter/shell/platform/linux_embedded/plugin/key_event_plugin.cc src/flutter/shell/platform/linux_embedded/plugin/keyboard_glfw_util.cc diff --git a/examples/flutter-drm-eglstream-backend/flutter_window.cc b/examples/flutter-drm-eglstream-backend/flutter_window.cc index e71c246d..9ead67f6 100644 --- a/examples/flutter-drm-eglstream-backend/flutter_window.cc +++ b/examples/flutter-drm-eglstream-backend/flutter_window.cc @@ -5,6 +5,8 @@ #include "flutter_window.h" #include +#include +#include #include #include "generated_plugin_registrant.h" @@ -61,11 +63,13 @@ void FlutterWindow::Run() { std::chrono::steady_clock::time_point::clock::now() + wait_duration); } else { - // Wait 1/60 [sec] = 13 [msec] if no events. - next_event_time = - std::min(next_event_time, - std::chrono::steady_clock::time_point::clock::now() + - std::chrono::milliseconds(13)); + // Wait for the next frame if no events. + auto frame_rate = flutter_view_controller_->view()->GetFrameRate(); + next_event_time = std::min( + next_event_time, + std::chrono::steady_clock::time_point::clock::now() + + std::chrono::milliseconds( + static_cast(std::trunc(1000000.0 / frame_rate)))); } next_flutter_event_time = std::max(next_flutter_event_time, next_event_time); diff --git a/examples/flutter-drm-gbm-backend/flutter_window.cc b/examples/flutter-drm-gbm-backend/flutter_window.cc index e71c246d..9ead67f6 100644 --- a/examples/flutter-drm-gbm-backend/flutter_window.cc +++ b/examples/flutter-drm-gbm-backend/flutter_window.cc @@ -5,6 +5,8 @@ #include "flutter_window.h" #include +#include +#include #include #include "generated_plugin_registrant.h" @@ -61,11 +63,13 @@ void FlutterWindow::Run() { std::chrono::steady_clock::time_point::clock::now() + wait_duration); } else { - // Wait 1/60 [sec] = 13 [msec] if no events. - next_event_time = - std::min(next_event_time, - std::chrono::steady_clock::time_point::clock::now() + - std::chrono::milliseconds(13)); + // Wait for the next frame if no events. + auto frame_rate = flutter_view_controller_->view()->GetFrameRate(); + next_event_time = std::min( + next_event_time, + std::chrono::steady_clock::time_point::clock::now() + + std::chrono::milliseconds( + static_cast(std::trunc(1000000.0 / frame_rate)))); } next_flutter_event_time = std::max(next_flutter_event_time, next_event_time); diff --git a/examples/flutter-external-texture-plugin/flutter_window.cc b/examples/flutter-external-texture-plugin/flutter_window.cc index e71c246d..9ead67f6 100644 --- a/examples/flutter-external-texture-plugin/flutter_window.cc +++ b/examples/flutter-external-texture-plugin/flutter_window.cc @@ -5,6 +5,8 @@ #include "flutter_window.h" #include +#include +#include #include #include "generated_plugin_registrant.h" @@ -61,11 +63,13 @@ void FlutterWindow::Run() { std::chrono::steady_clock::time_point::clock::now() + wait_duration); } else { - // Wait 1/60 [sec] = 13 [msec] if no events. - next_event_time = - std::min(next_event_time, - std::chrono::steady_clock::time_point::clock::now() + - std::chrono::milliseconds(13)); + // Wait for the next frame if no events. + auto frame_rate = flutter_view_controller_->view()->GetFrameRate(); + next_event_time = std::min( + next_event_time, + std::chrono::steady_clock::time_point::clock::now() + + std::chrono::milliseconds( + static_cast(std::trunc(1000000.0 / frame_rate)))); } next_flutter_event_time = std::max(next_flutter_event_time, next_event_time); diff --git a/examples/flutter-wayland-client/flutter_window.cc b/examples/flutter-wayland-client/flutter_window.cc index e71c246d..9ead67f6 100644 --- a/examples/flutter-wayland-client/flutter_window.cc +++ b/examples/flutter-wayland-client/flutter_window.cc @@ -5,6 +5,8 @@ #include "flutter_window.h" #include +#include +#include #include #include "generated_plugin_registrant.h" @@ -61,11 +63,13 @@ void FlutterWindow::Run() { std::chrono::steady_clock::time_point::clock::now() + wait_duration); } else { - // Wait 1/60 [sec] = 13 [msec] if no events. - next_event_time = - std::min(next_event_time, - std::chrono::steady_clock::time_point::clock::now() + - std::chrono::milliseconds(13)); + // Wait for the next frame if no events. + auto frame_rate = flutter_view_controller_->view()->GetFrameRate(); + next_event_time = std::min( + next_event_time, + std::chrono::steady_clock::time_point::clock::now() + + std::chrono::milliseconds( + static_cast(std::trunc(1000000.0 / frame_rate)))); } next_flutter_event_time = std::max(next_flutter_event_time, next_event_time); diff --git a/examples/flutter-weston-desktop-shell-virtual-keyboard/flutter_window.cc b/examples/flutter-weston-desktop-shell-virtual-keyboard/flutter_window.cc index e71c246d..9ead67f6 100644 --- a/examples/flutter-weston-desktop-shell-virtual-keyboard/flutter_window.cc +++ b/examples/flutter-weston-desktop-shell-virtual-keyboard/flutter_window.cc @@ -5,6 +5,8 @@ #include "flutter_window.h" #include +#include +#include #include #include "generated_plugin_registrant.h" @@ -61,11 +63,13 @@ void FlutterWindow::Run() { std::chrono::steady_clock::time_point::clock::now() + wait_duration); } else { - // Wait 1/60 [sec] = 13 [msec] if no events. - next_event_time = - std::min(next_event_time, - std::chrono::steady_clock::time_point::clock::now() + - std::chrono::milliseconds(13)); + // Wait for the next frame if no events. + auto frame_rate = flutter_view_controller_->view()->GetFrameRate(); + next_event_time = std::min( + next_event_time, + std::chrono::steady_clock::time_point::clock::now() + + std::chrono::milliseconds( + static_cast(std::trunc(1000000.0 / frame_rate)))); } next_flutter_event_time = std::max(next_flutter_event_time, next_event_time); diff --git a/examples/flutter-weston-desktop-shell/flutter_window.cc b/examples/flutter-weston-desktop-shell/flutter_window.cc index e71c246d..9ead67f6 100644 --- a/examples/flutter-weston-desktop-shell/flutter_window.cc +++ b/examples/flutter-weston-desktop-shell/flutter_window.cc @@ -5,6 +5,8 @@ #include "flutter_window.h" #include +#include +#include #include #include "generated_plugin_registrant.h" @@ -61,11 +63,13 @@ void FlutterWindow::Run() { std::chrono::steady_clock::time_point::clock::now() + wait_duration); } else { - // Wait 1/60 [sec] = 13 [msec] if no events. - next_event_time = - std::min(next_event_time, - std::chrono::steady_clock::time_point::clock::now() + - std::chrono::milliseconds(13)); + // Wait for the next frame if no events. + auto frame_rate = flutter_view_controller_->view()->GetFrameRate(); + next_event_time = std::min( + next_event_time, + std::chrono::steady_clock::time_point::clock::now() + + std::chrono::milliseconds( + static_cast(std::trunc(1000000.0 / frame_rate)))); } next_flutter_event_time = std::max(next_flutter_event_time, next_event_time); diff --git a/examples/flutter-x11-client/flutter_window.cc b/examples/flutter-x11-client/flutter_window.cc index e71c246d..9ead67f6 100644 --- a/examples/flutter-x11-client/flutter_window.cc +++ b/examples/flutter-x11-client/flutter_window.cc @@ -5,6 +5,8 @@ #include "flutter_window.h" #include +#include +#include #include #include "generated_plugin_registrant.h" @@ -61,11 +63,13 @@ void FlutterWindow::Run() { std::chrono::steady_clock::time_point::clock::now() + wait_duration); } else { - // Wait 1/60 [sec] = 13 [msec] if no events. - next_event_time = - std::min(next_event_time, - std::chrono::steady_clock::time_point::clock::now() + - std::chrono::milliseconds(13)); + // Wait for the next frame if no events. + auto frame_rate = flutter_view_controller_->view()->GetFrameRate(); + next_event_time = std::min( + next_event_time, + std::chrono::steady_clock::time_point::clock::now() + + std::chrono::milliseconds( + static_cast(std::trunc(1000000.0 / frame_rate)))); } next_flutter_event_time = std::max(next_flutter_event_time, next_event_time); diff --git a/src/client_wrapper/include/flutter/flutter_view.h b/src/client_wrapper/include/flutter/flutter_view.h index b82c9b5d..343667e2 100644 --- a/src/client_wrapper/include/flutter/flutter_view.h +++ b/src/client_wrapper/include/flutter/flutter_view.h @@ -24,6 +24,9 @@ class FlutterView { // you have to call this every time in the main loop. bool DispatchEvent() { return FlutterDesktopViewDispatchEvent(view_); } + // Returns the display frame rate. + int32_t GetFrameRate() { return FlutterDesktopViewGetFrameRate(view_); } + private: // Handle for interacting with the C API's view. FlutterDesktopViewRef view_ = nullptr; diff --git a/src/flutter/shell/platform/linux_embedded/flutter_linuxes.cc b/src/flutter/shell/platform/linux_embedded/flutter_linuxes.cc index 5f4edd9a..746d291e 100644 --- a/src/flutter/shell/platform/linux_embedded/flutter_linuxes.cc +++ b/src/flutter/shell/platform/linux_embedded/flutter_linuxes.cc @@ -136,6 +136,10 @@ bool FlutterDesktopViewDispatchEvent(FlutterDesktopViewRef view) { return ViewFromHandle(view)->DispatchEvent(); } +int32_t FlutterDesktopViewGetFrameRate(FlutterDesktopViewRef view) { + return ViewFromHandle(view)->GetFrameRate(); +} + FlutterDesktopEngineRef FlutterDesktopEngineCreate( const FlutterDesktopEngineProperties& engine_properties) { flutter::FlutterProjectBundle project(engine_properties); diff --git a/src/flutter/shell/platform/linux_embedded/flutter_linuxes_engine.cc b/src/flutter/shell/platform/linux_embedded/flutter_linuxes_engine.cc index 27547426..16e8b497 100644 --- a/src/flutter/shell/platform/linux_embedded/flutter_linuxes_engine.cc +++ b/src/flutter/shell/platform/linux_embedded/flutter_linuxes_engine.cc @@ -135,6 +135,8 @@ FlutterLinuxesEngine::FlutterLinuxesEngine(const FlutterProjectBundle& project) std::make_unique>( messenger_wrapper_.get(), "flutter/settings", &JsonMessageCodec::GetInstance()); + + vsync_waiter_ = std::make_unique(); } FlutterLinuxesEngine::~FlutterLinuxesEngine() { Stop(); } @@ -204,7 +206,15 @@ bool FlutterLinuxesEngine::RunWithEntrypoint(const char* entrypoint) { auto host = static_cast(user_data); return host->HandlePlatformMessage(engine_message); }; - +// todo: add drm/x11 support. +// https://github.com/sony/flutter-embedded-linux/issues/136 +// https://github.com/sony/flutter-embedded-linux/issues/137 +#if defined(DISPLAY_BACKEND_TYPE_WAYLAND) + args.vsync_callback = [](void* user_data, intptr_t baton) -> void { + auto host = static_cast(user_data); + host->vsync_waiter_->NotifyWaitForVsync(baton); + }; +#endif args.custom_task_runners = &custom_task_runners; if (aot_data_) { @@ -362,4 +372,19 @@ bool FlutterLinuxesEngine::MarkExternalTextureFrameAvailable( engine_, texture_id) == kSuccess); } +void FlutterLinuxesEngine::OnVsync(uint64_t last_frame_time_nanos, + uint64_t vsync_interval_time_nanos) { + uint64_t current_time_nanos = embedder_api_.GetCurrentTime(); + uint64_t after_vsync_passed_time_nanos = + (current_time_nanos - last_frame_time_nanos) % vsync_interval_time_nanos; + uint64_t frame_start_time_nanos = + current_time_nanos + + (vsync_interval_time_nanos - after_vsync_passed_time_nanos); + uint64_t frame_target_time_nanos = + frame_start_time_nanos + vsync_interval_time_nanos; + + vsync_waiter_->NotifyVsync(engine_, &embedder_api_, frame_start_time_nanos, + frame_target_time_nanos); +} + } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/flutter_linuxes_engine.h b/src/flutter/shell/platform/linux_embedded/flutter_linuxes_engine.h index 3ef50bd8..139b1183 100644 --- a/src/flutter/shell/platform/linux_embedded/flutter_linuxes_engine.h +++ b/src/flutter/shell/platform/linux_embedded/flutter_linuxes_engine.h @@ -21,6 +21,7 @@ #include "flutter/shell/platform/linux_embedded/flutter_project_bundle.h" #include "flutter/shell/platform/linux_embedded/public/flutter_linuxes.h" #include "flutter/shell/platform/linux_embedded/task_runner.h" +#include "flutter/shell/platform/linux_embedded/vsync_waiter.h" namespace flutter { @@ -110,6 +111,10 @@ class FlutterLinuxesEngine { // given |texture_id|. bool MarkExternalTextureFrameAvailable(int64_t texture_id); + // Notifies the engine about the vsync event. + void OnVsync(uint64_t last_frame_time_nanos, + uint64_t vsync_interval_time_nanos); + private: // Allows swapping out embedder_api_ calls in tests. friend class EngineEmbedderApiModifier; @@ -158,6 +163,9 @@ class FlutterLinuxesEngine { // is being destroyed. FlutterDesktopOnPluginRegistrarDestroyed plugin_registrar_destruction_callback_ = nullptr; + + // The vsync waiter. + std::unique_ptr vsync_waiter_; }; } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/flutter_linuxes_view.cc b/src/flutter/shell/platform/linux_embedded/flutter_linuxes_view.cc index 69f3b5f7..2539f997 100644 --- a/src/flutter/shell/platform/linux_embedded/flutter_linuxes_view.cc +++ b/src/flutter/shell/platform/linux_embedded/flutter_linuxes_view.cc @@ -204,6 +204,11 @@ void FlutterLinuxesView::OnScroll(double x, double y, double delta_x, SendScroll(x, y, delta_x, delta_y, scroll_offset_multiplier); } +void FlutterLinuxesView::OnVsync(uint64_t last_frame_time_nanos, + uint64_t vsync_interval_time_nanos) { + engine_->OnVsync(last_frame_time_nanos, vsync_interval_time_nanos); +} + FlutterLinuxesView::touch_point* FlutterLinuxesView::GgeTouchPoint(int32_t id) { const size_t nmemb = sizeof(touch_event_) / sizeof(struct touch_point); int invalid = -1; @@ -381,4 +386,8 @@ LinuxesRenderSurfaceTarget* FlutterLinuxesView::GetRenderSurfaceTarget() const { FlutterLinuxesEngine* FlutterLinuxesView::GetEngine() { return engine_.get(); } +int32_t FlutterLinuxesView::GetFrameRate() { + return binding_handler_->GetFrameRate(); +} + } // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/flutter_linuxes_view.h b/src/flutter/shell/platform/linux_embedded/flutter_linuxes_view.h index fc7f2ce1..a0e7e89d 100644 --- a/src/flutter/shell/platform/linux_embedded/flutter_linuxes_view.h +++ b/src/flutter/shell/platform/linux_embedded/flutter_linuxes_view.h @@ -55,6 +55,9 @@ class FlutterLinuxesView : public WindowBindingHandlerDelegate { // Returns the engine backing this view. FlutterLinuxesEngine* GetEngine(); + // Returns the frame rate of the display. + int32_t GetFrameRate(); + // Callbacks for clearing context, settings context and swapping buffers. void* ProcResolver(const char* name); bool MakeCurrent(); @@ -115,6 +118,10 @@ class FlutterLinuxesView : public WindowBindingHandlerDelegate { void OnScroll(double x, double y, double delta_x, double delta_y, int scroll_offset_multiplier) override; + // |WindowBindingHandlerDelegate| + void OnVsync(uint64_t frame_start_time_nanos, + uint64_t frame_target_time_nanos) override; + private: // Struct holding the mouse state. The engine doesn't keep track of which // mouse buttons have been pressed, so it's the embedding's responsibility. diff --git a/src/flutter/shell/platform/linux_embedded/public/flutter_linuxes.h b/src/flutter/shell/platform/linux_embedded/public/flutter_linuxes.h index b534988a..c99d8128 100644 --- a/src/flutter/shell/platform/linux_embedded/public/flutter_linuxes.h +++ b/src/flutter/shell/platform/linux_embedded/public/flutter_linuxes.h @@ -109,13 +109,16 @@ FLUTTER_EXPORT void FlutterDesktopViewControllerDestroy( // Its lifetime is the same as the |controller|'s. FLUTTER_EXPORT FlutterDesktopEngineRef FlutterDesktopViewControllerGetEngine( FlutterDesktopViewControllerRef controller); -// Returns the view managed by the given controller. +// Returns the view managed by the given controller. FLUTTER_EXPORT FlutterDesktopViewRef FlutterDesktopViewControllerGetView(FlutterDesktopViewControllerRef controller); FLUTTER_EXPORT bool FlutterDesktopViewDispatchEvent(FlutterDesktopViewRef view); +// Returns the display frame rate by the given controller. +FLUTTER_EXPORT int32_t FlutterDesktopViewGetFrameRate(FlutterDesktopViewRef view); + // ========== Engine ========== // Creates a Flutter engine with the given properties. diff --git a/src/flutter/shell/platform/linux_embedded/vsync_waiter.cc b/src/flutter/shell/platform/linux_embedded/vsync_waiter.cc new file mode 100644 index 00000000..01d86316 --- /dev/null +++ b/src/flutter/shell/platform/linux_embedded/vsync_waiter.cc @@ -0,0 +1,38 @@ +// Copyright 2021 Sony Corporation. 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/platform/linux_embedded/vsync_waiter.h" + +#include + +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/linux_embedded/logger.h" + +namespace flutter { + +VsyncWaiter::VsyncWaiter() : event_counter_(0) {} + +void VsyncWaiter::NotifyWaitForVsync(intptr_t baton) { + std::lock_guard lk(mutex_); + baton_ = baton; + event_counter_++; +} + +void VsyncWaiter::NotifyVsync(FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterEngineProcTable* embedder_api, + uint64_t frame_start_time_nanos, + uint64_t frame_target_time_nanos) { + std::lock_guard lk(mutex_); + if (event_counter_ > 0 && baton_ != 0) { + assert(event_counter_ == 1); + event_counter_--; + auto result = embedder_api->OnVsync(engine, baton_, frame_start_time_nanos, + frame_target_time_nanos); + if (result != kSuccess) { + LINUXES_LOG(ERROR) << "FlutterEngineOnVsync failed: batton = " << baton_; + } + } +} + +} // namespace flutter diff --git a/src/flutter/shell/platform/linux_embedded/vsync_waiter.h b/src/flutter/shell/platform/linux_embedded/vsync_waiter.h new file mode 100644 index 00000000..aa0f23be --- /dev/null +++ b/src/flutter/shell/platform/linux_embedded/vsync_waiter.h @@ -0,0 +1,35 @@ +// Copyright 2021 Sony Corporation. 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_PLATFORM_LINUX_EMBEDDED_VSYNC_WAITER_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_VSYNC_WAITER_H_ + +#include +#include + +#include "flutter/shell/platform/embedder/embedder.h" + +namespace flutter { + +class VsyncWaiter { + public: + VsyncWaiter(); + ~VsyncWaiter() = default; + + void NotifyWaitForVsync(intptr_t baton); + + void NotifyVsync(FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterEngineProcTable* embedder_api, + uint64_t frame_start_time_nanos, + uint64_t frame_target_time_nanos); + + private: + intptr_t baton_; + uint32_t event_counter_; + std::mutex mutex_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_EMBEDDED_VSYNC_WAITER_H_ diff --git a/src/flutter/shell/platform/linux_embedded/window/linuxes_window_drm.h b/src/flutter/shell/platform/linux_embedded/window/linuxes_window_drm.h index 2d89cd43..a931e078 100644 --- a/src/flutter/shell/platform/linux_embedded/window/linuxes_window_drm.h +++ b/src/flutter/shell/platform/linux_embedded/window/linuxes_window_drm.h @@ -173,6 +173,9 @@ class LinuxesWindowDrm : public LinuxesWindow, public WindowBindingHandler { return {GetCurrentWidth(), GetCurrentHeight()}; } + // |FlutterWindowBindingHandler| + int32_t GetFrameRate() override { return 60000; } + // |FlutterWindowBindingHandler| void UpdateFlutterCursor(const std::string& cursor_name) override { if (show_cursor_) { diff --git a/src/flutter/shell/platform/linux_embedded/window/linuxes_window_wayland.cc b/src/flutter/shell/platform/linux_embedded/window/linuxes_window_wayland.cc index a0b235f4..1406a3c0 100644 --- a/src/flutter/shell/platform/linux_embedded/window/linuxes_window_wayland.cc +++ b/src/flutter/shell/platform/linux_embedded/window/linuxes_window_wayland.cc @@ -67,6 +67,57 @@ const xdg_surface_listener LinuxesWindowWayland::kXdgSurfaceListener = { }, }; +const wp_presentation_listener LinuxesWindowWayland::kWpPresentationListener = { + .clock_id = + [](void* data, wp_presentation* wp_presentation, uint32_t clk_id) { + auto self = reinterpret_cast(data); + self->wp_presentation_clk_id_ = clk_id; + LINUXES_LOG(TRACE) << "presentation info: clk_id = " << clk_id; + }, +}; + +const wp_presentation_feedback_listener + LinuxesWindowWayland::kWpPresentationFeedbackListener = { + .sync_output = + [](void* data, + struct wp_presentation_feedback* wp_presentation_feedback, + wl_output* output) {}, + .presented = + [](void* data, + struct wp_presentation_feedback* wp_presentation_feedback, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, + uint32_t refresh, uint32_t seq_hi, uint32_t seq_lo, + uint32_t flags) { + auto self = reinterpret_cast(data); + self->last_frame_time_nanos_ = + (((static_cast(tv_sec_hi) << 32) + tv_sec_lo) * + 1000000000) + + tv_nsec; + self->frame_rate_ = refresh; + }, + .discarded = + [](void* data, + struct wp_presentation_feedback* wp_presentation_feedback) {}, +}; + +const wl_callback_listener LinuxesWindowWayland::kWlSurfaceFrameListener = { + .done = + [](void* data, wl_callback* wl_callback, uint32_t time) { + // The presentation-time is an extended protocol and isn't supported + // by all compositors. This path is for when it wasn't supported. + auto self = reinterpret_cast(data); + if (self->wp_presentation_clk_id_ != UINT32_MAX) { + return; + } + + self->last_frame_time_nanos_ = static_cast(time) * 1000000; + + auto callback = wl_surface_frame(self->native_window_->Surface()); + wl_callback_destroy(wl_callback); + wl_callback_add_listener(callback, &kWlSurfaceFrameListener, data); + }, +}; + const wl_seat_listener LinuxesWindowWayland::kWlSeatListener = { .capabilities = [](void* data, wl_seat* seat, uint32_t caps) -> void { auto self = reinterpret_cast(data); @@ -277,8 +328,14 @@ const wl_output_listener LinuxesWindowWayland::kWlOutputListener = { int32_t height, int32_t refresh) -> void { auto self = reinterpret_cast(data); if (flags & WL_OUTPUT_MODE_CURRENT) { - LINUXES_LOG(INFO) << "Display output resolution: " << width << "x" - << height; + LINUXES_LOG(INFO) << "Display output info: width = " << width + << ", height = " << height + << ", refresh = " << refresh; + // Some composers send 0 for the refresh value. + if (refresh != 0) { + self->frame_rate_ = refresh; + } + if (self->window_mode_ == FlutterWindowMode::kFullscreen) { self->current_width_ = width; self->current_height_ = height; @@ -501,11 +558,14 @@ LinuxesWindowWayland::LinuxesWindowWayland(FlutterWindowMode window_mode, wl_data_offer_(nullptr), wl_data_source_(nullptr), wl_cursor_theme_(nullptr), + serial_(0), zwp_text_input_manager_v1_(nullptr), zwp_text_input_manager_v3_(nullptr), zwp_text_input_v1_(nullptr), zwp_text_input_v3_(nullptr), - serial_(0) { + wp_presentation_(nullptr), + wp_presentation_clk_id_(UINT32_MAX), + frame_rate_(60000) { window_mode_ = window_mode; current_width_ = width; current_height_ = height; @@ -697,16 +757,14 @@ PhysicalWindowBounds LinuxesWindowWayland::GetPhysicalWindowBounds() { return {GetCurrentWidth(), GetCurrentHeight()}; } +int32_t LinuxesWindowWayland::GetFrameRate() { return frame_rate_; } + bool LinuxesWindowWayland::DispatchEvent() { if (!IsValid()) { LINUXES_LOG(ERROR) << "Wayland display is invalid."; return false; } - pollfd fds[] = { - {wl_display_get_fd(wl_display_), POLLIN}, - }; - // Prepare to call wl_display_read_events. while (wl_display_prepare_read(wl_display_) != 0) { // If Wayland compositor terminates, -1 is returned. @@ -715,14 +773,40 @@ bool LinuxesWindowWayland::DispatchEvent() { return false; } } - - // Handle events. wl_display_flush(wl_display_); + + // Handle Vsync. + { + if (wp_presentation_clk_id_ != UINT32_MAX) { + // This path is used if the presentation-time protocol is supported by the + // compositor. + wp_presentation_feedback_add_listener( + ::wp_presentation_feedback(wp_presentation_, + native_window_->Surface()), + &kWpPresentationFeedbackListener, this); + auto result = wl_display_dispatch_pending(wl_display_); + if (result == -1) { + return false; + } + } + + if (binding_handler_delegate_) { + const uint64_t vsync_interval_time_nanos = 1000000000000 / frame_rate_; + binding_handler_delegate_->OnVsync(last_frame_time_nanos_, + vsync_interval_time_nanos); + } + } + + // Handle Wayland events. + pollfd fds[] = { + {wl_display_get_fd(wl_display_), POLLIN}, + }; if (poll(fds, 1, 0) > 0) { - int result = wl_display_read_events(wl_display_); + auto result = wl_display_read_events(wl_display_); if (result == -1) { return false; } + result = wl_display_dispatch_pending(wl_display_); if (result == -1) { return false; @@ -780,6 +864,9 @@ bool LinuxesWindowWayland::CreateRenderSurface(int32_t width, int32_t height) { xdg_toplevel_set_title(xdg_toplevel_, "Flutter"); wl_surface_commit(native_window_->Surface()); + auto* callback = wl_surface_frame(native_window_->Surface()); + wl_callback_add_listener(callback, &kWlSurfaceFrameListener, this); + render_surface_ = std::make_unique(std::make_unique( std::make_unique(wl_display_))); render_surface_->SetNativeWindow(native_window_.get()); @@ -946,6 +1033,7 @@ void LinuxesWindowWayland::WlRegistryHandler(wl_registry* wl_registry, wl_output_ = static_cast( wl_registry_bind(wl_registry, name, &wl_output_interface, 1)); wl_output_add_listener(wl_output_, &kWlOutputListener, this); + return; } if (!strcmp(interface, wl_shm_interface.name)) { @@ -987,6 +1075,16 @@ void LinuxesWindowWayland::WlRegistryHandler(wl_registry* wl_registry, wl_data_device_manager_ = static_cast( wl_registry_bind(wl_registry, name, &wl_data_device_manager_interface, wl_data_device_manager_version_)); + return; + } + + if (!strcmp(interface, wp_presentation_interface.name)) { + constexpr uint32_t kMaxVersion = 1; + wp_presentation_ = static_cast(wl_registry_bind( + wl_registry, name, &wp_presentation_interface, kMaxVersion)); + wp_presentation_add_listener(wp_presentation_, &kWpPresentationListener, + this); + return; } } diff --git a/src/flutter/shell/platform/linux_embedded/window/linuxes_window_wayland.h b/src/flutter/shell/platform/linux_embedded/window/linuxes_window_wayland.h index 9780f859..e23bc06d 100644 --- a/src/flutter/shell/platform/linux_embedded/window/linuxes_window_wayland.h +++ b/src/flutter/shell/platform/linux_embedded/window/linuxes_window_wayland.h @@ -20,6 +20,7 @@ // These header files are automatically generated by the // wayland-scanner. extern "C" { +#include "wayland/protocol/presentation-time-protocol.h" #include "wayland/protocol/text-input-unstable-v1-client-protocol.h" #include "wayland/protocol/text-input-unstable-v3-client-protocol.h" #include "wayland/protocol/weston-desktop-shell-client-protocol.h" @@ -58,6 +59,9 @@ class LinuxesWindowWayland : public LinuxesWindow, public WindowBindingHandler { // |FlutterWindowBindingHandler| PhysicalWindowBounds GetPhysicalWindowBounds() override; + // |FlutterWindowBindingHandler| + int32_t GetFrameRate() override; + // |FlutterWindowBindingHandler| void UpdateFlutterCursor(const std::string& cursor_name) override; @@ -102,6 +106,10 @@ class LinuxesWindowWayland : public LinuxesWindow, public WindowBindingHandler { static const wl_data_source_listener kWlDataSourceListener; static const zwp_text_input_v1_listener kZwpTextInputV1Listener; static const zwp_text_input_v3_listener kZwpTextInputV3Listener; + static const wl_callback_listener kWlSurfaceFrameListener; + static const wp_presentation_listener kWpPresentationListener; + static const wp_presentation_feedback_listener + kWpPresentationFeedbackListener; // A pointer to a FlutterWindowsView that can be used to update engine // windowing and input state. @@ -111,6 +119,7 @@ class LinuxesWindowWayland : public LinuxesWindow, public WindowBindingHandler { std::unique_ptr render_surface_; bool display_valid_; + uint32_t last_frame_time_; // Indicates that exists a keyboard show request from Flutter Engine. bool is_requested_show_virtual_keyboard_; @@ -136,6 +145,12 @@ class LinuxesWindowWayland : public LinuxesWindow, public WindowBindingHandler { zwp_text_input_v1* zwp_text_input_v1_; zwp_text_input_v3* zwp_text_input_v3_; + // Frame information for Vsync events. + wp_presentation* wp_presentation_; + uint32_t wp_presentation_clk_id_; + uint64_t last_frame_time_nanos_; + int32_t frame_rate_; + CursorInfo cursor_info_; // List of cursor name and wl_cursor supported by Wayland. diff --git a/src/flutter/shell/platform/linux_embedded/window/linuxes_window_x11.cc b/src/flutter/shell/platform/linux_embedded/window/linuxes_window_x11.cc index 447375b6..76d1338b 100644 --- a/src/flutter/shell/platform/linux_embedded/window/linuxes_window_x11.cc +++ b/src/flutter/shell/platform/linux_embedded/window/linuxes_window_x11.cc @@ -145,6 +145,8 @@ PhysicalWindowBounds LinuxesWindowX11::GetPhysicalWindowBounds() { return {GetCurrentWidth(), GetCurrentHeight()}; } +int32_t LinuxesWindowX11::GetFrameRate() { return 60000; } + void LinuxesWindowX11::UpdateFlutterCursor(const std::string& cursor_name) { // TODO: implement here } diff --git a/src/flutter/shell/platform/linux_embedded/window/linuxes_window_x11.h b/src/flutter/shell/platform/linux_embedded/window/linuxes_window_x11.h index e385436b..ff1b552c 100644 --- a/src/flutter/shell/platform/linux_embedded/window/linuxes_window_x11.h +++ b/src/flutter/shell/platform/linux_embedded/window/linuxes_window_x11.h @@ -44,6 +44,9 @@ class LinuxesWindowX11 : public LinuxesWindow, public WindowBindingHandler { // |FlutterWindowBindingHandler| PhysicalWindowBounds GetPhysicalWindowBounds() override; + // |FlutterWindowBindingHandler| + int32_t GetFrameRate() override; + // |FlutterWindowBindingHandler| void UpdateFlutterCursor(const std::string& cursor_name) override; diff --git a/src/flutter/shell/platform/linux_embedded/window_binding_handler.h b/src/flutter/shell/platform/linux_embedded/window_binding_handler.h index 9d3dfd87..463f6452 100644 --- a/src/flutter/shell/platform/linux_embedded/window_binding_handler.h +++ b/src/flutter/shell/platform/linux_embedded/window_binding_handler.h @@ -53,6 +53,9 @@ class WindowBindingHandler { // Returns the bounds of the backing window in physical pixels. virtual PhysicalWindowBounds GetPhysicalWindowBounds() = 0; + // Returns the frame rate of the display. + virtual int32_t GetFrameRate() = 0; + // Sets the cursor that should be used when the mouse is over the Flutter // content. See mouse_cursor.dart for the values and meanings of cursor_name. virtual void UpdateFlutterCursor(const std::string& cursor_name) = 0; diff --git a/src/flutter/shell/platform/linux_embedded/window_binding_handler_delegate.h b/src/flutter/shell/platform/linux_embedded/window_binding_handler_delegate.h index 4187342f..a01a6cd8 100644 --- a/src/flutter/shell/platform/linux_embedded/window_binding_handler_delegate.h +++ b/src/flutter/shell/platform/linux_embedded/window_binding_handler_delegate.h @@ -74,6 +74,11 @@ class WindowBindingHandlerDelegate { // Typically called by currently configured WindowBindingHandler virtual void OnScroll(double x, double y, double delta_x, double delta_y, int scroll_offset_multiplier) = 0; + + // Notifies delegate that backing window vsync has happened. + // Typically called by currently configured WindowBindingHandler + virtual void OnVsync(uint64_t last_frame_time_nanos, + uint64_t vsync_interval_time_nanos) = 0; }; } // namespace flutter diff --git a/src/templates/app/common/flutter_window.cc b/src/templates/app/common/flutter_window.cc index e71c246d..9ead67f6 100644 --- a/src/templates/app/common/flutter_window.cc +++ b/src/templates/app/common/flutter_window.cc @@ -5,6 +5,8 @@ #include "flutter_window.h" #include +#include +#include #include #include "generated_plugin_registrant.h" @@ -61,11 +63,13 @@ void FlutterWindow::Run() { std::chrono::steady_clock::time_point::clock::now() + wait_duration); } else { - // Wait 1/60 [sec] = 13 [msec] if no events. - next_event_time = - std::min(next_event_time, - std::chrono::steady_clock::time_point::clock::now() + - std::chrono::milliseconds(13)); + // Wait for the next frame if no events. + auto frame_rate = flutter_view_controller_->view()->GetFrameRate(); + next_event_time = std::min( + next_event_time, + std::chrono::steady_clock::time_point::clock::now() + + std::chrono::milliseconds( + static_cast(std::trunc(1000000.0 / frame_rate)))); } next_flutter_event_time = std::max(next_flutter_event_time, next_event_time);