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

Commit 96d7502

Browse files
authored
Add scheduleWarmUpFrame (#50570)
This PR adds `PlatformDispatcher.scheduleWarmUpFrame`. This PR is needed for the follow up changes: * The framework will switch to using this function to render warmup frames in flutter/flutter#143290. * Then the engine will finally be able to switch to multiview pipeline with no regression on startup timing in #49950. For why the warm up frame must involve the engine to render, see flutter/flutter#142851. ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide] and the [C++, Objective-C, Java style guides]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I added new tests to check the change I am making or feature I am adding, or the PR is [test-exempt]. See [testing the engine] for instructions on writing and running engine tests. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I signed the [CLA]. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style [testing the engine]: https://github.com/flutter/flutter/wiki/Testing-the-engine [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat
1 parent 2c3a86e commit 96d7502

18 files changed

+405
-7
lines changed

lib/ui/dart_ui.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ typedef CanvasPath Path;
9898
V(NativeStringAttribute::initSpellOutStringAttribute) \
9999
V(PlatformConfigurationNativeApi::DefaultRouteName) \
100100
V(PlatformConfigurationNativeApi::ScheduleFrame) \
101+
V(PlatformConfigurationNativeApi::EndWarmUpFrame) \
101102
V(PlatformConfigurationNativeApi::Render) \
102103
V(PlatformConfigurationNativeApi::UpdateSemantics) \
103104
V(PlatformConfigurationNativeApi::SetNeedsReportTimings) \

lib/ui/platform_dispatcher.dart

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,11 +801,47 @@ class PlatformDispatcher {
801801
///
802802
/// * [SchedulerBinding], the Flutter framework class which manages the
803803
/// scheduling of frames.
804+
/// * [scheduleWarmUpFrame], which should only be used to schedule warm up
805+
/// frames.
804806
void scheduleFrame() => _scheduleFrame();
805807

806808
@Native<Void Function()>(symbol: 'PlatformConfigurationNativeApi::ScheduleFrame')
807809
external static void _scheduleFrame();
808810

811+
/// Schedule a frame to run as soon as possible, rather than waiting for the
812+
/// engine to request a frame in response to a system "Vsync" signal.
813+
///
814+
/// The application can call this method as soon as it starts up so that the
815+
/// first frame (which is likely to be quite expensive) can start a few extra
816+
/// milliseconds earlier. Using it in other situations might lead to
817+
/// unintended results, such as screen tearing. Depending on platforms and
818+
/// situations, the warm up frame might or might not be actually rendered onto
819+
/// the screen.
820+
///
821+
/// For more introduction to the warm up frame, see
822+
/// [SchedulerBinding.scheduleWarmUpFrame].
823+
///
824+
/// This method uses the provided callbacks as the begin frame callback and
825+
/// the draw frame callback instead of [onBeginFrame] and [onDrawFrame].
826+
///
827+
/// See also:
828+
///
829+
/// * [SchedulerBinding.scheduleWarmUpFrame], which uses this method, and
830+
/// introduces the warm up frame in more details.
831+
/// * [scheduleFrame], which schedules the frame at the next appropriate
832+
/// opportunity and should be used to render regular frames.
833+
void scheduleWarmUpFrame({required VoidCallback beginFrame, required VoidCallback drawFrame}) {
834+
// We use timers here to ensure that microtasks flush in between.
835+
Timer.run(beginFrame);
836+
Timer.run(() {
837+
drawFrame();
838+
_endWarmUpFrame();
839+
});
840+
}
841+
842+
@Native<Void Function()>(symbol: 'PlatformConfigurationNativeApi::EndWarmUpFrame')
843+
external static void _endWarmUpFrame();
844+
809845
/// Additional accessibility features that may be enabled by the platform.
810846
AccessibilityFeatures get accessibilityFeatures => _configuration.accessibilityFeatures;
811847

lib/ui/window/platform_configuration.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,11 @@ void PlatformConfigurationNativeApi::ScheduleFrame() {
589589
UIDartState::Current()->platform_configuration()->client()->ScheduleFrame();
590590
}
591591

592+
void PlatformConfigurationNativeApi::EndWarmUpFrame() {
593+
UIDartState::ThrowIfUIOperationsProhibited();
594+
UIDartState::Current()->platform_configuration()->client()->EndWarmUpFrame();
595+
}
596+
592597
void PlatformConfigurationNativeApi::UpdateSemantics(SemanticsUpdate* update) {
593598
UIDartState::ThrowIfUIOperationsProhibited();
594599
UIDartState::Current()->platform_configuration()->client()->UpdateSemantics(

lib/ui/window/platform_configuration.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ class PlatformConfigurationClient {
6565
///
6666
virtual void ScheduleFrame() = 0;
6767

68+
//--------------------------------------------------------------------------
69+
/// @brief Called when a warm up frame has ended.
70+
///
71+
/// For more introduction, see `Animator::EndWarmUpFrame`.
72+
///
73+
virtual void EndWarmUpFrame() = 0;
74+
6875
//--------------------------------------------------------------------------
6976
/// @brief Updates the client's rendering on the GPU with the newly
7077
/// provided Scene.
@@ -557,6 +564,8 @@ class PlatformConfigurationNativeApi {
557564

558565
static void ScheduleFrame();
559566

567+
static void EndWarmUpFrame();
568+
560569
static void Render(int64_t view_id,
561570
Scene* scene,
562571
double width,

lib/web_ui/lib/platform_dispatcher.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ abstract class PlatformDispatcher {
9090

9191
void scheduleFrame();
9292

93+
void scheduleWarmUpFrame({required VoidCallback beginFrame, required VoidCallback drawFrame});
94+
9395
AccessibilityFeatures get accessibilityFeatures;
9496

9597
VoidCallback? get onAccessibilityFeaturesChanged;

lib/web_ui/lib/src/engine/initialization.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,8 @@ Future<void> initializeEngineServices({
187187
// TODO(yjbanov): technically Flutter flushes microtasks between
188188
// onBeginFrame and onDrawFrame. We don't, which hasn't
189189
// been an issue yet, but eventually we'll have to
190-
// implement it properly.
190+
// implement it properly. (Also see the to-do in
191+
// `EnginePlatformDispatcher.scheduleWarmUpFrame`).
191192
EnginePlatformDispatcher.instance.invokeOnDrawFrame();
192193
}
193194
});

lib/web_ui/lib/src/engine/platform_dispatcher.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,19 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
782782
scheduleFrameCallback!();
783783
}
784784

785+
@override
786+
void scheduleWarmUpFrame({required ui.VoidCallback beginFrame, required ui.VoidCallback drawFrame}) {
787+
Timer.run(beginFrame);
788+
// We use timers here to ensure that microtasks flush in between.
789+
//
790+
// TODO(dkwingsmt): This logic was moved from the framework and is different
791+
// from how Web renders a regular frame, which doesn't flush microtasks
792+
// between the callbacks at all (see `initializeEngineServices`). We might
793+
// want to change this. See the to-do in `initializeEngineServices` and
794+
// https://github.com/flutter/engine/pull/50570#discussion_r1496671676
795+
Timer.run(drawFrame);
796+
}
797+
785798
/// Updates the application's rendering on the GPU with the newly provided
786799
/// [Scene]. This function must be called within the scope of the
787800
/// [onBeginFrame] or [onDrawFrame] callbacks being invoked. If this function

lib/web_ui/test/engine/platform_dispatcher/platform_dispatcher_test.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,23 @@ void testMain() {
417417
dispatcher.dispose();
418418
expect(dispatcher.accessibilityPlaceholder.isConnected, isFalse);
419419
});
420+
421+
test('scheduleWarmupFrame should call both callbacks', () async {
422+
bool beginFrameCalled = false;
423+
final Completer<void> drawFrameCalled = Completer<void>();
424+
dispatcher.scheduleWarmUpFrame(beginFrame: () {
425+
expect(drawFrameCalled.isCompleted, false);
426+
expect(beginFrameCalled, false);
427+
beginFrameCalled = true;
428+
}, drawFrame: () {
429+
expect(beginFrameCalled, true);
430+
expect(drawFrameCalled.isCompleted, false);
431+
drawFrameCalled.complete();
432+
});
433+
await drawFrameCalled.future;
434+
expect(beginFrameCalled, true);
435+
expect(drawFrameCalled.isCompleted, true);
436+
});
420437
});
421438
}
422439

runtime/runtime_controller.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,11 @@ void RuntimeController::ScheduleFrame() {
340340
client_.ScheduleFrame();
341341
}
342342

343+
// |PlatformConfigurationClient|
344+
void RuntimeController::EndWarmUpFrame() {
345+
client_.EndWarmUpFrame();
346+
}
347+
343348
// |PlatformConfigurationClient|
344349
void RuntimeController::Render(Scene* scene, double width, double height) {
345350
// TODO(dkwingsmt): Currently only supports a single window.

runtime/runtime_controller.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,9 @@ class RuntimeController : public PlatformConfigurationClient {
657657
// |PlatformConfigurationClient|
658658
void ScheduleFrame() override;
659659

660+
// |PlatformConfigurationClient|
661+
void EndWarmUpFrame() override;
662+
660663
// |PlatformConfigurationClient|
661664
void Render(Scene* scene, double width, double height) override;
662665

runtime/runtime_delegate.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class RuntimeDelegate {
2525

2626
virtual void ScheduleFrame(bool regenerate_layer_trees = true) = 0;
2727

28+
virtual void EndWarmUpFrame() = 0;
29+
2830
virtual void Render(std::unique_ptr<flutter::LayerTree> layer_tree,
2931
float device_pixel_ratio) = 0;
3032

shell/common/animator.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,12 @@ void Animator::AwaitVSync() {
264264
}
265265
}
266266

267+
void Animator::EndWarmUpFrame() {
268+
// Do nothing. The warm up frame does not need any additional work to end the
269+
// frame for now. This will change once the pipeline supports multi-view.
270+
// https://github.com/flutter/flutter/issues/142851
271+
}
272+
267273
void Animator::ScheduleSecondaryVsyncCallback(uintptr_t id,
268274
const fml::closure& callback) {
269275
waiter_->ScheduleSecondaryCallback(id, callback);

shell/common/animator.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,22 @@ class Animator final {
5353

5454
void RequestFrame(bool regenerate_layer_trees = true);
5555

56+
//--------------------------------------------------------------------------
57+
/// @brief Tells the Animator that a warm up frame has ended.
58+
///
59+
/// In a warm up frame, `Animator::Render` is called out of vsync
60+
/// tasks, and Animator requires an explicit end-of-frame call to
61+
/// know when to send the layer trees to the pipeline.
62+
///
63+
/// This is different from regular frames, where Animator::Render is
64+
/// always called within a vsync task, and Animator can send
65+
/// the views at the end of the vsync task.
66+
///
67+
/// For more about warm up frames, see
68+
/// `PlatformDispatcher.scheduleWarmUpFrame`.
69+
///
70+
void EndWarmUpFrame();
71+
5672
//--------------------------------------------------------------------------
5773
/// @brief Tells the Animator that this frame needs to render another view.
5874
///

shell/common/engine.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,10 @@ void Engine::ScheduleFrame(bool regenerate_layer_trees) {
462462
animator_->RequestFrame(regenerate_layer_trees);
463463
}
464464

465+
void Engine::EndWarmUpFrame() {
466+
animator_->EndWarmUpFrame();
467+
}
468+
465469
void Engine::Render(std::unique_ptr<flutter::LayerTree> layer_tree,
466470
float device_pixel_ratio) {
467471
if (!layer_tree) {

shell/common/engine.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,9 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
837837
/// tree.
838838
void ScheduleFrame() { ScheduleFrame(true); }
839839

840+
// |RuntimeDelegate|
841+
void EndWarmUpFrame() override;
842+
840843
// |RuntimeDelegate|
841844
FontCollection& GetFontCollection() override;
842845

0 commit comments

Comments
 (0)