Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: SDL3 via dlopen — binary runs without SDL libs, falls back to DRM

All SDL3 functions loaded via dlsym at runtime instead of link-time
dependency. Binary starts and works on any hardware:
- SDL3 libs present + working GPU: GPU-accelerated rendering
- SDL3 libs missing or DRI broken: graceful fallback to DRM/fbdev
- Probe runs in child process to catch Mesa/GBM segfaults

No more -lSDL3 in LDFLAGS. Oven builds re-enabled with USE_SDL=1.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+135 -69
+4 -3
fedac/native/Makefile
··· 47 47 LDFLAGS += $(AV_LIBS) 48 48 endif 49 49 50 - # SDL3 GPU-accelerated display (optional: make USE_SDL=1) 50 + # SDL3 GPU-accelerated display (runtime dlopen — no link-time dep) 51 + # USE_SDL=1 enables compile-time support; SDL3 is loaded via dlopen at runtime. 52 + # Binary runs and falls back to DRM even without SDL3 libs installed. 51 53 ifdef USE_SDL 52 54 SDL_CFLAGS := $(shell pkg-config --cflags sdl3 2>/dev/null) 53 - SDL_LIBS := $(shell pkg-config --libs sdl3 2>/dev/null || echo "-lSDL3") 54 55 CFLAGS += -DUSE_SDL $(SDL_CFLAGS) 55 - LDFLAGS += $(SDL_LIBS) 56 + # No -lSDL3 — all symbols resolved via dlsym at runtime 56 57 endif 57 58 58 59 # Wayland display backend (for running under cage compositor: make USE_WAYLAND=1)
+130 -65
fedac/native/src/drm-display.c
··· 10 10 #include <sys/select.h> 11 11 #include <linux/fb.h> 12 12 13 - #ifdef USE_SDL 14 13 // ============================================================ 15 - // SDL3 GPU-accelerated display (uses KMSDRM backend on bare metal) 14 + // SDL3 GPU-accelerated display — loaded via dlopen (no link-time dep) 15 + // Falls back to DRM/fbdev if SDL3 libs are missing or broken. 16 16 // ============================================================ 17 17 18 18 #include <dlfcn.h> ··· 21 21 22 22 extern void ac_log(const char *fmt, ...); 23 23 24 - // Probe whether SDL3 can load + init without crashing (runs in a child process). 25 - // This catches missing DRI drivers, GBM failures, and segfaults in Mesa. 24 + // SDL3 function pointers (resolved via dlsym at runtime) 25 + static void *sdl_lib_handle = NULL; 26 + typedef int (*pfn_SDL_Init)(unsigned int); 27 + typedef void (*pfn_SDL_Quit)(void); 28 + typedef const char *(*pfn_SDL_GetError)(void); 29 + typedef unsigned int (*pfn_SDL_GetPrimaryDisplay)(void); 30 + typedef const void *(*pfn_SDL_GetDesktopDisplayMode)(unsigned int); 31 + typedef void *(*pfn_SDL_CreateWindow)(const char *, int, int, unsigned int); 32 + typedef void (*pfn_SDL_DestroyWindow)(void *); 33 + typedef void *(*pfn_SDL_CreateRenderer)(void *, const char *); 34 + typedef void (*pfn_SDL_DestroyRenderer)(void *); 35 + typedef int (*pfn_SDL_RenderClear)(void *); 36 + typedef int (*pfn_SDL_RenderPresent)(void *); 37 + typedef int (*pfn_SDL_RenderTexture)(void *, void *, const void *, const void *); 38 + typedef void *(*pfn_SDL_CreateTexture)(void *, unsigned int, int, int, int); 39 + typedef void (*pfn_SDL_DestroyTexture)(void *); 40 + typedef int (*pfn_SDL_UpdateTexture)(void *, const void *, const void *, int); 41 + typedef int (*pfn_SDL_SetRenderVSync)(void *, int); 42 + typedef int (*pfn_SDL_SetTextureScaleMode)(void *, int); 43 + typedef void (*pfn_SDL_HideCursor)(void); 44 + typedef const char *(*pfn_SDL_GetRendererName)(void *); 45 + 46 + static struct { 47 + pfn_SDL_Init Init; 48 + pfn_SDL_Quit Quit; 49 + pfn_SDL_GetError GetError; 50 + pfn_SDL_GetPrimaryDisplay GetPrimaryDisplay; 51 + pfn_SDL_GetDesktopDisplayMode GetDesktopDisplayMode; 52 + pfn_SDL_CreateWindow CreateWindow; 53 + pfn_SDL_DestroyWindow DestroyWindow; 54 + pfn_SDL_CreateRenderer CreateRenderer; 55 + pfn_SDL_DestroyRenderer DestroyRenderer; 56 + pfn_SDL_RenderClear RenderClear; 57 + pfn_SDL_RenderPresent RenderPresent; 58 + pfn_SDL_RenderTexture RenderTexture; 59 + pfn_SDL_CreateTexture CreateTexture; 60 + pfn_SDL_DestroyTexture DestroyTexture; 61 + pfn_SDL_UpdateTexture UpdateTexture; 62 + pfn_SDL_SetRenderVSync SetRenderVSync; 63 + pfn_SDL_SetTextureScaleMode SetTextureScaleMode; 64 + pfn_SDL_HideCursor HideCursor; 65 + pfn_SDL_GetRendererName GetRendererName; 66 + } sdl = {0}; 67 + 68 + static int sdl_load(void) { 69 + if (sdl_lib_handle) return 1; 70 + setenv("LIBGL_DRIVERS_PATH", "/lib64/dri", 0); 71 + setenv("GBM_DRIVERS_PATH", "/lib64/dri", 0); 72 + setenv("MESA_LOADER_DRIVER_OVERRIDE", "iris", 0); 73 + sdl_lib_handle = dlopen("libSDL3.so.0", RTLD_LAZY); 74 + if (!sdl_lib_handle) { 75 + ac_log("[sdl3] dlopen failed: %s\n", dlerror()); 76 + return 0; 77 + } 78 + #define LOAD(name) sdl.name = (pfn_SDL_##name)dlsym(sdl_lib_handle, "SDL_" #name) 79 + LOAD(Init); LOAD(Quit); LOAD(GetError); 80 + LOAD(GetPrimaryDisplay); LOAD(GetDesktopDisplayMode); 81 + LOAD(CreateWindow); LOAD(DestroyWindow); 82 + LOAD(CreateRenderer); LOAD(DestroyRenderer); 83 + LOAD(RenderClear); LOAD(RenderPresent); LOAD(RenderTexture); 84 + LOAD(CreateTexture); LOAD(DestroyTexture); LOAD(UpdateTexture); 85 + LOAD(SetRenderVSync); LOAD(SetTextureScaleMode); 86 + LOAD(HideCursor); LOAD(GetRendererName); 87 + #undef LOAD 88 + if (!sdl.Init || !sdl.CreateWindow || !sdl.CreateRenderer) { 89 + ac_log("[sdl3] Missing required symbols\n"); 90 + dlclose(sdl_lib_handle); 91 + sdl_lib_handle = NULL; 92 + return 0; 93 + } 94 + return 1; 95 + } 96 + 97 + // Probe SDL3 in a child process — catches segfaults from broken DRI/GBM/Mesa 26 98 static int sdl_probe_safe(void) { 27 99 pid_t pid = fork(); 28 - if (pid < 0) return 0; // fork failed, skip SDL 100 + if (pid < 0) return 0; 29 101 if (pid == 0) { 30 - // Child: try dlopen + SDL_Init. If anything crashes, parent sees the signal. 31 - void *lib = dlopen("libSDL3.so.0", RTLD_LAZY); 32 - if (!lib) _exit(2); // SDL not available 33 - dlclose(lib); 102 + // Child: try full init. Crash = signal caught by parent. 34 103 setenv("SDL_VIDEO_DRIVER", "kmsdrm", 0); 35 104 setenv("LIBGL_DRIVERS_PATH", "/lib64/dri", 0); 36 105 setenv("GBM_DRIVERS_PATH", "/lib64/dri", 0); 37 106 setenv("MESA_LOADER_DRIVER_OVERRIDE", "iris", 0); 38 - int ok = SDL_Init(SDL_INIT_VIDEO) ? 0 : 1; 39 - if (!ok) SDL_Quit(); 107 + void *lib = dlopen("libSDL3.so.0", RTLD_LAZY); 108 + if (!lib) _exit(2); 109 + pfn_SDL_Init fn_init = (pfn_SDL_Init)dlsym(lib, "SDL_Init"); 110 + pfn_SDL_Quit fn_quit = (pfn_SDL_Quit)dlsym(lib, "SDL_Quit"); 111 + if (!fn_init) _exit(3); 112 + int ok = fn_init(0x20) ? 0 : 1; // SDL_INIT_VIDEO = 0x20 113 + if (!ok && fn_quit) fn_quit(); 114 + dlclose(lib); 40 115 _exit(ok); 41 116 } 42 - // Parent: wait for child 43 117 int status = 0; 44 118 waitpid(pid, &status, 0); 45 119 if (WIFSIGNALED(status)) { 46 - ac_log("[sdl3] Probe crashed (signal %d) — falling back to DRM\n", 47 - WTERMSIG(status)); 120 + ac_log("[sdl3] Probe crashed (signal %d) — falling back to DRM\n", WTERMSIG(status)); 48 121 return 0; 49 122 } 50 - if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { 51 - return 1; 52 - } 53 - int code = WIFEXITED(status) ? WEXITSTATUS(status) : -1; 54 - ac_log("[sdl3] Probe failed (exit %d) — falling back to DRM\n", code); 123 + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) return 1; 124 + ac_log("[sdl3] Probe failed (exit %d) — falling back to DRM\n", 125 + WIFEXITED(status) ? WEXITSTATUS(status) : -1); 55 126 return 0; 56 127 } 57 128 58 129 static ACDisplay *sdl_init(void) { 59 - // Probe in a child process first — catches segfaults from broken DRI/GBM 130 + // Probe in child process first (catches DRI segfaults) 60 131 if (!sdl_probe_safe()) return NULL; 132 + // Load SDL3 via dlopen 133 + if (!sdl_load()) return NULL; 61 134 62 - // Set KMSDRM hints for bare metal (no X11/Wayland) 63 - if (getpid() == 1) { 64 - setenv("SDL_VIDEO_DRIVER", "kmsdrm", 0); 65 - } 135 + if (getpid() == 1) setenv("SDL_VIDEO_DRIVER", "kmsdrm", 0); 66 136 67 - if (!SDL_Init(SDL_INIT_VIDEO)) { 68 - ac_log("[sdl3] SDL_Init failed: %s\n", SDL_GetError()); 137 + if (!sdl.Init(0x20)) { // SDL_INIT_VIDEO 138 + ac_log("[sdl3] SDL_Init failed: %s\n", sdl.GetError ? sdl.GetError() : "?"); 69 139 return NULL; 70 140 } 71 141 72 - // Get primary display and its mode 73 - SDL_DisplayID primary = SDL_GetPrimaryDisplay(); 142 + unsigned int primary = sdl.GetPrimaryDisplay(); 74 143 if (!primary) { 75 - ac_log("[sdl3] No primary display: %s\n", SDL_GetError()); 76 - SDL_Quit(); 144 + ac_log("[sdl3] No primary display: %s\n", sdl.GetError()); 145 + sdl.Quit(); 77 146 return NULL; 78 147 } 79 - const SDL_DisplayMode *dm = SDL_GetDesktopDisplayMode(primary); 148 + // SDL_DisplayMode: { int displayID, int format, int w, int h, float refresh, ... } 149 + typedef struct { unsigned int id; unsigned int fmt; int w, h; float refresh; int pad; void *d; } SDLMode; 150 + const SDLMode *dm = (const SDLMode *)sdl.GetDesktopDisplayMode(primary); 80 151 if (!dm) { 81 - ac_log("[sdl3] GetDesktopDisplayMode failed: %s\n", SDL_GetError()); 82 - SDL_Quit(); 152 + ac_log("[sdl3] GetDesktopDisplayMode failed: %s\n", sdl.GetError()); 153 + sdl.Quit(); 83 154 return NULL; 84 155 } 85 156 86 - SDL_Window *win = SDL_CreateWindow("ac-native", dm->w, dm->h, 87 - SDL_WINDOW_FULLSCREEN); 157 + void *win = sdl.CreateWindow("ac-native", dm->w, dm->h, 0x1); // SDL_WINDOW_FULLSCREEN 88 158 if (!win) { 89 - ac_log("[sdl3] CreateWindow failed: %s\n", SDL_GetError()); 90 - SDL_Quit(); 159 + ac_log("[sdl3] CreateWindow failed: %s\n", sdl.GetError()); 160 + sdl.Quit(); 91 161 return NULL; 92 162 } 93 163 94 - SDL_HideCursor(); 164 + if (sdl.HideCursor) sdl.HideCursor(); 95 165 96 - SDL_Renderer *ren = SDL_CreateRenderer(win, NULL); 166 + void *ren = sdl.CreateRenderer(win, NULL); 97 167 if (!ren) { 98 - ac_log("[sdl3] CreateRenderer failed: %s\n", SDL_GetError()); 99 - SDL_DestroyWindow(win); 100 - SDL_Quit(); 168 + ac_log("[sdl3] CreateRenderer failed: %s\n", sdl.GetError()); 169 + sdl.DestroyWindow(win); 170 + sdl.Quit(); 101 171 return NULL; 102 172 } 103 173 104 - // Enable vsync 105 - SDL_SetRenderVSync(ren, 1); 174 + if (sdl.SetRenderVSync) sdl.SetRenderVSync(ren, 1); 106 175 107 - // Log renderer info 108 - const char *ren_name = SDL_GetRendererName(ren); 176 + const char *ren_name = sdl.GetRendererName ? sdl.GetRendererName(ren) : "unknown"; 109 177 ac_log("[sdl3] Renderer: %s\n", ren_name ? ren_name : "unknown"); 110 178 111 179 ACDisplay *d = calloc(1, sizeof(ACDisplay)); ··· 115 183 d->height = dm->h; 116 184 d->sdl_window = win; 117 185 d->sdl_renderer = ren; 118 - // Texture created lazily in display_present (needs framebuffer dimensions) 119 186 d->sdl_texture = NULL; 120 187 d->sdl_tex_w = 0; 121 188 d->sdl_tex_h = 0; ··· 125 192 ac_log("[sdl3] Ready (%dx%d)\n", d->width, d->height); 126 193 return d; 127 194 } 128 - #endif /* USE_SDL */ 129 195 130 196 // ============================================================ 131 197 // fbdev fallback — uses /dev/fb0 (EFI framebuffer via efifb) ··· 503 569 if (!d || !screen) return; 504 570 505 571 #ifdef USE_SDL 506 - if (d->is_sdl) { 572 + if (d->is_sdl && sdl.CreateTexture) { 507 573 // Create/recreate texture if framebuffer size changed 508 574 if (!d->sdl_texture || d->sdl_tex_w != screen->width || d->sdl_tex_h != screen->height) { 509 - if (d->sdl_texture) SDL_DestroyTexture(d->sdl_texture); 510 - d->sdl_texture = SDL_CreateTexture(d->sdl_renderer, 511 - SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 575 + if (d->sdl_texture && sdl.DestroyTexture) sdl.DestroyTexture(d->sdl_texture); 576 + d->sdl_texture = sdl.CreateTexture(d->sdl_renderer, 577 + 0x16362004, 1, // SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING 512 578 screen->width, screen->height); 513 - if (d->sdl_texture) { 514 - SDL_SetTextureScaleMode(d->sdl_texture, SDL_SCALEMODE_NEAREST); 579 + if (d->sdl_texture && sdl.SetTextureScaleMode) { 580 + sdl.SetTextureScaleMode(d->sdl_texture, 0); // SDL_SCALEMODE_NEAREST 515 581 } 516 582 d->sdl_tex_w = screen->width; 517 583 d->sdl_tex_h = screen->height; 518 584 fprintf(stderr, "[sdl3] Created texture %dx%d\n", screen->width, screen->height); 519 585 } 520 - // Upload pixels to GPU texture 521 - SDL_UpdateTexture(d->sdl_texture, NULL, 522 - screen->pixels, screen->stride * (int)sizeof(uint32_t)); 523 - // GPU-accelerated scale to fullscreen 524 - SDL_RenderClear(d->sdl_renderer); 525 - SDL_RenderTexture(d->sdl_renderer, d->sdl_texture, NULL, NULL); 526 - SDL_RenderPresent(d->sdl_renderer); 586 + if (sdl.UpdateTexture) 587 + sdl.UpdateTexture(d->sdl_texture, NULL, 588 + screen->pixels, screen->stride * (int)sizeof(uint32_t)); 589 + if (sdl.RenderClear) sdl.RenderClear(d->sdl_renderer); 590 + if (sdl.RenderTexture) sdl.RenderTexture(d->sdl_renderer, d->sdl_texture, NULL, NULL); 591 + if (sdl.RenderPresent) sdl.RenderPresent(d->sdl_renderer); 527 592 return; 528 593 } 529 594 #endif ··· 821 886 822 887 #ifdef USE_SDL 823 888 if (d->is_sdl) { 824 - if (d->sdl_texture) SDL_DestroyTexture(d->sdl_texture); 825 - if (d->sdl_renderer) SDL_DestroyRenderer(d->sdl_renderer); 826 - if (d->sdl_window) SDL_DestroyWindow(d->sdl_window); 827 - SDL_Quit(); 889 + if (d->sdl_texture && sdl.DestroyTexture) sdl.DestroyTexture(d->sdl_texture); 890 + if (d->sdl_renderer && sdl.DestroyRenderer) sdl.DestroyRenderer(d->sdl_renderer); 891 + if (d->sdl_window && sdl.DestroyWindow) sdl.DestroyWindow(d->sdl_window); 892 + if (sdl.Quit) sdl.Quit(); 828 893 free(d); 829 894 return; 830 895 }
+1 -1
oven/native-builder.mjs
··· 464 464 addLogLine(job, "stdout", "Phase 2: Compiling C kernel in Docker..."); 465 465 const cidFile = `/tmp/oven-cid-${job.id}`; 466 466 await runPhase(job, "build", "bash", ["-c", [ 467 - `CID=$(docker create -e AC_BUILD_NAME=${buildName} -e AC_BUILD_SDL=0 -v ac-os-ccache:/ccache ac-os-builder)`, 467 + `CID=$(docker create -e AC_BUILD_NAME=${buildName} -e AC_BUILD_SDL=1 -v ac-os-ccache:/ccache ac-os-builder)`, 468 468 `echo $CID > ${cidFile}`, 469 469 `docker start -a $CID`, 470 470 ].join(" && ")], repoDir);