(flutter) better synchronization in ThermionTextureWidget with presentation deadline

This commit is contained in:
Nick Fisher
2025-05-30 14:35:24 +08:00
parent 5f9a7bb959
commit ccdf2ecda6

View File

@@ -5,7 +5,6 @@ import 'package:thermion_flutter/src/widgets/src/resize_observer.dart';
import 'package:thermion_flutter/thermion_flutter.dart' hide Texture;
class ThermionTextureWidget extends StatefulWidget {
///
///
///
@@ -45,23 +44,21 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
static final _views = <View>[];
final _logger = Logger("_ThermionTextureWidgetState");
late final _logger = Logger(this.runtimeType.toString());
int _fps = 0;
int _frameCount = 0;
int _frameRequestCount = 0;
int _frameRequestPercentage = 0;
int _lastFpsUpdateTime = 0;
Timer? _fpsUpdateTimer;
@override
void dispose() {
_views.remove(widget.viewer.view);
final texture = _texture;
_texture = null;
super.dispose();
_views.remove(widget.viewer.view);
if (texture != null) {
ThermionFlutterPlatform.instance.destroyTextureDescriptor(texture!);
ThermionFlutterPlatform.instance.destroyTextureDescriptor(texture);
}
_fpsUpdateTimer?.cancel();
_states.remove(this);
@@ -69,9 +66,9 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
@override
void initState() {
if (_views.contains(widget.viewer.view)) {
throw Exception("View already embedded in a widget");
}
// if (_views.contains(widget.viewer.view)) {
// throw Exception("View already embedded in a widget");
// }
_views.add(widget.viewer.view);
// Start FPS counter update timer if enabled
@@ -80,18 +77,13 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
if (mounted) {
setState(() {
_fps = _frameCount;
_frameRequestPercentage = _frameCount > 0
? (_frameCount / _frameRequestCount * 100).round()
: 0;
_frameCount = 0;
_frameRequestCount = 0;
});
}
});
}
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
var dpr = MediaQuery.of(context).devicePixelRatio;
var size = ((context.findRenderObject()) as RenderBox).size;
@@ -147,13 +139,8 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
super.initState();
}
bool _rendering = false;
static final _states = <_ThermionTextureWidgetState>{};
int lastRender = 0;
int _headroomInMs = 5;
///
/// Each instance of ThermionTextureWidget in the widget hierarchy must
/// call [markFrameAvailable] on every frame to notify Flutter that the content
@@ -167,42 +154,48 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
/// [_ThermionTextureWidgetState] in a static set, and allowing the first
/// instance to call [requestFrame].
///
void _requestFrame() {
DateTime _deadline = DateTime.now();
Completer? _frameCompleter;
void _requestFrame() async {
if (!mounted) {
return;
}
if (widget.showFpsCounter) {
_frameRequestCount++;
await _frameCompleter?.future;
_frameCompleter = Completer();
var headroom = _deadline.difference(DateTime.now());
if (headroom.inMilliseconds > 5) {
var waitForNs = headroom.inMicroseconds - 5000;
await Future.delayed(Duration(microseconds: waitForNs));
}
WidgetsBinding.instance.scheduleFrameCallback((d) async {
if (widget.viewer.rendering && _resizing.isEmpty) {
if (_states.isNotEmpty && this == _states.first && _texture != null) {
await FilamentApp.instance!.requestFrame();
if (!mounted) {
return;
if (widget.showFpsCounter) {
_frameCount++;
}
}
if (widget.viewer.rendering &&
!_rendering &&
_resizing.isEmpty &&
(d.inMilliseconds - lastRender >
widget.viewer.msPerFrame - _headroomInMs)) {
_rendering = true;
if (this == _states.first && _texture != null) {
await FilamentApp.instance!.requestFrame();
lastRender = d.inMilliseconds;
}
if (widget.showFpsCounter) {
_frameCount++;
}
}
if (_texture != null) {
await ThermionFlutterPlatform.instance
.markTextureFrameAvailable(_texture!);
}
_rendering = false;
WidgetsBinding.instance.scheduleFrameCallback((Duration d) async {
if (_texture != null) {
await ThermionFlutterPlatform.instance
.markTextureFrameAvailable(_texture!);
}
_requestFrame();
_frameCompleter?.complete();
});
var deadlineInMicros = (widget.viewer.msPerFrame * 1000).toInt();
_deadline = DateTime.now().add(Duration(microseconds: deadlineInMicros));
Timer.run(_requestFrame);
}
final _resizing = <Future>[];