Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
C
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116#include "drm-display.h"#include "font.h"#include <stdio.h>#include <errno.h>#include <stdlib.h>#include <string.h>#include <fcntl.h>#include <unistd.h>#include <sys/ioctl.h>#include <sys/mman.h>#include <sys/select.h>#include <linux/fb.h>
// ============================================================// SDL3 GPU-accelerated display — loaded via dlopen (no link-time dep)// Falls back to DRM/fbdev if SDL3 libs are missing or broken.// ============================================================
#include <dlfcn.h>#include <signal.h>#include <sys/wait.h>
extern void ac_log(const char *fmt, ...);
// SDL3 function pointers (resolved via dlsym at runtime)static void *sdl_lib_handle = NULL;typedef int (*pfn_SDL_Init)(unsigned int);typedef void (*pfn_SDL_Quit)(void);typedef const char *(*pfn_SDL_GetError)(void);typedef unsigned int (*pfn_SDL_GetPrimaryDisplay)(void);typedef const void *(*pfn_SDL_GetDesktopDisplayMode)(unsigned int);typedef void *(*pfn_SDL_CreateWindow)(const char *, int, int, unsigned int);typedef void (*pfn_SDL_DestroyWindow)(void *);typedef void *(*pfn_SDL_CreateRenderer)(void *, const char *);typedef void (*pfn_SDL_DestroyRenderer)(void *);typedef int (*pfn_SDL_RenderClear)(void *);typedef int (*pfn_SDL_RenderPresent)(void *);typedef int (*pfn_SDL_RenderTexture)(void *, void *, const void *, const void *);typedef void *(*pfn_SDL_CreateTexture)(void *, unsigned int, int, int, int);typedef void (*pfn_SDL_DestroyTexture)(void *);typedef int (*pfn_SDL_UpdateTexture)(void *, const void *, const void *, int);typedef int (*pfn_SDL_SetRenderVSync)(void *, int);typedef int (*pfn_SDL_SetTextureScaleMode)(void *, int);typedef void (*pfn_SDL_HideCursor)(void);typedef const char *(*pfn_SDL_GetRendererName)(void *);
static struct { pfn_SDL_Init Init; pfn_SDL_Quit Quit; pfn_SDL_GetError GetError; pfn_SDL_GetPrimaryDisplay GetPrimaryDisplay; pfn_SDL_GetDesktopDisplayMode GetDesktopDisplayMode; pfn_SDL_CreateWindow CreateWindow; pfn_SDL_DestroyWindow DestroyWindow; pfn_SDL_CreateRenderer CreateRenderer; pfn_SDL_DestroyRenderer DestroyRenderer; pfn_SDL_RenderClear RenderClear; pfn_SDL_RenderPresent RenderPresent; pfn_SDL_RenderTexture RenderTexture; pfn_SDL_CreateTexture CreateTexture; pfn_SDL_DestroyTexture DestroyTexture; pfn_SDL_UpdateTexture UpdateTexture; pfn_SDL_SetRenderVSync SetRenderVSync; pfn_SDL_SetTextureScaleMode SetTextureScaleMode; pfn_SDL_HideCursor HideCursor; pfn_SDL_GetRendererName GetRendererName;} sdl = {0};
static int sdl_load(void) { if (sdl_lib_handle) return 1; setenv("LIBGL_DRIVERS_PATH", "/lib64/dri", 0); setenv("GBM_DRIVERS_PATH", "/lib64/dri", 0); setenv("MESA_LOADER_DRIVER_OVERRIDE", "iris", 0); sdl_lib_handle = dlopen("libSDL3.so.0", RTLD_LAZY); if (!sdl_lib_handle) { ac_log("[sdl3] dlopen failed: %s\n", dlerror()); return 0; } #define LOAD(name) sdl.name = (pfn_SDL_##name)dlsym(sdl_lib_handle, "SDL_" #name) LOAD(Init); LOAD(Quit); LOAD(GetError); LOAD(GetPrimaryDisplay); LOAD(GetDesktopDisplayMode); LOAD(CreateWindow); LOAD(DestroyWindow); LOAD(CreateRenderer); LOAD(DestroyRenderer); LOAD(RenderClear); LOAD(RenderPresent); LOAD(RenderTexture); LOAD(CreateTexture); LOAD(DestroyTexture); LOAD(UpdateTexture); LOAD(SetRenderVSync); LOAD(SetTextureScaleMode); LOAD(HideCursor); LOAD(GetRendererName); #undef LOAD if (!sdl.Init || !sdl.CreateWindow || !sdl.CreateRenderer) { ac_log("[sdl3] Missing required symbols\n"); dlclose(sdl_lib_handle); sdl_lib_handle = NULL; return 0; } return 1;}
// Signal-based crash recovery for SDL3 init (no fork needed)#include <setjmp.h>static sigjmp_buf sdl_crash_jmp;static volatile int sdl_crash_sig = 0;
static void sdl_crash_handler(int sig) { sdl_crash_sig = sig; siglongjmp(sdl_crash_jmp, 1);}
static ACDisplay *sdl_init(void) { // Check if SDL was disabled by init after a previous crash const char *no_sdl = getenv("AC_NO_SDL"); if (no_sdl && no_sdl[0] == '1') { ac_log("[sdl3] Disabled via AC_NO_SDL (previous crash) — using DRM\n"); return NULL; }
// Set Mesa env vars before any dlopen setenv("LIBGL_DRIVERS_PATH", "/lib64/dri", 0); setenv("GBM_DRIVERS_PATH", "/lib64/dri", 0); setenv("MESA_LOADER_DRIVER_OVERRIDE", "iris", 0); setenv("SDL_VIDEO_DRIVER", "kmsdrm", 0);
// Install crash handler — catches SIGSEGV/SIGBUS from Mesa DRI loading struct sigaction sa = {0}, old_segv = {0}, old_bus = {0}, old_abrt = {0}; sa.sa_handler = sdl_crash_handler; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sigaction(SIGSEGV, &sa, &old_segv); sigaction(SIGBUS, &sa, &old_bus); sigaction(SIGABRT, &sa, &old_abrt);
if (sigsetjmp(sdl_crash_jmp, 1) != 0) { // Crashed during SDL init — restore handlers and fall back sigaction(SIGSEGV, &old_segv, NULL); sigaction(SIGBUS, &old_bus, NULL); sigaction(SIGABRT, &old_abrt, NULL); ac_log("[sdl3] Crashed (signal %d) during init — falling back to DRM\n", sdl_crash_sig); if (sdl_lib_handle) { dlclose(sdl_lib_handle); sdl_lib_handle = NULL; } memset(&sdl, 0, sizeof(sdl)); return NULL; }
// Try loading SDL3 (dlopen may trigger Mesa DRI load → potential crash) if (!sdl_load()) { sigaction(SIGSEGV, &old_segv, NULL); sigaction(SIGBUS, &old_bus, NULL); sigaction(SIGABRT, &old_abrt, NULL); return NULL; }
if (!sdl.Init(0x20)) { // SDL_INIT_VIDEO ac_log("[sdl3] SDL_Init failed: %s\n", sdl.GetError ? sdl.GetError() : "?"); sigaction(SIGSEGV, &old_segv, NULL); sigaction(SIGBUS, &old_bus, NULL); sigaction(SIGABRT, &old_abrt, NULL); return NULL; } // Past the danger zone — restore handlers now sigaction(SIGSEGV, &old_segv, NULL); sigaction(SIGBUS, &old_bus, NULL); sigaction(SIGABRT, &old_abrt, NULL);
unsigned int primary = sdl.GetPrimaryDisplay(); if (!primary) { ac_log("[sdl3] No primary display: %s\n", sdl.GetError()); sdl.Quit(); return NULL; } // SDL_DisplayMode: { int displayID, int format, int w, int h, float refresh, ... } typedef struct { unsigned int id; unsigned int fmt; int w, h; float refresh; int pad; void *d; } SDLMode; const SDLMode *dm = (const SDLMode *)sdl.GetDesktopDisplayMode(primary); if (!dm) { ac_log("[sdl3] GetDesktopDisplayMode failed: %s\n", sdl.GetError()); sdl.Quit(); return NULL; }
void *win = sdl.CreateWindow("ac-native", dm->w, dm->h, 0x1); // SDL_WINDOW_FULLSCREEN if (!win) { ac_log("[sdl3] CreateWindow failed: %s\n", sdl.GetError()); sdl.Quit(); return NULL; }
if (sdl.HideCursor) sdl.HideCursor();
void *ren = sdl.CreateRenderer(win, NULL); if (!ren) { ac_log("[sdl3] CreateRenderer failed: %s\n", sdl.GetError()); sdl.DestroyWindow(win); sdl.Quit(); return NULL; }
if (sdl.SetRenderVSync) sdl.SetRenderVSync(ren, 1);
const char *ren_name = sdl.GetRendererName ? sdl.GetRendererName(ren) : "unknown"; ac_log("[sdl3] Renderer: %s\n", ren_name ? ren_name : "unknown");
ACDisplay *d = calloc(1, sizeof(ACDisplay)); d->fd = -1; d->is_sdl = 1; d->width = dm->w; d->height = dm->h; d->sdl_window = win; d->sdl_renderer = ren; d->sdl_texture = NULL; d->sdl_tex_w = 0; d->sdl_tex_h = 0; snprintf(d->sdl_renderer_name, sizeof(d->sdl_renderer_name), "%s", ren_name ? ren_name : "unknown");
// Restore signal handlers — SDL init survived sigaction(SIGSEGV, &old_segv, NULL); sigaction(SIGBUS, &old_bus, NULL); sigaction(SIGABRT, &old_abrt, NULL);
ac_log("[sdl3] Ready (%dx%d)\n", d->width, d->height); return d;}
// ============================================================// fbdev fallback — uses /dev/fb0 (EFI framebuffer via efifb)// ============================================================
static ACDisplay *fbdev_init(void) { // Try opening fb0 with retries (may take time to appear in devtmpfs) int fd = -1; for (int attempt = 0; attempt < 100; attempt++) { fd = open("/dev/fb0", O_RDWR); if (fd >= 0) break; usleep(20000); // 20ms } if (fd < 0) { fprintf(stderr, "[fbdev] Cannot open /dev/fb0 after retries\n"); // List what's in /dev for debugging return NULL; }
struct fb_var_screeninfo vinfo; struct fb_fix_screeninfo finfo; if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo) < 0 || ioctl(fd, FBIOGET_FSCREENINFO, &finfo) < 0) { fprintf(stderr, "[fbdev] Cannot get screen info\n"); close(fd); return NULL; }
fprintf(stderr, "[fbdev] Display: %dx%d, %d bpp, stride %d\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel, finfo.line_length); fprintf(stderr, "[fbdev] RGBA: %d/%d/%d/%d offset %d/%d/%d/%d\n", vinfo.red.length, vinfo.green.length, vinfo.blue.length, vinfo.transp.length, vinfo.red.offset, vinfo.green.offset, vinfo.blue.offset, vinfo.transp.offset);
if (vinfo.bits_per_pixel != 32) { // Try setting 32bpp vinfo.bits_per_pixel = 32; vinfo.red.offset = 16; vinfo.red.length = 8; vinfo.green.offset = 8; vinfo.green.length = 8; vinfo.blue.offset = 0; vinfo.blue.length = 8; vinfo.transp.offset = 24; vinfo.transp.length = 8; if (ioctl(fd, FBIOPUT_VSCREENINFO, &vinfo) < 0) { fprintf(stderr, "[fbdev] Cannot set 32bpp, using native %dbpp\n", vinfo.bits_per_pixel); // Re-read after failed set ioctl(fd, FBIOGET_VSCREENINFO, &vinfo); ioctl(fd, FBIOGET_FSCREENINFO, &finfo); if (vinfo.bits_per_pixel != 32) { fprintf(stderr, "[fbdev] Unsupported bpp: %d\n", vinfo.bits_per_pixel); close(fd); return NULL; } } ioctl(fd, FBIOGET_FSCREENINFO, &finfo); fprintf(stderr, "[fbdev] Set 32bpp: stride now %d\n", finfo.line_length); }
uint32_t map_size = finfo.line_length * vinfo.yres; uint32_t *map = mmap(0, map_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (map == MAP_FAILED) { perror("[fbdev] mmap failed"); close(fd); return NULL; }
// Clear to black memset(map, 0, map_size);
// Blank the console cursor / disable console int tty_fd = open("/dev/tty0", O_RDWR); if (tty_fd >= 0) { // KDSETMODE KD_GRAPHICS = 1 ioctl(tty_fd, 0x4B3A, 1); close(tty_fd); }
ACDisplay *d = calloc(1, sizeof(ACDisplay)); d->fd = fd; d->is_fbdev = 1; d->width = (int)vinfo.xres; d->height = (int)vinfo.yres; d->fbdev_map = map; d->fbdev_size = map_size; d->fbdev_stride = (int)(finfo.line_length / sizeof(uint32_t));
// Detect if framebuffer is BGR (blue at high offset, red at low) // Our internal format is ARGB: A<<24 | R<<16 | G<<8 | B // EFI framebuffers are often XBGR: X<<24 | B<<16 | G<<8 | R if (vinfo.blue.offset > vinfo.red.offset) { d->fbdev_swap_rb = 1; fprintf(stderr, "[fbdev] BGR format detected — will swap R/B\n"); }
// Use buffers[0].map as the software back buffer (we copy to fbdev_map on flip) uint32_t buf_size = (uint32_t)(d->width * d->height * 4); d->buffers[0].map = malloc(buf_size); d->buffers[0].size = buf_size; d->buffers[0].pitch = (uint32_t)(d->width * 4); memset(d->buffers[0].map, 0, buf_size);
fprintf(stderr, "[fbdev] Ready (%dx%d, swap_rb=%d)\n", d->width, d->height, d->fbdev_swap_rb); return d;}
// ============================================================// DRM display// ============================================================
static int try_open_drm(void) { const char *paths[] = { "/dev/dri/card0", "/dev/dri/card1", NULL }; for (int i = 0; paths[i]; i++) { int fd = open(paths[i], O_RDWR | O_CLOEXEC); if (fd >= 0) { if (drmSetMaster(fd) == 0) { fprintf(stderr, "[drm] Opened %s\n", paths[i]); return fd; } fprintf(stderr, "[drm] Opened %s (no master)\n", paths[i]); return fd; } } return -1;}
static int create_dumb_buffer(ACDisplay *d, int idx) { struct drm_mode_create_dumb create = { .width = (uint32_t)d->width, .height = (uint32_t)d->height, .bpp = 32, };
if (drmIoctl(d->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create) < 0) { perror("[drm] Create dumb buffer failed"); return -1; }
d->buffers[idx].handle = create.handle; d->buffers[idx].pitch = create.pitch; d->buffers[idx].size = create.size;
if (drmModeAddFB(d->fd, (uint32_t)d->width, (uint32_t)d->height, 24, 32, create.pitch, create.handle, &d->buffers[idx].fb_id) < 0) { perror("[drm] AddFB failed"); return -1; }
struct drm_mode_map_dumb map = { .handle = create.handle }; if (drmIoctl(d->fd, DRM_IOCTL_MODE_MAP_DUMB, &map) < 0) { perror("[drm] Map dumb buffer failed"); return -1; }
d->buffers[idx].map = mmap(0, create.size, PROT_READ | PROT_WRITE, MAP_SHARED, d->fd, map.offset); if (d->buffers[idx].map == MAP_FAILED) { perror("[drm] mmap failed"); return -1; }
memset(d->buffers[idx].map, 0, create.size); return 0;}
ACDisplay *drm_init(void) { extern void ac_log(const char *fmt, ...); ac_log("[drm] drm_init() start\n"); // SDL3 is now opt-in (was always-attempt with crash-recovery). The // dlopen + Mesa DRI load + SDL_Init + window/renderer creation take // 100s-of-ms even on cached cold-boot, with no measurable visual // benefit on the supported hardware. Skip it unless AC_USE_SDL=1 // explicitly. Going straight to DRM dumb buffers shaves real time // off boot and removes a class of partial-init "garbled char" bugs // that surface when SDL3 starts but Mesa's GLES context doesn't. const char *use_sdl = getenv("AC_USE_SDL"); if (use_sdl && use_sdl[0] == '1') { ACDisplay *sdl = sdl_init(); if (sdl) return sdl; ac_log("[drm] SDL3 failed, falling back to DRM dumb buffers\n"); } else { ac_log("[drm] SDL3 path skipped (set AC_USE_SDL=1 to enable)\n"); }
ACDisplay *d = calloc(1, sizeof(ACDisplay)); if (!d) { ac_log("[drm] calloc failed\n"); return NULL; }
ac_log("[drm] try_open_drm...\n"); d->fd = try_open_drm(); ac_log("[drm] fd=%d\n", d->fd); if (d->fd < 0) { ac_log("[drm] No DRM device, trying fbdev\n"); free(d); return fbdev_init(); }
uint64_t has_dumb; if (drmGetCap(d->fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 || !has_dumb) { fprintf(stderr, "[drm] No dumb buffer support, trying fbdev...\n"); close(d->fd); free(d); return fbdev_init(); }
drmModeRes *res = drmModeGetResources(d->fd); if (!res) { fprintf(stderr, "[drm] No resources, trying fbdev...\n"); close(d->fd); free(d); return fbdev_init(); }
// Prefer internal panel (eDP/LVDS) over external (HDMI/DP) to avoid slow EDID probes drmModeConnector *conn = NULL; // First pass: look for internal panel for (int i = 0; i < res->count_connectors; i++) { drmModeConnector *c = drmModeGetConnector(d->fd, res->connectors[i]); if (!c) continue; if (c->connection == DRM_MODE_CONNECTED && c->count_modes > 0 && (c->connector_type == DRM_MODE_CONNECTOR_eDP || c->connector_type == DRM_MODE_CONNECTOR_LVDS || c->connector_type == DRM_MODE_CONNECTOR_DSI)) { conn = c; fprintf(stderr, "[drm] Using internal panel (type %d)\n", c->connector_type); break; } drmModeFreeConnector(c); } // Second pass: any connected display if (!conn) { for (int i = 0; i < res->count_connectors; i++) { conn = drmModeGetConnector(d->fd, res->connectors[i]); if (conn && conn->connection == DRM_MODE_CONNECTED && conn->count_modes > 0) break; if (conn) drmModeFreeConnector(conn); conn = NULL; } }
if (!conn) { fprintf(stderr, "[drm] No connected display, trying fbdev...\n"); drmModeFreeResources(res); close(d->fd); free(d); return fbdev_init(); }
d->connector_id = conn->connector_id; d->mode = conn->modes[0]; d->width = d->mode.hdisplay; d->height = d->mode.vdisplay; fprintf(stderr, "[drm] Display: %dx%d @ %dHz\n", d->width, d->height, d->mode.vrefresh);
drmModeEncoder *enc = NULL; if (conn->encoder_id) { enc = drmModeGetEncoder(d->fd, conn->encoder_id); } if (!enc) { for (int i = 0; i < conn->count_encoders; i++) { enc = drmModeGetEncoder(d->fd, conn->encoders[i]); if (enc) break; } } if (!enc) { fprintf(stderr, "[drm] No encoder, trying fbdev...\n"); drmModeFreeConnector(conn); drmModeFreeResources(res); close(d->fd); free(d); return fbdev_init(); }
d->crtc_id = enc->crtc_id; if (!d->crtc_id) { for (int i = 0; i < res->count_crtcs; i++) { if (enc->possible_crtcs & (1u << i)) { d->crtc_id = res->crtcs[i]; break; } } }
d->saved_crtc = drmModeGetCrtc(d->fd, d->crtc_id);
drmModeFreeEncoder(enc); drmModeFreeConnector(conn); drmModeFreeResources(res);
if (create_dumb_buffer(d, 0) < 0 || create_dumb_buffer(d, 1) < 0) { drm_destroy(d); return fbdev_init(); }
d->front = 0;
if (drmModeSetCrtc(d->fd, d->crtc_id, d->buffers[0].fb_id, 0, 0, &d->connector_id, 1, &d->mode) < 0) { perror("[drm] SetCrtc failed, trying fbdev..."); drm_destroy(d); return fbdev_init(); }
fprintf(stderr, "[drm] Ready\n"); return d;}
void drm_flip(ACDisplay *d) { if (d->is_fbdev) { uint32_t *src = d->buffers[0].map; uint32_t *dst = d->fbdev_map; int src_stride = d->width; int dst_stride = d->fbdev_stride; if (d->fbdev_swap_rb) { // Convert ARGB → ABGR (swap R and B channels) for (int y = 0; y < d->height; y++) { uint32_t *s = src + y * src_stride; uint32_t *dd = dst + y * dst_stride; for (int x = 0; x < d->width; x++) { uint32_t p = s[x]; dd[x] = (p & 0xFF00FF00u) | // keep A and G ((p & 0x00FF0000u) >> 16) | // R → B ((p & 0x000000FFu) << 16); // B → R } } } else { for (int y = 0; y < d->height; y++) { memcpy(dst + y * dst_stride, src + y * src_stride, (size_t)(d->width * 4)); } } return; } int back = 1 - d->front; // Page flip synchronized to vblank (prevents tearing) if (drmModePageFlip(d->fd, d->crtc_id, d->buffers[back].fb_id, DRM_MODE_PAGE_FLIP_EVENT, d) == 0) { // Wait for the flip event (blocks until vblank) fd_set fds; FD_ZERO(&fds); FD_SET(d->fd, &fds); struct timeval tv = { .tv_sec = 0, .tv_usec = 50000 }; // 50ms timeout if (select(d->fd + 1, &fds, NULL, NULL, &tv) > 0) { drmEventContext ev = { .version = 2, .page_flip_handler = NULL, // we just need to consume the event }; drmHandleEvent(d->fd, &ev); } } else { // Fallback to SetCrtc if page flip not supported drmModeSetCrtc(d->fd, d->crtc_id, d->buffers[back].fb_id, 0, 0, &d->connector_id, 1, &d->mode); } d->front = back;}
uint32_t *drm_back_buffer(ACDisplay *d) { if (d->is_fbdev) { return d->buffers[0].map; } return d->buffers[1 - d->front].map;}
int drm_back_stride(ACDisplay *d) { if (d->is_fbdev) { return d->width; } return (int)(d->buffers[1 - d->front].pitch / sizeof(uint32_t));}
uint32_t *drm_front_buffer(ACDisplay *d) { if (d->is_fbdev) return d->fbdev_map; return d->buffers[d->front].map;}
int drm_front_stride(ACDisplay *d) { if (d->is_fbdev) return d->fbdev_stride; return (int)(d->buffers[d->front].pitch / sizeof(uint32_t));}
void display_present(ACDisplay *d, ACFramebuffer *screen, int scale) { if (!d || !screen) return;
if (d->is_sdl && sdl.CreateTexture) { // Create/recreate texture if framebuffer size changed if (!d->sdl_texture || d->sdl_tex_w != screen->width || d->sdl_tex_h != screen->height) { if (d->sdl_texture && sdl.DestroyTexture) sdl.DestroyTexture(d->sdl_texture); d->sdl_texture = sdl.CreateTexture(d->sdl_renderer, 0x16362004, 1, // SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING screen->width, screen->height); if (d->sdl_texture && sdl.SetTextureScaleMode) { sdl.SetTextureScaleMode(d->sdl_texture, 0); // SDL_SCALEMODE_NEAREST } d->sdl_tex_w = screen->width; d->sdl_tex_h = screen->height; fprintf(stderr, "[sdl3] Created texture %dx%d\n", screen->width, screen->height); } if (sdl.UpdateTexture) sdl.UpdateTexture(d->sdl_texture, NULL, screen->pixels, screen->stride * (int)sizeof(uint32_t)); if (sdl.RenderClear) sdl.RenderClear(d->sdl_renderer); if (sdl.RenderTexture) sdl.RenderTexture(d->sdl_renderer, d->sdl_texture, NULL, NULL); if (sdl.RenderPresent) sdl.RenderPresent(d->sdl_renderer); return; } (void)scale; // CPU fallback: scale to back buffer and flip fb_copy_scaled(screen, drm_back_buffer(d), d->width, d->height, drm_back_stride(d), scale); drm_flip(d);}
// ============================================================// Secondary HDMI display — solid color fill// ============================================================
// Pixel budget for the CPU-scaled dumb buffer. Above this the per-frame copy// (32-bit, uncached write-combined memory) costs tens of ms on the upcycled// laptops we run on, and 4K sinks usually only offer 30Hz over old HDMI// anyway — so 1080p/1200p class modes win unless nothing smaller fits.#define AC_HDMI_MAX_PIXELS (1920 * 1200)
// Choose the HDMI mode for mirroring a screen_w×screen_h framebuffer.// Ranking, most important first:// 1. within the pixel budget (cheap to fill every frame)// 2. largest integer scale of the screen that fits (crisp, no resampling)// 3. aspect closest to the screen's (less letterbox)// 4. highest refresh (60 over 50/30/24)// 5. the sink's preferred flag, then larger area, then list order// Interlaced and doublescan modes are skipped. Returns the index into// conn->modes; never fails when count_modes > 0.static int drm_pick_secondary_mode(drmModeConnector *conn, int screen_w, int screen_h, char *why, size_t why_len) { int best = 0; long best_key[6] = {0}; int have_best = 0; if (screen_w < 1) screen_w = 1; if (screen_h < 1) screen_h = 1; for (int i = 0; i < conn->count_modes; i++) { drmModeModeInfo *m = &conn->modes[i]; if (m->flags & (DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLSCAN)) continue; if (m->hdisplay == 0 || m->vdisplay == 0) continue; long pixels = (long)m->hdisplay * m->vdisplay; int sx = m->hdisplay / screen_w, sy = m->vdisplay / screen_h; int scale = sx < sy ? sx : sy; // aspect distance ×1000, smaller is better → negate so bigger wins long aspect = -labs((long)m->hdisplay * 1000 / m->vdisplay - (long)screen_w * 1000 / screen_h); // A screen bigger than every mode (scale 0) gets clipped, so there // the largest mode matters more than its aspect. long key[6] = { pixels <= AC_HDMI_MAX_PIXELS ? 1 : 0, scale, scale > 0 ? aspect : pixels, m->vrefresh, (m->type & DRM_MODE_TYPE_PREFERRED) ? 1 : 0, pixels, }; int better = !have_best; for (int k = 0; k < 6 && !better; k++) { if (key[k] > best_key[k]) better = 1; else if (key[k] < best_key[k]) break; } if (better) { best = i; memcpy(best_key, key, sizeof(key)); have_best = 1; } } if (why) { drmModeModeInfo *m = &conn->modes[best]; long pixels = (long)m->hdisplay * m->vdisplay; snprintf(why, why_len, "scale=%ld budget=%s aspect_err=%ld/1000 %s", best_key[1], pixels <= AC_HDMI_MAX_PIXELS ? "ok" : "over", -best_key[2], (m->type & DRM_MODE_TYPE_PREFERRED) ? "preferred" : ""); } return best;}
ACSecondaryDisplay *drm_init_secondary(ACDisplay *primary, int screen_w, int screen_h) { if (!primary || primary->is_fbdev || primary->fd < 0) return NULL; if (primary->is_sdl) return NULL;
drmModeRes *res = drmModeGetResources(primary->fd); if (!res) return NULL;
drmModeConnector *conn = NULL; for (int i = 0; i < res->count_connectors; i++) { drmModeConnector *c = drmModeGetConnector(primary->fd, res->connectors[i]); if (!c) continue; if (c->connector_id == primary->connector_id) { drmModeFreeConnector(c); continue; } if (c->connection == DRM_MODE_CONNECTED && c->count_modes > 0 && (c->connector_type == DRM_MODE_CONNECTOR_HDMIA || c->connector_type == DRM_MODE_CONNECTOR_HDMIB || c->connector_type == DRM_MODE_CONNECTOR_DisplayPort)) { conn = c; break; } drmModeFreeConnector(c); } drmModeFreeResources(res);
if (!conn) { ac_log("[drm-secondary] No HDMI/DP display found\n"); return NULL; }
// Log what the sink offers (first few + total) so /logs shows the menu // the picker chose from. { char menu[256]; int off = 0; for (int i = 0; i < conn->count_modes && off < (int)sizeof(menu) - 24; i++) { off += snprintf(menu + off, sizeof(menu) - (size_t)off, "%s%dx%d@%d%s", i ? " " : "", conn->modes[i].hdisplay, conn->modes[i].vdisplay, conn->modes[i].vrefresh, (conn->modes[i].flags & DRM_MODE_FLAG_INTERLACE) ? "i" : ""); } ac_log("[drm-secondary] connector %u type %u: %d modes: %s%s\n", conn->connector_id, conn->connector_type, conn->count_modes, menu, off >= (int)sizeof(menu) - 24 ? " ..." : ""); }
char why[128] = ""; int mi = drm_pick_secondary_mode(conn, screen_w, screen_h, why, sizeof(why));
ACSecondaryDisplay *s = calloc(1, sizeof(ACSecondaryDisplay)); s->fd = primary->fd; s->connector_id = conn->connector_id; s->mode = conn->modes[mi]; s->width = s->mode.hdisplay; s->height = s->mode.vdisplay; s->present_every = ((long)s->width * s->height <= AC_HDMI_MAX_PIXELS) ? 1 : 8; ac_log("[drm-secondary] picked %dx%d@%dHz for screen %dx%d (%s) every=%d\n", s->width, s->height, s->mode.vrefresh, screen_w, screen_h, why, s->present_every);
drmModeEncoder *enc = NULL; if (conn->encoder_id) enc = drmModeGetEncoder(primary->fd, conn->encoder_id); if (!enc) { for (int i = 0; i < conn->count_encoders; i++) { enc = drmModeGetEncoder(primary->fd, conn->encoders[i]); if (enc && enc->crtc_id != primary->crtc_id) break; if (enc) { drmModeFreeEncoder(enc); enc = NULL; } } } drmModeFreeConnector(conn);
if (!enc) { ac_log("[drm-secondary] No encoder\n"); free(s); return NULL; }
s->crtc_id = enc->crtc_id; if (!s->crtc_id) { drmModeRes *r2 = drmModeGetResources(primary->fd); if (r2) { for (int i = 0; i < r2->count_crtcs; i++) { if (r2->crtcs[i] != primary->crtc_id && (enc->possible_crtcs & (1 << i))) { s->crtc_id = r2->crtcs[i]; break; } } drmModeFreeResources(r2); } } drmModeFreeEncoder(enc);
if (!s->crtc_id) { ac_log("[drm-secondary] No free CRTC\n"); free(s); return NULL; }
s->saved_crtc = drmModeGetCrtc(primary->fd, s->crtc_id);
// Allocate two dumb buffers for double-buffering for (int b = 0; b < 2; b++) { struct drm_mode_create_dumb create = { .width = s->width, .height = s->height, .bpp = 32 }; if (drmIoctl(s->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create) < 0) { ac_log("[drm-secondary] Create dumb buffer %d failed\n", b); free(s); return NULL; } s->bufs[b].handle = create.handle; s->bufs[b].pitch = create.pitch; s->bufs[b].size = create.size;
if (drmModeAddFB(s->fd, s->width, s->height, 24, 32, s->bufs[b].pitch, s->bufs[b].handle, &s->bufs[b].fb_id) < 0) { ac_log("[drm-secondary] AddFB %d failed\n", b); struct drm_mode_destroy_dumb destroy = { .handle = s->bufs[b].handle }; drmIoctl(s->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy); free(s); return NULL; }
struct drm_mode_map_dumb map_req = { .handle = s->bufs[b].handle }; if (drmIoctl(s->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req) < 0) { ac_log("[drm-secondary] Map %d failed\n", b); drmModeRmFB(s->fd, s->bufs[b].fb_id); struct drm_mode_destroy_dumb destroy = { .handle = s->bufs[b].handle }; drmIoctl(s->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy); free(s); return NULL; } s->bufs[b].map = mmap(0, s->bufs[b].size, PROT_READ | PROT_WRITE, MAP_SHARED, s->fd, map_req.offset); if (s->bufs[b].map == MAP_FAILED) { ac_log("[drm-secondary] mmap %d failed\n", b); drmModeRmFB(s->fd, s->bufs[b].fb_id); struct drm_mode_destroy_dumb destroy = { .handle = s->bufs[b].handle }; drmIoctl(s->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy); free(s); return NULL; } // Clear to black memset(s->bufs[b].map, 0, s->bufs[b].size); }
s->buf_front = 0; if (drmModeSetCrtc(s->fd, s->crtc_id, s->bufs[0].fb_id, 0, 0, &s->connector_id, 1, &s->mode) < 0) { ac_log("[drm-secondary] SetCrtc failed\n"); for (int b = 0; b < 2; b++) { munmap(s->bufs[b].map, s->bufs[b].size); drmModeRmFB(s->fd, s->bufs[b].fb_id); struct drm_mode_destroy_dumb destroy = { .handle = s->bufs[b].handle }; drmIoctl(s->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy); } free(s); return NULL; }
// Small internal render target (1/8 native res for fast CPU rendering) int sw = (s->width + 7) / 8; int sh = (s->height + 7) / 8; s->small_fb = fb_create(sw, sh); if (!s->small_fb) { ac_log("[drm-secondary] fb_create small failed\n"); // non-fatal — will fall back to full-res if NULL }
s->active = 1; ac_log("[drm-secondary] HDMI output active %dx%d (small %dx%d)\n", s->width, s->height, sw, sh); return s;}
int drm_secondary_is_connected(ACDisplay *primary) { if (!primary || primary->is_fbdev || primary->fd < 0) return 0; drmModeRes *res = drmModeGetResources(primary->fd); if (!res) return 0; int found = 0; for (int i = 0; i < res->count_connectors && !found; i++) { drmModeConnector *c = drmModeGetConnector(primary->fd, res->connectors[i]); if (!c) continue; if (c->connector_id != primary->connector_id && c->connection == DRM_MODE_CONNECTED && c->count_modes > 0 && (c->connector_type == DRM_MODE_CONNECTOR_HDMIA || c->connector_type == DRM_MODE_CONNECTOR_HDMIB || c->connector_type == DRM_MODE_CONNECTOR_DisplayPort)) found = 1; drmModeFreeConnector(c); } drmModeFreeResources(res); return found;}
void drm_secondary_present_waveform(ACSecondaryDisplay *s, ACGraph *g, float *waveform, int wf_size, int wf_pos) { if (!s || !s->active || !g) return;
// Use small_fb as the render target (1/8 native res — fast) ACFramebuffer *render_fb = s->small_fb; if (!render_fb) return; // nothing to render into
int rw = render_fb->width; int rh = render_fb->height;
// Save graph's original target and switch to small buffer ACFramebuffer *orig_target = g->fb; graph_page(g, render_fb);
// Dark background graph_wipe(g, (ACColor){8, 8, 16, 255});
// Draw waveform — quantized bars, two-tone top/bottom if (waveform) { // Use 64 bars across the width for a clean spectrum look int N = 64; int bar_w = rw / N; if (bar_w < 1) bar_w = 1;
for (int i = 0; i < N; i++) { int idx = (wf_pos + wf_size - N + i) % wf_size; float sample = waveform[idx]; float amp = sample < 0 ? -sample : sample; // abs int bar_h = (int)(amp * rh * 0.92f); if (bar_h < 1) bar_h = 1; if (bar_h > rh) bar_h = rh;
int x = i * bar_w; int gap = bar_w > 2 ? 1 : 0; // 1px gap between bars if wide enough
// Top region (empty space above bar) — slightly lighter than bg graph_ink(g, (ACColor){16, 24, 40, 255}); graph_box(g, x, 0, bar_w - gap, rh - bar_h, 1);
// Bottom bar fill — bright blue/cyan graph_ink(g, (ACColor){60, 160, 240, 255}); graph_box(g, x, rh - bar_h, bar_w - gap, bar_h, 1);
// Top 2px of each bar: bright accent graph_ink(g, (ACColor){160, 220, 255, 255}); graph_box(g, x, rh - bar_h, bar_w - gap, 2, 1); } }
// Resolution text (shows native res) at small scale char res_str[32]; snprintf(res_str, sizeof(res_str), "%dx%d", s->width, s->height); graph_ink(g, (ACColor){160, 160, 180, 255}); font_draw(g, res_str, 2, 2, 1);
// Restore original render target graph_page(g, orig_target);
// Scale small_fb up to HDMI back buffer int back = 1 - s->buf_front; int dst_stride = (int)(s->bufs[back].pitch / sizeof(uint32_t)); fb_copy_scaled(render_fb, s->bufs[back].map, s->width, s->height, dst_stride, 8);
// Vblank-synced flip (see drm_secondary_present_mirror for why no // event flag and why -EBUSY just drops the frame). int ret = drmModePageFlip(s->fd, s->crtc_id, s->bufs[back].fb_id, 0, NULL); if (ret == 0) { s->buf_front = back; return; } if (ret == -EBUSY) { s->flips_dropped++; return; } drmModeSetCrtc(s->fd, s->crtc_id, s->bufs[back].fb_id, 0, 0, &s->connector_id, 1, &s->mode); s->buf_front = back;}
void drm_secondary_present_mirror(ACSecondaryDisplay *s, ACFramebuffer *screen) { if (!s || !s->active || !screen || screen->width < 1 || screen->height < 1) return;
int sw = screen->width, sh = screen->height; // Fit the whole image to the sink, preserving aspect to the nearest // output pixel. Fractional nearest-neighbor scaling fills a 1080p TV // from a 683x384 screen instead of leaving a 1366x768 island in it. int dst_w=s->width; int dst_h=(int)(((int64_t)dst_w*sh+sw-1)/sw); if(dst_h>s->height) { dst_h=s->height; dst_w=(int)(((int64_t)dst_h*sw+sh-1)/sh); } if (sw != s->mirror_w || sh != s->mirror_h) { s->mirror_x = (s->width-dst_w)/2; s->mirror_y = (s->height-dst_h)/2; s->mirror_w = sw; s->mirror_h = sh; // Letterbox bars change with the source size — reblack both buffers. for (int b = 0; b < 2; b++) if (s->bufs[b].map && s->bufs[b].map != MAP_FAILED) memset(s->bufs[b].map, 0, s->bufs[b].size); ac_log("[drm-secondary] mirror %dx%d -> %dx%d at %d,%d on %dx%d (nearest)\n", sw, sh, dst_w, dst_h, s->mirror_x, s->mirror_y, s->width, s->height); }
int back = 1 - s->buf_front; int stride = (int)(s->bufs[back].pitch / sizeof(uint32_t)); uint32_t *dst = s->bufs[back].map + (size_t)s->mirror_y * stride + s->mirror_x; fb_copy_resized(screen, dst, dst_w, dst_h, stride);
// Plain vblank-synced flip, no event: the primary's drm_flip waits on // this same fd for *its* flip event, so the secondary must not emit // any. -EBUSY means the previous flip is still queued (the two panels' // 60Hz clocks drift) — drop this frame rather than tear or block. int ret = drmModePageFlip(s->fd, s->crtc_id, s->bufs[back].fb_id, 0, NULL); if (ret == 0) { s->buf_front = back; return; } if (ret == -EBUSY) { s->flips_dropped++; return; } // Flip refused outright (driver won't flip this CRTC/buffer). Fall back // to a full mode-set — but say so once; doing this silently every frame // was the visible flicker on HDMI. if (!s->flip_refused_logged) { ac_log("[drm-secondary] page flip refused (%d) — falling back to SetCrtc\n", ret); s->flip_refused_logged = 1; } drmModeSetCrtc(s->fd, s->crtc_id, s->bufs[back].fb_id, 0, 0, &s->connector_id, 1, &s->mode); s->buf_front = back;}
void drm_secondary_fill(ACSecondaryDisplay *s, uint8_t r, uint8_t g, uint8_t b) { if (!s || !s->active || !s->bufs[0].map) return; uint32_t pixel = ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b; int back = 1 - s->buf_front; uint32_t *p = s->bufs[back].map; int total = s->width * s->height; for (int i = 0; i < total; i++) p[i] = pixel; drmModeSetCrtc(s->fd, s->crtc_id, s->bufs[back].fb_id, 0, 0, &s->connector_id, 1, &s->mode); s->buf_front = back;}
void drm_secondary_destroy(ACSecondaryDisplay *s) { if (!s) return; if (s->saved_crtc) { drmModeSetCrtc(s->fd, s->saved_crtc->crtc_id, s->saved_crtc->buffer_id, s->saved_crtc->x, s->saved_crtc->y, &s->connector_id, 1, &s->saved_crtc->mode); drmModeFreeCrtc(s->saved_crtc); } if (s->small_fb) fb_destroy(s->small_fb); for (int b = 0; b < 2; b++) { if (s->bufs[b].map && s->bufs[b].map != MAP_FAILED) munmap(s->bufs[b].map, s->bufs[b].size); if (s->bufs[b].fb_id) drmModeRmFB(s->fd, s->bufs[b].fb_id); if (s->bufs[b].handle) { struct drm_mode_destroy_dumb destroy = { .handle = s->bufs[b].handle }; drmIoctl(s->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy); } } free(s);}
const char *drm_display_driver(ACDisplay *d) { if (!d) return "none"; if (d->is_sdl) { static char buf[48]; snprintf(buf, sizeof(buf), "sdl3:%s", d->sdl_renderer_name); return buf; } if (d->is_fbdev) return "fbdev"; return "drm";}
void drm_destroy(ACDisplay *d) { if (!d) return;
if (d->is_sdl) { if (d->sdl_texture && sdl.DestroyTexture) sdl.DestroyTexture(d->sdl_texture); if (d->sdl_renderer && sdl.DestroyRenderer) sdl.DestroyRenderer(d->sdl_renderer); if (d->sdl_window && sdl.DestroyWindow) sdl.DestroyWindow(d->sdl_window); if (sdl.Quit) sdl.Quit(); free(d); return; }
if (d->is_fbdev) { // Restore console text mode int tty_fd = open("/dev/tty0", O_RDWR); if (tty_fd >= 0) { ioctl(tty_fd, 0x4B3A, 0); // KD_TEXT close(tty_fd); } if (d->fbdev_map && d->fbdev_map != MAP_FAILED) munmap(d->fbdev_map, d->fbdev_size); if (d->buffers[0].map) free(d->buffers[0].map); if (d->fd >= 0) close(d->fd); free(d); return; }
if (d->saved_crtc) { drmModeSetCrtc(d->fd, d->saved_crtc->crtc_id, d->saved_crtc->buffer_id, d->saved_crtc->x, d->saved_crtc->y, &d->connector_id, 1, &d->saved_crtc->mode); drmModeFreeCrtc(d->saved_crtc); }
for (int i = 0; i < 2; i++) { if (d->buffers[i].map && d->buffers[i].map != MAP_FAILED) munmap(d->buffers[i].map, d->buffers[i].size); if (d->buffers[i].fb_id) drmModeRmFB(d->fd, d->buffers[i].fb_id); if (d->buffers[i].handle) { struct drm_mode_destroy_dumb destroy = { .handle = d->buffers[i].handle }; drmIoctl(d->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy); } }
if (d->fd >= 0) close(d->fd); free(d);}
// Release DRM master so another process (cage) can take the displayint drm_release_master(void *display) { ACDisplay *d = (ACDisplay *)display; if (!d || d->is_fbdev || d->fd < 0) return -1; int rc = drmDropMaster(d->fd); // Also close the fd so cage can open /dev/dri/card0 exclusively close(d->fd); d->fd = -1; return rc;}
// Reclaim DRM master after browser exits — reopen the deviceint drm_acquire_master(void *display) { ACDisplay *d = (ACDisplay *)display; if (!d || d->is_fbdev) return -1; if (d->fd < 0) { // Reopen the DRM device const char *paths[] = {"/dev/dri/card0", "/dev/dri/card1", NULL}; for (int i = 0; paths[i]; i++) { d->fd = open(paths[i], O_RDWR | O_CLOEXEC); if (d->fd >= 0) break; } if (d->fd < 0) return -1; } return drmSetMaster(d->fd);}