diff --git a/engine/src/flutter/shell/common/shell_test_platform_view_gl.cc b/engine/src/flutter/shell/common/shell_test_platform_view_gl.cc index 8717991c10a..0c7d5d6f727 100644 --- a/engine/src/flutter/shell/common/shell_test_platform_view_gl.cc +++ b/engine/src/flutter/shell/common/shell_test_platform_view_gl.cc @@ -63,7 +63,9 @@ bool ShellTestPlatformViewGL::GLContextClearCurrent() { } // |GPUSurfaceGLDelegate| -bool ShellTestPlatformViewGL::GLContextPresent(uint32_t fbo_id) { +bool ShellTestPlatformViewGL::GLContextPresent( + uint32_t fbo_id, + const std::optional& damage) { return gl_surface_.Present(); } diff --git a/engine/src/flutter/shell/common/shell_test_platform_view_gl.h b/engine/src/flutter/shell/common/shell_test_platform_view_gl.h index d072dd9731b..0484969885f 100644 --- a/engine/src/flutter/shell/common/shell_test_platform_view_gl.h +++ b/engine/src/flutter/shell/common/shell_test_platform_view_gl.h @@ -58,7 +58,8 @@ class ShellTestPlatformViewGL : public ShellTestPlatformView, bool GLContextClearCurrent() override; // |GPUSurfaceGLDelegate| - bool GLContextPresent(uint32_t fbo_id) override; + bool GLContextPresent(uint32_t fbo_id, + const std::optional& damage) override; // |GPUSurfaceGLDelegate| intptr_t GLContextFBO(GLFrameInfo frame_info) const override; diff --git a/engine/src/flutter/shell/gpu/gpu_surface_gl.cc b/engine/src/flutter/shell/gpu/gpu_surface_gl.cc index 6e81d0a0ae4..218eb76e97b 100644 --- a/engine/src/flutter/shell/gpu/gpu_surface_gl.cc +++ b/engine/src/flutter/shell/gpu/gpu_surface_gl.cc @@ -242,7 +242,7 @@ std::unique_ptr GPUSurfaceGL::AcquireFrame(const SkISize& size) { SurfaceFrame::SubmitCallback submit_callback = [weak = weak_factory_.GetWeakPtr()](const SurfaceFrame& surface_frame, SkCanvas* canvas) { - return weak ? weak->PresentSurface(canvas) : false; + return weak ? weak->PresentSurface(surface_frame, canvas) : false; }; framebuffer_info = delegate_->GLContextFramebufferInfo(); @@ -251,17 +251,19 @@ std::unique_ptr GPUSurfaceGL::AcquireFrame(const SkISize& size) { std::move(context_switch)); } -bool GPUSurfaceGL::PresentSurface(SkCanvas* canvas) { +bool GPUSurfaceGL::PresentSurface(const SurfaceFrame& frame, SkCanvas* canvas) { if (delegate_ == nullptr || canvas == nullptr || context_ == nullptr) { return false; } + delegate_->GLContextSetDamageRegion(frame.submit_info().buffer_damage); + { TRACE_EVENT0("flutter", "SkCanvas::Flush"); onscreen_surface_->getCanvas()->flush(); } - if (!delegate_->GLContextPresent(fbo_id_)) { + if (!delegate_->GLContextPresent(fbo_id_, frame.submit_info().frame_damage)) { return false; } diff --git a/engine/src/flutter/shell/gpu/gpu_surface_gl.h b/engine/src/flutter/shell/gpu/gpu_surface_gl.h index fd641149dec..390d78bbb9c 100644 --- a/engine/src/flutter/shell/gpu/gpu_surface_gl.h +++ b/engine/src/flutter/shell/gpu/gpu_surface_gl.h @@ -60,7 +60,7 @@ class GPUSurfaceGL : public Surface { const SkISize& untransformed_size, const SkMatrix& root_surface_transformation); - bool PresentSurface(SkCanvas* canvas); + bool PresentSurface(const SurfaceFrame& frame, SkCanvas* canvas); GPUSurfaceGLDelegate* delegate_; sk_sp context_; diff --git a/engine/src/flutter/shell/gpu/gpu_surface_gl_delegate.h b/engine/src/flutter/shell/gpu/gpu_surface_gl_delegate.h index 58a600347fe..35739d5019f 100644 --- a/engine/src/flutter/shell/gpu/gpu_surface_gl_delegate.h +++ b/engine/src/flutter/shell/gpu/gpu_surface_gl_delegate.h @@ -5,6 +5,8 @@ #ifndef FLUTTER_SHELL_GPU_GPU_SURFACE_GL_DELEGATE_H_ #define FLUTTER_SHELL_GPU_GPU_SURFACE_GL_DELEGATE_H_ +#include + #include "flutter/common/graphics/gl_context_switch.h" #include "flutter/flow/embedded_views.h" #include "flutter/fml/macros.h" @@ -31,9 +33,17 @@ class GPUSurfaceGLDelegate { // either the GPU or IO threads. virtual bool GLContextClearCurrent() = 0; + // Inform the GL Context that there's going to be no writing beyond + // the specified region + virtual void GLContextSetDamageRegion(const std::optional& region) {} + // Called to present the main GL surface. This is only called for the main GL // context and not any of the contexts dedicated for IO. - virtual bool GLContextPresent(uint32_t fbo_id) = 0; + // + // Damage is a hint to compositor telling it which parts of front buffer + // need to be updated + virtual bool GLContextPresent(uint32_t fbo_id, + const std::optional& damage) = 0; // The ID of the main window bound framebuffer. Typically FBO0. virtual intptr_t GLContextFBO(GLFrameInfo frame_info) const = 0; diff --git a/engine/src/flutter/shell/platform/android/android_context_gl.cc b/engine/src/flutter/shell/platform/android/android_context_gl.cc index 42ab9cd656d..75ae7b804f5 100644 --- a/engine/src/flutter/shell/platform/android/android_context_gl.cc +++ b/engine/src/flutter/shell/platform/android/android_context_gl.cc @@ -6,8 +6,12 @@ #include +#include #include +// required to get API level +#include + #include "flutter/fml/trace_event.h" namespace flutter { @@ -105,10 +109,135 @@ static bool TeardownContext(EGLDisplay display, EGLContext context) { return true; } +class AndroidEGLSurfaceDamage { + public: + void init(EGLDisplay display, EGLContext context) { + if (GetAPILevel() < 28) { + // Disable partial repaint for devices older than Android 9. There + // are old devices that have extensions below available but the + // implementation causes glitches (i.e. Xperia Z3 with Android 6). + partial_redraw_supported_ = false; + return; + } + + const char* extensions = eglQueryString(display, EGL_EXTENSIONS); + + if (HasExtension(extensions, "EGL_KHR_partial_update")) { + set_damage_region_ = reinterpret_cast( + eglGetProcAddress("eglSetDamageRegionKHR")); + } + + if (HasExtension(extensions, "EGL_EXT_swap_buffers_with_damage")) { + swap_buffers_with_damage_ = + reinterpret_cast( + eglGetProcAddress("eglSwapBuffersWithDamageEXT")); + } else if (HasExtension(extensions, "EGL_KHR_swap_buffers_with_damage")) { + swap_buffers_with_damage_ = + reinterpret_cast( + eglGetProcAddress("eglSwapBuffersWithDamageKHR")); + } + + partial_redraw_supported_ = + set_damage_region_ != nullptr && swap_buffers_with_damage_ != nullptr; + } + + static int GetAPILevel() { + char sdk_version_string[PROP_VALUE_MAX]; + if (__system_property_get("ro.build.version.sdk", sdk_version_string)) { + return atoi(sdk_version_string); + } else { + return -1; + } + } + + void SetDamageRegion(EGLDisplay display, + EGLSurface surface, + const std::optional& region) { + if (set_damage_region_ && region) { + auto rects = RectToInts(display, surface, *region); + set_damage_region_(display, surface, rects.data(), 1); + } + } + + // 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; + + bool SupportsPartialRepaint() const { return partial_redraw_supported_; } + + std::optional InitialDamage(EGLDisplay display, EGLSurface surface) { + if (!partial_redraw_supported_) { + return std::nullopt; + } + + EGLint age; + eglQuerySurface(display, surface, EGL_BUFFER_AGE_EXT, &age); + + if (age == 0) { // full repaint + return std::nullopt; + } else { + // join up to (age - 1) last rects from damage history + --age; + auto res = SkIRect::MakeEmpty(); + for (auto i = damage_history_.rbegin(); + i != damage_history_.rend() && age > 0; ++i, --age) { + res.join(*i); + } + return res; + } + } + + bool SwapBuffersWithDamage(EGLDisplay display, + EGLSurface surface, + const std::optional& damage) { + if (swap_buffers_with_damage_ && damage) { + damage_history_.push_back(*damage); + if (damage_history_.size() > kMaxHistorySize) { + damage_history_.pop_front(); + } + auto rects = RectToInts(display, surface, *damage); + return swap_buffers_with_damage_(display, surface, rects.data(), 1); + } else { + return eglSwapBuffers(display, surface); + } + } + + private: + std::array static RectToInts(EGLDisplay display, + EGLSurface surface, + const SkIRect& rect) { + EGLint height; + eglQuerySurface(display, surface, EGL_HEIGHT, &height); + + std::array res{rect.left(), height - rect.bottom(), rect.width(), + rect.height()}; + return res; + } + + PFNEGLSETDAMAGEREGIONKHRPROC set_damage_region_ = nullptr; + PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage_ = nullptr; + + bool partial_redraw_supported_; + + bool HasExtension(const char* extensions, const char* name) { + const char* r = strstr(extensions, name); + auto len = strlen(name); + // check that the extension name is terminated by space or null terminator + return r != nullptr && (r[len] == ' ' || r[len] == 0); + } + + std::list damage_history_; +}; + AndroidEGLSurface::AndroidEGLSurface(EGLSurface surface, EGLDisplay display, EGLContext context) - : surface_(surface), display_(display), context_(context) {} + : surface_(surface), + display_(display), + context_(context), + damage_(std::make_unique()) { + damage_->init(display_, context); +} AndroidEGLSurface::~AndroidEGLSurface() { auto result = eglDestroySurface(display_, surface_); @@ -128,9 +257,23 @@ bool AndroidEGLSurface::MakeCurrent() const { return true; } -bool AndroidEGLSurface::SwapBuffers() { +void AndroidEGLSurface::SetDamageRegion( + const std::optional& buffer_damage) { + damage_->SetDamageRegion(display_, surface_, buffer_damage); +} + +bool AndroidEGLSurface::SwapBuffers( + const std::optional& surface_damage) { TRACE_EVENT0("flutter", "AndroidContextGL::SwapBuffers"); - return eglSwapBuffers(display_, surface_); + return damage_->SwapBuffersWithDamage(display_, surface_, surface_damage); +} + +bool AndroidEGLSurface::SupportsPartialRepaint() const { + return damage_->SupportsPartialRepaint(); +} + +std::optional AndroidEGLSurface::InitialDamage() { + return damage_->InitialDamage(display_, surface_); } SkISize AndroidEGLSurface::GetSize() const { diff --git a/engine/src/flutter/shell/platform/android/android_context_gl.h b/engine/src/flutter/shell/platform/android/android_context_gl.h index 08668e488ca..38f17280744 100644 --- a/engine/src/flutter/shell/platform/android/android_context_gl.h +++ b/engine/src/flutter/shell/platform/android/android_context_gl.h @@ -23,6 +23,8 @@ namespace flutter { /// This can be used in conjunction to unique_ptr to provide better guarantees /// about the lifespan of the `EGLSurface` object. /// +class AndroidEGLSurfaceDamage; + class AndroidEGLSurface { public: AndroidEGLSurface(EGLSurface surface, EGLDisplay display, EGLContext context); @@ -43,13 +45,35 @@ class AndroidEGLSurface { /// bool MakeCurrent() const; + //---------------------------------------------------------------------------- + /// + /// @return Whether target surface supports partial repaint. + /// + bool SupportsPartialRepaint() const; + + //---------------------------------------------------------------------------- + /// @brief This is the minimal area that needs to be repainted to get + /// correct result. + /// + /// With double or triple buffering this buffer content may lag behind + /// current front buffer and the rect accounts for accumulated damage. + /// + /// @return The area of current surface where it is behind front buffer. + /// + std::optional InitialDamage(); + + //---------------------------------------------------------------------------- + /// @brief Sets the damage region for current surface. Corresponds to + // eglSetDamageRegionKHR + void SetDamageRegion(const std::optional& buffer_damage); + //---------------------------------------------------------------------------- /// @brief This only applies to on-screen surfaces such as those created /// by `AndroidContextGL::CreateOnscreenSurface`. /// /// @return Whether the EGL surface color buffer was swapped. /// - bool SwapBuffers(); + bool SwapBuffers(const std::optional& surface_damage); //---------------------------------------------------------------------------- /// @return The size of an `EGLSurface`. @@ -60,6 +84,7 @@ class AndroidEGLSurface { const EGLSurface surface_; const EGLDisplay display_; const EGLContext context_; + std::unique_ptr damage_; }; //------------------------------------------------------------------------------ diff --git a/engine/src/flutter/shell/platform/android/android_surface_gl.cc b/engine/src/flutter/shell/platform/android/android_surface_gl.cc index a94b873aec8..237cf2105d8 100644 --- a/engine/src/flutter/shell/platform/android/android_surface_gl.cc +++ b/engine/src/flutter/shell/platform/android/android_surface_gl.cc @@ -126,10 +126,27 @@ bool AndroidSurfaceGL::GLContextClearCurrent() { return GLContextPtr()->ClearCurrent(); } -bool AndroidSurfaceGL::GLContextPresent(uint32_t fbo_id) { +SurfaceFrame::FramebufferInfo AndroidSurfaceGL::GLContextFramebufferInfo() + const { + FML_DCHECK(IsValid()); + SurfaceFrame::FramebufferInfo res; + res.supports_readback = true; + res.supports_partial_repaint = onscreen_surface_->SupportsPartialRepaint(); + res.existing_damage = onscreen_surface_->InitialDamage(); + return res; +} + +void AndroidSurfaceGL::GLContextSetDamageRegion( + const std::optional& region) { + FML_DCHECK(IsValid()); + onscreen_surface_->SetDamageRegion(region); +} + +bool AndroidSurfaceGL::GLContextPresent(uint32_t fbo_id, + const std::optional& damage) { FML_DCHECK(IsValid()); FML_DCHECK(onscreen_surface_); - return onscreen_surface_->SwapBuffers(); + return onscreen_surface_->SwapBuffers(damage); } intptr_t AndroidSurfaceGL::GLContextFBO(GLFrameInfo frame_info) const { diff --git a/engine/src/flutter/shell/platform/android/android_surface_gl.h b/engine/src/flutter/shell/platform/android/android_surface_gl.h index e078d2363e6..92ddd6361a9 100644 --- a/engine/src/flutter/shell/platform/android/android_surface_gl.h +++ b/engine/src/flutter/shell/platform/android/android_surface_gl.h @@ -58,7 +58,14 @@ class AndroidSurfaceGL final : public GPUSurfaceGLDelegate, bool GLContextClearCurrent() override; // |GPUSurfaceGLDelegate| - bool GLContextPresent(uint32_t fbo_id) override; + SurfaceFrame::FramebufferInfo GLContextFramebufferInfo() const override; + + // |GPUSurfaceGLDelegate| + void GLContextSetDamageRegion(const std::optional& region) override; + + // |GPUSurfaceGLDelegate| + bool GLContextPresent(uint32_t fbo_id, + const std::optional& damage) override; // |GPUSurfaceGLDelegate| intptr_t GLContextFBO(GLFrameInfo frame_info) const override; diff --git a/engine/src/flutter/shell/platform/android/surface/android_surface_mock.cc b/engine/src/flutter/shell/platform/android/surface/android_surface_mock.cc index a0b08cb4892..515d867f23f 100644 --- a/engine/src/flutter/shell/platform/android/surface/android_surface_mock.cc +++ b/engine/src/flutter/shell/platform/android/surface/android_surface_mock.cc @@ -18,7 +18,9 @@ bool AndroidSurfaceMock::GLContextClearCurrent() { return true; } -bool AndroidSurfaceMock::GLContextPresent(uint32_t fbo_id) { +bool AndroidSurfaceMock::GLContextPresent( + uint32_t fbo_id, + const std::optional& damage) { return true; } diff --git a/engine/src/flutter/shell/platform/android/surface/android_surface_mock.h b/engine/src/flutter/shell/platform/android/surface/android_surface_mock.h index f710133f64c..04a6b2e03f1 100644 --- a/engine/src/flutter/shell/platform/android/surface/android_surface_mock.h +++ b/engine/src/flutter/shell/platform/android/surface/android_surface_mock.h @@ -48,7 +48,8 @@ class AndroidSurfaceMock final : public GPUSurfaceGLDelegate, bool GLContextClearCurrent() override; // |GPUSurfaceGLDelegate| - bool GLContextPresent(uint32_t fbo_id) override; + bool GLContextPresent(uint32_t fbo_id, + const std::optional& damage) override; // |GPUSurfaceGLDelegate| intptr_t GLContextFBO(GLFrameInfo frame_info) const override; diff --git a/engine/src/flutter/shell/platform/darwin/ios/ios_surface_gl.h b/engine/src/flutter/shell/platform/darwin/ios/ios_surface_gl.h index 54a0584a7e0..3ea6fc40739 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/ios_surface_gl.h +++ b/engine/src/flutter/shell/platform/darwin/ios/ios_surface_gl.h @@ -38,7 +38,7 @@ class IOSSurfaceGL final : public IOSSurface, public GPUSurfaceGLDelegate { bool GLContextClearCurrent() override; // |GPUSurfaceGLDelegate| - bool GLContextPresent(uint32_t fbo_id) override; + bool GLContextPresent(uint32_t fbo_id, const std::optional& damage) override; // |GPUSurfaceGLDelegate| intptr_t GLContextFBO(GLFrameInfo frame_info) const override; diff --git a/engine/src/flutter/shell/platform/darwin/ios/ios_surface_gl.mm b/engine/src/flutter/shell/platform/darwin/ios/ios_surface_gl.mm index 9b6bb6a99aa..fb1658e2bbe 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/ios_surface_gl.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/ios_surface_gl.mm @@ -86,7 +86,7 @@ bool IOSSurfaceGL::GLContextClearCurrent() { } // |GPUSurfaceGLDelegate| -bool IOSSurfaceGL::GLContextPresent(uint32_t fbo_id) { +bool IOSSurfaceGL::GLContextPresent(uint32_t fbo_id, const std::optional& damage) { TRACE_EVENT0("flutter", "IOSSurfaceGL::GLContextPresent"); return IsValid() && render_target_->PresentRenderBuffer(); } diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl.cc b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl.cc index 69c75cb2e74..dfe67e290b0 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl.cc @@ -45,7 +45,8 @@ bool EmbedderSurfaceGL::GLContextClearCurrent() { } // |GPUSurfaceGLDelegate| -bool EmbedderSurfaceGL::GLContextPresent(uint32_t fbo_id) { +bool EmbedderSurfaceGL::GLContextPresent(uint32_t fbo_id, + const std::optional& damage) { return gl_dispatch_table_.gl_present_callback(fbo_id); } diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl.h b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl.h index 6f07bcce9ef..16700702810 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl.h +++ b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl.h @@ -56,7 +56,8 @@ class EmbedderSurfaceGL final : public EmbedderSurface, bool GLContextClearCurrent() override; // |GPUSurfaceGLDelegate| - bool GLContextPresent(uint32_t fbo_id) override; + bool GLContextPresent(uint32_t fbo_id, + const std::optional& damage) override; // |GPUSurfaceGLDelegate| intptr_t GLContextFBO(GLFrameInfo frame_info) const override;