aboutsummaryrefslogtreecommitdiffstats
path: root/src/render.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/render.c')
-rw-r--r--src/render.c371
1 files changed, 332 insertions, 39 deletions
diff --git a/src/render.c b/src/render.c
index 76a4ad0..4f612ae 100644
--- a/src/render.c
+++ b/src/render.c
@@ -55,7 +55,18 @@ static int hud_frameskip = 0;
static bool hud_turbo = false;
// ---- sixel output ----
+// The GB body "chrome" (see below) surrounds the LCD, so the sixel canvas can
+// be larger than the raw screen. Size all buffers for the chrome canvas.
+#define CH_SIDE 40 // body margin left/right of the LCD (unscaled px)
+#define CH_TOP 46 // body area above the LCD
+#define CH_BOTTOM 172 // body area below the LCD (branding + controls live here)
+#define CHROME_W (SCREEN_W + 2*CH_SIDE) // 240
+#define CHROME_H (SCREEN_H + CH_TOP + CH_BOTTOM) // 362
+#define LCD_X0 CH_SIDE
+#define LCD_Y0 CH_TOP
+
static bool sixel_enabled = false;
+static bool chrome_enabled = false;
static int sixel_scale = 2;
static char *sixel_buf = NULL; // reusable output buffer
static size_t sixel_buf_cap = 0;
@@ -66,7 +77,8 @@ void render_set_sixel(bool on, int scale) {
sixel_enabled = on;
sixel_scale = scale;
if (on) {
- int W2 = SCREEN_W * scale, H2 = SCREEN_H * scale;
+ // Size for the largest canvas we might emit (the chrome frame).
+ int W2 = CHROME_W * scale, H2 = CHROME_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;
@@ -79,8 +91,55 @@ void render_set_sixel(bool on, int scale) {
need_full = true;
}
+// Enable the drawn Game Boy body/frame around the LCD (sixel mode only).
+void render_set_chrome(bool on) {
+ chrome_enabled = on;
+ need_full = true;
+}
+
void render_force_full(void) { need_full = true; }
+// ---- LCD recolor filter ----------------------------------------------------
+// Optionally remap every LCD pixel to a 4-shade palette by luminance, giving
+// the authentic monochrome-LCD look (and greening color CGB games for that
+// retro vibe). Applied to every render path (half-block, sixel, chrome).
+static bool lcd_recolor = false;
+static u8 lcd_pal[4][3];
+
+void render_set_palette(const char *name) {
+ // classic DMG-01 "pea green" (#9bbc0f ramp) and a GB-Pocket-ish grayscale
+ static const u8 dmg_green[4][3] =
+ { {155,188,15}, {139,172,15}, {48,98,48}, {15,56,15} };
+ static const u8 pocket_gray[4][3] =
+ { {224,224,224}, {168,168,168}, {104,104,104}, {40,40,40} };
+ if (!name) { lcd_recolor = false; return; }
+ if (!strcasecmp(name, "dmg") || !strcasecmp(name, "green")) {
+ memcpy(lcd_pal, dmg_green, sizeof(lcd_pal)); lcd_recolor = true;
+ } else if (!strcasecmp(name, "pocket") || !strcasecmp(name, "gray")
+ || !strcasecmp(name, "grey")) {
+ memcpy(lcd_pal, pocket_gray, sizeof(lcd_pal)); lcd_recolor = true;
+ } else {
+ lcd_recolor = false; // unknown name -> passthrough
+ }
+ need_full = true;
+}
+
+// Return the framebuffer to draw: the raw PPU output, or a recolored copy.
+static u8 fb_scratch[SCREEN_H][SCREEN_W][3];
+static u8 (*lcd_fb(PPU *p))[SCREEN_W][3] {
+ if (!lcd_recolor) return p->fb;
+ for (int y = 0; y < SCREEN_H; y++)
+ for (int x = 0; x < SCREEN_W; x++) {
+ const u8 *in = p->fb[y][x];
+ int lum = (in[0]*77 + in[1]*150 + in[2]*29) >> 8; // 0..255
+ int idx = lum >= 192 ? 0 : lum >= 128 ? 1 : lum >= 64 ? 2 : 3;
+ fb_scratch[y][x][0] = lcd_pal[idx][0];
+ fb_scratch[y][x][1] = lcd_pal[idx][1];
+ fb_scratch[y][x][2] = lcd_pal[idx][2];
+ }
+ return fb_scratch;
+}
+
// Update the on-screen HUD values; forces a status-line repaint on change.
void render_set_hud(int frameskip, bool turbo) {
if (frameskip != hud_frameskip || turbo != hud_turbo) {
@@ -93,50 +152,48 @@ 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.
+// colors (4 shades on DMG, a modest set on CGB; the chrome adds a handful of
+// flat body/button colors). We build a per-frame palette (<=256 entries),
+// quantizing more coarsely only if a frame overflows, then emit standard
+// sixel bands. The encoder works on any RGB888 buffer so the same path serves
+// both the bare LCD and the full body-chrome canvas.
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) {
+// Build a palette from an RGB888 buffer (w*h pixels, row-major, 3 bytes/pixel)
+// writing one palette index per pixel into idx[]. Returns the color count.
+static int build_palette_buf(const u8 *rgb, int w, int h, u8 *idx) {
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];
+ int n = 0, overflow = 0, npix = w * h;
+ for (int i = 0; i < npix && !overflow; i++) {
+ int r = rgb[i*3+0] & mask;
+ int g = rgb[i*3+1] & mask;
+ int b = rgb[i*3+2] & mask;
+ int key = (r << 16) | (g << 8) | b;
+ unsigned hh = ((unsigned)key * 2654435761u) & 8191u;
+ while (htab[hh] != -1 && pal_key[htab[hh]] != key)
+ hh = (hh + 1) & 8191u;
+ if (htab[hh] == -1) {
+ if (n >= 256) { overflow = 1; break; }
+ htab[hh] = n;
+ pal_key[n] = key;
+ pal_r[n] = r; pal_g[n] = g; pal_b[n] = b;
+ n++;
}
+ idx[i] = (u8)htab[hh];
}
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;
-
+// Emit a full sixel image for a w*h palettized buffer at integer scale sc.
+// Returns the advanced output pointer.
+static char *encode_sixel_buf(char *o, int w, int h, int sc,
+ int ncol, const u8 *idx) {
+ int W2 = w * sc, H2 = h * sc;
// 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)
@@ -146,14 +203,15 @@ static void render_frame_sixel(GB *gb) {
(pal_b[i] * 100 + 127) / 255);
static u8 present[256];
- static u8 sx[SCREEN_W * 6]; // sixel value per column for the active color
+ static u8 sx[CHROME_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;
+ const u8 *row = &idx[sy * w];
+ for (int x = 0; x < w; x++) present[row[x]] = 1;
}
int emitted = 0;
for (int c = 0; c < ncol; c++) {
@@ -164,7 +222,7 @@ static void render_frame_sixel(GB *gb) {
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);
+ if (idx[((by + k) / sc) * w + sx0] == c) bits |= (1 << k);
sx[X2] = (u8)(0x3f + bits);
}
o += sprintf(o, "#%d", c);
@@ -181,7 +239,18 @@ static void render_frame_sixel(GB *gb) {
*o++ = '-'; // next band
}
o += sprintf(o, "\x1b\\"); // ST: end sixel
+ return o;
+}
+
+static u8 screen_idx[SCREEN_W * SCREEN_H];
+static void render_frame_sixel(GB *gb) {
+ PPU *p = &gb->ppu;
+ if (!sixel_buf) return;
+ u8 (*fb)[SCREEN_W][3] = lcd_fb(p);
+ int ncol = build_palette_buf(&fb[0][0][0], SCREEN_W, SCREEN_H, screen_idx);
+ char *o = encode_sixel_buf(sixel_buf, SCREEN_W, SCREEN_H,
+ sixel_scale, ncol, screen_idx);
// In sixel mode we emit only the graphics - no status line / HUD text, so
// the terminal shows nothing but the game image.
need_full = false;
@@ -189,9 +258,233 @@ static void render_frame_sixel(GB *gb) {
fflush(stdout);
}
+// ---- Game Boy body chrome --------------------------------------------------
+// A flat-shaded DMG-style shell drawn around the LCD. Flat colors keep the
+// per-frame palette tiny. Buttons brighten while the matching key is held.
+static u8 chrome_canvas[CHROME_W * CHROME_H * 3];
+static u8 chrome_idx[CHROME_W * CHROME_H];
+
+static inline void put_px(u8 *cv, int x, int y, int r, int g, int b) {
+ if ((unsigned)x >= (unsigned)CHROME_W || (unsigned)y >= (unsigned)CHROME_H)
+ return;
+ u8 *p = &cv[(y * CHROME_W + x) * 3];
+ p[0] = (u8)r; p[1] = (u8)g; p[2] = (u8)b;
+}
+static void fill_rect(u8 *cv, int x0, int y0, int w, int h,
+ int r, int g, int b) {
+ for (int y = y0; y < y0 + h; y++)
+ for (int x = x0; x < x0 + w; x++) put_px(cv, x, y, r, g, b);
+}
+static void fill_circle(u8 *cv, int cx, int cy, int rad,
+ int r, int g, int b) {
+ for (int y = cy - rad; y <= cy + rad; y++)
+ for (int x = cx - rad; x <= cx + rad; x++) {
+ int dx = x - cx, dy = y - cy;
+ if (dx*dx + dy*dy <= rad*rad) put_px(cv, x, y, r, g, b);
+ }
+}
+// rounded rect, uniform corner radius
+static void fill_rrect(u8 *cv, int x0, int y0, int w, int h, int rad,
+ int r, int g, int b) {
+ if (rad < 0) rad = 0;
+ for (int y = 0; y < h; y++)
+ for (int x = 0; x < w; x++) {
+ int cx = x < rad ? rad : (x >= w - rad ? w - 1 - rad : x);
+ int cy = y < rad ? rad : (y >= h - rad ? h - 1 - rad : y);
+ int dx = x - cx, dy = y - cy;
+ if (dx*dx + dy*dy <= rad*rad) put_px(cv, x0 + x, y0 + y, r, g, b);
+ }
+}
+
+// ---- tiny 5x7 bitmap font (column-major: 5 bytes, bit0=top row) ----
+// Only the glyphs we actually silk-screen onto the shell are defined; the rest
+// render blank. Indexed by (ch - 32), covering ASCII space .. 'z'.
+static const u8 font5x7[95][5] = {
+ [' '-32] = {0x00,0x00,0x00,0x00,0x00},
+ ['('-32] = {0x00,0x1C,0x22,0x41,0x00},
+ [')'-32] = {0x00,0x41,0x22,0x1C,0x00},
+ ['.'-32] = {0x00,0x40,0x60,0x00,0x00},
+ ['0'-32] = {0x3E,0x51,0x49,0x45,0x3E},
+ ['1'-32] = {0x00,0x42,0x7F,0x40,0x00},
+ ['2'-32] = {0x42,0x61,0x51,0x49,0x46},
+ ['3'-32] = {0x21,0x41,0x45,0x4B,0x31},
+ ['4'-32] = {0x18,0x14,0x12,0x7F,0x10},
+ ['5'-32] = {0x27,0x45,0x45,0x45,0x39},
+ ['6'-32] = {0x3C,0x4A,0x49,0x49,0x30},
+ ['7'-32] = {0x01,0x71,0x09,0x05,0x03},
+ ['8'-32] = {0x36,0x49,0x49,0x49,0x36},
+ ['9'-32] = {0x06,0x49,0x49,0x29,0x1E},
+ ['A'-32] = {0x7E,0x11,0x11,0x11,0x7E},
+ ['B'-32] = {0x7F,0x49,0x49,0x49,0x36},
+ ['C'-32] = {0x3E,0x41,0x41,0x41,0x22},
+ ['D'-32] = {0x7F,0x41,0x41,0x22,0x1C},
+ ['E'-32] = {0x7F,0x49,0x49,0x49,0x41},
+ ['F'-32] = {0x7F,0x09,0x09,0x09,0x01},
+ ['G'-32] = {0x3E,0x41,0x49,0x49,0x7A},
+ ['H'-32] = {0x7F,0x08,0x08,0x08,0x7F},
+ ['I'-32] = {0x00,0x41,0x7F,0x41,0x00},
+ ['L'-32] = {0x7F,0x40,0x40,0x40,0x40},
+ ['M'-32] = {0x7F,0x02,0x0C,0x02,0x7F},
+ ['N'-32] = {0x7F,0x04,0x08,0x10,0x7F},
+ ['O'-32] = {0x3E,0x41,0x41,0x41,0x3E},
+ ['P'-32] = {0x7F,0x09,0x09,0x09,0x06},
+ ['R'-32] = {0x7F,0x09,0x19,0x29,0x46},
+ ['S'-32] = {0x46,0x49,0x49,0x49,0x31},
+ ['T'-32] = {0x01,0x01,0x7F,0x01,0x01},
+ ['U'-32] = {0x3F,0x40,0x40,0x40,0x3F},
+ ['W'-32] = {0x7F,0x20,0x18,0x20,0x7F},
+ ['X'-32] = {0x63,0x14,0x08,0x14,0x63},
+ ['Y'-32] = {0x07,0x08,0x70,0x08,0x07},
+ ['b'-32] = {0x7F,0x48,0x44,0x44,0x38},
+ ['l'-32] = {0x00,0x41,0x7F,0x40,0x00},
+ ['o'-32] = {0x38,0x44,0x44,0x44,0x38},
+ ['p'-32] = {0x7C,0x14,0x14,0x14,0x08},
+ ['s'-32] = {0x48,0x54,0x54,0x54,0x20},
+ ['y'-32] = {0x0C,0x50,0x50,0x50,0x3C},
+};
+
+static void draw_char(u8 *cv, int x, int y, char ch, int sc,
+ int r, int g, int b) {
+ if (ch < 32 || ch > 126) return;
+ const u8 *gl = font5x7[ch - 32];
+ for (int col = 0; col < 5; col++)
+ for (int row = 0; row < 7; row++)
+ if (gl[col] & (1 << row))
+ fill_rect(cv, x + col*sc, y + row*sc, sc, sc, r, g, b);
+}
+static int text_w(const char *s, int sc) { return (int)strlen(s) * 6 * sc - sc; }
+static void draw_text(u8 *cv, int x, int y, const char *s, int sc,
+ int r, int g, int b) {
+ for (; *s; s++, x += 6*sc) draw_char(cv, x, y, *s, sc, r, g, b);
+}
+// draw centered on cx
+static void draw_text_c(u8 *cv, int cx, int y, const char *s, int sc,
+ int r, int g, int b) {
+ draw_text(cv, cx - text_w(s, sc)/2, y, s, sc, r, g, b);
+}
+
+// Composite the full Game Boy shell + (recolored) LCD + lit buttons into
+// chrome_canvas. Shared by the live sixel renderer and the frame capture path.
+static void build_chrome_canvas(GB *gb) {
+ PPU *p = &gb->ppu;
+ u8 *cv = chrome_canvas;
+ u8 btn = gb->buttons;
+ u8 (*fb)[SCREEN_W][3] = lcd_fb(p);
+
+ // backdrop + body shell (classic "brick" gray) with a touch of depth
+ fill_rect (cv, 0, 0, CHROME_W, CHROME_H, 26, 26, 30);
+ fill_rrect(cv, 5, 5, CHROME_W - 10, CHROME_H - 10, 22, 201, 200, 186);
+ fill_rect (cv, 14, 11, CHROME_W - 28, 2, 224, 223, 210); // top highlight
+ fill_rect (cv, 16, 20, CHROME_W - 32, 2, 178, 177, 164); // ridge line
+ fill_rect (cv, 12, CHROME_H - 15, CHROME_W - 24, 3, 176, 175, 162); // shadow
+
+ // screen bezel (dark plum-gray) with the DMG accent stripe
+ int bx = LCD_X0 - 16, by = LCD_Y0 - 20;
+ int bw = SCREEN_W + 32, bh = SCREEN_H + 44;
+ fill_rrect(cv, bx, by, bw, bh, 10, 74, 72, 88);
+ fill_rect (cv, bx + 12, by + 8, 3, 5, 150, 40, 70); // red accent
+ fill_rect (cv, bx + 17, by + 8, 3, 5, 40, 60, 140); // blue accent
+ // power LED + vertical label in the narrow left margin (BATTERY-style)
+ fill_circle(cv, 15, LCD_Y0 + 2, 3, 210, 60, 60);
+ fill_circle(cv, 15, LCD_Y0 + 2, 1, 255, 180, 180);
+ { const char *pw = "POWER"; int yy = LCD_Y0 + 12;
+ for (const char *c = pw; *c; c++, yy += 8)
+ draw_char(cv, 10, yy, *c, 1, 120, 118, 108); }
+
+ // blit the live LCD into the bezel window
+ for (int y = 0; y < SCREEN_H; y++)
+ for (int x = 0; x < SCREEN_W; x++) {
+ u8 *s = fb[y][x];
+ put_px(cv, LCD_X0 + x, LCD_Y0 + y, s[0], s[1], s[2]);
+ }
+ // subtitle under the LCD, inside the bezel
+ draw_text_c(cv, LCD_X0 + SCREEN_W/2, LCD_Y0 + SCREEN_H + 8,
+ "DOT MATRIX WITH STEREO SOUND", 1, 150, 148, 165);
+
+ // ---- brand: "sl0pboy" ----
+ int logo_y = LCD_Y0 + SCREEN_H + 32;
+ draw_text_c(cv, CHROME_W/2, logo_y, "sl0pboy", 2, 48, 46, 92);
+ int lw = text_w("sl0pboy", 2);
+ fill_rect(cv, CHROME_W/2 - lw/2, logo_y + 17, lw/2, 2, 210, 40, 120); // magenta
+ fill_rect(cv, CHROME_W/2, logo_y + 17, lw/2, 2, 40, 190, 200); // cyan
+
+ // ---- controls ----
+ int base_dk = 52, base_dg = 52, base_db = 60; // dpad / neutral dark
+ int lit_dk = 120, lit_dg = 120, lit_db = 135; // pressed dpad highlight
+ int ctrl_y = LCD_Y0 + SCREEN_H + 84;
+
+ // D-pad (left): a dark cross, arms brighten when held
+ int dcx = LCD_X0 + 26, dcy = ctrl_y;
+ fill_rect(cv, dcx - 33, dcy - 11, 66, 22, base_dk, base_dg, base_db);
+ fill_rect(cv, dcx - 11, dcy - 33, 22, 66, base_dk, base_dg, base_db);
+ if (btn & BTN_UP) fill_rect(cv, dcx-11, dcy-33, 22, 22, lit_dk, lit_dg, lit_db);
+ if (btn & BTN_DOWN) fill_rect(cv, dcx-11, dcy+11, 22, 22, lit_dk, lit_dg, lit_db);
+ if (btn & BTN_LEFT) fill_rect(cv, dcx-33, dcy-11, 22, 22, lit_dk, lit_dg, lit_db);
+ if (btn & BTN_RIGHT) fill_rect(cv, dcx+11, dcy-11, 22, 22, lit_dk, lit_dg, lit_db);
+ fill_rect(cv, dcx - 6, dcy - 6, 12, 12, 40, 40, 46); // hub dimple
+
+ // A / B buttons (right, diagonal): maroon, brighten when held
+ int acx = LCD_X0 + SCREEN_W - 2, acy = ctrl_y - 12;
+ int bcx = LCD_X0 + SCREEN_W - 36, bcy = ctrl_y + 4;
+ fill_circle(cv, acx, acy, 15, 92, 92, 104); // socket ring
+ fill_circle(cv, bcx, bcy, 15, 92, 92, 104);
+ if (btn & BTN_A) fill_circle(cv, acx, acy, 13, 240, 96, 150);
+ else fill_circle(cv, acx, acy, 13, 150, 30, 74);
+ if (btn & BTN_B) fill_circle(cv, bcx, bcy, 13, 240, 96, 150);
+ else fill_circle(cv, bcx, bcy, 13, 150, 30, 74);
+ draw_char(cv, acx + 18, acy - 3, 'A', 1, 90, 88, 100);
+ draw_char(cv, bcx + 18, bcy - 3, 'B', 1, 90, 88, 100);
+
+ // Select / Start pills (center bottom), angled + labelled
+ int py = ctrl_y + 30;
+ int selx = CHROME_W/2 - 34, stax = CHROME_W/2 + 6;
+ if (btn & BTN_SELECT) fill_rrect(cv, selx, py, 28, 9, 4, 150,150,165);
+ else fill_rrect(cv, selx, py, 28, 9, 4, 92,92,104);
+ if (btn & BTN_START) fill_rrect(cv, stax, py, 28, 9, 4, 150,150,165);
+ else fill_rrect(cv, stax, py, 28, 9, 4, 92,92,104);
+ draw_text_c(cv, selx + 14, py + 13, "SELECT", 1, 78, 76, 88);
+ draw_text_c(cv, stax + 14, py + 13, "START", 1, 78, 76, 88);
+
+ // speaker grille (bottom-right): slanted rows of dark dots
+ int sgx = CHROME_W - 78, sgy = CHROME_H - 56;
+ for (int i = 0; i < 5; i++)
+ for (int j = 0; j < 6; j++)
+ fill_circle(cv, sgx + j*8 + i*4, sgy + i*8, 2, 168, 167, 154);
+}
+
+static void render_frame_chrome(GB *gb) {
+ if (!sixel_buf) return;
+ build_chrome_canvas(gb);
+ int ncol = build_palette_buf(chrome_canvas, CHROME_W, CHROME_H, chrome_idx);
+ char *o = encode_sixel_buf(sixel_buf, CHROME_W, CHROME_H,
+ sixel_scale, ncol, chrome_idx);
+ need_full = false;
+ fwrite(sixel_buf, 1, o - sixel_buf, stdout);
+ fflush(stdout);
+}
+
+// ---- frame capture (for screenshots / video recording) ---------------------
+// Produce the exact frame the user sees - LCD recolor plus the Game Boy chrome
+// if enabled - as a contiguous RGB888 buffer, so captures match the display.
+void render_capture_size(int *w, int *h) {
+ if (w) *w = chrome_enabled ? CHROME_W : SCREEN_W;
+ if (h) *h = chrome_enabled ? CHROME_H : SCREEN_H;
+}
+const u8 *render_capture_frame(GB *gb, int *w, int *h) {
+ render_capture_size(w, h);
+ if (chrome_enabled) { build_chrome_canvas(gb); return chrome_canvas; }
+ u8 (*fb)[SCREEN_W][3] = lcd_fb(&gb->ppu);
+ return &fb[0][0][0];
+}
+
void render_frame(GB *gb) {
- if (sixel_enabled) { render_frame_sixel(gb); return; }
+ if (sixel_enabled) {
+ if (chrome_enabled) render_frame_chrome(gb);
+ else render_frame_sixel(gb);
+ return;
+ }
PPU *p = &gb->ppu;
+ u8 (*fb)[SCREEN_W][3] = lcd_fb(p);
char *o = outbuf;
if (resized) { resized = 0; need_full = true; o += sprintf(o, "\x1b[2J"); }
@@ -206,8 +499,8 @@ void render_frame(GB *gb) {
for (int y = 0; y < SCREEN_H; y += 2) {
int row = y / 2;
for (int x = 0; x < SCREEN_W; x++) {
- u8 *top = p->fb[y][x];
- u8 *bot = p->fb[y+1][x];
+ u8 *top = fb[y][x];
+ u8 *bot = fb[y+1][x];
if (!need_full) {
u8 *pt = prev_fb[y][x], *pb2 = prev_fb[y+1][x];
if (top[0]==pt[0] && top[1]==pt[1] && top[2]==pt[2] &&
@@ -239,7 +532,7 @@ void render_frame(GB *gb) {
hud_frameskip, hud_turbo ? " TURBO" : "");
}
- memcpy(prev_fb, p->fb, sizeof(prev_fb));
+ memcpy(prev_fb, fb, sizeof(prev_fb));
need_full = false;
if (o != outbuf) { // nothing changed -> write nothing