Enable dirty region management within the Embedder API (#35022)
diff --git a/shell/common/shell_test_platform_view_gl.cc b/shell/common/shell_test_platform_view_gl.cc
index 4923542..ac68ff3 100644
--- a/shell/common/shell_test_platform_view_gl.cc
+++ b/shell/common/shell_test_platform_view_gl.cc
@@ -69,8 +69,10 @@
}
// |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{
+ .fbo_id = 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 c890c1e..b4afc12 100644
--- a/shell/common/shell_test_platform_view_gl.h
+++ b/shell/common/shell_test_platform_view_gl.h
@@ -61,7 +61,7 @@
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 f9da6a1..d1e31b9 100644
--- a/shell/gpu/gpu_surface_gl_delegate.h
+++ b/shell/gpu/gpu_surface_gl_delegate.h
@@ -22,17 +22,32 @@
uint32_t height;
};
+// A structure to represent the frame buffer information which is returned to
+// the rendering backend after requesting a frame buffer object.
+struct GLFBOInfo {
+ // The frame buffer's ID.
+ uint32_t fbo_id;
+ // This boolean flags whether the returned FBO supports partial repaint.
+ const bool partial_repaint_enabled;
+ // The frame buffer's existing damage (i.e. damage since it was last used).
+ const SkIRect existing_damage;
+};
+
// Information passed during presentation of a frame.
struct GLPresentInfo {
uint32_t fbo_id;
- // Damage is a hint to compositor telling it which parts of front buffer
- // need to be updated
- const std::optional<SkIRect>& damage;
+ // The frame damage is a hint to compositor telling it which parts of front
+ // buffer need to be updated.
+ const std::optional<SkIRect>& 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<fml::TimePoint> presentation_time = std::nullopt;
+
+ // The buffer damage refers to the region that needs to be set as damaged
+ // within the frame buffer.
+ const std::optional<SkIRect>& buffer_damage;
};
class GPUSurfaceGLDelegate {
@@ -54,8 +69,9 @@
// context and not any of the contexts dedicated for IO.
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;
+ // The information about the main window bound framebuffer. ID is Typically
+ // FBO0.
+ 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 2441314..3fc7c44 100644
--- a/shell/gpu/gpu_surface_gl_impeller.cc
+++ b/shell/gpu/gpu_surface_gl_impeller.cc
@@ -62,10 +62,11 @@
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 66bb663..eafb612 100644
--- a/shell/gpu/gpu_surface_gl_skia.cc
+++ b/shell/gpu/gpu_surface_gl_skia.cc
@@ -181,10 +181,10 @@
GLFrameInfo frame_info = {static_cast<uint32_t>(size.width()),
static_cast<uint32_t>(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,7 +195,9 @@
}
onscreen_surface_ = std::move(onscreen_surface);
- fbo_id_ = fbo_id;
+ fbo_id_ = fbo_info.fbo_id;
+ supports_partial_repaint_ = fbo_info.partial_repaint_enabled;
+ existing_damage_ = fbo_info.existing_damage;
return true;
}
@@ -248,6 +250,9 @@
};
framebuffer_info = delegate_->GLContextFramebufferInfo();
+ // Partial repaint is enabled by default
+ framebuffer_info.supports_partial_repaint = supports_partial_repaint_;
+ framebuffer_info.existing_damage = existing_damage_;
return std::make_unique<SurfaceFrame>(surface, std::move(framebuffer_info),
submit_callback,
std::move(context_switch));
@@ -268,8 +273,9 @@
GLPresentInfo present_info = {
.fbo_id = fbo_id_,
- .damage = frame.submit_info().frame_damage,
+ .frame_damage = frame.submit_info().frame_damage,
.presentation_time = frame.submit_info().presentation_time,
+ .buffer_damage = frame.submit_info().buffer_damage,
};
if (!delegate_->GLContextPresent(present_info)) {
return false;
@@ -284,11 +290,11 @@
// 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
+ fbo_info.fbo_id // window FBO ID
);
if (!new_onscreen_surface) {
@@ -296,7 +302,9 @@
}
onscreen_surface_ = std::move(new_onscreen_surface);
- fbo_id_ = fbo_id;
+ fbo_id_ = fbo_info.fbo_id;
+ supports_partial_repaint_ = fbo_info.partial_repaint_enabled;
+ 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 90cad24..39f6e3f 100644
--- a/shell/gpu/gpu_surface_gl_skia.h
+++ b/shell/gpu/gpu_surface_gl_skia.h
@@ -67,6 +67,8 @@
sk_sp<SkSurface> onscreen_surface_;
/// FBO backing the current `onscreen_surface_`.
uint32_t fbo_id_ = 0;
+ // Private variable used to keep track of the current FBO's existing damage.
+ 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
@@ -74,6 +76,8 @@
// external view embedder is present.
const bool render_to_surface_ = true;
bool valid_ = false;
+ // Partial repaint is on by default.
+ bool supports_partial_repaint_ = true;
// WeakPtrFactory must be the last member.
fml::TaskRunnerAffineWeakPtrFactory<GPUSurfaceGLSkia> weak_factory_;
diff --git a/shell/platform/android/android_surface_gl_impeller.cc b/shell/platform/android/android_surface_gl_impeller.cc
index 28c1cfe..635acc7 100644
--- a/shell/platform/android/android_surface_gl_impeller.cc
+++ b/shell/platform/android/android_surface_gl_impeller.cc
@@ -295,9 +295,11 @@
}
// |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{
+ .fbo_id = 0,
+ };
}
// |GPUSurfaceGLDelegate|
diff --git a/shell/platform/android/android_surface_gl_impeller.h b/shell/platform/android/android_surface_gl_impeller.h
index 8bcf14d..9d23998 100644
--- a/shell/platform/android/android_surface_gl_impeller.h
+++ b/shell/platform/android/android_surface_gl_impeller.h
@@ -68,7 +68,7 @@
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<const GrGLInterface> GetGLInterface() const override;
diff --git a/shell/platform/android/android_surface_gl_skia.cc b/shell/platform/android/android_surface_gl_skia.cc
index 23242c9..50ac308 100644
--- a/shell/platform/android/android_surface_gl_skia.cc
+++ b/shell/platform/android/android_surface_gl_skia.cc
@@ -159,13 +159,15 @@
if (present_info.presentation_time) {
onscreen_surface_->SetPresentationTime(*present_info.presentation_time);
}
- return onscreen_surface_->SwapBuffers(present_info.damage);
+ return onscreen_surface_->SwapBuffers(present_info.frame_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{
+ .fbo_id = 0,
+ };
}
// |GPUSurfaceGLDelegate|
diff --git a/shell/platform/android/android_surface_gl_skia.h b/shell/platform/android/android_surface_gl_skia.h
index 99a910b..73f3694 100644
--- a/shell/platform/android/android_surface_gl_skia.h
+++ b/shell/platform/android/android_surface_gl_skia.h
@@ -67,7 +67,7 @@
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<const GrGLInterface> GetGLInterface() const override;
diff --git a/shell/platform/android/surface/android_surface_mock.cc b/shell/platform/android/surface/android_surface_mock.cc
index c3a9f38..8f718fc 100644
--- a/shell/platform/android/surface/android_surface_mock.cc
+++ b/shell/platform/android/surface/android_surface_mock.cc
@@ -22,8 +22,10 @@
return true;
}
-intptr_t AndroidSurfaceMock::GLContextFBO(GLFrameInfo frame_info) const {
- return 0;
+GLFBOInfo AndroidSurfaceMock::GLContextFBO(GLFrameInfo frame_info) const {
+ return GLFBOInfo{
+ .fbo_id = 0,
+ };
}
} // namespace flutter
diff --git a/shell/platform/android/surface/android_surface_mock.h b/shell/platform/android/surface/android_surface_mock.h
index d736387..3f326a2 100644
--- a/shell/platform/android/surface/android_surface_mock.h
+++ b/shell/platform/android/surface/android_surface_mock.h
@@ -51,7 +51,7 @@
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/embedder/embedder.cc b/shell/platform/embedder/embedder.cc
index 0868901..97fced2 100644
--- a/shell/platform/embedder/embedder.cc
+++ b/shell/platform/embedder/embedder.cc
@@ -132,6 +132,12 @@
return false;
}
+ if (!SAFE_EXISTS(open_gl_config, populate_existing_damage)) {
+ FML_LOG(INFO) << "populate_existing_damage was not defined, disabling "
+ "partial repaint. If you wish to enable partial repaint, "
+ "please define this callback.";
+ }
+
return true;
}
@@ -222,7 +228,29 @@
}
#endif // FML_OS_LINUX || FML_OS_WIN
-static flutter::Shell::CreateCallback<flutter::PlatformView>
+#ifdef SHELL_ENABLE_GL
+// Auxiliary function used to translate rectangles of type SkIRect to
+// FlutterRect.
+static FlutterRect SkIRectToFlutterRect(const SkIRect sk_rect) {
+ FlutterRect flutter_rect = {static_cast<double>(sk_rect.fLeft),
+ static_cast<double>(sk_rect.fTop),
+ static_cast<double>(sk_rect.fRight),
+ static_cast<double>(sk_rect.fBottom)};
+ return flutter_rect;
+}
+
+// Auxiliary function used to translate rectangles of type FlutterRect to
+// SkIRect.
+static const SkIRect FlutterRectToSkIRect(FlutterRect flutter_rect) {
+ SkIRect rect = {static_cast<int32_t>(flutter_rect.left),
+ static_cast<int32_t>(flutter_rect.top),
+ static_cast<int32_t>(flutter_rect.right),
+ static_cast<int32_t>(flutter_rect.bottom)};
+ return rect;
+}
+#endif
+
+static inline flutter::Shell::CreateCallback<flutter::PlatformView>
InferOpenGLPlatformViewCreationCallback(
const FlutterRendererConfig* config,
void* user_data,
@@ -241,15 +269,45 @@
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 {
+ 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 = {};
- present_info.struct_size = sizeof(FlutterPresentInfo);
- present_info.fbo_id = fbo_id;
+ // Format the frame and buffer damages accordingly. Note that, since the
+ // current compute damage algorithm only returns one rectangle for damage
+ // we are assuming the number of rectangles provided in frame and buffer
+ // damage are always 1. Once the function that computes damage implements
+ // support for multiple damage rectangles, GLPresentInfo should also
+ // contain the number of damage rectangles.
+ const size_t num_rects = 1;
+
+ std::array<FlutterRect, num_rects> frame_damage_rect = {
+ SkIRectToFlutterRect(*(gl_present_info.frame_damage))};
+ std::array<FlutterRect, num_rects> buffer_damage_rect = {
+ SkIRectToFlutterRect(*(gl_present_info.buffer_damage))};
+
+ FlutterDamage frame_damage{
+ .struct_size = sizeof(FlutterDamage),
+ .num_rects = frame_damage_rect.size(),
+ .damage = frame_damage_rect.data(),
+ };
+ FlutterDamage buffer_damage{
+ .struct_size = sizeof(FlutterDamage),
+ .num_rects = buffer_damage_rect.size(),
+ .damage = buffer_damage_rect.data(),
+ };
+
+ // Construct the present information concerning the frame being rendered.
+ FlutterPresentInfo present_info = {
+ .struct_size = sizeof(FlutterPresentInfo),
+ .fbo_id = gl_present_info.fbo_id,
+ .frame_damage = frame_damage,
+ .buffer_damage = buffer_damage,
+ };
+
return present_with_info(user_data, &present_info);
}
};
@@ -269,6 +327,50 @@
}
};
+ auto gl_populate_existing_damage =
+ [populate_existing_damage = config->open_gl.populate_existing_damage,
+ user_data](intptr_t id) -> flutter::GLFBOInfo {
+ // If no populate_existing_damage was provided, disable partial
+ // repaint.
+ if (!populate_existing_damage) {
+ return flutter::GLFBOInfo{
+ .fbo_id = static_cast<uint32_t>(id),
+ .partial_repaint_enabled = false,
+ .existing_damage = SkIRect::MakeEmpty(),
+ };
+ }
+
+ // Given the FBO's ID, get its existing damage.
+ FlutterDamage existing_damage;
+ populate_existing_damage(user_data, id, &existing_damage);
+
+ bool partial_repaint_enabled = true;
+ SkIRect existing_damage_rect;
+
+ // Verify that at least one damage rectangle was provided.
+ if (existing_damage.num_rects <= 0 || existing_damage.damage == nullptr) {
+ FML_LOG(INFO) << "No damage was provided. Forcing full repaint.";
+ existing_damage_rect = SkIRect::MakeEmpty();
+ partial_repaint_enabled = false;
+ } else if (existing_damage.num_rects > 1) {
+ // Log message notifying users that multi-damage is not yet available in
+ // case they try to make use of it.
+ FML_LOG(INFO) << "Damage with multiple rectangles not yet supported. "
+ "Repainting the whole frame.";
+ existing_damage_rect = SkIRect::MakeEmpty();
+ partial_repaint_enabled = false;
+ } else {
+ existing_damage_rect = FlutterRectToSkIRect(*(existing_damage.damage));
+ }
+
+ // Pass the information about this FBO to the rendering backend.
+ return flutter::GLFBOInfo{
+ .fbo_id = static_cast<uint32_t>(id),
+ .partial_repaint_enabled = partial_repaint_enabled,
+ .existing_damage = existing_damage_rect,
+ };
+ };
+
const FlutterOpenGLRendererConfig* open_gl_config = &config->open_gl;
std::function<bool()> gl_make_resource_current_callback = nullptr;
if (SAFE_ACCESS(open_gl_config, make_resource_current, nullptr) != nullptr) {
@@ -326,6 +428,7 @@
gl_make_resource_current_callback, // gl_make_resource_current_callback
gl_surface_transformation_callback, // gl_surface_transformation_callback
gl_proc_resolver, // gl_proc_resolver
+ gl_populate_existing_damage, // gl_populate_existing_damage
};
return fml::MakeCopyable(
diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h
index 88a3014..93e92b7 100644
--- a/shell/platform/embedder/embedder.h
+++ b/shell/platform/embedder/embedder.h
@@ -433,6 +433,16 @@
FlutterSize lower_left_corner_radius;
} FlutterRoundedRect;
+/// A structure to represent a damage region.
+typedef struct {
+ /// The size of this struct. Must be sizeof(FlutterDamage).
+ size_t struct_size;
+ /// The number of rectangles within the damage region.
+ size_t num_rects;
+ /// The actual damage region(s) in question.
+ FlutterRect* damage;
+} FlutterDamage;
+
/// This information is passed to the embedder when requesting a frame buffer
/// object.
///
@@ -449,6 +459,13 @@
void* /* user data */,
const FlutterFrameInfo* /* frame info */);
+/// Callback for when a frame buffer object is requested with necessary
+/// information for partial repaint.
+typedef void (*FlutterFrameBufferWithDamageCallback)(
+ void* /* user data */,
+ const intptr_t /* fbo id */,
+ FlutterDamage* /* existing damage */);
+
/// This information is passed to the embedder when a surface is presented.
///
/// See: \ref FlutterOpenGLRendererConfig.present_with_info.
@@ -457,6 +474,10 @@
size_t struct_size;
/// Id of the fbo backing the surface that was presented.
uint32_t fbo_id;
+ /// Damage representing the area that the compositor needs to render.
+ FlutterDamage frame_damage;
+ /// Damage used to set the buffer's damage region.
+ FlutterDamage buffer_damage;
} FlutterPresentInfo;
/// Callback for when a surface is presented.
@@ -471,7 +492,10 @@
BoolCallback clear_current;
/// Specifying one (and only one) of `present` or `present_with_info` is
/// required. Specifying both is an error and engine initialization will be
- /// terminated. The return value indicates success of the present call.
+ /// terminated. The return value indicates success of the present call. If
+ /// the intent is to use dirty region management, present_with_info must be
+ /// defined as present will not succeed in communicating information about
+ /// damage.
BoolCallback present;
/// Specifying one (and only one) of the `fbo_callback` or
/// `fbo_with_frame_info_callback` is required. Specifying both is an error
@@ -520,8 +544,27 @@
/// required. Specifying both is an error and engine initialization will be
/// terminated. When using this variant, the embedder is passed a
/// `FlutterPresentInfo` struct that the embedder can use to release any
- /// resources. The return value indicates success of the present call.
+ /// resources. The return value indicates success of the present call. This
+ /// callback is essential for dirty region management. If not defined, all the
+ /// pixels on the screen will be rendered at every frame (regardless of
+ /// whether damage is actually being computed or not). This is because the
+ /// information that is passed along to the callback contains the frame and
+ /// buffer damage that are essential for dirty region management.
BoolPresentInfoCallback present_with_info;
+ /// Specifying this callback is a requirement for dirty region management.
+ /// Dirty region management will only render the areas of the screen that have
+ /// changed in between frames, greatly reducing rendering times and energy
+ /// consumption. To take advantage of these benefits, it is necessary to
+ /// define populate_existing_damage as a callback that takes user
+ /// data, an FBO ID, and an existing damage FlutterDamage. The callback should
+ /// use the given FBO ID to identify the FBO's exisiting damage (i.e. areas
+ /// that have changed since the FBO was last used) and use it to populate the
+ /// given existing damage variable. This callback is dependent on either
+ /// fbo_callback or fbo_with_frame_info_callback being defined as they are
+ /// responsible for providing populate_existing_damage with the FBO's
+ /// ID. Not specifying populate_existing_damage will result in full
+ /// repaint (i.e. rendering all the pixels on the screen at every frame).
+ FlutterFrameBufferWithDamageCallback populate_existing_damage;
} FlutterOpenGLRendererConfig;
/// Alias for id<MTLDevice>.
diff --git a/shell/platform/embedder/embedder_surface_gl.cc b/shell/platform/embedder/embedder_surface_gl.cc
index e406ee9..88cdb31 100644
--- a/shell/platform/embedder/embedder_surface_gl.cc
+++ b/shell/platform/embedder/embedder_surface_gl.cc
@@ -19,7 +19,8 @@
if (!gl_dispatch_table_.gl_make_current_callback ||
!gl_dispatch_table_.gl_clear_current_callback ||
!gl_dispatch_table_.gl_present_callback ||
- !gl_dispatch_table_.gl_fbo_callback) {
+ !gl_dispatch_table_.gl_fbo_callback ||
+ !gl_dispatch_table_.gl_populate_existing_damage) {
return;
}
@@ -46,12 +47,16 @@
// |GPUSurfaceGLDelegate|
bool EmbedderSurfaceGL::GLContextPresent(const GLPresentInfo& present_info) {
- return gl_dispatch_table_.gl_present_callback(present_info.fbo_id);
+ // Pass the present information to the embedder present callback.
+ return gl_dispatch_table_.gl_present_callback(present_info);
}
// |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 {
+ // Get the FBO ID using the gl_fbo_callback and then get exiting damage by
+ // passing that ID to the gl_populate_existing_damage.
+ return gl_dispatch_table_.gl_populate_existing_damage(
+ gl_dispatch_table_.gl_fbo_callback(frame_info));
}
// |GPUSurfaceGLDelegate|
diff --git a/shell/platform/embedder/embedder_surface_gl.h b/shell/platform/embedder/embedder_surface_gl.h
index c0a2b6a..583f0f4 100644
--- a/shell/platform/embedder/embedder_surface_gl.h
+++ b/shell/platform/embedder/embedder_surface_gl.h
@@ -18,12 +18,13 @@
struct GLDispatchTable {
std::function<bool(void)> gl_make_current_callback; // required
std::function<bool(void)> gl_clear_current_callback; // required
- std::function<bool(uint32_t)> gl_present_callback; // required
+ std::function<bool(GLPresentInfo)> gl_present_callback; // required
std::function<intptr_t(GLFrameInfo)> gl_fbo_callback; // required
std::function<bool(void)> gl_make_resource_current_callback; // optional
std::function<SkMatrix(void)>
- gl_surface_transformation_callback; // optional
- std::function<void*(const char*)> gl_proc_resolver; // optional
+ gl_surface_transformation_callback; // optional
+ std::function<void*(const char*)> gl_proc_resolver; // optional
+ std::function<GLFBOInfo(intptr_t)> gl_populate_existing_damage; // required
};
EmbedderSurfaceGL(
@@ -59,7 +60,7 @@
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 918abff..58e48c4 100644
--- a/shell/platform/embedder/tests/embedder_config_builder.cc
+++ b/shell/platform/embedder/tests/embedder_config_builder.cc
@@ -52,13 +52,14 @@
opengl_renderer_config_.present_with_info =
[](void* context, const FlutterPresentInfo* present_info) -> bool {
return reinterpret_cast<EmbedderTestContextGL*>(context)->GLPresent(
- present_info->fbo_id);
+ *present_info);
};
opengl_renderer_config_.fbo_with_frame_info_callback =
[](void* context, const FlutterFrameInfo* frame_info) -> uint32_t {
return reinterpret_cast<EmbedderTestContextGL*>(context)->GLGetFramebuffer(
*frame_info);
};
+ opengl_renderer_config_.populate_existing_damage = nullptr;
opengl_renderer_config_.make_resource_current = [](void* context) -> bool {
return reinterpret_cast<EmbedderTestContextGL*>(context)
->GLMakeResourceCurrent();
@@ -160,7 +161,10 @@
FML_CHECK(renderer_config_.type == FlutterRendererType::kOpenGL);
renderer_config_.open_gl.present = [](void* context) -> bool {
// passing a placeholder fbo_id.
- return reinterpret_cast<EmbedderTestContextGL*>(context)->GLPresent(0);
+ return reinterpret_cast<EmbedderTestContextGL*>(context)->GLPresent(
+ FlutterPresentInfo{
+ .fbo_id = 0,
+ });
};
#endif
}
@@ -312,6 +316,10 @@
};
}
+FlutterRendererConfig& EmbedderConfigBuilder::GetRendererConfig() {
+ return renderer_config_;
+}
+
void EmbedderConfigBuilder::SetRenderTaskRunner(
const FlutterTaskRunnerDescription* runner) {
if (runner == nullptr) {
diff --git a/shell/platform/embedder/tests/embedder_config_builder.h b/shell/platform/embedder/tests/embedder_config_builder.h
index 3ce230c..2727c85 100644
--- a/shell/platform/embedder/tests/embedder_config_builder.h
+++ b/shell/platform/embedder/tests/embedder_config_builder.h
@@ -104,6 +104,8 @@
FlutterCompositor& GetCompositor();
+ FlutterRendererConfig& GetRendererConfig();
+
void SetRenderTargetType(
EmbedderTestBackingStoreProducer::RenderTargetType type,
FlutterSoftwarePixelFormat software_pixfmt = kNative32);
diff --git a/shell/platform/embedder/tests/embedder_test_context_gl.cc b/shell/platform/embedder/tests/embedder_test_context_gl.cc
index 9a7bc9b..0215999 100644
--- a/shell/platform/embedder/tests/embedder_test_context_gl.cc
+++ b/shell/platform/embedder/tests/embedder_test_context_gl.cc
@@ -39,7 +39,7 @@
return gl_surface_->ClearCurrent();
}
-bool EmbedderTestContextGL::GLPresent(uint32_t fbo_id) {
+bool EmbedderTestContextGL::GLPresent(FlutterPresentInfo present_info) {
FML_CHECK(gl_surface_) << "GL surface must be initialized.";
gl_surface_present_count_++;
@@ -50,7 +50,7 @@
}
if (callback) {
- callback(fbo_id);
+ callback(present_info);
}
FireRootSurfacePresentCallbackIfPresent(
@@ -64,6 +64,12 @@
gl_get_fbo_callback_ = callback;
}
+void EmbedderTestContextGL::SetGLPopulateExistingDamageCallback(
+ GLPopulateExistingDamageCallback callback) {
+ std::scoped_lock lock(gl_callback_mutex_);
+ gl_populate_existing_damage_callback_ = callback;
+}
+
void EmbedderTestContextGL::SetGLPresentCallback(GLPresentCallback callback) {
std::scoped_lock lock(gl_callback_mutex_);
gl_present_callback_ = callback;
@@ -86,6 +92,22 @@
return gl_surface_->GetFramebuffer(size.width, size.height);
}
+void EmbedderTestContextGL::GLPopulateExistingDamage(
+ const intptr_t id,
+ FlutterDamage* existing_damage) {
+ FML_CHECK(gl_surface_) << "GL surface must be initialized.";
+
+ GLPopulateExistingDamageCallback callback;
+ {
+ std::scoped_lock lock(gl_callback_mutex_);
+ callback = gl_populate_existing_damage_callback_;
+ }
+
+ if (callback) {
+ callback(id, existing_damage);
+ }
+}
+
bool EmbedderTestContextGL::GLMakeResourceCurrent() {
FML_CHECK(gl_surface_) << "GL surface must be initialized.";
return gl_surface_->MakeResourceCurrent();
diff --git a/shell/platform/embedder/tests/embedder_test_context_gl.h b/shell/platform/embedder/tests/embedder_test_context_gl.h
index 65e2c34..9b91ef5 100644
--- a/shell/platform/embedder/tests/embedder_test_context_gl.h
+++ b/shell/platform/embedder/tests/embedder_test_context_gl.h
@@ -14,7 +14,10 @@
class EmbedderTestContextGL : public EmbedderTestContext {
public:
using GLGetFBOCallback = std::function<void(FlutterFrameInfo frame_info)>;
- using GLPresentCallback = std::function<void(uint32_t fbo_id)>;
+ using GLPopulateExistingDamageCallback =
+ std::function<void(intptr_t id, FlutterDamage* existing_damage)>;
+ using GLPresentCallback =
+ std::function<void(FlutterPresentInfo present_info)>;
explicit EmbedderTestContextGL(std::string assets_path = "");
@@ -38,6 +41,9 @@
///
void SetGLGetFBOCallback(GLGetFBOCallback callback);
+ void SetGLPopulateExistingDamageCallback(
+ GLPopulateExistingDamageCallback callback);
+
uint32_t GetWindowFBOId() const;
//----------------------------------------------------------------------------
@@ -53,6 +59,9 @@
///
void SetGLPresentCallback(GLPresentCallback callback);
+ void GLPopulateExistingDamage(const intptr_t id,
+ FlutterDamage* existing_damage);
+
protected:
virtual void SetupCompositor() override;
@@ -65,6 +74,7 @@
std::mutex gl_callback_mutex_;
GLGetFBOCallback gl_get_fbo_callback_;
GLPresentCallback gl_present_callback_;
+ GLPopulateExistingDamageCallback gl_populate_existing_damage_callback_;
void SetupSurface(SkISize surface_size) override;
@@ -72,7 +82,7 @@
bool GLClearCurrent();
- bool GLPresent(uint32_t fbo_id);
+ bool GLPresent(FlutterPresentInfo present_info);
uint32_t GLGetFramebuffer(FlutterFrameInfo frame_info);
diff --git a/shell/platform/embedder/tests/embedder_unittests_gl.cc b/shell/platform/embedder/tests/embedder_unittests_gl.cc
index 9174461..76cf610 100644
--- a/shell/platform/embedder/tests/embedder_unittests_gl.cc
+++ b/shell/platform/embedder/tests/embedder_unittests_gl.cc
@@ -3106,6 +3106,72 @@
ASSERT_FALSE(engine.is_valid());
}
+TEST_F(EmbedderTest, MustStillRunWhenPopulateExistingDamageIsNotProvided) {
+ auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext);
+
+ EmbedderConfigBuilder builder(context);
+ builder.SetOpenGLRendererConfig(SkISize::Make(1, 1));
+ builder.GetRendererConfig().open_gl.populate_existing_damage = nullptr;
+
+ auto engine = builder.LaunchEngine();
+ ASSERT_TRUE(engine.is_valid());
+}
+
+TEST_F(EmbedderTest, MustRunWhenPopulateExistingDamageIsProvided) {
+ auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext);
+
+ EmbedderConfigBuilder builder(context);
+ builder.SetOpenGLRendererConfig(SkISize::Make(1, 1));
+
+ builder.GetRendererConfig().open_gl.populate_existing_damage =
+ [](void* context, const intptr_t id,
+ FlutterDamage* existing_damage) -> void {
+ return reinterpret_cast<EmbedderTestContextGL*>(context)
+ ->GLPopulateExistingDamage(id, existing_damage);
+ };
+
+ auto engine = builder.LaunchEngine();
+ ASSERT_TRUE(engine.is_valid());
+}
+
+TEST_F(EmbedderTest, MustRunWithPopulateExistingDamageAndFBOCallback) {
+ auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext);
+
+ EmbedderConfigBuilder builder(context);
+ builder.SetOpenGLRendererConfig(SkISize::Make(1, 1));
+ builder.GetRendererConfig().open_gl.fbo_callback =
+ [](void* context) -> uint32_t { return 0; };
+ builder.GetRendererConfig().open_gl.fbo_with_frame_info_callback = nullptr;
+ builder.GetRendererConfig().open_gl.populate_existing_damage =
+ [](void* context, const intptr_t id,
+ FlutterDamage* existing_damage) -> void {
+ return reinterpret_cast<EmbedderTestContextGL*>(context)
+ ->GLPopulateExistingDamage(id, existing_damage);
+ };
+
+ auto engine = builder.LaunchEngine();
+ ASSERT_TRUE(engine.is_valid());
+}
+
+TEST_F(EmbedderTest,
+ MustNotRunWhenPopulateExistingDamageButNoOtherFBOCallback) {
+ auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext);
+
+ EmbedderConfigBuilder builder(context);
+ builder.SetOpenGLRendererConfig(SkISize::Make(1, 1));
+ builder.GetRendererConfig().open_gl.fbo_callback = nullptr;
+ builder.GetRendererConfig().open_gl.fbo_with_frame_info_callback = nullptr;
+ builder.GetRendererConfig().open_gl.populate_existing_damage =
+ [](void* context, const intptr_t id,
+ FlutterDamage* existing_damage) -> void {
+ return reinterpret_cast<EmbedderTestContextGL*>(context)
+ ->GLPopulateExistingDamage(id, existing_damage);
+ };
+
+ auto engine = builder.LaunchEngine();
+ ASSERT_FALSE(engine.is_valid());
+}
+
TEST_F(EmbedderTest, PresentInfoContainsValidFBOId) {
auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext);
@@ -3140,8 +3206,8 @@
const uint32_t window_fbo_id =
static_cast<EmbedderTestContextGL&>(context).GetWindowFBOId();
static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback(
- [window_fbo_id = window_fbo_id](uint32_t fbo_id) {
- ASSERT_EQ(fbo_id, window_fbo_id);
+ [window_fbo_id = window_fbo_id](FlutterPresentInfo present_info) {
+ ASSERT_EQ(present_info.fbo_id, window_fbo_id);
frame_latch.CountDown();
});
@@ -3149,6 +3215,315 @@
frame_latch.Wait();
}
+TEST_F(EmbedderTest,
+ PresentInfoReceivesFullDamageWhenExistingDamageIsWholeScreen) {
+ auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext);
+
+ EmbedderConfigBuilder builder(context);
+ builder.SetOpenGLRendererConfig(SkISize::Make(800, 600));
+ builder.SetDartEntrypoint("render_gradient");
+ builder.GetRendererConfig().open_gl.populate_existing_damage =
+ [](void* context, const intptr_t id,
+ FlutterDamage* existing_damage) -> void {
+ return reinterpret_cast<EmbedderTestContextGL*>(context)
+ ->GLPopulateExistingDamage(id, existing_damage);
+ };
+
+ // Return existing damage as the entire screen on purpose.
+ static_cast<EmbedderTestContextGL&>(context)
+ .SetGLPopulateExistingDamageCallback(
+ [](const intptr_t id, FlutterDamage* existing_damage_ptr) {
+ const size_t num_rects = 1;
+ FlutterRect existing_damage_rects[num_rects] = {
+ FlutterRect{0, 0, 800, 600}};
+ existing_damage_ptr->num_rects = num_rects;
+ existing_damage_ptr->damage = existing_damage_rects;
+ });
+
+ auto engine = builder.LaunchEngine();
+ ASSERT_TRUE(engine.is_valid());
+
+ // First frame should be entirely rerendered.
+ static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback(
+ [](FlutterPresentInfo present_info) {
+ const size_t num_rects = 1;
+ ASSERT_EQ(present_info.frame_damage.num_rects, num_rects);
+ ASSERT_EQ(present_info.frame_damage.damage->left, 0);
+ ASSERT_EQ(present_info.frame_damage.damage->top, 0);
+ ASSERT_EQ(present_info.frame_damage.damage->right, 800);
+ ASSERT_EQ(present_info.frame_damage.damage->bottom, 600);
+
+ ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects);
+ ASSERT_EQ(present_info.buffer_damage.damage->left, 0);
+ ASSERT_EQ(present_info.buffer_damage.damage->top, 0);
+ ASSERT_EQ(present_info.buffer_damage.damage->right, 800);
+ ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600);
+ });
+
+ // Send a window metrics events so frames may be scheduled.
+ FlutterWindowMetricsEvent event = {};
+ event.struct_size = sizeof(event);
+ event.width = 800;
+ event.height = 600;
+ event.pixel_ratio = 1.0;
+ ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
+ kSuccess);
+
+ // Because it's the same as the first frame, the second frame damage should
+ // be empty but, because there was a full existing buffer damage, the buffer
+ // damage should be the entire screen.
+ static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback(
+ [](FlutterPresentInfo present_info) {
+ const size_t num_rects = 1;
+ ASSERT_EQ(present_info.frame_damage.num_rects, num_rects);
+ ASSERT_EQ(present_info.frame_damage.damage->left, 0);
+ ASSERT_EQ(present_info.frame_damage.damage->top, 0);
+ ASSERT_EQ(present_info.frame_damage.damage->right, 0);
+ ASSERT_EQ(present_info.frame_damage.damage->bottom, 0);
+
+ ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects);
+ ASSERT_EQ(present_info.buffer_damage.damage->left, 0);
+ ASSERT_EQ(present_info.buffer_damage.damage->top, 0);
+ ASSERT_EQ(present_info.buffer_damage.damage->right, 800);
+ ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600);
+ });
+
+ ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
+ kSuccess);
+}
+
+TEST_F(EmbedderTest, PresentInfoReceivesEmptyDamage) {
+ auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext);
+
+ EmbedderConfigBuilder builder(context);
+ builder.SetOpenGLRendererConfig(SkISize::Make(800, 600));
+ builder.SetDartEntrypoint("render_gradient");
+ builder.GetRendererConfig().open_gl.populate_existing_damage =
+ [](void* context, const intptr_t id,
+ FlutterDamage* existing_damage) -> void {
+ return reinterpret_cast<EmbedderTestContextGL*>(context)
+ ->GLPopulateExistingDamage(id, existing_damage);
+ };
+
+ // Return no existing damage on purpose.
+ static_cast<EmbedderTestContextGL&>(context)
+ .SetGLPopulateExistingDamageCallback(
+ [](const intptr_t id, FlutterDamage* existing_damage_ptr) {
+ const size_t num_rects = 1;
+ FlutterRect existing_damage_rects[num_rects] = {
+ FlutterRect{0, 0, 0, 0}};
+ existing_damage_ptr->num_rects = num_rects;
+ existing_damage_ptr->damage = existing_damage_rects;
+ });
+
+ auto engine = builder.LaunchEngine();
+ ASSERT_TRUE(engine.is_valid());
+
+ // First frame should be entirely rerendered.
+ static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback(
+ [](FlutterPresentInfo present_info) {
+ const size_t num_rects = 1;
+ ASSERT_EQ(present_info.frame_damage.num_rects, num_rects);
+ ASSERT_EQ(present_info.frame_damage.damage->left, 0);
+ ASSERT_EQ(present_info.frame_damage.damage->top, 0);
+ ASSERT_EQ(present_info.frame_damage.damage->right, 800);
+ ASSERT_EQ(present_info.frame_damage.damage->bottom, 600);
+
+ ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects);
+ ASSERT_EQ(present_info.buffer_damage.damage->left, 0);
+ ASSERT_EQ(present_info.buffer_damage.damage->top, 0);
+ ASSERT_EQ(present_info.buffer_damage.damage->right, 800);
+ ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600);
+ });
+
+ // Send a window metrics events so frames may be scheduled.
+ FlutterWindowMetricsEvent event = {};
+ event.struct_size = sizeof(event);
+ event.width = 800;
+ event.height = 600;
+ event.pixel_ratio = 1.0;
+ ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
+ kSuccess);
+
+ // Because it's the same as the first frame, the second frame should not be
+ // rerendered assuming there is no existing damage.
+ static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback(
+ [](FlutterPresentInfo present_info) {
+ const size_t num_rects = 1;
+ ASSERT_EQ(present_info.frame_damage.num_rects, num_rects);
+ ASSERT_EQ(present_info.frame_damage.damage->left, 0);
+ ASSERT_EQ(present_info.frame_damage.damage->top, 0);
+ ASSERT_EQ(present_info.frame_damage.damage->right, 0);
+ ASSERT_EQ(present_info.frame_damage.damage->bottom, 0);
+
+ ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects);
+ ASSERT_EQ(present_info.buffer_damage.damage->left, 0);
+ ASSERT_EQ(present_info.buffer_damage.damage->top, 0);
+ ASSERT_EQ(present_info.buffer_damage.damage->right, 0);
+ ASSERT_EQ(present_info.buffer_damage.damage->bottom, 0);
+ });
+
+ ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
+ kSuccess);
+}
+
+TEST_F(EmbedderTest, PresentInfoReceivesPartialDamage) {
+ auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext);
+
+ EmbedderConfigBuilder builder(context);
+ builder.SetOpenGLRendererConfig(SkISize::Make(800, 600));
+ builder.SetDartEntrypoint("render_gradient");
+ builder.GetRendererConfig().open_gl.populate_existing_damage =
+ [](void* context, const intptr_t id,
+ FlutterDamage* existing_damage) -> void {
+ return reinterpret_cast<EmbedderTestContextGL*>(context)
+ ->GLPopulateExistingDamage(id, existing_damage);
+ };
+
+ // Return existing damage as only part of the screen on purpose.
+ static_cast<EmbedderTestContextGL&>(context)
+ .SetGLPopulateExistingDamageCallback(
+ [](const intptr_t id, FlutterDamage* existing_damage_ptr) {
+ const size_t num_rects = 1;
+ FlutterRect existing_damage_rects[num_rects] = {
+ FlutterRect{200, 150, 400, 300}};
+ existing_damage_ptr->num_rects = num_rects;
+ existing_damage_ptr->damage = existing_damage_rects;
+ });
+
+ auto engine = builder.LaunchEngine();
+ ASSERT_TRUE(engine.is_valid());
+
+ // First frame should be entirely rerendered.
+ static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback(
+ [](FlutterPresentInfo present_info) {
+ const size_t num_rects = 1;
+ ASSERT_EQ(present_info.frame_damage.num_rects, num_rects);
+ ASSERT_EQ(present_info.frame_damage.damage->left, 0);
+ ASSERT_EQ(present_info.frame_damage.damage->top, 0);
+ ASSERT_EQ(present_info.frame_damage.damage->right, 800);
+ ASSERT_EQ(present_info.frame_damage.damage->bottom, 600);
+
+ ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects);
+ ASSERT_EQ(present_info.buffer_damage.damage->left, 0);
+ ASSERT_EQ(present_info.buffer_damage.damage->top, 0);
+ ASSERT_EQ(present_info.buffer_damage.damage->right, 800);
+ ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600);
+ });
+
+ // Send a window metrics events so frames may be scheduled.
+ FlutterWindowMetricsEvent event = {};
+ event.struct_size = sizeof(event);
+ event.width = 800;
+ event.height = 600;
+ event.pixel_ratio = 1.0;
+ ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
+ kSuccess);
+
+ // Because it's the same as the first frame, the second frame damage should be
+ // empty but, because there was a partial existing damage, the buffer damage
+ // should represent that partial damage area.
+ static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback(
+ [](FlutterPresentInfo present_info) {
+ const size_t num_rects = 1;
+ ASSERT_EQ(present_info.frame_damage.num_rects, num_rects);
+ ASSERT_EQ(present_info.frame_damage.damage->left, 0);
+ ASSERT_EQ(present_info.frame_damage.damage->top, 0);
+ ASSERT_EQ(present_info.frame_damage.damage->right, 0);
+ ASSERT_EQ(present_info.frame_damage.damage->bottom, 0);
+
+ ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects);
+ ASSERT_EQ(present_info.buffer_damage.damage->left, 200);
+ ASSERT_EQ(present_info.buffer_damage.damage->top, 150);
+ ASSERT_EQ(present_info.buffer_damage.damage->right, 400);
+ ASSERT_EQ(present_info.buffer_damage.damage->bottom, 300);
+ });
+
+ ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
+ kSuccess);
+}
+
+TEST_F(EmbedderTest, PopulateExistingDamageReceivesValidID) {
+ auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext);
+
+ EmbedderConfigBuilder builder(context);
+ builder.SetOpenGLRendererConfig(SkISize::Make(800, 600));
+ builder.SetDartEntrypoint("render_gradient");
+ builder.GetRendererConfig().open_gl.populate_existing_damage =
+ [](void* context, const intptr_t id,
+ FlutterDamage* existing_damage) -> void {
+ return reinterpret_cast<EmbedderTestContextGL*>(context)
+ ->GLPopulateExistingDamage(id, existing_damage);
+ };
+
+ auto engine = builder.LaunchEngine();
+ ASSERT_TRUE(engine.is_valid());
+
+ const uint32_t window_fbo_id =
+ static_cast<EmbedderTestContextGL&>(context).GetWindowFBOId();
+ static_cast<EmbedderTestContextGL&>(context)
+ .SetGLPopulateExistingDamageCallback(
+ [window_fbo_id = window_fbo_id](intptr_t id,
+ FlutterDamage* existing_damage) {
+ ASSERT_EQ(id, window_fbo_id);
+ });
+
+ // Send a window metrics events so frames may be scheduled.
+ FlutterWindowMetricsEvent event = {};
+ event.struct_size = sizeof(event);
+ event.width = 800;
+ event.height = 600;
+ event.pixel_ratio = 1.0;
+ ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
+ kSuccess);
+}
+
+TEST_F(EmbedderTest, PopulateExistingDamageReceivesInvalidID) {
+ auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext);
+
+ EmbedderConfigBuilder builder(context);
+ builder.SetOpenGLRendererConfig(SkISize::Make(800, 600));
+ builder.SetDartEntrypoint("render_gradient");
+ builder.GetRendererConfig().open_gl.populate_existing_damage =
+ [](void* context, const intptr_t id,
+ FlutterDamage* existing_damage) -> void {
+ return reinterpret_cast<EmbedderTestContextGL*>(context)
+ ->GLPopulateExistingDamage(id, existing_damage);
+ };
+
+ // Return a bad FBO ID on purpose.
+ builder.GetRendererConfig().open_gl.fbo_with_frame_info_callback =
+ [](void* context, const FlutterFrameInfo* frame_info) -> uint32_t {
+ return 123;
+ };
+
+ auto engine = builder.LaunchEngine();
+ ASSERT_TRUE(engine.is_valid());
+
+ context.AddNativeCallback("SignalNativeTest",
+ CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
+ /* Nothing to do. */
+ }));
+
+ const uint32_t window_fbo_id =
+ static_cast<EmbedderTestContextGL&>(context).GetWindowFBOId();
+ static_cast<EmbedderTestContextGL&>(context)
+ .SetGLPopulateExistingDamageCallback(
+ [window_fbo_id = window_fbo_id](intptr_t id,
+ FlutterDamage* existing_damage) {
+ ASSERT_NE(id, window_fbo_id);
+ });
+
+ // Send a window metrics events so frames may be scheduled.
+ FlutterWindowMetricsEvent event = {};
+ event.struct_size = sizeof(event);
+ event.width = 800;
+ event.height = 600;
+ event.pixel_ratio = 1.0;
+ ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
+ kSuccess);
+}
+
TEST_F(EmbedderTest, SetSingleDisplayConfigurationWithDisplayId) {
auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext);