Enable dirty region management within the Embedder API (flutter/engine#35022)

This commit is contained in:
Bernardo Eilert Trevisan 2022-08-12 10:52:54 -07:00 committed by GitHub
parent aa5f5cecc6
commit d9da8e0d2e
21 changed files with 657 additions and 51 deletions

View File

@ -69,8 +69,10 @@ bool ShellTestPlatformViewGL::GLContextPresent(
}
// |GPUSurfaceGLDelegate|
intptr_t ShellTestPlatformViewGL::GLContextFBO(GLFrameInfo frame_info) const {
return gl_surface_.GetFramebuffer(frame_info.width, frame_info.height);
GLFBOInfo ShellTestPlatformViewGL::GLContextFBO(GLFrameInfo frame_info) const {
return GLFBOInfo{
.fbo_id = gl_surface_.GetFramebuffer(frame_info.width, frame_info.height),
};
}
// |GPUSurfaceGLDelegate|

View File

@ -61,7 +61,7 @@ class ShellTestPlatformViewGL : public ShellTestPlatformView,
bool GLContextPresent(const GLPresentInfo& present_info) override;
// |GPUSurfaceGLDelegate|
intptr_t GLContextFBO(GLFrameInfo frame_info) const override;
GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override;
// |GPUSurfaceGLDelegate|
GLProcResolver GetGLProcResolver() const override;

View File

@ -22,17 +22,32 @@ struct GLFrameInfo {
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 @@ class GPUSurfaceGLDelegate {
// 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,

View File

@ -62,10 +62,11 @@ std::unique_ptr<SurfaceFrame> GPUSurfaceGLImpeller::AcquireFrame(
if (weak) {
GLPresentInfo present_info = {
.fbo_id = 0,
.damage = std::nullopt,
.frame_damage = std::nullopt,
// TODO (https://github.com/flutter/flutter/issues/105597): wire-up
// presentation time to impeller backend.
.presentation_time = std::nullopt,
.buffer_damage = std::nullopt,
};
delegate->GLContextPresent(present_info);
}

View File

@ -181,10 +181,10 @@ bool GPUSurfaceGLSkia::CreateOrUpdateSurfaces(const SkISize& size) {
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 @@ bool GPUSurfaceGLSkia::CreateOrUpdateSurfaces(const SkISize& size) {
}
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 @@ std::unique_ptr<SurfaceFrame> GPUSurfaceGLSkia::AcquireFrame(
};
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 @@ bool GPUSurfaceGLSkia::PresentSurface(const SurfaceFrame& frame,
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 @@ bool GPUSurfaceGLSkia::PresentSurface(const SurfaceFrame& frame,
// The FBO has changed, ask the delegate for the new FBO and do a surface
// re-wrap.
const uint32_t fbo_id = delegate_->GLContextFBO(frame_info);
const GLFBOInfo fbo_info = delegate_->GLContextFBO(frame_info);
auto new_onscreen_surface =
WrapOnscreenSurface(context_.get(), // GL context
current_size, // root surface size
fbo_id // window FBO ID
fbo_info.fbo_id // window FBO ID
);
if (!new_onscreen_surface) {
@ -296,7 +302,9 @@ bool GPUSurfaceGLSkia::PresentSurface(const SurfaceFrame& frame,
}
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;

View File

@ -67,6 +67,8 @@ class GPUSurfaceGLSkia : public Surface {
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 @@ class GPUSurfaceGLSkia : public Surface {
// 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_;

View File

@ -295,9 +295,11 @@ bool AndroidSurfaceGLImpeller::GLContextPresent(
}
// |GPUSurfaceGLDelegate|
intptr_t AndroidSurfaceGLImpeller::GLContextFBO(GLFrameInfo frame_info) const {
GLFBOInfo AndroidSurfaceGLImpeller::GLContextFBO(GLFrameInfo frame_info) const {
// FBO0 is the default window bound framebuffer in EGL environments.
return 0;
return GLFBOInfo{
.fbo_id = 0,
};
}
// |GPUSurfaceGLDelegate|

View File

@ -68,7 +68,7 @@ class AndroidSurfaceGLImpeller final : public GPUSurfaceGLDelegate,
bool GLContextPresent(const GLPresentInfo& present_info) override;
// |GPUSurfaceGLDelegate|
intptr_t GLContextFBO(GLFrameInfo frame_info) const override;
GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override;
// |GPUSurfaceGLDelegate|
sk_sp<const GrGLInterface> GetGLInterface() const override;

View File

@ -159,13 +159,15 @@ bool AndroidSurfaceGLSkia::GLContextPresent(const GLPresentInfo& present_info) {
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|

View File

@ -67,7 +67,7 @@ class AndroidSurfaceGLSkia final : public GPUSurfaceGLDelegate,
bool GLContextPresent(const GLPresentInfo& present_info) override;
// |GPUSurfaceGLDelegate|
intptr_t GLContextFBO(GLFrameInfo frame_info) const override;
GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override;
// |GPUSurfaceGLDelegate|
sk_sp<const GrGLInterface> GetGLInterface() const override;

View File

@ -22,8 +22,10 @@ bool AndroidSurfaceMock::GLContextPresent(const GLPresentInfo& present_info) {
return true;
}
intptr_t AndroidSurfaceMock::GLContextFBO(GLFrameInfo frame_info) const {
return 0;
GLFBOInfo AndroidSurfaceMock::GLContextFBO(GLFrameInfo frame_info) const {
return GLFBOInfo{
.fbo_id = 0,
};
}
} // namespace flutter

View File

@ -51,7 +51,7 @@ class AndroidSurfaceMock final : public GPUSurfaceGLDelegate,
bool GLContextPresent(const GLPresentInfo& present_info) override;
// |GPUSurfaceGLDelegate|
intptr_t GLContextFBO(GLFrameInfo frame_info) const override;
GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override;
};
} // namespace flutter

View File

@ -132,6 +132,12 @@ static bool IsOpenGLRendererConfigValid(const FlutterRendererConfig* config) {
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 @@ static void* DefaultGLProcResolver(const char* name) {
}
#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 @@ InferOpenGLPlatformViewCreationCallback(
auto gl_clear_current = [ptr = config->open_gl.clear_current,
user_data]() -> bool { return ptr(user_data); };
auto gl_present = [present = config->open_gl.present,
present_with_info = config->open_gl.present_with_info,
user_data](uint32_t fbo_id) -> bool {
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 @@ InferOpenGLPlatformViewCreationCallback(
}
};
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 @@ InferOpenGLPlatformViewCreationCallback(
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(

View File

@ -433,6 +433,16 @@ typedef struct {
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 @@ typedef uint32_t (*UIntFrameInfoCallback)(
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 @@ typedef struct {
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 @@ typedef struct {
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 @@ typedef struct {
/// 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>.

View File

@ -19,7 +19,8 @@ EmbedderSurfaceGL::EmbedderSurfaceGL(
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 @@ bool EmbedderSurfaceGL::GLContextClearCurrent() {
// |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|

View File

@ -18,12 +18,13 @@ class EmbedderSurfaceGL final : public EmbedderSurface,
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 @@ class EmbedderSurfaceGL final : public EmbedderSurface,
bool GLContextPresent(const GLPresentInfo& present_info) override;
// |GPUSurfaceGLDelegate|
intptr_t GLContextFBO(GLFrameInfo frame_info) const override;
GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override;
// |GPUSurfaceGLDelegate|
bool GLContextFBOResetAfterPresent() const override;

View File

@ -52,13 +52,14 @@ EmbedderConfigBuilder::EmbedderConfigBuilder(
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 @@ void EmbedderConfigBuilder::SetOpenGLPresentCallBack() {
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 @@ void EmbedderConfigBuilder::SetupVsyncCallback() {
};
}
FlutterRendererConfig& EmbedderConfigBuilder::GetRendererConfig() {
return renderer_config_;
}
void EmbedderConfigBuilder::SetRenderTaskRunner(
const FlutterTaskRunnerDescription* runner) {
if (runner == nullptr) {

View File

@ -104,6 +104,8 @@ class EmbedderConfigBuilder {
FlutterCompositor& GetCompositor();
FlutterRendererConfig& GetRendererConfig();
void SetRenderTargetType(
EmbedderTestBackingStoreProducer::RenderTargetType type,
FlutterSoftwarePixelFormat software_pixfmt = kNative32);

View File

@ -39,7 +39,7 @@ bool EmbedderTestContextGL::GLClearCurrent() {
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 @@ bool EmbedderTestContextGL::GLPresent(uint32_t fbo_id) {
}
if (callback) {
callback(fbo_id);
callback(present_info);
}
FireRootSurfacePresentCallbackIfPresent(
@ -64,6 +64,12 @@ void EmbedderTestContextGL::SetGLGetFBOCallback(GLGetFBOCallback callback) {
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 @@ uint32_t EmbedderTestContextGL::GLGetFramebuffer(FlutterFrameInfo frame_info) {
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();

View File

@ -14,7 +14,10 @@ namespace testing {
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 @@ class EmbedderTestContextGL : public EmbedderTestContext {
///
void SetGLGetFBOCallback(GLGetFBOCallback callback);
void SetGLPopulateExistingDamageCallback(
GLPopulateExistingDamageCallback callback);
uint32_t GetWindowFBOId() const;
//----------------------------------------------------------------------------
@ -53,6 +59,9 @@ class EmbedderTestContextGL : public EmbedderTestContext {
///
void SetGLPresentCallback(GLPresentCallback callback);
void GLPopulateExistingDamage(const intptr_t id,
FlutterDamage* existing_damage);
protected:
virtual void SetupCompositor() override;
@ -65,6 +74,7 @@ class EmbedderTestContextGL : public EmbedderTestContext {
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 @@ class EmbedderTestContextGL : public EmbedderTestContext {
bool GLClearCurrent();
bool GLPresent(uint32_t fbo_id);
bool GLPresent(FlutterPresentInfo present_info);
uint32_t GLGetFramebuffer(FlutterFrameInfo frame_info);

View File

@ -3106,6 +3106,72 @@ TEST_F(EmbedderTest, MustNotRunWithBothPresentCallbacksSet) {
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 @@ TEST_F(EmbedderTest, PresentInfoContainsValidFBOId) {
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 @@ TEST_F(EmbedderTest, PresentInfoContainsValidFBOId) {
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);