88 lines
4.1 KiB
C
88 lines
4.1 KiB
C
/* Isolated VM102 experiment. Requires private virglrenderer >=1.2.0.
|
|
* Backport asynchronous completion delivery to the QEMU/GLib main thread.
|
|
* QEMU built against 1.1.0 has compiled out its native async-fence path.
|
|
* Never install as a global preload. No video acceleration flags are added.
|
|
*/
|
|
#define _GNU_SOURCE
|
|
#define VIRGL_RENDERER_UNSTABLE_APIS
|
|
#include <virgl/virglrenderer.h>
|
|
#include <dlfcn.h>
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <stdatomic.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
extern unsigned int g_idle_add_full(int, int (*)(void *), void *, void (*)(void *));
|
|
static struct virgl_renderer_callbacks original, replaced;
|
|
static pthread_t main_thread;
|
|
static atomic_uint epoch;
|
|
struct completion { void *cookie; uint32_t ctx, ring; uint64_t fence; unsigned epoch; int context; };
|
|
|
|
static int deliver(void *opaque) {
|
|
struct completion *c = opaque;
|
|
if (!pthread_equal(main_thread, pthread_self())) abort();
|
|
if (c->epoch != atomic_load(&epoch)) return 0;
|
|
if (c->context) original.write_context_fence(c->cookie, c->ctx, c->ring, c->fence);
|
|
else original.write_fence(c->cookie, (uint32_t)c->fence);
|
|
return 0;
|
|
}
|
|
static void schedule(void *cookie, uint32_t ctx, uint32_t ring, uint64_t fence, int context) {
|
|
struct completion *c = malloc(sizeof(*c));
|
|
if (!c) abort();
|
|
*c = (struct completion){cookie, ctx, ring, fence, atomic_load(&epoch), context};
|
|
if (!g_idle_add_full(0, deliver, c, free)) abort();
|
|
}
|
|
static void fence(void *cookie, uint32_t id) { schedule(cookie, 0, 0, id, 0); }
|
|
static void context_fence(void *cookie, uint32_t ctx, uint32_t ring, uint64_t id) { schedule(cookie, ctx, ring, id, 1); }
|
|
static int target(void) {
|
|
char args[32768]; int fd = open("/proc/self/cmdline", O_RDONLY | O_CLOEXEC);
|
|
if (fd < 0) return 0;
|
|
ssize_t n = read(fd, args, sizeof(args)); close(fd);
|
|
if (n <= 0 || n == sizeof(args) || args[n-1]) return 0;
|
|
for (char *p=args; p<args+n; p+=strlen(p)+1) if (!strcmp(p,"-id")) {
|
|
p+=strlen(p)+1; return p<args+n && !strcmp(p,"102");
|
|
}
|
|
return 0;
|
|
}
|
|
int virgl_renderer_init(void *cookie, int flags, struct virgl_renderer_callbacks *cb) {
|
|
typedef int (*Init)(void *, int, struct virgl_renderer_callbacks *);
|
|
Init real=(Init)dlsym(RTLD_NEXT,"virgl_renderer_init"); if(!real) abort();
|
|
if (!target()) return real(cookie,flags,cb);
|
|
if (!cb || cb->version!=4 || !cb->get_egl_display || !cb->write_fence || !cb->write_context_fence || (flags & VIRGL_RENDERER_ASYNC_FENCE_CB)) return -1;
|
|
main_thread=pthread_self(); atomic_fetch_add(&epoch,1); original=*cb; replaced=*cb;
|
|
replaced.write_fence=fence; replaced.write_context_fence=context_fence;
|
|
fprintf(stderr,"VDI async shim: VM102 flags 0x%x -> 0x%x, main-thread GLib delivery\n",flags,flags|VIRGL_RENDERER_THREAD_SYNC|VIRGL_RENDERER_ASYNC_FENCE_CB);
|
|
return real(cookie,flags|VIRGL_RENDERER_THREAD_SYNC|VIRGL_RENDERER_ASYNC_FENCE_CB,&replaced);
|
|
}
|
|
void virgl_renderer_cleanup(void *cookie) {
|
|
typedef void (*Cleanup)(void *);
|
|
Cleanup real=(Cleanup)dlsym(RTLD_NEXT,"virgl_renderer_cleanup"); if(!real) abort();
|
|
real(cookie); atomic_fetch_add(&epoch,1);
|
|
}
|
|
void virgl_renderer_reset(void) {
|
|
typedef void (*Reset)(void);
|
|
Reset real=(Reset)dlsym(RTLD_NEXT,"virgl_renderer_reset"); if(!real) abort();
|
|
atomic_fetch_add(&epoch,1); real();
|
|
}
|
|
|
|
#ifdef VDI_TEST
|
|
extern int g_main_context_iteration(void *, int);
|
|
static int seen;
|
|
static void verify(void *c, uint32_t f) { if(c!=(void *)1 || f!=(uint32_t)++seen) abort(); }
|
|
static void *producer(void *unused) { (void)unused; for(uint32_t i=1;i<=1000;i++) fence((void *)1,i); return NULL; }
|
|
int main(void) {
|
|
main_thread=pthread_self(); original.write_fence=verify;
|
|
pthread_t t; if(pthread_create(&t,NULL,producer,NULL)) abort(); pthread_join(t,NULL);
|
|
while(seen<1000) g_main_context_iteration(NULL,1);
|
|
fence((void *)1,9999); atomic_fetch_add(&epoch,1);
|
|
while(g_main_context_iteration(NULL,0)) {}
|
|
if(seen!=1000) abort();
|
|
puts("PASS: 1000 ordered callbacks on main thread; stale epoch dropped");
|
|
return 0;
|
|
}
|
|
#endif
|