refact: texture render as an option (#8168)

* refact: texture render as an option

Signed-off-by: fufesou <linlong1266@gmail.com>

* refact: texture render, translation

Signed-off-by: fufesou <linlong1266@gmail.com>

* refact: texture render as option

Signed-off-by: fufesou <linlong1266@gmail.com>

* Update ui_interface.rs

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
This commit is contained in:
fufesou
2024-05-28 16:42:30 +08:00
committed by GitHub
parent 010b17509a
commit 72ec86b58d
66 changed files with 481 additions and 282 deletions

View File

@@ -12,7 +12,6 @@ import 'package:flutter_hbb/common/formatter/id_formatter.dart';
import 'package:flutter_hbb/desktop/widgets/refresh_wrapper.dart';
import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
import 'package:flutter_hbb/main.dart';
import 'package:flutter_hbb/models/desktop_render_texture.dart';
import 'package:flutter_hbb/models/peer_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
@@ -2799,11 +2798,6 @@ sessionRefreshVideo(SessionID sessionId, PeerInfo pi) async {
}
}
bool isChooseDisplayToOpenInNewWindow(PeerInfo pi, SessionID sessionId) =>
pi.isSupportMultiDisplay &&
useTextureRender &&
bind.sessionGetDisplaysAsIndividualWindows(sessionId: sessionId) == 'Y';
Future<List<Rect>> getScreenListWayland() async {
final screenRectList = <Rect>[];
if (isMainDesktopWindow) {

View File

@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/models/desktop_render_texture.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:get/get.dart';
@@ -227,8 +226,7 @@ List<(String, String)> otherDefaultSettings() {
if ((isDesktop || isWebDesktop)) ('Zoom cursor', kOptionZoomCursor),
('Show quality monitor', kOptionShowQualityMonitor),
('Mute', kOptionDisableAudio),
if (isDesktop)
('Enable file copy and paste', kOptionEnableFileCopyPaste),
if (isDesktop) ('Enable file copy and paste', kOptionEnableFileCopyPaste),
('Disable clipboard', kOptionDisableClipboard),
('Lock after session end', kOptionLockAfterSessionEnd),
('Privacy mode', kOptionPrivacyMode),
@@ -236,12 +234,12 @@ List<(String, String)> otherDefaultSettings() {
('True color (4:4:4)', kOptionI444),
('Reverse mouse wheel', kKeyReverseMouseWheel),
('swap-left-right-mouse', kOptionSwapLeftRightMouse),
if (isDesktop && useTextureRender)
if (isDesktop && bind.mainGetUseTextureRender())
(
'Show displays as individual windows',
kKeyShowDisplaysAsIndividualWindows
),
if (isDesktop && useTextureRender)
if (isDesktop && bind.mainGetUseTextureRender())
(
'Use all my displays for the remote session',
kKeyUseAllMyDisplaysForTheRemoteSession

View File

@@ -8,7 +8,6 @@ import 'package:flutter_hbb/common/widgets/dialog.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/models/model.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/desktop_render_texture.dart';
import 'package:get/get.dart';
bool isEditOsPassword = false;
@@ -581,7 +580,7 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
child: Text(translate('Lock after session end'))));
}
if (useTextureRender &&
if (bind.mainGetUseTextureRender() &&
pi.isSupportMultiDisplay &&
PrivacyModeState.find(id).isEmpty &&
pi.displaysCount.value > 1 &&
@@ -600,7 +599,9 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
}
final isMultiScreens = !isWeb && (await getScreenRectList()).length > 1;
if (useTextureRender && pi.isSupportMultiDisplay && isMultiScreens) {
if (bind.mainGetUseTextureRender() &&
pi.isSupportMultiDisplay &&
isMultiScreens) {
final value = bind.sessionGetUseAllMyDisplaysForTheRemoteSession(
sessionId: ffi.sessionId) ==
'Y';

View File

@@ -73,6 +73,7 @@ const String kOptionViewStyle = "view_style";
const String kOptionScrollStyle = "scroll_style";
const String kOptionImageQuality = "image_quality";
const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs";
const String kOptionTextureRender = "use-texture-render";
const String kOptionOpenInTabs = "allow-open-in-tabs";
const String kOptionOpenInWindows = "allow-open-in-windows";
const String kOptionForceAlwaysRelay = "force-always-relay";
@@ -153,6 +154,8 @@ const String kKeyUseAllMyDisplaysForTheRemoteSession =
const String kKeyShowMonitorsToolbar = 'show_monitors_toolbar';
const String kKeyReverseMouseWheel = "reverse_mouse_wheel";
const String kMsgboxTextWaitingForImage = 'Connected, waiting for image...';
// the executable name of the portable version
const String kEnvPortableExecutable = "RUSTDESK_APPNAME";

View File

@@ -387,10 +387,25 @@ class _GeneralState extends State<_General> {
isServer: false,
),
// though this is related to GUI, but opengl problem affects all users, so put in config rather than local
if (isLinux)
Tooltip(
message: translate('software_render_tip'),
child: _OptionCheckBox(
context,
"Always use software rendering",
kOptionAllowAlwaysSoftwareRender,
),
),
Tooltip(
message: translate('software_render_tip'),
child: _OptionCheckBox(context, "Always use software rendering",
kOptionAllowAlwaysSoftwareRender),
message: translate('texture_render_tip'),
child: _OptionCheckBox(
context,
"Use texture rendering",
kOptionTextureRender,
optGetter: bind.mainGetUseTextureRender,
optSetter: (k, v) async =>
await bind.mainSetLocalOption(key: k, value: v ? 'Y' : 'N'),
),
),
if (!bind.isCustomClient())
_OptionCheckBox(
@@ -426,7 +441,7 @@ class _GeneralState extends State<_General> {
context,
'Remove wallpaper during incoming sessions',
kOptionAllowRemoveWallpaper,
update: () {
update: (bool v) {
setState(() {});
},
),
@@ -457,8 +472,8 @@ class _GeneralState extends State<_General> {
context,
'Enable hardware codec',
kOptionEnableHwcodec,
update: () {
if (mainGetBoolOptionSync(kOptionEnableHwcodec)) {
update: (bool v) {
if (v) {
bind.mainCheckHwcodec();
}
},
@@ -941,7 +956,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
List<Widget> directIp(BuildContext context) {
TextEditingController controller = TextEditingController();
update() => setState(() {});
update(bool v) => setState(() {});
RxBool applyEnabled = false.obs;
return [
_OptionCheckBox(context, 'Enable direct IP access', kOptionDirectServer,
@@ -1102,7 +1117,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
List<Widget> autoDisconnect(BuildContext context) {
TextEditingController controller = TextEditingController();
update() => setState(() {});
update(bool v) => setState(() {});
RxBool applyEnabled = false.obs;
return [
_OptionCheckBox(
@@ -1803,33 +1818,41 @@ Widget _Card(
}
// ignore: non_constant_identifier_names
Widget _OptionCheckBox(BuildContext context, String label, String key,
{Function()? update,
bool reverse = false,
bool enabled = true,
Icon? checkedIcon,
bool? fakeValue,
bool isServer = true}) {
bool value =
isServer ? mainGetBoolOptionSync(key) : mainGetLocalBoolOptionSync(key);
Widget _OptionCheckBox(
BuildContext context,
String label,
String key, {
Function(bool)? update,
bool reverse = false,
bool enabled = true,
Icon? checkedIcon,
bool? fakeValue,
bool isServer = true,
bool Function()? optGetter,
Future<void> Function(String, bool)? optSetter,
}) {
getOpt() => optGetter != null
? optGetter()
: (isServer
? mainGetBoolOptionSync(key)
: mainGetLocalBoolOptionSync(key));
bool value = getOpt();
final isOptFixed = isOptionFixed(key);
if (reverse) value = !value;
var ref = value.obs;
onChanged(option) async {
if (option != null) {
if (reverse) option = !option;
isServer
? await mainSetBoolOption(key, option)
: await mainSetLocalBoolOption(key, option);
final readOption = isServer
? mainGetBoolOptionSync(key)
: mainGetLocalBoolOptionSync(key);
final setter =
optSetter ?? (isServer ? mainSetBoolOption : mainSetLocalBoolOption);
await setter(key, option);
final readOption = getOpt();
if (reverse) {
ref.value = !readOption;
} else {
ref.value = readOption;
}
update?.call();
update?.call(readOption);
}
}

View File

@@ -16,7 +16,6 @@ import '../../common.dart';
import '../../common/widgets/dialog.dart';
import '../../common/widgets/toolbar.dart';
import '../../models/model.dart';
import '../../models/desktop_render_texture.dart';
import '../../models/platform_model.dart';
import '../../common/shared_state.dart';
import '../../utils/image.dart';
@@ -593,12 +592,11 @@ class _ImagePaintState extends State<ImagePaint> {
onHover: (evt) {},
child: child);
});
if (c.imageOverflow.isTrue && c.scrollStyle == ScrollStyle.scrollbar) {
final paintWidth = c.getDisplayWidth() * s;
final paintHeight = c.getDisplayHeight() * s;
final paintSize = Size(paintWidth, paintHeight);
final paintWidget = useTextureRender
final paintWidget = m.useTextureRender
? _BuildPaintTextureRender(
c, s, Offset.zero, paintSize, isViewOriginal())
: _buildScrollbarNonTextureRender(m, paintSize, s);
@@ -619,7 +617,7 @@ class _ImagePaintState extends State<ImagePaint> {
));
} else {
if (c.size.width > 0 && c.size.height > 0) {
final paintWidget = useTextureRender
final paintWidget = m.useTextureRender
? _BuildPaintTextureRender(
c,
s,

View File

@@ -7,7 +7,6 @@ import 'package:flutter_hbb/common/widgets/audio_input.dart';
import 'package:flutter_hbb/common/widgets/toolbar.dart';
import 'package:flutter_hbb/models/chat_model.dart';
import 'package:flutter_hbb/models/state_model.dart';
import 'package:flutter_hbb/models/desktop_render_texture.dart';
import 'package:flutter_hbb/consts.dart';
import 'package:flutter_hbb/utils/multi_window_manager.dart';
import 'package:flutter_hbb/plugin/widgets/desc_ui.dart';
@@ -615,14 +614,14 @@ class _MonitorMenu extends StatelessWidget {
bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y';
bool get supportIndividualWindows =>
useTextureRender && ffi.ffiModel.pi.isSupportMultiDisplay;
!isWeb && ffi.ffiModel.pi.isSupportMultiDisplay;
@override
Widget build(BuildContext context) => showMonitorsToolbar
? buildMultiMonitorMenu()
: Obx(() => buildMonitorMenu());
? buildMultiMonitorMenu(context)
: Obx(() => buildMonitorMenu(context));
Widget buildMonitorMenu() {
Widget buildMonitorMenu(BuildContext context) {
final width = SimpleWrapper<double>(0);
final monitorsIcon =
globalMonitorsWidget(width, Colors.white, Colors.black38);
@@ -636,20 +635,23 @@ class _MonitorMenu extends StatelessWidget {
menuStyle: MenuStyle(
padding:
MaterialStatePropertyAll(EdgeInsets.symmetric(horizontal: 6))),
menuChildrenGetter: () => [buildMonitorSubmenuWidget()]);
menuChildrenGetter: () => [buildMonitorSubmenuWidget(context)]);
}
Widget buildMultiMonitorMenu() {
return Row(children: buildMonitorList(true));
Widget buildMultiMonitorMenu(BuildContext context) {
return Row(children: buildMonitorList(context, true));
}
Widget buildMonitorSubmenuWidget() {
Widget buildMonitorSubmenuWidget(BuildContext context) {
final m = Provider.of<ImageModel>(context);
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(children: buildMonitorList(false)),
supportIndividualWindows ? Divider() : Offstage(),
supportIndividualWindows ? chooseDisplayBehavior() : Offstage(),
Row(children: buildMonitorList(context, false)),
supportIndividualWindows && m.useTextureRender ? Divider() : Offstage(),
supportIndividualWindows && m.useTextureRender
? chooseDisplayBehavior()
: Offstage(),
],
);
}
@@ -680,7 +682,7 @@ class _MonitorMenu extends StatelessWidget {
),
);
List<Widget> buildMonitorList(bool isMulti) {
List<Widget> buildMonitorList(BuildContext context, bool isMulti) {
final List<Widget> monitorList = [];
final pi = ffi.ffiModel.pi;
@@ -735,7 +737,10 @@ class _MonitorMenu extends StatelessWidget {
for (int i = 0; i < pi.displays.length; i++) {
monitorList.add(buildMonitorButton(i));
}
if (supportIndividualWindows && pi.displays.length > 1) {
final m = Provider.of<ImageModel>(context);
if (supportIndividualWindows &&
m.useTextureRender &&
pi.displays.length > 1) {
monitorList.add(buildMonitorButton(kAllDisplayValue));
}
return monitorList;
@@ -818,7 +823,12 @@ class _MonitorMenu extends StatelessWidget {
}
RxInt display = CurrentDisplayState.find(id);
if (display.value != i) {
if (isChooseDisplayToOpenInNewWindow(pi, ffi.sessionId)) {
final isChooseDisplayToOpenInNewWindow = pi.isSupportMultiDisplay &&
bind.mainGetUseTextureRender() &&
bind.sessionGetDisplaysAsIndividualWindows(
sessionId: ffi.sessionId) ==
'Y';
if (isChooseDisplayToOpenInNewWindow) {
openMonitorInNewTabOrWindow(i, ffi.id, pi);
} else {
openMonitorInTheSameTab(i, ffi, pi, updateCursorPos: !isMulti);

View File

@@ -11,15 +11,10 @@ import './platform_model.dart';
import 'package:texture_rgba_renderer/texture_rgba_renderer.dart'
if (dart.library.html) 'package:flutter_hbb/web/texture_rgba_renderer.dart';
// Feature flutter_texture_render need to be enabled if feature vram is enabled.
final useTextureRender = !isWeb &&
(bind.mainHasPixelbufferTextureRender() || bind.mainHasGpuTextureRender());
class _PixelbufferTexture {
int _textureKey = -1;
int _display = 0;
SessionID? _sessionId;
final support = bind.mainHasPixelbufferTextureRender();
bool _destroying = false;
int? _id;
@@ -28,26 +23,24 @@ class _PixelbufferTexture {
int get display => _display;
create(int d, SessionID sessionId, FFI ffi) {
if (support) {
_display = d;
_textureKey = bind.getNextTextureKey();
_sessionId = sessionId;
_display = d;
_textureKey = bind.getNextTextureKey();
_sessionId = sessionId;
textureRenderer.createTexture(_textureKey).then((id) async {
_id = id;
if (id != -1) {
ffi.textureModel.setRgbaTextureId(display: d, id: id);
final ptr = await textureRenderer.getTexturePtr(_textureKey);
platformFFI.registerPixelbufferTexture(sessionId, display, ptr);
debugPrint(
"create pixelbuffer texture: peerId: ${ffi.id} display:$_display, textureId:$id, texturePtr:$ptr");
}
});
}
textureRenderer.createTexture(_textureKey).then((id) async {
_id = id;
if (id != -1) {
ffi.textureModel.setRgbaTextureId(display: d, id: id);
final ptr = await textureRenderer.getTexturePtr(_textureKey);
platformFFI.registerPixelbufferTexture(sessionId, display, ptr);
debugPrint(
"create pixelbuffer texture: peerId: ${ffi.id} display:$_display, textureId:$id, texturePtr:$ptr");
}
});
}
destroy(bool unregisterTexture, FFI ffi) async {
if (!_destroying && support && _textureKey != -1 && _sessionId != null) {
if (!_destroying && _textureKey != -1 && _sessionId != null) {
_destroying = true;
if (unregisterTexture) {
platformFFI.registerPixelbufferTexture(_sessionId!, display, 0);

View File

@@ -244,7 +244,7 @@ class FfiModel with ChangeNotifier {
handleMsgBox({
'type': 'success',
'title': 'Successful',
'text': 'Connected, waiting for image...',
'text': kMsgboxTextWaitingForImage,
'link': '',
}, sessionId, peerId);
updatePrivacyMode(data.updatePrivacyMode, sessionId, peerId);
@@ -380,12 +380,22 @@ class FfiModel with ChangeNotifier {
_handleSyncPeerOption(evt, peerId);
} else if (name == 'follow_current_display') {
handleFollowCurrentDisplay(evt, sessionId, peerId);
} else if (name == 'use_texture_render') {
_handleUseTextureRender(evt, sessionId, peerId);
} else {
debugPrint('Unknown event name: $name');
}
};
}
_handleUseTextureRender(
Map<String, dynamic> evt, SessionID sessionId, String peerId) {
parent.target?.imageModel.setUseTextureRender(evt['v'] == 'Y');
waitForFirstImage.value = true;
showConnectedWaitingForImage(parent.target!.dialogManager, sessionId,
'success', 'Successful', kMsgboxTextWaitingForImage);
}
_handleSyncPeerOption(Map<String, dynamic> evt, String peer) {
final k = evt['k'];
final v = evt['v'];
@@ -572,7 +582,7 @@ class FfiModel with ChangeNotifier {
showElevationError(sessionId, type, title, text, dialogManager);
} else if (type == 'relay-hint' || type == 'relay-hint2') {
showRelayHintDialog(sessionId, type, title, text, dialogManager, peerId);
} else if (text == 'Connected, waiting for image...') {
} else if (text == kMsgboxTextWaitingForImage) {
showConnectedWaitingForImage(dialogManager, sessionId, type, title, text);
} else if (title == 'Privacy mode') {
final hasRetry = evt['hasRetry'] == 'true';
@@ -1156,6 +1166,8 @@ class ImageModel with ChangeNotifier {
late final SessionID sessionId;
bool _useTextureRender = false;
WeakReference<FFI> parent;
final List<Function(String)> callbacksOnFirstImage = [];
@@ -1164,6 +1176,8 @@ class ImageModel with ChangeNotifier {
sessionId = parent.target!.sessionId;
}
get useTextureRender => _useTextureRender;
addCallbackOnFirstImage(Function(String) cb) => callbacksOnFirstImage.add(cb);
onRgba(int display, Uint8List rgba) {
@@ -1233,6 +1247,19 @@ class ImageModel with ChangeNotifier {
return min(xscale, yscale) / 1.5;
}
updateUserTextureRender() {
final preValue = _useTextureRender;
_useTextureRender = isDesktop && bind.mainGetUseTextureRender();
if (preValue != _useTextureRender) {
notifyListeners();
}
}
setUseTextureRender(bool value) {
_useTextureRender = value;
notifyListeners();
}
void disposeImage() {
_image?.dispose();
_image = null;
@@ -2387,7 +2414,7 @@ class FFI {
sessionId: sessionId, displays: Int32List.fromList(displays));
ffiModel.pi.currentDisplay = display;
}
if (connType == ConnType.defaultConn && useTextureRender) {
if (isDesktop && connType == ConnType.defaultConn) {
textureModel.updateCurrentDisplay(display ?? 0);
}
final stream = bind.sessionStart(sessionId: sessionId, id: id);
@@ -2409,9 +2436,8 @@ class FFI {
}
}
final hasPixelBufferTextureRender = bind.mainHasPixelbufferTextureRender();
imageModel.updateUserTextureRender();
final hasGpuTextureRender = bind.mainHasGpuTextureRender();
final SimpleWrapper<bool> isToNewWindowNotified = SimpleWrapper(false);
// Preserved for the rgba data.
stream.listen((message) {
@@ -2460,7 +2486,7 @@ class FFI {
}
} else if (message is EventToUI_Rgba) {
final display = message.field0;
if (hasPixelBufferTextureRender) {
if (imageModel.useTextureRender) {
debugPrint("EventToUI_Rgba display:$display");
textureModel.setTextureType(display: display, gpuTexture: false);
onEvent2UIRgba();

View File

@@ -1414,10 +1414,6 @@ class RustdeskImpl {
throw UnimplementedError();
}
bool mainHasPixelbufferTextureRender({dynamic hint}) {
return false;
}
bool mainHasFileClipboard({dynamic hint}) {
return false;
}
@@ -1608,5 +1604,9 @@ class RustdeskImpl {
throw UnimplementedError();
}
bool mainGetUseTextureRender({dynamic hint}) {
throw UnimplementedError();
}
void dispose() {}
}