Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 57a067c

Browse files
authored
Enable partial repaint for Android/OpenGL (#29591)
1 parent 174b04e commit 57a067c

15 files changed

+232
-20
lines changed

shell/common/shell_test_platform_view_gl.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ bool ShellTestPlatformViewGL::GLContextClearCurrent() {
6363
}
6464

6565
// |GPUSurfaceGLDelegate|
66-
bool ShellTestPlatformViewGL::GLContextPresent(uint32_t fbo_id) {
66+
bool ShellTestPlatformViewGL::GLContextPresent(
67+
uint32_t fbo_id,
68+
const std::optional<SkIRect>& damage) {
6769
return gl_surface_.Present();
6870
}
6971

shell/common/shell_test_platform_view_gl.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ class ShellTestPlatformViewGL : public ShellTestPlatformView,
5858
bool GLContextClearCurrent() override;
5959

6060
// |GPUSurfaceGLDelegate|
61-
bool GLContextPresent(uint32_t fbo_id) override;
61+
bool GLContextPresent(uint32_t fbo_id,
62+
const std::optional<SkIRect>& damage) override;
6263

6364
// |GPUSurfaceGLDelegate|
6465
intptr_t GLContextFBO(GLFrameInfo frame_info) const override;

shell/gpu/gpu_surface_gl.cc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ std::unique_ptr<SurfaceFrame> GPUSurfaceGL::AcquireFrame(const SkISize& size) {
242242
SurfaceFrame::SubmitCallback submit_callback =
243243
[weak = weak_factory_.GetWeakPtr()](const SurfaceFrame& surface_frame,
244244
SkCanvas* canvas) {
245-
return weak ? weak->PresentSurface(canvas) : false;
245+
return weak ? weak->PresentSurface(surface_frame, canvas) : false;
246246
};
247247

248248
framebuffer_info = delegate_->GLContextFramebufferInfo();
@@ -251,17 +251,19 @@ std::unique_ptr<SurfaceFrame> GPUSurfaceGL::AcquireFrame(const SkISize& size) {
251251
std::move(context_switch));
252252
}
253253

254-
bool GPUSurfaceGL::PresentSurface(SkCanvas* canvas) {
254+
bool GPUSurfaceGL::PresentSurface(const SurfaceFrame& frame, SkCanvas* canvas) {
255255
if (delegate_ == nullptr || canvas == nullptr || context_ == nullptr) {
256256
return false;
257257
}
258258

259+
delegate_->GLContextSetDamageRegion(frame.submit_info().buffer_damage);
260+
259261
{
260262
TRACE_EVENT0("flutter", "SkCanvas::Flush");
261263
onscreen_surface_->getCanvas()->flush();
262264
}
263265

264-
if (!delegate_->GLContextPresent(fbo_id_)) {
266+
if (!delegate_->GLContextPresent(fbo_id_, frame.submit_info().frame_damage)) {
265267
return false;
266268
}
267269

shell/gpu/gpu_surface_gl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class GPUSurfaceGL : public Surface {
6060
const SkISize& untransformed_size,
6161
const SkMatrix& root_surface_transformation);
6262

63-
bool PresentSurface(SkCanvas* canvas);
63+
bool PresentSurface(const SurfaceFrame& frame, SkCanvas* canvas);
6464

6565
GPUSurfaceGLDelegate* delegate_;
6666
sk_sp<GrDirectContext> context_;

shell/gpu/gpu_surface_gl_delegate.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#ifndef FLUTTER_SHELL_GPU_GPU_SURFACE_GL_DELEGATE_H_
66
#define FLUTTER_SHELL_GPU_GPU_SURFACE_GL_DELEGATE_H_
77

8+
#include <optional>
9+
810
#include "flutter/common/graphics/gl_context_switch.h"
911
#include "flutter/flow/embedded_views.h"
1012
#include "flutter/fml/macros.h"
@@ -31,9 +33,17 @@ class GPUSurfaceGLDelegate {
3133
// either the GPU or IO threads.
3234
virtual bool GLContextClearCurrent() = 0;
3335

36+
// Inform the GL Context that there's going to be no writing beyond
37+
// the specified region
38+
virtual void GLContextSetDamageRegion(const std::optional<SkIRect>& region) {}
39+
3440
// Called to present the main GL surface. This is only called for the main GL
3541
// context and not any of the contexts dedicated for IO.
36-
virtual bool GLContextPresent(uint32_t fbo_id) = 0;
42+
//
43+
// Damage is a hint to compositor telling it which parts of front buffer
44+
// need to be updated
45+
virtual bool GLContextPresent(uint32_t fbo_id,
46+
const std::optional<SkIRect>& damage) = 0;
3747

3848
// The ID of the main window bound framebuffer. Typically FBO0.
3949
virtual intptr_t GLContextFBO(GLFrameInfo frame_info) const = 0;

shell/platform/android/android_context_gl.cc

Lines changed: 146 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66

77
#include <EGL/eglext.h>
88

9+
#include <list>
910
#include <utility>
1011

12+
// required to get API level
13+
#include <sys/system_properties.h>
14+
1115
#include "flutter/fml/trace_event.h"
1216

1317
namespace flutter {
@@ -105,10 +109,135 @@ static bool TeardownContext(EGLDisplay display, EGLContext context) {
105109
return true;
106110
}
107111

112+
class AndroidEGLSurfaceDamage {
113+
public:
114+
void init(EGLDisplay display, EGLContext context) {
115+
if (GetAPILevel() < 28) {
116+
// Disable partial repaint for devices older than Android 9. There
117+
// are old devices that have extensions below available but the
118+
// implementation causes glitches (i.e. Xperia Z3 with Android 6).
119+
partial_redraw_supported_ = false;
120+
return;
121+
}
122+
123+
const char* extensions = eglQueryString(display, EGL_EXTENSIONS);
124+
125+
if (HasExtension(extensions, "EGL_KHR_partial_update")) {
126+
set_damage_region_ = reinterpret_cast<PFNEGLSETDAMAGEREGIONKHRPROC>(
127+
eglGetProcAddress("eglSetDamageRegionKHR"));
128+
}
129+
130+
if (HasExtension(extensions, "EGL_EXT_swap_buffers_with_damage")) {
131+
swap_buffers_with_damage_ =
132+
reinterpret_cast<PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC>(
133+
eglGetProcAddress("eglSwapBuffersWithDamageEXT"));
134+
} else if (HasExtension(extensions, "EGL_KHR_swap_buffers_with_damage")) {
135+
swap_buffers_with_damage_ =
136+
reinterpret_cast<PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC>(
137+
eglGetProcAddress("eglSwapBuffersWithDamageKHR"));
138+
}
139+
140+
partial_redraw_supported_ =
141+
set_damage_region_ != nullptr && swap_buffers_with_damage_ != nullptr;
142+
}
143+
144+
static int GetAPILevel() {
145+
char sdk_version_string[PROP_VALUE_MAX];
146+
if (__system_property_get("ro.build.version.sdk", sdk_version_string)) {
147+
return atoi(sdk_version_string);
148+
} else {
149+
return -1;
150+
}
151+
}
152+
153+
void SetDamageRegion(EGLDisplay display,
154+
EGLSurface surface,
155+
const std::optional<SkIRect>& region) {
156+
if (set_damage_region_ && region) {
157+
auto rects = RectToInts(display, surface, *region);
158+
set_damage_region_(display, surface, rects.data(), 1);
159+
}
160+
}
161+
162+
// Maximum damage history - for triple buffering we need to store damage for
163+
// last two frames; Some Android devices (Pixel 4) use quad buffering.
164+
static const int kMaxHistorySize = 10;
165+
166+
bool SupportsPartialRepaint() const { return partial_redraw_supported_; }
167+
168+
std::optional<SkIRect> InitialDamage(EGLDisplay display, EGLSurface surface) {
169+
if (!partial_redraw_supported_) {
170+
return std::nullopt;
171+
}
172+
173+
EGLint age;
174+
eglQuerySurface(display, surface, EGL_BUFFER_AGE_EXT, &age);
175+
176+
if (age == 0) { // full repaint
177+
return std::nullopt;
178+
} else {
179+
// join up to (age - 1) last rects from damage history
180+
--age;
181+
auto res = SkIRect::MakeEmpty();
182+
for (auto i = damage_history_.rbegin();
183+
i != damage_history_.rend() && age > 0; ++i, --age) {
184+
res.join(*i);
185+
}
186+
return res;
187+
}
188+
}
189+
190+
bool SwapBuffersWithDamage(EGLDisplay display,
191+
EGLSurface surface,
192+
const std::optional<SkIRect>& damage) {
193+
if (swap_buffers_with_damage_ && damage) {
194+
damage_history_.push_back(*damage);
195+
if (damage_history_.size() > kMaxHistorySize) {
196+
damage_history_.pop_front();
197+
}
198+
auto rects = RectToInts(display, surface, *damage);
199+
return swap_buffers_with_damage_(display, surface, rects.data(), 1);
200+
} else {
201+
return eglSwapBuffers(display, surface);
202+
}
203+
}
204+
205+
private:
206+
std::array<EGLint, 4> static RectToInts(EGLDisplay display,
207+
EGLSurface surface,
208+
const SkIRect& rect) {
209+
EGLint height;
210+
eglQuerySurface(display, surface, EGL_HEIGHT, &height);
211+
212+
std::array<EGLint, 4> res{rect.left(), height - rect.bottom(), rect.width(),
213+
rect.height()};
214+
return res;
215+
}
216+
217+
PFNEGLSETDAMAGEREGIONKHRPROC set_damage_region_ = nullptr;
218+
PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage_ = nullptr;
219+
220+
bool partial_redraw_supported_;
221+
222+
bool HasExtension(const char* extensions, const char* name) {
223+
const char* r = strstr(extensions, name);
224+
auto len = strlen(name);
225+
// check that the extension name is terminated by space or null terminator
226+
return r != nullptr && (r[len] == ' ' || r[len] == 0);
227+
}
228+
229+
std::list<SkIRect> damage_history_;
230+
};
231+
108232
AndroidEGLSurface::AndroidEGLSurface(EGLSurface surface,
109233
EGLDisplay display,
110234
EGLContext context)
111-
: surface_(surface), display_(display), context_(context) {}
235+
: surface_(surface),
236+
display_(display),
237+
context_(context),
238+
damage_(std::make_unique<AndroidEGLSurfaceDamage>()) {
239+
damage_->init(display_, context);
240+
}
112241

113242
AndroidEGLSurface::~AndroidEGLSurface() {
114243
auto result = eglDestroySurface(display_, surface_);
@@ -128,9 +257,23 @@ bool AndroidEGLSurface::MakeCurrent() const {
128257
return true;
129258
}
130259

131-
bool AndroidEGLSurface::SwapBuffers() {
260+
void AndroidEGLSurface::SetDamageRegion(
261+
const std::optional<SkIRect>& buffer_damage) {
262+
damage_->SetDamageRegion(display_, surface_, buffer_damage);
263+
}
264+
265+
bool AndroidEGLSurface::SwapBuffers(
266+
const std::optional<SkIRect>& surface_damage) {
132267
TRACE_EVENT0("flutter", "AndroidContextGL::SwapBuffers");
133-
return eglSwapBuffers(display_, surface_);
268+
return damage_->SwapBuffersWithDamage(display_, surface_, surface_damage);
269+
}
270+
271+
bool AndroidEGLSurface::SupportsPartialRepaint() const {
272+
return damage_->SupportsPartialRepaint();
273+
}
274+
275+
std::optional<SkIRect> AndroidEGLSurface::InitialDamage() {
276+
return damage_->InitialDamage(display_, surface_);
134277
}
135278

136279
SkISize AndroidEGLSurface::GetSize() const {

shell/platform/android/android_context_gl.h

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ namespace flutter {
2323
/// This can be used in conjunction to unique_ptr to provide better guarantees
2424
/// about the lifespan of the `EGLSurface` object.
2525
///
26+
class AndroidEGLSurfaceDamage;
27+
2628
class AndroidEGLSurface {
2729
public:
2830
AndroidEGLSurface(EGLSurface surface, EGLDisplay display, EGLContext context);
@@ -43,13 +45,35 @@ class AndroidEGLSurface {
4345
///
4446
bool MakeCurrent() const;
4547

48+
//----------------------------------------------------------------------------
49+
///
50+
/// @return Whether target surface supports partial repaint.
51+
///
52+
bool SupportsPartialRepaint() const;
53+
54+
//----------------------------------------------------------------------------
55+
/// @brief This is the minimal area that needs to be repainted to get
56+
/// correct result.
57+
///
58+
/// With double or triple buffering this buffer content may lag behind
59+
/// current front buffer and the rect accounts for accumulated damage.
60+
///
61+
/// @return The area of current surface where it is behind front buffer.
62+
///
63+
std::optional<SkIRect> InitialDamage();
64+
65+
//----------------------------------------------------------------------------
66+
/// @brief Sets the damage region for current surface. Corresponds to
67+
// eglSetDamageRegionKHR
68+
void SetDamageRegion(const std::optional<SkIRect>& buffer_damage);
69+
4670
//----------------------------------------------------------------------------
4771
/// @brief This only applies to on-screen surfaces such as those created
4872
/// by `AndroidContextGL::CreateOnscreenSurface`.
4973
///
5074
/// @return Whether the EGL surface color buffer was swapped.
5175
///
52-
bool SwapBuffers();
76+
bool SwapBuffers(const std::optional<SkIRect>& surface_damage);
5377

5478
//----------------------------------------------------------------------------
5579
/// @return The size of an `EGLSurface`.
@@ -60,6 +84,7 @@ class AndroidEGLSurface {
6084
const EGLSurface surface_;
6185
const EGLDisplay display_;
6286
const EGLContext context_;
87+
std::unique_ptr<AndroidEGLSurfaceDamage> damage_;
6388
};
6489

6590
//------------------------------------------------------------------------------

shell/platform/android/android_surface_gl.cc

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,27 @@ bool AndroidSurfaceGL::GLContextClearCurrent() {
126126
return GLContextPtr()->ClearCurrent();
127127
}
128128

129-
bool AndroidSurfaceGL::GLContextPresent(uint32_t fbo_id) {
129+
SurfaceFrame::FramebufferInfo AndroidSurfaceGL::GLContextFramebufferInfo()
130+
const {
131+
FML_DCHECK(IsValid());
132+
SurfaceFrame::FramebufferInfo res;
133+
res.supports_readback = true;
134+
res.supports_partial_repaint = onscreen_surface_->SupportsPartialRepaint();
135+
res.existing_damage = onscreen_surface_->InitialDamage();
136+
return res;
137+
}
138+
139+
void AndroidSurfaceGL::GLContextSetDamageRegion(
140+
const std::optional<SkIRect>& region) {
141+
FML_DCHECK(IsValid());
142+
onscreen_surface_->SetDamageRegion(region);
143+
}
144+
145+
bool AndroidSurfaceGL::GLContextPresent(uint32_t fbo_id,
146+
const std::optional<SkIRect>& damage) {
130147
FML_DCHECK(IsValid());
131148
FML_DCHECK(onscreen_surface_);
132-
return onscreen_surface_->SwapBuffers();
149+
return onscreen_surface_->SwapBuffers(damage);
133150
}
134151

135152
intptr_t AndroidSurfaceGL::GLContextFBO(GLFrameInfo frame_info) const {

shell/platform/android/android_surface_gl.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,14 @@ class AndroidSurfaceGL final : public GPUSurfaceGLDelegate,
5858
bool GLContextClearCurrent() override;
5959

6060
// |GPUSurfaceGLDelegate|
61-
bool GLContextPresent(uint32_t fbo_id) override;
61+
SurfaceFrame::FramebufferInfo GLContextFramebufferInfo() const override;
62+
63+
// |GPUSurfaceGLDelegate|
64+
void GLContextSetDamageRegion(const std::optional<SkIRect>& region) override;
65+
66+
// |GPUSurfaceGLDelegate|
67+
bool GLContextPresent(uint32_t fbo_id,
68+
const std::optional<SkIRect>& damage) override;
6269

6370
// |GPUSurfaceGLDelegate|
6471
intptr_t GLContextFBO(GLFrameInfo frame_info) const override;

shell/platform/android/surface/android_surface_mock.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ bool AndroidSurfaceMock::GLContextClearCurrent() {
1818
return true;
1919
}
2020

21-
bool AndroidSurfaceMock::GLContextPresent(uint32_t fbo_id) {
21+
bool AndroidSurfaceMock::GLContextPresent(
22+
uint32_t fbo_id,
23+
const std::optional<SkIRect>& damage) {
2224
return true;
2325
}
2426

0 commit comments

Comments
 (0)