blob: ec2944bb41327f2c1f4817982795d6e1f3df3d4e [file] [log] [blame]
// Copyright 2013 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 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
class FrameTimingRecorder {
final int _vsyncStartMicros = _currentFrameVsyncStart;
final int _buildStartMicros = _currentFrameBuildStart;
int? _buildFinishMicros;
int? _rasterStartMicros;
int? _rasterFinishMicros;
/// Collects frame timings from frames.
///
/// This list is periodically reported to the framework (see [_kFrameTimingsSubmitInterval]).
static List<ui.FrameTiming> _frameTimings = <ui.FrameTiming>[];
/// These two metrics are collected early in the process, before the respective
/// scene builders are created. These are instead treated as global state, which
/// are used to initialize any recorders that are created by the scene builders.
static int _currentFrameVsyncStart = 0;
static int _currentFrameBuildStart = 0;
static void recordCurrentFrameVsync() {
if (frameTimingsEnabled) {
_currentFrameVsyncStart = _nowMicros();
}
}
static void recordCurrentFrameBuildStart() {
if (frameTimingsEnabled) {
_currentFrameBuildStart = _nowMicros();
}
}
/// The last time (in microseconds) we submitted frame timings.
static int _frameTimingsLastSubmitTime = _nowMicros();
/// The amount of time in microseconds we wait between submitting
/// frame timings.
static const int _kFrameTimingsSubmitInterval = 100000; // 100 milliseconds
/// Whether we are collecting [ui.FrameTiming]s.
static bool get frameTimingsEnabled {
return EnginePlatformDispatcher.instance.onReportTimings != null;
}
/// Current timestamp in microseconds taken from the high-precision
/// monotonically increasing timer.
///
/// See also:
///
/// * https://developer.mozilla.org/en-US/docs/Web/API/Performance/now,
/// particularly notes about Firefox rounding to 1ms for security reasons,
/// which can be bypassed in tests by setting certain browser options.
static int _nowMicros() {
return (domWindow.performance.now() * 1000).toInt();
}
void recordBuildFinish([int? buildFinish]) {
assert(_buildFinishMicros == null, "can't record build finish more than once");
_buildFinishMicros = buildFinish ?? _nowMicros();
}
void recordRasterStart([int? rasterStart]) {
assert(_rasterStartMicros == null, "can't record raster start more than once");
_rasterStartMicros = rasterStart ?? _nowMicros();
}
void recordRasterFinish([int? rasterFinish]) {
assert(_rasterFinishMicros == null, "can't record raster finish more than once");
_rasterFinishMicros = rasterFinish ?? _nowMicros();
}
void submitTimings() {
assert(
_buildFinishMicros != null &&
_rasterStartMicros != null &&
_rasterFinishMicros != null,
'Attempted to submit an incomplete timings.'
);
final ui.FrameTiming timing = ui.FrameTiming(
vsyncStart: _vsyncStartMicros,
buildStart: _buildStartMicros,
buildFinish: _buildFinishMicros!,
rasterStart: _rasterStartMicros!,
rasterFinish: _rasterFinishMicros!,
rasterFinishWallTime: _rasterFinishMicros!,
);
_frameTimings.add(timing);
final int now = _nowMicros();
if (now - _frameTimingsLastSubmitTime > _kFrameTimingsSubmitInterval) {
_frameTimingsLastSubmitTime = now;
EnginePlatformDispatcher.instance.invokeOnReportTimings(_frameTimings);
_frameTimings = <ui.FrameTiming>[];
}
}
}