mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[Windows] Improve logic to update swap intervals (flutter/engine#46172)
This relands https://github.com/flutter/engine/pull/45310 to unblock the ANGLE roll with the fix for https://github.com/flutter/flutter/issues/134262. ## Background ### Swap interval If the Windows system compositor is enabled, the Windows embedder disables the swap interval so that presenting to a surface does not block until the v-blank. If the Windows system compositor is disabled (which is possible on Windows 7), the Windows embedder enables swap interval to prevent screen tearing. ### GL context threading Our current version of ANGLE allows making a GL context current on multiple threads. However, the latest version of ANGLE errors if a GL context is made current on multiple threads. This is causing the ANGLE roll to fail ([example](https://ci.chromium.org/ui/p/flutter/builders/try/Windows%20Engine%20Drone/203788/overview)). The Windows embedder has two GL context threading issues: 1. At startup, the platform thread creates and binds the GL context. This change ensures the GL context is released from the platform thread so that the raster thread can use the GL context for rendering. 2. When the system compositor updates, the GL context is bound to the platform thread to update the swap interval. This change ensures the swap interval update happens on the raster thread. ### Window resizing Resizing the window recreates the GL surface and resets the swap interval. The previous fix released the current GL context after updating the swap interval (this ensured the platform thread released the GL context at startup). This broke window resizing as it caused the engine to "lose" its GL context during rendering (see https://github.com/flutter/flutter/issues/134262). This reland releases the GL context only if on the startup case. [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
parent
41604c6b10
commit
f020b2c7c9
@ -256,6 +256,11 @@ bool AngleSurfaceManager::CreateSurface(WindowsRenderTarget* render_target,
|
||||
surface_height_ = height;
|
||||
render_surface_ = surface;
|
||||
|
||||
if (!MakeCurrent()) {
|
||||
LogEglError("Unable to make surface current to update the swap interval");
|
||||
return false;
|
||||
}
|
||||
|
||||
SetVSyncEnabled(vsync_enabled);
|
||||
return true;
|
||||
}
|
||||
@ -300,11 +305,20 @@ void AngleSurfaceManager::DestroySurface() {
|
||||
render_surface_ = EGL_NO_SURFACE;
|
||||
}
|
||||
|
||||
bool AngleSurfaceManager::HasContextCurrent() {
|
||||
return eglGetCurrentContext() != EGL_NO_CONTEXT;
|
||||
}
|
||||
|
||||
bool AngleSurfaceManager::MakeCurrent() {
|
||||
return (eglMakeCurrent(egl_display_, render_surface_, render_surface_,
|
||||
egl_context_) == EGL_TRUE);
|
||||
}
|
||||
|
||||
bool AngleSurfaceManager::ClearCurrent() {
|
||||
return (eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
||||
EGL_NO_CONTEXT) == EGL_TRUE);
|
||||
}
|
||||
|
||||
bool AngleSurfaceManager::ClearContext() {
|
||||
return (eglMakeCurrent(egl_display_, nullptr, nullptr, egl_context_) ==
|
||||
EGL_TRUE);
|
||||
@ -328,12 +342,6 @@ EGLSurface AngleSurfaceManager::CreateSurfaceFromHandle(
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@ -33,8 +33,10 @@ class AngleSurfaceManager {
|
||||
|
||||
// Creates an EGLSurface wrapper and backing DirectX 11 SwapChain
|
||||
// associated with window, in the appropriate format for display.
|
||||
// Target represents the visual entity to bind to. Width and
|
||||
// Target represents the visual entity to bind to. Width and
|
||||
// height represent dimensions surface is created at.
|
||||
//
|
||||
// This binds |egl_context_| to the current thread.
|
||||
virtual bool CreateSurface(WindowsRenderTarget* render_target,
|
||||
EGLint width,
|
||||
EGLint height,
|
||||
@ -42,8 +44,10 @@ class AngleSurfaceManager {
|
||||
|
||||
// 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
|
||||
// not match current surface dimensions. Target represents the visual entity
|
||||
// to bind to.
|
||||
//
|
||||
// This binds |egl_context_| to the current thread.
|
||||
virtual void ResizeSurface(WindowsRenderTarget* render_target,
|
||||
EGLint width,
|
||||
EGLint height,
|
||||
@ -56,11 +60,17 @@ class AngleSurfaceManager {
|
||||
// Releases the pass-in EGLSurface wrapping and backing resources if not null.
|
||||
virtual void DestroySurface();
|
||||
|
||||
// Binds egl_context_ to the current rendering thread and to the draw and read
|
||||
// surfaces returning a boolean result reflecting success.
|
||||
// Check if the current thread has a context bound.
|
||||
bool HasContextCurrent();
|
||||
|
||||
// Binds |egl_context_| to the current rendering thread and to the draw and
|
||||
// read surfaces returning a boolean result reflecting success.
|
||||
bool MakeCurrent();
|
||||
|
||||
// Clears current egl_context_
|
||||
// Unbinds the current EGL context from the current thread.
|
||||
bool ClearCurrent();
|
||||
|
||||
// Clears the |egl_context_| draw and read surfaces.
|
||||
bool ClearContext();
|
||||
|
||||
// Binds egl_resource_context_ to the current rendering thread and to the draw
|
||||
|
||||
@ -390,6 +390,10 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) {
|
||||
args.aot_data = aot_data_.get();
|
||||
}
|
||||
|
||||
// The platform thread creates OpenGL contexts. These
|
||||
// must be released to be used by the engine's threads.
|
||||
FML_DCHECK(!surface_manager_ || !surface_manager_->HasContextCurrent());
|
||||
|
||||
FlutterRendererConfig renderer_config;
|
||||
|
||||
if (enable_impeller_) {
|
||||
|
||||
@ -199,7 +199,7 @@ class FlutterWindowsEngine {
|
||||
bool MarkExternalTextureFrameAvailable(int64_t texture_id);
|
||||
|
||||
// Posts the given callback onto the raster thread.
|
||||
bool PostRasterThreadTask(fml::closure callback);
|
||||
virtual bool PostRasterThreadTask(fml::closure callback);
|
||||
|
||||
// Invoke on the embedder's vsync callback to schedule a frame.
|
||||
void OnVsync(intptr_t baton);
|
||||
|
||||
@ -594,6 +594,12 @@ void FlutterWindowsView::CreateRenderSurface() {
|
||||
engine_->surface_manager()->CreateSurface(GetRenderTarget(), bounds.width,
|
||||
bounds.height, enable_vsync);
|
||||
|
||||
// The EGL context cannot be current on multiple threads.
|
||||
// Creating the render surface runs on the platform thread and
|
||||
// makes the EGL context current. Thus, the EGL context must be
|
||||
// released so that the raster thread can use it for rendering.
|
||||
engine_->surface_manager()->ClearCurrent();
|
||||
|
||||
resize_target_width_ = bounds.width;
|
||||
resize_target_height_ = bounds.height;
|
||||
}
|
||||
@ -668,9 +674,24 @@ void FlutterWindowsView::UpdateSemanticsEnabled(bool enabled) {
|
||||
}
|
||||
|
||||
void FlutterWindowsView::OnDwmCompositionChanged() {
|
||||
if (engine_->surface_manager()) {
|
||||
engine_->surface_manager()->SetVSyncEnabled(binding_handler_->NeedsVSync());
|
||||
AngleSurfaceManager* surface_manager = engine_->surface_manager();
|
||||
if (!surface_manager) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the surface with the new composition state.
|
||||
// Switch to the raster thread as the render EGL context can only be
|
||||
// current on a single thread a time.
|
||||
auto needs_vsync = binding_handler_->NeedsVSync();
|
||||
engine_->PostRasterThreadTask([surface_manager, needs_vsync]() {
|
||||
if (!surface_manager->MakeCurrent()) {
|
||||
FML_LOG(ERROR)
|
||||
<< "Unable to make surface current to update the swap interval";
|
||||
return;
|
||||
}
|
||||
|
||||
surface_manager->SetVSyncEnabled(needs_vsync);
|
||||
});
|
||||
}
|
||||
|
||||
void FlutterWindowsView::OnWindowStateEvent(HWND hwnd, WindowStateEvent event) {
|
||||
|
||||
@ -1287,6 +1287,13 @@ TEST(FlutterWindowsViewTest, UpdatesVSyncOnDwmUpdates) {
|
||||
std::unique_ptr<MockAngleSurfaceManager> surface_manager =
|
||||
std::make_unique<MockAngleSurfaceManager>();
|
||||
|
||||
EXPECT_CALL(*engine.get(), PostRasterThreadTask)
|
||||
.Times(2)
|
||||
.WillRepeatedly([](fml::closure callback) {
|
||||
callback();
|
||||
return true;
|
||||
});
|
||||
|
||||
EXPECT_CALL(*window_binding_handler.get(), NeedsVSync)
|
||||
.WillOnce(Return(true))
|
||||
.WillOnce(Return(false));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user