From 49337d55cef47024653c1187e429f0028b6ea12c Mon Sep 17 00:00:00 2001 From: AJI Date: Sat, 9 Sep 2023 18:56:41 +0800 Subject: [PATCH] Fix damage calculation in embedder gl when populate_existing_damage is not provided --- shell/gpu/gpu_surface_gl_delegate.h | 2 - .../android/android_surface_gl_skia.cc | 1 - shell/platform/embedder/embedder.cc | 22 +- shell/platform/embedder/fixtures/main.dart | 22 ++ .../embedder/tests/embedder_gl_unittests.cc | 213 ++++++++++++++++-- 5 files changed, 220 insertions(+), 40 deletions(-) diff --git a/shell/gpu/gpu_surface_gl_delegate.h b/shell/gpu/gpu_surface_gl_delegate.h index dee0c682969a0..39a58e6a708a6 100644 --- a/shell/gpu/gpu_surface_gl_delegate.h +++ b/shell/gpu/gpu_surface_gl_delegate.h @@ -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 existing_damage; }; diff --git a/shell/platform/android/android_surface_gl_skia.cc b/shell/platform/android/android_surface_gl_skia.cc index d6ec0c3cf3d96..6a5dda8a06082 100644 --- a/shell/platform/android/android_surface_gl_skia.cc +++ b/shell/platform/android/android_surface_gl_skia.cc @@ -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(), }; } diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index eb6b4edb85bbf..972390e7c3e8e 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -363,8 +363,7 @@ InferOpenGLPlatformViewCreationCallback( if (!populate_existing_damage) { return flutter::GLFBOInfo{ .fbo_id = static_cast(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 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(id), - .partial_repaint_enabled = partial_repaint_enabled, .existing_damage = existing_damage_rect, }; }; diff --git a/shell/platform/embedder/fixtures/main.dart b/shell/platform/embedder/fixtures/main.dart index c48de1daaab81..81cc224868c11 100644 --- a/shell/platform/embedder/fixtures/main.dart +++ b/shell/platform/embedder/fixtures/main.dart @@ -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(); +} diff --git a/shell/platform/embedder/tests/embedder_gl_unittests.cc b/shell/platform/embedder/tests/embedder_gl_unittests.cc index d233f836d9a74..dfca7ff6fb8a0 100644 --- a/shell/platform/embedder/tests/embedder_gl_unittests.cc +++ b/shell/platform/embedder/tests/embedder_gl_unittests.cc @@ -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(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(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(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(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(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(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(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(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(); + + // 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(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(context) + ->GLPopulateExistingDamage(id, existing_damage); + }; + + // Return existing damage as the entire screen on purpose. + static_cast(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(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(); + + // 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(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(