Fix damage calculation when not providing populate_existing_damage for gl embedder (flutter/engine#45611)

# Description

This PR fixes the `gl_populate_existing_damage` in embedder.cc, which currently returns an empty rectangle when a full repaint is needed. This leads to the `frame_damage` being considered empty in rasterizer.cc, causing incorrect partial repaint within Flutter when `gl_populate_existing_damage` is not provided by the embedder.

# Related Issue

https://github.com/flutter/flutter/issues/119601

# Tests

Add a new test. Also fixes damage calculation related tests in EmbedderTest by

* Use a new Dart embedder fixture entry point `render_gradient_retained` which retains the old layer to make sure layer tree diff happens.
* Add `latch.Wait()` after `SetGLPresentCallback` to make sure assertions have been executed.
* Make `existing_damage_rects` static since it should be valid after `populate_existing_damage` returns.
This commit is contained in:
AJI
2023-09-29 04:06:51 +08:00
committed by GitHub
parent c94d335b6b
commit 86b582a6bc
5 changed files with 220 additions and 40 deletions

View File

@@ -27,8 +27,6 @@ struct GLFrameInfo {
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 std::optional<SkIRect> existing_damage;
};

View File

@@ -166,7 +166,6 @@ GLFBOInfo AndroidSurfaceGLSkia::GLContextFBO(GLFrameInfo frame_info) const {
// The default window bound framebuffer on Android.
return GLFBOInfo{
.fbo_id = 0,
.partial_repaint_enabled = onscreen_surface_->SupportsPartialRepaint(),
.existing_damage = onscreen_surface_->InitialDamage(),
};
}

View File

@@ -363,8 +363,7 @@ InferOpenGLPlatformViewCreationCallback(
if (!populate_existing_damage) {
return flutter::GLFBOInfo{
.fbo_id = static_cast<uint32_t>(id),
.partial_repaint_enabled = false,
.existing_damage = SkIRect::MakeEmpty(),
.existing_damage = std::nullopt,
};
}
@@ -372,29 +371,22 @@ InferOpenGLPlatformViewCreationCallback(
FlutterDamage existing_damage;
populate_existing_damage(user_data, id, &existing_damage);
bool partial_repaint_enabled = true;
SkIRect existing_damage_rect;
std::optional<SkIRect> existing_damage_rect = std::nullopt;
// 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));
existing_damage_rect = SkIRect::MakeEmpty();
for (size_t i = 0; i < existing_damage.num_rects; i++) {
existing_damage_rect->join(
FlutterRectToSkIRect(existing_damage.damage[i]));
}
}
// 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,
};
};

View File

@@ -1298,3 +1298,25 @@ void channel_listener_response() async {
});
signalNativeTest();
}
@pragma('vm:entry-point')
void render_gradient_retained() {
OffsetEngineLayer? offsetLayer; // Retain the offset layer.
PlatformDispatcher.instance.onBeginFrame = (Duration duration) {
Size size = Size(800.0, 600.0);
SceneBuilder builder = SceneBuilder();
offsetLayer = builder.pushOffset(0.0, 0.0, oldLayer: offsetLayer);
// display_list_layer will comparing the display_list
// no need to retain the picture
builder.addPicture(
Offset(0.0, 0.0), CreateGradientBox(size)); // gradient - flutter
builder.pop();
PlatformDispatcher.instance.views.first.render(builder.build());
};
PlatformDispatcher.instance.scheduleFrame();
}

View File

