From 00d7d23fb5b97fd71f28a53ab4dcc9493ec585a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Sharma?= <737941+loic-sharma@users.noreply.github.com> Date: Wed, 20 Dec 2023 17:23:20 -0800 Subject: [PATCH] Reland "[Windows] Move to FlutterCompositor for rendering" (#49262) ## Reland https://github.com/flutter/engine/pull/48849 was reverted as it incorrectly expected to receive always 1 layer. However, the engine will present 0 layers on an ["empty" app](https://github.com/flutter/flutter/blob/6981fe6fd3aacb95bfc4a6c427ee5d493f43c5dc/dev/integration_tests/ui/lib/empty.dart#L8-L19). This pull request is split into two commits: 1. https://github.com/flutter/engine/pull/49262/commits/df604a16c0047e26ae30df6a9c83b8fb7e5572fb is the original pull request, unchanged 2. https://github.com/flutter/engine/pull/49262/commits/c30b36928981e454a9caf692767e709757227bb2 adds the ability to "clear" the view if the engine presents 0 layers ## Original pull request description This migrates the Windows embedder to `FlutterCompositor` so that the engine renders off-screen to a framebuffer instead of directly onto the window's surface. This will allow us to support platform views and multiple views on Windows. Addresses https://github.com/flutter/flutter/issues/128904 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style --- ci/licenses_golden/excluded_files | 2 + ci/licenses_golden/licenses_flutter | 10 + .../renderer/backend/gles/description_gles.cc | 4 + .../renderer/backend/gles/description_gles.h | 2 + .../linux/fl_backing_store_provider.cc | 3 +- shell/platform/windows/BUILD.gn | 9 + shell/platform/windows/compositor.h | 39 ++++ shell/platform/windows/compositor_opengl.cc | 188 ++++++++++++++++ shell/platform/windows/compositor_opengl.h | 61 +++++ .../windows/compositor_opengl_unittests.cc | 208 ++++++++++++++++++ shell/platform/windows/compositor_software.cc | 65 ++++++ shell/platform/windows/compositor_software.h | 35 +++ .../windows/compositor_software_unittests.cc | 139 ++++++++++++ shell/platform/windows/flutter_window.cc | 13 +- shell/platform/windows/flutter_window.h | 3 + .../windows/flutter_windows_engine.cc | 71 ++++-- .../platform/windows/flutter_windows_engine.h | 5 + .../platform/windows/flutter_windows_view.cc | 29 ++- shell/platform/windows/flutter_windows_view.h | 31 ++- .../windows/flutter_windows_view_unittests.cc | 51 ++++- .../testing/mock_window_binding_handler.h | 1 + .../platform/windows/window_binding_handler.h | 7 +- 22 files changed, 932 insertions(+), 44 deletions(-) create mode 100644 shell/platform/windows/compositor.h create mode 100644 shell/platform/windows/compositor_opengl.cc create mode 100644 shell/platform/windows/compositor_opengl.h create mode 100644 shell/platform/windows/compositor_opengl_unittests.cc create mode 100644 shell/platform/windows/compositor_software.cc create mode 100644 shell/platform/windows/compositor_software.h create mode 100644 shell/platform/windows/compositor_software_unittests.cc diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index 3e41760234bc0..e4b2b12355467 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -369,6 +369,8 @@ ../../../flutter/shell/platform/windows/client_wrapper/flutter_view_unittests.cc ../../../flutter/shell/platform/windows/client_wrapper/plugin_registrar_windows_unittests.cc ../../../flutter/shell/platform/windows/client_wrapper/testing +../../../flutter/shell/platform/windows/compositor_opengl_unittests.cc +../../../flutter/shell/platform/windows/compositor_software_unittests.cc ../../../flutter/shell/platform/windows/cursor_handler_unittests.cc ../../../flutter/shell/platform/windows/direct_manipulation_unittests.cc ../../../flutter/shell/platform/windows/dpi_utils_unittests.cc diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 9a1de1da99d92..9a00e8054cbed 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -7201,6 +7201,11 @@ ORIGIN: ../../../flutter/shell/platform/windows/client_wrapper/include/flutter/f ORIGIN: ../../../flutter/shell/platform/windows/client_wrapper/include/flutter/flutter_view.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/client_wrapper/include/flutter/flutter_view_controller.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/client_wrapper/include/flutter/plugin_registrar_windows.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/windows/compositor.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/windows/compositor_opengl.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/windows/compositor_opengl.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/windows/compositor_software.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/windows/compositor_software.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/cursor_handler.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/cursor_handler.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/direct_manipulation.cc + ../../../flutter/LICENSE @@ -10039,6 +10044,11 @@ FILE: ../../../flutter/shell/platform/windows/client_wrapper/include/flutter/flu FILE: ../../../flutter/shell/platform/windows/client_wrapper/include/flutter/flutter_view.h FILE: ../../../flutter/shell/platform/windows/client_wrapper/include/flutter/flutter_view_controller.h FILE: ../../../flutter/shell/platform/windows/client_wrapper/include/flutter/plugin_registrar_windows.h +FILE: ../../../flutter/shell/platform/windows/compositor.h +FILE: ../../../flutter/shell/platform/windows/compositor_opengl.cc +FILE: ../../../flutter/shell/platform/windows/compositor_opengl.h +FILE: ../../../flutter/shell/platform/windows/compositor_software.cc +FILE: ../../../flutter/shell/platform/windows/compositor_software.h FILE: ../../../flutter/shell/platform/windows/cursor_handler.cc FILE: ../../../flutter/shell/platform/windows/cursor_handler.h FILE: ../../../flutter/shell/platform/windows/direct_manipulation.cc diff --git a/impeller/renderer/backend/gles/description_gles.cc b/impeller/renderer/backend/gles/description_gles.cc index a2142c1191444..40d583bca535d 100644 --- a/impeller/renderer/backend/gles/description_gles.cc +++ b/impeller/renderer/backend/gles/description_gles.cc @@ -156,6 +156,10 @@ std::string DescriptionGLES::GetString() const { return stream.str(); } +Version DescriptionGLES::GetGlVersion() const { + return gl_version_; +} + bool DescriptionGLES::IsES() const { return is_es_; } diff --git a/impeller/renderer/backend/gles/description_gles.h b/impeller/renderer/backend/gles/description_gles.h index dac13cab61422..818ea0d0cc358 100644 --- a/impeller/renderer/backend/gles/description_gles.h +++ b/impeller/renderer/backend/gles/description_gles.h @@ -27,6 +27,8 @@ class DescriptionGLES { std::string GetString() const; + Version GetGlVersion() const; + bool HasExtension(const std::string& ext) const; /// @brief Returns whether GLES includes the debug extension. diff --git a/shell/platform/linux/fl_backing_store_provider.cc b/shell/platform/linux/fl_backing_store_provider.cc index 643e9a343621e..f5f4bf7115ec8 100644 --- a/shell/platform/linux/fl_backing_store_provider.cc +++ b/shell/platform/linux/fl_backing_store_provider.cc @@ -82,7 +82,8 @@ uint32_t fl_backing_store_provider_get_gl_format(FlBackingStoreProvider* self) { // In Linux kN32_SkColorType is assumed to be kBGRA_8888_SkColorType. // So we must choose a valid gl format to be compatible with surface format // BGRA8. - // Following logics are copied from Skia GrGLCaps.cpp. + // Following logics are copied from Skia GrGLCaps.cpp: + // https://github.com/google/skia/blob/4738ed711e03212aceec3cd502a4adb545f38e63/src/gpu/ganesh/gl/GrGLCaps.cpp#L1963-L2116 if (epoxy_is_desktop_gl()) { // For OpenGL. diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index ecc9080965993..4bebc496bfd37 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -42,6 +42,11 @@ source_set("flutter_windows_source") { "accessibility_bridge_windows.h", "angle_surface_manager.cc", "angle_surface_manager.h", + "compositor.h", + "compositor_opengl.cc", + "compositor_opengl.h", + "compositor_software.cc", + "compositor_software.h", "cursor_handler.cc", "cursor_handler.h", "direct_manipulation.cc", @@ -134,6 +139,7 @@ source_set("flutter_windows_source") { deps = [ ":flutter_windows_headers", "//flutter/fml:fml", + "//flutter/impeller/renderer/backend/gles", "//flutter/shell/platform/common:common_cpp", "//flutter/shell/platform/common:common_cpp_input", "//flutter/shell/platform/common:common_cpp_switches", @@ -175,6 +181,8 @@ executable("flutter_windows_unittests") { # Common Windows test sources. sources = [ "accessibility_bridge_windows_unittests.cc", + "compositor_opengl_unittests.cc", + "compositor_software_unittests.cc", "cursor_handler_unittests.cc", "direct_manipulation_unittests.cc", "dpi_utils_unittests.cc", @@ -235,6 +243,7 @@ executable("flutter_windows_unittests") { ":flutter_windows_fixtures", ":flutter_windows_headers", ":flutter_windows_source", + "//flutter/impeller/renderer/backend/gles", "//flutter/shell/platform/common:common_cpp", "//flutter/shell/platform/common/client_wrapper:client_wrapper", "//flutter/shell/platform/embedder:embedder_as_internal_library", diff --git a/shell/platform/windows/compositor.h b/shell/platform/windows/compositor.h new file mode 100644 index 0000000000000..c7691f53e8510 --- /dev/null +++ b/shell/platform/windows/compositor.h @@ -0,0 +1,39 @@ +// 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_COMPOSITOR_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_COMPOSITOR_H_ + +#include "flutter/shell/platform/embedder/embedder.h" + +namespace flutter { + +// Enables the Flutter engine to render content on Windows. +// +// The engine uses this to: +// +// 1. Create backing stores used for rendering Flutter content +// 2. Composite and present Flutter content and platform views onto a view +// +// Platform views are not yet supported. +class Compositor { + public: + virtual ~Compositor() = default; + + // Creates a backing store used for rendering Flutter content. + // + // The backing store's configuration is stored in |backing_store_out|. + virtual bool CreateBackingStore(const FlutterBackingStoreConfig& config, + FlutterBackingStore* backing_store_out) = 0; + + // Destroys a backing store and releases its resources. + virtual bool CollectBackingStore(const FlutterBackingStore* store) = 0; + + // Present Flutter content and platform views onto the view. + virtual bool Present(const FlutterLayer** layers, size_t layers_count) = 0; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_COMPOSITOR_H_ diff --git a/shell/platform/windows/compositor_opengl.cc b/shell/platform/windows/compositor_opengl.cc new file mode 100644 index 0000000000000..6da274b99399e --- /dev/null +++ b/shell/platform/windows/compositor_opengl.cc @@ -0,0 +1,188 @@ +// 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/compositor_opengl.h" + +#include "GLES3/gl3.h" +#include "flutter/shell/platform/windows/flutter_windows_view.h" + +namespace flutter { + +namespace { + +constexpr uint32_t kWindowFrameBufferId = 0; + +// The metadata for an OpenGL framebuffer backing store. +struct FramebufferBackingStore { + uint32_t framebuffer_id; + uint32_t texture_id; +}; + +// Based off Skia's logic: +// https://github.com/google/skia/blob/4738ed711e03212aceec3cd502a4adb545f38e63/src/gpu/ganesh/gl/GrGLCaps.cpp#L1963-L2116 +int GetSupportedTextureFormat(const impeller::DescriptionGLES* description) { + if (description->HasExtension("GL_EXT_texture_format_BGRA8888")) { + return GL_BGRA8_EXT; + } else if (description->HasExtension("GL_APPLE_texture_format_BGRA8888") && + description->GetGlVersion().IsAtLeast(impeller::Version(3, 0))) { + return GL_BGRA8_EXT; + } else { + return GL_RGBA8; + } +} + +} // namespace + +CompositorOpenGL::CompositorOpenGL(FlutterWindowsEngine* engine, + impeller::ProcTableGLES::Resolver resolver) + : engine_(engine), resolver_(resolver) {} + +bool CompositorOpenGL::CreateBackingStore( + const FlutterBackingStoreConfig& config, + FlutterBackingStore* result) { + if (!is_initialized_ && !Initialize()) { + return false; + } + + auto store = std::make_unique(); + + gl_->GenTextures(1, &store->texture_id); + gl_->GenFramebuffers(1, &store->framebuffer_id); + + gl_->BindFramebuffer(GL_FRAMEBUFFER, store->framebuffer_id); + + gl_->BindTexture(GL_TEXTURE_2D, store->texture_id); + gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + gl_->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, config.size.width, + config.size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + gl_->BindTexture(GL_TEXTURE_2D, 0); + + gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT, + GL_TEXTURE_2D, store->texture_id, 0); + + result->type = kFlutterBackingStoreTypeOpenGL; + result->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; + result->open_gl.framebuffer.name = store->framebuffer_id; + result->open_gl.framebuffer.target = format_; + result->open_gl.framebuffer.user_data = store.release(); + result->open_gl.framebuffer.destruction_callback = [](void* user_data) { + // Backing store destroyed in `CompositorOpenGL::CollectBackingStore`, set + // on FlutterCompositor.collect_backing_store_callback during engine start. + }; + return true; +} + +bool CompositorOpenGL::CollectBackingStore(const FlutterBackingStore* store) { + FML_DCHECK(is_initialized_); + FML_DCHECK(store->type == kFlutterBackingStoreTypeOpenGL); + FML_DCHECK(store->open_gl.type == kFlutterOpenGLTargetTypeFramebuffer); + + auto user_data = static_cast( + store->open_gl.framebuffer.user_data); + + gl_->DeleteFramebuffers(1, &user_data->framebuffer_id); + gl_->DeleteTextures(1, &user_data->texture_id); + + delete user_data; + return true; +} + +bool CompositorOpenGL::Present(const FlutterLayer** layers, + size_t layers_count) { + if (!engine_->view()) { + 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 + // created. However, on an empty frame no backing stores are created and + // the present needs to initialize the compositor. + if (!is_initialized_ && !Initialize()) { + return false; + } + + return ClearSurface(); + } + + // TODO: Support compositing layers and platform views. + // See: https://github.com/flutter/flutter/issues/31713 + FML_DCHECK(is_initialized_); + FML_DCHECK(layers_count == 1); + FML_DCHECK(layers[0]->offset.x == 0 && layers[0]->offset.y == 0); + FML_DCHECK(layers[0]->type == kFlutterLayerContentTypeBackingStore); + FML_DCHECK(layers[0]->backing_store->type == kFlutterBackingStoreTypeOpenGL); + FML_DCHECK(layers[0]->backing_store->open_gl.type == + kFlutterOpenGLTargetTypeFramebuffer); + + auto width = layers[0]->size.width; + auto height = layers[0]->size.height; + + // Check if this frame can be presented. This resizes the surface if a resize + // is pending and |width| and |height| match the target size. + if (!engine_->view()->OnFrameGenerated(width, height)) { + return false; + } + + if (!engine_->surface_manager()->MakeCurrent()) { + return false; + } + + auto source_id = layers[0]->backing_store->open_gl.framebuffer.name; + gl_->BindFramebuffer(GL_READ_FRAMEBUFFER, source_id); + gl_->BindFramebuffer(GL_DRAW_FRAMEBUFFER, kWindowFrameBufferId); + + gl_->BlitFramebuffer(0, // srcX0 + 0, // srcY0 + width, // srcX1 + height, // srcY1 + 0, // dstX0 + 0, // dstY0 + width, // dstX1 + height, // dstY1 + GL_COLOR_BUFFER_BIT, // mask + GL_NEAREST // filter + ); + + return engine_->view()->SwapBuffers(); +} + +bool CompositorOpenGL::Initialize() { + FML_DCHECK(!is_initialized_); + + if (!engine_->surface_manager()->MakeCurrent()) { + return false; + } + + gl_ = std::make_unique(resolver_); + if (!gl_->IsValid()) { + gl_.reset(); + return false; + } + + format_ = GetSupportedTextureFormat(gl_->GetDescription()); + is_initialized_ = true; + return true; +} + +bool CompositorOpenGL::ClearSurface() { + FML_DCHECK(is_initialized_); + + // Resize the surface if needed. + engine_->view()->OnEmptyFrameGenerated(); + + if (!engine_->surface_manager()->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); + + return engine_->view()->SwapBuffers(); +} + +} // namespace flutter diff --git a/shell/platform/windows/compositor_opengl.h b/shell/platform/windows/compositor_opengl.h new file mode 100644 index 0000000000000..06c22202332e2 --- /dev/null +++ b/shell/platform/windows/compositor_opengl.h @@ -0,0 +1,61 @@ +// 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_COMPOSITOR_OPENGL_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_COMPOSITOR_OPENGL_H_ + +#include + +#include "flutter/impeller/renderer/backend/gles/proc_table_gles.h" +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/windows/compositor.h" +#include "flutter/shell/platform/windows/flutter_windows_engine.h" + +namespace flutter { + +// Enables the Flutter engine to render content on Windows using OpenGL. +class CompositorOpenGL : public Compositor { + public: + CompositorOpenGL(FlutterWindowsEngine* engine, + impeller::ProcTableGLES::Resolver resolver); + + /// |Compositor| + bool CreateBackingStore(const FlutterBackingStoreConfig& config, + FlutterBackingStore* result) override; + + /// |Compositor| + bool CollectBackingStore(const FlutterBackingStore* store) override; + + /// |Compositor| + bool Present(const FlutterLayer** layers, size_t layers_count) override; + + private: + // The Flutter engine that manages the views to render. + FlutterWindowsEngine* engine_; + + private: + // The compositor initializes itself lazily once |CreateBackingStore| is + // called. True if initialization completed successfully. + bool is_initialized_ = false; + + // Function used to resolve GLES functions. + impeller::ProcTableGLES::Resolver resolver_ = nullptr; + + // Table of resolved GLES functions. Null until the compositor is initialized. + std::unique_ptr gl_ = nullptr; + + // The OpenGL texture target format for backing stores. Invalid value until + // the compositor is initialized. + uint32_t format_ = 0; + + // Initialize the compositor. This must run on the raster thread. + bool Initialize(); + + // Clear the view's surface and removes any previously presented layers. + bool ClearSurface(); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_COMPOSITOR_OPENGL_H_ diff --git a/shell/platform/windows/compositor_opengl_unittests.cc b/shell/platform/windows/compositor_opengl_unittests.cc new file mode 100644 index 0000000000000..e6cd9039ba255 --- /dev/null +++ b/shell/platform/windows/compositor_opengl_unittests.cc @@ -0,0 +1,208 @@ +// 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 +#include + +#include "flutter/impeller/renderer/backend/gles/gles.h" +#include "flutter/shell/platform/windows/angle_surface_manager.h" +#include "flutter/shell/platform/windows/compositor_opengl.h" +#include "flutter/shell/platform/windows/flutter_windows_view.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" +#include "flutter/shell/platform/windows/testing/windows_test.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +namespace { +using ::testing::Return; + +const unsigned char* MockGetString(GLenum name) { + switch (name) { + case GL_VERSION: + case GL_SHADING_LANGUAGE_VERSION: + return reinterpret_cast("3.0"); + default: + return reinterpret_cast(""); + } +} + +void MockGetIntegerv(GLenum name, int* value) { + *value = 0; +} + +GLenum MockGetError() { + return GL_NO_ERROR; +} + +void DoNothing() {} + +const impeller::ProcTableGLES::Resolver kMockResolver = [](const char* name) { + std::string function_name{name}; + + if (function_name == "glGetString") { + return reinterpret_cast(&MockGetString); + } else if (function_name == "glGetIntegerv") { + return reinterpret_cast(&MockGetIntegerv); + } else if (function_name == "glGetError") { + return reinterpret_cast(&MockGetError); + } else { + return reinterpret_cast(&DoNothing); + } +}; + +class MockAngleSurfaceManager : public AngleSurfaceManager { + public: + MockAngleSurfaceManager() : AngleSurfaceManager(false) {} + + MOCK_METHOD(bool, MakeCurrent, (), (override)); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(MockAngleSurfaceManager); +}; + +class MockFlutterWindowsView : public FlutterWindowsView { + public: + MockFlutterWindowsView(std::unique_ptr window) + : FlutterWindowsView(std::move(window)) {} + virtual ~MockFlutterWindowsView() = default; + + MOCK_METHOD(bool, SwapBuffers, (), (override)); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindowsView); +}; + +class CompositorOpenGLTest : public WindowsTest { + public: + CompositorOpenGLTest() = default; + virtual ~CompositorOpenGLTest() = default; + + protected: + FlutterWindowsEngine* engine() { return engine_.get(); } + MockFlutterWindowsView* view() { return view_.get(); } + MockAngleSurfaceManager* surface_manager() { return surface_manager_; } + + void UseHeadlessEngine() { + auto surface_manager = std::make_unique(); + surface_manager_ = surface_manager.get(); + + FlutterWindowsEngineBuilder builder{GetContext()}; + + engine_ = builder.Build(); + EngineModifier modifier(engine_.get()); + modifier.SetSurfaceManager(std::move(surface_manager)); + } + + void UseEngineWithView() { + UseHeadlessEngine(); + + auto window = std::make_unique(); + EXPECT_CALL(*window.get(), SetView).Times(1); + EXPECT_CALL(*window.get(), GetRenderTarget).WillRepeatedly(Return(nullptr)); + + view_ = std::make_unique(std::move(window)); + + engine_->SetView(view_.get()); + } + + private: + std::unique_ptr engine_; + std::unique_ptr view_; + MockAngleSurfaceManager* surface_manager_; + + FML_DISALLOW_COPY_AND_ASSIGN(CompositorOpenGLTest); +}; + +} // namespace + +TEST_F(CompositorOpenGLTest, CreateBackingStore) { + UseHeadlessEngine(); + + auto compositor = CompositorOpenGL{engine(), kMockResolver}; + + FlutterBackingStoreConfig config = {}; + FlutterBackingStore backing_store = {}; + + EXPECT_CALL(*surface_manager(), MakeCurrent).WillOnce(Return(true)); + ASSERT_TRUE(compositor.CreateBackingStore(config, &backing_store)); + ASSERT_TRUE(compositor.CollectBackingStore(&backing_store)); +} + +TEST_F(CompositorOpenGLTest, InitializationFailure) { + UseHeadlessEngine(); + + auto compositor = CompositorOpenGL{engine(), kMockResolver}; + + FlutterBackingStoreConfig config = {}; + FlutterBackingStore backing_store = {}; + + EXPECT_CALL(*surface_manager(), MakeCurrent).WillOnce(Return(false)); + EXPECT_FALSE(compositor.CreateBackingStore(config, &backing_store)); +} + +TEST_F(CompositorOpenGLTest, Present) { + UseEngineWithView(); + + auto compositor = CompositorOpenGL{engine(), kMockResolver}; + + FlutterBackingStoreConfig config = {}; + FlutterBackingStore backing_store = {}; + + EXPECT_CALL(*surface_manager(), MakeCurrent).WillOnce(Return(true)); + ASSERT_TRUE(compositor.CreateBackingStore(config, &backing_store)); + + FlutterLayer layer = {}; + layer.type = kFlutterLayerContentTypeBackingStore; + layer.backing_store = &backing_store; + const FlutterLayer* layer_ptr = &layer; + + EXPECT_CALL(*surface_manager(), MakeCurrent).WillOnce(Return(true)); + EXPECT_CALL(*view(), SwapBuffers).WillOnce(Return(true)); + EXPECT_TRUE(compositor.Present(&layer_ptr, 1)); + + ASSERT_TRUE(compositor.CollectBackingStore(&backing_store)); +} + +TEST_F(CompositorOpenGLTest, PresentEmpty) { + UseEngineWithView(); + + auto compositor = CompositorOpenGL{engine(), kMockResolver}; + + // The context will be bound twice: first to initialize the compositor, second + // to clear the surface. + EXPECT_CALL(*surface_manager(), MakeCurrent) + .Times(2) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*view(), SwapBuffers).WillOnce(Return(true)); + EXPECT_TRUE(compositor.Present(nullptr, 0)); +} + +TEST_F(CompositorOpenGLTest, HeadlessPresentIgnored) { + UseHeadlessEngine(); + + auto compositor = CompositorOpenGL{engine(), kMockResolver}; + + FlutterBackingStoreConfig config = {}; + FlutterBackingStore backing_store = {}; + + EXPECT_CALL(*surface_manager(), MakeCurrent).WillOnce(Return(true)); + ASSERT_TRUE(compositor.CreateBackingStore(config, &backing_store)); + + FlutterLayer layer = {}; + layer.type = kFlutterLayerContentTypeBackingStore; + layer.backing_store = &backing_store; + const FlutterLayer* layer_ptr = &layer; + + EXPECT_FALSE(compositor.Present(&layer_ptr, 1)); + + ASSERT_TRUE(compositor.CollectBackingStore(&backing_store)); +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/windows/compositor_software.cc b/shell/platform/windows/compositor_software.cc new file mode 100644 index 0000000000000..6f6353cbe269c --- /dev/null +++ b/shell/platform/windows/compositor_software.cc @@ -0,0 +1,65 @@ +// 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/compositor_software.h" + +#include "flutter/shell/platform/windows/flutter_windows_view.h" + +namespace flutter { + +CompositorSoftware::CompositorSoftware(FlutterWindowsEngine* engine) + : engine_(engine) {} + +bool CompositorSoftware::CreateBackingStore( + const FlutterBackingStoreConfig& config, + FlutterBackingStore* result) { + size_t size = config.size.width * config.size.height * 4; + void* allocation = std::calloc(size, sizeof(uint8_t)); + if (!allocation) { + return false; + } + + result->type = kFlutterBackingStoreTypeSoftware; + result->software.allocation = allocation; + result->software.height = config.size.height; + result->software.row_bytes = config.size.width * 4; + result->software.user_data = nullptr; + result->software.destruction_callback = [](void* user_data) { + // Backing store destroyed in `CompositorSoftware::CollectBackingStore`, set + // on FlutterCompositor.collect_backing_store_callback during engine start. + }; + return true; +} + +bool CompositorSoftware::CollectBackingStore(const FlutterBackingStore* store) { + std::free(const_cast(store->software.allocation)); + return true; +} + +bool CompositorSoftware::Present(const FlutterLayer** layers, + size_t layers_count) { + if (!engine_->view()) { + return false; + } + + // Clear the view if there are no layers to present. + if (layers_count == 0) { + return engine_->view()->ClearSoftwareBitmap(); + } + + // TODO: Support compositing layers and platform views. + // See: https://github.com/flutter/flutter/issues/31713 + FML_DCHECK(layers_count == 1); + FML_DCHECK(layers[0]->offset.x == 0 && layers[0]->offset.y == 0); + FML_DCHECK(layers[0]->type == kFlutterLayerContentTypeBackingStore); + FML_DCHECK(layers[0]->backing_store->type == + kFlutterBackingStoreTypeSoftware); + + const auto& backing_store = layers[0]->backing_store->software; + + return engine_->view()->PresentSoftwareBitmap( + backing_store.allocation, backing_store.row_bytes, backing_store.height); +} + +} // namespace flutter diff --git a/shell/platform/windows/compositor_software.h b/shell/platform/windows/compositor_software.h new file mode 100644 index 0000000000000..c4e39111b3c1f --- /dev/null +++ b/shell/platform/windows/compositor_software.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_COMPOSITOR_SOFTWARE_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_COMPOSITOR_SOFTWARE_H_ + +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/windows/compositor.h" +#include "flutter/shell/platform/windows/flutter_windows_engine.h" + +namespace flutter { + +// Enables the Flutter engine to render content on Windows using software +// rasterization and bitmaps. +class CompositorSoftware : public Compositor { + public: + CompositorSoftware(FlutterWindowsEngine* engine); + + /// |Compositor| + bool CreateBackingStore(const FlutterBackingStoreConfig& config, + FlutterBackingStore* result) override; + /// |Compositor| + bool CollectBackingStore(const FlutterBackingStore* store) override; + + /// |Compositor| + bool Present(const FlutterLayer** layers, size_t layers_count) override; + + private: + FlutterWindowsEngine* engine_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_COMPOSITOR_SOFTWARE_H_ diff --git a/shell/platform/windows/compositor_software_unittests.cc b/shell/platform/windows/compositor_software_unittests.cc new file mode 100644 index 0000000000000..6665fa7cd70fb --- /dev/null +++ b/shell/platform/windows/compositor_software_unittests.cc @@ -0,0 +1,139 @@ +// 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 +#include + +#include "flutter/shell/platform/windows/compositor_software.h" +#include "flutter/shell/platform/windows/flutter_windows_view.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" +#include "flutter/shell/platform/windows/testing/windows_test.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +namespace { +using ::testing::Return; + +class MockFlutterWindowsView : public FlutterWindowsView { + public: + MockFlutterWindowsView(std::unique_ptr window) + : FlutterWindowsView(std::move(window)) {} + virtual ~MockFlutterWindowsView() = default; + + MOCK_METHOD(bool, + PresentSoftwareBitmap, + (const void* allocation, size_t row_bytes, size_t height), + (override)); + MOCK_METHOD(bool, ClearSoftwareBitmap, (), (override)); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(MockFlutterWindowsView); +}; + +class CompositorSoftwareTest : public WindowsTest { + public: + CompositorSoftwareTest() = default; + virtual ~CompositorSoftwareTest() = default; + + protected: + FlutterWindowsEngine* engine() { return engine_.get(); } + MockFlutterWindowsView* view() { return view_.get(); } + + void UseHeadlessEngine() { + FlutterWindowsEngineBuilder builder{GetContext()}; + + engine_ = builder.Build(); + } + + void UseEngineWithView() { + FlutterWindowsEngineBuilder builder{GetContext()}; + + auto window = std::make_unique(); + EXPECT_CALL(*window.get(), SetView).Times(1); + EXPECT_CALL(*window.get(), GetRenderTarget).WillRepeatedly(Return(nullptr)); + + engine_ = builder.Build(); + view_ = std::make_unique(std::move(window)); + + engine_->SetView(view_.get()); + } + + private: + std::unique_ptr engine_; + std::unique_ptr view_; + + FML_DISALLOW_COPY_AND_ASSIGN(CompositorSoftwareTest); +}; + +} // namespace + +TEST_F(CompositorSoftwareTest, CreateBackingStore) { + UseHeadlessEngine(); + + auto compositor = CompositorSoftware{engine()}; + + FlutterBackingStoreConfig config = {}; + FlutterBackingStore backing_store = {}; + + ASSERT_TRUE(compositor.CreateBackingStore(config, &backing_store)); + ASSERT_TRUE(compositor.CollectBackingStore(&backing_store)); +} + +TEST_F(CompositorSoftwareTest, Present) { + UseEngineWithView(); + + auto compositor = CompositorSoftware{engine()}; + + FlutterBackingStoreConfig config = {}; + FlutterBackingStore backing_store = {}; + + ASSERT_TRUE(compositor.CreateBackingStore(config, &backing_store)); + + FlutterLayer layer = {}; + layer.type = kFlutterLayerContentTypeBackingStore; + layer.backing_store = &backing_store; + const FlutterLayer* layer_ptr = &layer; + + EXPECT_CALL(*view(), PresentSoftwareBitmap).WillOnce(Return(true)); + EXPECT_TRUE(compositor.Present(&layer_ptr, 1)); + + ASSERT_TRUE(compositor.CollectBackingStore(&backing_store)); +} + +TEST_F(CompositorSoftwareTest, PresentEmpty) { + UseEngineWithView(); + + auto compositor = CompositorSoftware{engine()}; + + EXPECT_CALL(*view(), ClearSoftwareBitmap).WillOnce(Return(true)); + EXPECT_TRUE(compositor.Present(nullptr, 0)); +} + +TEST_F(CompositorSoftwareTest, HeadlessPresentIgnored) { + UseHeadlessEngine(); + + auto compositor = CompositorSoftware{engine()}; + + FlutterBackingStoreConfig config = {}; + FlutterBackingStore backing_store = {}; + + ASSERT_TRUE(compositor.CreateBackingStore(config, &backing_store)); + + FlutterLayer layer = {}; + layer.type = kFlutterLayerContentTypeBackingStore; + layer.backing_store = &backing_store; + const FlutterLayer* layer_ptr = &layer; + + EXPECT_FALSE(compositor.Present(&layer_ptr, 1)); + + ASSERT_TRUE(compositor.CollectBackingStore(&backing_store)); +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/windows/flutter_window.cc b/shell/platform/windows/flutter_window.cc index 831faeab9cda8..2b8adf61bfe84 100644 --- a/shell/platform/windows/flutter_window.cc +++ b/shell/platform/windows/flutter_window.cc @@ -199,7 +199,7 @@ void FlutterWindow::OnWindowResized() { DwmFlush(); } -void FlutterWindow::OnDpiScale(unsigned int dpi){}; +void FlutterWindow::OnDpiScale(unsigned int dpi) {}; // When DesktopWindow notifies that a WM_Size message has come in // lets FlutterEngine know about the new size. @@ -322,6 +322,13 @@ void FlutterWindow::OnResetImeComposing() { AbortImeComposing(); } +bool FlutterWindow::OnBitmapSurfaceCleared() { + HDC dc = ::GetDC(GetWindowHandle()); + bool result = ::PatBlt(dc, 0, 0, current_width_, current_height_, BLACKNESS); + ::ReleaseDC(GetWindowHandle(), dc); + return result; +} + bool FlutterWindow::OnBitmapSurfaceUpdated(const void* allocation, size_t row_bytes, size_t height) { @@ -334,8 +341,8 @@ bool FlutterWindow::OnBitmapSurfaceUpdated(const void* allocation, bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biSizeImage = 0; - int ret = SetDIBitsToDevice(dc, 0, 0, row_bytes / 4, height, 0, 0, 0, height, - allocation, &bmi, DIB_RGB_COLORS); + int ret = ::SetDIBitsToDevice(dc, 0, 0, row_bytes / 4, height, 0, 0, 0, + height, allocation, &bmi, DIB_RGB_COLORS); ::ReleaseDC(GetWindowHandle(), dc); return ret != 0; } diff --git a/shell/platform/windows/flutter_window.h b/shell/platform/windows/flutter_window.h index f758c0e71ac20..8846dd051e076 100644 --- a/shell/platform/windows/flutter_window.h +++ b/shell/platform/windows/flutter_window.h @@ -178,6 +178,9 @@ class FlutterWindow : public KeyboardManager::WindowDelegate, // |FlutterWindowBindingHandler| virtual void OnWindowResized() override; + // |FlutterWindowBindingHandler| + virtual bool OnBitmapSurfaceCleared() override; + // |FlutterWindowBindingHandler| virtual bool OnBitmapSurfaceUpdated(const void* allocation, size_t row_bytes, diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index 8f093f046c644..3bf140c38b7cd 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -17,6 +17,8 @@ #include "flutter/shell/platform/common/path_utils.h" #include "flutter/shell/platform/embedder/embedder_struct_macros.h" #include "flutter/shell/platform/windows/accessibility_bridge_windows.h" +#include "flutter/shell/platform/windows/compositor_opengl.h" +#include "flutter/shell/platform/windows/compositor_software.h" #include "flutter/shell/platform/windows/flutter_windows_view.h" #include "flutter/shell/platform/windows/keyboard_key_channel_handler.h" #include "flutter/shell/platform/windows/system_utils.h" @@ -65,23 +67,11 @@ FlutterRendererConfig GetOpenGLRendererConfig() { } return host->surface_manager()->ClearContext(); }; - config.open_gl.present = [](void* user_data) -> bool { - auto host = static_cast(user_data); - if (!host->view()) { - return false; - } - return host->view()->SwapBuffers(); - }; + config.open_gl.present = [](void* user_data) -> bool { FML_UNREACHABLE(); }; config.open_gl.fbo_reset_after_present = true; config.open_gl.fbo_with_frame_info_callback = [](void* user_data, const FlutterFrameInfo* info) -> uint32_t { - auto host = static_cast(user_data); - if (host->view()) { - return host->view()->GetFrameBufferId(info->size.width, - info->size.height); - } else { - return kWindowFrameBufferID; - } + FML_UNREACHABLE(); }; config.open_gl.gl_proc_resolver = [](void* user_data, const char* what) -> void* { @@ -115,16 +105,12 @@ FlutterRendererConfig GetSoftwareRendererConfig() { FlutterRendererConfig config = {}; config.type = kSoftware; config.software.struct_size = sizeof(config.software); - config.software.surface_present_callback = [](void* user_data, - const void* allocation, - size_t row_bytes, - size_t height) { - auto host = static_cast(user_data); - if (!host->view()) { - return false; - } - return host->view()->PresentSoftwareBitmap(allocation, row_bytes, height); - }; + config.software.surface_present_callback = + [](void* user_data, const void* allocation, size_t row_bytes, + size_t height) { + FML_UNREACHABLE(); + return false; + }; return config; } @@ -394,6 +380,43 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) { args.custom_task_runners = &custom_task_runners; + if (surface_manager_) { + auto resolver = [](const char* name) -> void* { + return reinterpret_cast(::eglGetProcAddress(name)); + }; + + compositor_ = std::make_unique(this, resolver); + } else { + compositor_ = std::make_unique(this); + } + + FlutterCompositor compositor = {}; + compositor.struct_size = sizeof(FlutterCompositor); + compositor.user_data = this; + compositor.create_backing_store_callback = + [](const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out, void* user_data) -> bool { + auto host = static_cast(user_data); + + return host->compositor_->CreateBackingStore(*config, backing_store_out); + }; + + compositor.collect_backing_store_callback = + [](const FlutterBackingStore* backing_store, void* user_data) -> bool { + auto host = static_cast(user_data); + + return host->compositor_->CollectBackingStore(backing_store); + }; + + compositor.present_layers_callback = [](const FlutterLayer** layers, + size_t layers_count, + void* user_data) -> bool { + auto host = static_cast(user_data); + + return host->compositor_->Present(layers, layers_count); + }; + args.compositor = &compositor; + if (aot_data_) { args.aot_data = aot_data_.get(); } diff --git a/shell/platform/windows/flutter_windows_engine.h b/shell/platform/windows/flutter_windows_engine.h index 4406b4235b36f..9e60f860f6e3c 100644 --- a/shell/platform/windows/flutter_windows_engine.h +++ b/shell/platform/windows/flutter_windows_engine.h @@ -23,6 +23,7 @@ #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/windows/accessibility_bridge_windows.h" #include "flutter/shell/platform/windows/angle_surface_manager.h" +#include "flutter/shell/platform/windows/compositor.h" #include "flutter/shell/platform/windows/cursor_handler.h" #include "flutter/shell/platform/windows/flutter_desktop_messenger.h" #include "flutter/shell/platform/windows/flutter_project_bundle.h" @@ -367,6 +368,10 @@ class FlutterWindowsEngine { // May be nullptr if ANGLE failed to initialize. std::unique_ptr surface_manager_; + // The compositor that creates backing stores for the engine to render into + // and then presents them onto views. + std::unique_ptr compositor_; + // The plugin registrar managing internal plugins. std::unique_ptr internal_plugin_registrar_; diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index 0f1aa9b81f400..856337f2a5cf0 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -105,12 +105,28 @@ void FlutterWindowsView::SetEngine(FlutterWindowsEngine* engine) { binding_handler_->GetDpiScale()); } -uint32_t FlutterWindowsView::GetFrameBufferId(size_t width, size_t height) { - // Called on an engine-controlled (non-platform) thread. +void FlutterWindowsView::OnEmptyFrameGenerated() { + // Called on the raster thread. std::unique_lock lock(resize_mutex_); if (resize_status_ != ResizeState::kResizeStarted) { - return kWindowFrameBufferID; + return; + } + + // Platform thread is blocked for the entire duration until the + // resize_status_ is set to kDone. + engine_->surface_manager()->ResizeSurface( + GetRenderTarget(), resize_target_width_, resize_target_height_, + binding_handler_->NeedsVSync()); + resize_status_ = ResizeState::kFrameGenerated; +} + +bool FlutterWindowsView::OnFrameGenerated(size_t width, size_t height) { + // Called on the raster thread. + std::unique_lock lock(resize_mutex_); + + if (resize_status_ != ResizeState::kResizeStarted) { + return true; } if (resize_target_width_ == width && resize_target_height_ == height) { @@ -119,9 +135,10 @@ uint32_t FlutterWindowsView::GetFrameBufferId(size_t width, size_t height) { engine_->surface_manager()->ResizeSurface(GetRenderTarget(), width, height, binding_handler_->NeedsVSync()); resize_status_ = ResizeState::kFrameGenerated; + return true; } - return kWindowFrameBufferID; + return false; } void FlutterWindowsView::UpdateFlutterCursor(const std::string& cursor_name) { @@ -607,6 +624,10 @@ bool FlutterWindowsView::SwapBuffers() { } } +bool FlutterWindowsView::ClearSoftwareBitmap() { + return binding_handler_->OnBitmapSurfaceCleared(); +} + bool FlutterWindowsView::PresentSoftwareBitmap(const void* allocation, size_t row_bytes, size_t height) { diff --git a/shell/platform/windows/flutter_windows_view.h b/shell/platform/windows/flutter_windows_view.h index 55125f8551fa2..ba5d3222d7dda 100644 --- a/shell/platform/windows/flutter_windows_view.h +++ b/shell/platform/windows/flutter_windows_view.h @@ -26,9 +26,6 @@ namespace flutter { -// ID for the window frame buffer. -inline constexpr uint32_t kWindowFrameBufferID = 0; - // An OS-windowing neutral abstration for a Flutter view that works // with win32 HWNDs. class FlutterWindowsView : public WindowBindingHandlerDelegate { @@ -68,15 +65,21 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate { // Swap the view's surface buffers. Must be called on the engine's raster // thread. Returns true if the buffers were swapped. // + // |OnFrameGenerated| or |OnEmptyFrameGenerated| must be called before this + // method. + // // If the view is resizing, this returns false if the frame is not the target // size. Otherwise, it unblocks the platform thread and blocks the raster // thread until the v-blank. - bool SwapBuffers(); + virtual bool SwapBuffers(); + + // Callback to clear a previously presented software bitmap. + virtual bool ClearSoftwareBitmap(); // Callback for presenting a software bitmap. - bool PresentSoftwareBitmap(const void* allocation, - size_t row_bytes, - size_t height); + virtual bool PresentSoftwareBitmap(const void* allocation, + size_t row_bytes, + size_t height); // Send initial bounds to embedder. Must occur after engine has initialized. void SendInitialBounds(); @@ -87,8 +90,18 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate { // |WindowBindingHandlerDelegate| void OnHighContrastChanged() override; - // Returns the frame buffer id for the engine to render to. - uint32_t GetFrameBufferId(size_t width, size_t height); + // Called on the raster thread when |CompositorOpenGL| receives an empty + // frame. + // + // This resizes the surface if a resize is pending. + void OnEmptyFrameGenerated(); + + // Called on the raster thread when |CompositorOpenGL| receives a frame. + // Returns true if the frame can be presented. + // + // This resizes the surface if a resize is pending and |width| and + // |height| match the target size. + bool OnFrameGenerated(size_t width, size_t height); // Sets the cursor that should be used when the mouse is over the Flutter // content. See mouse_cursor.dart for the values and meanings of cursor_name. diff --git a/shell/platform/windows/flutter_windows_view_unittests.cc b/shell/platform/windows/flutter_windows_view_unittests.cc index de1379bf55290..84ed57360fcde 100644 --- a/shell/platform/windows/flutter_windows_view_unittests.cc +++ b/shell/platform/windows/flutter_windows_view_unittests.cc @@ -849,8 +849,55 @@ TEST(FlutterWindowsViewTest, WindowResizeTests) { // 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); + // Complete the window resize by reporting a frame with the new window size. + view.OnFrameGenerated(500, 500); + resized_latch.Wait(); +} + +// Verify that an empty frame completes a view resize. +TEST(FlutterWindowsViewTest, TestEmptyFrameResizes) { + std::unique_ptr engine = GetTestEngine(); + EngineModifier modifier(engine.get()); + + auto window_binding_handler = + std::make_unique>(); + std::unique_ptr surface_manager = + std::make_unique(); + + 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(std::move(surface_manager)); + view.SetEngine(engine.get()); + + fml::AutoResetWaitableEvent metrics_sent_latch; + modifier.embedder_api().SendWindowMetricsEvent = MOCK_ENGINE_PROC( + SendWindowMetricsEvent, + ([&metrics_sent_latch](auto engine, + const FlutterWindowMetricsEvent* event) { + metrics_sent_latch.Signal(); + return kSuccess; + })); + + 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); + resized_latch.Signal(); + }).detach(); + + // Wait until the platform thread has started the window resize. + metrics_sent_latch.Wait(); + + // Complete the window resize by reporting an empty frame. + view.OnEmptyFrameGenerated(); resized_latch.Wait(); } diff --git a/shell/platform/windows/testing/mock_window_binding_handler.h b/shell/platform/windows/testing/mock_window_binding_handler.h index 43be5abc71a43..55ec886e8159f 100644 --- a/shell/platform/windows/testing/mock_window_binding_handler.h +++ b/shell/platform/windows/testing/mock_window_binding_handler.h @@ -33,6 +33,7 @@ class MockWindowBindingHandler : public WindowBindingHandler { MOCK_METHOD(void, SetFlutterCursor, (HCURSOR cursor_name), (override)); MOCK_METHOD(void, OnCursorRectUpdated, (const Rect& rect), (override)); MOCK_METHOD(void, OnResetImeComposing, (), (override)); + MOCK_METHOD(bool, OnBitmapSurfaceCleared, (), (override)); MOCK_METHOD(bool, OnBitmapSurfaceUpdated, (const void* allocation, size_t row_bytes, size_t height), diff --git a/shell/platform/windows/window_binding_handler.h b/shell/platform/windows/window_binding_handler.h index 390e9235c5f23..76b2100c99e8b 100644 --- a/shell/platform/windows/window_binding_handler.h +++ b/shell/platform/windows/window_binding_handler.h @@ -82,7 +82,12 @@ class WindowBindingHandler { // Invoked when the cursor/composing rect has been updated in the framework. virtual void OnCursorRectUpdated(const Rect& rect) = 0; - // Invoked when the Embedder provides us with new bitmap data for the contents + // Invoked when the embedder clears the contents of this Flutter view. + // + // Returns whether the surface was successfully updated or not. + virtual bool OnBitmapSurfaceCleared() = 0; + + // Invoked when the embedder provides us with new bitmap data for the contents // of this Flutter view. // // Returns whether the surface was successfully updated or not.