summaryrefslogtreecommitdiffstats
path: root/tools/guest-diag/sdl_cap.c
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-28 13:10:58 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-28 13:10:58 -0400
commit375d627a7abd0c07b95d70395ce89b2f4c42d106 (patch)
tree228ff83fcfa9c7316e545c2af921c69186cb72c7 /tools/guest-diag/sdl_cap.c
parent701346522d9e152049ec6a4fdb99448937803e60 (diff)
downloadopen-iov-375d627a7abd0c07b95d70395ce89b2f4c42d106.tar.gz
open-iov-375d627a7abd0c07b95d70395ce89b2f4c42d106.zip
tools/guest-diag: guest frame capture + input driving for the two-VM GZDoom video (Xvfb XGetImage capture, SDL present shim, window resize, XTEST input, QGA range fetch)
Diffstat (limited to 'tools/guest-diag/sdl_cap.c')
-rw-r--r--tools/guest-diag/sdl_cap.c229
1 files changed, 229 insertions, 0 deletions
diff --git a/tools/guest-diag/sdl_cap.c b/tools/guest-diag/sdl_cap.c
new file mode 100644
index 0000000..23c83ab
--- /dev/null
+++ b/tools/guest-diag/sdl_cap.c
@@ -0,0 +1,229 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// sdl_cap.c - LD_PRELOAD capture shim: records every frame GZDoom presents
+// through SDL into an append-only file, so the host can harvest the guest's
+// real rendered frames without any X/driver readback path.
+//
+// Hooks:
+// SDL_RenderPresent(SDL_Renderer*) -> SDL_RenderReadPixels(ARGB8888)
+// SDL_UpdateWindowSurface(SDL_Window*) -> SDL_GetWindowSurface pixels
+// Both forward to the real SDL after capturing.
+//
+// File layout (little endian):
+// [0] 8B magic "GZCAPV01" (+ 4088 B reserved zero)
+// [4096] frame records:
+// 4B "GZF2" | u32 w | u32 h | u32 stride | u32 bpp
+// u64 ts_ns (CLOCK_MONOTONIC) | u64 seq | stride*h bytes (top-down)
+// A trailing record with seq = 0xFFFFFFFFFFFFFFFF marks clean stop.
+//
+// Env: GZCAP (path, default /dev/shm/gzframes.bin), GZCAP_MAXF (0=unlimited)
+#define _GNU_SOURCE
+#include <dlfcn.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define ARGB8888 0x16362004u
+
+typedef struct SDL_Renderer SDL_Renderer;
+typedef struct SDL_Window SDL_Window;
+
+typedef struct SDL_Surface {
+ uint32_t flags;
+ void *format;
+ int w, h;
+ int pitch;
+ void *pixels;
+ void *userdata;
+ int locked;
+ void *list_blitmap;
+ struct { int x, y, w, h; } clip_rect;
+ void *map;
+ int refcount;
+} SDL_Surface;
+
+static int (*real_RenderReadPixels)(SDL_Renderer *, const void *, uint32_t,
+ void *, int);
+static int (*real_RenderPresent)(SDL_Renderer *);
+static SDL_Surface *(*real_GetWindowSurface)(SDL_Window *);
+static int (*real_UpdateWindowSurface)(SDL_Window *);
+static int (*real_GetRendererOutputSize)(SDL_Renderer *, int *, int *);
+static void (*real_GL_SwapWindow)(SDL_Window *);
+static int (*real_GL_GetDrawableSize)(SDL_Window *, int *, int *);
+static void (*real_glReadPixels)(int, int, int, int, unsigned, unsigned, void *);
+
+static FILE *cap_fp;
+static unsigned long long cap_count, cap_max = 0;
+static int cap_stop;
+
+__attribute__((constructor)) static void cap_init(void)
+{
+ const char *p = getenv("GZCAP");
+ const char *m = getenv("GZCAP_MAXF");
+ char hdr[4096];
+
+ if (m)
+ cap_max = strtoull(m, NULL, 0);
+ if (!p)
+ p = "/dev/shm/gzframes.bin";
+ real_RenderReadPixels = dlsym(RTLD_NEXT, "SDL_RenderReadPixels");
+ real_RenderPresent = dlsym(RTLD_NEXT, "SDL_RenderPresent");
+ real_GetWindowSurface = dlsym(RTLD_NEXT, "SDL_GetWindowSurface");
+ real_UpdateWindowSurface = dlsym(RTLD_NEXT, "SDL_UpdateWindowSurface");
+ real_GetRendererOutputSize =
+ dlsym(RTLD_NEXT, "SDL_GetRendererOutputSize");
+ real_GL_SwapWindow = dlsym(RTLD_NEXT, "SDL_GL_SwapWindow");
+ real_GL_GetDrawableSize = dlsym(RTLD_NEXT, "SDL_GL_GetDrawableSize");
+ real_glReadPixels = dlsym(RTLD_NEXT, "glReadPixels");
+ if (!real_glReadPixels)
+ real_glReadPixels = dlsym(RTLD_DEFAULT, "glReadPixels");
+ if (!real_glReadPixels) {
+ void *h = dlopen("libGL.so.1", RTLD_NOW | RTLD_GLOBAL);
+
+ if (!h)
+ h = dlopen("libGLESv2.so.2", RTLD_NOW | RTLD_GLOBAL);
+ if (h)
+ real_glReadPixels = dlsym(h, "glReadPixels");
+ }
+ fprintf(stderr, "[gzcap] init path=%s present=%p updwin=%p swap=%p read=%p gros=%p\n",
+ p, (void *)real_RenderPresent, (void *)real_UpdateWindowSurface,
+ (void *)real_GL_SwapWindow, (void *)real_RenderReadPixels,
+ (void *)real_GetRendererOutputSize, (void *)real_glReadPixels);
+ cap_fp = fopen(p, "wb");
+ if (!cap_fp)
+ return;
+ memset(hdr, 0, sizeof(hdr));
+ memcpy(hdr, "GZCAPV01", 8);
+ fwrite(hdr, 1, sizeof(hdr), cap_fp);
+ fflush(cap_fp);
+}
+
+static void cap_record(const void *px, int w, int h, int stride, int bpp)
+{
+ struct timespec ts;
+ uint64_t tns, seq;
+ uint32_t rh[4];
+ char m[4] = { 'G', 'Z', 'F', '2' };
+
+ if (!cap_fp || cap_stop)
+ return;
+ if (cap_max && cap_count >= cap_max) {
+ uint64_t endseq = 0xFFFFFFFFFFFFFFFFull;
+
+ fwrite(m, 1, 4, cap_fp);
+ fwrite(&endseq, 8, 1, cap_fp);
+ fflush(cap_fp);
+ cap_stop = 1;
+ return;
+ }
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ tns = (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
+ seq = cap_count;
+ rh[0] = (uint32_t)w;
+ rh[1] = (uint32_t)h;
+ rh[2] = (uint32_t)stride;
+ rh[3] = (uint32_t)bpp;
+ fwrite(m, 1, 4, cap_fp);
+ fwrite(rh, 4, 4, cap_fp);
+ fwrite(&tns, 8, 1, cap_fp);
+ fwrite(&seq, 8, 1, cap_fp);
+ fwrite(px, 1, (size_t)stride * h, cap_fp);
+ fflush(cap_fp);
+ cap_count++;
+ if ((cap_count & 31) == 0)
+ fprintf(stderr, "[gzcap] frames=%llu\n", cap_count);
+}
+
+int SDL_RenderPresent(SDL_Renderer *r)
+{
+ static int hit;
+
+ if (!hit++)
+ fprintf(stderr, "[gzcap] hook RenderPresent\n");
+ if (cap_fp && !cap_stop && real_RenderReadPixels &&
+ real_GetRendererOutputSize) {
+ int w = 0, h = 0;
+
+ if (real_GetRendererOutputSize(r, &w, &h) == 0 && w > 0 && h > 0 &&
+ w <= 4096 && h <= 4096) {
+ static unsigned char *buf;
+ static size_t bufsz;
+ size_t need = (size_t)w * 4 * h;
+
+ if (bufsz < need) {
+ free(buf);
+ buf = malloc(need);
+ bufsz = buf ? need : 0;
+ }
+ if (buf && real_RenderReadPixels(r, NULL, ARGB8888, buf,
+ w * 4) == 0)
+ cap_record(buf, w, h, w * 4, 32);
+ }
+ }
+ if (real_RenderPresent)
+ return real_RenderPresent(r);
+ return 0;
+}
+
+int SDL_UpdateWindowSurface(SDL_Window *win)
+{
+ static int hit;
+
+ if (!hit++)
+ fprintf(stderr, "[gzcap] hook UpdateWindowSurface\n");
+ if (cap_fp && !cap_stop && real_GetWindowSurface) {
+ SDL_Surface *s = real_GetWindowSurface(win);
+
+ if (s && s->pixels && s->w > 0 && s->h > 0 && s->pitch > 0 &&
+ s->w <= 4096 && s->h <= 4096)
+ cap_record(s->pixels, s->w, s->h, s->pitch, 32);
+ }
+ if (real_UpdateWindowSurface)
+ return real_UpdateWindowSurface(win);
+ return 0;
+}
+
+void SDL_GL_SwapWindow(SDL_Window *win)
+{
+ static int hit;
+ static unsigned char *gbuf;
+ static size_t gbufsz;
+
+ if (!hit++)
+ fprintf(stderr, "[gzcap] hook GLSwapWindow\n");
+ if (cap_fp && !cap_stop && real_glReadPixels && real_GL_GetDrawableSize) {
+ int w = 0, h = 0;
+
+ if (real_GL_GetDrawableSize(win, &w, &h) == 0 && w > 0 && h > 0 &&
+ w <= 4096 && h <= 4096) {
+ size_t need = (size_t)w * 4 * h;
+
+ if (gbufsz < need) {
+ free(gbuf);
+ gbuf = malloc(need);
+ gbufsz = gbuf ? need : 0;
+ }
+ if (gbuf) {
+ real_glReadPixels(0, 0, w, h, 0x1908, 0x1401, gbuf);
+ /* GL rows are bottom-up: flip into a scratch record */
+ {
+ unsigned char *tmp = malloc(need);
+ int y;
+
+ if (tmp) {
+ for (y = 0; y < h; y++)
+ memcpy(tmp + (size_t)y * w * 4,
+ gbuf + (size_t)(h - 1 - y) * w * 4,
+ (size_t)w * 4);
+ cap_record(tmp, w, h, w * 4, 32);
+ free(tmp);
+ }
+ }
+ }
+ }
+ }
+ if (real_GL_SwapWindow)
+ real_GL_SwapWindow(win);
+}