From 9f2e8cb17a42fbdbc53b71b4e131e529eaa5042f Mon Sep 17 00:00:00 2001 From: Loic Sharma Date: Tue, 26 Dec 2023 13:06:56 -0800 Subject: [PATCH] [Windows] Use Windows proc table to mock DWM composition --- shell/platform/windows/flutter_window.cc | 12 ---- shell/platform/windows/flutter_window.h | 3 - shell/platform/windows/flutter_windows.cc | 4 +- .../platform/windows/flutter_windows_view.cc | 27 ++++++--- shell/platform/windows/flutter_windows_view.h | 12 +++- .../windows/flutter_windows_view_unittests.cc | 58 ++++++++++++------- .../testing/mock_window_binding_handler.h | 1 - .../windows/testing/mock_windows_proc_table.h | 6 +- .../platform/windows/window_binding_handler.h | 4 -- shell/platform/windows/windows_proc_table.cc | 16 ++++- shell/platform/windows/windows_proc_table.h | 16 +++-- 11 files changed, 100 insertions(+), 59 deletions(-) diff --git a/shell/platform/windows/flutter_window.cc b/shell/platform/windows/flutter_window.cc index 51351105bde14..8c0b39abf1348 100644 --- a/shell/platform/windows/flutter_window.cc +++ b/shell/platform/windows/flutter_window.cc @@ -372,18 +372,6 @@ ui::AXPlatformNodeWin* FlutterWindow::GetAlert() { return alert_node_.get(); } -bool FlutterWindow::NeedsVSync() const { - // If the Desktop Window Manager composition is enabled, - // the system itself synchronizes with v-sync. - // See: https://learn.microsoft.com/windows/win32/dwm/composition-ovw - BOOL composition_enabled; - if (SUCCEEDED(::DwmIsCompositionEnabled(&composition_enabled))) { - return !composition_enabled; - } - - return true; -} - void FlutterWindow::OnWindowStateEvent(WindowStateEvent event) { switch (event) { case WindowStateEvent::kShow: diff --git a/shell/platform/windows/flutter_window.h b/shell/platform/windows/flutter_window.h index e712aab0b2691..202c21b78c20c 100644 --- a/shell/platform/windows/flutter_window.h +++ b/shell/platform/windows/flutter_window.h @@ -193,9 +193,6 @@ class FlutterWindow : public KeyboardManager::WindowDelegate, // |WindowBindingHandler| virtual ui::AXPlatformNodeWin* GetAlert() override; - // |WindowBindingHandler| - virtual bool NeedsVSync() const override; - // Called to obtain a pointer to the fragment root delegate. virtual ui::AXFragmentRootDelegateWin* GetAxFragmentRootDelegate(); diff --git a/shell/platform/windows/flutter_windows.cc b/shell/platform/windows/flutter_windows.cc index bb9d43e5536e5..ed789d0796c1c 100644 --- a/shell/platform/windows/flutter_windows.cc +++ b/shell/platform/windows/flutter_windows.cc @@ -82,8 +82,8 @@ FlutterDesktopViewControllerRef FlutterDesktopViewControllerCreate( width, height, engine_ptr->windows_proc_table()); auto engine = std::unique_ptr(engine_ptr); - auto view = - std::make_unique(std::move(window_wrapper)); + auto view = std::make_unique( + std::move(window_wrapper), engine_ptr->windows_proc_table()); auto controller = std::make_unique( std::move(engine), std::move(view)); diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index e4105cb3357a7..d902d1e7b2acf 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -40,8 +40,7 @@ bool SurfaceWillUpdate(size_t cur_width, /// Update the surface's swap interval to block until the v-blank iff /// the system compositor is disabled. -void UpdateVsync(const FlutterWindowsEngine& engine, - const WindowBindingHandler& window) { +void UpdateVsync(const FlutterWindowsEngine& engine, bool needs_vsync) { AngleSurfaceManager* surface_manager = engine.surface_manager(); if (!surface_manager) { return; @@ -52,7 +51,6 @@ void UpdateVsync(const FlutterWindowsEngine& engine, // the raster thread. If the engine is initializing, the raster thread doesn't // exist yet and the render surface can be made current on the platform // thread. - auto needs_vsync = window.NeedsVSync(); if (engine.running()) { engine.PostRasterThreadTask([surface_manager, needs_vsync]() { surface_manager->SetVSyncEnabled(needs_vsync); @@ -72,7 +70,13 @@ void UpdateVsync(const FlutterWindowsEngine& engine, } // namespace FlutterWindowsView::FlutterWindowsView( - std::unique_ptr window_binding) { + std::unique_ptr window_binding, + std::shared_ptr windows_proc_table) + : windows_proc_table_(std::move(windows_proc_table)) { + if (windows_proc_table_ == nullptr) { + windows_proc_table_ = std::make_shared(); + } + // Take the binding handler, and give it a pointer back to self. binding_handler_ = std::move(window_binding); binding_handler_->SetView(this); @@ -114,7 +118,7 @@ void FlutterWindowsView::OnEmptyFrameGenerated() { // resize_status_ is set to kDone. engine_->surface_manager()->ResizeSurface( GetWindowHandle(), resize_target_width_, resize_target_height_, - binding_handler_->NeedsVSync()); + NeedsVsync()); resize_status_ = ResizeState::kFrameGenerated; } @@ -130,7 +134,7 @@ bool FlutterWindowsView::OnFrameGenerated(size_t width, size_t height) { // Platform thread is blocked for the entire duration until the // resize_status_ is set to kDone. engine_->surface_manager()->ResizeSurface(GetWindowHandle(), width, height, - binding_handler_->NeedsVSync()); + NeedsVsync()); resize_status_ = ResizeState::kFrameGenerated; return true; } @@ -638,7 +642,7 @@ void FlutterWindowsView::CreateRenderSurface() { engine_->surface_manager()->CreateSurface(GetWindowHandle(), bounds.width, bounds.height); - UpdateVsync(*engine_, *binding_handler_); + UpdateVsync(*engine_, NeedsVsync()); resize_target_width_ = bounds.width; resize_target_height_ = bounds.height; @@ -706,7 +710,7 @@ void FlutterWindowsView::UpdateSemanticsEnabled(bool enabled) { } void FlutterWindowsView::OnDwmCompositionChanged() { - UpdateVsync(*engine_, *binding_handler_); + UpdateVsync(*engine_, NeedsVsync()); } void FlutterWindowsView::OnWindowStateEvent(HWND hwnd, WindowStateEvent event) { @@ -715,4 +719,11 @@ void FlutterWindowsView::OnWindowStateEvent(HWND hwnd, WindowStateEvent event) { } } +bool FlutterWindowsView::NeedsVsync() const { + // If the Desktop Window Manager composition is enabled, + // the system itself synchronizes with vsync. + // See: https://learn.microsoft.com/windows/win32/dwm/composition-ovw + return !windows_proc_table_->DwmIsCompositionEnabled(); +} + } // namespace flutter diff --git a/shell/platform/windows/flutter_windows_view.h b/shell/platform/windows/flutter_windows_view.h index e3f6643c20725..cacc845bf614d 100644 --- a/shell/platform/windows/flutter_windows_view.h +++ b/shell/platform/windows/flutter_windows_view.h @@ -23,6 +23,7 @@ #include "flutter/shell/platform/windows/window_binding_handler.h" #include "flutter/shell/platform/windows/window_binding_handler_delegate.h" #include "flutter/shell/platform/windows/window_state.h" +#include "flutter/shell/platform/windows/windows_proc_table.h" namespace flutter { @@ -35,7 +36,9 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate { // // In order for object to render Flutter content the SetEngine method must be // called with a valid FlutterWindowsEngine instance. - FlutterWindowsView(std::unique_ptr window_binding); + FlutterWindowsView( + std::unique_ptr window_binding, + std::shared_ptr windows_proc_table = nullptr); virtual ~FlutterWindowsView(); @@ -365,9 +368,16 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate { void SendPointerEventWithData(const FlutterPointerEvent& event_data, PointerState* state); + // If true, rendering to the window should synchronize with the vsync + // to prevent screen tearing. + bool NeedsVsync() const; + // The engine associated with this view. FlutterWindowsEngine* engine_ = nullptr; + // Mocks win32 APIs. + std::shared_ptr windows_proc_table_; + // Keeps track of pointer states in relation to the window. std::unordered_map> pointer_states_; diff --git a/shell/platform/windows/flutter_windows_view_unittests.cc b/shell/platform/windows/flutter_windows_view_unittests.cc index 84ed57360fcde..75d29cfeab3e3 100644 --- a/shell/platform/windows/flutter_windows_view_unittests.cc +++ b/shell/platform/windows/flutter_windows_view_unittests.cc @@ -21,6 +21,7 @@ #include "flutter/shell/platform/windows/testing/engine_modifier.h" #include "flutter/shell/platform/windows/testing/mock_angle_surface_manager.h" #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h" +#include "flutter/shell/platform/windows/testing/mock_windows_proc_table.h" #include "flutter/shell/platform/windows/testing/test_keyboard.h" #include "gmock/gmock.h" @@ -814,18 +815,20 @@ TEST(FlutterWindowsViewTest, WindowResizeTests) { auto window_binding_handler = std::make_unique>(); + auto windows_proc_table = std::make_shared(); std::unique_ptr surface_manager = std::make_unique(); - EXPECT_CALL(*window_binding_handler.get(), NeedsVSync) - .WillOnce(Return(false)); + EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled) + .WillOnce(Return(true)); EXPECT_CALL( *surface_manager.get(), ResizeSurface(_, /*width=*/500, /*height=*/500, /*enable_vsync=*/false)) .Times(1); EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1); - FlutterWindowsView view(std::move(window_binding_handler)); + FlutterWindowsView view(std::move(window_binding_handler), + std::move(windows_proc_table)); modifier.SetSurfaceManager(std::move(surface_manager)); view.SetEngine(engine.get()); @@ -861,18 +864,20 @@ TEST(FlutterWindowsViewTest, TestEmptyFrameResizes) { auto window_binding_handler = std::make_unique>(); + auto windows_proc_table = std::make_shared(); std::unique_ptr surface_manager = std::make_unique(); - EXPECT_CALL(*window_binding_handler.get(), NeedsVSync) - .WillOnce(Return(false)); + EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled) + .WillOnce(Return(true)); EXPECT_CALL( *surface_manager.get(), ResizeSurface(_, /*width=*/500, /*height=*/500, /*enable_vsync=*/false)) .Times(1); EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1); - FlutterWindowsView view(std::move(window_binding_handler)); + FlutterWindowsView view(std::move(window_binding_handler), + std::move(windows_proc_table)); modifier.SetSurfaceManager(std::move(surface_manager)); view.SetEngine(engine.get()); @@ -1254,17 +1259,19 @@ TEST(FlutterWindowsViewTest, DisablesVSyncAtStartup) { std::make_unique(); auto window_binding_handler = std::make_unique>(); + auto windows_proc_table = std::make_shared(); std::unique_ptr surface_manager = std::make_unique(); EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(false)); EXPECT_CALL(*engine.get(), PostRasterThreadTask).Times(0); - EXPECT_CALL(*window_binding_handler.get(), NeedsVSync) - .WillOnce(Return(false)); + EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled) + .WillOnce(Return(true)); EngineModifier modifier(engine.get()); - FlutterWindowsView view(std::move(window_binding_handler)); + FlutterWindowsView view(std::move(window_binding_handler), + std::move(windows_proc_table)); InSequence s; EXPECT_CALL(*surface_manager.get(), CreateSurface(_, _, _)) @@ -1289,15 +1296,18 @@ TEST(FlutterWindowsViewTest, EnablesVSyncAtStartup) { std::make_unique(); auto window_binding_handler = std::make_unique>(); + auto windows_proc_table = std::make_shared(); std::unique_ptr surface_manager = std::make_unique(); EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(false)); EXPECT_CALL(*engine.get(), PostRasterThreadTask).Times(0); - EXPECT_CALL(*window_binding_handler.get(), NeedsVSync).WillOnce(Return(true)); + EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled) + .WillOnce(Return(false)); EngineModifier modifier(engine.get()); - FlutterWindowsView view(std::move(window_binding_handler)); + FlutterWindowsView view(std::move(window_binding_handler), + std::move(windows_proc_table)); InSequence s; EXPECT_CALL(*surface_manager.get(), CreateSurface(_, _, _)) @@ -1322,14 +1332,17 @@ TEST(FlutterWindowsViewTest, DisablesVSyncAfterStartup) { std::make_unique(); auto window_binding_handler = std::make_unique>(); + auto windows_proc_table = std::make_shared(); std::unique_ptr surface_manager = std::make_unique(); EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(true)); - EXPECT_CALL(*window_binding_handler.get(), NeedsVSync).WillOnce(Return(true)); + EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled) + .WillOnce(Return(true)); EngineModifier modifier(engine.get()); - FlutterWindowsView view(std::move(window_binding_handler)); + FlutterWindowsView view(std::move(window_binding_handler), + std::move(windows_proc_table)); InSequence s; EXPECT_CALL(*surface_manager.get(), CreateSurface(_, _, _)) @@ -1340,7 +1353,7 @@ TEST(FlutterWindowsViewTest, DisablesVSyncAfterStartup) { callback(); return true; }); - EXPECT_CALL(*surface_manager.get(), SetVSyncEnabled(true)).Times(1); + EXPECT_CALL(*surface_manager.get(), SetVSyncEnabled(false)).Times(1); EXPECT_CALL(*surface_manager.get(), ClearCurrent).Times(0); EXPECT_CALL(*engine.get(), Stop).Times(1); @@ -1359,15 +1372,18 @@ TEST(FlutterWindowsViewTest, EnablesVSyncAfterStartup) { std::make_unique(); auto window_binding_handler = std::make_unique>(); + auto windows_proc_table = std::make_shared(); std::unique_ptr surface_manager = std::make_unique(); EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(true)); - EXPECT_CALL(*window_binding_handler.get(), NeedsVSync).WillOnce(Return(true)); + EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled) + .WillOnce(Return(false)); EngineModifier modifier(engine.get()); - FlutterWindowsView view(std::move(window_binding_handler)); + FlutterWindowsView view(std::move(window_binding_handler), + std::move(windows_proc_table)); InSequence s; EXPECT_CALL(*surface_manager.get(), CreateSurface(_, _, _)) @@ -1398,6 +1414,7 @@ TEST(FlutterWindowsViewTest, UpdatesVSyncOnDwmUpdates) { std::make_unique(); auto window_binding_handler = std::make_unique>(); + auto windows_proc_table = std::make_shared(); std::unique_ptr surface_manager = std::make_unique(); @@ -1409,14 +1426,15 @@ TEST(FlutterWindowsViewTest, UpdatesVSyncOnDwmUpdates) { return true; }); - EXPECT_CALL(*window_binding_handler.get(), NeedsVSync) - .WillOnce(Return(true)) - .WillOnce(Return(false)); + EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled) + .WillOnce(Return(false)) + .WillOnce(Return(true)); EXPECT_CALL(*surface_manager.get(), ClearCurrent).Times(0); EngineModifier modifier(engine.get()); - FlutterWindowsView view(std::move(window_binding_handler)); + FlutterWindowsView view(std::move(window_binding_handler), + std::move(windows_proc_table)); InSequence s; EXPECT_CALL(*surface_manager.get(), SetVSyncEnabled(true)).Times(1); diff --git a/shell/platform/windows/testing/mock_window_binding_handler.h b/shell/platform/windows/testing/mock_window_binding_handler.h index 6912fdb2426ba..8aa3bce69576a 100644 --- a/shell/platform/windows/testing/mock_window_binding_handler.h +++ b/shell/platform/windows/testing/mock_window_binding_handler.h @@ -40,7 +40,6 @@ class MockWindowBindingHandler : public WindowBindingHandler { MOCK_METHOD(PointerLocation, GetPrimaryPointerLocation, (), (override)); MOCK_METHOD(AlertPlatformNodeDelegate*, GetAlertDelegate, (), (override)); MOCK_METHOD(ui::AXPlatformNodeWin*, GetAlert, (), (override)); - MOCK_METHOD(bool, NeedsVSync, (), (const override)); private: FML_DISALLOW_COPY_AND_ASSIGN(MockWindowBindingHandler); diff --git a/shell/platform/windows/testing/mock_windows_proc_table.h b/shell/platform/windows/testing/mock_windows_proc_table.h index 64caf1d504e36..af03f66db37c0 100644 --- a/shell/platform/windows/testing/mock_windows_proc_table.h +++ b/shell/platform/windows/testing/mock_windows_proc_table.h @@ -21,14 +21,16 @@ class MockWindowsProcTable : public WindowsProcTable { MOCK_METHOD(BOOL, GetPointerType, (UINT32 pointer_id, POINTER_INPUT_TYPE* pointer_type), - (override)); + (const, override)); MOCK_METHOD(LRESULT, GetThreadPreferredUILanguages, (DWORD, PULONG, PZZWSTR, PULONG), (const, override)); - MOCK_METHOD(bool, GetHighContrastEnabled, (), (override)); + MOCK_METHOD(bool, GetHighContrastEnabled, (), (const, override)); + + MOCK_METHOD(bool, DwmIsCompositionEnabled, (), (const, override)); private: FML_DISALLOW_COPY_AND_ASSIGN(MockWindowsProcTable); diff --git a/shell/platform/windows/window_binding_handler.h b/shell/platform/windows/window_binding_handler.h index 9e327333ea244..83fa57b8acf7e 100644 --- a/shell/platform/windows/window_binding_handler.h +++ b/shell/platform/windows/window_binding_handler.h @@ -96,10 +96,6 @@ class WindowBindingHandler { // Retrieve the alert node. virtual ui::AXPlatformNodeWin* GetAlert() = 0; - - // If true, rendering to the window should synchronize with the vsync - // to prevent screen tearing. - virtual bool NeedsVSync() const = 0; }; } // namespace flutter diff --git a/shell/platform/windows/windows_proc_table.cc b/shell/platform/windows/windows_proc_table.cc index 2a058460afd72..36a4f4153cc67 100644 --- a/shell/platform/windows/windows_proc_table.cc +++ b/shell/platform/windows/windows_proc_table.cc @@ -4,6 +4,9 @@ #include "flutter/shell/platform/windows/windows_proc_table.h" +#include +#include + namespace flutter { WindowsProcTable::WindowsProcTable() { @@ -17,7 +20,7 @@ WindowsProcTable::~WindowsProcTable() { } BOOL WindowsProcTable::GetPointerType(UINT32 pointer_id, - POINTER_INPUT_TYPE* pointer_type) { + POINTER_INPUT_TYPE* pointer_type) const { if (!get_pointer_type_.has_value()) { return FALSE; } @@ -32,7 +35,7 @@ LRESULT WindowsProcTable::GetThreadPreferredUILanguages(DWORD flags, return ::GetThreadPreferredUILanguages(flags, count, languages, length); } -bool WindowsProcTable::GetHighContrastEnabled() { +bool WindowsProcTable::GetHighContrastEnabled() const { HIGHCONTRAST high_contrast = {.cbSize = sizeof(HIGHCONTRAST)}; if (!::SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(HIGHCONTRAST), &high_contrast, 0)) { @@ -42,4 +45,13 @@ bool WindowsProcTable::GetHighContrastEnabled() { return high_contrast.dwFlags & HCF_HIGHCONTRASTON; } +bool WindowsProcTable::DwmIsCompositionEnabled() const { + BOOL composition_enabled; + if (SUCCEEDED(::DwmIsCompositionEnabled(&composition_enabled))) { + return composition_enabled; + } + + return true; +} + } // namespace flutter diff --git a/shell/platform/windows/windows_proc_table.h b/shell/platform/windows/windows_proc_table.h index 584d92823ea2f..1063310394932 100644 --- a/shell/platform/windows/windows_proc_table.h +++ b/shell/platform/windows/windows_proc_table.h @@ -24,11 +24,12 @@ class WindowsProcTable { // Used to react differently to touch or pen inputs. Returns false on failure. // Available on Windows 8 and newer, otherwise returns false. virtual BOOL GetPointerType(UINT32 pointer_id, - POINTER_INPUT_TYPE* pointer_type); + POINTER_INPUT_TYPE* pointer_type) const; // Get the preferred languages for the thread, and optionally the process, // and system, in that order, depending on the flags. - // See + // + // See: // https://learn.microsoft.com/windows/win32/api/winnls/nf-winnls-getthreadpreferreduilanguages virtual LRESULT GetThreadPreferredUILanguages(DWORD flags, PULONG count, @@ -38,9 +39,16 @@ class WindowsProcTable { // Get whether high contrast is enabled. // // Available on Windows 8 and newer, otherwise returns false. - // See + // + // See: // https://learn.microsoft.com/windows/win32/winauto/high-contrast-parameter - virtual bool GetHighContrastEnabled(); + virtual bool GetHighContrastEnabled() const; + + // Get whether the system compositor, DWM, is enabled. + // + // See: + // https://learn.microsoft.com/windows/win32/api/dwmapi/nf-dwmapi-dwmiscompositionenabled + virtual bool DwmIsCompositionEnabled() const; private: using GetPointerType_ = BOOL __stdcall(UINT32 pointerId,