Skip to content

Commit

Permalink
Add scheduleWarmUpFrame (#50570)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
dkwingsmt authored Feb 22, 2024
1 parent 2c3a86e commit 96d7502
Show file tree
Hide file tree
Showing 18 changed files with 405 additions and 7 deletions.
1 change: 1 addition & 0 deletions lib/ui/dart_ui.cc
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ typedef CanvasPath Path;
V(NativeStringAttribute::initSpellOutStringAttribute) \
V(PlatformConfigurationNativeApi::DefaultRouteName) \
V(PlatformConfigurationNativeApi::ScheduleFrame) \
V(PlatformConfigurationNativeApi::EndWarmUpFrame) \
V(PlatformConfigurationNativeApi::Render) \
V(PlatformConfigurationNativeApi::UpdateSemantics) \
V(PlatformConfigurationNativeApi::SetNeedsReportTimings) \
Expand Down
36 changes: 36 additions & 0 deletions lib/ui/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -801,11 +801,47 @@ class PlatformDispatcher {
///
/// * [SchedulerBinding], the Flutter framework class which manages the
/// scheduling of frames.
/// * [scheduleWarmUpFrame], which should only be used to schedule warm up
/// frames.
void scheduleFrame() => _scheduleFrame();

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

/// Schedule a frame to run as soon as possible, rather than waiting for the
/// engine to request a frame in response to a system "Vsync" signal.
///
/// The application can call this method as soon as it starts up so that the
/// first frame (which is likely to be quite expensive) can start a few extra
/// milliseconds earlier. Using it in other situations might lead to
/// unintended results, such as screen tearing. Depending on platforms and
/// situations, the warm up frame might or might not be actually rendered onto
/// the screen.
///
/// For more introduction to the warm up frame, see
/// [SchedulerBinding.scheduleWarmUpFrame].
///
/// This method uses the provided callbacks as the begin frame callback and
/// the draw frame callback instead of [onBeginFrame] and [onDrawFrame].
///
/// See also:
///
/// * [SchedulerBinding.scheduleWarmUpFrame], which uses this method, and
/// introduces the warm up frame in more details.
/// * [scheduleFrame], which schedules the frame at the next appropriate
/// opportunity and should be used to render regular frames.
void scheduleWarmUpFrame({required VoidCallback beginFrame, required VoidCallback drawFrame}) {
// We use timers here to ensure that microtasks flush in between.
Timer.run(beginFrame);
Timer.run(() {
drawFrame();
_endWarmUpFrame();
});
}

@Native<Void Function()>(symbol: 'PlatformConfigurationNativeApi::EndWarmUpFrame')
external static void _endWarmUpFrame();

/// Additional accessibility features that may be enabled by the platform.
AccessibilityFeatures get accessibilityFeatures => _configuration.accessibilityFeatures;

Expand Down
5 changes: 5 additions & 0 deletions lib/ui/window/platform_configuration.cc
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,11 @@ void PlatformConfigurationNativeApi::ScheduleFrame() {
UIDartState::Current()->platform_configuration()->client()->ScheduleFrame();
}

void PlatformConfigurationNativeApi::EndWarmUpFrame() {
UIDartState::ThrowIfUIOperationsProhibited();
UIDartState::Current()->platform_configuration()->client()->EndWarmUpFrame();
}

void PlatformConfigurationNativeApi::UpdateSemantics(SemanticsUpdate* update) {
UIDartState::ThrowIfUIOperationsProhibited();
UIDartState::Current()->platform_configuration()->client()->UpdateSemantics(
Expand Down
9 changes: 9 additions & 0 deletions lib/ui/window/platform_configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ class PlatformConfigurationClient {
///
virtual void ScheduleFrame() = 0;

//--------------------------------------------------------------------------
/// @brief Called when a warm up frame has ended.
///
/// For more introduction, see `Animator::EndWarmUpFrame`.
///
virtual void EndWarmUpFrame() = 0;

//--------------------------------------------------------------------------
/// @brief Updates the client's rendering on the GPU with the newly
/// provided Scene.
Expand Down Expand Up @@ -557,6 +564,8 @@ class PlatformConfigurationNativeApi {

static void ScheduleFrame();

static void EndWarmUpFrame();

static void Render(int64_t view_id,
Scene* scene,
double width,
Expand Down
2 changes: 2 additions & 0 deletions lib/web_ui/lib/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ abstract class PlatformDispatcher {

void scheduleFrame();

void scheduleWarmUpFrame({required VoidCallback beginFrame, required VoidCallback drawFrame});

AccessibilityFeatures get accessibilityFeatures;

VoidCallback? get onAccessibilityFeaturesChanged;
Expand Down
3 changes: 2 additions & 1 deletion lib/web_ui/lib/src/engine/initialization.dart
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ Future<void> initializeEngineServices({
// TODO(yjbanov): technically Flutter flushes microtasks between
// onBeginFrame and onDrawFrame. We don't, which hasn't
// been an issue yet, but eventually we'll have to
// implement it properly.
// implement it properly. (Also see the to-do in
// `EnginePlatformDispatcher.scheduleWarmUpFrame`).
EnginePlatformDispatcher.instance.invokeOnDrawFrame();
}
});
Expand Down
13 changes: 13 additions & 0 deletions lib/web_ui/lib/src/engine/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,19 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
scheduleFrameCallback!();
}

@override
void scheduleWarmUpFrame({required ui.VoidCallback beginFrame, required ui.VoidCallback drawFrame}) {
Timer.run(beginFrame);
// We use timers here to ensure that microtasks flush in between.
//
// TODO(dkwingsmt): This logic was moved from the framework and is different
// from how Web renders a regular frame, which doesn't flush microtasks
// between the callbacks at all (see `initializeEngineServices`). We might
// want to change this. See the to-do in `initializeEngineServices` and
// https://github.com/flutter/engine/pull/50570#discussion_r1496671676
Timer.run(drawFrame);
}

/// Updates the application's rendering on the GPU with the newly provided
/// [Scene]. This function must be called within the scope of the
/// [onBeginFrame] or [onDrawFrame] callbacks being invoked. If this function
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,23 @@ void testMain() {
dispatcher.dispose();
expect(dispatcher.accessibilityPlaceholder.isConnected, isFalse);
});

test('scheduleWarmupFrame should call both callbacks', () async {
bool beginFrameCalled = false;
final Completer<void> drawFrameCalled = Completer<void>();
dispatcher.scheduleWarmUpFrame(beginFrame: () {
expect(drawFrameCalled.isCompleted, false);
expect(beginFrameCalled, false);
beginFrameCalled = true;
}, drawFrame: () {
expect(beginFrameCalled, true);
expect(drawFrameCalled.isCompleted, false);
drawFrameCalled.complete();
});
await drawFrameCalled.future;
expect(beginFrameCalled, true);
expect(drawFrameCalled.isCompleted, true);
});
});
}

Expand Down
5 changes: 5 additions & 0 deletions runtime/runtime_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,11 @@ void RuntimeController::ScheduleFrame() {
client_.ScheduleFrame();
}

// |PlatformConfigurationClient|
void RuntimeController::EndWarmUpFrame() {
client_.EndWarmUpFrame();
}

// |PlatformConfigurationClient|
void RuntimeController::Render(Scene* scene, double width, double height) {
// TODO(dkwingsmt): Currently only supports a single window.
Expand Down
3 changes: 3 additions & 0 deletions runtime/runtime_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,9 @@ class RuntimeController : public PlatformConfigurationClient {
// |PlatformConfigurationClient|
void ScheduleFrame() override;

// |PlatformConfigurationClient|
void EndWarmUpFrame() override;

// |PlatformConfigurationClient|
void Render(Scene* scene, double width, double height) override;

Expand Down
2 changes: 2 additions & 0 deletions runtime/runtime_delegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class RuntimeDelegate {

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

virtual void EndWarmUpFrame() = 0;

virtual void Render(std::unique_ptr<flutter::LayerTree> layer_tree,
float device_pixel_ratio) = 0;

Expand Down
6 changes: 6 additions & 0 deletions shell/common/animator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,12 @@ void Animator::AwaitVSync() {
}
}

void Animator::EndWarmUpFrame() {
// Do nothing. The warm up frame does not need any additional work to end the
// frame for now. This will change once the pipeline supports multi-view.
// https://github.com/flutter/flutter/issues/142851
}

void Animator::ScheduleSecondaryVsyncCallback(uintptr_t id,
const fml::closure& callback) {
waiter_->ScheduleSecondaryCallback(id, callback);
Expand Down
16 changes: 16 additions & 0 deletions shell/common/animator.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ class Animator final {

void RequestFrame(bool regenerate_layer_trees = true);

//--------------------------------------------------------------------------
/// @brief Tells the Animator that a warm up frame has ended.
///
/// In a warm up frame, `Animator::Render` is called out of vsync
/// tasks, and Animator requires an explicit end-of-frame call to
/// know when to send the layer trees to the pipeline.
///
/// This is different from regular frames, where Animator::Render is
/// always called within a vsync task, and Animator can send
/// the views at the end of the vsync task.
///
/// For more about warm up frames, see
/// `PlatformDispatcher.scheduleWarmUpFrame`.
///
void EndWarmUpFrame();

//--------------------------------------------------------------------------
/// @brief Tells the Animator that this frame needs to render another view.
///
Expand Down
4 changes: 4 additions & 0 deletions shell/common/engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,10 @@ void Engine::ScheduleFrame(bool regenerate_layer_trees) {
animator_->RequestFrame(regenerate_layer_trees);
}

void Engine::EndWarmUpFrame() {
animator_->EndWarmUpFrame();
}

void Engine::Render(std::unique_ptr<flutter::LayerTree> layer_tree,
float device_pixel_ratio) {
if (!layer_tree) {
Expand Down
3 changes: 3 additions & 0 deletions shell/common/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,9 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
/// tree.
void ScheduleFrame() { ScheduleFrame(true); }

// |RuntimeDelegate|
void EndWarmUpFrame() override;

// |RuntimeDelegate|
FontCollection& GetFontCollection() override;

Expand Down
Loading

0 comments on commit 96d7502

Please sign in to comment.