add fps counters and headroom
This commit is contained in:
@@ -26,12 +26,19 @@ class ThermionTextureWidget extends StatefulWidget {
|
|||||||
///
|
///
|
||||||
final Future Function(Size size, t.View view, double pixelRatio)? onResize;
|
final Future Function(Size size, t.View view, double pixelRatio)? onResize;
|
||||||
|
|
||||||
const ThermionTextureWidget(
|
///
|
||||||
{super.key,
|
/// When true, an FPS counter will be displayed at the top right of the widget
|
||||||
required this.viewer,
|
///
|
||||||
required this.view,
|
final bool showFpsCounter;
|
||||||
this.initial,
|
|
||||||
this.onResize});
|
const ThermionTextureWidget({
|
||||||
|
super.key,
|
||||||
|
required this.viewer,
|
||||||
|
required this.view,
|
||||||
|
this.initial,
|
||||||
|
this.onResize,
|
||||||
|
this.showFpsCounter = false,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() {
|
State<StatefulWidget> createState() {
|
||||||
@@ -40,21 +47,27 @@ class ThermionTextureWidget extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
|
class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
|
||||||
|
|
||||||
PlatformTextureDescriptor? _texture;
|
PlatformTextureDescriptor? _texture;
|
||||||
|
|
||||||
static final _views = <t.View>[];
|
static final _views = <t.View>[];
|
||||||
|
|
||||||
final _logger = Logger("_ThermionTextureWidgetState");
|
final _logger = Logger("_ThermionTextureWidgetState");
|
||||||
|
|
||||||
|
int _fps = 0;
|
||||||
|
int _frameCount = 0;
|
||||||
|
int _frameRequestCount = 0;
|
||||||
|
int _frameRequestPercentage = 0;
|
||||||
|
int _lastFpsUpdateTime = 0;
|
||||||
|
Timer? _fpsUpdateTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
_views.remove(widget.view);
|
_views.remove(widget.view);
|
||||||
if(_texture != null) {
|
if (_texture != null) {
|
||||||
ThermionFlutterPlatform.instance.destroyTextureDescriptor(_texture!);
|
ThermionFlutterPlatform.instance.destroyTextureDescriptor(_texture!);
|
||||||
}
|
}
|
||||||
|
_fpsUpdateTimer?.cancel();
|
||||||
_states.remove(this);
|
_states.remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +77,23 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
|
|||||||
throw Exception("View already embedded in a widget");
|
throw Exception("View already embedded in a widget");
|
||||||
}
|
}
|
||||||
_views.add(widget.view);
|
_views.add(widget.view);
|
||||||
|
|
||||||
|
// Start FPS counter update timer if enabled
|
||||||
|
if (widget.showFpsCounter) {
|
||||||
|
_fpsUpdateTimer = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_fps = _frameCount;
|
||||||
|
_frameRequestPercentage = _frameCount > 0
|
||||||
|
? (_frameCount / _frameRequestCount * 100).round()
|
||||||
|
: 0;
|
||||||
|
_frameCount = 0;
|
||||||
|
_frameRequestCount = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||||
await widget.viewer.initialized;
|
await widget.viewer.initialized;
|
||||||
|
|
||||||
@@ -71,9 +101,9 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
|
|||||||
|
|
||||||
var size = ((context.findRenderObject()) as RenderBox).size;
|
var size = ((context.findRenderObject()) as RenderBox).size;
|
||||||
|
|
||||||
_logger.info(
|
_logger
|
||||||
"Widget size in logical pixels ${size} (pixel ratio : $dpr)");
|
.info("Widget size in logical pixels ${size} (pixel ratio : $dpr)");
|
||||||
|
|
||||||
var width = (size.width * dpr).ceil();
|
var width = (size.width * dpr).ceil();
|
||||||
var height = (size.height * dpr).ceil();
|
var height = (size.height * dpr).ceil();
|
||||||
|
|
||||||
@@ -111,7 +141,7 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
|
|||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
if(texture != null) {
|
if (texture != null) {
|
||||||
ThermionFlutterPlatform.instance.destroyTextureDescriptor(texture);
|
ThermionFlutterPlatform.instance.destroyTextureDescriptor(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +156,7 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
|
|||||||
static final _states = <_ThermionTextureWidgetState>{};
|
static final _states = <_ThermionTextureWidgetState>{};
|
||||||
|
|
||||||
int lastRender = 0;
|
int lastRender = 0;
|
||||||
|
int _headroomInMs = 5;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Each instance of ThermionTextureWidget in the widget hierarchy must
|
/// Each instance of ThermionTextureWidget in the widget hierarchy must
|
||||||
@@ -144,18 +175,32 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
|
|||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (widget.showFpsCounter) {
|
||||||
|
_frameRequestCount++;
|
||||||
|
}
|
||||||
|
|
||||||
WidgetsBinding.instance.scheduleFrameCallback((d) async {
|
WidgetsBinding.instance.scheduleFrameCallback((d) async {
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (widget.viewer.rendering && !_rendering && _resizing.isEmpty && (d.inMilliseconds - lastRender > widget.viewer.msPerFrame)) {
|
if (widget.viewer.rendering &&
|
||||||
|
!_rendering &&
|
||||||
|
_resizing.isEmpty &&
|
||||||
|
(d.inMilliseconds - lastRender >
|
||||||
|
widget.viewer.msPerFrame - _headroomInMs)) {
|
||||||
_rendering = true;
|
_rendering = true;
|
||||||
if (this == _states.first && _texture != null) {
|
if (this == _states.first && _texture != null) {
|
||||||
await widget.viewer.requestFrame();
|
await widget.viewer.requestFrame();
|
||||||
lastRender = d.inMilliseconds;
|
lastRender = d.inMilliseconds;
|
||||||
|
|
||||||
|
if (widget.showFpsCounter) {
|
||||||
|
_frameCount++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(_texture != null) {
|
if (_texture != null) {
|
||||||
await ThermionFlutterPlatform.instance.markTextureFrameAvailable(_texture!);
|
await ThermionFlutterPlatform.instance
|
||||||
|
.markTextureFrameAvailable(_texture!);
|
||||||
}
|
}
|
||||||
_rendering = false;
|
_rendering = false;
|
||||||
}
|
}
|
||||||
@@ -197,7 +242,8 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
|
|||||||
_logger.info(
|
_logger.info(
|
||||||
"Resizing texture to dimensions ${newWidth}x${newHeight} (pixel ratio : $dpr)");
|
"Resizing texture to dimensions ${newWidth}x${newHeight} (pixel ratio : $dpr)");
|
||||||
|
|
||||||
_texture = await ThermionFlutterPlatform.instance.resizeTexture(_texture!, widget.view, newWidth, newHeight);
|
_texture = await ThermionFlutterPlatform.instance
|
||||||
|
.resizeTexture(_texture!, widget.view, newWidth, newHeight);
|
||||||
|
|
||||||
_logger.info(
|
_logger.info(
|
||||||
"Resized texture to dimensions ${_texture!.width}x${_texture!.height} (pixel ratio : $dpr)");
|
"Resized texture to dimensions ${_texture!.width}x${_texture!.height} (pixel ratio : $dpr)");
|
||||||
@@ -225,15 +271,50 @@ class _ThermionTextureWidgetState extends State<ThermionTextureWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ResizeObserver(
|
return ResizeObserver(
|
||||||
onResized: _resize,
|
onResized: _resize,
|
||||||
child: Stack(children: [
|
child: Stack(
|
||||||
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Texture(
|
child: Texture(
|
||||||
key: ObjectKey("flutter_texture_${_texture!.flutterTextureId}"),
|
key: ObjectKey("flutter_texture_${_texture!.flutterTextureId}"),
|
||||||
textureId: _texture!.flutterTextureId,
|
textureId: _texture!.flutterTextureId,
|
||||||
filterQuality: FilterQuality.none,
|
filterQuality: FilterQuality.none,
|
||||||
freeze: false,
|
freeze: false,
|
||||||
))
|
)),
|
||||||
]));
|
if (widget.showFpsCounter)
|
||||||
|
Positioned(
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withOpacity(0.6),
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'$_fps FPS',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Render: $_frameRequestPercentage%',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ Future kDefaultResizeCallback(Size size, t.View view, double pixelRatio) async {
|
|||||||
var far = await camera.getCullingFar();
|
var far = await camera.getCullingFar();
|
||||||
var focalLength = await camera.getFocalLength();
|
var focalLength = await camera.getFocalLength();
|
||||||
|
|
||||||
await camera.setLensProjection(near:near, far:far, focalLength: focalLength,
|
await camera.setLensProjection(
|
||||||
|
near: near,
|
||||||
|
far: far,
|
||||||
|
focalLength: focalLength,
|
||||||
aspect: size.width.toDouble() / size.height.toDouble());
|
aspect: size.width.toDouble() / size.height.toDouble());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,21 +32,23 @@ class ThermionWidget extends StatefulWidget {
|
|||||||
final t.View? view;
|
final t.View? view;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// A callback to invoke whenever this widget and the underlying surface are
|
/// A callback to invoke whenever this widget and the underlying surface are
|
||||||
/// resized. If a callback is not explicitly provided, the default callback
|
/// resized. If a callback is not explicitly provided, the default callback
|
||||||
/// will be run, which changes the aspect ratio for the active camera in
|
/// will be run, which changes the aspect ratio for the active camera in
|
||||||
/// the View managed by this widget. If you specify your own callback,
|
/// the View managed by this widget. If you specify your own callback,
|
||||||
/// you probably want to preserve this behaviour (otherwise the aspect ratio)
|
/// you probably want to preserve this behaviour (otherwise the aspect ratio)
|
||||||
/// will be incorrect.
|
/// will be incorrect.
|
||||||
///
|
///
|
||||||
/// To completely disable the resize callback, pass [null].
|
/// To completely disable the resize callback, pass [null].
|
||||||
///
|
///
|
||||||
/// IMPORTANT - size is specified in physical pixels, not logical pixels.
|
/// IMPORTANT - size is specified in physical pixels, not logical pixels.
|
||||||
/// If you need to work with Flutter dimensions, divide [size] by
|
/// If you need to work with Flutter dimensions, divide [size] by
|
||||||
/// [pixelRatio].
|
/// [pixelRatio].
|
||||||
///
|
///
|
||||||
final Future Function(Size size, t.View view, double pixelRatio)? onResize;
|
final Future Function(Size size, t.View view, double pixelRatio)? onResize;
|
||||||
|
|
||||||
|
final bool showFpsCounter;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// The content to render before the texture widget is available.
|
/// The content to render before the texture widget is available.
|
||||||
/// The default is a solid red Container, intentionally chosen to make it clear that there will be at least one frame where the Texture widget is not being rendered.
|
/// The default is a solid red Container, intentionally chosen to make it clear that there will be at least one frame where the Texture widget is not being rendered.
|
||||||
@@ -55,6 +60,7 @@ class ThermionWidget extends StatefulWidget {
|
|||||||
this.initial,
|
this.initial,
|
||||||
required this.viewer,
|
required this.viewer,
|
||||||
this.view,
|
this.view,
|
||||||
|
this.showFpsCounter = false,
|
||||||
this.onResize = kDefaultResizeCallback})
|
this.onResize = kDefaultResizeCallback})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@@ -98,6 +104,7 @@ class _ThermionWidgetState extends State<ThermionWidget> {
|
|||||||
initial: widget.initial,
|
initial: widget.initial,
|
||||||
viewer: widget.viewer,
|
viewer: widget.viewer,
|
||||||
view: view!,
|
view: view!,
|
||||||
|
showFpsCounter:widget.showFpsCounter,
|
||||||
onResize: widget.onResize);
|
onResize: widget.onResize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user