Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit b32035f

Browse files
committed
[Windows] Don't block raster thread until v-blank
1 parent bb29628 commit b32035f

13 files changed

+214
-25
lines changed

shell/platform/windows/angle_surface_manager.cc

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,8 @@ void AngleSurfaceManager::CleanUp() {
209209

210210
bool AngleSurfaceManager::CreateSurface(WindowsRenderTarget* render_target,
211211
EGLint width,
212-
EGLint height) {
212+
EGLint height,
213+
bool vsync_enabled) {
213214
if (!render_target || !initialize_succeeded_) {
214215
return false;
215216
}
@@ -226,17 +227,21 @@ bool AngleSurfaceManager::CreateSurface(WindowsRenderTarget* render_target,
226227
surfaceAttributes);
227228
if (surface == EGL_NO_SURFACE) {
228229
LogEglError("Surface creation failed.");
230+
return false;
229231
}
230232

231233
surface_width_ = width;
232234
surface_height_ = height;
233235
render_surface_ = surface;
236+
237+
SetVSyncEnabled(vsync_enabled);
234238
return true;
235239
}
236240

237241
void AngleSurfaceManager::ResizeSurface(WindowsRenderTarget* render_target,
238242
EGLint width,
239-
EGLint height) {
243+
EGLint height,
244+
bool vsync_enabled) {
240245
EGLint existing_width, existing_height;
241246
GetSurfaceDimensions(&existing_width, &existing_height);
242247
if (width != existing_width || height != existing_height) {
@@ -245,7 +250,7 @@ void AngleSurfaceManager::ResizeSurface(WindowsRenderTarget* render_target,
245250

246251
ClearContext();
247252
DestroySurface();
248-
if (!CreateSurface(render_target, width, height)) {
253+
if (!CreateSurface(render_target, width, height, vsync_enabled)) {
249254
FML_LOG(ERROR)
250255
<< "AngleSurfaceManager::ResizeSurface failed to create surface";
251256
}
@@ -300,6 +305,24 @@ EGLSurface AngleSurfaceManager::CreateSurfaceFromHandle(
300305
egl_config_, attributes);
301306
}
302307

308+
void AngleSurfaceManager::SetVSyncEnabled(bool enabled) {
309+
if (eglMakeCurrent(egl_display_, render_surface_, render_surface_,
310+
egl_context_) != EGL_TRUE) {
311+
LogEglError("Unable to make surface current to update the swap interval");
312+
return;
313+
}
314+
315+
// OpenGL swap intervals can be used to prevent screen tearing.
316+
// If enabled, the raster thread blocks until the v-blank.
317+
// This is unnecessary if DWM composition is enabled.
318+
// See: https://www.khronos.org/opengl/wiki/Swap_Interval
319+
// See: https://learn.microsoft.com/windows/win32/dwm/composition-ovw
320+
if (eglSwapInterval(egl_display_, enabled ? 1 : 0) != EGL_TRUE) {
321+
LogEglError("Unable to update the swap interval");
322+
return;
323+
}
324+
}
325+
303326
bool AngleSurfaceManager::GetDevice(ID3D11Device** device) {
304327
if (!resolved_device_) {
305328
PFNEGLQUERYDISPLAYATTRIBEXTPROC egl_query_display_attrib_EXT =

shell/platform/windows/angle_surface_manager.h

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
namespace flutter {
2525

26-
// A manager for inializing ANGLE correctly and using it to create and
26+
// A manager for initializing ANGLE correctly and using it to create and
2727
// destroy surfaces
2828
class AngleSurfaceManager {
2929
public:
@@ -34,17 +34,19 @@ class AngleSurfaceManager {
3434
// associated with window, in the appropriate format for display.
3535
// Target represents the visual entity to bind to. Width and
3636
// height represent dimensions surface is created at.
37-
bool CreateSurface(WindowsRenderTarget* render_target,
38-
EGLint width,
39-
EGLint height);
37+
virtual bool CreateSurface(WindowsRenderTarget* render_target,
38+
EGLint width,
39+
EGLint height,
40+
bool enable_vsync);
4041

4142
// Resizes backing surface from current size to newly requested size
4243
// based on width and height for the specific case when width and height do
4344
// not match current surface dimensions. Target represents the visual entity
4445
// to bind to.
45-
void ResizeSurface(WindowsRenderTarget* render_target,
46-
EGLint width,
47-
EGLint height);
46+
virtual void ResizeSurface(WindowsRenderTarget* render_target,
47+
EGLint width,
48+
EGLint height,
49+
bool enable_vsync);
4850

4951
// queries EGL for the dimensions of surface in physical
5052
// pixels returning width and height as out params.
@@ -76,6 +78,10 @@ class AngleSurfaceManager {
7678
// Gets the |EGLDisplay|.
7779
EGLDisplay egl_display() const { return egl_display_; };
7880

81+
// If enabled, makes the current surface's buffer swaps block until the
82+
// v-blank.
83+
virtual void SetVSyncEnabled(bool enabled);
84+
7985
// Gets the |ID3D11Device| chosen by ANGLE.
8086
bool GetDevice(ID3D11Device** device);
8187

shell/platform/windows/flutter_window.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,4 +312,16 @@ ui::AXPlatformNodeWin* FlutterWindow::GetAlert() {
312312
return alert_node_.get();
313313
}
314314

315+
bool FlutterWindow::NeedsVSync() {
316+
// If the Desktop Window Manager composition is enabled,
317+
// the system itself synchronizes with v-sync.
318+
// See: https://learn.microsoft.com/windows/win32/dwm/composition-ovw
319+
BOOL composition_enabled;
320+
if (SUCCEEDED(::DwmIsCompositionEnabled(&composition_enabled))) {
321+
return !composition_enabled;
322+
}
323+
324+
return true;
325+
}
326+
315327
} // namespace flutter

shell/platform/windows/flutter_window.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ class FlutterWindow : public Window, public WindowBindingHandler {
156156
// |WindowBindingHandler|
157157
ui::AXPlatformNodeWin* GetAlert() override;
158158

159+
// |WindowBindingHandler|
160+
bool NeedsVSync() override;
161+
159162
// |Window|
160163
ui::AXFragmentRootDelegateWin* GetAxFragmentRootDelegate() override;
161164

shell/platform/windows/flutter_windows_engine.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,4 +776,8 @@ void FlutterWindowsEngine::OnQuit(std::optional<HWND> hwnd,
776776
lifecycle_manager_->Quit(hwnd, wparam, lparam, exit_code);
777777
}
778778

779+
void FlutterWindowsEngine::OnDwmCompositionChanged() {
780+
view_->OnDwmCompositionChanged();
781+
}
782+
779783
} // namespace flutter

shell/platform/windows/flutter_windows_engine.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,9 @@ class FlutterWindowsEngine {
267267
LPARAM lparam,
268268
AppExitType exit_type);
269269

270+
// Called when a WM_DWMCOMPOSITIONCHANGED message is received.
271+
void OnDwmCompositionChanged();
272+
270273
protected:
271274
// Creates the keyboard key handler.
272275
//

shell/platform/windows/flutter_windows_view.cc

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ uint32_t FlutterWindowsView::GetFrameBufferId(size_t width, size_t height) {
8181
if (resize_target_width_ == width && resize_target_height_ == height) {
8282
// Platform thread is blocked for the entire duration until the
8383
// resize_status_ is set to kDone.
84-
engine_->surface_manager()->ResizeSurface(GetRenderTarget(), width, height);
85-
engine_->surface_manager()->MakeCurrent();
84+
engine_->surface_manager()->ResizeSurface(GetRenderTarget(), width, height,
85+
binding_handler_->NeedsVSync());
8686
resize_status_ = ResizeState::kFrameGenerated;
8787
}
8888

@@ -586,8 +586,10 @@ bool FlutterWindowsView::PresentSoftwareBitmap(const void* allocation,
586586
void FlutterWindowsView::CreateRenderSurface() {
587587
if (engine_ && engine_->surface_manager()) {
588588
PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds();
589+
bool enable_vsync = binding_handler_->NeedsVSync();
589590
engine_->surface_manager()->CreateSurface(GetRenderTarget(), bounds.width,
590-
bounds.height);
591+
bounds.height, enable_vsync);
592+
591593
resize_target_width_ = bounds.width;
592594
resize_target_height_ = bounds.height;
593595
}
@@ -644,4 +646,10 @@ ui::AXPlatformNodeWin* FlutterWindowsView::AlertNode() const {
644646
return binding_handler_->GetAlert();
645647
}
646648

649+
void FlutterWindowsView::OnDwmCompositionChanged() {
650+
if (engine_->surface_manager()) {
651+
engine_->surface_manager()->SetVSyncEnabled(binding_handler_->NeedsVSync());
652+
}
653+
}
654+
647655
} // namespace flutter

shell/platform/windows/flutter_windows_view.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate,
194194
// |TextInputPluginDelegate|
195195
void OnResetImeComposing() override;
196196

197+
// Called when a WM_ONCOMPOSITIONCHANGED message is received.
198+
void OnDwmCompositionChanged();
199+
197200
// Get a pointer to the alert node for this view.
198201
ui::AXPlatformNodeWin* AlertNode() const;
199202

shell/platform/windows/flutter_windows_view_unittests.cc

Lines changed: 124 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <future>
1313
#include <vector>
1414

15+
#include "flutter/fml/synchronization/waitable_event.h"
1516
#include "flutter/shell/platform/common/json_message_codec.h"
1617
#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
1718
#include "flutter/shell/platform/windows/flutter_window.h"
@@ -27,8 +28,10 @@
2728
namespace flutter {
2829
namespace testing {
2930

31+
using ::testing::_;
3032
using ::testing::InSequence;
3133
using ::testing::NiceMock;
34+
using ::testing::Return;
3235

3336
constexpr uint64_t kScanCodeKeyA = 0x1e;
3437
constexpr uint64_t kVirtualKeyA = 0x41;
@@ -117,8 +120,12 @@ class MockAngleSurfaceManager : public AngleSurfaceManager {
117120
public:
118121
MockAngleSurfaceManager() {}
119122

123+
MOCK_METHOD4(CreateSurface, bool(WindowsRenderTarget*, EGLint, EGLint, bool));
124+
MOCK_METHOD4(ResizeSurface, void(WindowsRenderTarget*, EGLint, EGLint, bool));
120125
MOCK_METHOD0(DestroySurface, void());
121126

127+
MOCK_METHOD1(SetVSyncEnabled, void(bool));
128+
122129
private:
123130
FML_DISALLOW_COPY_AND_ASSIGN(MockAngleSurfaceManager);
124131
};
@@ -714,28 +721,44 @@ TEST(FlutterWindowsViewTest, WindowResizeTests) {
714721

715722
auto window_binding_handler =
716723
std::make_unique<NiceMock<MockWindowBindingHandler>>();
724+
std::unique_ptr<MockAngleSurfaceManager> surface_manager =
725+
std::make_unique<MockAngleSurfaceManager>();
726+
727+
EXPECT_CALL(*window_binding_handler.get(), NeedsVSync)
728+
.WillOnce(Return(false));
729+
EXPECT_CALL(
730+
*surface_manager.get(),
731+
ResizeSurface(_, /*width=*/500, /*height=*/500, /*enable_vsync=*/false))
732+
.Times(1);
733+
EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1);
717734

718735
FlutterWindowsView view(std::move(window_binding_handler));
736+
modifier.SetSurfaceManager(surface_manager.release());
719737
view.SetEngine(std::move(engine));
720738

721-
bool send_window_metrics_event_called = false;
739+
fml::AutoResetWaitableEvent metrics_sent_latch;
722740
modifier.embedder_api().SendWindowMetricsEvent = MOCK_ENGINE_PROC(
723741
SendWindowMetricsEvent,
724-
([&send_window_metrics_event_called](
725-
auto engine, const FlutterWindowMetricsEvent* even) {
726-
send_window_metrics_event_called = true;
742+
([&metrics_sent_latch](auto engine,
743+
const FlutterWindowMetricsEvent* event) {
744+
metrics_sent_latch.Signal();
727745
return kSuccess;
728746
}));
729747

730-
std::promise<bool> resize_completed;
731-
std::thread([&resize_completed, &view]() {
748+
fml::AutoResetWaitableEvent resized_latch;
749+
std::thread([&resized_latch, &view]() {
750+
// Start the window resize. This sends the new window metrics
751+
// and then blocks until another thread completes the window resize.
732752
view.OnWindowSizeChanged(500, 500);
733-
resize_completed.set_value(true);
753+
resized_latch.Signal();
734754
}).detach();
735755

736-
auto result = resize_completed.get_future().wait_for(std::chrono::seconds(1));
737-
EXPECT_EQ(std::future_status::ready, result);
738-
EXPECT_TRUE(send_window_metrics_event_called);
756+
// Wait until the platform thread has started the window resize.
757+
metrics_sent_latch.Wait();
758+
759+
// Complete the window resize by requesting a buffer with the new window size.
760+
view.GetFrameBufferId(500, 500);
761+
resized_latch.Wait();
739762
}
740763

741764
TEST(FlutterWindowsViewTest, WindowRepaintTests) {
@@ -1083,5 +1106,96 @@ TEST(FlutterWindowsViewTest, TooltipNodeData) {
10831106
EXPECT_EQ(uia_tooltip, "tooltip");
10841107
}
10851108

1109+
// Don't block until the v-blank if it is disabled by the window.
1110+
TEST(FlutterWindowsViewTest, DisablesVSync) {
1111+
std::unique_ptr<MockFlutterWindowsEngine> engine =
1112+
std::make_unique<MockFlutterWindowsEngine>();
1113+
auto window_binding_handler =
1114+
std::make_unique<NiceMock<MockWindowBindingHandler>>();
1115+
std::unique_ptr<MockAngleSurfaceManager> surface_manager =
1116+
std::make_unique<MockAngleSurfaceManager>();
1117+
1118+
EXPECT_CALL(*window_binding_handler.get(), NeedsVSync)
1119+
.WillOnce(Return(false));
1120+
1121+
EngineModifier modifier(engine.get());
1122+
FlutterWindowsView view(std::move(window_binding_handler));
1123+
1124+
InSequence s;
1125+
EXPECT_CALL(*surface_manager.get(),
1126+
CreateSurface(_, _, _, /*vsync_enabled=*/false))
1127+
.Times(1)
1128+
.WillOnce(Return(true));
1129+
1130+
EXPECT_CALL(*engine.get(), Stop).Times(1);
1131+
EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1);
1132+
1133+
modifier.SetSurfaceManager(surface_manager.release());
1134+
view.SetEngine(std::move(engine));
1135+
1136+
view.CreateRenderSurface();
1137+
}
1138+
1139+
// Blocks until the v-blank if it is enabled by the window.
1140+
TEST(FlutterWindowsViewTest, EnablesVSync) {
1141+
std::unique_ptr<MockFlutterWindowsEngine> engine =
1142+
std::make_unique<MockFlutterWindowsEngine>();
1143+
auto window_binding_handler =
1144+
std::make_unique<NiceMock<MockWindowBindingHandler>>();
1145+
std::unique_ptr<MockAngleSurfaceManager> surface_manager =
1146+
std::make_unique<MockAngleSurfaceManager>();
1147+
1148+
EXPECT_CALL(*window_binding_handler.get(), NeedsVSync).WillOnce(Return(true));
1149+
1150+
EngineModifier modifier(engine.get());
1151+
FlutterWindowsView view(std::move(window_binding_handler));
1152+
1153+
InSequence s;
1154+
EXPECT_CALL(*surface_manager.get(),
1155+
CreateSurface(_, _, _, /*vsync_enabled=*/true))
1156+
.Times(1)
1157+
.WillOnce(Return(true));
1158+
1159+
EXPECT_CALL(*engine.get(), Stop).Times(1);
1160+
EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1);
1161+
1162+
modifier.SetSurfaceManager(surface_manager.release());
1163+
view.SetEngine(std::move(engine));
1164+
1165+
view.CreateRenderSurface();
1166+
}
1167+
1168+
// Desktop Window Manager composition can be disabled on Windows 7.
1169+
// If this happens, the app must synchronize with the vsync to prevent
1170+
// screen tearing.
1171+
TEST(FlutterWindowsViewTest, UpdatesVSyncOnDwmUpdates) {
1172+
std::unique_ptr<MockFlutterWindowsEngine> engine =
1173+
std::make_unique<MockFlutterWindowsEngine>();
1174+
auto window_binding_handler =
1175+
std::make_unique<NiceMock<MockWindowBindingHandler>>();
1176+
std::unique_ptr<MockAngleSurfaceManager> surface_manager =
1177+
std::make_unique<MockAngleSurfaceManager>();
1178+
1179+
EXPECT_CALL(*window_binding_handler.get(), NeedsVSync)
1180+
.WillOnce(Return(true))
1181+
.WillOnce(Return(false));
1182+
1183+
EngineModifier modifier(engine.get());
1184+
FlutterWindowsView view(std::move(window_binding_handler));
1185+
1186+
InSequence s;
1187+
EXPECT_CALL(*surface_manager.get(), SetVSyncEnabled(true)).Times(1);
1188+
EXPECT_CALL(*surface_manager.get(), SetVSyncEnabled(false)).Times(1);
1189+
1190+
EXPECT_CALL(*engine.get(), Stop).Times(1);
1191+
EXPECT_CALL(*surface_manager.get(), DestroySurface).Times(1);
1192+
1193+
modifier.SetSurfaceManager(surface_manager.release());
1194+
view.SetEngine(std::move(engine));
1195+
1196+
view.GetEngine()->OnDwmCompositionChanged();
1197+
view.GetEngine()->OnDwmCompositionChanged();
1198+
}
1199+
10861200
} // namespace testing
10871201
} // namespace flutter

shell/platform/windows/testing/mock_window_binding_handler.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class MockWindowBindingHandler : public WindowBindingHandler {
3636
MOCK_METHOD0(SendInitialAccessibilityFeatures, void());
3737
MOCK_METHOD0(GetAlertDelegate, AlertPlatformNodeDelegate*());
3838
MOCK_METHOD0(GetAlert, ui::AXPlatformNodeWin*());
39+
MOCK_METHOD0(NeedsVSync, bool());
3940

4041
private:
4142
FML_DISALLOW_COPY_AND_ASSIGN(MockWindowBindingHandler);

0 commit comments

Comments
 (0)