mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 19:11:08 +00:00
fix(desktop): Flatpak and Snap apps get the game GPU setting (#2295)
The session's TU_DEBUG=sysmem is meant for the Steam client and Chromium-based apps. Flatpak and Snap apps inherited it, so emulators and other programs installed that way rendered through system memory. droiddeck-game-env now holds the Chromium check next to game_gpu, and a small droiddeck-game-gpu wrapper applies both to any program. The AppImage, Flatpak and Snap launchers use it, so the rule lives in one place.
This commit is contained in:
1 parent
3bb279f635
commit
1760f008ae
9 files changed
+221
-11
No files matched your search
@@ -80,7 +80,7 @@ DroidDeck is [GPL-3.0](LICENSE). Steam and Proton belong to Valve Corporation. V
|
||||
- The proot fast path orders bindings like [PRoot](https://github.com/proot-me/proot)'s `path/binding.c` (GPL-2.0-or-later).
|
||||
- DiRT 3 and DiRT Showdown keep their telemetry socket the way RoccoC's DiRTTelemetryErrorFix (MIT) does, based on the fix by Carsten Gottschling.
|
||||
- Zink on PowerVR keeps colour textures in colour with Icenowy Zheng's upstream [Mesa](https://gitlab.freedesktop.org/mesa/mesa) fix d55418dae7 (MIT), carried as a runtime patch.
|
||||
- Games and AppImage programs such as emulators use [Mesa](https://gitlab.freedesktop.org/mesa/mesa) Turnip's per render pass memory choice. The Steam client and Chromium-based apps keep `TU_DEBUG=sysmem`.
|
||||
- Games and AppImage, Flatpak and Snap programs such as emulators use [Mesa](https://gitlab.freedesktop.org/mesa/mesa) Turnip's per render pass memory choice. The Steam client and Chromium-based apps keep `TU_DEBUG=sysmem`.
|
||||
- Black Ops III and Dark Souls Remastered start without [umu-protonfixes](https://github.com/Open-Wine-Components/umu-protonfixes) (BSD-2-Clause), whose `311210.py` and `570940.py` fail under ARM64 Proton.
|
||||
- [ReSkate](https://github.com/Dingo-Shenanigans/ReSkate) (GPL-3.0) by Dingo Shenanigans uses the launch option from its README and D3D12 feature level 12_1.
|
||||
- The arm64 client's sign in was checked by reading Steam's saved token with the `ConnectCache` format from [deadlock-api-ingest](https://github.com/deadlock-api/deadlock-api-ingest).
|
||||
|
||||
@@ -40,6 +40,7 @@ object SessionFiles {
|
||||
WinComponents.publish(context)
|
||||
val files = arrayOf(
|
||||
"usr/local/bin/droiddeck-game-env" to "usr/local/bin/droiddeck-game-env",
|
||||
"usr/local/bin/droiddeck-game-gpu" to "usr/local/bin/droiddeck-game-gpu",
|
||||
"usr/local/bin/droiddeck-diagnostic-inventory" to "usr/local/bin/droiddeck-diagnostic-inventory",
|
||||
"usr/local/bin/droiddeck-wincomponents" to "usr/local/bin/droiddeck-wincomponents",
|
||||
"usr/local/bin/droiddeck-msi-install" to "usr/local/bin/droiddeck-msi-install",
|
||||
|
||||
@@ -25,12 +25,6 @@ if [ -n "$(find -H "$APPDIR" -maxdepth 4 \( -name chrome-sandbox -o -name chrome
|
||||
flags+=(--ozone-platform=x11)
|
||||
fi
|
||||
fi
|
||||
if [ -n "${DROIDDECK_GAME_TU_DEBUG+set}" ]; then
|
||||
if [ ${#flags[@]} -eq 0 ]; then
|
||||
if [ -n "$DROIDDECK_GAME_TU_DEBUG" ]; then export TU_DEBUG=$DROIDDECK_GAME_TU_DEBUG; else unset TU_DEBUG; fi
|
||||
fi
|
||||
unset DROIDDECK_GAME_TU_DEBUG
|
||||
fi
|
||||
qt=$(cat "$dir/qt" 2>/dev/null)
|
||||
case $qt in wayland|xcb) ;; *) qt=auto ;; esac
|
||||
wayland=${GAMESCOPE_WAYLAND_DISPLAY:-${WAYLAND_DISPLAY:-}}
|
||||
@@ -61,6 +55,9 @@ if [ -x /usr/local/bin/droiddeck-fex ]; then
|
||||
*) run=(/usr/local/bin/droiddeck-fex run --mode "$fex" --for "$APPDIR" -- "$APPDIR/AppRun") ;;
|
||||
esac
|
||||
fi
|
||||
gpu=("${BASH_SOURCE[0]%/*}/droiddeck-game-gpu")
|
||||
[ ${#flags[@]} -gt 0 ] && gpu+=(--chromium)
|
||||
run=("${gpu[@]}" -- "${run[@]}")
|
||||
echo "== appimage $ARGV0 ($APPDIR) ${flags[*]} FEX $fex Qt $qt${QT_QPA_PLATFORM:+ ($QT_QPA_PLATFORM)}"
|
||||
cd "$APPDIR" || exit 1
|
||||
started=$(mktemp "${TMPDIR:-/tmp}/droiddeck-appimage.XXXXXX")
|
||||
|
||||
@@ -11,8 +11,16 @@ if [ -n "${BL_INSIDE:-}" ] || [ -n "${GAMESCOPE_WAYLAND_DISPLAY:-}" ]; then
|
||||
export XDG_SESSION_TYPE=x11
|
||||
socket=(--nosocket=wayland --socket=x11)
|
||||
fi
|
||||
app=
|
||||
for arg in "$@"; do
|
||||
case $arg in -*) ;; *) app=$arg; break ;; esac
|
||||
done
|
||||
gpu=("${BASH_SOURCE[0]%/*}/droiddeck-game-gpu")
|
||||
location=
|
||||
[ -n "$app" ] && location=$(flatpak info --show-location "$app" 2>/dev/null)
|
||||
[ -n "$location" ] && gpu+=(--for "$location/files")
|
||||
echo "== flatpak run ${socket[*]} $*"
|
||||
if [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ] && command -v dbus-run-session >/dev/null 2>&1; then
|
||||
exec dbus-run-session -- flatpak run "${socket[@]}" "$@"
|
||||
exec "${gpu[@]}" -- dbus-run-session -- flatpak run "${socket[@]}" "$@"
|
||||
fi
|
||||
exec flatpak run "${socket[@]}" "$@"
|
||||
exec "${gpu[@]}" -- flatpak run "${socket[@]}" "$@"
|
||||
@@ -11,6 +11,7 @@ import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
GAME_TU_DEBUG = "DROIDDECK_GAME_TU_DEBUG"
|
||||
CHROMIUM_FILES = {"chrome-sandbox", "chrome_100_percent.pak", "v8_context_snapshot.bin"}
|
||||
SYNC_KEYS = ("BL_SYNC", "BL_SYNC_FALLBACK", "BL_FSYNC_FIRST", "BL_SYNC_MIN_NOFILE", "PROTON_NO_ESYNC", "PROTON_NO_FSYNC", "PROTON_NO_NTSYNC", "WINEESYNC")
|
||||
SEED_REDISTS = "/usr/local/bin/droiddeck-seed-redists"
|
||||
PACK_KEYS = ("WINESERVER", "BL_SYNC_PACK", "WINEESYNC", "PROTON_NO_ESYNC", "PROTON_NO_FSYNC")
|
||||
@@ -346,11 +347,23 @@ def win_components(compat, app_id):
|
||||
return ""
|
||||
|
||||
|
||||
def game_gpu(env):
|
||||
def chromium_app(folder):
|
||||
root = os.path.realpath(folder)
|
||||
for top, dirs, files in os.walk(root):
|
||||
if CHROMIUM_FILES.intersection(files) or CHROMIUM_FILES.intersection(dirs):
|
||||
return True
|
||||
if os.path.relpath(top, root).count(os.sep) >= 2:
|
||||
dirs[:] = []
|
||||
return False
|
||||
|
||||
|
||||
def game_gpu(env, keep=False):
|
||||
if GAME_TU_DEBUG not in env:
|
||||
return env
|
||||
result = env.copy()
|
||||
value = result.pop(GAME_TU_DEBUG)
|
||||
if keep:
|
||||
return result
|
||||
if value:
|
||||
result["TU_DEBUG"] = value
|
||||
else:
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
import runpy
|
||||
import sys
|
||||
|
||||
BIN_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def main(argv):
|
||||
keep = False
|
||||
folder = None
|
||||
while argv and argv[0] != "--":
|
||||
if argv[0] == "--chromium":
|
||||
keep = True
|
||||
argv = argv[1:]
|
||||
elif argv[0] == "--for" and len(argv) > 1:
|
||||
folder = argv[1]
|
||||
argv = argv[2:]
|
||||
else:
|
||||
break
|
||||
if argv and argv[0] == "--":
|
||||
argv = argv[1:]
|
||||
if not argv:
|
||||
print("usage: droiddeck-game-gpu [--chromium] [--for FOLDER] -- PROGRAM [ARGS...]", file=sys.stderr)
|
||||
return 2
|
||||
env = dict(os.environ)
|
||||
try:
|
||||
game_env = runpy.run_path(os.path.join(BIN_DIR, "droiddeck-game-env"))
|
||||
if folder and not keep:
|
||||
keep = game_env["chromium_app"](folder)
|
||||
env = game_env["game_gpu"](env, keep)
|
||||
except Exception as error:
|
||||
print("droiddeck-game-gpu: the game GPU setting was not applied (%s)" % error, file=sys.stderr)
|
||||
try:
|
||||
os.execvpe(argv[0], argv, env)
|
||||
except OSError as error:
|
||||
print("droiddeck-game-gpu: %s: %s" % (argv[0], error.strerror), file=sys.stderr)
|
||||
return 127
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
@@ -1,11 +1,13 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
import re
|
||||
import runpy
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
import yaml
|
||||
|
||||
BIN_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
SNAP_DIR = "/snap"
|
||||
HOST_DIRS = ("/etc", "/home", "/root", "/tmp", "/run", "/var/snap", "/var/lib/snapd", "/var/tmp", "/var/log",
|
||||
"/media", "/mnt", "/snap", "/usr/src", "/lib/modules")
|
||||
@@ -59,6 +61,15 @@ def content_binds(name, meta, env):
|
||||
return out
|
||||
|
||||
|
||||
def game_gpu(env, snap):
|
||||
try:
|
||||
game_env = runpy.run_path(os.path.join(BIN_DIR, "droiddeck-game-env"))
|
||||
return game_env["game_gpu"](env, game_env["chromium_app"](snap))
|
||||
except Exception as error:
|
||||
print(f"droiddeck-snap-run: the game GPU setting was not applied ({error})", file=sys.stderr)
|
||||
return env
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) < 3:
|
||||
print("usage: droiddeck-snap-run SNAP APP [ARG...]", file=sys.stderr)
|
||||
@@ -72,7 +83,7 @@ def main(argv):
|
||||
revision = os.path.basename(os.path.realpath(f"{SNAP_DIR}/{name}/current"))
|
||||
home = os.environ.get("HOME", "/root")
|
||||
snap = f"{SNAP_DIR}/{name}/{revision}"
|
||||
env = dict(os.environ)
|
||||
env = game_gpu(dict(os.environ), snap)
|
||||
for k in ("LD_PRELOAD", "LD_LIBRARY_PATH"):
|
||||
env.pop(k, None)
|
||||
env.update({
|
||||
|
||||
@@ -902,6 +902,46 @@ class GameEnvironmentTest(unittest.TestCase):
|
||||
self.assertEqual(MODULE["apply_config"](env, config, "42")["TU_DEBUG"], "sysmem")
|
||||
self.assertNotIn("TU_DEBUG", MODULE["apply_config"](env, config, "43"))
|
||||
|
||||
def test_a_chromium_app_keeps_the_sessions_turnip_debug(self):
|
||||
gpu = MODULE["game_gpu"]
|
||||
self.assertEqual(gpu({"TU_DEBUG": "sysmem", "DROIDDECK_GAME_TU_DEBUG": "", "KEEP": "1"}, True),
|
||||
{"TU_DEBUG": "sysmem", "KEEP": "1"})
|
||||
self.assertEqual(gpu({"TU_DEBUG": "sysmem", "DROIDDECK_GAME_TU_DEBUG": "nolrz"}, True), {"TU_DEBUG": "sysmem"})
|
||||
self.assertEqual(gpu({"TU_DEBUG": "sysmem"}, True), {"TU_DEBUG": "sysmem"})
|
||||
|
||||
def test_chromium_files_are_found_four_levels_deep_and_no_further(self):
|
||||
chromium = MODULE["chromium_app"]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp) / "app"
|
||||
(root / "usr/lib/tool").mkdir(parents=True)
|
||||
(root / "usr/lib/tool/libtool.so").write_bytes(b"")
|
||||
self.assertFalse(chromium(str(root)))
|
||||
for name in ("chrome-sandbox", "chrome_100_percent.pak", "v8_context_snapshot.bin"):
|
||||
found = root / "a/b/c" / name
|
||||
found.parent.mkdir(parents=True, exist_ok=True)
|
||||
found.write_bytes(b"")
|
||||
self.assertTrue(chromium(str(root)), name)
|
||||
found.unlink()
|
||||
deep = root / "a/b/c/d/chrome-sandbox"
|
||||
deep.parent.mkdir(parents=True)
|
||||
deep.write_bytes(b"")
|
||||
self.assertFalse(chromium(str(root)))
|
||||
self.assertFalse(chromium(str(Path(tmp) / "missing")))
|
||||
|
||||
def test_chromium_detection_does_not_follow_links_inside_the_app(self):
|
||||
chromium = MODULE["chromium_app"]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
other = Path(tmp) / "other"
|
||||
other.mkdir()
|
||||
(other / "chrome-sandbox").write_bytes(b"")
|
||||
root = Path(tmp) / "app"
|
||||
root.mkdir()
|
||||
(root / "linked").symlink_to(other, target_is_directory=True)
|
||||
self.assertFalse(chromium(str(root)))
|
||||
link = Path(tmp) / "app-link"
|
||||
link.symlink_to(other, target_is_directory=True)
|
||||
self.assertTrue(chromium(str(link)))
|
||||
|
||||
def test_the_vram_cap_comes_before_the_games_own_dxvk_settings(self):
|
||||
cap = MODULE["vram_cap"]
|
||||
self.assertEqual(cap({"BL_VRAM_MB": "4096"})["DXVK_CONFIG"], "dxgi.maxDeviceMemory = 4096")
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import os
|
||||
import runpy
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
BIN = Path(__file__).resolve().parents[1] / "linuxfs/overlay/usr/local/bin"
|
||||
KEYS = ("TU_DEBUG", "DROIDDECK_GAME_TU_DEBUG")
|
||||
SESSION = {"TU_DEBUG": "sysmem", "DROIDDECK_GAME_TU_DEBUG": ""}
|
||||
|
||||
|
||||
class GameGpuLaunchersTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = Path(tempfile.mkdtemp())
|
||||
self.addCleanup(shutil.rmtree, self.tmp, True)
|
||||
self.out = self.tmp / "env"
|
||||
self.tools = self.tmp / "tools"
|
||||
self.tools.mkdir()
|
||||
self.app = self.tmp / "deploy"
|
||||
(self.app / "files/bin").mkdir(parents=True)
|
||||
self.report = self.tools / "report"
|
||||
self.report.write_text("#!/bin/sh\nfor k in %s; do eval \"echo $k=\\${$k-unset}\"; done > %s\n"
|
||||
% (" ".join(KEYS), self.out))
|
||||
self.report.chmod(0o755)
|
||||
flatpak = self.tools / "flatpak"
|
||||
flatpak.write_text("#!/bin/sh\n"
|
||||
"if [ \"$1\" = info ]; then [ \"$3\" = org.example.App ] && echo %s; exit 0; fi\n"
|
||||
"exec %s\n" % (self.app, self.report))
|
||||
flatpak.chmod(0o755)
|
||||
|
||||
def chromium(self):
|
||||
(self.app / "files/lib/browser").mkdir(parents=True)
|
||||
(self.app / "files/lib/browser/chrome_100_percent.pak").write_bytes(b"")
|
||||
|
||||
def run_env(self, command, session):
|
||||
env = {k: v for k, v in os.environ.items() if k not in KEYS + ("DBUS_SESSION_BUS_ADDRESS", "GAMESCOPE_WAYLAND_DISPLAY")}
|
||||
env.update(session, PATH="%s:%s" % (self.tools, env.get("PATH", "/usr/bin:/bin")),
|
||||
DBUS_SESSION_BUS_ADDRESS="unix:path=/dev/null")
|
||||
result = subprocess.run(command, env=env, capture_output=True, text=True, timeout=30)
|
||||
self.assertEqual(0, result.returncode, result.stderr)
|
||||
return dict(line.split("=", 1) for line in self.out.read_text().splitlines())
|
||||
|
||||
def wrapper(self, *args, session=SESSION):
|
||||
return self.run_env([sys.executable, str(BIN / "droiddeck-game-gpu"), *args, "--", str(self.report)], session)
|
||||
|
||||
def flatpak(self, session=SESSION):
|
||||
return self.run_env(["bash", str(BIN / "droiddeck-flatpak-run"), "--command=app", "org.example.App"], session)
|
||||
|
||||
def test_the_wrapper_gives_a_program_the_game_setting(self):
|
||||
self.assertEqual({"TU_DEBUG": "unset", "DROIDDECK_GAME_TU_DEBUG": "unset"}, self.wrapper())
|
||||
self.assertEqual("gmem", self.wrapper(session={"TU_DEBUG": "sysmem", "DROIDDECK_GAME_TU_DEBUG": "gmem"})["TU_DEBUG"])
|
||||
|
||||
def test_the_wrapper_keeps_the_session_setting_for_chromium(self):
|
||||
self.assertEqual({"TU_DEBUG": "sysmem", "DROIDDECK_GAME_TU_DEBUG": "unset"}, self.wrapper("--chromium"))
|
||||
self.assertEqual("unset", self.wrapper("--for", str(self.app / "files"))["TU_DEBUG"])
|
||||
self.chromium()
|
||||
self.assertEqual("sysmem", self.wrapper("--for", str(self.app / "files"))["TU_DEBUG"])
|
||||
|
||||
def test_without_the_game_setting_the_wrapper_changes_nothing(self):
|
||||
self.assertEqual("sysmem", self.wrapper(session={"TU_DEBUG": "sysmem"})["TU_DEBUG"])
|
||||
self.assertEqual("unset", self.wrapper(session={})["TU_DEBUG"])
|
||||
|
||||
def test_the_wrapper_reports_a_missing_program(self):
|
||||
result = subprocess.run([sys.executable, str(BIN / "droiddeck-game-gpu"), "--", str(self.tmp / "missing")],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
self.assertEqual(127, result.returncode)
|
||||
self.assertEqual(2, subprocess.run([sys.executable, str(BIN / "droiddeck-game-gpu")], capture_output=True,
|
||||
timeout=30).returncode)
|
||||
|
||||
def test_a_flatpak_app_gets_the_game_setting(self):
|
||||
self.assertEqual({"TU_DEBUG": "unset", "DROIDDECK_GAME_TU_DEBUG": "unset"}, self.flatpak())
|
||||
self.assertEqual("nolrz", self.flatpak({"TU_DEBUG": "sysmem", "DROIDDECK_GAME_TU_DEBUG": "nolrz"})["TU_DEBUG"])
|
||||
|
||||
def test_a_chromium_flatpak_keeps_the_session_setting(self):
|
||||
self.chromium()
|
||||
self.assertEqual({"TU_DEBUG": "sysmem", "DROIDDECK_GAME_TU_DEBUG": "unset"}, self.flatpak())
|
||||
|
||||
def test_a_flatpak_without_the_game_setting_is_unchanged(self):
|
||||
self.assertEqual("sysmem", self.flatpak({"TU_DEBUG": "sysmem"})["TU_DEBUG"])
|
||||
|
||||
def test_a_snap_gets_the_game_setting_unless_it_is_chromium(self):
|
||||
try:
|
||||
snap_run = runpy.run_path(str(BIN / "droiddeck-snap-run"))
|
||||
except ImportError:
|
||||
self.skipTest("PyYAML is not installed")
|
||||
gpu = snap_run["game_gpu"]
|
||||
snap = self.app / "files"
|
||||
self.assertEqual({"KEEP": "1"}, gpu(dict(SESSION, KEEP="1"), str(snap)))
|
||||
self.assertEqual({"TU_DEBUG": "nolrz"}, gpu({"TU_DEBUG": "sysmem", "DROIDDECK_GAME_TU_DEBUG": "nolrz"}, str(snap)))
|
||||
self.chromium()
|
||||
self.assertEqual({"TU_DEBUG": "sysmem"}, gpu(dict(SESSION), str(snap)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user