diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index d02f36ecccf1c..96727aad11d2e 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -7241,6 +7241,10 @@ ORIGIN: ../../../flutter/shell/platform/windows/egl/manager.cc + ../../../flutte ORIGIN: ../../../flutter/shell/platform/windows/egl/manager.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/egl/proc_table.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/egl/proc_table.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/windows/egl/surface.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/windows/egl/surface.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/windows/egl/window_surface.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/windows/egl/window_surface.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/event_watcher.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/event_watcher.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/external_texture.h + ../../../flutter/LICENSE @@ -10115,6 +10119,10 @@ FILE: ../../../flutter/shell/platform/windows/egl/manager.cc FILE: ../../../flutter/shell/platform/windows/egl/manager.h FILE: ../../../flutter/shell/platform/windows/egl/proc_table.cc FILE: ../../../flutter/shell/platform/windows/egl/proc_table.h +FILE: ../../../flutter/shell/platform/windows/egl/surface.cc +FILE: ../../../flutter/shell/platform/windows/egl/surface.h +FILE: ../../../flutter/shell/platform/windows/egl/window_surface.cc +FILE: ../../../flutter/shell/platform/windows/egl/window_surface.h FILE: ../../../flutter/shell/platform/windows/event_watcher.cc FILE: ../../../flutter/shell/platform/windows/event_watcher.h FILE: ../../../flutter/shell/platform/windows/external_texture.h diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index bfaa73ebda19f..7a30f737b69b6 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -59,6 +59,10 @@ source_set("flutter_windows_source") { "egl/manager.h", "egl/proc_table.cc", "egl/proc_table.h", + "egl/surface.cc", + "egl/surface.h", + "egl/window_surface.cc", + "egl/window_surface.h", "event_watcher.cc", "event_watcher.h", "external_texture.h", @@ -209,6 +213,7 @@ executable("flutter_windows_unittests") { "testing/egl/mock_context.h", "testing/egl/mock_manager.h", "testing/egl/mock_proc_table.h", + "testing/egl/mock_window_surface.h", "testing/engine_modifier.h", "testing/flutter_window_test.cc", "testing/flutter_window_test.h", diff --git a/shell/platform/windows/compositor_opengl.cc b/shell/platform/windows/compositor_opengl.cc index de203bfa56023..6d0262d1e9168 100644 --- a/shell/platform/windows/compositor_opengl.cc +++ b/shell/platform/windows/compositor_opengl.cc @@ -97,6 +97,11 @@ bool CompositorOpenGL::Present(const FlutterLayer** layers, return false; } + if (!engine_->egl_manager()->surface() || + !engine_->egl_manager()->surface()->IsValid()) { + return false; + } + // Clear the view if there are no layers to present. if (layers_count == 0) { // Normally the compositor is initialized when the first backing store is @@ -128,7 +133,7 @@ bool CompositorOpenGL::Present(const FlutterLayer** layers, return false; } - if (!engine_->egl_manager()->MakeCurrent()) { + if (!engine_->egl_manager()->surface()->MakeCurrent()) { return false; } @@ -154,7 +159,7 @@ bool CompositorOpenGL::Present(const FlutterLayer** layers, GL_NEAREST // filter ); - if (!engine_->egl_manager()->SwapBuffers()) { + if (!engine_->egl_manager()->surface()->SwapBuffers()) { return false; } @@ -165,7 +170,7 @@ bool CompositorOpenGL::Present(const FlutterLayer** layers, bool CompositorOpenGL::Initialize() { FML_DCHECK(!is_initialized_); - if (!engine_->egl_manager()->MakeCurrent()) { + if (!engine_->egl_manager()->surface()->MakeCurrent()) { return false; } @@ -186,14 +191,14 @@ bool CompositorOpenGL::ClearSurface() { // Resize the surface if needed. engine_->view()->OnEmptyFrameGenerated(); - if (!engine_->egl_manager()->MakeCurrent()) { + if (!engine_->egl_manager()->surface()->MakeCurrent()) { return false; } gl_->ClearColor(0.0f, 0.0f, 0.0f, 0.0f); gl_->Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - if (!engine_->egl_manager()->SwapBuffers()) { + if (!engine_->egl_manager()->surface()->SwapBuffers()) { return false; } diff --git a/shell/platform/windows/compositor_opengl_unittests.cc b/shell/platform/windows/compositor_opengl_unittests.cc index fffdd7ea4b38e..abb872b2fda29 100644 --- a/shell/platform/windows/compositor_opengl_unittests.cc +++ b/shell/platform/windows/compositor_opengl_unittests.cc @@ -10,6 +10,7 @@ #include "flutter/shell/platform/windows/egl/manager.h" #include "flutter/shell/platform/windows/flutter_windows_view.h" #include "flutter/shell/platform/windows/testing/egl/mock_manager.h" +#include "flutter/shell/platform/windows/testing/egl/mock_window_surface.h" #include "flutter/shell/platform/windows/testing/engine_modifier.h" #include "flutter/shell/platform/windows/testing/flutter_windows_engine_builder.h" #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h" @@ -65,10 +66,14 @@ class CompositorOpenGLTest : public WindowsTest { protected: FlutterWindowsEngine* engine() { return engine_.get(); } egl::MockManager* egl_manager() { return egl_manager_; } + egl::MockWindowSurface* surface() { return surface_.get(); } void UseHeadlessEngine() { auto egl_manager = std::make_unique(); egl_manager_ = egl_manager.get(); + surface_ = std::make_unique(); + + EXPECT_CALL(*egl_manager_, surface).WillRepeatedly(Return(surface_.get())); FlutterWindowsEngineBuilder builder{GetContext()}; @@ -92,6 +97,7 @@ class CompositorOpenGLTest : public WindowsTest { private: std::unique_ptr engine_; std::unique_ptr view_; + std::unique_ptr surface_; egl::MockManager* egl_manager_; FML_DISALLOW_COPY_AND_ASSIGN(CompositorOpenGLTest); @@ -107,7 +113,7 @@ TEST_F(CompositorOpenGLTest, CreateBackingStore) { FlutterBackingStoreConfig config = {}; FlutterBackingStore backing_store = {}; - EXPECT_CALL(*egl_manager(), MakeCurrent).WillOnce(Return(true)); + EXPECT_CALL(*surface(), MakeCurrent).WillOnce(Return(true)); ASSERT_TRUE(compositor.CreateBackingStore(config, &backing_store)); ASSERT_TRUE(compositor.CollectBackingStore(&backing_store)); } @@ -120,7 +126,7 @@ TEST_F(CompositorOpenGLTest, InitializationFailure) { FlutterBackingStoreConfig config = {}; FlutterBackingStore backing_store = {}; - EXPECT_CALL(*egl_manager(), MakeCurrent).WillOnce(Return(false)); + EXPECT_CALL(*surface(), MakeCurrent).WillOnce(Return(false)); EXPECT_FALSE(compositor.CreateBackingStore(config, &backing_store)); } @@ -132,7 +138,7 @@ TEST_F(CompositorOpenGLTest, Present) { FlutterBackingStoreConfig config = {}; FlutterBackingStore backing_store = {}; - EXPECT_CALL(*egl_manager(), MakeCurrent).WillOnce(Return(true)); + EXPECT_CALL(*surface(), MakeCurrent).WillOnce(Return(true)); ASSERT_TRUE(compositor.CreateBackingStore(config, &backing_store)); FlutterLayer layer = {}; @@ -140,8 +146,8 @@ TEST_F(CompositorOpenGLTest, Present) { layer.backing_store = &backing_store; const FlutterLayer* layer_ptr = &layer; - EXPECT_CALL(*egl_manager(), MakeCurrent).WillOnce(Return(true)); - EXPECT_CALL(*egl_manager(), SwapBuffers).WillOnce(Return(true)); + EXPECT_CALL(*surface(), MakeCurrent).WillOnce(Return(true)); + EXPECT_CALL(*surface(), SwapBuffers).WillOnce(Return(true)); EXPECT_TRUE(compositor.Present(&layer_ptr, 1)); ASSERT_TRUE(compositor.CollectBackingStore(&backing_store)); @@ -154,10 +160,8 @@ TEST_F(CompositorOpenGLTest, PresentEmpty) { // The context will be bound twice: first to initialize the compositor, second // to clear the surface. - EXPECT_CALL(*egl_manager(), MakeCurrent) - .Times(2) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*egl_manager(), SwapBuffers).WillOnce(Return(true)); + EXPECT_CALL(*surface(), MakeCurrent).Times(2).WillRepeatedly(Return(true)); + EXPECT_CALL(*surface(), SwapBuffers).WillOnce(Return(true)); EXPECT_TRUE(compositor.Present(nullptr, 0)); } @@ -169,7 +173,7 @@ TEST_F(CompositorOpenGLTest, HeadlessPresentIgnored) { FlutterBackingStoreConfig config = {}; FlutterBackingStore backing_store = {}; - EXPECT_CALL(*egl_manager(), MakeCurrent).WillOnce(Return(true)); + EXPECT_CALL(*surface(), MakeCurrent).WillOnce(Return(true)); ASSERT_TRUE(compositor.CreateBackingStore(config, &backing_store)); FlutterLayer layer = {}; diff --git a/shell/platform/windows/egl/context.h b/shell/platform/windows/egl/context.h index f3703c896815e..acbff12a8d140 100644 --- a/shell/platform/windows/egl/context.h +++ b/shell/platform/windows/egl/context.h @@ -14,7 +14,7 @@ namespace egl { // An EGL context to interact with OpenGL. // -// This enables automatic eror logging and mocking. +// This enables automatic error logging and mocking. // // Flutter Windows uses this to create render and resource contexts. class Context { diff --git a/shell/platform/windows/egl/manager.cc b/shell/platform/windows/egl/manager.cc index 406786c3e4b14..346ea84e270b9 100644 --- a/shell/platform/windows/egl/manager.cc +++ b/shell/platform/windows/egl/manager.cc @@ -263,92 +263,71 @@ bool Manager::IsValid() const { return is_valid_; } -bool Manager::CreateSurface(HWND hwnd, EGLint width, EGLint height) { +bool Manager::CreateWindowSurface(HWND hwnd, size_t width, size_t height) { + FML_DCHECK(surface_ == nullptr || !surface_->IsValid()); + if (!hwnd || !is_valid_) { return false; } - EGLSurface surface = EGL_NO_SURFACE; - // Disable ANGLE's automatic surface resizing and provide an explicit size. // The surface will need to be destroyed and re-created if the HWND is // resized. - const EGLint surface_attributes[] = { - EGL_FIXED_SIZE_ANGLE, EGL_TRUE, EGL_WIDTH, width, - EGL_HEIGHT, height, EGL_NONE}; - - surface = ::eglCreateWindowSurface(display_, config_, - static_cast(hwnd), - surface_attributes); + const EGLint surface_attributes[] = {EGL_FIXED_SIZE_ANGLE, + EGL_TRUE, + EGL_WIDTH, + static_cast(width), + EGL_HEIGHT, + static_cast(height), + EGL_NONE}; + + auto const surface = ::eglCreateWindowSurface( + display_, config_, static_cast(hwnd), + surface_attributes); if (surface == EGL_NO_SURFACE) { LogEGLError("Surface creation failed."); return false; } - surface_width_ = width; - surface_height_ = height; - surface_ = surface; + surface_ = std::make_unique( + display_, render_context_->GetHandle(), surface, width, height); return true; } -void Manager::ResizeSurface(HWND hwnd, - EGLint width, - EGLint height, - bool vsync_enabled) { - EGLint existing_width, existing_height; - GetSurfaceDimensions(&existing_width, &existing_height); - if (width != existing_width || height != existing_height) { - surface_width_ = width; - surface_height_ = height; +void Manager::ResizeWindowSurface(HWND hwnd, size_t width, size_t height) { + FML_CHECK(surface_ != nullptr); + auto const existing_width = surface_->width(); + auto const existing_height = surface_->height(); + auto const existing_vsync = surface_->vsync_enabled(); + + if (width != existing_width || height != existing_height) { // TODO: Destroying the surface and re-creating it is expensive. // Ideally this would use ANGLE's automatic surface sizing instead. // See: https://github.com/flutter/flutter/issues/79427 - render_context_->ClearCurrent(); - DestroySurface(); - if (!CreateSurface(hwnd, width, height)) { - FML_LOG(ERROR) << "Manager::ResizeSurface failed to create surface"; + if (!surface_->Destroy()) { + FML_LOG(ERROR) << "Manager::ResizeSurface failed to destroy surface"; + return; } - } - SetVSyncEnabled(vsync_enabled); -} - -void Manager::GetSurfaceDimensions(EGLint* width, EGLint* height) { - if (surface_ == EGL_NO_SURFACE || !is_valid_) { - *width = 0; - *height = 0; - return; - } - - // This avoids eglQuerySurface as ideally surfaces would be automatically - // sized by ANGLE to avoid expensive surface destroy & re-create. With - // automatic sizing, ANGLE could resize the surface before Flutter asks it to, - // which would break resize redraw synchronization. - *width = surface_width_; - *height = surface_height_; -} + if (!CreateWindowSurface(hwnd, width, height)) { + FML_LOG(ERROR) << "Manager::ResizeSurface failed to create surface"; + return; + } -void Manager::DestroySurface() { - if (display_ != EGL_NO_DISPLAY && surface_ != EGL_NO_SURFACE) { - ::eglDestroySurface(display_, surface_); + if (!surface_->SetVSyncEnabled(existing_vsync)) { + // Surfaces block until the v-blank by default. + // Failing to update the vsync might result in unnecessary blocking. + // This regresses performance but not correctness. + FML_LOG(ERROR) << "Manager::ResizeSurface failed to set vsync"; + } } - surface_ = EGL_NO_SURFACE; } bool Manager::HasContextCurrent() { return ::eglGetCurrentContext() != EGL_NO_CONTEXT; } -bool Manager::MakeCurrent() { - return (::eglMakeCurrent(display_, surface_, surface_, - render_context_->GetHandle()) == EGL_TRUE); -} - -bool Manager::SwapBuffers() { - return (::eglSwapBuffers(display_, surface_)); -} - EGLSurface Manager::CreateSurfaceFromHandle(EGLenum handle_type, EGLClientBuffer handle, const EGLint* attributes) const { @@ -356,23 +335,6 @@ EGLSurface Manager::CreateSurfaceFromHandle(EGLenum handle_type, config_, attributes); } -void Manager::SetVSyncEnabled(bool enabled) { - if (!MakeCurrent()) { - 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(display_, enabled ? 1 : 0) != EGL_TRUE) { - LogEGLError("Unable to update the swap interval"); - return; - } -} - bool Manager::GetDevice(ID3D11Device** device) { if (!resolved_device_) { if (!InitializeDevice()) { @@ -392,5 +354,9 @@ Context* Manager::resource_context() const { return resource_context_.get(); } +WindowSurface* Manager::surface() const { + return surface_.get(); +} + } // namespace egl } // namespace flutter diff --git a/shell/platform/windows/egl/manager.h b/shell/platform/windows/egl/manager.h index 88535af0f013f..601b6bdf8e22f 100644 --- a/shell/platform/windows/egl/manager.h +++ b/shell/platform/windows/egl/manager.h @@ -20,6 +20,8 @@ #include "flutter/fml/macros.h" #include "flutter/shell/platform/windows/egl/context.h" +#include "flutter/shell/platform/windows/egl/surface.h" +#include "flutter/shell/platform/windows/egl/window_surface.h" namespace flutter { namespace egl { @@ -42,37 +44,19 @@ class Manager { // // After the surface is created, |SetVSyncEnabled| should be called on a // thread that can bind the |render_context_|. - virtual bool CreateSurface(HWND hwnd, EGLint width, EGLint height); + virtual bool CreateWindowSurface(HWND hwnd, size_t width, size_t height); // 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. + // not match current surface dimensions. HWND is the window backing the + // surface. // // This binds |render_context_| to the current thread. - virtual void ResizeSurface(HWND hwnd, - EGLint width, - EGLint height, - bool enable_vsync); - - // queries EGL for the dimensions of surface in physical - // pixels returning width and height as out params. - void GetSurfaceDimensions(EGLint* width, EGLint* height); - - // Releases the pass-in EGLSurface wrapping and backing resources if not null. - virtual void DestroySurface(); + virtual void ResizeWindowSurface(HWND hwnd, size_t width, size_t height); // Check if the current thread has a context bound. bool HasContextCurrent(); - // Binds |render_context_| to the current rendering thread and to the draw and - // read surfaces returning a boolean result reflecting success. - virtual bool MakeCurrent(); - - // Swaps the front and back buffers of the DX11 swapchain backing surface if - // not null. - virtual bool SwapBuffers(); - // Creates a |EGLSurface| from the provided handle. EGLSurface CreateSurfaceFromHandle(EGLenum handle_type, EGLClientBuffer handle, @@ -81,16 +65,6 @@ class Manager { // Gets the |EGLDisplay|. EGLDisplay egl_display() const { return display_; }; - // If enabled, makes the current surface's buffer swaps block until the - // v-blank. - // - // If disabled, allows one thread to swap multiple buffers per v-blank - // but can result in screen tearing if the system compositor is disabled. - // - // This binds |render_context_| to the current thread and makes the render - // surface current. - virtual void SetVSyncEnabled(bool enabled); - // Gets the |ID3D11Device| chosen by ANGLE. bool GetDevice(ID3D11Device** device); @@ -100,6 +74,9 @@ class Manager { // Get the EGL context used for async texture uploads. virtual Context* resource_context() const; + // Get the EGL surface that backs the Flutter view. + virtual WindowSurface* surface() const; + protected: // Creates a new surface manager retaining reference to the passed-in target // for the lifetime of the manager. @@ -138,12 +115,8 @@ class Manager { // The EGL context used for async texture uploads. std::unique_ptr resource_context_; - // Current render_surface that engine will draw into. - EGLSurface surface_ = EGL_NO_SURFACE; - - // Requested dimensions for current surface - EGLint surface_width_ = 0; - EGLint surface_height_ = 0; + // Th EGL surface used to render into the Flutter view. + std::unique_ptr surface_; // The current D3D device. Microsoft::WRL::ComPtr resolved_device_ = nullptr; diff --git a/shell/platform/windows/egl/surface.cc b/shell/platform/windows/egl/surface.cc new file mode 100644 index 0000000000000..da3872c175663 --- /dev/null +++ b/shell/platform/windows/egl/surface.cc @@ -0,0 +1,66 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/windows/egl/surface.h" + +#include "flutter/shell/platform/windows/egl/egl.h" + +namespace flutter { +namespace egl { + +Surface::Surface(EGLDisplay display, EGLContext context, EGLSurface surface) + : display_(display), context_(context), surface_(surface) {} + +Surface::~Surface() { + Destroy(); +} + +bool Surface::IsValid() const { + return is_valid_; +} + +bool Surface::Destroy() { + if (surface_ != EGL_NO_SURFACE) { + // Ensure the surface is not current before destroying it. + if (::eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT) != EGL_TRUE) { + WINDOWS_LOG_EGL_ERROR; + return false; + } + + if (::eglDestroySurface(display_, surface_) != EGL_TRUE) { + WINDOWS_LOG_EGL_ERROR; + return false; + } + } + + is_valid_ = false; + surface_ = EGL_NO_SURFACE; + return true; +} + +bool Surface::MakeCurrent() const { + if (::eglMakeCurrent(display_, surface_, surface_, context_) != EGL_TRUE) { + WINDOWS_LOG_EGL_ERROR; + return false; + } + + return true; +} + +bool Surface::SwapBuffers() const { + if (::eglSwapBuffers(display_, surface_) != EGL_TRUE) { + WINDOWS_LOG_EGL_ERROR; + return false; + } + + return true; +} + +const EGLSurface& Surface::GetHandle() const { + return surface_; +} + +} // namespace egl +} // namespace flutter diff --git a/shell/platform/windows/egl/surface.h b/shell/platform/windows/egl/surface.h new file mode 100644 index 0000000000000..eeea0349ebf5b --- /dev/null +++ b/shell/platform/windows/egl/surface.h @@ -0,0 +1,56 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_EGL_SURFACE_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_EGL_SURFACE_H_ + +#include + +#include "flutter/fml/macros.h" + +namespace flutter { +namespace egl { + +// An EGL surface. This can be window surface or an off-screen buffer. +// +// This enables automatic error logging and mocking. +class Surface { + public: + Surface(EGLDisplay display, EGLContext context, EGLSurface surface); + + ~Surface(); + + // Destroy the EGL surface and invalidate this object. + // + // This also unbinds the current context from the thread. + virtual bool Destroy(); + + // Check whether the EGL surface is valid. + virtual bool IsValid() const; + + // Bind the EGL surface's context and read and draw surfaces to the + // current thread. Returns true on success. + virtual bool MakeCurrent() const; + + // Swap the surface's front the and back buffers. Used to present content. + // Returns true on success. + virtual bool SwapBuffers() const; + + // Get the raw EGL surface. + virtual const EGLSurface& GetHandle() const; + + protected: + bool is_valid_ = true; + + EGLDisplay display_ = EGL_NO_DISPLAY; + EGLContext context_ = EGL_NO_CONTEXT; + EGLSurface surface_ = EGL_NO_SURFACE; + + FML_DISALLOW_COPY_AND_ASSIGN(Surface); +}; + +} // namespace egl +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_EGL_SURFACE_H_ diff --git a/shell/platform/windows/egl/window_surface.cc b/shell/platform/windows/egl/window_surface.cc new file mode 100644 index 0000000000000..47801e9a273fb --- /dev/null +++ b/shell/platform/windows/egl/window_surface.cc @@ -0,0 +1,46 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/windows/egl/window_surface.h" + +#include "flutter/shell/platform/windows/egl/egl.h" + +namespace flutter { +namespace egl { + +WindowSurface::WindowSurface(EGLDisplay display, + EGLContext context, + EGLSurface surface, + size_t width, + size_t height) + : Surface(display, context, surface), width_(width), height_(height) {} + +bool WindowSurface::SetVSyncEnabled(bool enabled) { + if (!MakeCurrent()) { + return false; + } + + if (::eglSwapInterval(display_, enabled ? 1 : 0) != EGL_TRUE) { + WINDOWS_LOG_EGL_ERROR; + return false; + } + + vsync_enabled_ = enabled; + return true; +} + +size_t WindowSurface::width() const { + return width_; +} + +size_t WindowSurface::height() const { + return height_; +} + +bool WindowSurface::vsync_enabled() const { + return vsync_enabled_; +} + +} // namespace egl +} // namespace flutter diff --git a/shell/platform/windows/egl/window_surface.h b/shell/platform/windows/egl/window_surface.h new file mode 100644 index 0000000000000..376b3fef7ede9 --- /dev/null +++ b/shell/platform/windows/egl/window_surface.h @@ -0,0 +1,55 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_EGL_WINDOW_SURFACE_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_EGL_WINDOW_SURFACE_H_ + +#include + +#include "flutter/fml/macros.h" +#include "flutter/shell/platform/windows/egl/surface.h" + +namespace flutter { +namespace egl { + +// An EGL surface used to render a Flutter view to a win32 HWND. +// +// This enables automatic error logging and mocking. +class WindowSurface : public Surface { + public: + WindowSurface(EGLDisplay display, + EGLContext context, + EGLSurface surface, + size_t width, + size_t height); + + // If enabled, makes the surface's buffer swaps block until the v-blank. + // + // If disabled, allows one thread to swap multiple buffers per v-blank + // but can result in screen tearing if the system compositor is disabled. + // + // This binds the EGL context and surface to the current thread. + virtual bool SetVSyncEnabled(bool enabled); + + // Get the surface's width in physical pixels. + virtual size_t width() const; + + // Get the surface's height in physical pixels. + virtual size_t height() const; + + // Get whether the surface's buffer swap blocks until the v-blank. + virtual bool vsync_enabled() const; + + private: + size_t width_ = 0; + size_t height_ = 0; + bool vsync_enabled_ = true; + + FML_DISALLOW_COPY_AND_ASSIGN(WindowSurface); +}; + +} // namespace egl +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_EGL_WINDOW_SURFACE_H_ diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index ad7450bb56561..a5766c16577cd 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -58,7 +58,7 @@ FlutterRendererConfig GetOpenGLRendererConfig() { if (!host->egl_manager()) { return false; } - return host->egl_manager()->MakeCurrent(); + return host->egl_manager()->render_context()->MakeCurrent(); }; config.open_gl.clear_current = [](void* user_data) -> bool { auto host = static_cast(user_data); diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index 5d7f45e50d399..bdea9793822f4 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -53,10 +53,10 @@ void UpdateVsync(const FlutterWindowsEngine& engine, bool needs_vsync) { // thread. if (engine.running()) { engine.PostRasterThreadTask([egl_manager, needs_vsync]() { - egl_manager->SetVSyncEnabled(needs_vsync); + egl_manager->surface()->SetVSyncEnabled(needs_vsync); }); } else { - egl_manager->SetVSyncEnabled(needs_vsync); + egl_manager->surface()->SetVSyncEnabled(needs_vsync); // Release the EGL context so that the raster thread can use it. if (!egl_manager->render_context()->ClearCurrent()) { @@ -116,8 +116,8 @@ void FlutterWindowsView::OnEmptyFrameGenerated() { // Platform thread is blocked for the entire duration until the // resize_status_ is set to kDone. - engine_->egl_manager()->ResizeSurface(GetWindowHandle(), resize_target_width_, - resize_target_height_, NeedsVsync()); + engine_->egl_manager()->ResizeWindowSurface( + GetWindowHandle(), resize_target_width_, resize_target_height_); resize_status_ = ResizeState::kFrameGenerated; } @@ -132,8 +132,8 @@ bool FlutterWindowsView::OnFrameGenerated(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_->egl_manager()->ResizeSurface(GetWindowHandle(), width, height, - NeedsVsync()); + engine_->egl_manager()->ResizeWindowSurface(GetWindowHandle(), width, + height); resize_status_ = ResizeState::kFrameGenerated; return true; } @@ -167,8 +167,8 @@ bool FlutterWindowsView::OnWindowSizeChanged(size_t width, size_t height) { // We're using OpenGL rendering. Resizing the surface must happen on the // raster thread. - EGLint surface_width, surface_height; - engine_->egl_manager()->GetSurfaceDimensions(&surface_width, &surface_height); + size_t surface_width = engine_->egl_manager()->surface()->width(); + size_t surface_height = engine_->egl_manager()->surface()->height(); bool surface_will_update = SurfaceWillUpdate(surface_width, surface_height, width, height); @@ -637,8 +637,8 @@ bool FlutterWindowsView::PresentSoftwareBitmap(const void* allocation, void FlutterWindowsView::CreateRenderSurface() { if (engine_ && engine_->egl_manager()) { PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds(); - engine_->egl_manager()->CreateSurface(GetWindowHandle(), bounds.width, - bounds.height); + engine_->egl_manager()->CreateWindowSurface(GetWindowHandle(), bounds.width, + bounds.height); UpdateVsync(*engine_, NeedsVsync()); @@ -648,9 +648,21 @@ void FlutterWindowsView::CreateRenderSurface() { } void FlutterWindowsView::DestroyRenderSurface() { - if (engine_ && engine_->egl_manager()) { - engine_->egl_manager()->DestroySurface(); + if (!engine_) { + return; + } + + auto const manager = engine_->egl_manager(); + if (!manager) { + return; } + + auto const surface = manager->surface(); + if (!surface) { + return; + } + + surface->Destroy(); } void FlutterWindowsView::OnHighContrastChanged() { diff --git a/shell/platform/windows/flutter_windows_view_unittests.cc b/shell/platform/windows/flutter_windows_view_unittests.cc index 2dfc0aca753fc..43c471848f086 100644 --- a/shell/platform/windows/flutter_windows_view_unittests.cc +++ b/shell/platform/windows/flutter_windows_view_unittests.cc @@ -20,6 +20,7 @@ #include "flutter/shell/platform/windows/flutter_windows_texture_registrar.h" #include "flutter/shell/platform/windows/testing/egl/mock_context.h" #include "flutter/shell/platform/windows/testing/egl/mock_manager.h" +#include "flutter/shell/platform/windows/testing/egl/mock_window_surface.h" #include "flutter/shell/platform/windows/testing/engine_modifier.h" #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h" #include "flutter/shell/platform/windows/testing/mock_windows_proc_table.h" @@ -230,12 +231,13 @@ TEST(FlutterWindowsViewTest, SubMenuExpandedState) { // The view's surface must be destroyed after the engine is shutdown. // See: https://github.com/flutter/flutter/issues/124463 TEST(FlutterWindowsViewTest, Shutdown) { - std::unique_ptr engine = - std::make_unique(); + auto engine = std::make_unique(); auto window_binding_handler = std::make_unique>(); - std::unique_ptr egl_manager = - std::make_unique(); + auto egl_manager = std::make_unique(); + auto surface = std::make_unique(); + + EXPECT_CALL(*egl_manager.get(), surface).WillOnce(Return(surface.get())); EngineModifier modifier(engine.get()); FlutterWindowsView view(std::move(window_binding_handler)); @@ -243,7 +245,7 @@ TEST(FlutterWindowsViewTest, Shutdown) { // The engine must be stopped before the surface can be destroyed. InSequence s; EXPECT_CALL(*engine.get(), Stop).Times(1); - EXPECT_CALL(*egl_manager.get(), DestroySurface).Times(1); + EXPECT_CALL(*surface.get(), Destroy).Times(1); modifier.SetEGLManager(std::move(egl_manager)); view.SetEngine(engine.get()); @@ -817,16 +819,16 @@ TEST(FlutterWindowsViewTest, WindowResizeTests) { auto window_binding_handler = std::make_unique>(); auto windows_proc_table = std::make_shared(); - std::unique_ptr egl_manager = - std::make_unique(); - - EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled) - .WillOnce(Return(true)); - EXPECT_CALL( - *egl_manager.get(), - ResizeSurface(_, /*width=*/500, /*height=*/500, /*enable_vsync=*/false)) + auto egl_manager = std::make_unique(); + auto surface = std::make_unique(); + + EXPECT_CALL(*windows_proc_table.get(), DwmFlush).WillOnce(Return(S_OK)); + EXPECT_CALL(*egl_manager.get(), surface) + .WillRepeatedly(Return(surface.get())); + EXPECT_CALL(*egl_manager.get(), + ResizeWindowSurface(_, /*width=*/500, /*height=*/500)) .Times(1); - EXPECT_CALL(*egl_manager.get(), DestroySurface).Times(1); + EXPECT_CALL(*surface.get(), Destroy).Times(1); FlutterWindowsView view(std::move(window_binding_handler), std::move(windows_proc_table)); @@ -867,16 +869,16 @@ TEST(FlutterWindowsViewTest, TestEmptyFrameResizes) { auto window_binding_handler = std::make_unique>(); auto windows_proc_table = std::make_shared(); - std::unique_ptr egl_manager = - std::make_unique(); - - EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled) - .WillOnce(Return(true)); - EXPECT_CALL( - *egl_manager.get(), - ResizeSurface(_, /*width=*/500, /*height=*/500, /*enable_vsync=*/false)) + auto egl_manager = std::make_unique(); + auto surface = std::make_unique(); + + EXPECT_CALL(*windows_proc_table.get(), DwmFlush).WillOnce(Return(S_OK)); + EXPECT_CALL(*egl_manager.get(), surface) + .WillRepeatedly(Return(surface.get())); + EXPECT_CALL(*egl_manager.get(), + ResizeWindowSurface(_, /*width=*/500, /*height=*/500)) .Times(1); - EXPECT_CALL(*egl_manager.get(), DestroySurface).Times(1); + EXPECT_CALL(*surface.get(), Destroy).Times(1); FlutterWindowsView view(std::move(window_binding_handler), std::move(windows_proc_table)); @@ -919,10 +921,12 @@ TEST(FlutterWindowsViewTest, WindowResizeRace) { auto window_binding_handler = std::make_unique>(); auto windows_proc_table = std::make_shared(); - std::unique_ptr egl_manager = - std::make_unique(); + auto egl_manager = std::make_unique(); + auto surface = std::make_unique(); - EXPECT_CALL(*egl_manager.get(), DestroySurface).Times(1); + EXPECT_CALL(*egl_manager.get(), surface) + .WillRepeatedly(Return(surface.get())); + EXPECT_CALL(*surface.get(), Destroy).Times(1); FlutterWindowsView view(std::move(window_binding_handler), std::move(windows_proc_table)); @@ -1299,14 +1303,13 @@ TEST(FlutterWindowsViewTest, TooltipNodeData) { // Don't block until the v-blank if it is disabled by the window. // The surface is updated on the platform thread at startup. TEST(FlutterWindowsViewTest, DisablesVSyncAtStartup) { - std::unique_ptr engine = - std::make_unique(); + auto engine = std::make_unique(); auto window_binding_handler = std::make_unique>(); auto windows_proc_table = std::make_shared(); - std::unique_ptr egl_manager = - std::make_unique(); + auto egl_manager = std::make_unique(); egl::MockContext render_context; + auto surface = std::make_unique(); EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(false)); EXPECT_CALL(*engine.get(), PostRasterThreadTask).Times(0); @@ -1314,21 +1317,24 @@ TEST(FlutterWindowsViewTest, DisablesVSyncAtStartup) { EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled) .WillOnce(Return(true)); + EXPECT_CALL(*egl_manager.get(), render_context) + .WillOnce(Return(&render_context)); + EXPECT_CALL(*egl_manager.get(), surface) + .WillRepeatedly(Return(surface.get())); + EngineModifier modifier(engine.get()); FlutterWindowsView view(std::move(window_binding_handler), std::move(windows_proc_table)); InSequence s; - EXPECT_CALL(*egl_manager.get(), CreateSurface(_, _, _)) + EXPECT_CALL(*egl_manager.get(), CreateWindowSurface(_, _, _)) .Times(1) .WillOnce(Return(true)); - EXPECT_CALL(*egl_manager.get(), SetVSyncEnabled(false)).Times(1); - EXPECT_CALL(*egl_manager.get(), render_context) - .WillOnce(Return(&render_context)); + EXPECT_CALL(*surface.get(), SetVSyncEnabled(false)).Times(1); EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true)); EXPECT_CALL(*engine.get(), Stop).Times(1); - EXPECT_CALL(*egl_manager.get(), DestroySurface).Times(1); + EXPECT_CALL(*surface.get(), Destroy).Times(1); modifier.SetEGLManager(std::move(egl_manager)); view.SetEngine(engine.get()); @@ -1339,35 +1345,37 @@ TEST(FlutterWindowsViewTest, DisablesVSyncAtStartup) { // Blocks until the v-blank if it is enabled by the window. // The surface is updated on the platform thread at startup. TEST(FlutterWindowsViewTest, EnablesVSyncAtStartup) { - std::unique_ptr engine = - std::make_unique(); + auto engine = std::make_unique(); auto window_binding_handler = std::make_unique>(); auto windows_proc_table = std::make_shared(); - std::unique_ptr egl_manager = - std::make_unique(); + auto egl_manager = std::make_unique(); egl::MockContext render_context; + auto surface = std::make_unique(); EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(false)); EXPECT_CALL(*engine.get(), PostRasterThreadTask).Times(0); EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled) .WillOnce(Return(false)); + EXPECT_CALL(*egl_manager.get(), render_context) + .WillOnce(Return(&render_context)); + EXPECT_CALL(*egl_manager.get(), surface) + .WillRepeatedly(Return(surface.get())); + EngineModifier modifier(engine.get()); FlutterWindowsView view(std::move(window_binding_handler), std::move(windows_proc_table)); InSequence s; - EXPECT_CALL(*egl_manager.get(), CreateSurface(_, _, _)) + EXPECT_CALL(*egl_manager.get(), CreateWindowSurface(_, _, _)) .Times(1) .WillOnce(Return(true)); - EXPECT_CALL(*egl_manager.get(), SetVSyncEnabled(true)).Times(1); - EXPECT_CALL(*egl_manager.get(), render_context) - .WillOnce(Return(&render_context)); + EXPECT_CALL(*surface.get(), SetVSyncEnabled(true)).Times(1); EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true)); EXPECT_CALL(*engine.get(), Stop).Times(1); - EXPECT_CALL(*egl_manager.get(), DestroySurface).Times(1); + EXPECT_CALL(*surface.get(), Destroy).Times(1); modifier.SetEGLManager(std::move(egl_manager)); view.SetEngine(engine.get()); @@ -1378,24 +1386,27 @@ TEST(FlutterWindowsViewTest, EnablesVSyncAtStartup) { // Don't block until the v-blank if it is disabled by the window. // The surface is updated on the raster thread if the engine is running. TEST(FlutterWindowsViewTest, DisablesVSyncAfterStartup) { - std::unique_ptr engine = - std::make_unique(); + auto engine = std::make_unique(); auto window_binding_handler = std::make_unique>(); auto windows_proc_table = std::make_shared(); - std::unique_ptr egl_manager = - std::make_unique(); + auto egl_manager = std::make_unique(); + auto surface = std::make_unique(); EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(true)); EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled) .WillOnce(Return(true)); + EXPECT_CALL(*egl_manager.get(), render_context).Times(0); + EXPECT_CALL(*egl_manager.get(), surface) + .WillRepeatedly(Return(surface.get())); + EngineModifier modifier(engine.get()); FlutterWindowsView view(std::move(window_binding_handler), std::move(windows_proc_table)); InSequence s; - EXPECT_CALL(*egl_manager.get(), CreateSurface(_, _, _)) + EXPECT_CALL(*egl_manager.get(), CreateWindowSurface(_, _, _)) .Times(1) .WillOnce(Return(true)); EXPECT_CALL(*engine.get(), PostRasterThreadTask) @@ -1403,11 +1414,9 @@ TEST(FlutterWindowsViewTest, DisablesVSyncAfterStartup) { callback(); return true; }); - EXPECT_CALL(*egl_manager.get(), SetVSyncEnabled(false)).Times(1); - EXPECT_CALL(*egl_manager.get(), render_context).Times(0); - + EXPECT_CALL(*surface.get(), SetVSyncEnabled(false)).Times(1); EXPECT_CALL(*engine.get(), Stop).Times(1); - EXPECT_CALL(*egl_manager.get(), DestroySurface).Times(1); + EXPECT_CALL(*surface.get(), Destroy).Times(1); modifier.SetEGLManager(std::move(egl_manager)); view.SetEngine(engine.get()); @@ -1418,25 +1427,28 @@ TEST(FlutterWindowsViewTest, DisablesVSyncAfterStartup) { // Blocks until the v-blank if it is enabled by the window. // The surface is updated on the raster thread if the engine is running. TEST(FlutterWindowsViewTest, EnablesVSyncAfterStartup) { - std::unique_ptr engine = - std::make_unique(); + auto engine = std::make_unique(); auto window_binding_handler = std::make_unique>(); auto windows_proc_table = std::make_shared(); - std::unique_ptr egl_manager = - std::make_unique(); + auto egl_manager = std::make_unique(); + auto surface = std::make_unique(); EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(true)); EXPECT_CALL(*windows_proc_table.get(), DwmIsCompositionEnabled) .WillOnce(Return(false)); + EXPECT_CALL(*egl_manager.get(), render_context).Times(0); + EXPECT_CALL(*egl_manager.get(), surface) + .WillRepeatedly(Return(surface.get())); + EngineModifier modifier(engine.get()); FlutterWindowsView view(std::move(window_binding_handler), std::move(windows_proc_table)); InSequence s; - EXPECT_CALL(*egl_manager.get(), CreateSurface(_, _, _)) + EXPECT_CALL(*egl_manager.get(), CreateWindowSurface(_, _, _)) .Times(1) .WillOnce(Return(true)); EXPECT_CALL(*engine.get(), PostRasterThreadTask) @@ -1444,11 +1456,10 @@ TEST(FlutterWindowsViewTest, EnablesVSyncAfterStartup) { callback(); return true; }); - EXPECT_CALL(*egl_manager.get(), SetVSyncEnabled(true)).Times(1); - EXPECT_CALL(*egl_manager.get(), render_context).Times(0); + EXPECT_CALL(*surface.get(), SetVSyncEnabled(true)).Times(1); EXPECT_CALL(*engine.get(), Stop).Times(1); - EXPECT_CALL(*egl_manager.get(), DestroySurface).Times(1); + EXPECT_CALL(*surface.get(), Destroy).Times(1); modifier.SetEGLManager(std::move(egl_manager)); view.SetEngine(engine.get()); @@ -1460,13 +1471,12 @@ TEST(FlutterWindowsViewTest, EnablesVSyncAfterStartup) { // 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 engine = std::make_unique(); auto window_binding_handler = std::make_unique>(); auto windows_proc_table = std::make_shared(); - std::unique_ptr egl_manager = - std::make_unique(); + auto egl_manager = std::make_unique(); + auto surface = std::make_unique(); EXPECT_CALL(*engine.get(), running).WillRepeatedly(Return(true)); @@ -1481,17 +1491,19 @@ TEST(FlutterWindowsViewTest, UpdatesVSyncOnDwmUpdates) { .WillOnce(Return(true)); EXPECT_CALL(*egl_manager.get(), render_context).Times(0); + EXPECT_CALL(*egl_manager.get(), surface) + .WillRepeatedly(Return(surface.get())); EngineModifier modifier(engine.get()); FlutterWindowsView view(std::move(window_binding_handler), std::move(windows_proc_table)); InSequence s; - EXPECT_CALL(*egl_manager.get(), SetVSyncEnabled(true)).Times(1); - EXPECT_CALL(*egl_manager.get(), SetVSyncEnabled(false)).Times(1); + EXPECT_CALL(*surface.get(), SetVSyncEnabled(true)).Times(1); + EXPECT_CALL(*surface.get(), SetVSyncEnabled(false)).Times(1); EXPECT_CALL(*engine.get(), Stop).Times(1); - EXPECT_CALL(*egl_manager.get(), DestroySurface).Times(1); + EXPECT_CALL(*surface.get(), Destroy).Times(1); modifier.SetEGLManager(std::move(egl_manager)); view.SetEngine(engine.get()); diff --git a/shell/platform/windows/testing/egl/mock_manager.h b/shell/platform/windows/testing/egl/mock_manager.h index e48df3ea738e4..640ad50361199 100644 --- a/shell/platform/windows/testing/egl/mock_manager.h +++ b/shell/platform/windows/testing/egl/mock_manager.h @@ -18,17 +18,12 @@ class MockManager : public flutter::egl::Manager { public: MockManager() : Manager(false) {} - MOCK_METHOD(bool, CreateSurface, (HWND, EGLint, EGLint), (override)); - MOCK_METHOD(void, ResizeSurface, (HWND, EGLint, EGLint, bool), (override)); - MOCK_METHOD(void, DestroySurface, (), (override)); - - MOCK_METHOD(bool, MakeCurrent, (), (override)); - MOCK_METHOD(void, SetVSyncEnabled, (bool), (override)); - - MOCK_METHOD(bool, SwapBuffers, (), (override)); + MOCK_METHOD(bool, CreateWindowSurface, (HWND, size_t, size_t), (override)); + MOCK_METHOD(void, ResizeWindowSurface, (HWND, size_t, size_t), (override)); MOCK_METHOD(flutter::egl::Context*, render_context, (), (const, override)); MOCK_METHOD(flutter::egl::Context*, resource_context, (), (const, override)); + MOCK_METHOD(flutter::egl::WindowSurface*, surface, (), (const, override)); private: FML_DISALLOW_COPY_AND_ASSIGN(MockManager); diff --git a/shell/platform/windows/testing/egl/mock_window_surface.h b/shell/platform/windows/testing/egl/mock_window_surface.h new file mode 100644 index 0000000000000..79c231eff58d6 --- /dev/null +++ b/shell/platform/windows/testing/egl/mock_window_surface.h @@ -0,0 +1,35 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_EGL_MOCK_WINDOW_SURFACE_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_EGL_MOCK_WINDOW_SURFACE_H_ + +#include "flutter/fml/macros.h" +#include "flutter/shell/platform/windows/egl/window_surface.h" +#include "gmock/gmock.h" + +namespace flutter { +namespace testing { +namespace egl { + +/// Mock for the |WindowSurface| base class. +class MockWindowSurface : public flutter::egl::WindowSurface { + public: + MockWindowSurface() + : WindowSurface(EGL_NO_DISPLAY, EGL_NO_CONTEXT, EGL_NO_SURFACE, 0, 0) {} + + MOCK_METHOD(bool, Destroy, (), (override)); + MOCK_METHOD(bool, MakeCurrent, (), (const, override)); + MOCK_METHOD(bool, SwapBuffers, (), (const, override)); + MOCK_METHOD(bool, SetVSyncEnabled, (bool), (override)); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(MockWindowSurface); +}; + +} // namespace egl +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_EGL_MOCK_WINDOW_SURFACE_H_