Skip to content

Add WindowRenderer::resume_async for WASM-friendly wgpu init#59

Open
tonybierman wants to merge 2 commits intoDioxusLabs:mainfrom
tonybierman:feat/window-renderer-resume-async
Open

Add WindowRenderer::resume_async for WASM-friendly wgpu init#59
tonybierman wants to merge 2 commits intoDioxusLabs:mainfrom
tonybierman:feat/window-renderer-resume-async

Conversation

@tonybierman
Copy link
Copy Markdown

Adds an async resume_async method to WindowRenderer so GPU backends can drive wgpu's fundamentally-async surface initialization without pollster::block_on deadlocking the JS event loop on wasm32-unknown-unknown. The default body forwards to sync resume (non-breaking for downstream impls); anyrender_vello and anyrender_vello_hybrid override it with a real async body and now panic! from sync resume on wasm32 instead of silently deadlocking.

@tonybierman
Copy link
Copy Markdown
Author

tonybierman commented May 8, 2026

@nicoburns request review. This is antecedent to a blitz PR after its merged. Could arguably bump the AnyRender version.

@nicoburns
Copy link
Copy Markdown
Member

@tonybierman Would it be possible to get both PRs together? I'd like to actually run them as part of the review process. Just from looking at this, it seems to me like this ought not to be usable in blitz-shell because it holds an &mut reference to self and it will run into borrow-checking errors. But if you have it running then I guess it must work!

wgpu adapter/device/surface initialization is fundamentally async on
the web, so calling pollster::block_on in the synchronous resume path
deadlocks the JS event loop on wasm32-unknown-unknown. Reworks the
WindowRenderer trait to take an `on_ready` callback in `resume` and
split finalization into a new `complete_resume`:

  fn resume<F: FnOnce() + 'static>(
      &mut self, window, width, height, on_ready: F,
  );
  fn complete_resume(&mut self) -> bool { true }

GPU backends (anyrender_vello, anyrender_vello_hybrid) spawn the wgpu
init future via wasm_bindgen_futures::spawn_local on wasm and
pollster::block_on on native, deliver the surface back through a
futures-channel oneshot, and transition Pending → Active inside
complete_resume. The synchronous backends (null, skia, pixels,
softbuffer) call on_ready() inline at the end of resume and inherit
the default complete_resume that returns `true`.

This is a breaking change to the WindowRenderer trait: downstream
impls must update the `resume` signature. `complete_resume` picks up
the trait default unless the backend has its own async init.

Refs: DioxusLabs/blitz#160

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@tonybierman tonybierman force-pushed the feat/window-renderer-resume-async branch from 57c17f7 to 8b08a5e Compare May 9, 2026 00:39
Address code-review feedback on the callback + complete_resume redesign:

- Remove the `complete_resume` default body. Forgetting to override on an
  async-init backend would silently no-op rendering; an explicit impl is
  required. Trivial backends (null, skia, pixels, softbuffer) get
  `fn complete_resume(&mut self) -> bool { true }`.

- Add `WindowRenderer::is_pending()` so callers can distinguish suspended
  from initializing without depending on `is_active()` going through false
  during the wasm async window.

- Restructure RenderState in vello + vello_hybrid as
  `Suspended { wgpu_context } | Pending { receiver } | Active { wgpu_context, active }`.
  The previous shape kept `wgpu_context: Option<WGPUContext>` as a struct
  field and an `Active(ActiveRenderState)` variant, which made
  `Active(_) + wgpu_context: None` representable. The new shape moves
  ownership through `mem::replace` so the in-flight init can take the
  context, then return it on success.

- Add `debug_assert!` in `resume` against non-Suspended state — calling
  resume while Pending or Active orphans the in-flight context and pays
  for a fresh adapter+device init on the fallback path.

- Re-resume custom paint sources inside `complete_resume` rather than
  inside the spawned future, so `CustomPaintSource` doesn't need to be
  `Send`.

