diff options
| author | gbc dev <gbc@localhost> | 2026-07-14 23:26:49 +0200 |
|---|---|---|
| committer | gbc dev <gbc@localhost> | 2026-07-14 23:26:49 +0200 |
| commit | c879adc43b91a53eb7e76f232a39abb56d8144c1 (patch) | |
| tree | 3bd8cd77fed46ceb62504d2fb067f349bad2cf0e /src/render.c | |
| parent | speed control: frame skipping to decouple terminal draw from emulation (diff) | |
| download | sl0pboy-c879adc43b91a53eb7e76f232a39abb56d8144c1.tar.gz sl0pboy-c879adc43b91a53eb7e76f232a39abb56d8144c1.tar.xz sl0pboy-c879adc43b91a53eb7e76f232a39abb56d8144c1.zip | |
control: socket-based debug channel + gbctl CLI driver
Replace the input-only FIFO's limitations with a Unix-domain socket control
channel (--sock) that is a superset of the button vocabulary plus emulator
introspection: read/write bus memory, get/set CPU context, single-step, PC
breakpoints, value-change watchpoints, run/pause. Being a socket it replies to
each command and broadcasts async 'event stop ...' lines; a stored last-stop
record (stopinfo) lets per-request clients recover a missed event.
Add gbctl: a stdlib-python CLI that spawns a tmux pane running the emulator on
a ROM, auto-resolves the socket, and drives it with terse verbs (cpu/read/write/
reg/step/break/watch/continue/pause/press/hold/screen/stop). The FIFO stays as
legacy input-only.
Diffstat (limited to 'src/render.c')
| -rw-r--r-- | src/render.c | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/src/render.c b/src/render.c index df39c99..2320f02 100644 --- a/src/render.c +++ b/src/render.c @@ -54,6 +54,31 @@ static volatile sig_atomic_t resized = 0; // set by SIGWINCH static int hud_frameskip = 0; static bool hud_turbo = false; +// ---- sixel output ---- +static bool sixel_enabled = false; +static int sixel_scale = 2; +static char *sixel_buf = NULL; // reusable output buffer +static size_t sixel_buf_cap = 0; + +void render_set_sixel(bool on, int scale) { + if (scale < 1) scale = 1; + if (scale > 6) scale = 6; + sixel_enabled = on; + sixel_scale = scale; + if (on) { + int W2 = SCREEN_W * scale, H2 = SCREEN_H * scale; + int bands = (H2 + 5) / 6; + // worst case: every band emits every palette color across full width + size_t cap = (size_t)bands * 256 * (W2 + 8) + 65536; + if (cap > sixel_buf_cap) { + free(sixel_buf); + sixel_buf = malloc(cap); + sixel_buf_cap = sixel_buf ? cap : 0; + } + } + need_full = true; +} + void render_force_full(void) { need_full = true; } // Update the on-screen HUD values; forces a status-line repaint on change. @@ -66,7 +91,110 @@ void render_set_hud(int frameskip, bool turbo) { } static void on_winch(int s) { (void)s; resized = 1; } +// ---- sixel encoder --------------------------------------------------------- +// The GB framebuffer is truecolor RGB but any given frame uses few distinct +// colors (4 shades on DMG, a modest set on CGB). We build a per-frame palette +// (<=256 entries), quantizing more coarsely only if a frame overflows, then +// emit standard sixel bands. +static int pal_r[256], pal_g[256], pal_b[256]; +static int pal_key[256]; +static u8 idxmap[SCREEN_H][SCREEN_W]; // palette index per source pixel + +static int build_palette(PPU *p) { + static int htab[8192]; + for (int shift = 0; ; shift++) { + memset(htab, -1, sizeof(htab)); + int mask = (0xFF << shift) & 0xFF; + int n = 0, overflow = 0; + for (int y = 0; y < SCREEN_H && !overflow; y++) { + for (int x = 0; x < SCREEN_W; x++) { + int r = p->fb[y][x][0] & mask; + int g = p->fb[y][x][1] & mask; + int b = p->fb[y][x][2] & mask; + int key = (r << 16) | (g << 8) | b; + unsigned h = ((unsigned)key * 2654435761u) & 8191u; + while (htab[h] != -1 && pal_key[htab[h]] != key) + h = (h + 1) & 8191u; + if (htab[h] == -1) { + if (n >= 256) { overflow = 1; break; } + htab[h] = n; + pal_key[n] = key; + pal_r[n] = r; pal_g[n] = g; pal_b[n] = b; + n++; + } + idxmap[y][x] = (u8)htab[h]; + } + } + if (!overflow || shift >= 7) return n; + } +} + +static void render_frame_sixel(GB *gb) { + PPU *p = &gb->ppu; + if (!sixel_buf) return; + int sc = sixel_scale; + int W2 = SCREEN_W * sc, H2 = SCREEN_H * sc; + int ncol = build_palette(p); + char *o = sixel_buf; + + // home cursor, start sixel with 1:1 pixel aspect + raster size + o += sprintf(o, "\x1b[H\x1bPq\"1;1;%d;%d", W2, H2); + for (int i = 0; i < ncol; i++) // palette (0-100 percent) + o += sprintf(o, "#%d;2;%d;%d;%d", i, + (pal_r[i] * 100 + 127) / 255, + (pal_g[i] * 100 + 127) / 255, + (pal_b[i] * 100 + 127) / 255); + + static u8 present[256]; + static u8 sx[SCREEN_W * 6]; // sixel value per column for the active color + for (int by = 0; by < H2; by += 6) { + // which palette colors appear in this 6-row band? + memset(present, 0, ncol); + int rows = (H2 - by < 6) ? (H2 - by) : 6; + for (int k = 0; k < rows; k++) { + int sy = (by + k) / sc; + for (int x = 0; x < SCREEN_W; x++) present[idxmap[sy][x]] = 1; + } + int emitted = 0; + for (int c = 0; c < ncol; c++) { + if (!present[c]) continue; + if (emitted) *o++ = '$'; // graphics CR: back to band start + emitted = 1; + // build this color's 6-bit column values + for (int X2 = 0; X2 < W2; X2++) { + int sx0 = X2 / sc, bits = 0; + for (int k = 0; k < rows; k++) + if (idxmap[(by + k) / sc][sx0] == c) bits |= (1 << k); + sx[X2] = (u8)(0x3f + bits); + } + o += sprintf(o, "#%d", c); + // run-length encode + int X2 = 0; + while (X2 < W2) { + int run = 1; + while (X2 + run < W2 && sx[X2 + run] == sx[X2]) run++; + if (run >= 4) o += sprintf(o, "!%d%c", run, sx[X2]); + else for (int j = 0; j < run; j++) *o++ = sx[X2]; + X2 += run; + } + } + *o++ = '-'; // next band + } + o += sprintf(o, "\x1b\\"); // ST: end sixel + + // status line just below the image + o += sprintf(o, "\r\n\x1b[38;2;150;150;150m %.*s skip:%d%s " + "[f]ast [ ][ ] q:quit\x1b[0m\x1b[K", + 12, gb->cart.title, hud_frameskip, + hud_turbo ? " TURBO" : ""); + + need_full = false; + fwrite(sixel_buf, 1, o - sixel_buf, stdout); + fflush(stdout); +} + void render_frame(GB *gb) { + if (sixel_enabled) { render_frame_sixel(gb); return; } PPU *p = &gb->ppu; char *o = outbuf; @@ -190,6 +318,13 @@ static void handle_line(char *line) { handle_token(t); } +// Public entry: process a line of button tokens from any control channel. +void input_handle_command(const char *line) { + char tmp[256]; + snprintf(tmp, sizeof(tmp), "%s", line); + handle_line(tmp); +} + void input_open_fifo(const char *path) { snprintf(fifo_path, sizeof(fifo_path), "%s", path); if (mkfifo(fifo_path, 0666) == 0) fifo_created = true; |
