aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/render.c60
-rw-r--r--src/render.h1
2 files changed, 52 insertions, 9 deletions
diff --git a/src/render.c b/src/render.c
index 72e5558..75ce020 100644
--- a/src/render.c
+++ b/src/render.c
@@ -5,12 +5,15 @@
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
+#include <signal.h>
#define UPPER "\xe2\x96\x80" // ▀ U+2580
static struct termios orig_termios;
static bool raw_active = false;
+static void on_winch(int s);
+
void term_restore(void) {
if (raw_active) {
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
@@ -33,22 +36,50 @@ void term_init(void) {
// enter alt screen, hide cursor, clear
printf("\x1b[?1049h\x1b[?25l\x1b[2J\x1b[H");
fflush(stdout);
+ signal(SIGWINCH, on_winch);
+ render_force_full();
atexit(term_restore);
}
// large reusable output buffer
static char outbuf[SCREEN_W * (SCREEN_H/2) * 40 + 4096];
+// previous frame's pixels, for inter-frame diffing
+static u8 prev_fb[SCREEN_H][SCREEN_W][3];
+static bool need_full = true; // force a complete repaint
+static volatile sig_atomic_t resized = 0; // set by SIGWINCH
+
+void render_force_full(void) { need_full = true; }
+static void on_winch(int s) { (void)s; resized = 1; }
+
void render_frame(GB *gb) {
PPU *p = &gb->ppu;
char *o = outbuf;
- o += sprintf(o, "\x1b[H");
+
+ if (resized) { resized = 0; need_full = true; o += sprintf(o, "\x1b[2J"); }
+
+ // Terminal SGR (color) state persists across cursor moves, so we can keep
+ // one fg/bg tracker for the whole frame even when we jump over unchanged
+ // cells. Reset each frame since we emit a final \x1b[0m.
+ int pr = -1, pg = -1, pb = -1; // last-emitted fg
+ int PR = -1, PG = -1, PB = -1; // last-emitted bg
+ int cur_row = -2, cur_col = -2; // terminal cell just written (0-indexed)
+
for (int y = 0; y < SCREEN_H; y += 2) {
- int pr = -1, pg = -1, pb = -1; // prev fg
- int PR = -1, PG = -1, PB = -1; // prev bg
+ 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];
+ 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] &&
+ bot[0]==pb2[0] && bot[1]==pb2[1] && bot[2]==pb2[2])
+ continue; // cell unchanged -> skip
+ }
+ // reposition cursor only when we're not already right after the
+ // previously written cell
+ if (row != cur_row || x != cur_col + 1)
+ o += sprintf(o, "\x1b[%d;%dH", row + 1, x + 1);
if (top[0] != pr || top[1] != pg || top[2] != pb) {
o += sprintf(o, "\x1b[38;2;%d;%d;%dm", top[0], top[1], top[2]);
pr = top[0]; pg = top[1]; pb = top[2];
@@ -58,14 +89,25 @@ void render_frame(GB *gb) {
PR = bot[0]; PG = bot[1]; PB = bot[2];
}
memcpy(o, UPPER, 3); o += 3;
+ cur_row = row; cur_col = x;
}
- memcpy(o, "\x1b[0m\r\n", 6); o += 6;
}
- o += sprintf(o, "\x1b[0m\x1b[38;2;150;150;150m %.*s "
- "[wasd/arrows] z:A x:B space:sel enter:start q:quit\x1b[0m\r\n",
- 15, gb->cart.title);
- fwrite(outbuf, 1, o - outbuf, stdout);
- fflush(stdout);
+
+ // status line: static text, only (re)draw on a full repaint
+ if (need_full) {
+ o += sprintf(o, "\x1b[0m\x1b[%d;1H\x1b[38;2;150;150;150m %.*s "
+ "[wasd/arrows] z:A x:B space:sel enter:start q:quit",
+ SCREEN_H/2 + 1, 15, gb->cart.title);
+ }
+
+ memcpy(prev_fb, p->fb, sizeof(prev_fb));
+ need_full = false;
+
+ if (o != outbuf) { // nothing changed -> write nothing
+ o += sprintf(o, "\x1b[0m");
+ fwrite(outbuf, 1, o - outbuf, stdout);
+ fflush(stdout);
+ }
}
// ---- input with decay-based "hold" emulation ----
diff --git a/src/render.h b/src/render.h
index 15f9cda..93ca32a 100644
--- a/src/render.h
+++ b/src/render.h
@@ -6,6 +6,7 @@
void term_init(void);
void term_restore(void);
void render_frame(GB *gb);
+void render_force_full(void); // force a complete repaint next frame
// Poll keyboard, update gb->buttons. Returns false if quit requested.
bool input_poll(GB *gb);