mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-08 05:20:59 +03:00
fix: texture render lifetime protocol, watchdog fallback, startup probe
Root cause of #15848 (and the long-standing macOS #6296 / Linux #3343 class): raw native texture pointers are shared across the platform thread, the engine raster thread and the video thread, with teardown ordered by a 100 ms sleep - or, when moving a tab to a new window, by nothing at all. A lost race frees the texture while it is still in use: the raster thread parks on a destroyed lock (frozen/black view, a never-presented 'transparent' hole, every later session black) and the video thread hangs while holding session locks (app half-dead until restart, still reported as Responding). - unregister textures with compare-and-clear (new session_unregister_* FFI): a late clear can no longer wipe a new window's registration (#8016) and Rust never keeps pushing into a freed texture; the 100 ms sleeps are gone (the plugins now drain in-flight pushes and defer object deletion until the raster thread is done) - guard the async texture create path against destroy racing it (#13596) - per-display locks: the per-frame plugin call no longer holds session-level locks, so a stalled plugin or driver call cannot freeze every window's UI thread - adopt the frame size after 30 consecutive mismatches instead of dropping frames forever (silent black screen on a live connection) - watchdog: frames pushed but never consumed by the engine fall the session back to software rendering live, record texture-render-health, and flip the effective default off; toggling the option clears the record and re-arms validation - Dart raster-stall monitor records a hung raster thread for the next launch (rendering cannot be rescued in-process in that state) - startup probe: render one frame through a 1x1 texture in the main window each launch; failure disables texture rendering before the first session goes black, a pass self-heals a stale failure record Platform defaults are unchanged (macOS off, Win10+ on, Linux on). Requires flutter_texture_rgba_renderer ad4c37e and flutter_gpu_texture_renderer 767bb9f (pinned in pubspec). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -88,6 +88,7 @@ const String kOptionEdgeScrollEdgeThickness = "edge-scroll-edge-thickness";
|
||||
const String kOptionImageQuality = "image_quality";
|
||||
const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs";
|
||||
const String kOptionTextureRender = "use-texture-render";
|
||||
const String kOptionTextureRenderHealth = "texture-render-health";
|
||||
const String kOptionD3DRender = "allow-d3d-render";
|
||||
const String kOptionOpenInTabs = "allow-open-in-tabs";
|
||||
const String kOptionOpenInWindows = "allow-open-in-windows";
|
||||
|
||||
@@ -25,6 +25,7 @@ import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:window_size/window_size.dart' as window_size;
|
||||
import '../widgets/button.dart';
|
||||
import '../widgets/texture_render_probe.dart';
|
||||
|
||||
class DesktopHomePage extends StatefulWidget {
|
||||
const DesktopHomePage({Key? key}) : super(key: key);
|
||||
@@ -60,15 +61,20 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
final isIncomingOnly = bind.isIncomingOnly();
|
||||
return _buildBlock(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
return Stack(
|
||||
children: [
|
||||
buildLeftPane(context),
|
||||
if (!isIncomingOnly) const VerticalDivider(width: 1),
|
||||
if (!isIncomingOnly) Expanded(child: buildRightPane(context)),
|
||||
_buildBlock(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
buildLeftPane(context),
|
||||
if (!isIncomingOnly) const VerticalDivider(width: 1),
|
||||
if (!isIncomingOnly) Expanded(child: buildRightPane(context)),
|
||||
],
|
||||
)),
|
||||
const Positioned(left: 0, top: 0, child: TextureRenderProbe()),
|
||||
],
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBlock({required Widget child}) {
|
||||
|
||||
@@ -156,6 +156,7 @@ class _RemotePageState extends State<RemotePage>
|
||||
widget.tabController?.state.listen(_onMacOSTabStateChanged);
|
||||
}
|
||||
Get.put<FFI>(_ffi, tag: widget.id);
|
||||
_RasterStallMonitor.start();
|
||||
_ffi.imageModel.addCallbackOnFirstImage((String peerId) {
|
||||
_ffi.canvasModel.activateLocalCursor();
|
||||
showKBLayoutTypeChooserIfNeeded(
|
||||
@@ -1402,3 +1403,45 @@ class CursorPaint extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Detects a hung raster thread: the UI keeps scheduling frames but the
|
||||
/// engine never reports completed frame timings. Rendering cannot be rescued
|
||||
/// in-process (software rendering needs the same raster thread), so this only
|
||||
/// records the breakage — the next launch then defaults texture rendering to
|
||||
/// off and the startup probe re-validates the environment.
|
||||
class _RasterStallMonitor {
|
||||
static bool _started = false;
|
||||
static bool _reported = false;
|
||||
static DateTime? _lastTimings;
|
||||
static DateTime? _scheduledSince;
|
||||
|
||||
static void start() {
|
||||
if (_started || isWeb) return;
|
||||
_started = true;
|
||||
SchedulerBinding.instance.addTimingsCallback((_) {
|
||||
_lastTimings = DateTime.now();
|
||||
});
|
||||
Timer.periodic(const Duration(seconds: 2), (_) {
|
||||
if (_reported) return;
|
||||
// A minimized window legitimately stops producing frame timings.
|
||||
if (stateGlobal.isMinimized ||
|
||||
!SchedulerBinding.instance.hasScheduledFrame) {
|
||||
_scheduledSince = null;
|
||||
return;
|
||||
}
|
||||
final now = DateTime.now();
|
||||
_scheduledSince ??= now;
|
||||
final lastTimings = _lastTimings;
|
||||
final stalled =
|
||||
now.difference(_scheduledSince!) > const Duration(seconds: 10) &&
|
||||
(lastTimings == null || lastTimings.isBefore(_scheduledSince!));
|
||||
if (stalled) {
|
||||
_reported = true;
|
||||
bind.mainSetLocalOption(
|
||||
key: kOptionTextureRenderHealth, value: 'failed-raster-stall');
|
||||
debugPrint(
|
||||
'raster thread stall detected, texture rendering disabled for next launch');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
131
flutter/lib/desktop/widgets/texture_render_probe.dart
Normal file
131
flutter/lib/desktop/widgets/texture_render_probe.dart
Normal file
@@ -0,0 +1,131 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
import '../../common.dart';
|
||||
import '../../consts.dart';
|
||||
import '../../models/platform_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.
|
||||
class TextureRenderProbe extends StatefulWidget {
|
||||
const TextureRenderProbe({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<TextureRenderProbe> createState() => _TextureRenderProbeState();
|
||||
}
|
||||
|
||||
class _TextureRenderProbeState extends State<TextureRenderProbe> {
|
||||
static bool _ranThisLaunch = false;
|
||||
final _renderer = TextureRgbaRenderer();
|
||||
int _textureId = -1;
|
||||
int _textureKey = -1;
|
||||
int _ptr = 0;
|
||||
Timer? _timer;
|
||||
int _ticks = 0;
|
||||
bool _sawTimings = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (_ranThisLaunch || isWeb || !isDesktop) return;
|
||||
_ranThisLaunch = true;
|
||||
// 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);
|
||||
Future.delayed(const Duration(seconds: 5), () {
|
||||
if (!_sawTimings) {
|
||||
SchedulerBinding.instance.removeTimingsCallback(_onTimings);
|
||||
_finish(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onTimings(List<FrameTiming> timings) {
|
||||
if (_sawTimings) return;
|
||||
_sawTimings = true;
|
||||
SchedulerBinding.instance.removeTimingsCallback(_onTimings);
|
||||
_start();
|
||||
}
|
||||
|
||||
void _start() async {
|
||||
if (!mounted) return;
|
||||
_textureKey = bind.getNextTextureKey();
|
||||
final id = await _renderer.createTexture(_textureKey);
|
||||
if (!mounted || id == -1) {
|
||||
_finish(!mounted ? null : false);
|
||||
return;
|
||||
}
|
||||
_ptr = await _renderer.getTexturePtr(_textureKey);
|
||||
if (!mounted || _ptr <= 0) {
|
||||
_finish(!mounted ? null : false);
|
||||
return;
|
||||
}
|
||||
setState(() => _textureId = id);
|
||||
_timer = Timer.periodic(const Duration(milliseconds: 100), (_) {
|
||||
_ticks += 1;
|
||||
bind.mainPushTextureProbeFrame(ptr: _ptr);
|
||||
if (bind.mainGetTextureProbeConsumed(ptr: _ptr) > 0) {
|
||||
_finish(true);
|
||||
} else if (_ticks >= 10) {
|
||||
_finish(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _finish(bool? ok) {
|
||||
_timer?.cancel();
|
||||
_timer = null;
|
||||
if (ok != null) {
|
||||
final old = bind.mainGetLocalOption(key: kOptionTextureRenderHealth);
|
||||
if (ok) {
|
||||
if (old != 'ok') {
|
||||
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 (_textureKey != -1) {
|
||||
_renderer.closeTexture(_textureKey);
|
||||
_textureKey = -1;
|
||||
}
|
||||
_ptr = 0;
|
||||
if (mounted && _textureId != -1) {
|
||||
setState(() => _textureId = -1);
|
||||
} else {
|
||||
_textureId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
if (_textureKey != -1) {
|
||||
_renderer.closeTexture(_textureKey);
|
||||
_textureKey = -1;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@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.
|
||||
return IgnorePointer(
|
||||
child: SizedBox(
|
||||
width: 1, height: 1, child: Texture(textureId: _textureId)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ class _PixelbufferTexture {
|
||||
int _display = 0;
|
||||
SessionID? _sessionId;
|
||||
bool _destroying = false;
|
||||
bool _closed = false;
|
||||
int _ptr = 0;
|
||||
int? _id;
|
||||
|
||||
final textureRenderer = TextureRgbaRenderer();
|
||||
@@ -27,11 +29,22 @@ class _PixelbufferTexture {
|
||||
_textureKey = bind.getNextTextureKey();
|
||||
_sessionId = sessionId;
|
||||
|
||||
textureRenderer.createTexture(_textureKey).then((id) async {
|
||||
final textureKey = _textureKey;
|
||||
textureRenderer.createTexture(textureKey).then((id) async {
|
||||
_id = id;
|
||||
if (id != -1) {
|
||||
if (_closed) {
|
||||
// Destroyed while creation was still in flight (rapid
|
||||
// connect/disconnect); nobody else will close this texture.
|
||||
await textureRenderer.closeTexture(textureKey);
|
||||
return;
|
||||
}
|
||||
ffi.textureModel.setRgbaTextureId(display: d, id: id);
|
||||
final ptr = await textureRenderer.getTexturePtr(_textureKey);
|
||||
final ptr = await textureRenderer.getTexturePtr(textureKey);
|
||||
if (_closed) {
|
||||
return;
|
||||
}
|
||||
_ptr = ptr;
|
||||
platformFFI.registerPixelbufferTexture(sessionId, display, ptr);
|
||||
debugPrint(
|
||||
"create pixelbuffer texture: peerId: ${ffi.id} display:$_display, textureId:$id, texturePtr:$ptr");
|
||||
@@ -39,13 +52,17 @@ class _PixelbufferTexture {
|
||||
});
|
||||
}
|
||||
|
||||
destroy(bool unregisterTexture, FFI ffi) async {
|
||||
destroy(bool closeSession, FFI ffi) async {
|
||||
_closed = true;
|
||||
if (!_destroying && _textureKey != -1 && _sessionId != null) {
|
||||
_destroying = true;
|
||||
if (unregisterTexture) {
|
||||
platformFFI.registerPixelbufferTexture(_sessionId!, display, 0);
|
||||
// sleep for a while to avoid the texture is used after it's unregistered.
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
if (_ptr != 0) {
|
||||
// Compare-and-clear: clears only if Rust still holds this pointer, so
|
||||
// a registration a new window has already made stays intact (#8016).
|
||||
// Returning from this synchronous call also guarantees no push
|
||||
// through the old pointer is still in flight.
|
||||
platformFFI.unregisterPixelbufferTexture(_sessionId!, display, _ptr);
|
||||
_ptr = 0;
|
||||
}
|
||||
await textureRenderer.closeTexture(_textureKey);
|
||||
_textureKey = -1;
|
||||
@@ -61,6 +78,7 @@ class _GpuTexture {
|
||||
SessionID? _sessionId;
|
||||
final support = bind.mainHasGpuTextureRender();
|
||||
bool _destroying = false;
|
||||
bool _closed = false;
|
||||
int _display = 0;
|
||||
int? _id;
|
||||
int? _output;
|
||||
@@ -79,9 +97,18 @@ class _GpuTexture {
|
||||
gpuTextureRenderer.registerTexture().then((id) async {
|
||||
_id = id;
|
||||
if (id != null) {
|
||||
if (_closed) {
|
||||
// Destroyed while creation was still in flight (rapid
|
||||
// connect/disconnect); nobody else will unregister this texture.
|
||||
await gpuTextureRenderer.unregisterTexture(id);
|
||||
return;
|
||||
}
|
||||
_textureId = id;
|
||||
ffi.textureModel.setGpuTextureId(display: d, id: id);
|
||||
final output = await gpuTextureRenderer.output(id);
|
||||
if (_closed) {
|
||||
return;
|
||||
}
|
||||
_output = output;
|
||||
if (output != null) {
|
||||
platformFFI.registerGpuTexture(sessionId, d, output);
|
||||
@@ -95,20 +122,22 @@ class _GpuTexture {
|
||||
}
|
||||
}
|
||||
|
||||
destroy(bool unregisterTexture, FFI ffi) async {
|
||||
destroy(bool closeSession, FFI ffi) async {
|
||||
// must stop texture render, render unregistered texture cause crash
|
||||
_closed = true;
|
||||
if (!_destroying && support && _sessionId != null && _textureId != -1) {
|
||||
_destroying = true;
|
||||
if (unregisterTexture) {
|
||||
platformFFI.registerGpuTexture(_sessionId!, _display, 0);
|
||||
// sleep for a while to avoid the texture is used after it's unregistered.
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
final output = _output;
|
||||
if (output != null) {
|
||||
// Compare-and-clear, see _PixelbufferTexture.destroy.
|
||||
platformFFI.unregisterGpuTexture(_sessionId!, _display, output);
|
||||
_output = null;
|
||||
}
|
||||
await gpuTextureRenderer.unregisterTexture(_textureId);
|
||||
_textureId = -1;
|
||||
_destroying = false;
|
||||
debugPrint(
|
||||
"destroy gpu texture: peerId: ${ffi.id} display:$_display, textureId:$_id, output:$_output");
|
||||
"destroy gpu texture: peerId: ${ffi.id} display:$_display, textureId:$_id, output:$output");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -735,6 +735,11 @@ class FfiModel with ChangeNotifier {
|
||||
_handleUseTextureRender(
|
||||
Map<String, dynamic> evt, SessionID sessionId, String peerId) {
|
||||
parent.target?.imageModel.setUseTextureRender(evt['v'] == 'Y');
|
||||
if (evt['reason'] == 'fallback') {
|
||||
// The Rust watchdog detected that pushed frames were never rendered
|
||||
// and switched this session to software rendering.
|
||||
showToast(translate('texture-render-fallback-tip'));
|
||||
}
|
||||
waitForFirstImage.value = true;
|
||||
isRefreshing = true;
|
||||
showConnectedWaitingForImage(parent.target!.dialogManager, sessionId,
|
||||
|
||||
@@ -131,6 +131,12 @@ class PlatformFFI {
|
||||
void registerGpuTexture(SessionID sessionId, int display, int ptr) =>
|
||||
_ffiBind.sessionRegisterGpuTexture(
|
||||
sessionId: sessionId, display: display, ptr: ptr);
|
||||
void unregisterPixelbufferTexture(SessionID sessionId, int display, int ptr) =>
|
||||
_ffiBind.sessionUnregisterPixelbufferTexture(
|
||||
sessionId: sessionId, display: display, ptr: ptr);
|
||||
void unregisterGpuTexture(SessionID sessionId, int display, int ptr) =>
|
||||
_ffiBind.sessionUnregisterGpuTexture(
|
||||
sessionId: sessionId, display: display, ptr: ptr);
|
||||
|
||||
/// Init the FFI class, loads the native Rust core library.
|
||||
Future<void> init(String appType) async {
|
||||
|
||||
@@ -136,6 +136,12 @@ class PlatformFFI {
|
||||
void registerGpuTexture(SessionID sessionId, int display, int ptr) =>
|
||||
_ffiBind.sessionRegisterGpuTexture(
|
||||
sessionId: sessionId, display: display, ptr: ptr);
|
||||
void unregisterPixelbufferTexture(SessionID sessionId, int display, int ptr) =>
|
||||
_ffiBind.sessionUnregisterPixelbufferTexture(
|
||||
sessionId: sessionId, display: display, ptr: ptr);
|
||||
void unregisterGpuTexture(SessionID sessionId, int display, int ptr) =>
|
||||
_ffiBind.sessionUnregisterGpuTexture(
|
||||
sessionId: sessionId, display: display, ptr: ptr);
|
||||
|
||||
Future<void> init(String appType) async {
|
||||
Completer completer = Completer();
|
||||
|
||||
@@ -1450,6 +1450,24 @@ class RustdeskImpl {
|
||||
required int ptr,
|
||||
dynamic hint}) {}
|
||||
|
||||
void sessionUnregisterPixelbufferTexture(
|
||||
{required UuidValue sessionId,
|
||||
required int display,
|
||||
required int ptr,
|
||||
dynamic hint}) {}
|
||||
|
||||
void sessionUnregisterGpuTexture(
|
||||
{required UuidValue sessionId,
|
||||
required int display,
|
||||
required int ptr,
|
||||
dynamic hint}) {}
|
||||
|
||||
void mainPushTextureProbeFrame({required int ptr, dynamic hint}) {}
|
||||
|
||||
int mainGetTextureProbeConsumed({required int ptr, dynamic hint}) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Future<void> queryOnlines({required List<String> ids, dynamic hint}) {
|
||||
return Future(() =>
|
||||
js.context.callMethod('setByName', ['query_onlines', jsonEncode(ids)]));
|
||||
|
||||
@@ -538,8 +538,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "08a471bb8ceccdd50483c81cdfa8b81b07b14b87"
|
||||
resolved-ref: "08a471bb8ceccdd50483c81cdfa8b81b07b14b87"
|
||||
ref: "767bb9fe9dcd2c23e2664114bf33760842e872e7"
|
||||
resolved-ref: "767bb9fe9dcd2c23e2664114bf33760842e872e7"
|
||||
url: "https://github.com/rustdesk-org/flutter_gpu_texture_renderer"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
@@ -1298,8 +1298,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "42797e0f03141dc2b585f76c64a13974508058b4"
|
||||
resolved-ref: "42797e0f03141dc2b585f76c64a13974508058b4"
|
||||
ref: "ad4c37e414ee40853c0e9503b2da6486d4c1768d"
|
||||
resolved-ref: "ad4c37e414ee40853c0e9503b2da6486d4c1768d"
|
||||
url: "https://github.com/rustdesk-org/flutter_texture_rgba_renderer"
|
||||
source: git
|
||||
version: "0.0.16"
|
||||
|
||||
@@ -88,13 +88,13 @@ dependencies:
|
||||
texture_rgba_renderer:
|
||||
git:
|
||||
url: https://github.com/rustdesk-org/flutter_texture_rgba_renderer
|
||||
ref: 42797e0f03141dc2b585f76c64a13974508058b4
|
||||
ref: ad4c37e414ee40853c0e9503b2da6486d4c1768d
|
||||
percent_indicator: ^4.2.2
|
||||
dropdown_button2: ^2.0.0
|
||||
flutter_gpu_texture_renderer:
|
||||
git:
|
||||
url: https://github.com/rustdesk-org/flutter_gpu_texture_renderer
|
||||
ref: 08a471bb8ceccdd50483c81cdfa8b81b07b14b87
|
||||
ref: 767bb9fe9dcd2c23e2664114bf33760842e872e7
|
||||
uuid: ^3.0.7
|
||||
auto_size_text_field: ^2.2.1
|
||||
flex_color_picker: ^3.3.0
|
||||
|
||||
Submodule libs/hbb_common updated: 69cea8dafe...d19ce39e51
453
src/flutter.rs
453
src/flutter.rs
@@ -24,8 +24,9 @@ use std::{
|
||||
str::FromStr,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
Arc, RwLock,
|
||||
Arc, Mutex, RwLock,
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
/// tag "main" for [Desktop Main Page] and [Mobile (Client and Server)] (the mobile don't need multiple windows, only one global event stream is needed)
|
||||
@@ -269,26 +270,96 @@ pub type FlutterGpuTextureRendererPluginCApiSetTexture =
|
||||
#[cfg(feature = "vram")]
|
||||
pub type FlutterGpuTextureRendererPluginCApiGetAdapterLuid = unsafe extern "C" fn() -> i64;
|
||||
|
||||
pub type FlutterRgbaRendererPluginGetConsumed = unsafe extern "C" fn(texture_rgba: *mut c_void) -> u64;
|
||||
|
||||
#[cfg(feature = "vram")]
|
||||
pub type FlutterGpuTextureRendererPluginCApiGetConsumed =
|
||||
unsafe extern "C" fn(output: *mut c_void) -> u64;
|
||||
|
||||
pub(super) type TextureRgbaPtr = usize;
|
||||
|
||||
#[derive(Default)]
|
||||
struct DisplaySessionInfo {
|
||||
// TextureRgba pointer in flutter native.
|
||||
texture_rgba_ptr: TextureRgbaPtr,
|
||||
size: (usize, usize),
|
||||
size_mismatch_count: u32,
|
||||
#[cfg(feature = "vram")]
|
||||
gpu_output_ptr: usize,
|
||||
notify_render_type: Option<RenderType>,
|
||||
// Watchdog: frames pushed to a texture the engine never consumes mean
|
||||
// texture rendering is broken on this machine (black view while the
|
||||
// connection works). Armed until the first consumption is observed, so a
|
||||
// later minimized window cannot false-positive.
|
||||
pushed_count: u64,
|
||||
watchdog_pushed_at_sample: u64,
|
||||
watchdog_last_sample: Option<Instant>,
|
||||
watchdog_stall_windows: u32,
|
||||
watchdog_armed: bool,
|
||||
}
|
||||
|
||||
impl DisplaySessionInfo {
|
||||
fn reset_watchdog(&mut self) {
|
||||
self.pushed_count = 0;
|
||||
self.watchdog_pushed_at_sample = 0;
|
||||
self.watchdog_last_sample = None;
|
||||
self.watchdog_stall_windows = 0;
|
||||
self.watchdog_armed = true;
|
||||
}
|
||||
|
||||
// Returns true when texture rendering is deemed broken: >=3 sampled
|
||||
// seconds in which frames kept being pushed but none was ever consumed.
|
||||
fn check_watchdog(&mut self, consumed: u64) -> bool {
|
||||
if consumed > 0 {
|
||||
self.watchdog_armed = false;
|
||||
return false;
|
||||
}
|
||||
let pushed_in_window = self.pushed_count - self.watchdog_pushed_at_sample;
|
||||
self.watchdog_pushed_at_sample = self.pushed_count;
|
||||
if pushed_in_window >= 10 {
|
||||
self.watchdog_stall_windows += 1;
|
||||
}
|
||||
if self.watchdog_stall_windows >= 3 {
|
||||
self.watchdog_armed = false;
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn watchdog_sample_due(&mut self) -> bool {
|
||||
if !self.watchdog_armed {
|
||||
return false;
|
||||
}
|
||||
let now = Instant::now();
|
||||
match self.watchdog_last_sample {
|
||||
Some(t) if now.duration_since(t) < Duration::from_secs(1) => false,
|
||||
_ => {
|
||||
self.watchdog_last_sample = Some(now);
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Video Texture Renderer in Flutter
|
||||
// Each display entry has its own mutex so the per-frame plugin call only ever
|
||||
// holds that display's lock; a stalled plugin/driver call must not back up
|
||||
// the session-level locks (which would freeze every window's UI thread).
|
||||
#[derive(Clone)]
|
||||
struct VideoRenderer {
|
||||
is_support_multi_ui_session: bool,
|
||||
map_display_sessions: Arc<RwLock<HashMap<usize, DisplaySessionInfo>>>,
|
||||
map_display_sessions: Arc<RwLock<HashMap<usize, Arc<Mutex<DisplaySessionInfo>>>>>,
|
||||
// Latched by the watchdog; consumed once by the pushing caller to trigger
|
||||
// the software-render fallback for this ui session.
|
||||
texture_render_failed: Arc<AtomicBool>,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
on_rgba_func: Option<Symbol<'static, FlutterRgbaRendererPluginOnRgba>>,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
get_consumed_func: Option<Symbol<'static, FlutterRgbaRendererPluginGetConsumed>>,
|
||||
#[cfg(feature = "vram")]
|
||||
on_texture_func: Option<Symbol<'static, FlutterGpuTextureRendererPluginCApiSetTexture>>,
|
||||
#[cfg(feature = "vram")]
|
||||
get_gpu_consumed_func: Option<Symbol<'static, FlutterGpuTextureRendererPluginCApiGetConsumed>>,
|
||||
}
|
||||
|
||||
impl Default for VideoRenderer {
|
||||
@@ -312,6 +383,17 @@ impl Default for VideoRenderer {
|
||||
None
|
||||
}
|
||||
};
|
||||
// Absent in older plugin builds; the watchdog just stays disabled.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let get_consumed_func = match &*TEXTURE_RGBA_RENDERER_PLUGIN {
|
||||
Ok(lib) => unsafe {
|
||||
lib.symbol::<FlutterRgbaRendererPluginGetConsumed>(
|
||||
"FlutterRgbaRendererPluginGetConsumed",
|
||||
)
|
||||
.ok()
|
||||
},
|
||||
Err(_) => None,
|
||||
};
|
||||
#[cfg(feature = "vram")]
|
||||
let on_texture_func = match &*TEXTURE_GPU_RENDERER_PLUGIN {
|
||||
Ok(lib) => {
|
||||
@@ -333,14 +415,29 @@ impl Default for VideoRenderer {
|
||||
None
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "vram")]
|
||||
let get_gpu_consumed_func = match &*TEXTURE_GPU_RENDERER_PLUGIN {
|
||||
Ok(lib) => unsafe {
|
||||
lib.symbol::<FlutterGpuTextureRendererPluginCApiGetConsumed>(
|
||||
"FlutterGpuTextureRendererPluginCApiGetConsumed",
|
||||
)
|
||||
.ok()
|
||||
},
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
Self {
|
||||
map_display_sessions: Default::default(),
|
||||
is_support_multi_ui_session: false,
|
||||
texture_render_failed: Default::default(),
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
on_rgba_func,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
get_consumed_func,
|
||||
#[cfg(feature = "vram")]
|
||||
on_texture_func,
|
||||
#[cfg(feature = "vram")]
|
||||
get_gpu_consumed_func,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,19 +446,18 @@ impl VideoRenderer {
|
||||
#[inline]
|
||||
fn set_size(&mut self, display: usize, width: usize, height: usize) {
|
||||
let mut sessions_lock = self.map_display_sessions.write().unwrap();
|
||||
if let Some(info) = sessions_lock.get_mut(&display) {
|
||||
if let Some(info) = sessions_lock.get(&display) {
|
||||
let mut info = info.lock().unwrap();
|
||||
info.size = (width, height);
|
||||
info.size_mismatch_count = 0;
|
||||
info.notify_render_type = None;
|
||||
} else {
|
||||
sessions_lock.insert(
|
||||
display,
|
||||
DisplaySessionInfo {
|
||||
texture_rgba_ptr: usize::default(),
|
||||
Arc::new(Mutex::new(DisplaySessionInfo {
|
||||
size: (width, height),
|
||||
#[cfg(feature = "vram")]
|
||||
gpu_output_ptr: usize::default(),
|
||||
notify_render_type: None,
|
||||
},
|
||||
..Default::default()
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -369,7 +465,8 @@ impl VideoRenderer {
|
||||
fn register_pixelbuffer_texture(&self, display: usize, ptr: usize) {
|
||||
let mut sessions_lock = self.map_display_sessions.write().unwrap();
|
||||
if ptr == 0 {
|
||||
if let Some(info) = sessions_lock.get_mut(&display) {
|
||||
if let Some(info_arc) = sessions_lock.get(&display).cloned() {
|
||||
let mut info = info_arc.lock().unwrap();
|
||||
if info.texture_rgba_ptr != usize::default() {
|
||||
info.texture_rgba_ptr = usize::default();
|
||||
}
|
||||
@@ -377,10 +474,12 @@ impl VideoRenderer {
|
||||
if info.gpu_output_ptr != usize::default() {
|
||||
return;
|
||||
}
|
||||
drop(info);
|
||||
sessions_lock.remove(&display);
|
||||
}
|
||||
sessions_lock.remove(&display);
|
||||
} else {
|
||||
if let Some(info) = sessions_lock.get_mut(&display) {
|
||||
if let Some(info) = sessions_lock.get(&display) {
|
||||
let mut info = info.lock().unwrap();
|
||||
if info.texture_rgba_ptr != usize::default()
|
||||
&& info.texture_rgba_ptr != ptr as TextureRgbaPtr
|
||||
{
|
||||
@@ -392,38 +491,61 @@ impl VideoRenderer {
|
||||
}
|
||||
info.texture_rgba_ptr = ptr as _;
|
||||
info.notify_render_type = None;
|
||||
info.reset_watchdog();
|
||||
} else {
|
||||
if ptr != 0 {
|
||||
sessions_lock.insert(
|
||||
display,
|
||||
DisplaySessionInfo {
|
||||
texture_rgba_ptr: ptr as _,
|
||||
size: (0, 0),
|
||||
#[cfg(feature = "vram")]
|
||||
gpu_output_ptr: usize::default(),
|
||||
notify_render_type: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
let mut info = DisplaySessionInfo {
|
||||
texture_rgba_ptr: ptr as _,
|
||||
..Default::default()
|
||||
};
|
||||
info.reset_watchdog();
|
||||
sessions_lock.insert(display, Arc::new(Mutex::new(info)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the pointer only if it still holds `ptr`. An unconditional clear
|
||||
// could wipe out the registration a new window just made while a tab
|
||||
// moves between windows (#8016); skipping the clear (the old behavior on
|
||||
// move) left Rust pushing into a freed native texture. Waiting on the
|
||||
// display mutex also drains an in-flight push through the old pointer.
|
||||
fn unregister_pixelbuffer_texture(&self, display: usize, ptr: usize) {
|
||||
if ptr == 0 {
|
||||
return;
|
||||
}
|
||||
let mut sessions_lock = self.map_display_sessions.write().unwrap();
|
||||
if let Some(info_arc) = sessions_lock.get(&display).cloned() {
|
||||
let mut info = info_arc.lock().unwrap();
|
||||
if info.texture_rgba_ptr != ptr as TextureRgbaPtr {
|
||||
return;
|
||||
}
|
||||
info.texture_rgba_ptr = usize::default();
|
||||
#[cfg(feature = "vram")]
|
||||
if info.gpu_output_ptr != usize::default() {
|
||||
return;
|
||||
}
|
||||
drop(info);
|
||||
sessions_lock.remove(&display);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "vram")]
|
||||
pub fn register_gpu_output(&self, display: usize, ptr: usize) {
|
||||
let mut sessions_lock = self.map_display_sessions.write().unwrap();
|
||||
if ptr == 0 {
|
||||
if let Some(info) = sessions_lock.get_mut(&display) {
|
||||
if let Some(info_arc) = sessions_lock.get(&display).cloned() {
|
||||
let mut info = info_arc.lock().unwrap();
|
||||
if info.gpu_output_ptr != usize::default() {
|
||||
info.gpu_output_ptr = usize::default();
|
||||
}
|
||||
if info.texture_rgba_ptr != usize::default() {
|
||||
return;
|
||||
}
|
||||
drop(info);
|
||||
sessions_lock.remove(&display);
|
||||
}
|
||||
sessions_lock.remove(&display);
|
||||
} else {
|
||||
if let Some(info) = sessions_lock.get_mut(&display) {
|
||||
if let Some(info) = sessions_lock.get(&display) {
|
||||
let mut info = info.lock().unwrap();
|
||||
if info.gpu_output_ptr != usize::default() && info.gpu_output_ptr != ptr {
|
||||
log::error!(
|
||||
"gpu_output_ptr is not null and not equal to ptr, relace {} to {}",
|
||||
@@ -433,50 +555,90 @@ impl VideoRenderer {
|
||||
}
|
||||
info.gpu_output_ptr = ptr as _;
|
||||
info.notify_render_type = None;
|
||||
info.reset_watchdog();
|
||||
} else {
|
||||
if ptr != usize::default() {
|
||||
sessions_lock.insert(
|
||||
display,
|
||||
DisplaySessionInfo {
|
||||
texture_rgba_ptr: usize::default(),
|
||||
size: (0, 0),
|
||||
gpu_output_ptr: ptr,
|
||||
notify_render_type: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
let mut info = DisplaySessionInfo {
|
||||
gpu_output_ptr: ptr,
|
||||
..Default::default()
|
||||
};
|
||||
info.reset_watchdog();
|
||||
sessions_lock.insert(display, Arc::new(Mutex::new(info)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See unregister_pixelbuffer_texture for why this is compare-and-clear.
|
||||
#[cfg(feature = "vram")]
|
||||
pub fn unregister_gpu_output(&self, display: usize, ptr: usize) {
|
||||
if ptr == 0 {
|
||||
return;
|
||||
}
|
||||
let mut sessions_lock = self.map_display_sessions.write().unwrap();
|
||||
if let Some(info_arc) = sessions_lock.get(&display).cloned() {
|
||||
let mut info = info_arc.lock().unwrap();
|
||||
if info.gpu_output_ptr != ptr {
|
||||
return;
|
||||
}
|
||||
info.gpu_output_ptr = usize::default();
|
||||
if info.texture_rgba_ptr != usize::default() {
|
||||
return;
|
||||
}
|
||||
drop(info);
|
||||
sessions_lock.remove(&display);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn display_session_info(&self, display: usize) -> Option<Arc<Mutex<DisplaySessionInfo>>> {
|
||||
let read_lock = self.map_display_sessions.read().unwrap();
|
||||
if !self.is_support_multi_ui_session {
|
||||
read_lock.values().next().cloned()
|
||||
} else {
|
||||
read_lock.get(&display).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn on_rgba(&self, display: usize, rgba: &scrap::ImageRgb) -> bool {
|
||||
let mut write_lock = self.map_display_sessions.write().unwrap();
|
||||
let opt_info = if !self.is_support_multi_ui_session {
|
||||
write_lock.values_mut().next()
|
||||
} else {
|
||||
write_lock.get_mut(&display)
|
||||
};
|
||||
let Some(info) = opt_info else {
|
||||
let Some(info_arc) = self.display_session_info(display) else {
|
||||
return false;
|
||||
};
|
||||
let mut info = info_arc.lock().unwrap();
|
||||
if info.texture_rgba_ptr == usize::default() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if info.size.0 != rgba.w || info.size.1 != rgba.h {
|
||||
log::error!(
|
||||
"width/height mismatch: ({},{}) != ({},{})",
|
||||
info.size.0,
|
||||
info.size.1,
|
||||
rgba.w,
|
||||
rgba.h
|
||||
);
|
||||
// Peer info's handling is async and may be late than video frame's handling
|
||||
// Allow peer info not set, but not allow wrong width/height for correct local cursor position
|
||||
if info.size != (0, 0) {
|
||||
return false;
|
||||
info.size_mismatch_count += 1;
|
||||
if info.size_mismatch_count == 1 {
|
||||
log::error!(
|
||||
"width/height mismatch: ({},{}) != ({},{})",
|
||||
info.size.0,
|
||||
info.size.1,
|
||||
rgba.w,
|
||||
rgba.h
|
||||
);
|
||||
}
|
||||
// The stream is the source of truth; if the sizes still
|
||||
// disagree after this many frames the peer info is not
|
||||
// coming, and dropping forever leaves a live session black.
|
||||
if info.size_mismatch_count < 30 {
|
||||
return false;
|
||||
}
|
||||
log::warn!(
|
||||
"adopting frame size ({},{}) after {} mismatched frames",
|
||||
rgba.w,
|
||||
rgba.h,
|
||||
info.size_mismatch_count
|
||||
);
|
||||
info.size = (rgba.w, rgba.h);
|
||||
info.size_mismatch_count = 0;
|
||||
}
|
||||
} else {
|
||||
info.size_mismatch_count = 0;
|
||||
}
|
||||
if let Some(func) = &self.on_rgba_func {
|
||||
unsafe {
|
||||
@@ -490,6 +652,20 @@ impl VideoRenderer {
|
||||
)
|
||||
};
|
||||
}
|
||||
info.pushed_count += 1;
|
||||
if let Some(get_consumed) = &self.get_consumed_func {
|
||||
if info.watchdog_sample_due() {
|
||||
let consumed = unsafe { get_consumed(info.texture_rgba_ptr as _) };
|
||||
if info.check_watchdog(consumed) {
|
||||
log::error!(
|
||||
"texture rendering broken: {} frames pushed to display {}, none consumed",
|
||||
info.pushed_count,
|
||||
display
|
||||
);
|
||||
self.texture_render_failed.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
}
|
||||
if info.notify_render_type != Some(RenderType::PixelBuffer) {
|
||||
info.notify_render_type = Some(RenderType::PixelBuffer);
|
||||
true
|
||||
@@ -500,21 +676,30 @@ impl VideoRenderer {
|
||||
|
||||
#[cfg(feature = "vram")]
|
||||
pub fn on_texture(&self, display: usize, texture: *mut c_void) -> bool {
|
||||
let mut write_lock = self.map_display_sessions.write().unwrap();
|
||||
let opt_info = if !self.is_support_multi_ui_session {
|
||||
write_lock.values_mut().next()
|
||||
} else {
|
||||
write_lock.get_mut(&display)
|
||||
};
|
||||
let Some(info) = opt_info else {
|
||||
let Some(info_arc) = self.display_session_info(display) else {
|
||||
return false;
|
||||
};
|
||||
let mut info = info_arc.lock().unwrap();
|
||||
if info.gpu_output_ptr == usize::default() {
|
||||
return false;
|
||||
}
|
||||
if let Some(func) = &self.on_texture_func {
|
||||
unsafe { func(info.gpu_output_ptr as _, texture) };
|
||||
}
|
||||
info.pushed_count += 1;
|
||||
if let Some(get_consumed) = &self.get_gpu_consumed_func {
|
||||
if info.watchdog_sample_due() {
|
||||
let consumed = unsafe { get_consumed(info.gpu_output_ptr as _) };
|
||||
if info.check_watchdog(consumed) {
|
||||
log::error!(
|
||||
"gpu texture rendering broken: {} frames pushed to display {}, none consumed",
|
||||
info.pushed_count,
|
||||
display
|
||||
);
|
||||
self.texture_render_failed.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
}
|
||||
if info.notify_render_type != Some(RenderType::Texture) {
|
||||
info.notify_render_type = Some(RenderType::Texture);
|
||||
true
|
||||
@@ -524,11 +709,10 @@ impl VideoRenderer {
|
||||
}
|
||||
|
||||
pub fn reset_all_display_render_type(&self) {
|
||||
let mut write_lock = self.map_display_sessions.write().unwrap();
|
||||
write_lock
|
||||
.values_mut()
|
||||
.map(|v| v.notify_render_type = None)
|
||||
.count();
|
||||
let read_lock = self.map_display_sessions.read().unwrap();
|
||||
for info in read_lock.values() {
|
||||
info.lock().unwrap().notify_render_type = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,9 +845,24 @@ impl FlutterHandler {
|
||||
}
|
||||
|
||||
pub fn update_use_texture_render(&self) {
|
||||
self.use_texture_render
|
||||
.store(crate::ui_interface::use_texture_render(), Ordering::Relaxed);
|
||||
let v = crate::ui_interface::use_texture_render();
|
||||
self.use_texture_render.store(v, Ordering::Relaxed);
|
||||
self.display_rgbas.write().unwrap().clear();
|
||||
if v {
|
||||
// Texture render was (re-)enabled; validate it afresh so a still
|
||||
// broken environment fails over again instead of staying black.
|
||||
for (_, session) in self.session_handlers.read().unwrap().iter() {
|
||||
for info in session
|
||||
.renderer
|
||||
.map_display_sessions
|
||||
.read()
|
||||
.unwrap()
|
||||
.values()
|
||||
{
|
||||
info.lock().unwrap().reset_watchdog();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -887,12 +1086,13 @@ impl InvokeUiSession for FlutterHandler {
|
||||
if !self.use_texture_render.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
for (_, session) in self.session_handlers.read().unwrap().iter() {
|
||||
for (session_id, session) in self.session_handlers.read().unwrap().iter() {
|
||||
if session.renderer.on_texture(display, texture) {
|
||||
if let Some(stream) = &session.event_stream {
|
||||
stream.add(EventToUI::Texture(display, true));
|
||||
}
|
||||
}
|
||||
Self::check_texture_render_failed(session_id, session);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1262,16 +1462,31 @@ impl FlutterHandler {
|
||||
display: usize,
|
||||
rgba: &mut scrap::ImageRgb,
|
||||
) {
|
||||
for (_, session) in self.session_handlers.read().unwrap().iter() {
|
||||
for (session_id, session) in self.session_handlers.read().unwrap().iter() {
|
||||
if use_texture_render || session.displays.len() > 1 {
|
||||
if session.renderer.on_rgba(display, rgba) {
|
||||
if let Some(stream) = &session.event_stream {
|
||||
stream.add(EventToUI::Texture(display, false));
|
||||
}
|
||||
}
|
||||
Self::check_texture_render_failed(session_id, session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Consume the watchdog latch outside the per-frame hot path work; the
|
||||
// actual fallback (config write, decoder reset) runs on its own thread.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn check_texture_render_failed(session_id: &SessionID, session: &SessionHandler) {
|
||||
if session
|
||||
.renderer
|
||||
.texture_render_failed
|
||||
.swap(false, Ordering::SeqCst)
|
||||
{
|
||||
let session_id = session_id.clone();
|
||||
std::thread::spawn(move || on_texture_render_failed(session_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function is only used for the default connection session.
|
||||
@@ -1795,6 +2010,106 @@ pub fn session_register_gpu_texture(_session_id: SessionID, _display: usize, _ou
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn session_unregister_pixelbuffer_texture(session_id: SessionID, display: usize, ptr: usize) {
|
||||
for s in sessions::get_sessions() {
|
||||
if let Some(h) = s
|
||||
.ui_handler
|
||||
.session_handlers
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&session_id)
|
||||
{
|
||||
h.renderer.unregister_pixelbuffer_texture(display, ptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn session_unregister_gpu_texture(_session_id: SessionID, _display: usize, _output_ptr: usize) {
|
||||
#[cfg(feature = "vram")]
|
||||
for s in sessions::get_sessions() {
|
||||
if let Some(h) = s
|
||||
.ui_handler
|
||||
.session_handlers
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&_session_id)
|
||||
{
|
||||
h.renderer.unregister_gpu_output(_display, _output_ptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Startup-probe plumbing: the main window pushes one frame into a throwaway
|
||||
// 1x1 texture and polls whether the engine consumed it, validating the
|
||||
// texture pipeline before any session depends on it.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn push_texture_probe_frame(ptr: usize) {
|
||||
if ptr == 0 {
|
||||
return;
|
||||
}
|
||||
let Ok(lib) = &*TEXTURE_RGBA_RENDERER_PLUGIN else {
|
||||
return;
|
||||
};
|
||||
let Ok(func) = (unsafe {
|
||||
lib.symbol::<FlutterRgbaRendererPluginOnRgba>("FlutterRgbaRendererPluginOnRgba")
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
let frame: [u8; 4] = [255, 255, 255, 255];
|
||||
unsafe { func(ptr as _, frame.as_ptr(), 4, 1, 1, 1) };
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn get_texture_probe_consumed(ptr: usize) -> u64 {
|
||||
if ptr == 0 {
|
||||
return 0;
|
||||
}
|
||||
let Ok(lib) = &*TEXTURE_RGBA_RENDERER_PLUGIN else {
|
||||
return 0;
|
||||
};
|
||||
let Ok(func) = (unsafe {
|
||||
lib.symbol::<FlutterRgbaRendererPluginGetConsumed>("FlutterRgbaRendererPluginGetConsumed")
|
||||
}) else {
|
||||
return 0;
|
||||
};
|
||||
unsafe { func(ptr as _) }
|
||||
}
|
||||
|
||||
// Frames are being pushed but the engine never consumes them: texture
|
||||
// rendering does not work in this environment (GPU/driver/engine breakage).
|
||||
// Fall back to software rendering for the live session and record it so the
|
||||
// default flips to opt-in; the startup probe re-validates on later launches.
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
fn on_texture_render_failed(session_id: SessionID) {
|
||||
log::error!(
|
||||
"texture rendering failed for session {}, falling back to software rendering",
|
||||
session_id
|
||||
);
|
||||
LocalConfig::set_option(
|
||||
hbb_common::config::keys::OPTION_TEXTURE_RENDER_HEALTH.to_owned(),
|
||||
format!(
|
||||
"failed-watchdog@{}",
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0)
|
||||
),
|
||||
);
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.push_event(
|
||||
"use_texture_render",
|
||||
&[("v", "N"), ("reason", "fallback")],
|
||||
&[],
|
||||
);
|
||||
session.use_texture_render_changed();
|
||||
session.ui_handler.update_use_texture_render();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(feature = "vram"))]
|
||||
pub fn get_adapter_luid() -> Option<i64> {
|
||||
|
||||
@@ -1227,6 +1227,13 @@ pub fn main_set_local_option(key: String, value: String) {
|
||||
let is_render_target =
|
||||
|session: &crate::flutter::FlutterSession| session.is_default() || session.is_view_camera();
|
||||
if is_texture_render_key {
|
||||
// An explicit user toggle gives texture rendering a fresh chance; a
|
||||
// stale failure record must not override it (the watchdog re-records
|
||||
// if the environment is still broken).
|
||||
set_local_option(
|
||||
config::keys::OPTION_TEXTURE_RENDER_HEALTH.to_owned(),
|
||||
"".to_owned(),
|
||||
);
|
||||
let session_event = [("v", &value)];
|
||||
for session in sessions::get_sessions() {
|
||||
if !is_render_target(&session) {
|
||||
@@ -2295,6 +2302,39 @@ pub fn session_register_gpu_texture(
|
||||
))
|
||||
}
|
||||
|
||||
pub fn session_unregister_pixelbuffer_texture(
|
||||
session_id: SessionID,
|
||||
display: usize,
|
||||
ptr: usize,
|
||||
) -> SyncReturn<()> {
|
||||
SyncReturn(super::flutter::session_unregister_pixelbuffer_texture(
|
||||
session_id, display, ptr,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn session_unregister_gpu_texture(
|
||||
session_id: SessionID,
|
||||
display: usize,
|
||||
ptr: usize,
|
||||
) -> SyncReturn<()> {
|
||||
SyncReturn(super::flutter::session_unregister_gpu_texture(
|
||||
session_id, display, ptr,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn main_push_texture_probe_frame(ptr: usize) -> SyncReturn<()> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
super::flutter::push_texture_probe_frame(ptr);
|
||||
SyncReturn(())
|
||||
}
|
||||
|
||||
pub fn main_get_texture_probe_consumed(ptr: usize) -> SyncReturn<u64> {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
return SyncReturn(super::flutter::get_texture_probe_consumed(ptr));
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
SyncReturn(0)
|
||||
}
|
||||
|
||||
pub fn query_onlines(ids: Vec<String>) {
|
||||
let _ = flutter::async_tasks::query_onlines(ids);
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "يتم دعم صيغة CIDR، مثال: 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Падтрымліваецца натацыя CIDR, напрыклад: 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Поддържа се CIDR нотация, например: 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "S'admet la notació CIDR, per exemple 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "支持 CIDR 写法,例如 192.168.1.0/24"),
|
||||
("Continue", "继续"),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", "纹理渲染失效,已自动切换为软件渲染。"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Je podporován zápis CIDR, například 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "CIDR-notation understøttes, f.eks. 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Die CIDR-Notation wird unterstützt, z. B. 192.168.1.0/24"),
|
||||
("Continue", "Weiter"),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Υποστηρίζεται η σημειογραφία CIDR, π.χ. 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -285,5 +285,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("id_whitelist_caveat_tip", "The ID is reported by the connecting client. This whitelist reduces exposure and does not replace the password or 2FA."),
|
||||
("whitelist_cidr_tip", "CIDR notation is supported, e.g. 192.168.1.0/24"),
|
||||
("Your ip is blocked by the peer", "Your IP is blocked by the peer"),
|
||||
("texture-render-fallback-tip", "Texture rendering failed and was disabled. Using software rendering instead."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "La notacio CIDR estas subtenata, ekzemple 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Se admite la notación CIDR, por ejemplo 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Toetatud on CIDR-tähistus, näiteks 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "CIDR notazioa onartzen da, adibidez 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "نماد CIDR پشتیبانی می شود، برای مثال 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "CIDR-merkintä on tuettu, esimerkiksi 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "La notation CIDR est prise en charge, par exemple 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "მხარდაჭერილია CIDR ჩანაწერი, მაგალითად 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "CIDR નોટેશન સપોર્ટેડ છે, ઉदાહરણ તરીકે 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "יש תמיכה בסימון CIDR, לדוגמה 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "CIDR नोटेशन समर्थित है, उदाहरण के लिए 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Podržan je CIDR zapis, primjerice 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "A CIDR jelölés támogatott, például 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Notasi CIDR didukung, misalnya 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "È supportata la notazione CIDR, ad esempio 192.168.1.0/24"),
|
||||
("Continue", "Continua"),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "CIDR 表記に対応しています。例: 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "CIDR 표기를 지원합니다. 예: 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "CIDR жазбасына қолдау көрсетіледі, мысалы 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Palaikomas CIDR žymėjimas, pavyzdžiui 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Tiek atbalstīts CIDR pieraksts, piemēram 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "CIDR നൊട്ടേഷൻ പിന്തുണയ്ക്കുന്നു, ഉദാഹരണത്തിന് 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "CIDR-notasjon støttes, for eksempel 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "CIDR-notatie wordt ondersteund, bijv. 192.168.1.0/24"),
|
||||
("Continue", "Doorgaan"),
|
||||
("Browser didn't open? Use the url below to sign in.", "Is de browser niet geopend? Gebruik onderstaande URL om in te loggen."),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Obsługiwana jest notacja CIDR, na przykład 192.168.1.0/24"),
|
||||
("Continue", "Kontynuuj"),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "A notação CIDR é suportada, por exemplo 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "A notação CIDR é suportada, por exemplo 192.168.1.0/24"),
|
||||
("Continue", "Continuar"),
|
||||
("Browser didn't open? Use the url below to sign in.", "O navegador não foi aberto? Use a URL abaixo para fazer login."),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Notația CIDR este acceptată, de exemplu 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Поддерживается нотация CIDR, например 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Sa notatzione CIDR est suportada, pro esempru 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Je podporovaný zápis CIDR, napríklad 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Podprt je zapis CIDR, na primer 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Mbështetet shënimi CIDR, për shembull 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Podržan je CIDR zapis, na primer 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "CIDR-notation stöds, till exempel 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "CIDR குறியீடு ஆதரிக்கப்படுகிறது, எடுத்துக்காட்டாக 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", ""),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "รองรับรูปแบบ CIDR เช่น 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "CIDR gösterimi desteklenir, örneğin 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "支援 CIDR 寫法,例如 192.168.1.0/24"),
|
||||
("Continue", "繼續"),
|
||||
("Browser didn't open? Use the url below to sign in.", "瀏覽器未開啟?請使用下方網址登入。"),
|
||||
("texture-render-fallback-tip", "紋理渲染失效,已自動切換為軟體渲染。"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Підтримується нотація CIDR, наприклад 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -775,5 +775,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("whitelist_cidr_tip", "Hỗ trợ ký hiệu CIDR, ví dụ 192.168.1.0/24"),
|
||||
("Continue", ""),
|
||||
("Browser didn't open? Use the url below to sign in.", ""),
|
||||
("texture-render-fallback-tip", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -176,6 +176,16 @@ pub fn get_option<T: AsRef<str>>(key: T) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// The watchdog/startup probe records texture-render breakage (frames pushed
|
||||
// but never consumed by the engine). A failed record forces texture render
|
||||
// off on every desktop platform — even an explicit "Y" — because the live
|
||||
// fallback relies on it; toggling the option (or a passing probe) clears it.
|
||||
#[inline]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub fn texture_render_health_failed() -> bool {
|
||||
LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER_HEALTH).starts_with("failed")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn use_texture_render() -> bool {
|
||||
#[cfg(target_os = "android")]
|
||||
@@ -183,28 +193,33 @@ pub fn use_texture_render() -> bool {
|
||||
#[cfg(target_os = "ios")]
|
||||
return false;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
return cfg!(feature = "flutter")
|
||||
&& LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) == "Y";
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
return cfg!(feature = "flutter")
|
||||
&& LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) != "N";
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
if !cfg!(feature = "flutter") {
|
||||
return false;
|
||||
}
|
||||
// https://learn.microsoft.com/en-us/windows/win32/sysinfo/targeting-your-application-at-windows-8-1
|
||||
#[cfg(debug_assertions)]
|
||||
let default_texture = true;
|
||||
#[cfg(not(debug_assertions))]
|
||||
let default_texture = crate::platform::is_win_10_or_greater();
|
||||
if default_texture {
|
||||
LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) != "N"
|
||||
} else {
|
||||
return LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) == "Y";
|
||||
if texture_render_health_failed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
return LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) == "Y";
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
return LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) != "N";
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// https://learn.microsoft.com/en-us/windows/win32/sysinfo/targeting-your-application-at-windows-8-1
|
||||
#[cfg(debug_assertions)]
|
||||
let default_texture = true;
|
||||
#[cfg(not(debug_assertions))]
|
||||
let default_texture = crate::platform::is_win_10_or_greater();
|
||||
if default_texture {
|
||||
LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) != "N"
|
||||
} else {
|
||||
return LocalConfig::get_option(config::keys::OPTION_TEXTURE_RENDER) == "Y";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user