mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-07 21:11:05 +03:00
fix: address texture watchdog/probe review findings
- watchdog: compare the plugin's cumulative consumed counter against a snapshot taken when arming (re-arm was a no-op before), and judge on cumulative pushes + elapsed time so sparse damage-driven streams are still detected - failure handling is idempotent (one record per breakage) and updates every render session like main_set_local_option does, not only the failing one; the fallback toast is not claimed for multi-display windows the soft path cannot rescue - probe: skip when the consumed API is missing (old plugin) or a raster-stall is recorded (compositing could hang the main window); a fail verdict requires fresh frame timings and a non-minimized window; a pass never clears failed-raster-stall; toast only when texture rendering was effectively on; probe pixel is transparent - raster-stall monitor: judge on frame-timing staleness (a mid-episode hang was undetectable before), gate on lifecycle/minimized/idle with a moving quiet anchor, threshold 30s, shared with the camera page; clear stateGlobal minimized flag on plain window restore - adopt mismatched frame sizes only in multi-ui-session mode (legacy mode pairs frames loosely and could ping-pong between displays) - drop the now-dead closeSession parameter from texture destroy(); trim comments to repo style Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,15 +6,14 @@ import 'package:flutter/scheduler.dart';
|
||||
import '../../common.dart';
|
||||
import '../../consts.dart';
|
||||
import '../../models/platform_model.dart';
|
||||
import '../../models/state_model.dart';
|
||||
|
||||
import 'package:texture_rgba_renderer/texture_rgba_renderer.dart'
|
||||
if (dart.library.html) 'package:flutter_hbb/web/texture_rgba_renderer.dart';
|
||||
|
||||
/// Startup probe: renders one frame through a 1x1 external texture and
|
||||
/// verifies the engine consumed it. The verdict is recorded in
|
||||
/// `texture-render-health` — a failure turns texture rendering off before the
|
||||
/// first session goes black, a pass clears a stale failure (self-healing
|
||||
/// after a driver/OS fix). Mounted once, in the main window.
|
||||
/// records in `texture-render-health` whether the engine consumed it, so a
|
||||
/// broken environment is detected before the first session goes black.
|
||||
class TextureRenderProbe extends StatefulWidget {
|
||||
const TextureRenderProbe({Key? key}) : super(key: key);
|
||||
|
||||
@@ -31,12 +30,24 @@ class _TextureRenderProbeState extends State<TextureRenderProbe> {
|
||||
Timer? _timer;
|
||||
int _ticks = 0;
|
||||
bool _sawTimings = false;
|
||||
bool _wasEffectiveOn = false;
|
||||
DateTime? _lastTimings;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (_ranThisLaunch || isWeb || !isDesktop) return;
|
||||
_ranThisLaunch = true;
|
||||
if (bind.isIncomingOnly()) return;
|
||||
// An old plugin without the consumed counter cannot be judged, and a
|
||||
// recorded raster-stall means compositing a texture may hang this window.
|
||||
if (!bind.mainTextureRenderProbeSupported()) return;
|
||||
if (bind
|
||||
.mainGetLocalOption(key: kOptionTextureRenderHealth)
|
||||
.startsWith('failed-raster-stall')) {
|
||||
return;
|
||||
}
|
||||
_wasEffectiveOn = bind.mainGetUseTextureRender();
|
||||
// Only probe after the window has really rendered a frame: a hidden
|
||||
// window (silent/tray start) must not record a false failure.
|
||||
SchedulerBinding.instance.addTimingsCallback(_onTimings);
|
||||
@@ -49,9 +60,9 @@ class _TextureRenderProbeState extends State<TextureRenderProbe> {
|
||||
}
|
||||
|
||||
void _onTimings(List<FrameTiming> timings) {
|
||||
_lastTimings = DateTime.now();
|
||||
if (_sawTimings) return;
|
||||
_sawTimings = true;
|
||||
SchedulerBinding.instance.removeTimingsCallback(_onTimings);
|
||||
_start();
|
||||
}
|
||||
|
||||
@@ -75,7 +86,11 @@ class _TextureRenderProbeState extends State<TextureRenderProbe> {
|
||||
if (bind.mainGetTextureProbeConsumed(ptr: _ptr) > 0) {
|
||||
_finish(true);
|
||||
} else if (_ticks >= 10) {
|
||||
_finish(false);
|
||||
// Only a window that is visibly compositing can prove a failure.
|
||||
final timingsFresh = _lastTimings != null &&
|
||||
DateTime.now().difference(_lastTimings!) <
|
||||
const Duration(milliseconds: 1500);
|
||||
_finish(!stateGlobal.isMinimized && timingsFresh ? false : null);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -83,17 +98,22 @@ class _TextureRenderProbeState extends State<TextureRenderProbe> {
|
||||
void _finish(bool? ok) {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
SchedulerBinding.instance.removeTimingsCallback(_onTimings);
|
||||
if (ok != null) {
|
||||
final old = bind.mainGetLocalOption(key: kOptionTextureRenderHealth);
|
||||
if (ok) {
|
||||
if (old != 'ok') {
|
||||
// A 1x1 probe pass disproves the black-texture class, not a
|
||||
// raster-stall under load; that record only clears via the toggle.
|
||||
if (old != 'ok' && !old.startsWith('failed-raster-stall')) {
|
||||
bind.mainSetLocalOption(key: kOptionTextureRenderHealth, value: 'ok');
|
||||
}
|
||||
} else if (!old.startsWith('failed')) {
|
||||
debugPrint('texture render probe failed, disabling texture rendering');
|
||||
bind.mainSetLocalOption(
|
||||
key: kOptionTextureRenderHealth, value: 'failed-probe');
|
||||
showToast(translate('texture-render-fallback-tip'));
|
||||
if (_wasEffectiveOn) {
|
||||
showToast(translate('texture-render-fallback-tip'));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_textureKey != -1) {
|
||||
@@ -111,6 +131,7 @@ class _TextureRenderProbeState extends State<TextureRenderProbe> {
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
SchedulerBinding.instance.removeTimingsCallback(_onTimings);
|
||||
if (_textureKey != -1) {
|
||||
_renderer.closeTexture(_textureKey);
|
||||
_textureKey = -1;
|
||||
@@ -121,8 +142,8 @@ class _TextureRenderProbeState extends State<TextureRenderProbe> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_textureId == -1) return const SizedBox.shrink();
|
||||
// Must actually composite for the engine to sample the texture; 1x1 in a
|
||||
// corner is imperceptible.
|
||||
// Must actually composite for the engine to sample the texture; the
|
||||
// pushed pixel is fully transparent.
|
||||
return IgnorePointer(
|
||||
child: SizedBox(
|
||||
width: 1, height: 1, child: Texture(textureId: _textureId)),
|
||||
|
||||
Reference in New Issue
Block a user