diff --git a/examples/glfw/BUILD.gn b/examples/glfw/BUILD.gn index 2ff312c0ee8b5..f7c1fd6df24e2 100644 --- a/examples/glfw/BUILD.gn +++ b/examples/glfw/BUILD.gn @@ -14,5 +14,9 @@ if (build_embedder_examples) { "//flutter/shell/platform/embedder:embedder", "//third_party/glfw", ] + + libs = [ + "EGL", + ] } } diff --git a/examples/glfw/CMakeLists.txt b/examples/glfw/CMakeLists.txt index 53bca275dc673..2563bd8bee05a 100644 --- a/examples/glfw/CMakeLists.txt +++ b/examples/glfw/CMakeLists.txt @@ -11,8 +11,10 @@ option(GLFW_BUILD_EXAMPLES "" OFF) option(GLFW_BUILD_TESTS "" OFF) option(GLFW_BUILD_DOCS "" OFF) option(GLFW_INSTALL "" OFF) +find_package(OpenGL REQUIRED COMPONENTS EGL) +include_directories(${OPENGL_INCLUDE_DIRS}) add_subdirectory(${CMAKE_SOURCE_DIR}/../../../third_party/glfw glfw) -target_link_libraries(flutter_glfw glfw) +target_link_libraries(flutter_glfw glfw OpenGL::EGL) include_directories(${CMAKE_SOURCE_DIR}/../../../third_party/glfw/include) ############################################################ diff --git a/examples/glfw/FlutterEmbedderGLFW.cc b/examples/glfw/FlutterEmbedderGLFW.cc index 8b17b25958fc1..8056fb81cf9cd 100644 --- a/examples/glfw/FlutterEmbedderGLFW.cc +++ b/examples/glfw/FlutterEmbedderGLFW.cc @@ -6,13 +6,26 @@ #include #include +#define GLFW_EXPOSE_NATIVE_EGL +#define GLFW_INCLUDE_GLEXT + +#include +#include #include "GLFW/glfw3.h" +#include "GLFW/glfw3native.h" #include "embedder.h" +#include +#include // This value is calculated after the window is created. static double g_pixelRatio = 1.0; static const size_t kInitialWindowWidth = 800; static const size_t kInitialWindowHeight = 600; +// Maximum damage history - for triple buffering we need to store damage for +// last two frames; Some Android devices (Pixel 4) use quad buffering. +static const int kMaxHistorySize = 10; + +std::list damage_history_; static_assert(FLUTTER_ENGINE_VERSION == 1, "This Flutter Embedder was authored against the stable Flutter " @@ -82,6 +95,24 @@ void GLFWwindowSizeCallback(GLFWwindow* window, int width, int height) { &event); } +std::array static RectToInts(EGLDisplay display, + EGLSurface surface, + const FlutterRect rect) { + EGLint height; + eglQuerySurface(display, surface, EGL_HEIGHT, &height); + + std::array res{static_cast(rect.left), height - static_cast(rect.bottom), static_cast(rect.right) - static_cast(rect.left), + static_cast(rect.bottom) - static_cast(rect.top)}; + return res; +} + +void JoinFlutterRect(FlutterRect* rect, FlutterRect additional_rect) { + rect->left = std::min(rect->left, additional_rect.left); + rect->top = std::min(rect->top, additional_rect.top); + rect->right = std::max(rect->right, additional_rect.right); + rect->bottom = std::max(rect->bottom, additional_rect.bottom); +} + bool RunFlutter(GLFWwindow* window, const std::string& project_path, const std::string& icudtl_path) { @@ -96,17 +127,80 @@ bool RunFlutter(GLFWwindow* window, glfwMakeContextCurrent(nullptr); // is this even a thing? return true; }; - config.open_gl.present = [](void* userdata) -> bool { - glfwSwapBuffers(static_cast(userdata)); + config.open_gl.present_with_info = [](void* userdata, const FlutterPresentInfo* info) -> bool { + PFNEGLSETDAMAGEREGIONKHRPROC set_damage_region_ = + reinterpret_cast( + eglGetProcAddress("eglSetDamageRegionKHR")); + PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage_ = + reinterpret_cast( + eglGetProcAddress("eglSwapBuffersWithDamageKHR")); + + GLFWwindow* window = static_cast(userdata); + EGLDisplay display = glfwGetEGLDisplay(); + EGLSurface surface = glfwGetEGLSurface(window); + + auto buffer_rects = RectToInts(display, surface, {0, 0, kInitialWindowWidth, kInitialWindowHeight}); + set_damage_region_(display, surface, buffer_rects.data(), 1); + + // Swap buffers with frame damage + auto frame_rects = RectToInts(display, surface, info->frame_damage.damage); + swap_buffers_with_damage_(display, surface, frame_rects.data(), 1); + + // Add frame damage to damage history + damage_history_.push_back(info->frame_damage.damage); + if (damage_history_.size() > kMaxHistorySize) { + damage_history_.pop_front(); + } + std::cout << "Buffer Damage: " << info->buffer_damage.damage.left << ", " << info->buffer_damage.damage.top << ", " << info->buffer_damage.damage.right << ", " << info->buffer_damage.damage.bottom << std::endl; + std::cout << "Frame Damage: " << info->frame_damage.damage.left << ", " << info->frame_damage.damage.top << ", " << info->frame_damage.damage.right << ", " << info->frame_damage.damage.bottom << std::endl; return true; }; - config.open_gl.fbo_callback = [](void*) -> uint32_t { - return 0; // FBO0 + config.open_gl.fbo_with_frame_info_callback = [](void* userdata, const FlutterFrameInfo* info) -> FlutterFrameBuffer { + // Given the FBO age, create existing damage region by joining all frame + // damages since FBO was last used + GLFWwindow* window = static_cast(userdata); + EGLDisplay display = glfwGetEGLDisplay(); + EGLSurface surface = glfwGetEGLSurface(window); + + EGLint age; + if (glfwExtensionSupported("GL_EXT_buffer_age") == GLFW_TRUE) { + eglQuerySurface(display, surface, EGL_BUFFER_AGE_EXT, &age); + } else { + age = 4; // Virtually no driver should have a swapchain length > 4. + } + std::cout << "Buffer age: " << age << std::endl; + + FlutterDamage existing_damage; + existing_damage.damage = {0, 0, kInitialWindowWidth, kInitialWindowHeight}; + + if (age > 1) { + --age; + // join up to (age - 1) last rects from damage history + for (auto i = damage_history_.rbegin(); + i != damage_history_.rend() && age > 0; ++i, --age) { + std::cout << "Damage in history: " << i->left << ", " << i->top << ", " << i->right << ", " << i->bottom << std::endl; + if (i == damage_history_.rbegin()) { + if (i != damage_history_.rend()) { + existing_damage.damage = {i->left, i->top, i->right, i->bottom}; + } + } else { + JoinFlutterRect(&(existing_damage.damage), *i); + } + } + } + + FlutterFrameBuffer fbo; + fbo.fbo_id = 0; // FBO0 + fbo.damage = existing_damage; + std::cout << "Existing Damage: " << fbo.damage.damage.left << ", " << fbo.damage.damage.top << ", " << fbo.damage.damage.right << ", " << fbo.damage.damage.bottom << std::endl; + return fbo; }; config.open_gl.gl_proc_resolver = [](void*, const char* name) -> void* { return reinterpret_cast(glfwGetProcAddress(name)); }; + config.open_gl.fbo_reset_after_present = true; + // This directory is generated by `flutter build bundle`. std::string assets_path = project_path + "/build/flutter_assets"; FlutterProjectArgs args = { diff --git a/examples/glfw/main.dart b/examples/glfw/main.dart index f1e74ab50be7c..7ee4c46088308 100644 --- a/examples/glfw/main.dart +++ b/examples/glfw/main.dart @@ -1,10 +1,10 @@ // 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. - import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart' show debugDefaultTargetPlatformOverride; +import 'package:flutter_spinkit/flutter_spinkit.dart'; void main() { // This is a hack to make Flutter think you are running on Google Fuchsia, @@ -12,7 +12,6 @@ void main() { debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; runApp(MyApp()); } - class MyApp extends StatelessWidget { // This widget is the root of your application. @override @@ -35,39 +34,20 @@ class MyApp extends StatelessWidget { ); } } - class MyHomePage extends StatefulWidget { MyHomePage({Key? key, required this.title}) : super(key: key); - // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect // how it looks. - // This class is the configuration for the state. It holds the values (in this // case the title) provided by the parent (in this case the App widget) and // used by the build method of the State. Fields in a Widget subclass are // always marked "final". - final String title; - @override _MyHomePageState createState() => _MyHomePageState(); } - class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - @override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance as done @@ -85,38 +65,10 @@ class _MyHomePageState extends State { body: Center( // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, - ), - ], + child: RepaintBoundary( + child: SpinKitRotatingCircle(color: Colors.blue, size: 50.0), ), ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. ); } } diff --git a/flow/layers/container_layer.cc b/flow/layers/container_layer.cc index ad6fd9126e200..1916608450aee 100644 --- a/flow/layers/container_layer.cc +++ b/flow/layers/container_layer.cc @@ -11,6 +11,7 @@ namespace flutter { ContainerLayer::ContainerLayer() : child_paint_bounds_(SkRect::MakeEmpty()) {} void ContainerLayer::Diff(DiffContext* context, const Layer* old_layer) { + std::cout << "Container layer" << std:endl; auto old_container = static_cast(old_layer); DiffContext::AutoSubtreeRestore subtree(context); DiffChildren(context, old_container); diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 8587d253d1f4d..26e549c25d83a 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -656,6 +656,7 @@ RasterStatus Rasterizer::DrawToSurfaceUnsafe( damage = std::make_unique(); if (frame->framebuffer_info().existing_damage && !force_full_repaint) { damage->SetPreviousLayerTree(last_layer_tree_.get()); + std::cout << "prev layer tree was set" << std::endl; damage->AddAdditonalDamage(*frame->framebuffer_info().existing_damage); damage->SetClipAlignment( frame->framebuffer_info().horizontal_clip_alignment, diff --git a/shell/common/shell_test_platform_view_gl.cc b/shell/common/shell_test_platform_view_gl.cc index 492354230e8dc..1a5c13cb7e5ab 100644 --- a/shell/common/shell_test_platform_view_gl.cc +++ b/shell/common/shell_test_platform_view_gl.cc @@ -5,6 +5,7 @@ #include "flutter/shell/common/shell_test_platform_view_gl.h" #include "flutter/shell/gpu/gpu_surface_gl_skia.h" +#include "shell/gpu/gpu_surface_gl_delegate.h" namespace flutter { namespace testing { @@ -69,8 +70,9 @@ bool ShellTestPlatformViewGL::GLContextPresent( } // |GPUSurfaceGLDelegate| -intptr_t ShellTestPlatformViewGL::GLContextFBO(GLFrameInfo frame_info) const { - return gl_surface_.GetFramebuffer(frame_info.width, frame_info.height); +GLFBOInfo ShellTestPlatformViewGL::GLContextFBO(GLFrameInfo frame_info) const { + return GLFBOInfo{ + gl_surface_.GetFramebuffer(frame_info.width, frame_info.height)}; } // |GPUSurfaceGLDelegate| diff --git a/shell/common/shell_test_platform_view_gl.h b/shell/common/shell_test_platform_view_gl.h index c890c1e0fc6ec..b4afc1266975b 100644 --- a/shell/common/shell_test_platform_view_gl.h +++ b/shell/common/shell_test_platform_view_gl.h @@ -61,7 +61,7 @@ class ShellTestPlatformViewGL : public ShellTestPlatformView, bool GLContextPresent(const GLPresentInfo& present_info) override; // |GPUSurfaceGLDelegate| - intptr_t GLContextFBO(GLFrameInfo frame_info) const override; + GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override; // |GPUSurfaceGLDelegate| GLProcResolver GetGLProcResolver() const override; diff --git a/shell/gpu/gpu_surface_gl_delegate.h b/shell/gpu/gpu_surface_gl_delegate.h index f9da6a11120fd..5451c6d838f1e 100644 --- a/shell/gpu/gpu_surface_gl_delegate.h +++ b/shell/gpu/gpu_surface_gl_delegate.h @@ -24,15 +24,25 @@ struct GLFrameInfo { // Information passed during presentation of a frame. struct GLPresentInfo { - uint32_t fbo_id; + intptr_t fbo_id; // Damage is a hint to compositor telling it which parts of front buffer // need to be updated - const std::optional& damage; + const std::optional& frame_damage; // Time at which this frame is scheduled to be presented. This is a hint // that can be passed to the platform to drop queued frames. std::optional presentation_time = std::nullopt; + + const std::optional& buffer_damage; +}; + +// Information passed when an FBO is requested. +struct GLFBOInfo { + uint32_t fbo_id; + + // Refers to the FBO's damage since it was last used. + const SkIRect existing_damage; }; class GPUSurfaceGLDelegate { @@ -55,7 +65,7 @@ class GPUSurfaceGLDelegate { virtual bool GLContextPresent(const GLPresentInfo& present_info) = 0; // The ID of the main window bound framebuffer. Typically FBO0. - virtual intptr_t GLContextFBO(GLFrameInfo frame_info) const = 0; + virtual GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const = 0; // The rendering subsystem assumes that the ID of the main window bound // framebuffer remains constant throughout. If this assumption in incorrect, diff --git a/shell/gpu/gpu_surface_gl_impeller.cc b/shell/gpu/gpu_surface_gl_impeller.cc index 24413147274a6..5bedb17fe5f0e 100644 --- a/shell/gpu/gpu_surface_gl_impeller.cc +++ b/shell/gpu/gpu_surface_gl_impeller.cc @@ -62,10 +62,11 @@ std::unique_ptr GPUSurfaceGLImpeller::AcquireFrame( if (weak) { GLPresentInfo present_info = { .fbo_id = 0, - .damage = std::nullopt, + .frame_damage = std::nullopt, // TODO (https://github.com/flutter/flutter/issues/105597): wire-up // presentation time to impeller backend. .presentation_time = std::nullopt, + .buffer_damage = std::nullopt }; delegate->GLContextPresent(present_info); } diff --git a/shell/gpu/gpu_surface_gl_skia.cc b/shell/gpu/gpu_surface_gl_skia.cc index 66bb6636d95d8..59f26c7c0d814 100644 --- a/shell/gpu/gpu_surface_gl_skia.cc +++ b/shell/gpu/gpu_surface_gl_skia.cc @@ -19,6 +19,9 @@ #include "third_party/skia/include/gpu/GrBackendSurface.h" #include "third_party/skia/include/gpu/GrContextOptions.h" +#include +#include + // These are common defines present on all OpenGL headers. However, we don't // want to perform GL header reasolution on each platform we support. So just // define these upfront. It is unlikely we will need more. But, if we do, we can @@ -181,10 +184,10 @@ bool GPUSurfaceGLSkia::CreateOrUpdateSurfaces(const SkISize& size) { GLFrameInfo frame_info = {static_cast(size.width()), static_cast(size.height())}; - const uint32_t fbo_id = delegate_->GLContextFBO(frame_info); + const GLFBOInfo fbo_info = delegate_->GLContextFBO(frame_info); onscreen_surface = WrapOnscreenSurface(context_.get(), // GL context size, // root surface size - fbo_id // window FBO ID + fbo_info.fbo_id // window FBO ID ); if (onscreen_surface == nullptr) { @@ -195,8 +198,8 @@ bool GPUSurfaceGLSkia::CreateOrUpdateSurfaces(const SkISize& size) { } onscreen_surface_ = std::move(onscreen_surface); - fbo_id_ = fbo_id; - + fbo_id_ = fbo_info.fbo_id; + existing_damage_ = fbo_info.existing_damage; return true; } @@ -248,6 +251,10 @@ std::unique_ptr GPUSurfaceGLSkia::AcquireFrame( }; framebuffer_info = delegate_->GLContextFramebufferInfo(); + // Partial repaint is enabled by default + framebuffer_info.supports_partial_repaint = true; + framebuffer_info.existing_damage = existing_damage_; + // TODO(btrevisa): figure out whether to set clip alignments. return std::make_unique(surface, std::move(framebuffer_info), submit_callback, std::move(context_switch)); @@ -267,13 +274,19 @@ bool GPUSurfaceGLSkia::PresentSurface(const SurfaceFrame& frame, } GLPresentInfo present_info = { - .fbo_id = fbo_id_, - .damage = frame.submit_info().frame_damage, + .fbo_id = static_cast(fbo_id_), + .frame_damage = frame.submit_info().frame_damage, .presentation_time = frame.submit_info().presentation_time, + .buffer_damage = frame.submit_info().buffer_damage, }; + auto start = std::chrono::high_resolution_clock::now(); if (!delegate_->GLContextPresent(present_info)) { return false; } + auto stop = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(stop - start); + + std::cout << duration.count() << std::endl; if (delegate_->GLContextFBOResetAfterPresent()) { auto current_size = @@ -284,11 +297,11 @@ bool GPUSurfaceGLSkia::PresentSurface(const SurfaceFrame& frame, // The FBO has changed, ask the delegate for the new FBO and do a surface // re-wrap. - const uint32_t fbo_id = delegate_->GLContextFBO(frame_info); + const GLFBOInfo fbo__info = delegate_->GLContextFBO(frame_info); auto new_onscreen_surface = - WrapOnscreenSurface(context_.get(), // GL context - current_size, // root surface size - fbo_id // window FBO ID + WrapOnscreenSurface(context_.get(), // GL context + current_size, // root surface size + fbo__info.fbo_id // window FBO ID ); if (!new_onscreen_surface) { @@ -296,7 +309,8 @@ bool GPUSurfaceGLSkia::PresentSurface(const SurfaceFrame& frame, } onscreen_surface_ = std::move(new_onscreen_surface); - fbo_id_ = fbo_id; + fbo_id_ = fbo__info.fbo_id; + existing_damage_ = fbo__info.existing_damage; } return true; diff --git a/shell/gpu/gpu_surface_gl_skia.h b/shell/gpu/gpu_surface_gl_skia.h index 90cad241945fd..4317905e19b5e 100644 --- a/shell/gpu/gpu_surface_gl_skia.h +++ b/shell/gpu/gpu_surface_gl_skia.h @@ -67,6 +67,7 @@ class GPUSurfaceGLSkia : public Surface { sk_sp onscreen_surface_; /// FBO backing the current `onscreen_surface_`. uint32_t fbo_id_ = 0; + SkIRect existing_damage_ = SkIRect::MakeEmpty(); bool context_owner_ = false; // TODO(38466): Refactor GPU surface APIs take into account the fact that an // external view embedder may want to render to the root surface. This is a diff --git a/shell/platform/android/android_surface_gl_impeller.cc b/shell/platform/android/android_surface_gl_impeller.cc index 28c1cfe55f19e..8eef4f494aca9 100644 --- a/shell/platform/android/android_surface_gl_impeller.cc +++ b/shell/platform/android/android_surface_gl_impeller.cc @@ -10,6 +10,7 @@ #include "flutter/impeller/renderer/backend/gles/proc_table_gles.h" #include "flutter/impeller/toolkit/egl/context.h" #include "flutter/impeller/toolkit/egl/surface.h" +#include "flutter/shell/gpu/gpu_surface_gl_delegate.h" #include "flutter/shell/gpu/gpu_surface_gl_impeller.h" namespace flutter { @@ -295,9 +296,9 @@ bool AndroidSurfaceGLImpeller::GLContextPresent( } // |GPUSurfaceGLDelegate| -intptr_t AndroidSurfaceGLImpeller::GLContextFBO(GLFrameInfo frame_info) const { +GLFBOInfo AndroidSurfaceGLImpeller::GLContextFBO(GLFrameInfo frame_info) const { // FBO0 is the default window bound framebuffer in EGL environments. - return 0; + return GLFBOInfo{0}; } // |GPUSurfaceGLDelegate| diff --git a/shell/platform/android/android_surface_gl_impeller.h b/shell/platform/android/android_surface_gl_impeller.h index 8bcf14dbee27b..9d2399868da97 100644 --- a/shell/platform/android/android_surface_gl_impeller.h +++ b/shell/platform/android/android_surface_gl_impeller.h @@ -68,7 +68,7 @@ class AndroidSurfaceGLImpeller final : public GPUSurfaceGLDelegate, bool GLContextPresent(const GLPresentInfo& present_info) override; // |GPUSurfaceGLDelegate| - intptr_t GLContextFBO(GLFrameInfo frame_info) const override; + GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override; // |GPUSurfaceGLDelegate| sk_sp GetGLInterface() const override; diff --git a/shell/platform/android/android_surface_gl_skia.cc b/shell/platform/android/android_surface_gl_skia.cc index 23242c9b3bad4..d4efe282e6b09 100644 --- a/shell/platform/android/android_surface_gl_skia.cc +++ b/shell/platform/android/android_surface_gl_skia.cc @@ -9,6 +9,7 @@ #include "flutter/fml/logging.h" #include "flutter/fml/memory/ref_ptr.h" +#include "flutter/shell/gpu/gpu_surface_gl_delegate.h" #include "flutter/shell/platform/android/android_egl_surface.h" #include "flutter/shell/platform/android/android_shell_holder.h" @@ -162,10 +163,10 @@ bool AndroidSurfaceGLSkia::GLContextPresent(const GLPresentInfo& present_info) { return onscreen_surface_->SwapBuffers(present_info.damage); } -intptr_t AndroidSurfaceGLSkia::GLContextFBO(GLFrameInfo frame_info) const { +GLFBOInfo AndroidSurfaceGLSkia::GLContextFBO(GLFrameInfo frame_info) const { FML_DCHECK(IsValid()); // The default window bound framebuffer on Android. - return 0; + return GLFBOInfo{0}; } // |GPUSurfaceGLDelegate| diff --git a/shell/platform/android/android_surface_gl_skia.h b/shell/platform/android/android_surface_gl_skia.h index 99a910b69b76d..73f36945127f8 100644 --- a/shell/platform/android/android_surface_gl_skia.h +++ b/shell/platform/android/android_surface_gl_skia.h @@ -67,7 +67,7 @@ class AndroidSurfaceGLSkia final : public GPUSurfaceGLDelegate, bool GLContextPresent(const GLPresentInfo& present_info) override; // |GPUSurfaceGLDelegate| - intptr_t GLContextFBO(GLFrameInfo frame_info) const override; + GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override; // |GPUSurfaceGLDelegate| sk_sp GetGLInterface() const override; diff --git a/shell/platform/android/surface/android_surface_mock.cc b/shell/platform/android/surface/android_surface_mock.cc index c3a9f38fc8f0c..71be40b318f64 100644 --- a/shell/platform/android/surface/android_surface_mock.cc +++ b/shell/platform/android/surface/android_surface_mock.cc @@ -22,8 +22,8 @@ bool AndroidSurfaceMock::GLContextPresent(const GLPresentInfo& present_info) { return true; } -intptr_t AndroidSurfaceMock::GLContextFBO(GLFrameInfo frame_info) const { - return 0; +GLFBOInfo AndroidSurfaceMock::GLContextFBO(GLFrameInfo frame_info) const { + return GLFBOInfo{0}; } } // namespace flutter diff --git a/shell/platform/android/surface/android_surface_mock.h b/shell/platform/android/surface/android_surface_mock.h index d7363873a9a31..3f326a21920b7 100644 --- a/shell/platform/android/surface/android_surface_mock.h +++ b/shell/platform/android/surface/android_surface_mock.h @@ -51,7 +51,7 @@ class AndroidSurfaceMock final : public GPUSurfaceGLDelegate, bool GLContextPresent(const GLPresentInfo& present_info) override; // |GPUSurfaceGLDelegate| - intptr_t GLContextFBO(GLFrameInfo frame_info) const override; + GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override; }; } // namespace flutter diff --git a/shell/platform/darwin/ios/ios_surface_gl.h b/shell/platform/darwin/ios/ios_surface_gl.h index 28ad45eb30f96..fd718efc9fd59 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.h +++ b/shell/platform/darwin/ios/ios_surface_gl.h @@ -11,6 +11,7 @@ #import "flutter/shell/platform/darwin/ios/ios_context.h" #import "flutter/shell/platform/darwin/ios/ios_render_target_gl.h" #import "flutter/shell/platform/darwin/ios/ios_surface.h" +#include "shell/gpu/gpu_surface_gl_delegate.h" @class CAEAGLLayer; @@ -41,7 +42,7 @@ class IOSSurfaceGL final : public IOSSurface, public GPUSurfaceGLDelegate { bool GLContextPresent(const GLPresentInfo& present_info) override; // |GPUSurfaceGLDelegate| - intptr_t GLContextFBO(GLFrameInfo frame_info) const override; + GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override; // |GPUSurfaceGLDelegate| SurfaceFrame::FramebufferInfo GLContextFramebufferInfo() const override; diff --git a/shell/platform/darwin/ios/ios_surface_gl.mm b/shell/platform/darwin/ios/ios_surface_gl.mm index 7c5e7ec8e2c71..8d1486fba44d2 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.mm +++ b/shell/platform/darwin/ios/ios_surface_gl.mm @@ -3,6 +3,7 @@ // found in the LICENSE file. #import "flutter/shell/platform/darwin/ios/ios_surface_gl.h" +#include "shell/gpu/gpu_surface_gl_delegate.h" #include "flutter/fml/trace_event.h" #include "flutter/shell/gpu/gpu_surface_gl_skia.h" @@ -51,8 +52,9 @@ } // |GPUSurfaceGLDelegate| -intptr_t IOSSurfaceGL::GLContextFBO(GLFrameInfo frame_info) const { - return IsValid() ? render_target_->GetFramebuffer() : GL_NONE; +GLFBOInfo IOSSurfaceGL::GLContextFBO(GLFrameInfo frame_info) const { + return IsValid() ? GLFBOInfo{static_cast(render_target_->GetFramebuffer())} + : GLFBOInfo{GL_NONE}; } // |GPUSurfaceGLDelegate| diff --git a/shell/platform/darwin/macos/framework/Source/FlutterOpenGLRenderer.mm b/shell/platform/darwin/macos/framework/Source/FlutterOpenGLRenderer.mm index a6abbc4ec5863..d20a064a5eff1 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterOpenGLRenderer.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterOpenGLRenderer.mm @@ -148,7 +148,8 @@ - (FlutterRendererConfig)createRendererConfig { .open_gl.make_current = reinterpret_cast(OnMakeCurrent), .open_gl.clear_current = reinterpret_cast(OnClearCurrent), .open_gl.present = reinterpret_cast(OnPresent), - .open_gl.fbo_with_frame_info_callback = reinterpret_cast(OnFBO), + .open_gl.fbo_with_frame_info_callback = + reinterpret_cast(OnFBO), .open_gl.fbo_reset_after_present = true, .open_gl.make_resource_current = reinterpret_cast(OnMakeResourceCurrent), .open_gl.gl_external_texture_frame_callback = diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index fea4882cc1536..f2b90c941540f 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -221,6 +221,15 @@ static void* DefaultGLProcResolver(const char* name) { } #endif // FML_OS_LINUX || FML_OS_WIN +/// NEW: Adding this function to make it easier to translate a SkIRect to a +/// FlutterRect. +FlutterRect SkIRectToFlutterRect(const std::optional rect) { + FlutterRect flutter_rect = { + static_cast(rect->fLeft), static_cast(rect->fTop), + static_cast(rect->fRight), static_cast(rect->fBottom)}; + return flutter_rect; +} + static flutter::Shell::CreateCallback InferOpenGLPlatformViewCreationCallback( const FlutterRendererConfig* config, @@ -240,15 +249,32 @@ InferOpenGLPlatformViewCreationCallback( auto gl_clear_current = [ptr = config->open_gl.clear_current, user_data]() -> bool { return ptr(user_data); }; - auto gl_present = [present = config->open_gl.present, - present_with_info = config->open_gl.present_with_info, - user_data](uint32_t fbo_id) -> bool { + /// NOTE: this means that for partial repaint to work, we must have it use the + /// present_with_info callback rather than the present callback. + auto gl_present = + [present = config->open_gl.present, + present_with_info = config->open_gl.present_with_info, + user_data](flutter::GLPresentInfo gl_present_info) -> bool { if (present) { return present(user_data); } else { - FlutterPresentInfo present_info = {}; + FlutterPresentInfo present_info; present_info.struct_size = sizeof(FlutterPresentInfo); - present_info.fbo_id = fbo_id; + present_info.fbo_id = gl_present_info.fbo_id; + + /// Format the frame_damage appropriately. + FlutterDamage frame_damage; + frame_damage.damage = SkIRectToFlutterRect(gl_present_info.frame_damage); + + present_info.frame_damage = frame_damage; + + FlutterDamage buffer_damage; + buffer_damage.damage = SkIRectToFlutterRect(gl_present_info.buffer_damage); + present_info.buffer_damage = buffer_damage; + + /// Pass the present_info to the present_with_info callback so that it can + /// correctly only render part of the screen and save the FBO's damage + /// region. return present_with_info(user_data, &present_info); } }; @@ -257,9 +283,11 @@ InferOpenGLPlatformViewCreationCallback( [fbo_callback = config->open_gl.fbo_callback, fbo_with_frame_info_callback = config->open_gl.fbo_with_frame_info_callback, - user_data](flutter::GLFrameInfo gl_frame_info) -> intptr_t { + user_data](flutter::GLFrameInfo gl_frame_info) -> FlutterFrameBuffer { if (fbo_callback) { - return fbo_callback(user_data); + FlutterFrameBuffer fbo; + fbo.fbo_id = fbo_callback(user_data); + return fbo; } else { FlutterFrameInfo frame_info = {}; frame_info.struct_size = sizeof(FlutterFrameInfo); diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 7cc732a8b53db..11e9a2661cdb8 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -377,6 +377,19 @@ typedef struct { FlutterSize lower_left_corner_radius; } FlutterRoundedRect; +/// QUESTION: should damage be a vector of FlutterRect's? Intuitively, this +/// seems like the correct approach since we could have areas that are being +/// rendered in different corners of the screen. However, the partial repaint +/// implemented for android and iOS seems to use only one rectangle. On the +/// other hand, one rectangle might help with reduce the complexity of the +/// rendering process. +/// NEW: Adding new struct so that the embedder can represent the idea of +/// damage in a self-contained way. +typedef struct { + // Rectangle that represents the area that needs to be rendered. + FlutterRect damage; +} FlutterDamage; + /// This information is passed to the embedder when requesting a frame buffer /// object. /// @@ -393,6 +406,21 @@ typedef uint32_t (*UIntFrameInfoCallback)( void* /* user data */, const FlutterFrameInfo* /* frame info */); +/// NEW: Adding new struct so that the embedder can represent the idea of +/// a frame buffer in a self-contained way. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterFrameBuffer). + size_t struct_size; + intptr_t fbo_id; + FlutterDamage damage; +} FlutterFrameBuffer; + +/// NEW: adding this type of callback since the return value of the +/// fbo_with_damage callback is a FlutterFrameBuffer (instead of a uintptr). +typedef FlutterFrameBuffer (*FlutterFrameBufferFrameInfoCallback)( + void* /* user data */, + const FlutterFrameInfo* /* frame info */); + /// This information is passed to the embedder when a surface is presented. /// /// See: \ref FlutterOpenGLRendererConfig.present_with_info. @@ -401,6 +429,10 @@ typedef struct { size_t struct_size; /// Id of the fbo backing the surface that was presented. uint32_t fbo_id; + /// Rectangle representing the area that the compositor needs to render. + FlutterDamage frame_damage; + /// Rectangle used to set the buffer's damage region. + FlutterDamage buffer_damage; } FlutterPresentInfo; /// Callback for when a surface is presented. @@ -459,7 +491,7 @@ typedef struct { /// surface from. When using this variant, the embedder is passed a /// `FlutterFrameInfo` struct that indicates the properties of the surface /// that flutter will acquire from the returned fbo. - UIntFrameInfoCallback fbo_with_frame_info_callback; + FlutterFrameBufferFrameInfoCallback fbo_with_frame_info_callback; /// Specifying one (and only one) of `present` or `present_with_info` is /// required. Specifying both is an error and engine initialization will be /// terminated. When using this variant, the embedder is passed a diff --git a/shell/platform/embedder/embedder_surface_gl.cc b/shell/platform/embedder/embedder_surface_gl.cc index e406ee9ed10fa..10129d64f7fe7 100644 --- a/shell/platform/embedder/embedder_surface_gl.cc +++ b/shell/platform/embedder/embedder_surface_gl.cc @@ -46,12 +46,32 @@ bool EmbedderSurfaceGL::GLContextClearCurrent() { // |GPUSurfaceGLDelegate| bool EmbedderSurfaceGL::GLContextPresent(const GLPresentInfo& present_info) { - return gl_dispatch_table_.gl_present_callback(present_info.fbo_id); + /// NEW: Pass the frame damage (in present_info) so that the embedder can keep + // track of it. + return gl_dispatch_table_.gl_present_callback(present_info); +} + +/// NEW: Adding this function to make it easier to translate a FlutterRect back +/// to a SkIRect. +const SkIRect FlutterRectToSkIRect(FlutterRect flutter_rect) { + SkIRect rect = {static_cast(flutter_rect.left), + static_cast(flutter_rect.top), + static_cast(flutter_rect.right), + static_cast(flutter_rect.bottom)}; + return rect; } // |GPUSurfaceGLDelegate| -intptr_t EmbedderSurfaceGL::GLContextFBO(GLFrameInfo frame_info) const { - return gl_dispatch_table_.gl_fbo_callback(frame_info); +GLFBOInfo EmbedderSurfaceGL::GLContextFBO(GLFrameInfo frame_info) const { + /// NEW: updating GLContextFBO to trigger a callback that will not only return + /// the FBO ID but also its existing damage (at least when doing partial + /// partial repaint. + FlutterFrameBuffer fbo = gl_dispatch_table_.gl_fbo_callback(frame_info); + GLFBOInfo gl_fbo = { + static_cast(fbo.fbo_id), // fbo_id + FlutterRectToSkIRect(fbo.damage.damage), // existing_damage + }; + return gl_fbo; } // |GPUSurfaceGLDelegate| @@ -106,5 +126,4 @@ sk_sp EmbedderSurfaceGL::CreateResourceContext() const { "callback on FlutterOpenGLRendererConfig."; return nullptr; } - } // namespace flutter diff --git a/shell/platform/embedder/embedder_surface_gl.h b/shell/platform/embedder/embedder_surface_gl.h index c0a2b6a55d98f..c575f1bc29f29 100644 --- a/shell/platform/embedder/embedder_surface_gl.h +++ b/shell/platform/embedder/embedder_surface_gl.h @@ -16,11 +16,11 @@ class EmbedderSurfaceGL final : public EmbedderSurface, public GPUSurfaceGLDelegate { public: struct GLDispatchTable { - std::function gl_make_current_callback; // required - std::function gl_clear_current_callback; // required - std::function gl_present_callback; // required - std::function gl_fbo_callback; // required - std::function gl_make_resource_current_callback; // optional + std::function gl_make_current_callback; // required + std::function gl_clear_current_callback; // required + std::function gl_present_callback; // required + std::function gl_fbo_callback; // required + std::function gl_make_resource_current_callback; // optional std::function gl_surface_transformation_callback; // optional std::function gl_proc_resolver; // optional @@ -59,7 +59,7 @@ class EmbedderSurfaceGL final : public EmbedderSurface, bool GLContextPresent(const GLPresentInfo& present_info) override; // |GPUSurfaceGLDelegate| - intptr_t GLContextFBO(GLFrameInfo frame_info) const override; + GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override; // |GPUSurfaceGLDelegate| bool GLContextFBOResetAfterPresent() const override; diff --git a/shell/platform/embedder/tests/embedder_config_builder.cc b/shell/platform/embedder/tests/embedder_config_builder.cc index ae7e32f1cc86b..970df93d30a28 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.cc +++ b/shell/platform/embedder/tests/embedder_config_builder.cc @@ -55,7 +55,8 @@ EmbedderConfigBuilder::EmbedderConfigBuilder( present_info->fbo_id); }; opengl_renderer_config_.fbo_with_frame_info_callback = - [](void* context, const FlutterFrameInfo* frame_info) -> uint32_t { + [](void* context, + const FlutterFrameInfo* frame_info) -> FlutterFrameBuffer { return reinterpret_cast(context)->GLGetFramebuffer( *frame_info); }; @@ -148,8 +149,10 @@ void EmbedderConfigBuilder::SetOpenGLFBOCallBack() { frame_info.struct_size = sizeof(FlutterFrameInfo); frame_info.size.width = 0; frame_info.size.height = 0; - return reinterpret_cast(context)->GLGetFramebuffer( - frame_info); + FlutterFrameBuffer fbo = + reinterpret_cast(context)->GLGetFramebuffer( + frame_info); + return fbo.fbo_id; }; #endif } @@ -365,6 +368,10 @@ FlutterCompositor& EmbedderConfigBuilder::GetCompositor() { return compositor_; } +FlutterRendererConfig& EmbedderConfigBuilder::GetRendererConfig() { + return renderer_config_; +} + void EmbedderConfigBuilder::SetRenderTargetType( EmbedderTestBackingStoreProducer::RenderTargetType type) { auto& compositor = context_.GetCompositor(); diff --git a/shell/platform/embedder/tests/embedder_config_builder.h b/shell/platform/embedder/tests/embedder_config_builder.h index 2e2eb182326b6..8d5cbef8ae933 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.h +++ b/shell/platform/embedder/tests/embedder_config_builder.h @@ -104,6 +104,8 @@ class EmbedderConfigBuilder { FlutterCompositor& GetCompositor(); + FlutterRendererConfig& GetRendererConfig(); + void SetRenderTargetType( EmbedderTestBackingStoreProducer::RenderTargetType type); diff --git a/shell/platform/embedder/tests/embedder_test_context_gl.cc b/shell/platform/embedder/tests/embedder_test_context_gl.cc index 9a7bc9bcf1c26..79936e380553b 100644 --- a/shell/platform/embedder/tests/embedder_test_context_gl.cc +++ b/shell/platform/embedder/tests/embedder_test_context_gl.cc @@ -10,6 +10,7 @@ #include "flutter/shell/platform/embedder/tests/embedder_assertions.h" #include "flutter/shell/platform/embedder/tests/embedder_test_compositor_gl.h" #include "flutter/testing/testing.h" +#include "shell/gpu/gpu_surface_gl_delegate.h" #include "tests/embedder_test.h" #include "third_party/dart/runtime/bin/elf_loader.h" #include "third_party/skia/include/core/SkSurface.h" @@ -69,7 +70,8 @@ void EmbedderTestContextGL::SetGLPresentCallback(GLPresentCallback callback) { gl_present_callback_ = callback; } -uint32_t EmbedderTestContextGL::GLGetFramebuffer(FlutterFrameInfo frame_info) { +FlutterFrameBuffer EmbedderTestContextGL::GLGetFramebuffer( + FlutterFrameInfo frame_info) { FML_CHECK(gl_surface_) << "GL surface must be initialized."; GLGetFBOCallback callback; @@ -83,7 +85,9 @@ uint32_t EmbedderTestContextGL::GLGetFramebuffer(FlutterFrameInfo frame_info) { } const auto size = frame_info.size; - return gl_surface_->GetFramebuffer(size.width, size.height); + FlutterFrameBuffer fbo; + fbo.fbo_id = gl_surface_->GetFramebuffer(size.width, size.height); + return fbo; } bool EmbedderTestContextGL::GLMakeResourceCurrent() { diff --git a/shell/platform/embedder/tests/embedder_test_context_gl.h b/shell/platform/embedder/tests/embedder_test_context_gl.h index 65e2c34c355c2..5dd3b38093b71 100644 --- a/shell/platform/embedder/tests/embedder_test_context_gl.h +++ b/shell/platform/embedder/tests/embedder_test_context_gl.h @@ -7,6 +7,7 @@ #include "flutter/shell/platform/embedder/tests/embedder_test_context.h" #include "flutter/testing/test_gl_surface.h" +#include "shell/gpu/gpu_surface_gl_delegate.h" namespace flutter { namespace testing { @@ -74,7 +75,7 @@ class EmbedderTestContextGL : public EmbedderTestContext { bool GLPresent(uint32_t fbo_id); - uint32_t GLGetFramebuffer(FlutterFrameInfo frame_info); + FlutterFrameBuffer GLGetFramebuffer(FlutterFrameInfo frame_info); bool GLMakeResourceCurrent(); diff --git a/shell/platform/embedder/tests/embedder_unittests_gl.cc b/shell/platform/embedder/tests/embedder_unittests_gl.cc index 9174461529058..17a350c9d9b61 100644 --- a/shell/platform/embedder/tests/embedder_unittests_gl.cc +++ b/shell/platform/embedder/tests/embedder_unittests_gl.cc @@ -44,6 +44,29 @@ namespace testing { using EmbedderTest = testing::EmbedderTest; +TEST_F(EmbedderTest, CanCreateOpenGLWithFBOWithDamageCallbackOnly) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + + EmbedderConfigBuilder builder(context); + + builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); + + builder.GetRendererConfig().open_gl.fbo_with_frame_info_callback = + [](void* userdata, + const FlutterFrameInfo* frame_info) -> FlutterFrameBuffer { + // No need to fill in the returned FBO since it will not actually be used. + FlutterFrameBuffer fbo; + return fbo; + }; + + // Make sure other FBO callbacks are not defined. + builder.GetRendererConfig().open_gl.fbo_callback = nullptr; + + auto engine = builder.LaunchEngine(); + + ASSERT_TRUE(engine.is_valid()); +} + TEST_F(EmbedderTest, CanGetVulkanEmbedderContext) { auto& context = GetEmbedderContext(EmbedderTestContextType::kVulkanContext); EmbedderConfigBuilder builder(context); diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index ceb4634d433b5..45f798ccfbe5a 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -67,13 +67,17 @@ FlutterRendererConfig GetOpenGLRendererConfig() { }; config.open_gl.fbo_reset_after_present = true; config.open_gl.fbo_with_frame_info_callback = - [](void* user_data, const FlutterFrameInfo* info) -> uint32_t { + [](void* user_data, const FlutterFrameInfo* info) -> FlutterFrameBuffer { auto host = static_cast(user_data); if (host->view()) { - return host->view()->GetFrameBufferId(info->size.width, - info->size.height); + FlutterFrameBuffer fbo; + fbo.fbo_id = + host->view()->GetFrameBufferId(info->size.width, info->size.height); + return fbo; } else { - return kWindowFrameBufferID; + FlutterFrameBuffer fbo; + fbo.fbo_id = kWindowFrameBufferID; + return fbo; } }; config.open_gl.gl_proc_resolver = [](void* user_data,