fix: хм
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
@@ -10,15 +11,34 @@ class FpsOverlayLayer extends StatefulWidget {
|
||||
State<FpsOverlayLayer> createState() => _FpsOverlayLayerState();
|
||||
}
|
||||
|
||||
class _FrameSample {
|
||||
const _FrameSample({
|
||||
required this.endMicros,
|
||||
required this.costMicros,
|
||||
required this.rasterBound,
|
||||
});
|
||||
|
||||
final int endMicros;
|
||||
final int costMicros;
|
||||
final bool rasterBound;
|
||||
}
|
||||
|
||||
class _FpsOverlayLayerState extends State<FpsOverlayLayer> {
|
||||
static const int _maxSamples = 90;
|
||||
static const int _minUiRefreshMs = 160;
|
||||
static const double _initialWidthGuess = 96;
|
||||
static const int _fpsWindowMicros = 1000000;
|
||||
static const int _jankWindowMicros = 3000000;
|
||||
static const int _minUiRefreshMs = 100;
|
||||
static const double _initialWidthGuess = 132;
|
||||
static const double _initialHeightGuess = 36;
|
||||
|
||||
final List<int> _frameMicros = <int>[];
|
||||
final List<_FrameSample> _recent = <_FrameSample>[];
|
||||
final List<_FrameSample> _janky = <_FrameSample>[];
|
||||
final GlobalKey _badgeKey = GlobalKey();
|
||||
double _refreshRate = 60;
|
||||
double _budgetMicros = 1000000 / 60;
|
||||
double _fps = 0;
|
||||
int _worstMicros = 0;
|
||||
int _jankCount = 0;
|
||||
bool _worstRasterBound = false;
|
||||
DateTime _lastUiUpdate = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
double? _left;
|
||||
double? _top;
|
||||
@@ -38,6 +58,8 @@ class _FpsOverlayLayerState extends State<FpsOverlayLayer> {
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_refreshRate = _resolveRefreshRate();
|
||||
_budgetMicros = 1000000 / _refreshRate;
|
||||
if (_left != null && _top != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
@@ -47,6 +69,11 @@ class _FpsOverlayLayerState extends State<FpsOverlayLayer> {
|
||||
}
|
||||
}
|
||||
|
||||
double _resolveRefreshRate() {
|
||||
final hz = View.of(context).display.refreshRate;
|
||||
return hz.isFinite && hz >= 30 ? hz : 60;
|
||||
}
|
||||
|
||||
void _ensureInitialPosition() {
|
||||
if (_left != null) return;
|
||||
final mq = MediaQuery.of(context);
|
||||
@@ -63,12 +90,8 @@ class _FpsOverlayLayerState extends State<FpsOverlayLayer> {
|
||||
final bottomMax = screen.height - mq.padding.bottom;
|
||||
|
||||
final box = _badgeKey.currentContext?.findRenderObject() as RenderBox?;
|
||||
final bw = box?.hasSize == true
|
||||
? box!.size.width
|
||||
: _initialWidthGuess;
|
||||
final bh = box?.hasSize == true
|
||||
? box!.size.height
|
||||
: _initialHeightGuess;
|
||||
final bw = box?.hasSize == true ? box!.size.width : _initialWidthGuess;
|
||||
final bh = box?.hasSize == true ? box!.size.height : _initialHeightGuess;
|
||||
|
||||
_left = _left!.clamp(0.0, math.max(0.0, screen.width - bw));
|
||||
_top = _top!.clamp(topMin, math.max(topMin, bottomMax - bh));
|
||||
@@ -76,23 +99,54 @@ class _FpsOverlayLayerState extends State<FpsOverlayLayer> {
|
||||
|
||||
void _onTimings(List<FrameTiming> timings) {
|
||||
for (final t in timings) {
|
||||
final us = t.totalSpan.inMicroseconds;
|
||||
if (us <= 0) continue;
|
||||
_frameMicros.add(us);
|
||||
while (_frameMicros.length > _maxSamples) {
|
||||
_frameMicros.removeAt(0);
|
||||
final build = t.buildDuration.inMicroseconds;
|
||||
final raster = t.rasterDuration.inMicroseconds;
|
||||
final sample = _FrameSample(
|
||||
endMicros: t.timestampInMicroseconds(ui.FramePhase.rasterFinish),
|
||||
costMicros: build > raster ? build : raster,
|
||||
rasterBound: raster >= build,
|
||||
);
|
||||
_recent.add(sample);
|
||||
if (sample.costMicros > _budgetMicros) _janky.add(sample);
|
||||
}
|
||||
if (_recent.isEmpty) return;
|
||||
|
||||
final newest = _recent.last.endMicros;
|
||||
_recent.removeWhere((s) => newest - s.endMicros > _fpsWindowMicros);
|
||||
_janky.removeWhere((s) => newest - s.endMicros > _jankWindowMicros);
|
||||
|
||||
var sum = 0;
|
||||
for (final s in _recent) {
|
||||
sum += s.costMicros;
|
||||
}
|
||||
var worst = 0;
|
||||
var worstRasterBound = false;
|
||||
for (final s in _janky) {
|
||||
if (s.costMicros > worst) {
|
||||
worst = s.costMicros;
|
||||
worstRasterBound = s.rasterBound;
|
||||
}
|
||||
}
|
||||
|
||||
final mean = sum / _recent.length;
|
||||
final fps = 1000000 / math.max(mean, _budgetMicros);
|
||||
|
||||
final now = DateTime.now();
|
||||
if (now.difference(_lastUiUpdate).inMilliseconds < _minUiRefreshMs) {
|
||||
return;
|
||||
}
|
||||
if (now.difference(_lastUiUpdate).inMilliseconds < _minUiRefreshMs) return;
|
||||
_lastUiUpdate = now;
|
||||
if (!mounted || _frameMicros.isEmpty) return;
|
||||
final sum = _frameMicros.fold<int>(0, (a, b) => a + b);
|
||||
final avg = sum / _frameMicros.length;
|
||||
final fps = avg > 0 ? (1000000.0 / avg).clamp(0.0, 999.0) : 0.0;
|
||||
setState(() => _fps = fps);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_fps = fps;
|
||||
_worstMicros = worst;
|
||||
_jankCount = _janky.length;
|
||||
_worstRasterBound = worstRasterBound;
|
||||
});
|
||||
}
|
||||
|
||||
Color get _tint {
|
||||
if (_jankCount == 0) return const Color(0xFFB8F5C6);
|
||||
if (_worstMicros > _budgetMicros * 3) return const Color(0xFFFFAB91);
|
||||
return const Color(0xFFFFE082);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -100,6 +154,7 @@ class _FpsOverlayLayerState extends State<FpsOverlayLayer> {
|
||||
_ensureInitialPosition();
|
||||
_clampPositionToScreen();
|
||||
|
||||
final tint = _tint;
|
||||
return Positioned(
|
||||
left: _left,
|
||||
top: _top,
|
||||
@@ -123,18 +178,31 @@ class _FpsOverlayLayerState extends State<FpsOverlayLayer> {
|
||||
color: const Color(0xCC000000),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'${_fps.round()} FPS',
|
||||
style: TextStyle(
|
||||
color: _fps >= 55
|
||||
? const Color(0xFFB8F5C6)
|
||||
: _fps >= 30
|
||||
? const Color(0xFFFFE082)
|
||||
: const Color(0xFFFFAB91),
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFeatures: const [FontFeature.tabularFigures()],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'${_fps.round()} FPS · ${_refreshRate.round()} Hz',
|
||||
style: TextStyle(
|
||||
color: tint,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFeatures: const [FontFeature.tabularFigures()],
|
||||
),
|
||||
),
|
||||
if (_jankCount > 0)
|
||||
Text(
|
||||
'${(_worstMicros / 1000).round()} ms ×$_jankCount '
|
||||
'${_worstRasterBound ? 'gpu' : 'ui'}',
|
||||
style: TextStyle(
|
||||
color: tint,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFeatures: const [FontFeature.tabularFigures()],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user