Skip to content

renderer: add present() for a single explicit frame - #6

Open
shadowcodex wants to merge 1 commit into
ericdrowell:mainfrom
shadowcodex-forks:renderer-present
Open

renderer: add present() for a single explicit frame#6
shadowcodex wants to merge 1 commit into
ericdrowell:mainfrom
shadowcodex-forks:renderer-present

Conversation

@shadowcodex

@shadowcodex shadowcodex commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Adds renderer.present(callback): draw exactly one frame, now.

Why

loop is the only way to draw, and it owns requestAnimationFrame. That is the right default, but it rules out two cases:

  • A host that already has a frame loop. Anything with its own fixed-timestep simulation and scheduler cannot hand that over to the renderer. It wants to say "draw this state, now" at a point it chooses.
  • Capture. Taking a screenshot needs one frame on demand, not a stream. Starting a loop and stopping it after the first callback works, but it means the capture path depends on the animation scheduler running at all — in a background tab, in a headless browser, or under a test harness that fakes timers, it does not.

Both currently end up calling loop and stopping it immediately, which is a scheduler round-trip to do something synchronous.

What changed

present is loop's body with the scheduling removed: resize, begin the pass, run the callback, submit. loop now calls it, so there is one implementation of a frame, not two — a fix that skipped the resize or the submit in one path could not be made in only one of them.

The signature takes () => void rather than (elapsedSeconds: number) => void. A caller doing its own scheduling already knows its own clock, and inventing a second one inside the renderer would give it a different origin from the one it is stepping its simulation with.

Tests

A check in scripts/gpu/entry.ts: draw a known target through present() with no loop anywhere, and read the canvas back. It also covers the extraction — a present that dropped the resize or the submit would leave the canvas untouched and fail here.

318 tests pass, npm run typecheck clean, and Chrome/WebGPU passes the new check. WebKit did not run locally — playwright-core install webkit was missing in my environment, unrelated to this change.

Context

One of four focused PRs replacing #2. Independent of the other three. We carry it as a local patch over the published package today, so there is no urgency from our side.


Over to you

Genuine questions, not politeness:

  • Is this applicable at all? If it does not fit where BroMetal is going, say so and close it — no offence taken. We carry it as a local patch over the published package today, so nothing of ours is blocked on it landing.
  • Is there a better approach? We arrived at this from the outside, without the context you have on the design. If you would solve it differently — a different API shape, a different layer, or a reason the current behaviour is deliberate — we would rather write your version than have you merge ours.
  • Anything you want changed — naming, comment style, test placement, scope — tell us and we will do it.

@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

@shadowcodex is attempting to deploy a commit to the Eric Rowell's projects Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant