Frame pacing #108
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
OragonEfreet/banjo#108
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Banjo example loops run flat-out today. Two distinct problems hide behind that:
The application loop has no timing model. The
stepcallback runs as fast as the CPU allows, examples paper over it withbj_sleep(15). Physics demos manually computedtfrom a stopwatch; pong has to reinvent frame-rate-independent motion. There's no separation between simulation logic that should run at a fixed rate (physics, network ticks, AI) and per-frame logic that should ride the display.Backends mostly self-throttle now, after #107: Wayland fires from
wl_surface.frame, X11 has a 60Hz soft-pace, Emscripten usesrequestAnimationFrame, Win32 fires fromWM_PAINT, Cocoa fires fromdrawRect:. What's still missing: Wayland needs properwl_buffer.release+ double-buffering; Win32 has no vsync between paints (animation in step still busy-waits); Cocoa would benefit fromCADisplayLinkfor animation pacing; the fake backend has no configurable tick for tests.This card delivers both: the application-loop redesign first (so users have a clean way to express "physics at 60Hz, render at vsync"), then the per-backend pacing tweaks on top.
Scope 1 — Application loop redesign
Two callback slots managed by
bj_app_run, internally a Glenn Fiedler accumulator. Inspired by Unity (Update+FixedUpdate) and Godot (_process+_physics_process). See https://gafferongames.com/post/fix_your_timestep/.Public API additions
One unified
bj_tick_infostruct, passed by value to both step callbacks. Small enough (16 bytes) to ride in registers on every ABI Banjo targets, so no per-iteration allocation question to worry about.Breaking changes
bj_app_step_fngains thebj_tick_infoparameter. All 18 examples migrate.bj_app_setup_fnandbj_app_teardown_fnsignatures don't change (no clock context to carry there).Internal flow (Fiedler accumulator)
Why
alphais on the struct but always 0 in fixed_step: render-time is where you interpolate physics state (pos_rendered = lerp(pos_prev, pos_current, alpha)), so the value is meaningful forsteponly. Putting it on the unified struct keeps one type to remember; fixed_step ignoring its zero is harmless.Examples migrating to use fixed_step
pong.c— physics moves tofixed_step; the existingbj_step_delay_stopwatch+clamped_dtplumbing goes away.physics_kinematics.c,physics_particle.c— same.Tests
unit_appwith cases asserting fixed_step fires the right number of times for a given simulated dt; assert step's delta is monotonic; assert fixed-rate setter takes effect.Scope 2 — Per-backend pacing tweaks
The per-backend tick is already mostly aligned with platform refresh after #107. What's left:
wl_surface.framecallback already drives drawwl_buffer.releaselistener; double-buffer (twowl_buffers, two shm fds); attach the one not held by the compositor. Closes the buffer-reuse correctness issue.WM_PAINTDwmFlush()(orSleep(refresh_period - elapsed)) inside step for animation-driven invalidate cycles.drawRect:CADisplayLinkso animation cadence matches the display, not the run-loop.requestAnimationFramepoll_eventsbj_fake_set_tick_interval(ns)for tests. (Optional; depends on whether the accumulator tests need it.)Acceptance criteria
examples/pong.creads cleanly:updatemoves tofixed_step, no manualbj_step_delay_stopwatch, noclamped_dtplumbing.examples/startmatches refresh rate on Wayland/Emscripten/Win32 (real vsync) and is bounded by the soft-pace on X11.unit_appcovers the fixed-step accumulator semantics.wl_buffer.releaseroutes through existingwl_proxy_*dlsym infrastructure).wl_surface.framelives with today is properly released.Out of scope
eglSwapInterval— separate card, lands when the GL renderer does.bj_get_last_frame_nsand similar frame-time introspection — separate API.LateUpdate-style third callback — Banjo doesn't need it at its size.CI gap
Same as #107: Cocoa and Emscripten ship without automated tests. Tracked in the "add macOS + Emscripten CI workflows" card.
Depends on
bj_rendererfrom the public API #114