Make shader warm-up async so it can handle image (#28687)
## Description
This moves another 15-20ms from the animation jank of one of our important client to the startup latency. Unfortunately, this is probably not captured in our current benchmarks (presumably some other bottlenecks overshadow this shader compilation in the worst_frame benchmark). Considering that drawing images is such a common operation, maybe we should add one in the future to benchmark this.
We need this PR to land soon for our client because this changes the API to return Future.
## Related Issues
https://github.com/flutter/flutter/issues/813
diff --git a/dev/benchmarks/macrobenchmarks/test_driver/cubic_bezier_perf.dart b/dev/benchmarks/macrobenchmarks/test_driver/cubic_bezier_perf.dart
index 69afb9e..3dae641 100644
--- a/dev/benchmarks/macrobenchmarks/test_driver/cubic_bezier_perf.dart
+++ b/dev/benchmarks/macrobenchmarks/test_driver/cubic_bezier_perf.dart
@@ -10,8 +10,8 @@
class CubicBezierShaderWarmUp extends DefaultShaderWarmUp {
@override
- void warmUpOnCanvas(Canvas canvas) {
- super.warmUpOnCanvas(canvas);
+ Future<void> warmUpOnCanvas(Canvas canvas) async {
+ await super.warmUpOnCanvas(canvas);
// Warm up the cubic shaders used by CubicBezierPage.
//
diff --git a/examples/layers/raw/shader_warm_up.dart b/examples/layers/raw/shader_warm_up.dart
index 63f82b3..9a6a973 100644
--- a/examples/layers/raw/shader_warm_up.dart
+++ b/examples/layers/raw/shader_warm_up.dart
@@ -9,14 +9,14 @@
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart' show DefaultShaderWarmUp;
-void beginFrame(Duration timeStamp) {
+Future<void> beginFrame(Duration timeStamp) async {
// PAINT
final ui.PictureRecorder recorder = ui.PictureRecorder();
final ui.Rect paintBounds = ui.Rect.fromLTRB(0, 0, 1000, 1000);
final ui.Canvas canvas = ui.Canvas(recorder, paintBounds);
final ui.Paint backgroundPaint = ui.Paint()..color = Colors.white;
canvas.drawRect(paintBounds, backgroundPaint);
- const DefaultShaderWarmUp().warmUpOnCanvas(canvas);
+ await const DefaultShaderWarmUp().warmUpOnCanvas(canvas);
final ui.Picture picture = recorder.endRecording();
// COMPOSITE
@@ -27,7 +27,7 @@
ui.window.render(sceneBuilder.build());
}
-void main() {
+Future<void> main() async {
ui.window.onBeginFrame = beginFrame;
ui.window.scheduleFrame();
}
diff --git a/packages/flutter/lib/src/painting/shader_warm_up.dart b/packages/flutter/lib/src/painting/shader_warm_up.dart
index b2ea90f..03051e6 100644
--- a/packages/flutter/lib/src/painting/shader_warm_up.dart
+++ b/packages/flutter/lib/src/painting/shader_warm_up.dart
@@ -2,7 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:async';
import 'dart:developer';
+import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
@@ -65,6 +67,8 @@
/// Trigger draw operations on a given canvas to warm up GPU shader
/// compilation cache.
///
+ /// Parameter [image] is to be used for drawImage related operations.
+ ///
/// To decide which draw operations to be added to your custom warm up
/// process, try capture an skp using `flutter screenshot --observatory-
/// port=<port> --type=skia` and analyze it with https://debugger.skia.org.
@@ -73,20 +77,20 @@
/// Skia draw operations are commonly used, and which shader compilations
/// are causing janks.
@protected
- void warmUpOnCanvas(ui.Canvas canvas);
+ Future<void> warmUpOnCanvas(ui.Canvas canvas);
/// Construct an offscreen image of [size], and execute [warmUpOnCanvas] on a
/// canvas associated with that image.
- void execute() {
+ Future<void> execute() async {
final ui.PictureRecorder recorder = ui.PictureRecorder();
final ui.Canvas canvas = ui.Canvas(recorder);
- warmUpOnCanvas(canvas);
+ await warmUpOnCanvas(canvas);
final ui.Picture picture = recorder.endRecording();
final TimelineTask shaderWarmUpTask = TimelineTask();
shaderWarmUpTask.start('Warm-up shader');
- picture.toImage(size.width.ceil(), size.height.ceil()).then((ui.Image image) {
+ picture.toImage(size.width.ceil(), size.height.ceil()).then((ui.Image _) {
shaderWarmUpTask.finish();
});
}
@@ -104,7 +108,7 @@
/// Trigger common draw operations on a canvas to warm up GPU shader
/// compilation cache.
@override
- void warmUpOnCanvas(ui.Canvas canvas) {
+ Future<void> warmUpOnCanvas(ui.Canvas canvas) {
final ui.RRect rrect = ui.RRect.fromLTRBXY(20.0, 20.0, 60.0, 60.0, 10.0, 10.0);
final ui.Path rrectPath = ui.Path()..addRRect(rrect);
@@ -179,5 +183,31 @@
final ui.Paragraph paragraph = paragraphBuilder.build()
..layout(const ui.ParagraphConstraints(width: 60.0));
canvas.drawParagraph(paragraph, const ui.Offset(20.0, 20.0));
+
+
+ // Construct an image for drawImage related operations
+ const int imageWidth = 40;
+ const int imageHeight = 40;
+ final Uint8List pixels = Uint8List.fromList(List<int>.generate(
+ imageWidth * imageHeight * 4,
+ (int i) => i % 4 < 2 ? 0x00 : 0xFF, // opaque blue
+ ));
+
+ final Completer<void> completer = Completer<void>();
+ ui.decodeImageFromPixels(pixels, imageWidth, imageHeight, ui.PixelFormat.rgba8888, (ui.Image image) {
+ // Warm up image shaders
+ canvas.translate(0.0, 80.0);
+ canvas.save();
+ final ui.Rect srcRect = ui.Rect.fromLTWH(0.0, 0.0, image.width.toDouble(), image.height.toDouble());
+ canvas.drawImage(image, const ui.Offset(20.0, 20.0), ui.Paint());
+ canvas.translate(80.0, 0.0);
+ canvas.drawImageRect(image, srcRect, ui.Rect.fromLTWH(20.0, 20.0, 20.0, 20.0), paints[0]);
+ canvas.translate(80.0, 0.0);
+ canvas.drawImageRect(image, srcRect, ui.Rect.fromLTWH(10.0, 10.0, 60.0, 60.0), paints[0]);
+ canvas.restore();
+ completer.complete();
+ });
+
+ return completer.future;
}
}