diff options
| author | gbc dev <gbc@localhost> | 2026-07-04 23:07:50 +0200 |
|---|---|---|
| committer | gbc dev <gbc@localhost> | 2026-07-04 23:07:50 +0200 |
| commit | f4ba890eb4b98e1a65a3d9fc8ba8f62dda66895b (patch) | |
| tree | cb1cccb2dfd0f1b59e59e8e86454849179c88cb2 /src/main.c | |
| parent | input: external control channel via FIFO (diff) | |
| download | sl0pboy-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.c | 72 |
1 files changed, 61 insertions, 11 deletions
@@ -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; |
