aboutsummaryrefslogtreecommitdiffstats
path: root/src/main.c
diff options
context:
space:
mode:
authorgbc dev <gbc@localhost>2026-07-04 23:07:50 +0200
committergbc dev <gbc@localhost>2026-07-04 23:07:50 +0200
commitf4ba890eb4b98e1a65a3d9fc8ba8f62dda66895b (patch)
treecb1cccb2dfd0f1b59e59e8e86454849179c88cb2 /src/main.c
parentinput: external control channel via FIFO (diff)
downloadsl0pboy-f4ba890eb4b98e1a65a3d9fc8ba8f62dda66895b.tar.gz
sl0pboy-f4ba890eb4b98e1a65a3d9fc8ba8f62dda66895b.tar.xz
sl0pboy-f4ba890eb4b98e1a65a3d9fc8ba8f62dda66895b.zip
speed control: frame skipping to decouple terminal draw from emulation
Emulation always runs at native rate (correct game speed); --frameskip N draws only 1 of every N+1 frames so a slow terminal no longer bottlenecks the loop. Adds --fps cap, --uncapped/--turbo, live [ ] and f keys, HUD readout, and an exit summary of emulated-vs-drawn fps. Replaces the confusing --render-fps.
Diffstat (limited to 'src/main.c')
-rw-r--r--src/main.c72
1 files changed, 61 insertions, 11 deletions
diff --git a/src/main.c b/src/main.c
index 173ab5b..5ce4fb5 100644
--- a/src/main.c
+++ b/src/main.c
@@ -35,6 +35,8 @@ int main(int argc, char **argv) {
int shot_frames = -1;
const char *shot_path = "shot.ppm";
const char *fifo_path = NULL;
+ double target_fps = 59.73; // emulation speed cap; 0 = uncapped
+ int frameskip = 0; // draw 1 of every (frameskip+1) frames
for (int i = 2; i < argc; i++) {
if (!strcmp(argv[i], "--test")) {
test = true;
@@ -45,8 +47,15 @@ int main(int argc, char **argv) {
} else if (!strcmp(argv[i], "--fifo")) {
fifo_path = (i + 1 < argc && argv[i+1][0] != '-')
? argv[++i] : "/tmp/gbc.fifo";
+ } else if (!strcmp(argv[i], "--fps")) {
+ if (i + 1 < argc) target_fps = atof(argv[++i]);
+ } else if (!strcmp(argv[i], "--frameskip")) {
+ if (i + 1 < argc) frameskip = atoi(argv[++i]);
+ } else if (!strcmp(argv[i], "--uncapped") || !strcmp(argv[i], "--turbo")) {
+ target_fps = 0;
}
}
+ if (frameskip < 0) frameskip = 0;
GB *gb = calloc(1, sizeof(GB));
if (cart_load(&gb->cart, rom) != 0) { free(gb); return 1; }
@@ -83,26 +92,67 @@ int main(int argc, char **argv) {
}
term_init();
- const double frame_time = 1.0 / 59.73;
- double next = now_sec();
+ // The emulator always advances every GB frame at the paced native rate so
+ // games run at correct speed. Frame *skipping* only affects how often we
+ // actually draw to the terminal: rendering is the expensive part, so on a
+ // slow terminal we render 1 of every (frameskip+1) frames while emulation
+ // keeps real-time. In turbo we run uncapped and additionally clamp redraws
+ // to ~60 Hz so the terminal isn't flooded.
+ bool turbo = (target_fps <= 0);
+ double last_render = 0;
+ double start_time = now_sec();
+ double next_emu = start_time;
+ u64 frames = 0, drawn = 0;
+ int since_render = 0;
while (running) {
+ frames++;
if (!input_poll(gb)) break;
- run_frame(gb);
- render_frame(gb);
+ if (input_take_turbo()) turbo = !turbo;
+ frameskip += input_take_frameskip_delta();
+ if (frameskip < 0) frameskip = 0;
+ if (frameskip > 30) frameskip = 30;
+ render_set_hud(frameskip, turbo);
- next += frame_time;
+ run_frame(gb); // always emulate a full frame
+
+ // decide whether to draw this frame
+ bool do_render = (since_render >= frameskip);
double t = now_sec();
- double sleep = next - t;
- if (sleep > 0) {
- struct timespec ts = { (time_t)sleep, (long)((sleep - (time_t)sleep) * 1e9) };
- nanosleep(&ts, NULL);
- } else if (sleep < -0.1) {
- next = t; // fell behind; resync
+ if (do_render && turbo && (t - last_render) < 1.0 / 60.0)
+ do_render = false; // clamp turbo redraws to ~60 Hz
+ if (do_render) {
+ render_frame(gb);
+ last_render = t;
+ since_render = 0;
+ drawn++;
+ } else {
+ since_render++;
+ }
+
+ // pace emulation to native speed (skip sleeping in turbo)
+ double emu_dt = turbo ? 0.0 : 1.0 / target_fps;
+ if (emu_dt > 0.0) {
+ next_emu += emu_dt;
+ double sleep = next_emu - now_sec();
+ if (sleep > 0) {
+ struct timespec ts = { (time_t)sleep,
+ (long)((sleep - (time_t)sleep) * 1e9) };
+ nanosleep(&ts, NULL);
+ } else if (sleep < -0.1) {
+ next_emu = now_sec(); // fell behind; resync
+ }
+ } else {
+ next_emu = now_sec(); // uncapped: keep baseline fresh
}
}
term_restore();
input_close_fifo();
+ double elapsed = now_sec() - start_time;
+ if (elapsed > 0)
+ fprintf(stderr, "emulated %llu frames (%.1f fps), drew %llu (%.1f fps) "
+ "in %.2fs\n", (unsigned long long)frames, frames / elapsed,
+ (unsigned long long)drawn, drawn / elapsed, elapsed);
cart_free(&gb->cart);
free(gb);
return 0;