mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 19:11:08 +00:00
feat: Mali Vulkan support through MaliBridge (#2027)
This commit is contained in:
83 files changed
+2331
-80
No files matched your search
@@ -41,6 +41,10 @@ if [[ ! -x "${java_dir}/bin/java" ]]; then
|
||||
echo "Java 17 not found at ${java_dir}; set JAVA_HOME." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "${repo_root}/external/MaliBridge/scripts/build-client-linux.sh" ]]; then
|
||||
echo "external/MaliBridge is empty; run: git submodule update --init" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "Docker is required to cross-compile the glibc ARM64 preload libraries." >&2
|
||||
exit 1
|
||||
@@ -135,6 +139,7 @@ docker run --rm --platform "${docker_platform}" \
|
||||
-o "$d/libssbs.so" tools/linuxfs/ssbs/ssbs_adapter.c -ldl
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$d/libssbs.so"
|
||||
mkdir -p "$d/usr/local/bin"
|
||||
external/MaliBridge/scripts/build-client-linux.sh "$d" aarch64-linux-gnu-gcc
|
||||
aarch64-linux-gnu-gcc -O2 -Wall -Wextra -o "$d/usr/local/bin/droiddeck-clipboard" tools/linuxfs/clipboard/clipboard.c -ldl
|
||||
aarch64-linux-gnu-strip --strip-unneeded "$d/usr/local/bin/droiddeck-clipboard"
|
||||
for script in tools/linuxfs/overlay/usr/local/bin/droiddeck-* tools/linuxfs/overlay/usr/local/bin/steam-compatibility; do
|
||||
|
||||
@@ -34,6 +34,9 @@ def identity(name, root=ROOT):
|
||||
for name_ in sorted(set(paths)):
|
||||
if '__pycache__' in name_ or name_.endswith(('.pyc', '.md')):
|
||||
continue
|
||||
if (root / name_).is_dir():
|
||||
values[name_] = subprocess.check_output(['git', 'ls-files', '--stage', '--', name_], cwd=root, text=True).split()[1]
|
||||
continue
|
||||
content = (root / name_).read_bytes()
|
||||
if b'\0' not in content:
|
||||
content = content.replace(b'\r\n', b'\n')
|
||||
|
||||
@@ -225,6 +225,21 @@
|
||||
"xz-utils"
|
||||
]
|
||||
},
|
||||
"vkbridge": {
|
||||
"script": "tools/components/recipes/vkbridge.sh",
|
||||
"inputs": [
|
||||
"external/MaliBridge",
|
||||
"tools/components/recipes/vkbridge.sh"
|
||||
],
|
||||
"outputs": [
|
||||
"app/src/main/assets/linuxfs/usr/local/lib/vkbridge/libvulkan_droidbridge.so",
|
||||
"app/src/main/assets/linuxfs/usr/local/share/vkbridge/droidbridge_icd.json",
|
||||
"app/src/main/assets/linuxfs/usr/local/bin/droiddeck-vkbridge-smoke"
|
||||
],
|
||||
"packages": [
|
||||
"gcc-aarch64-linux-gnu"
|
||||
]
|
||||
},
|
||||
"dirt-ws2": {
|
||||
"script": "tools/components/recipes/dirt-ws2.sh",
|
||||
"inputs": [
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
test -f external/MaliBridge/scripts/build-client-linux.sh
|
||||
bash external/MaliBridge/scripts/build-client-linux.sh app/src/main/assets/linuxfs aarch64-linux-gnu-gcc
|
||||
@@ -114,8 +114,10 @@ fi
|
||||
if [ "$(cat /usr/share/droiddeck/kwin-compositor 2>/dev/null)" = opengl ]; then
|
||||
unset LIBGL_ALWAYS_SOFTWARE QT_QUICK_BACKEND
|
||||
export KWIN_COMPOSE=O2 MESA_LOADER_DRIVER_OVERRIDE=zink GALLIUM_DRIVER=zink LIBGL_KOPPER_DRI2=true
|
||||
if [ -z "${KWIN_RENDER_NODES:-}" ] && [ -c /dev/dri/renderD0 ]; then
|
||||
export KWIN_RENDER_NODES=/dev/dri/renderD0
|
||||
if [ -z "${KWIN_RENDER_NODES:-}" ]; then
|
||||
for node in /dev/dri/renderD0 /dev/dri/renderD307 /dev/dri/renderD*; do
|
||||
if [ -c "$node" ]; then export KWIN_RENDER_NODES="$node"; break; fi
|
||||
done
|
||||
fi
|
||||
mkdir -p /run/droiddeck/kwin
|
||||
printf '#!/bin/sh\nGALLIUM_THREAD=0 exec /usr/bin/kwin_wayland "$@"\n' > /run/droiddeck/kwin/kwin_wayland
|
||||
|
||||
@@ -478,6 +478,14 @@ def apply_config(env, config, app_id, fixes=None):
|
||||
return result
|
||||
|
||||
|
||||
def bridge_feature_level(env):
|
||||
if env.get("BL_VKBRIDGE") != "1" or env.get("VKD3D_FEATURE_LEVEL") not in ("12_1", "12_2"):
|
||||
return None
|
||||
was = env["VKD3D_FEATURE_LEVEL"]
|
||||
env["VKD3D_FEATURE_LEVEL"] = "12_0"
|
||||
return "Mali bridge: VKD3D_FEATURE_LEVEL %s capped at 12_0 (no ray tracing or mesh shaders)" % was
|
||||
|
||||
|
||||
GAME_DEBUG_CHANNELS = (
|
||||
"timestamp", "pid", "tid", "seh", "unwind", "threadname", "debugstr", "loaddll", "mscoree",
|
||||
)
|
||||
@@ -974,6 +982,9 @@ def main():
|
||||
env["AUDIO_DIRECT_MIC"] = "0"
|
||||
env = directaudio_settings(env)
|
||||
env = vram_cap(env)
|
||||
note = bridge_feature_level(env)
|
||||
if note:
|
||||
print("droiddeck-game-env: " + note, file=sys.stderr)
|
||||
env = force_ssbs(command, env)
|
||||
game_cores(env)
|
||||
cpu_topology(env, command)
|
||||
|
||||
@@ -160,7 +160,7 @@ collect_steam_logs() {
|
||||
cp "$fex_state/status.json" "$fex_state/config/Config.json" "$BL_DEBUG_DIR/droiddeck-fex/" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
trap 'collect_steam_logs 2>/dev/null || true' TERM INT HUP
|
||||
trap 'bl_stopping=1; collect_steam_logs 2>/dev/null || true' TERM INT HUP
|
||||
if [ -n "${BL_VK_DRIVER:-}" ]; then
|
||||
bl_icd_lib=$(sed -n 's/.*"library_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$BL_VK_DRIVER" 2>/dev/null | head -1)
|
||||
bl_icd_lib=$(printf '%s' "$bl_icd_lib" | sed 's|\\/|/|g')
|
||||
@@ -177,6 +177,83 @@ if [ -n "${BL_VK_DRIVER:-}" ]; then
|
||||
fi
|
||||
unset bl_icd_lib
|
||||
fi
|
||||
bl_webhelper_layout() {
|
||||
if [ -n "${BL_VKBRIDGE_WEBHELPER_ARGS:-}" ]; then
|
||||
printf '%s\n' "$BL_VKBRIDGE_WEBHELPER_ARGS"
|
||||
elif [ -z "${BL_VKBRIDGE_CEF_ANGLE:-}" ]; then
|
||||
echo stock
|
||||
elif [ "$BL_VKBRIDGE_CEF_ANGLE" = vulkan ] && [ "${BL_VKBRIDGE_CEF_VULKAN:-1}" != 0 ]; then
|
||||
echo "--use-gl=angle --use-angle=vulkan +Vulkan +VulkanFromANGLE +DefaultANGLEVulkan"
|
||||
else
|
||||
echo "--use-gl=angle --use-angle=$BL_VKBRIDGE_CEF_ANGLE"
|
||||
fi
|
||||
}
|
||||
bl_now_ms() {
|
||||
if [ -n "${EPOCHREALTIME:-}" ]; then
|
||||
local t=${EPOCHREALTIME/[.,]/}
|
||||
echo $((10#$t / 1000))
|
||||
else
|
||||
date +%s%3N
|
||||
fi
|
||||
}
|
||||
vkbridge_wait_socket() {
|
||||
local sock=$1 limit=${2:-10000} start now waited
|
||||
[ -n "$sock" ] || return 1
|
||||
if [ -S "$sock" ]; then
|
||||
echo "== vkbridge: server socket ready (waited 0 ms)"
|
||||
return 0
|
||||
fi
|
||||
start=$(bl_now_ms)
|
||||
while :; do
|
||||
now=$(bl_now_ms)
|
||||
waited=$((now - start))
|
||||
if [ -S "$sock" ]; then
|
||||
echo "== vkbridge: server socket ready (waited $waited ms; the server listens after its startup probe)"
|
||||
return 0
|
||||
fi
|
||||
if [ -n "${bl_stopping:-}" ]; then
|
||||
echo "== vkbridge: no server socket after $waited ms; the session is stopping"
|
||||
return 1
|
||||
fi
|
||||
if [ "$waited" -ge "$limit" ]; then
|
||||
echo "== vkbridge: no server socket after $waited ms (gave up at $limit ms)"
|
||||
return 1
|
||||
fi
|
||||
sleep 0.05
|
||||
done
|
||||
}
|
||||
if [ "${BL_VKBRIDGE:-0}" = 1 ]; then
|
||||
bl_vkb_icd=/usr/local/share/vkbridge/droidbridge_icd.json
|
||||
[ -z "${BL_INSIDE:-}" ] && vkbridge_wait_socket "${VKBRIDGE_SOCKET:-}" "${BL_VKBRIDGE_WAIT_MS:-10000}"
|
||||
bl_vkb_missing=
|
||||
[ -r "$bl_vkb_icd" ] || bl_vkb_missing="$bl_vkb_missing icd"
|
||||
[ -r /usr/local/lib/vkbridge/libvulkan_droidbridge.so ] || bl_vkb_missing="$bl_vkb_missing library"
|
||||
[ -S "${VKBRIDGE_SOCKET:-}" ] || bl_vkb_missing="$bl_vkb_missing socket"
|
||||
if [ -z "$bl_vkb_missing" ]; then
|
||||
export VK_DRIVER_FILES=$bl_vkb_icd VK_ICD_FILENAMES=$bl_vkb_icd
|
||||
export VKBRIDGE_SOCKET VKBRIDGE_CLIENT_LOG
|
||||
if [ -r /root/.vkbridge-env ]; then
|
||||
while IFS='=' read -r _k _v; do
|
||||
case "$_k" in VKBRIDGE_*|BL_VKBRIDGE_*) export "$_k=$_v" ;; esac
|
||||
done < /root/.vkbridge-env
|
||||
[ -z "${BL_INSIDE:-}" ] && echo "== vkbridge: development settings from /root/.vkbridge-env: $(cut -d= -f1 /root/.vkbridge-env | tr '\n' ' ')"
|
||||
fi
|
||||
export VKD3D_FEATURE_LEVEL=${VKD3D_FEATURE_LEVEL:-12_0}
|
||||
unset TU_DEBUG
|
||||
if [ -z "${BL_INSIDE:-}" ]; then
|
||||
step "vkbridge self-test"
|
||||
timeout 60 /usr/local/bin/droiddeck-vkbridge-smoke 2>&1 | sed 's/^/== vkbridge: /'
|
||||
if command -v vulkaninfo >/dev/null 2>&1; then
|
||||
timeout 30 vulkaninfo --summary 2>&1 | sed -n '/^Devices:/,$p' | head -40 | sed 's/^/== vulkaninfo: /'
|
||||
fi
|
||||
fi
|
||||
echo "== vulkan driver: Mali bridge ($bl_vkb_icd, server at $VKBRIDGE_SOCKET)"
|
||||
else
|
||||
echo "== vulkan driver: Mali bridge requested but NOT usable (icd $bl_vkb_icd, socket ${VKBRIDGE_SOCKET:-unset}; missing:$bl_vkb_missing)"
|
||||
[ -n "${BL_INSIDE:-}" ] || exit 69
|
||||
fi
|
||||
unset bl_vkb_icd bl_vkb_missing
|
||||
fi
|
||||
export GAMESCOPE_FORCE_VULKAN_REALTIME=${BL_GAMESCOPE_REALTIME:-0}
|
||||
|
||||
start_agent() {
|
||||
@@ -307,6 +384,13 @@ if [ -z "${BL_INSIDE:-}" ]; then
|
||||
if [ "$mode" = steam ] && [ "${BL_CURSOR_COMPOSITE:-0}" = 1 ]; then
|
||||
export gamescope_cursor_composite=2
|
||||
fi
|
||||
# Through the Mali bridge, Steam's UI flickered while gamescope handed its layers to the phone's
|
||||
# compositor as separate planes, for a reason not yet found; one composited image does not
|
||||
# (Mali-G76 MC4: 145 footer flips in 6 s at rest, 0 composited).
|
||||
if [ "$mode" = steam ] && [ "${BL_VKBRIDGE:-0}" = 1 ] && [ -z "${gamescope_composite_force:-}" ]; then
|
||||
export gamescope_composite_force=1
|
||||
echo "== gamescope: composites every frame (Mali bridge: the Steam UI flickers on separate planes)"
|
||||
fi
|
||||
args=(--backend wayland --expose-wayland -f -W "$width" -H "$height" -w "$nested" -h "$nested_h")
|
||||
if [ "$nested" != "$width" ] || [ "$nested_h" != "$height" ]; then
|
||||
args+=(-S stretch)
|
||||
@@ -1023,6 +1107,28 @@ case $mode in
|
||||
_bl_cef=(-cef-disable-gpu)
|
||||
_bl_ui=(-nobigpicture)
|
||||
fi
|
||||
if [ "${BL_VKBRIDGE:-0}" = 1 ]; then
|
||||
# On the GPU the Steam UI held about 400 MB through the bridge (Mali-G76 MC4, 3 GB of RAM); with
|
||||
# a game loading beside it, Android's low-memory killer took the whole session down.
|
||||
if [ -z "${BL_VKBRIDGE_CEF_GPU:-}" ]; then
|
||||
_bl_mem_kb=$(awk '/^MemTotal:/ {print $2}' /proc/meminfo 2>/dev/null)
|
||||
if [ "${_bl_mem_kb:-0}" -gt 0 ] && [ "$_bl_mem_kb" -lt 4194304 ]; then
|
||||
BL_VKBRIDGE_CEF_GPU=0
|
||||
echo "== steam: $((_bl_mem_kb / 1024)) MB of RAM: the browser UI draws in software to leave games the memory"
|
||||
fi
|
||||
fi
|
||||
if [ "${BL_VKBRIDGE_CEF_GPU:-1}" = 0 ]; then
|
||||
_bl_cef=(-cef-disable-gpu -cef-disable-gpu-compositing)
|
||||
echo "== steam: browser UI in software (Mali bridge)"
|
||||
elif [ "${_bl_cef[0]}" = -cef-force-gpu ]; then
|
||||
_bl_cef=(-cef-force-gpu -cef-use-gl=angle -cef-use-angle="${BL_VKBRIDGE_CEF_ANGLE:-vulkan}")
|
||||
fi
|
||||
[ "${BL_VKBRIDGE_CEF_LOG:-0}" = 1 ] && _bl_cef+=(-cef-enable-logging=stderr -cef-v=1)
|
||||
[ -n "${BL_VKBRIDGE_CEF_EXTRA:-}" ] && read -r -a _bl_cef_extra <<<"$BL_VKBRIDGE_CEF_EXTRA" && _bl_cef+=("${_bl_cef_extra[@]}")
|
||||
BL_WEBHELPER_ARGS=$(bl_webhelper_layout)
|
||||
export BL_WEBHELPER_ARGS
|
||||
echo "== steam: web helper process layout: $BL_WEBHELPER_ARGS (Mali bridge)"
|
||||
fi
|
||||
if [ "${BL_STEAM_UI:-}" != desktop ]; then
|
||||
if [ "$steam_x64" = 0 ] || [ "${BL_X64_DECK:-1}" != 0 ]; then
|
||||
_bl_ui+=(-steamdeck -steamos3)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
@@ -34,7 +35,8 @@ static int is_kgsl(int fd) {
|
||||
struct stat st;
|
||||
pthread_mutex_lock(&lock);
|
||||
if (kgsl_state == 0) {
|
||||
kgsl_state = stat("/dev/kgsl-3d0", &st) == 0 ? 1 : -1;
|
||||
const char *standin = getenv("BL_DRM_STANDIN");
|
||||
kgsl_state = stat("/dev/kgsl-3d0", &st) == 0 || (standin && *standin && stat(standin, &st) == 0) ? 1 : -1;
|
||||
kgsl_dev = st.st_rdev;
|
||||
}
|
||||
pthread_mutex_unlock(&lock);
|
||||
@@ -49,18 +51,42 @@ static void *real(const char *name) {
|
||||
return fn;
|
||||
}
|
||||
|
||||
static int same_by_flock(int a, int b) {
|
||||
struct stat sa, sb;
|
||||
if (fstat(a, &sa) || fstat(b, &sb) || sa.st_dev != sb.st_dev || sa.st_ino != sb.st_ino ||
|
||||
flock(a, LOCK_EX | LOCK_NB)) {
|
||||
return 0;
|
||||
}
|
||||
int same = flock(b, LOCK_EX | LOCK_NB) == 0;
|
||||
flock(a, LOCK_UN);
|
||||
return same;
|
||||
}
|
||||
|
||||
static int same_file_description(int a, int b) {
|
||||
if (a == b) {
|
||||
return 1;
|
||||
}
|
||||
#ifdef SYS_kcmp
|
||||
int saved_errno = errno;
|
||||
long ret = syscall(SYS_kcmp, getpid(), getpid(), 0 , a, b);
|
||||
errno = saved_errno;
|
||||
return ret == 0;
|
||||
#else
|
||||
return 0;
|
||||
int ret = -1;
|
||||
#ifdef SYS_kcmp
|
||||
static int kcmp_missing;
|
||||
if (!kcmp_missing) {
|
||||
long k = syscall(SYS_kcmp, getpid(), getpid(), 0, a, b);
|
||||
if (k >= 0) {
|
||||
ret = k == 0;
|
||||
} else if (errno == ENOSYS || errno == EPERM) {
|
||||
kcmp_missing = 1;
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (ret < 0) {
|
||||
const char *standin = getenv("BL_DRM_STANDIN");
|
||||
ret = standin && *standin ? same_by_flock(a, b) : 0;
|
||||
}
|
||||
errno = saved_errno;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int find_handle_locked(int fd, uint32_t handle) {
|
||||
|
||||
@@ -57,6 +57,26 @@ class ComponentPrepareTest(unittest.TestCase):
|
||||
script.write_text(script.read_text() + '\nprintf changed\n')
|
||||
self.assertNotEqual(changed, components.identity('fake', self.root))
|
||||
|
||||
def test_identity_follows_a_submodule_commit(self):
|
||||
library = self.root / 'library'
|
||||
subprocess.run(['git', 'init', '-q', str(library)], check=True)
|
||||
def commit(text):
|
||||
(library / 'source.c').write_text(text)
|
||||
subprocess.run(['git', '-C', str(library), 'add', '.'], check=True)
|
||||
subprocess.run(['git', '-C', str(library), '-c', 'user.name=Test Developer', '-c', 'user.email=test@example.test',
|
||||
'commit', '-qm', text], check=True)
|
||||
return subprocess.check_output(['git', '-C', str(library), 'rev-parse', 'HEAD'], text=True).strip()
|
||||
first = commit('int a;\n')
|
||||
subprocess.run(['git', '-C', str(self.root), 'add', 'library'], check=True)
|
||||
spec = dict(script='tools/components/recipes/fake.sh', inputs=['library'], outputs=[])
|
||||
(self.root / components.REGISTRY).write_text(json.dumps(dict(fake=spec)))
|
||||
original = components.identity('fake', self.root)
|
||||
commit('int b;\n')
|
||||
self.assertEqual(original, components.identity('fake', self.root))
|
||||
subprocess.run(['git', '-C', str(self.root), 'add', 'library'], check=True)
|
||||
self.assertNotEqual(original, components.identity('fake', self.root))
|
||||
self.assertNotEqual(first, subprocess.check_output(['git', '-C', str(library), 'rev-parse', 'HEAD'], text=True).strip())
|
||||
|
||||
def test_tool_requirements_are_installed_in_one_union(self):
|
||||
definitions = dict(one=dict(packages=['gcc', 'cmake']), two=dict(packages=['gcc', 'qemu-user']))
|
||||
with patch.object(components, 'registry', return_value=definitions), patch.object(components.subprocess, 'run') as run:
|
||||
|
||||
@@ -649,6 +649,15 @@ class GameEnvironmentTest(unittest.TestCase):
|
||||
self.assertEqual(evaluate(), (0, 2))
|
||||
self.assertEqual(evaluate("changed"), (0, 3))
|
||||
|
||||
def test_the_mali_bridge_caps_the_vkd3d_feature_level(self):
|
||||
bridge = {"BL_VKBRIDGE": "1", "VKD3D_FEATURE_LEVEL": "12_2"}
|
||||
self.assertIn("capped", MODULE["bridge_feature_level"](bridge))
|
||||
self.assertEqual(bridge["VKD3D_FEATURE_LEVEL"], "12_0")
|
||||
for env in ({"BL_VKBRIDGE": "1", "VKD3D_FEATURE_LEVEL": "11_1"}, {"VKD3D_FEATURE_LEVEL": "12_2"}, {"BL_VKBRIDGE": "1"}):
|
||||
before = dict(env)
|
||||
self.assertIsNone(MODULE["bridge_feature_level"](env))
|
||||
self.assertEqual(env, before)
|
||||
|
||||
def test_profile_precedence_and_unset(self):
|
||||
env = {"KEEP": "inherited", "REMOVE": "inherited", "CUSTOM": "launch option"}
|
||||
config = {"version": 1, "shared": {"CUSTOM": "shared"}, "games": {"42": {"REMOVE": None, "CUSTOM": "game", "EMPTY": ""}}}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
SESSION = Path(__file__).resolve().parents[1] / "linuxfs/overlay/usr/local/bin/droiddeck-session"
|
||||
|
||||
|
||||
def wait_functions():
|
||||
text = SESSION.read_text()
|
||||
found = []
|
||||
for name in ("bl_now_ms", "vkbridge_wait_socket"):
|
||||
match = re.search(r"^%s\(\) \{\n.*?^\}\n" % name, text, re.M | re.S)
|
||||
if match:
|
||||
found.append(match.group(0))
|
||||
return found
|
||||
|
||||
|
||||
class VkBridgeSocketWaitTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.root = Path(tempfile.mkdtemp())
|
||||
self.addCleanup(shutil.rmtree, self.root, True)
|
||||
self.sock = self.root / "vkbridge" / "server.sock"
|
||||
self.sock.parent.mkdir()
|
||||
self.listeners = []
|
||||
self.addCleanup(lambda: [s.close() for s in self.listeners])
|
||||
|
||||
def listen(self):
|
||||
listener = socket.socket(socket.AF_UNIX)
|
||||
listener.bind(str(self.sock))
|
||||
listener.listen(1)
|
||||
self.listeners.append(listener)
|
||||
|
||||
def listen_after(self, seconds):
|
||||
timer = threading.Timer(seconds, self.listen)
|
||||
timer.start()
|
||||
self.addCleanup(timer.cancel)
|
||||
|
||||
def run_wait(self, limit_ms, prelude=""):
|
||||
functions = wait_functions()
|
||||
self.assertEqual(2, len(functions), "droiddeck-session lacks bl_now_ms() or vkbridge_wait_socket()")
|
||||
script = "set -u\n" + "".join(functions) + prelude
|
||||
script += 'if vkbridge_wait_socket "$1" "$2"; then echo USABLE; else echo FALLBACK; fi\n'
|
||||
started = time.monotonic()
|
||||
out = subprocess.run(["bash", "-c", script, "session", str(self.sock), str(limit_ms)],
|
||||
capture_output=True, text=True, timeout=30, check=True).stdout
|
||||
return out, time.monotonic() - started
|
||||
|
||||
def waited_ms(self, out):
|
||||
match = re.search(r"waited (\d+) ms|after (\d+) ms", out)
|
||||
self.assertIsNotNone(match, out)
|
||||
return int(match.group(1) or match.group(2))
|
||||
|
||||
def test_a_socket_already_listening_is_used_at_once(self):
|
||||
self.listen()
|
||||
out, elapsed = self.run_wait(10000)
|
||||
self.assertTrue(out.endswith("USABLE\n"), out)
|
||||
self.assertIn("== vkbridge: server socket ready (waited 0 ms)", out)
|
||||
self.assertLess(elapsed, 2)
|
||||
|
||||
def test_a_server_still_probing_is_waited_for(self):
|
||||
self.listen_after(0.4)
|
||||
out, elapsed = self.run_wait(10000)
|
||||
self.assertTrue(out.endswith("USABLE\n"), out)
|
||||
self.assertIn("== vkbridge: server socket ready (waited ", out)
|
||||
waited = self.waited_ms(out)
|
||||
self.assertGreaterEqual(waited, 300, out)
|
||||
self.assertLess(waited, 5000, out)
|
||||
self.assertLess(elapsed, 6)
|
||||
|
||||
def test_no_server_falls_back_after_the_limit(self):
|
||||
out, elapsed = self.run_wait(300)
|
||||
self.assertTrue(out.endswith("FALLBACK\n"), out)
|
||||
self.assertIn("== vkbridge: no server socket after ", out)
|
||||
self.assertIn("(gave up at 300 ms)", out)
|
||||
self.assertGreaterEqual(self.waited_ms(out), 300)
|
||||
self.assertLess(elapsed, 5)
|
||||
|
||||
def test_a_plain_file_is_not_a_socket(self):
|
||||
self.sock.write_text("stale")
|
||||
out, _ = self.run_wait(200)
|
||||
self.assertTrue(out.endswith("FALLBACK\n"), out)
|
||||
|
||||
def test_a_stopping_session_does_not_wait(self):
|
||||
out, elapsed = self.run_wait(10000, prelude="bl_stopping=1\n")
|
||||
self.assertTrue(out.endswith("FALLBACK\n"), out)
|
||||
self.assertIn("the session is stopping", out)
|
||||
self.assertLess(elapsed, 2)
|
||||
|
||||
def test_no_socket_path_is_a_fallback_without_waiting(self):
|
||||
functions = "".join(wait_functions())
|
||||
out = subprocess.run(["bash", "-c", "set -u\n" + functions +
|
||||
'if vkbridge_wait_socket "" 10000; then echo USABLE; else echo FALLBACK; fi\n'],
|
||||
capture_output=True, text=True, timeout=10, check=True).stdout
|
||||
self.assertEqual("FALLBACK\n", out)
|
||||
|
||||
|
||||
class VkBridgeSessionCheckTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.text = SESSION.read_text()
|
||||
match = re.search(r'^if \[ "\$\{BL_VKBRIDGE:-0\}" = 1 \]; then\n.*?^fi\n', self.text, re.M | re.S)
|
||||
self.assertIsNotNone(match)
|
||||
self.block = match.group(0)
|
||||
|
||||
def test_the_first_pass_waits_before_the_usability_check(self):
|
||||
wait = self.block.index("vkbridge_wait_socket")
|
||||
check = self.block.index('[ -S "${VKBRIDGE_SOCKET:-}" ]')
|
||||
self.assertLess(wait, check)
|
||||
self.assertIn('[ -z "${BL_INSIDE:-}" ] && vkbridge_wait_socket', self.block)
|
||||
|
||||
def test_the_fallback_message_names_what_is_missing(self):
|
||||
self.assertIn("Mali bridge requested but NOT usable", self.block)
|
||||
self.assertIn("missing:$bl_vkb_missing", self.block)
|
||||
|
||||
def test_a_stop_ends_the_wait(self):
|
||||
self.assertRegex(self.text, r"trap '[^']*bl_stopping=1[^']*' TERM INT HUP")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,48 @@
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
SESSION = Path(__file__).resolve().parents[1] / "linuxfs/overlay/usr/local/bin/droiddeck-session"
|
||||
KEYS = ("BL_VKBRIDGE_WEBHELPER_ARGS", "BL_VKBRIDGE_CEF_ANGLE", "BL_VKBRIDGE_CEF_VULKAN")
|
||||
|
||||
|
||||
def layout_function():
|
||||
match = re.search(r"^bl_webhelper_layout\(\) \{\n.*?^\}\n", SESSION.read_text(), re.M | re.S)
|
||||
return match.group(0) if match else None
|
||||
|
||||
|
||||
class WebhelperLayoutTest(unittest.TestCase):
|
||||
def layout(self, **settings):
|
||||
function = layout_function()
|
||||
self.assertIsNotNone(function, "droiddeck-session lacks bl_webhelper_layout()")
|
||||
env = {k: v for k, v in os.environ.items() if k not in KEYS}
|
||||
env.update(settings)
|
||||
return subprocess.run(["bash", "-c", "set -u\n" + function + "bl_webhelper_layout\n"], env=env,
|
||||
capture_output=True, text=True, check=True).stdout.strip()
|
||||
|
||||
def test_unset_keeps_steams_own_layout(self):
|
||||
self.assertEqual(self.layout(), "stock")
|
||||
|
||||
def test_angle_on_vulkan_brings_chromiums_vulkan_compositing(self):
|
||||
self.assertEqual(self.layout(BL_VKBRIDGE_CEF_ANGLE="vulkan"),
|
||||
"--use-gl=angle --use-angle=vulkan +Vulkan +VulkanFromANGLE +DefaultANGLEVulkan")
|
||||
|
||||
def test_vulkan_compositing_can_be_left_off(self):
|
||||
self.assertEqual(self.layout(BL_VKBRIDGE_CEF_ANGLE="vulkan", BL_VKBRIDGE_CEF_VULKAN="0"),
|
||||
"--use-gl=angle --use-angle=vulkan")
|
||||
|
||||
def test_other_angle_backends_get_only_the_angle_switches(self):
|
||||
self.assertEqual(self.layout(BL_VKBRIDGE_CEF_ANGLE="gl"), "--use-gl=angle --use-angle=gl")
|
||||
|
||||
def test_explicit_layout_wins(self):
|
||||
self.assertEqual(self.layout(BL_VKBRIDGE_CEF_ANGLE="vulkan", BL_VKBRIDGE_WEBHELPER_ARGS="--x +Y"),
|
||||
"--x +Y")
|
||||
|
||||
def test_the_steam_block_uses_it(self):
|
||||
self.assertIn("BL_WEBHELPER_ARGS=$(bl_webhelper_layout)", SESSION.read_text())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -61,6 +61,19 @@ class WebhelperPreloadTest(unittest.TestCase):
|
||||
args = [WEBHELPER, "--enable-features=A"]
|
||||
self.assertEqual(self.argv(*args, spec="stock"), args)
|
||||
|
||||
def test_angle_switches_are_appended_with_the_gpu_process_kept_out_of_process(self):
|
||||
out = self.argv(WEBHELPER, "-lang=en_US", "--enable-features=A", spec="--use-gl=angle --use-angle=vulkan")
|
||||
self.assertEqual(out, [WEBHELPER, "-lang=en_US", "--use-gl=angle", "--use-angle=vulkan",
|
||||
"--enable-features=A"])
|
||||
self.assertNotIn("--in-process-gpu", out)
|
||||
|
||||
def test_vulkan_compositing_features_join_steams_feature_list(self):
|
||||
out = self.argv(WEBHELPER, "--enable-features=PlatformHEVCDecoderSupport,V4L2VideoDecode",
|
||||
spec="--use-gl=angle --use-angle=vulkan +Vulkan +VulkanFromANGLE +DefaultANGLEVulkan")
|
||||
self.assertEqual(out, [WEBHELPER, "--use-gl=angle", "--use-angle=vulkan",
|
||||
"--enable-features=PlatformHEVCDecoderSupport,V4L2VideoDecode,"
|
||||
"Vulkan,VulkanFromANGLE,DefaultANGLEVulkan"])
|
||||
|
||||
def test_an_empty_setting_means_the_default(self):
|
||||
self.assertIn("--no-zygote", self.argv(WEBHELPER, spec=""))
|
||||
|
||||
|
||||
Reference in new issue
Block a user