aboutsummaryrefslogtreecommitdiffstats
path: root/src/render.c
diff options
context:
space:
mode:
authorgbc dev <gbc@localhost>2026-07-14 23:26:49 +0200
committergbc dev <gbc@localhost>2026-07-14 23:26:49 +0200
commitc879adc43b91a53eb7e76f232a39abb56d8144c1 (patch)
tree3bd8cd77fed46ceb62504d2fb067f349bad2cf0e /src/render.c
parentspeed control: frame skipping to decouple terminal draw from emulation (diff)
downloadsl0pboy-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.c135
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;