[Windows] Move DWM composition status to Windows proc table for mocking (flutter/engine#49397)

The `FlutterWindowsView` needs the `DwmIsCompositionEnabled` win32 API to check whether it should block presents until the v-blank to prevent screen tearing.

Currently, the view used the `FlutterWindow` to allow mocking this win32 API. However, the window is a complex type with lots of other responsibilities. The `WindowsProcTable` is the new preferred type for mocking win32 API.

Part of https://github.com/flutter/flutter/issues/140626

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
Loïc Sharma 2023-12-26 15:08:34 -08:00 committed by GitHub
parent e18aa559bd
commit ed5f58647f
11 changed files with 100 additions and 59 deletions

View File

@ -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:

View File

@ -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();

View File

@ -82,8 +82,8 @@ FlutterDesktopViewControllerRef FlutterDesktopViewControllerCreate(
width, height, engine_ptr->windows_proc_table());
auto engine = std::unique_ptr<flutter::FlutterWindowsEngine>(engine_ptr);
auto view =
std::make_unique<flutter::FlutterWindowsView>(std::move(window_wrapper));
auto view = std::make_unique<flutter::FlutterWindowsView>(
std::move(window_wrapper), engine_ptr->windows_proc_table());
auto controller = std::make_unique<flutter::FlutterWindowsViewController>(
std::move(engine), std::move(view));

View File

@ -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<WindowBindingHandler> window_binding) {
std::unique_ptr<WindowBindingHandler> window_binding,
std::shared_ptr<WindowsProcTable> windows_proc_table)
: windows_proc_table_(std::move(windows_proc_table)) {
if (windows_proc_table_ == nullptr) {
windows_proc_table_ = std::make_shared<WindowsProcTable>();
}
// 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

View File

@ -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<WindowBindingHandler> window_binding);
FlutterWindowsView(
std::unique_ptr<WindowBindingHandler> window_binding,
std::shared_ptr<WindowsProcTable> 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<WindowsProcTable> windows_proc_table_;
// Keeps track of pointer states in relation to the window.
std::unordered_map<int32_t, std::unique_ptr<PointerState>> pointer_states_;

View File

@ -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<NiceMock<MockWindowBindingHandler>>();
auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
std::unique_ptr<MockAngleSurfaceManager> surface_manager =
std::make_unique<MockAngleSurfaceManager>();
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<NiceMock<MockWindowBindingHandler>>();
auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
std::unique_ptr<MockAngleSurfaceManager> surface_manager =
std::make_unique<MockAngleSurfaceManager>();
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<MockFlutterWindowsEngine>();
auto window_binding_handler =
std::make_unique<NiceMock<MockWindowBindingHandler>>();
auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
std::unique_ptr<MockAngleSurfaceManager> surface_manager =
std::make_unique<MockAngleSurfaceManager>();
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<MockFlutterWindowsEngine>();
auto window_binding_handler =
std::make_unique<NiceMock<MockWindowBindingHandler>>();
auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
std::unique_ptr<MockAngleSurfaceManager> surface_manager =
std::make_unique<MockAngleSurfaceManager>();
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<MockFlutterWindowsEngine>();
auto window_binding_handler =
std::make_unique<NiceMock<MockWindowBindingHandler>>();
auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
std::unique_ptr<MockAngleSurfaceManager> surface_manager =
std::make_unique<MockAngleSurfaceManager>();
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<MockFlutterWindowsEngine>();
auto window_binding_handler =
std::make_unique<NiceMock<MockWindowBindingHandler>>();
auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
std::unique_ptr<MockAngleSurfaceManager> surface_manager =
std::make_unique<MockAngleSurfaceManager>();
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<MockFlutterWindowsEngine>();
auto window_binding_handler =
std::make_unique<NiceMock<MockWindowBindingHandler>>();
auto windows_proc_table = std::make_shared<MockWindowsProcTable>();
std::unique_ptr<MockAngleSurfaceManager> surface_manager =
std::make_unique<MockAngleSurfaceManager>();
@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -4,6 +4,9 @@
#include "flutter/shell/platform/windows/windows_proc_table.h"
#include <WinUser.h>
#include <dwmapi.h>
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

View File

@ -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,