- Doc the `spawn_init` cfg helper, fix `errorss` typo, drop a redundant
  `let mut wgpu_context = wgpu_context` rebind, and tighten a few
  comments that drifted from the new design.

- Update bunnymark example to the new resume signature.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
tonybierman added a commit to tonybierman/blitz that referenced this pull request May 9, 2026
Replace the View::resume_async + BlitzWasmApplication approach with the
unified callback/event design landed in DioxusLabs/anyrender#59. The
same BlitzApplication driver now runs on native and wasm32; there is no
async fn in the WindowRenderer trait and no parallel wasm event loop.

Removed:
- BlitzWasmApplication and the wasm-only re-export (the slot/Rc<RefCell>
  pattern that user code had to participate in).
- View::resume_async.
- wasm-bindgen-futures dep from blitz-shell (renderers own spawn_local
  now).

Added:
- BlitzShellEvent::ResumeReady { window_id } variant.
- View::resume now passes a callback that dispatches ResumeReady through
  the proxy. Native targets call the callback inline (pollster::block_on
  inside the renderer); wasm32 dispatches it later from the JS microtask
  queue.
- View::complete_resume re-resolves the doc, syncs the renderer to the
  current viewport size (resize/scale events that arrived while Pending
  were no-ops on the renderer), paints the first frame, and installs the
  doc poll waker.
- BlitzApplication::handle_blitz_shell_event handles ResumeReady by
  calling view.complete_resume.
- DioxusNativeWindowRenderer wrapper forwards the new
  resume(.., on_ready) signature and complete_resume / is_pending to the
  inner renderer.

Example:
- examples/wasm_hello now drives BlitzApplication directly, mirroring
  the native pattern. The wasm-specific code is just bundled-font setup,
  canvas attach, surface seed size, then run_app.

The [patch.crates-io] block points at the AnyRender PR branch and will
be removed once that PR merges and anyrender is released to crates.io.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
tonybierman added a commit to tonybierman/blitz that referenced this pull request May 9, 2026
wgpu adapter/device/surface init is fundamentally async on the web, so
the existing synchronous View::resume path (which calls
WindowRenderer::resume → pollster::block_on) deadlocks the JS event
loop on wasm32-unknown-unknown.

Implements the unified design discussed on DioxusLabs/anyrender#59:
the renderer's `resume` takes an `on_ready: F` callback and an extra
`complete_resume` finalize step; on wasm32 the renderer drives wgpu
init via `wasm_bindgen_futures::spawn_local` and signals readiness
through a new `BlitzShellEvent::ResumeReady` variant that the embedder
handles via the existing event loop. The same code path runs on native
and wasm — no `View::resume_async`, no parallel `BlitzWasmApplication`,
no slot/`Rc<RefCell>` pattern in user code, no `async fn` in the
WindowRenderer trait.

blitz-shell:
- BlitzShellEvent::ResumeReady { window_id } variant.
- View::resume passes a callback that dispatches ResumeReady through
  the proxy. Native targets call it inline (pollster::block_on inside
  the renderer); wasm32 dispatches it later from the JS microtask
  queue.
- View::complete_resume re-resolves the doc, syncs the renderer to
  the current viewport size (resize/scale events that arrived while
  Pending were no-ops on the renderer), paints the first frame, and
  installs the doc poll waker.
- BlitzApplication handles ResumeReady by calling view.complete_resume.
- DioxusNativeWindowRenderer wrapper forwards the new
  resume(.., on_ready) signature plus complete_resume / is_pending.
- Drop wasm-bindgen-futures from blitz-shell — renderers own
  spawn_local now.
- web-time as a drop-in for std::time::Instant on wasm32.

examples/wasm_hello: minimal Trunk-served demo driving BlitzApplication
directly, mirroring the native pattern.

The [patch.crates-io] block points at the AnyRender PR branch and will
be removed once that PR merges and anyrender is released to crates.io.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.

2 participants