Skip to content

Properly render the warmup frames #142851

@dkwingsmt

Description

@dkwingsmt

We should incorporate the warmup frames into the current rendering structure, so that the warmup frames can be rendered and presented onto the screen.

Why

While it might sound against the purpose for warmup frames, we've discovered that the warmup frames should actually be rendered and presented.

As soon as the Dart isolate is started, a warm up frame is scheduled, which immediately render the widget tree to a layer tree and submit it to the engine. If we don't present this layer tree, then the user will have to wait for the next vsync to see the first frame, which is bound to be later. This delay was actually flagged as a performance regression earlier (b/307872797).

Analysis

This is how a regular frame is presented:.

  • A frame is scheduled (such as via PlatformDispatcher.scheduleFrame), which causes the Animator to submit a task for the next vsync.
  • The next vsync task:
    • Calls Animator::BeginFrame
      • Produces a pipeline continuation
      • Calls RuntimeController.BeginFrame, which
        • Calls PlatformDispatcher.onBeginFrame
        • Clears microtasks
        • Calls PlatformDispatcher.onDrawFrame
          • which calls FlutterView.render for all views, which just stores the layer tree in Animator.
    • Calls Animator::EndFrame
      • which submit the stored layer trees to the pipeline continuation.

Therefore, rendering a frame requires the pipeline continuation produced in BeginFrame, and the submission in EndFrame. This is why we proposed to enforce the render rule in #137073.

However, the render rule forbids FlutterView.render calls out of vsyncs, which are warm up frames.

We initially proposed a "workaround mechanism". We've abolished this plan. Expand here to see the details. Currently, we implemented a workaround mechanism in `RuntimeController`: * In `RuntimeController::Render`, if all registered views have been rendered, then it calls `Animator::EndFrame`.

This allows the multiview pipeline to coexist with existing warmup frame situations. But it is not only an ugly rule, but also can not detect the case where only part of all views are warmup frames.

The tentative plan is to add a new function, PlatformDispatcher.forceFrame, which does everything of the "vsync task" as mentioned above, but is run directly instead of within a vsync.

The render rule should be enforced after this issue is resolved.

Real app testing

To illustrate the difference, I added some logging statements to the engine, and tested on an app on macOS.

Case 1: This is how the app starts up on an unmodified engine (the number at the start is time in milliseconds):

[    0] Animator::BeginFrame
[    0]   created continuation
[    3] Animator::EndFrame
[  947] Animator::BeginFrame
[  947]   use existing continuation
[  950] Animator::EndFrame
[ 2879] Animator::Render      <-- The warmup render, out of vsync
[ 2879]   Fabricate recorder
[ 2879] Animator::EndFrame
[ 2879]   OnAnimatorDraw
[ 2879] Rasterizer::Draw
[ 2879] Rasterizer::DoDraw
[ 3006] Animator::BeginFrame
[ 3006]   created continuation
[ 3131] Animator::Render
[ 3131] Animator::EndFrame
[ 3131]   OnAnimatorDraw
[ 3158] Rasterizer::Frame callback
[ 3167] Rasterizer::Draw
[ 3167] Rasterizer::DoDraw
[ 3179] Rasterizer::Frame callback
[ 3187] Animator::BeginFrame
[ 3187]   created continuation
[ 3239] Animator::Render
...

Case 2: This is how the app starts up if out-of-vsync render calls are skipped (basically with flutter/engine#45555 applied):

[    0] Animator::BeginFrame
[    0]   created continuation
[    5] Animator::EndFrame
[ 1081] Animator::BeginFrame
[ 1081]   use existing continuation
[ 1083] Animator::EndFrame
[ 3149] Animator::BeginFrame
[ 3150]   use existing continuation
[ 3283] Animator::Render      <-- The first regular render, within vsync
[ 3283] Animator::EndFrame
[ 3283]   OnAnimatorDraw
[ 3283] Rasterizer::Draw
[ 3283] Rasterizer::DoDraw
[ 3293] Animator::BeginFrame
[ 3293]   created continuation
[ 3357] Animator::Render
[ 3357] Animator::EndFrame
[ 3357]   OnAnimatorDraw
[ 3359] Animator::BeginFrame
[ 3359]   continuation full
[ 3360] Animator::EndFrame
[ 3378] Animator::BeginFrame
[ 3378]   continuation full
[ 3378] Animator::EndFrame
[ 3394] Animator::BeginFrame
[ 3394]   continuation full
[ 3394] Animator::EndFrame
[ 3410] Animator::BeginFrame
[ 3410]   continuation full
[ 3410] Animator::EndFrame
[ 3413] Rasterizer::Frame callback
[ 3417] Rasterizer::Draw
[ 3417] Rasterizer::DoDraw
[ 3428] Animator::BeginFrame
[ 3428]   created continuation
...

We can see that in case 2, the first Animator::Render is as late as the 3rd Animator::BeginFrame, while in case 1 the first render is between the 2nd and the 3rd Animator::BeginFrame. Having an earlier Animator::Render means that the first Rasterizer::DoDraw is earlier, and the first frame is drawn sooner.

Metadata

Metadata

Assignees

Labels

P1High-priority issues at the top of the work listc: new featureNothing broken; request for a new capabilityc: proposalA detailed proposal for a change to Flutterengineflutter/engine related. See also e: labels.team-engineOwned by Engine teamtriaged-engineTriaged by Engine team

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions