Skip to content

Commit

Permalink
Enable partial repaint for Android/OpenGL (#29591)
Browse files Browse the repository at this point in the history
  • Loading branch information
knopp authored Jan 20, 2022
1 parent 174b04e commit 57a067c
Show file tree
Hide file tree
Showing 15 changed files with 232 additions and 20 deletions.
4 changes: 3 additions & 1 deletion shell/common/shell_test_platform_view_gl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<SkIRect>& damage) {
return gl_surface_.Present();
}

Expand Down
3 changes: 2 additions & 1 deletion shell/common/shell_test_platform_view_gl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<SkIRect>& damage) override;

// |GPUSurfaceGLDelegate|
intptr_t GLContextFBO(GLFrameInfo frame_info) const override;
Expand Down
8 changes: 5 additions & 3 deletions shell/gpu/gpu_surface_gl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ std::unique_ptr<SurfaceFrame> 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();
Expand All @@ -251,17 +251,19 @@ std::unique_ptr<SurfaceFrame> 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;
}

Expand Down
2 changes: 1 addition & 1 deletion shell/gpu/gpu_surface_gl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<GrDirectContext> context_;
Expand Down
12 changes: 11 additions & 1 deletion shell/gpu/gpu_surface_gl_delegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#ifndef FLUTTER_SHELL_GPU_GPU_SURFACE_GL_DELEGATE_H_
#define FLUTTER_SHELL_GPU_GPU_SURFACE_GL_DELEGATE_H_

#include <optional>

#include "flutter/common/graphics/gl_context_switch.h"
#include "flutter/flow/embedded_views.h"
#include "flutter/fml/macros.h"
Expand All @@ -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<SkIRect>& 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<SkIRect>& damage) = 0;

// The ID of the main window bound framebuffer. Typically FBO0.
virtual intptr_t GLContextFBO(GLFrameInfo frame_info) const = 0;
Expand Down
149 changes: 146 additions & 3 deletions shell/platform/android/android_context_gl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@

#include <EGL/eglext.h>

#include <list>
#include <utility>

// required to get API level
#include <sys/system_properties.h>

#include "flutter/fml/trace_event.h"

namespace flutter {
Expand Down Expand Up @@ -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<PFNEGLSETDAMAGEREGIONKHRPROC>(
eglGetProcAddress("eglSetDamageRegionKHR"));
}

if (HasExtension(extensions, "EGL_EXT_swap_buffers_with_damage")) {
swap_buffers_with_damage_ =
reinterpret_cast<PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC>(
eglGetProcAddress("eglSwapBuffersWithDamageEXT"));
} else if (HasExtension(extensions, "EGL_KHR_swap_buffers_with_damage")) {
swap_buffers_with_damage_ =
reinterpret_cast<PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC>(
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<SkIRect>& 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<SkIRect> 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<SkIRect>& 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<EGLint, 4> static RectToInts(EGLDisplay display,
EGLSurface surface,
const SkIRect& rect) {
EGLint height;
eglQuerySurface(display, surface, EGL_HEIGHT, &height);

std::array<EGLint, 4> 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<SkIRect> 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<AndroidEGLSurfaceDamage>()) {
damage_->init(display_, context);
}

AndroidEGLSurface::~AndroidEGLSurface() {
auto result = eglDestroySurface(display_, surface_);
Expand All @@ -128,9 +257,23 @@ bool AndroidEGLSurface::MakeCurrent() const {
return true;
}

bool AndroidEGLSurface::SwapBuffers() {
void AndroidEGLSurface::SetDamageRegion(
const std::optional<SkIRect>& buffer_damage) {
damage_->SetDamageRegion(display_, surface_, buffer_damage);
}

bool AndroidEGLSurface::SwapBuffers(
const std::optional<SkIRect>& 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<SkIRect> AndroidEGLSurface::InitialDamage() {
return damage_->InitialDamage(display_, surface_);
}

SkISize AndroidEGLSurface::GetSize() const {
Expand Down
27 changes: 26 additions & 1 deletion shell/platform/android/android_context_gl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<SkIRect> InitialDamage();

//----------------------------------------------------------------------------
/// @brief Sets the damage region for current surface. Corresponds to
// eglSetDamageRegionKHR
void SetDamageRegion(const std::optional<SkIRect>& 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<SkIRect>& surface_damage);

//----------------------------------------------------------------------------
/// @return The size of an `EGLSurface`.
Expand All @@ -60,6 +84,7 @@ class AndroidEGLSurface {
const EGLSurface surface_;
const EGLDisplay display_;
const EGLContext context_;
std::unique_ptr<AndroidEGLSurfaceDamage> damage_;
};

//------------------------------------------------------------------------------
Expand Down
21 changes: 19 additions & 2 deletions shell/platform/android/android_surface_gl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<SkIRect>& region) {
FML_DCHECK(IsValid());
onscreen_surface_->SetDamageRegion(region);
}

bool AndroidSurfaceGL::GLContextPresent(uint32_t fbo_id,
const std::optional<SkIRect>& 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 {
Expand Down
9 changes: 8 additions & 1 deletion shell/platform/android/android_surface_gl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<SkIRect>& region) override;

// |GPUSurfaceGLDelegate|
bool GLContextPresent(uint32_t fbo_id,
const std::optional<SkIRect>& damage) override;

// |GPUSurfaceGLDelegate|
intptr_t GLContextFBO(GLFrameInfo frame_info) const override;
Expand Down
4 changes: 3 additions & 1 deletion shell/platform/android/surface/android_surface_mock.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<SkIRect>& damage) {
return true;
}

Expand Down
3 changes: 2 additions & 1 deletion shell/platform/android/surface/android_surface_mock.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<SkIRect>& damage) override;

// |GPUSurfaceGLDelegate|
intptr_t GLContextFBO(GLFrameInfo frame_info) const override;
Expand Down
2 changes: 1 addition & 1 deletion shell/platform/darwin/ios/ios_surface_gl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<SkIRect>& damage) override;

// |GPUSurfaceGLDelegate|
intptr_t GLContextFBO(GLFrameInfo frame_info) const override;
Expand Down
Loading

0 comments on commit 57a067c

Please sign in to comment.