@@ -3655,7 +3655,7 @@ TEST_F(EmbedderTest,
EmbedderConfigBuilder builder(context);
builder.SetOpenGLRendererConfig(SkISize::Make(800, 600));
builder.SetDartEntrypoint("render_gradient");
builder.SetDartEntrypoint("render_gradient_retained");
builder.GetRendererConfig().open_gl.populate_existing_damage =
[](void* context, const intptr_t id,
FlutterDamage* existing_damage) -> void {
@@ -3668,7 +3668,8 @@ TEST_F(EmbedderTest,
.SetGLPopulateExistingDamageCallback(
[](const intptr_t id, FlutterDamage* existing_damage_ptr) {
const size_t num_rects = 1;
FlutterRect existing_damage_rects[num_rects] = {
// The array must be valid after the callback returns.
static 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;
@@ -3677,9 +3678,11 @@ TEST_F(EmbedderTest,
auto engine = builder.LaunchEngine();
ASSERT_TRUE(engine.is_valid());
fml::AutoResetWaitableEvent latch;
// First frame should be entirely rerendered.
static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback(
[](FlutterPresentInfo present_info) {
[&](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);
@@ -3692,6 +3695,8 @@ TEST_F(EmbedderTest,
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);
latch.Signal();
});
// Send a window metrics events so frames may be scheduled.
@@ -3702,12 +3707,13 @@ TEST_F(EmbedderTest,
event.pixel_ratio = 1.0;
ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
kSuccess);
latch.Wait();
// 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) {
[&](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);
@@ -3720,10 +3726,13 @@ TEST_F(EmbedderTest,
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);
latch.Signal();
});
ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
kSuccess);
latch.Wait();
}
TEST_F(EmbedderTest, PresentInfoReceivesEmptyDamage) {
@@ -3731,7 +3740,7 @@ TEST_F(EmbedderTest, PresentInfoReceivesEmptyDamage) {
EmbedderConfigBuilder builder(context);
builder.SetOpenGLRendererConfig(SkISize::Make(800, 600));
builder.SetDartEntrypoint("render_gradient");
builder.SetDartEntrypoint("render_gradient_retained");
builder.GetRendererConfig().open_gl.populate_existing_damage =
[](void* context, const intptr_t id,
FlutterDamage* existing_damage) -> void {
@@ -3744,7 +3753,8 @@ TEST_F(EmbedderTest, PresentInfoReceivesEmptyDamage) {
.SetGLPopulateExistingDamageCallback(
[](const intptr_t id, FlutterDamage* existing_damage_ptr) {
const size_t num_rects = 1;
FlutterRect existing_damage_rects[num_rects] = {
// The array must be valid after the callback returns.
static 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;
@@ -3753,9 +3763,11 @@ TEST_F(EmbedderTest, PresentInfoReceivesEmptyDamage) {
auto engine = builder.LaunchEngine();
ASSERT_TRUE(engine.is_valid());
fml::AutoResetWaitableEvent latch;
// First frame should be entirely rerendered.
static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback(
[](FlutterPresentInfo present_info) {
[&](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);
@@ -3768,6 +3780,8 @@ TEST_F(EmbedderTest, PresentInfoReceivesEmptyDamage) {
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);
latch.Signal();
});
// Send a window metrics events so frames may be scheduled.
@@ -3778,11 +3792,12 @@ TEST_F(EmbedderTest, PresentInfoReceivesEmptyDamage) {
event.pixel_ratio = 1.0;
ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
kSuccess);
latch.Wait();
// 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) {
[&](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);
@@ -3795,10 +3810,13 @@ TEST_F(EmbedderTest, PresentInfoReceivesEmptyDamage) {
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);
latch.Signal();
});
ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
kSuccess);
latch.Wait();
}
TEST_F(EmbedderTest, PresentInfoReceivesPartialDamage) {
@@ -3806,7 +3824,7 @@ TEST_F(EmbedderTest, PresentInfoReceivesPartialDamage) {
EmbedderConfigBuilder builder(context);
builder.SetOpenGLRendererConfig(SkISize::Make(800, 600));
builder.SetDartEntrypoint("render_gradient");
builder.SetDartEntrypoint("render_gradient_retained");
builder.GetRendererConfig().open_gl.populate_existing_damage =
[](void* context, const intptr_t id,
FlutterDamage* existing_damage) -> void {
@@ -3817,9 +3835,10 @@ TEST_F(EmbedderTest, PresentInfoReceivesPartialDamage) {
// 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 intptr_t id, FlutterDamage* existing_damage_ptr) {
const size_t num_rects = 1;
FlutterRect existing_damage_rects[num_rects] = {
// The array must be valid after the callback returns.
static 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;
@@ -3828,9 +3847,11 @@ TEST_F(EmbedderTest, PresentInfoReceivesPartialDamage) {
auto engine = builder.LaunchEngine();
ASSERT_TRUE(engine.is_valid());
fml::AutoResetWaitableEvent latch;
// First frame should be entirely rerendered.
static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback(
[](FlutterPresentInfo present_info) {
[&](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);
@@ -3843,6 +3864,8 @@ TEST_F(EmbedderTest, PresentInfoReceivesPartialDamage) {
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);
latch.Signal();
});
// Send a window metrics events so frames may be scheduled.
@@ -3853,12 +3876,13 @@ TEST_F(EmbedderTest, PresentInfoReceivesPartialDamage) {
event.pixel_ratio = 1.0;
ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
kSuccess);
latch.Wait();
// 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) {
[&](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);
@@ -3871,10 +3895,13 @@ TEST_F(EmbedderTest, PresentInfoReceivesPartialDamage) {
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);
latch.Signal();
});
ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
kSuccess);
latch.Wait();
}
TEST_F(EmbedderTest, PopulateExistingDamageReceivesValidID) {
@@ -3882,7 +3909,7 @@ TEST_F(EmbedderTest, PopulateExistingDamageReceivesValidID) {
EmbedderConfigBuilder builder(context);
builder.SetOpenGLRendererConfig(SkISize::Make(800, 600));
builder.SetDartEntrypoint("render_gradient");
builder.SetDartEntrypoint("render_gradient_retained");
builder.GetRendererConfig().open_gl.populate_existing_damage =
[](void* context, const intptr_t id,
FlutterDamage* existing_damage) -> void {
@@ -3900,6 +3927,8 @@ TEST_F(EmbedderTest, PopulateExistingDamageReceivesValidID) {
[window_fbo_id = window_fbo_id](intptr_t id,
FlutterDamage* existing_damage) {
ASSERT_EQ(id, window_fbo_id);
existing_damage->num_rects = 0;
existing_damage->damage = nullptr;
});
// Send a window metrics events so frames may be scheduled.
@@ -3917,7 +3946,7 @@ TEST_F(EmbedderTest, PopulateExistingDamageReceivesInvalidID) {
EmbedderConfigBuilder builder(context);
builder.SetOpenGLRendererConfig(SkISize::Make(800, 600));
builder.SetDartEntrypoint("render_gradient");
builder.SetDartEntrypoint("render_gradient_retained");
builder.GetRendererConfig().open_gl.populate_existing_damage =
[](void* context, const intptr_t id,
FlutterDamage* existing_damage) -> void {
@@ -3946,6 +3975,8 @@ TEST_F(EmbedderTest, PopulateExistingDamageReceivesInvalidID) {
[window_fbo_id = window_fbo_id](intptr_t id,
FlutterDamage* existing_damage) {
ASSERT_NE(id, window_fbo_id);
existing_damage->num_rects = 0;
existing_damage->damage = nullptr;
});
// Send a window metrics events so frames may be scheduled.
@@ -4510,24 +4541,162 @@ TEST_F(EmbedderTest, ExternalTextureGLRefreshedTooOften) {
glFinish();
}
TEST_F(EmbedderTest,
PresentInfoReceivesNoDamageWhenPopulateExistingDamageIsUndefined) {
TEST_F(
EmbedderTest,
PresentInfoReceivesFullScreenDamageWhenPopulateExistingDamageIsNotProvided) {
auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext);
EmbedderConfigBuilder builder(context);
builder.SetOpenGLRendererConfig(SkISize::Make(800, 600));
builder.SetDartEntrypoint("render_gradient");
builder.SetDartEntrypoint("render_gradient_retained");
builder.GetRendererConfig().open_gl.populate_existing_damage = nullptr;
auto engine = builder.LaunchEngine();
ASSERT_TRUE(engine.is_valid());
// No damage should be passed.
fml::AutoResetWaitableEvent latch;
// First frame should be entirely rerendered.
static_cast<EmbedderTestContextGL&>(context).SetGLPresentCallback(
[](FlutterPresentInfo present_info) {
ASSERT_EQ(present_info.frame_damage.damage, nullptr);
ASSERT_EQ(present_info.buffer_damage.damage, nullptr);
[&](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);
latch.Signal();
});
// 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);
latch.Wait();
// Since populate_existing_damage is not provided, the partial repaint
// functionality is actually disabled. So, the next frame should be entirely
// new frame.
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);
latch.Signal();
});
ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
kSuccess);
latch.Wait();
}
TEST_F(EmbedderTest,
PresentInfoReceivesJoinedDamageWhenExistingDamageContainsMultipleRects) {
auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext);
EmbedderConfigBuilder builder(context);
builder.SetOpenGLRendererConfig(SkISize::Make(800, 600));
builder.SetDartEntrypoint("render_gradient_retained");
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 = 2;
// The array must be valid after the callback returns.
static FlutterRect existing_damage_rects[num_rects] = {
FlutterRect{100, 150, 200, 250},
FlutterRect{200, 250, 300, 350},
};
existing_damage_ptr->num_rects = num_rects;
existing_damage_ptr->damage = existing_damage_rects;
});
auto engine = builder.LaunchEngine();
ASSERT_TRUE(engine.is_valid());
fml::AutoResetWaitableEvent latch;
// 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);
latch.Signal();
});
// 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);
latch.Wait();
// 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, 100);
ASSERT_EQ(present_info.buffer_damage.damage->top, 150);
ASSERT_EQ(present_info.buffer_damage.damage->right, 300);
ASSERT_EQ(present_info.buffer_damage.damage->bottom, 350);
latch.Signal();
});
ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
kSuccess);
latch.Wait();
}
INSTANTIATE_TEST_SUITE_P(