| // 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:html' as html; |
| import 'dart:typed_data'; |
| import 'dart:ui' as ui; |
| |
| import 'recorder.dart'; |
| |
| /// Measures the performance of image decoding. |
| /// |
| /// The benchmark measures the decoding latency and not impact on jank. It |
| /// cannot distinguish between blocking and non-blocking decoding. It simply |
| /// measures the total time it takes to decode image frames. For example, the |
| /// WASM codecs execute on the main thread and block the UI, leading to jank, |
| /// but the browser's WebCodecs API is asynchronous running on a separate thread |
| /// and does not jank. However, the benchmark result may be the same. |
| /// |
| /// This benchmark does not support the HTML renderer because the HTML renderer |
| /// cannot decode image frames (it always returns 1 dummy frame, even for |
| /// animated images). |
| class BenchImageDecoding extends RawRecorder { |
| BenchImageDecoding() : super( |
| name: benchmarkName, |
| useCustomWarmUp: true, |
| ); |
| |
| static const String benchmarkName = 'bench_image_decoding'; |
| |
| // These test images are taken from https://github.com/flutter/flutter_gallery_assets/tree/master/lib/splash_effects |
| static const List<String> _imageUrls = <String>[ |
| 'assets/packages/flutter_gallery_assets/splash_effects/splash_effect_1.gif', |
| 'assets/packages/flutter_gallery_assets/splash_effects/splash_effect_2.gif', |
| 'assets/packages/flutter_gallery_assets/splash_effects/splash_effect_3.gif', |
| ]; |
| |
| final List<Uint8List> _imageData = <Uint8List>[]; |
| |
| @override |
| Future<void> setUpAll() async { |
| if (_imageData.isNotEmpty) { |
| return; |
| } |
| for (final String imageUrl in _imageUrls) { |
| final html.Body image = await html.window.fetch(imageUrl) as html.Body; |
| _imageData.add((await image.arrayBuffer() as ByteBuffer).asUint8List()); |
| } |
| } |
| |
| // The number of samples recorded so far. |
| int _sampleCount = 0; |
| |
| // The number of samples used for warm-up. |
| static const int _warmUpSampleCount = 5; |
| |
| // The number of samples used to measure performance after the warm-up. |
| static const int _measuredSampleCount = 20; |
| |
| @override |
| Future<void> body(Profile profile) async { |
| await profile.recordAsync('recordImageDecode', () async { |
| final List<Future<void>> allDecodes = <Future<void>>[ |
| for (final Uint8List data in _imageData) |
| _decodeImage(data), |
| ]; |
| await Future.wait(allDecodes); |
| }, reported: true); |
| |
| _sampleCount += 1; |
| if (_sampleCount == _warmUpSampleCount) { |
| profile.stopWarmingUp(); |
| } |
| if (_sampleCount >= _warmUpSampleCount + _measuredSampleCount) { |
| profile.stopBenchmark(); |
| } |
| } |
| } |
| |
| Future<void> _decodeImage(Uint8List data) async { |
| final ui.Codec codec = await ui.instantiateImageCodec(data); |
| const int decodeFrameCount = 5; |
| if (codec.frameCount < decodeFrameCount) { |
| throw Exception( |
| 'Test image contains too few frames for this benchmark (${codec.frameCount}). ' |
| 'Choose a test image with at least $decodeFrameCount frames.' |
| ); |
| } |
| for (int i = 0; i < decodeFrameCount; i++) { |
| (await codec.getNextFrame()).image.dispose(); |
| } |
| codec.dispose(); |
| } |