Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions shell/platform/windows/angle_surface_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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) {
Expand All @@ -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";
}
Expand Down Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that we will still block until the v-blank when this parameter is true, i.e. when composition is not enabled? And if so, is that what we intend?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and yes. Screen tearing is possible if a Windows 7 user does not have DWM composition enabled. The app should synchronize with the vsync to prevent screen tearing.

// 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 =
Expand Down
20 changes: 13 additions & 7 deletions shell/platform/windows/angle_surface_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.
Expand Down Expand Up @@ -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);

Expand Down
12 changes: 12 additions & 0 deletions shell/platform/windows/flutter_window.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions shell/platform/windows/flutter_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ class FlutterWindow : public Window, public WindowBindingHandler {
// |WindowBindingHandler|
ui::AXPlatformNodeWin* GetAlert() override;

// |WindowBindingHandler|
bool NeedsVSync() override;

// |Window|
ui::AXFragmentRootDelegateWin* GetAxFragmentRootDelegate() override;

Expand Down
4 changes: 4 additions & 0 deletions shell/platform/windows/flutter_windows_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -774,4 +774,8 @@ FlutterWindowsEngine::accessibility_bridge() {
return view_->accessibility_bridge();
}

void FlutterWindowsEngine::OnDwmCompositionChanged() {
view_->OnDwmCompositionChanged();
}

} // namespace flutter
3 changes: 3 additions & 0 deletions shell/platform/windows/flutter_windows_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down
14 changes: 11 additions & 3 deletions shell/platform/windows/flutter_windows_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is now unneeded as resizing the window makes the surface current to update the wait interval.

engine_->surface_manager()->ResizeSurface(GetRenderTarget(), width, height,
binding_handler_->NeedsVSync());
resize_status_ = ResizeState::kFrameGenerated;
}

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
3 changes: 3 additions & 0 deletions shell/platform/windows/flutter_windows_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
134 changes: 124 additions & 10 deletions shell/platform/windows/flutter_windows_view_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <future>
#include <vector>

#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"
Expand All @@ -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;
Expand Down Expand Up @@ -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);
};
Expand Down Expand Up @@ -714,28 +721,44 @@ TEST(FlutterWindowsViewTest, WindowResizeTests) {

auto window_binding_handler =
std::make_unique<NiceMock<MockWindowBindingHandler>>();
std::unique_ptr<MockAngleSurfaceManager> surface_manager =
std::make_unique<MockAngleSurfaceManager>();

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<bool> 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) {
Expand Down Expand Up @@ -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<MockFlutterWindowsEngine> engine =
std::make_unique<MockFlutterWindowsEngine>();
auto window_binding_handler =
std::make_unique<NiceMock<MockWindowBindingHandler>>();
std::unique_ptr<MockAngleSurfaceManager> surface_manager =
std::make_unique<MockAngleSurfaceManager>();

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

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

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
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading