// SPDX-License-Identifier: BSD-3-Clause /* * gl_frames.c - render animated frames with direct GL (GLX pbuffer) on the * guest's NVIDIA vGPU and publish them into the ivshmem (kvmfr) BAR so the * host can see what the GL client actually rendered. * * This is the GL sibling of tools/cuda-ab/frame_producer.cu: the transport * (guest GPU -> guest sysmem -> ivshmem -> host frame_reader.py) is identical, * but the pixels come from the direct-GL client channel instead of CUDA. * The pbuffer avoids the X window draw path that wedges the X server. * * Layout at BAR offset 0x100000 (same as frame_producer.cu): * struct frame_hdr { magic, version, width, height, stride, format, seq } * then width*height*4 bytes of RGBA8. * * Usage: sudo ./gl_frames [frames] [width] [height] [fps] * Prints: FRAME seq= ms= */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FRAME_OFFSET 0x100000UL #define HDR_MAGIC 0x4b564d46u /* "FMVK" little-endian-ish */ #define BAR_BYTES (128UL << 20) #define BAR_PATH_DEF "/sys/bus/pci/devices/0000:00:04.0/resource2" struct frame_hdr { uint32_t magic; uint32_t version; uint32_t width; uint32_t height; uint32_t stride; uint32_t format; /* 0 = RGBA8 */ uint32_t seq; uint32_t reserved; uint64_t timestamp_ns; }; /* * The ivshmem kvmfr device moves with the QEMU topology (it was 00:03.0 in an * older launch, 00:04.0 in the current one). Discover the 128 MiB BAR instead * of hardcoding a slot; GL_FRAMES_BAR overrides for debugging. */ static const char *find_bar(char *buf, size_t len) { glob_t g; size_t i; const char *hit = NULL; if (glob("/sys/bus/pci/devices/*/resource2", 0, NULL, &g) != 0) return NULL; for (i = 0; i < g.gl_pathc; i++) { struct stat st; if (stat(g.gl_pathv[i], &st) == 0 && (unsigned long long)st.st_size == BAR_BYTES) { snprintf(buf, len, "%s", g.gl_pathv[i]); hit = buf; break; } } globfree(&g); return hit; } static uint64_t now_ns(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000000000ull + ts.tv_nsec; } static void draw_frame(int f, int w, int h) { float a = (float)f * 0.15f; int i; glViewport(0, 0, w, h); glClearColor(0.05f, 0.05f, 0.10f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); for (i = 0; i < 8; i++) { float t = a + (float)i * 0.78539816339f; glBegin(GL_TRIANGLES); glColor3f(1.0f, 0.2f + 0.09f * i, 0.1f); glVertex2f(0.0f, 0.0f); glColor3f(0.1f, 0.9f, 0.3f); glVertex2f(0.75f * cosf(t), 0.75f * sinf(t)); glColor3f(0.2f, 0.3f, 1.0f); glVertex2f(0.55f * cosf(t + 0.5f), 0.55f * sinf(t + 0.5f)); glEnd(); } glFinish(); } int main(int argc, char **argv) { int frames = argc > 1 ? atoi(argv[1]) : 30; int w = argc > 2 ? atoi(argv[2]) : 640; int h = argc > 3 ? atoi(argv[3]) : 480; int fps = argc > 4 ? atoi(argv[4]) : 5; static int attribs[] = { GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT, GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_ALPHA_SIZE, 8, None }; GLXFBConfig *fb; GLXPbuffer pb; XVisualInfo *vi; GLXContext ctx; Display *d; int scr, n = 0, fd, i; unsigned char *bar, *px; struct frame_hdr *hdr; volatile uint32_t *seqp; char found[256]; const char *bar_path = getenv("GL_FRAMES_BAR"); setvbuf(stdout, NULL, _IONBF, 0); if (!bar_path) bar_path = find_bar(found, sizeof(found)); if (!bar_path) bar_path = BAR_PATH_DEF; printf("BAR %s\n", bar_path); fd = open(bar_path, O_RDWR | O_SYNC); if (fd < 0) { printf("OPEN_FAIL %s\n", bar_path); return 1; } bar = (unsigned char *)mmap(NULL, BAR_BYTES, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (bar == MAP_FAILED) { printf("MMAP_FAIL\n"); return 1; } hdr = (struct frame_hdr *)(bar + FRAME_OFFSET); seqp = &hdr->seq; d = XOpenDisplay(NULL); if (!d) { printf("NODISPLAY\n"); return 2; } scr = DefaultScreen(d); fb = glXChooseFBConfig(d, scr, attribs, &n); if (!fb || n < 1) { printf("NOFBCONFIG\n"); return 3; } pb = glXCreatePbuffer(d, fb[0], (int[]){ GLX_PBUFFER_WIDTH, w, GLX_PBUFFER_HEIGHT, h, None }); if (!pb) { printf("NOPBUFFER\n"); return 4; } vi = glXGetVisualFromFBConfig(d, fb[0]); ctx = glXCreateContext(d, vi, NULL, True); if (!ctx) { printf("NOCTX\n"); return 5; } if (!glXMakeCurrent(d, pb, ctx)) { printf("MAKECURRENT_FAIL\n"); return 6; } printf("GL %s | %s\n", (const char *)glGetString(GL_VENDOR), (const char *)glGetString(GL_RENDERER)); px = (unsigned char *)malloc((size_t)w * h * 4); if (!px) { printf("HOST_ALLOC_FAIL\n"); return 7; } hdr->magic = HDR_MAGIC; hdr->version = 1; hdr->width = (uint32_t)w; hdr->height = (uint32_t)h; hdr->stride = (uint32_t)w * 4; hdr->format = 0; for (i = 0; i < frames; i++) { uint64_t t0 = now_ns(); uint32_t seq; draw_frame(i, w, h); glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, px); if (glGetError() != GL_NO_ERROR) printf("GL_ERROR frame=%d\n", i); memcpy(bar + FRAME_OFFSET + sizeof(struct frame_hdr), px, (size_t)w * h * 4); hdr->timestamp_ns = now_ns(); __sync_synchronize(); seq = (uint32_t)(i + 1); *seqp = seq; __sync_synchronize(); printf("FRAME seq=%u ms=%.1f px0=%02x%02x%02x%02x\n", seq, (double)(now_ns() - t0) / 1e6, px[0], px[1], px[2], px[3]); if (fps > 0) usleep((useconds_t)(1000000 / fps)); } printf("FRAME_DONE frames=%d %dx%d\n", frames, w, h); glXMakeCurrent(d, None, NULL); glXDestroyContext(d, ctx); glXDestroyPbuffer(d, pb); XCloseDisplay(d); free(px); munmap(bar, BAR_BYTES); close(fd); return 0; }