mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
mangoapp: run the performance overlay under Android
Three things kept Valve's mangoapp from drawing the overlay in a session: - GLFW 3.4 takes XDG_SESSION_TYPE=wayland with WAYLAND_DISPLAY set as Wayland-only, and mangoapp needs gamescope's X server; the wrapper sets XDG_SESSION_TYPE=x11. - On a Qualcomm display driver it opens a tracefs instance for the GPU memory total and aborts when that fails, which it always does for an Android app. The session preload now places the instance in an empty directory of its own when the real one cannot be made. - gamescope sends it each frame's timings through a System V message queue, and Android kernels have no System V IPC. sysv.c now emulates the queue operations gamescope and mangoapp use, beside its semaphores and shared memory.
This commit is contained in:
1 parent
2341659cea
commit
c2bcf20172
3 files changed
+174
-1
No files matched your search
@@ -7,7 +7,8 @@
|
||||
* segment is a plain file that shmat maps. Objects are found by the inode of their file, which is
|
||||
* what the id is. semop applies its operations one by one (no all-or-nothing across a set) and
|
||||
* SEM_UNDO is accepted but not performed - enough for the client, which uses single-counter sets.
|
||||
* Message queues stay unsupported. */
|
||||
* A message queue is a table of slots in its file: gamescope hands mangoapp (Deck mode's performance
|
||||
* overlay) each frame's timings through one. */
|
||||
#define _GNU_SOURCE
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
@@ -15,6 +16,7 @@
|
||||
#include <limits.h>
|
||||
#include <linux/futex.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdint.h>
|
||||
@@ -23,6 +25,7 @@
|
||||
#include <string.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/msg.h>
|
||||
#include <sys/sem.h>
|
||||
#include <sys/shm.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -396,6 +399,126 @@ int shmctl(int shmid, int cmd, struct shmid_ds *buf) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ message queues */
|
||||
|
||||
/* Only what gamescope and mangoapp use: msgtyp 0 or > 0, IPC_NOWAIT and IPC_RMID. */
|
||||
#define MSG_MAGIC 0x574e4d51u /* "WNMQ" */
|
||||
#define MSG_SLOTS 32
|
||||
|
||||
struct msg_queue {
|
||||
uint32_t magic;
|
||||
_Atomic int32_t lock; /* 0 free, else the holder's pid */
|
||||
_Atomic int32_t changed; /* bumped on every send and receive; waiters sleep on it */
|
||||
uint64_t next_seq;
|
||||
struct { uint64_t seq; long mtype; size_t len; char data[2048]; } slot[MSG_SLOTS]; /* seq 0: free */
|
||||
};
|
||||
|
||||
static struct msg_queue *msg_map(int id) {
|
||||
int fd = open_by_id("msg", id);
|
||||
if (fd < 0) return NULL;
|
||||
struct msg_queue *q = mmap(NULL, sizeof(*q), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
close(fd);
|
||||
if (q == MAP_FAILED) return NULL;
|
||||
if (q->magic == MSG_MAGIC) return q;
|
||||
munmap(q, sizeof(*q));
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* A process killed while holding the lock (Android ends sessions without warning) loses it. */
|
||||
static void msg_lock(struct msg_queue *q) {
|
||||
for (int32_t owner = 0; !atomic_compare_exchange_strong(&q->lock, &owner, getpid()); owner = 0) {
|
||||
if (kill(owner, 0) != 0 && errno == ESRCH) atomic_compare_exchange_strong(&q->lock, &owner, 0);
|
||||
else futex(&q->lock, FUTEX_WAIT, owner, &(struct timespec){0, 20000000});
|
||||
}
|
||||
}
|
||||
|
||||
static void msg_unlock(struct msg_queue *q, int changed) {
|
||||
atomic_store(&q->lock, 0);
|
||||
futex(&q->lock, FUTEX_WAKE, 1, NULL);
|
||||
if (changed) {
|
||||
atomic_fetch_add(&q->changed, 1);
|
||||
futex(&q->changed, FUTEX_WAKE, INT_MAX, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
int msgget(key_t key, int msgflg) {
|
||||
int created;
|
||||
int fd = open_object("msg", key, msgflg, &created);
|
||||
if (fd < 0) return -1;
|
||||
uint32_t magic = MSG_MAGIC;
|
||||
if (created && (ftruncate(fd, sizeof(struct msg_queue)) != 0 || pwrite(fd, &magic, sizeof(magic), 0) != sizeof(magic))) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
int id = object_id(fd);
|
||||
close(fd);
|
||||
return id;
|
||||
}
|
||||
|
||||
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg) {
|
||||
struct msg_queue *q = msg_map(msqid);
|
||||
if (!q) return -1;
|
||||
if (msgsz > sizeof(q->slot[0].data) || *(const long *)msgp < 1) { munmap(q, sizeof(*q)); errno = EINVAL; return -1; }
|
||||
for (;;) {
|
||||
msg_lock(q);
|
||||
int32_t seen = atomic_load(&q->changed);
|
||||
for (int i = 0; i < MSG_SLOTS; i++) {
|
||||
if (q->slot[i].seq) continue;
|
||||
q->slot[i].mtype = *(const long *)msgp;
|
||||
q->slot[i].len = msgsz;
|
||||
memcpy(q->slot[i].data, (const long *)msgp + 1, msgsz);
|
||||
q->slot[i].seq = ++q->next_seq;
|
||||
msg_unlock(q, 1);
|
||||
munmap(q, sizeof(*q));
|
||||
return 0;
|
||||
}
|
||||
msg_unlock(q, 0);
|
||||
if ((msgflg & IPC_NOWAIT) || (futex(&q->changed, FUTEX_WAIT, seen, NULL) != 0 && errno == EINTR)) {
|
||||
munmap(q, sizeof(*q));
|
||||
if (msgflg & IPC_NOWAIT) errno = EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg) {
|
||||
if (msgtyp < 0) { errno = EINVAL; return -1; }
|
||||
struct msg_queue *q = msg_map(msqid);
|
||||
if (!q) return -1;
|
||||
for (;;) {
|
||||
msg_lock(q);
|
||||
int32_t seen = atomic_load(&q->changed);
|
||||
int best = -1;
|
||||
for (int i = 0; i < MSG_SLOTS; i++)
|
||||
if (q->slot[i].seq && (!msgtyp || q->slot[i].mtype == msgtyp) && (best < 0 || q->slot[i].seq < q->slot[best].seq))
|
||||
best = i;
|
||||
if (best >= 0) {
|
||||
size_t n = q->slot[best].len < msgsz ? q->slot[best].len : msgsz;
|
||||
*(long *)msgp = q->slot[best].mtype;
|
||||
memcpy((long *)msgp + 1, q->slot[best].data, n);
|
||||
q->slot[best].seq = 0;
|
||||
msg_unlock(q, 1);
|
||||
munmap(q, sizeof(*q));
|
||||
return (ssize_t)n;
|
||||
}
|
||||
msg_unlock(q, 0);
|
||||
if ((msgflg & IPC_NOWAIT) || (futex(&q->changed, FUTEX_WAIT, seen, NULL) != 0 && errno == EINTR)) {
|
||||
munmap(q, sizeof(*q));
|
||||
if (msgflg & IPC_NOWAIT) errno = ENOMSG;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int msgctl(int msqid, int cmd, struct msqid_ds *buf) {
|
||||
if (cmd != IPC_RMID) { errno = EINVAL; return -1; }
|
||||
pthread_mutex_lock(&g_lock);
|
||||
int rc = unlink_by_id("msg", msqid);
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* fork() carries over only the calling thread, so a lock another thread was holding at that
|
||||
* instant stays held in the child by a thread that is not there to release it. Taking it before
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* A trace instance for mangoapp where the kernel's tracefs is closed to the app.
|
||||
*
|
||||
* Valve's mangoapp (Deck mode's performance overlay) reads the GPU memory total on a Qualcomm
|
||||
* display driver (msm_dpu / msm_drm) from the gpu_mem_total event, through a tracefs instance of
|
||||
* its own - and aborts when tracefs_instance_create() fails, taking the whole overlay with it.
|
||||
* Android mounts tracefs for root and the readtracefs group only, so it always fails here.
|
||||
*
|
||||
* The real calls run first. Only when they fail is the instance placed in an empty directory of
|
||||
* the session's own, where it exists but has no events: the overlay runs, and the one reading it
|
||||
* cannot take shows no GPU memory.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <dlfcn.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define STANDIN_DIR "/tmp/droiddeck-tracefs"
|
||||
|
||||
struct tracefs_instance;
|
||||
struct tep_handle;
|
||||
|
||||
struct tracefs_instance *tracefs_instance_create(const char *name) {
|
||||
struct tracefs_instance *(*real)(const char *) = dlsym(RTLD_NEXT, "tracefs_instance_create");
|
||||
struct tracefs_instance *instance = real ? real(name) : NULL;
|
||||
if (instance || !name) return instance;
|
||||
struct tracefs_instance *(*alloc)(const char *, const char *) = dlsym(RTLD_NEXT, "tracefs_instance_alloc");
|
||||
if (!alloc) return NULL;
|
||||
char path[PATH_MAX];
|
||||
mkdir(STANDIN_DIR, 0700);
|
||||
mkdir(STANDIN_DIR "/instances", 0700);
|
||||
snprintf(path, sizeof(path), STANDIN_DIR "/instances/%s", name);
|
||||
mkdir(path, 0700);
|
||||
return alloc(STANDIN_DIR, name);
|
||||
}
|
||||
|
||||
/* The stand-in has no event formats to read, so it gets an empty handle rather than none. */
|
||||
struct tep_handle *tracefs_local_events(const char *tracing_dir) {
|
||||
struct tep_handle *(*real)(const char *) = dlsym(RTLD_NEXT, "tracefs_local_events");
|
||||
struct tep_handle *tep = real ? real(tracing_dir) : NULL;
|
||||
if (tep || !tracing_dir || strncmp(tracing_dir, STANDIN_DIR "/", sizeof(STANDIN_DIR))) return tep;
|
||||
struct tep_handle *(*tep_alloc)(void) = dlsym(RTLD_DEFAULT, "tep_alloc");
|
||||
return tep_alloc ? tep_alloc() : NULL;
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/bin/sh
|
||||
# gamescope --mangoapp runs `mangoapp` from PATH; this puts Valve's build beside the libraries the
|
||||
# runtime lacks (fmt 10, spdlog 1.13, glfw, libtracefs, libtraceevent), staged by the app.
|
||||
# mangoapp reads its window through GLFW's X11 API. GLFW 3.4 takes XDG_SESSION_TYPE=wayland with a
|
||||
# WAYLAND_DISPLAY as Wayland-only, and unset, libwayland falls back to wayland-0 - DroidDeck's own
|
||||
# compositor, which shares XDG_RUNTIME_DIR - so both fail; x11 makes GLFW use gamescope's X server.
|
||||
export XDG_SESSION_TYPE=x11
|
||||
LD_LIBRARY_PATH=/usr/local/lib/mangoapp${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} exec /usr/local/lib/mangoapp/mangoapp "$@"
|
||||
Reference in new issue
Block a user