| // 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(); |
| } |
| } |