blob: 890a3759c6fd5a04868d96a82e70e3f16b55e7e3 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:developer';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'debug.dart';
/// Interface for drawing an image to warm up Skia shader compilations.
///
/// When Skia first sees a certain type of draw operation on the GPU, it needs
/// to compile the corresponding shader. The compilation can be slow (20ms-
/// 200ms). Having that time as startup latency is often better than having
/// jank in the middle of an animation.
///
/// Therefore, we use this during the [PaintingBinding.initInstances] call to
/// move common shader compilations from animation time to startup time. If
/// needed, app developers can create a custom [ShaderWarmUp] subclass and
/// hand it to [PaintingBinding.shaderWarmUp] before
/// [PaintingBinding.initInstances] is called. Usually, that can be done before
/// calling [runApp].
///
/// To determine whether a draw operation is useful for warming up shaders,
/// check whether it improves the slowest frame rasterization time. Also,
/// tracing with `flutter run --profile --trace-skia` may reveal whether there
/// is shader-compilation-related jank. If there is such jank, some long
/// `GrGLProgramBuilder::finalize` calls would appear in the middle of an
/// animation. Their parent calls, which look like `XyzOp` (e.g., `FillRecOp`,
/// `CircularRRectOp`) would suggest Xyz draw operations are causing the shaders
/// to be compiled. A useful shader warm-up draw operation would eliminate such
/// long compilation calls in the animation. To double-check the warm-up, trace
/// with `flutter run --profile --trace-skia --start-paused`. The
/// `GrGLProgramBuilder` with the associated `XyzOp` should appear during
/// startup rather than in the middle of a later animation.
///
/// This warm-up needs to be run on each individual device because the shader
/// compilation depends on the specific GPU hardware and driver a device has. It
/// can't be pre-computed during the Flutter engine compilation as the engine is
/// device-agnostic.
///
/// If no warm-up is desired (e.g., when the startup latency is crucial), set
/// [PaintingBinding.shaderWarmUp] either to a custom ShaderWarmUp with an empty
/// [warmUpOnCanvas] or null.
///
/// See also:
///
/// * [PaintingBinding.shaderWarmUp], the actual instance of [ShaderWarmUp]
/// that's used to warm up the shaders.
/// * <https://flutter.dev/docs/perf/rendering/shader>
abstract class ShaderWarmUp {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const ShaderWarmUp();
/// The size of the warm up image.
///
/// The exact size shouldn't matter much as long as all draws are onscreen.
/// 100x100 is an arbitrary small size that's easy to fit significant draw
/// calls onto.
///
/// A custom shader warm up can override this based on targeted devices.
ui.Size get size => const ui.Size(100.0, 100.0);
/// Trigger draw operations on a given canvas to warm up GPU shader
/// compilation cache.
///
/// To decide which draw operations to be added to your custom warm up
/// process, consider capturing an skp using `flutter screenshot
/// --observatory-uri=<uri> --type=skia` and analyzing it with
/// <https://debugger.skia.org/>. Alternatively, one may run the app with
/// `flutter run --trace-skia` and then examine the raster thread in the
/// observatory timeline to see which Skia draw operations are commonly used,
/// and which shader compilations are causing jank.
@protected
Future<void> warmUpOnCanvas(ui.Canvas canvas);
/// Construct an offscreen image of [size], and execute [warmUpOnCanvas] on a
/// canvas associated with that image.
///
/// Currently, this has no effect when [kIsWeb] is true.
Future<void> execute() async {
final ui.PictureRecorder recorder = ui.PictureRecorder();
final ui.Canvas canvas = ui.Canvas(recorder);
await warmUpOnCanvas(canvas);
final ui.Picture picture = recorder.endRecording();
assert(debugCaptureShaderWarmUpPicture(picture));
if (!kIsWeb) { // Picture.toImage is not yet implemented on the web.
final TimelineTask shaderWarmUpTask = TimelineTask();
shaderWarmUpTask.start('Warm-up shader');
try {
final ui.Image image = await picture.toImage(size.width.ceil(), size.height.ceil());
assert(debugCaptureShaderWarmUpImage(image));
image.dispose();
} finally {
shaderWarmUpTask.finish();
}
}
picture.dispose();
}
}