Add incremental rendering API to OfflineAudioContext#630
Open
cmzy wants to merge 1 commit into
Open
Conversation
OfflineAudioContext currently offers two rendering entry points, and neither fits hosts that embed a JavaScript engine: - start_rendering_sync() renders the whole buffer in one call and consumes the renderer; - suspend()/suspend_sync() must be registered before rendering starts - suspending after the renderer has been taken panics with "cannot suspend when rendering has already started". The Web Audio suspend/resume contract is inherently incremental: render up to the suspend point, resolve the suspend promise, let script mutate the graph inside the promise callback, resume, render the next segment. An embedder cannot know the suspend points up front (script may schedule new ones from inside a suspend callback), so a pull-style API is needed. This adds three methods, mutually exclusive with suspend/suspend_sync: - render_upto_sync(frame): renders forward to the given frame (rounded up to whole render quanta), returns the total rendered frame count, and can be called repeatedly to continue; - rendered_frames_sync(): lock-free progress accessor (drives currentTime on the embedder side); - take_rendered_sync(): takes the finished AudioBuffer exactly once. Implementation notes: - The rendered frame count is a lock-free AtomicUsize rather than being derived from the incremental state. The early-return paths of render_upto_sync report the count while holding the state mutex, and a std Mutex is not re-entrant: deriving the count through the same mutex self-deadlocks the calling thread on completely legal call sequences (render to the end, take the result, call render_upto_sync again). The atomic also keeps reporting the full length after the result has been taken instead of dropping back to 0. - While parked between segments the context state is set to Suspended (scripts observing `state` from a suspend callback must read "suspended" per spec), Running while a segment renders, and Closed after the final segment. - After finalizing, the event loop is spun once more so that the complete/statechange events queued by the finalization itself get delivered, matching the tail of start_rendering_sync. - RenderThread grows render_offline_quanta() (renders n quanta without consuming self) and finish_offline_render() (destructor run + event drain), splitting the existing one-shot render_audiobuffer_sync into resumable pieces. Tests: chunked rendering produces bit-identical output to a one-shot render of the same graph, state transitions are observable, the taken result is single-shot, and post-completion / post-start_rendering calls are safe no-ops.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
OfflineAudioContext currently offers two rendering entry points, and
neither fits hosts that embed a JavaScript engine:
consumes the renderer;
suspending after the renderer has been taken panics with "cannot
suspend when rendering has already started".
The Web Audio suspend/resume contract is inherently incremental: render
up to the suspend point, resolve the suspend promise, let script mutate
the graph inside the promise callback, resume, render the next segment.
An embedder cannot know the suspend points up front (script may schedule
new ones from inside a suspend callback), so a pull-style API is needed.
This adds three methods, mutually exclusive with suspend/suspend_sync:
up to whole render quanta), returns the total rendered frame count,
and can be called repeatedly to continue;
currentTime on the embedder side);
Implementation notes:
derived from the incremental state. The early-return paths of
render_upto_sync report the count while holding the state mutex, and a
std Mutex is not re-entrant: deriving the count through the same mutex
self-deadlocks the calling thread on completely legal call sequences
(render to the end, take the result, call render_upto_sync again). The
atomic also keeps reporting the full length after the result has been
taken instead of dropping back to 0.
(scripts observing
statefrom a suspend callback must read"suspended" per spec), Running while a segment renders, and Closed
after the final segment.
complete/statechange events queued by the finalization itself get
delivered, matching the tail of start_rendering_sync.
consuming self) and finish_offline_render() (destructor run + event
drain), splitting the existing one-shot render_audiobuffer_sync into
resumable pieces.
Tests: chunked rendering produces bit-identical output to a one-shot
render of the same graph, state transitions are observable, the taken
result is single-shot, and post-completion / post-start_rendering calls
are safe no-ops.