Skip to content

fix(馃悰): register ImageBitmap so it can cross to worklet runtimes - #576

Merged
wcandillon merged 2 commits into
wcandillon:mainfrom
reczkok:fix/image-bitmap-worklets
Oct 18, 2026
Merged

wcandillon merged 2 commits into
wcandillon:mainfrom
reczkok:fix/image-bitmap-worklets

Conversation

@reczkok

@reczkok reczkok commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Sending an ImageBitmap to a worklet runtime aborts the app. In my case it was a bitmap decoded with createImageBitmap on the JS thread and handed to a scene running on a createWorkletRuntime worker. Other WebGPU objects (buffers, textures, the device) cross fine.

EXC_CRASH (SIGABRT), abort() called, thread WebGPUSerializerRuntime_queue
  __cxa_throw                                         libc++abi.dylib
  worklets::CustomSerializable::toJSValue(jsi::Runtime&)
  worklets::SerializableArray::toJSValue(jsi::Runtime&)
  worklets::SerializableObject::toJSValue(jsi::Runtime&)
  worklets::SerializableWorklet::toJSValue(jsi::Runtime&)

The exception thrown there is No native class registered for brand: ImageBitmap.

What

Unboxing a WebGPU object on another runtime looks up its class brand in NativeObjectRegistry. A brand is only registered by that class's installConstructor. RNWebGPUManager::installConstructors calls it for every WebGPU class except ImageBitmap, so its brand is never registered, and the throw inside the Worklets serializer terminates the app.

Fix

Add ImageBitmap::installConstructor(runtime) to installConstructors, next to the other classes. As a side effect, ImageBitmap now exists as a global on the main runtime and on runtimes that call installWebGPU(). x instanceof ImageBitmap then works there as it does on the web; before, it threw a ReferenceError (three.js checks texture.image instanceof ImageBitmap without a typeof guard in TextureNode).

Test

WorkletSerialization.spec.ts gains a case that decodes a PNG with createImageBitmap, sends the bitmap to a worklet runtime and back, and checks its size there, instanceof ImageBitmap on that runtime, and that the same object comes back. I ran it on an iPhone. It passes with the fix, and without it the app aborts with the crash above. I haven't run it on Android.

Canvas has the same gap: it's the only other native object class missing from installConstructors.

RNWebGPUManager::installConstructors installed every WebGPU class except
ImageBitmap, so its brand was never registered with NativeObjectRegistry.
Sending an ImageBitmap to a worklet runtime then threw "No native class
registered for brand: ImageBitmap" while unboxing it, inside the Worklets
serializer, which aborts the app. Installing its constructor registers
the brand, and also makes `instanceof ImageBitmap` work on the main
runtime and on runtimes that call installWebGPU().

The worklet serialization spec round-trips a decoded ImageBitmap through
a worklet runtime and checks its size, its class there and that the same
object comes back.
@wcandillon

Copy link
Copy Markdown
Owner

that's a great catch thank you 鈽猴笍

@wcandillon
wcandillon self-requested a review October 18, 2026 17:43
@wcandillon
wcandillon merged commit ae0ce9f into wcandillon:main Oct 18, 2026
5 checks passed
@github-actions

Copy link
Copy Markdown

馃帀 This PR is included in version 0.17.0 馃帀

The release is available on:

Your semantic-release bot 馃摝馃殌

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants