-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Description
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 theAnimatorto 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.renderfor all views, which just stores the layer tree inAnimator.
- which calls
- Calls
- Calls
Animator::EndFrame- which submit the stored layer trees to the pipeline continuation.
- Calls
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.