diff --git a/shell/platform/windows/angle_surface_manager.cc b/shell/platform/windows/angle_surface_manager.cc index cd8973dad6d16..18d21f6b425b9 100644 --- a/shell/platform/windows/angle_surface_manager.cc +++ b/shell/platform/windows/angle_surface_manager.cc @@ -209,7 +209,8 @@ void AngleSurfaceManager::CleanUp() { bool AngleSurfaceManager::CreateSurface(WindowsRenderTarget* render_target, EGLint width, - EGLint height) { + EGLint height, + bool vsync_enabled) { if (!render_target || !initialize_succeeded_) { return false; } @@ -226,17 +227,21 @@ bool AngleSurfaceManager::CreateSurface(WindowsRenderTarget* render_target, surfaceAttributes); if (surface == EGL_NO_SURFACE) { LogEglError("Surface creation failed."); + return false; } surface_width_ = width; surface_height_ = height; render_surface_ = surface; + + SetVSyncEnabled(vsync_enabled); return true; } void AngleSurfaceManager::ResizeSurface(WindowsRenderTarget* render_target, EGLint width, - EGLint height) { + EGLint height, + bool vsync_enabled) { EGLint existing_width, existing_height; GetSurfaceDimensions(&existing_width, &existing_height); if (width != existing_width || height != existing_height) { @@ -245,7 +250,7 @@ void AngleSurfaceManager::ResizeSurface(WindowsRenderTarget* render_target, ClearContext(); DestroySurface(); - if (!CreateSurface(render_target, width, height)) { + if (!CreateSurface(render_target, width, height, vsync_enabled)) { FML_LOG(ERROR) << "AngleSurfaceManager::ResizeSurface failed to create surface"; } @@ -300,6 +305,24 @@ EGLSurface AngleSurfaceManager::CreateSurfaceFromHandle( egl_config_, attributes); } +void AngleSurfaceManager::SetVSyncEnabled(bool enabled) { + if (eglMakeCurrent(egl_display_, render_surface_, render_surface_, + egl_context_) != EGL_TRUE) { + LogEglError("Unable to make surface current to update the swap interval"); + return; + } + + // OpenGL swap intervals can be used to prevent screen tearing. + // If enabled, the raster thread blocks until the v-blank. + // This is unnecessary if DWM composition is enabled. + // See: https://www.khronos.org/opengl/wiki/Swap_Interval + // See: https://learn.microsoft.com/windows/win32/dwm/composition-ovw + if (eglSwapInterval(egl_display_, enabled ? 1 : 0) != EGL_TRUE) { + LogEglError("Unable to update the swap interval"); + return; + } +} + bool AngleSurfaceManager::GetDevice(ID3D11Device** device) { if (!resolved_device_) { PFNEGLQUERYDISPLAYATTRIBEXTPROC egl_query_display_attrib_EXT = diff --git a/shell/platform/windows/angle_surface_manager.h b/shell/platform/windows/angle_surface_manager.h index ca5ac61f7e9eb..edfe7a8cbcd95 100644 --- a/shell/platform/windows/angle_surface_manager.h +++ b/shell/platform/windows/angle_surface_manager.h @@ -23,7 +23,7 @@ namespace flutter { -// A manager for inializing ANGLE correctly and using it to create and +// A manager for initializing ANGLE correctly and using it to create and // destroy surfaces class AngleSurfaceManager { public: @@ -34,17 +34,19 @@ class AngleSurfaceManager { // associated with window, in the appropriate format for display. // Target represents the visual entity to bind to. Width and // height represent dimensions surface is created at. - bool CreateSurface(WindowsRenderTarget* render_target, - EGLint width, - EGLint height); + virtual bool CreateSurface(WindowsRenderTarget* render_target, + EGLint width, + EGLint height, + bool enable_vsync); // Resizes backing surface from current size to newly requested size // based on width and height for the specific case when width and height do // not match current surface dimensions. Target represents the visual entity // to bind to. - void ResizeSurface(WindowsRenderTarget* render_target, - EGLint width, - EGLint height); + virtual void ResizeSurface(WindowsRenderTarget* render_target, + EGLint width, + EGLint height, + bool enable_vsync); // queries EGL for the dimensions of surface in physical // pixels returning width and height as out params. @@ -76,6 +78,10 @@ class AngleSurfaceManager { // Gets the |EGLDisplay|. EGLDisplay egl_display() const { return egl_display_; }; + // If enabled, makes the current surface's buffer swaps block until the + // v-blank. + virtual void SetVSyncEnabled(bool enabled); + // Gets the |ID3D11Device| chosen by ANGLE. bool GetDevice(ID3D11Device** device); diff --git a/shell/platform/windows/flutter_window.cc b/shell/platform/windows/flutter_window.cc index a1c84c7969eae..e5fbc89403644 100644 --- a/shell/platform/windows/flutter_window.cc +++ b/shell/platform/windows/flutter_window.cc @@ -312,4 +312,16 @@ ui::AXPlatformNodeWin* FlutterWindow::GetAlert() { return alert_node_.get(); } +bool FlutterWindow::NeedsVSync() { + // 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; +} + } // namespace flutter diff --git a/shell/platform/windows/flutter_window.h b/shell/platform/windows/flutter_window.h index b122f4a8c5fac..5725af5de1a6a 100644 --- a/shell/platform/windows/flutter_window.h +++ b/shell/platform/windows/flutter_window.h @@ -156,6 +156,9 @@ class FlutterWindow : public Window, public WindowBindingHandler { // |WindowBindingHandler| ui::AXPlatformNodeWin* GetAlert() override; + // |WindowBindingHandler| + bool NeedsVSync() override; + // |Window| ui::AXFragmentRootDelegateWin* GetAxFragmentRootDelegate() override; diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index 95d17aa93c37e..39dbe8ea6be05 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -774,4 +774,8 @@ FlutterWindowsEngine::accessibility_bridge() { return view_->accessibility_bridge(); } +void FlutterWindowsEngine::OnDwmCompositionChanged() { + view_->OnDwmCompositionChanged(); +} + } // namespace flutter diff --git a/shell/platform/windows/flutter_windows_engine.h b/shell/platform/windows/flutter_windows_engine.h index 3b73f0a865941..797cc2142da3c 100644 --- a/shell/platform/windows/flutter_windows_engine.h +++ b/shell/platform/windows/flutter_windows_engine.h @@ -266,6 +266,9 @@ class FlutterWindowsEngine { LPARAM lparam, AppExitType exit_type); + // Called when a WM_DWMCOMPOSITIONCHANGED message is received. + void OnDwmCompositionChanged(); + protected: // Creates the keyboard key handler. // diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index 77288a39911fb..4107546669a1e 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -81,8 +81,8 @@ uint32_t FlutterWindowsView::GetFrameBufferId(size_t width, size_t height) { if (resize_target_width_ == width && resize_target_height_ == height) { // Platform thread is blocked for the entire duration until the // resize_status_ is set to kDone. - engine_->surface_manager()->ResizeSurface(GetRenderTarget(), width, height); - engine_->surface_manager()->MakeCurrent(); + engine_->surface_manager()->ResizeSurface(GetRenderTarget(), width, height, + binding_handler_->NeedsVSync()); resize_status_ = ResizeState::kFrameGenerated; } @@ -586,8 +586,10 @@ bool FlutterWindowsView::PresentSoftwareBitmap(const void* allocation, void FlutterWindowsView::CreateRenderSurface() { if (engine_ && engine_->surface_manager()) { PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds(); + bool enable_vsync = binding_handler_->NeedsVSync(); engine_->surface_manager()->CreateSurface(GetRenderTarget(), bounds.width, - bounds.height); + bounds.height, enable_vsync); + resize_target_width_ = bounds.width; resize_target_height_ = bounds.height; } @@ -661,4 +663,10 @@ void FlutterWindowsView::UpdateSemanticsEnabled(bool enabled) { } } +void FlutterWindowsView::OnDwmCompositionChanged() { + if (engine_->surface_manager()) { + engine_->surface_manager()->SetVSyncEnabled(binding_handler_->NeedsVSync()); + } +} + } // namespace flutter diff --git a/shell/platform/windows/flutter_windows_view.h b/shell/platform/windows/flutter_windows_view.h index 9160add16634a..8dc31fafee39c 100644 --- a/shell/platform/windows/flutter_windows_view.h +++ b/shell/platform/windows/flutter_windows_view.h @@ -195,6 +195,9 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, // |TextInputPluginDelegate| void OnResetImeComposing() override; + // Called when a WM_ONCOMPOSITIONCHANGED message is received. + void OnDwmCompositionChanged(); + // Get a pointer to the alert node for this view. ui::AXPlatformNodeWin* AlertNode() const; diff --git a/shell/platform/windows/flutter_windows_view_unittests.cc b/shell/platform/windows/flutter_windows_view_unittests.cc index b56e51beec16f..89082dd46cba1 100644 --- a/shell/platform/windows/flutter_windows_view_unittests.cc +++ b/shell/platform/windows/flutter_windows_view_unittests.cc @@ -12,6 +12,7 @@ #include #include +#include "flutter/fml/synchronization/waitable_event.h" #include "flutter/shell/platform/common/json_message_codec.h" #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" #include "flutter/shell/platform/windows/flutter_window.h" @@ -27,8 +28,10 @@ namespace flutter { namespace testing { +using ::testing::_; using ::testing::InSequence; using ::testing::NiceMock; +using ::testing::Return; constexpr uint64_t kScanCodeKeyA = 0x1e; constexpr uint64_t kVirtualKeyA = 0x41; @@ -117,8 +120,12 @@ class MockAngleSurfaceManager : public AngleSurfaceManager { public: MockAngleSurfaceManager() {} + MOCK_METHOD4(CreateSurface, bool(WindowsRenderTarget*, EGLint, EGLint, bool)); + MOCK_METHOD4(ResizeSurface, void(WindowsRenderTarget*, EGLint, EGLint, bool)); MOCK_METHOD0(DestroySurface, void()); + MOCK_METHOD1(SetVSyncEnabled, void(bool)); + private: FML_DISALLOW_COPY_AND_ASSIGN(MockAngleSurfaceManager); }; @@ -714,28 +721,44 @@ TEST(FlutterWindowsViewTest, WindowResizeTests) { auto window_binding_handler = std::make_unique>(); + std::unique_ptr surface_manager = + std::make_unique(); + + EXPECT_CALL(*window_binding_handler.get(), NeedsVSync) + .WillOnce(Return(false)); + 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)); + modifier.SetSurfaceManager(surface_manager.release()); view.SetEngine(std::move(engine)); - bool send_window_metrics_event_called = false; + fml::AutoResetWaitableEvent metrics_sent_latch; modifier.embedder_api().SendWindowMetricsEvent = MOCK_ENGINE_PROC( SendWindowMetricsEvent, - ([&send_window_metrics_event_called]( - auto engine, const FlutterWindowMetricsEvent* even) { - send_window_metrics_event_called = true; + ([&metrics_sent_latch](auto engine, + const FlutterWindowMetricsEvent* event) { + metrics_sent_latch.Signal(); return kSuccess; })); - std::promise resize_completed; - std::thread([&resize_completed, &view]() { + fml::AutoResetWaitableEvent resized_latch; + std::thread([&resized_latch, &view]() { + // Start the window resize. This sends the new window metrics + // and then blocks until another thread completes the window resize. view.OnWindowSizeChanged(500, 500); - resize_completed.set_value(true); + resized_latch.Signal(); }).detach(); - auto result = resize_completed.get_future().wait_for(std::chrono::seconds(1)); - EXPECT_EQ(std::future_status::ready, result); - EXPECT_TRUE(send_window_metrics_event_called); + // Wait until the platform thread has started the window resize. + metrics_sent_latch.Wait(); + + // Complete the window resize by requesting a buffer with the new window size. + view.GetFrameBufferId(500, 500); + resized_latch.Wait(); } TEST(FlutterWindowsViewTest, WindowRepaintTests) { @@ -1083,5 +1106,96 @@ TEST(FlutterWindowsViewTest, TooltipNodeData) { EXPECT_EQ(uia_tooltip, "tooltip"); } +// Don't block until the v-blank if it is disabled by the window. +TEST(FlutterWindowsViewTest, DisablesVSync) { + std::unique_ptr engine = + std::make_unique(); + auto window_binding_handler = + std::make_unique>(); + std::unique_ptr surface_manager = + std::make_unique(); + + EXPECT_CALL(*window_binding_handler.get(), NeedsVSync) + .WillOnce(Return(false)); + + EngineModifier modifier(engine.get()); + FlutterWindowsView view(std::move(window_binding_handler)); + + InSequence s; + EXPECT_CALL(*surface_manager.get(), + CreateSurface(_, _, _, /*vsync_enabled=*/false)) + .Times(1) + .WillOnce(Return(true)); + + EXPECT_CALL(*engine.get(), Stop).Times(1); + EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1); + + modifier.SetSurfaceManager(surface_manager.release()); + view.SetEngine(std::move(engine)); + + view.CreateRenderSurface(); +} + +// Blocks until the v-blank if it is enabled by the window. +TEST(FlutterWindowsViewTest, EnablesVSync) { + std::unique_ptr engine = + std::make_unique(); + auto window_binding_handler = + std::make_unique>(); + std::unique_ptr surface_manager = + std::make_unique(); + + EXPECT_CALL(*window_binding_handler.get(), NeedsVSync).WillOnce(Return(true)); + + EngineModifier modifier(engine.get()); + FlutterWindowsView view(std::move(window_binding_handler)); + + InSequence s; + EXPECT_CALL(*surface_manager.get(), + CreateSurface(_, _, _, /*vsync_enabled=*/true)) + .Times(1) + .WillOnce(Return(true)); + + EXPECT_CALL(*engine.get(), Stop).Times(1); + EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1); + + modifier.SetSurfaceManager(surface_manager.release()); + view.SetEngine(std::move(engine)); + + view.CreateRenderSurface(); +} + +// Desktop Window Manager composition can be disabled on Windows 7. +// If this happens, the app must synchronize with the vsync to prevent +// screen tearing. +TEST(FlutterWindowsViewTest, UpdatesVSyncOnDwmUpdates) { + std::unique_ptr engine = + std::make_unique(); + auto window_binding_handler = + std::make_unique>(); + std::unique_ptr surface_manager = + std::make_unique(); + + EXPECT_CALL(*window_binding_handler.get(), NeedsVSync) + .WillOnce(Return(true)) + .WillOnce(Return(false)); + + EngineModifier modifier(engine.get()); + FlutterWindowsView view(std::move(window_binding_handler)); + + InSequence s; + EXPECT_CALL(*surface_manager.get(), SetVSyncEnabled(true)).Times(1); + EXPECT_CALL(*surface_manager.get(), SetVSyncEnabled(false)).Times(1); + + EXPECT_CALL(*engine.get(), Stop).Times(1); + EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1); + + modifier.SetSurfaceManager(surface_manager.release()); + view.SetEngine(std::move(engine)); + + view.GetEngine()->OnDwmCompositionChanged(); + view.GetEngine()->OnDwmCompositionChanged(); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/testing/mock_window_binding_handler.h b/shell/platform/windows/testing/mock_window_binding_handler.h index a2c9145ecc9ad..0f5daadaec670 100644 --- a/shell/platform/windows/testing/mock_window_binding_handler.h +++ b/shell/platform/windows/testing/mock_window_binding_handler.h @@ -36,6 +36,7 @@ class MockWindowBindingHandler : public WindowBindingHandler { MOCK_METHOD0(SendInitialAccessibilityFeatures, void()); MOCK_METHOD0(GetAlertDelegate, AlertPlatformNodeDelegate*()); MOCK_METHOD0(GetAlert, ui::AXPlatformNodeWin*()); + MOCK_METHOD0(NeedsVSync, bool()); private: FML_DISALLOW_COPY_AND_ASSIGN(MockWindowBindingHandler); diff --git a/shell/platform/windows/window_binding_handler.h b/shell/platform/windows/window_binding_handler.h index e13d7a138cfa5..2bc28fe284498 100644 --- a/shell/platform/windows/window_binding_handler.h +++ b/shell/platform/windows/window_binding_handler.h @@ -106,6 +106,10 @@ 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() = 0; }; } // namespace flutter diff --git a/shell/platform/windows/windows_lifecycle_manager.cc b/shell/platform/windows/windows_lifecycle_manager.cc index ecb14d43082a1..c7c46fa1e9c1c 100644 --- a/shell/platform/windows/windows_lifecycle_manager.cc +++ b/shell/platform/windows/windows_lifecycle_manager.cc @@ -48,7 +48,7 @@ bool WindowsLifecycleManager::WindowProc(HWND hwnd, // send a request to the framework to see if the app should exit. If it // is, we re-dispatch a new WM_CLOSE message. In order to allow the new // message to reach other delegates, we ignore it here. - case WM_CLOSE: + case WM_CLOSE: { auto key = std::make_tuple(hwnd, wpar, lpar); auto itr = sent_close_messages_.find(key); if (itr != sent_close_messages_.end()) { @@ -65,6 +65,13 @@ bool WindowsLifecycleManager::WindowProc(HWND hwnd, return true; } break; + } + + // DWM composition can be disabled on Windows 7. + // Notify the engine as this can result in screen tearing. + case WM_DWMCOMPOSITIONCHANGED: + engine_->OnDwmCompositionChanged(); + break; } return false; } diff --git a/shell/platform/windows/windows_lifecycle_manager.h b/shell/platform/windows/windows_lifecycle_manager.h index e2ccfa42fcded..e1d1f604c4a3d 100644 --- a/shell/platform/windows/windows_lifecycle_manager.h +++ b/shell/platform/windows/windows_lifecycle_manager.h @@ -18,7 +18,8 @@ class FlutterWindowsEngine; /// A manager for lifecycle events of the top-level window. /// /// Currently handles the following events: -/// WM_CLOSE +/// 1. WM_CLOSE +/// 2. WM_DWMCOMPOSITIONCHANGED class WindowsLifecycleManager { public: WindowsLifecycleManager(FlutterWindowsEngine* engine);