// SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2026 Open-IOV contributors // test_tri.c - minimal SDL2 + OpenGL triangle for the offscreen pbuffer // harness. Intended to be run with SDL_VIDEODRIVER=offscreen and the // gz_pbuf.so shim preloaded (plus sdl_cap.so to capture the swaps): // // SDL_VIDEODRIVER=offscreen DISPLAY=:0 \ // LD_PRELOAD="/tmp/sdl_cap.so /tmp/gz_pbuf.so" GZCAP=/tmp/testframes.bin \ // ./test_tri 20 640 480 // // It creates an SDL2 GL window/context (the shim turns the context into a // GLX pbuffer), clears to a colour, draws a rotating triangle, reads the // centre pixel back (non-black proof), and calls SDL_GL_SwapWindow each // frame. The sdl_cap.so hook captures the swap; the gz_pbuf.so hook turns // the swap into glFinish. Build: // gcc -O2 -o test_tri test_tri.c $(pkg-config --cflags --libs sdl2) -lGL #include #include #include #include #include int main(int argc, char **argv) { int frames = argc > 1 ? atoi(argv[1]) : 20; int w = argc > 2 ? atoi(argv[2]) : 640; int h = argc > 3 ? atoi(argv[3]) : 480; SDL_Window *win; SDL_GLContext ctx; int i; setvbuf(stdout, NULL, _IONBF, 0); if (SDL_Init(SDL_INIT_VIDEO) != 0) { fprintf(stderr, "test_tri: SDL_Init: %s\n", SDL_GetError()); return 1; } printf("test_tri: videodriver=%s\n", SDL_GetCurrentVideoDriver() ? SDL_GetCurrentVideoDriver() : "?"); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); win = SDL_CreateWindow("test_tri", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN); if (!win) { fprintf(stderr, "test_tri: SDL_CreateWindow: %s\n", SDL_GetError()); SDL_Quit(); return 2; } ctx = SDL_GL_CreateContext(win); if (!ctx) { fprintf(stderr, "test_tri: SDL_GL_CreateContext: %s\n", SDL_GetError()); SDL_DestroyWindow(win); SDL_Quit(); return 3; } if (SDL_GL_MakeCurrent(win, ctx) != 0) fprintf(stderr, "test_tri: SDL_GL_MakeCurrent: %s\n", SDL_GetError()); SDL_GL_SetSwapInterval(0); printf("test_tri: GL %s | %s | %s\n", (const char *)glGetString(GL_VENDOR), (const char *)glGetString(GL_RENDERER), (const char *)glGetString(GL_VERSION)); for (i = 0; i < frames; i++) { float a = (float)i * 0.10f; unsigned char px[4] = { 0, 0, 0, 0 }; 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(); glRotatef(a * 57.29578f, 0.0f, 0.0f, 1.0f); glBegin(GL_TRIANGLES); glColor3f(1.0f, 0.2f, 0.1f); glVertex2f(0.0f, 0.8f); glColor3f(0.1f, 1.0f, 0.3f); glVertex2f(-0.8f, -0.6f); glColor3f(0.2f, 0.3f, 1.0f); glVertex2f(0.8f, -0.6f); glEnd(); glFinish(); glReadPixels(w / 2, h / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, px); printf("test_tri: frame=%d centre=%02x%02x%02x%02x err=0x%x\n", i, px[0], px[1], px[2], px[3], glGetError()); SDL_GL_SwapWindow(win); SDL_Delay(20); } printf("test_tri: done frames=%d %dx%d\n", frames, w, h); SDL_GL_DeleteContext(ctx); SDL_DestroyWindow(win); SDL_Quit(); return 0; }