// 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:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/semantics.dart'; import 'material3.dart'; import 'recorder.dart'; /// Measures the cost of semantics when constructing screens containing /// Material 3 widgets. class BenchMaterial3Semantics extends WidgetBuildRecorder { BenchMaterial3Semantics() : super(name: benchmarkName); static const String benchmarkName = 'bench_material3_semantics'; @override Future setUpAll() async { FlutterTimeline.debugCollectionEnabled = true; await super.setUpAll(); SemanticsBinding.instance.ensureSemantics(); } @override Future tearDownAll() async { FlutterTimeline.debugReset(); } @override void frameDidDraw() { // Only record frames that show the widget. Frames that remove the widget // are not interesting. if (showWidget) { final AggregatedTimings timings = FlutterTimeline.debugCollect(); final AggregatedTimedBlock semanticsBlock = timings.getAggregated('SEMANTICS'); final AggregatedTimedBlock updateChildren = timings.getAggregated('Semantics.updateChildren'); final AggregatedTimedBlock ensureGeometry = timings.getAggregated('Semantics.ensureGeometry'); final AggregatedTimedBlock ensureSemanticsNode = timings.getAggregated( 'Semantics.ensureSemanticsNode', ); profile!.addTimedBlock(semanticsBlock, reported: true); profile!.addTimedBlock(updateChildren, reported: true); profile!.addTimedBlock(ensureGeometry, reported: true); profile!.addTimedBlock(ensureSemanticsNode, reported: true); } super.frameDidDraw(); FlutterTimeline.debugReset(); } @override Widget createWidget() { return const SingleColumnMaterial3Components(); } } /// Measures the cost of semantics when scrolling screens containing Material 3 /// widgets. /// /// The implementation uses a ListView that jumps the scroll position between /// 0 and 1 every frame. Such a small delta is not enough for lazy rendering to /// add/remove widgets, but its enough to trigger the framework to recompute /// some of the semantics. /// /// The expected output numbers of this benchmarks should be very small as /// scrolling a list view should be a matter of shifting some widgets and /// updating the projected clip imposed by the viewport. As of June 2023, the /// numbers are not great. Semantics consumes >50% of frame time. class BenchMaterial3ScrollSemantics extends WidgetRecorder { BenchMaterial3ScrollSemantics() : super(name: benchmarkName); static const String benchmarkName = 'bench_material3_scroll_semantics'; @override Future setUpAll() async { FlutterTimeline.debugCollectionEnabled = true; await super.setUpAll(); SemanticsBinding.instance.ensureSemantics(); } @override Future tearDownAll() async { FlutterTimeline.debugReset(); } @override void frameDidDraw() { final AggregatedTimings timings = FlutterTimeline.debugCollect(); final AggregatedTimedBlock semanticsBlock = timings.getAggregated('SEMANTICS'); final AggregatedTimedBlock updateChildren = timings.getAggregated('Semantics.updateChildren'); final AggregatedTimedBlock ensureGeometry = timings.getAggregated('Semantics.ensureGeometry'); final AggregatedTimedBlock ensureSemanticsNode = timings.getAggregated( 'Semantics.ensureSemanticsNode', ); profile!.addTimedBlock(semanticsBlock, reported: true); profile!.addTimedBlock(updateChildren, reported: true); profile!.addTimedBlock(ensureGeometry, reported: true); profile!.addTimedBlock(ensureSemanticsNode, reported: true); super.frameDidDraw(); FlutterTimeline.debugReset(); } @override Widget createWidget() => _ScrollTest(); } class _ScrollTest extends StatefulWidget { @override State<_ScrollTest> createState() => _ScrollTestState(); } class _ScrollTestState extends State<_ScrollTest> with SingleTickerProviderStateMixin { late final Ticker ticker; late final ScrollController scrollController; @override void initState() { super.initState(); scrollController = ScrollController(); var forward = true; // A one-off timer is necessary to allow the framework to measure the // available scroll extents before the scroll controller can be exercised // to change the scroll position. Timer.run(() { ticker = createTicker((_) { scrollController.jumpTo(forward ? 1 : 0); forward = !forward; }); ticker.start(); }); } @override void dispose() { ticker.dispose(); scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SingleColumnMaterial3Components(scrollController: scrollController); } }