Skip to content

fix(🤖): load react-native-skia's library before creating the Dawn instance - #584

Closed
wcandillon wants to merge 2 commits into
mainfrom
android-load-skia-first
Closed

wcandillon wants to merge 2 commits into
mainfrom
android-load-skia-first

Conversation

@wcandillon

Copy link
Copy Markdown
Owner

Stacked on #582: this branch contains its commit, and the diff shrinks to this change once it merges. It completes the Android half of adopting react-native-skia's Dawn instance (#581).

Problem

createInstance() runs when the native module installs, which is when JS imports react-native-webgpu. With #582 it finds librnskia.so by name, but only if that library is already loaded, and react-native-skia loads it when JS imports react-native-skia. So whether the two packages share an instance depends on which import comes first in the app's bundle. The example app has the order backwards (App.tsx imports the Tests screen before the MNIST screen, the only one importing Skia), and nothing documents the requirement.

Fix

WebGPUModule's static initializer loads librnskia.so before this package's own library, inside a try block: System.loadLibrary is a no-op when the library is already loaded, Skia's own later call is a no-op in turn, and an app without react-native-skia has no such library and gets the UnsatisfiedLinkError the block ignores. From then on the lookup in createInstance() does not depend on the import order.

iOS needs nothing of the kind: dlsym(RTLD_DEFAULT) searches every Mach-O image in the process, so the symbol is found whenever it is present in the binary (wcandillon/react-native-skia#4306 covers that part).

Test

SkiaSharedInstance.spec.ts is the end-to-end check of the contract: it imports Skia's device with importDevice(Skia.getNativeDevice()), copies 16 bytes through it and races mapAsync against a 3 s timeout. With a second instance mapAsync never settles. The harness (Tests.tsx) exposes Skia.getNativeDevice to the on-device eval, since the pointer cannot cross the eval boundary.

The spec runs on Android only until the example app picks up a react-native-skia release with wcandillon/react-native-skia#4306; before it the getter is missing from the iOS binary for an unrelated reason.

Test plan

Locally, on an Android API 35 emulator (arm64, SwiftShader Vulkan, the adapter Skia selects there), Debug build of the example app with react-native-skia 3.7.0:

SkiaSharedInstance.spec.ts logcat load order
With this change passes, mapAsync settles librnskia.so, then libreact-native-webgpu.so
Without the preload (same build otherwise) fails after 3 s: "mapAsync on the device imported from Skia did not settle within 3000 ms" libreact-native-webgpu.so, then librnskia.so

Six unrelated suites (Buffer, Device, GPU, ComputeShader, ArrayBuffer, ABuffer: 54 tests) pass on the same build. CI's build-test-android job runs the new spec on its emulator; the iOS job skips it.

enzomanuelmangano and others added 2 commits October 21, 2026 18:52
System.loadLibrary loads librnskia.so with RTLD_LOCAL, so
dlsym(RTLD_DEFAULT, "rnskia_getWGPUInstance") never finds it and
react-native-webgpu creates its own instance. Look the getter up in
librnskia.so with RTLD_NOLOAD when the global lookup fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tance

createInstance() adopts Skia's Dawn instance so that work on a device
imported with importDevice(Skia.getNativeDevice()) settles. With #582 it
finds librnskia.so by name, but only once that library is loaded, which
react-native-skia does when JS imports it: whether the instance was shared
depended on which package the app imports first. WebGPUModule now loads
librnskia.so before its own library when it is there, so the lookup no
longer depends on the import order. iOS needs nothing of the kind:
dlsym(RTLD_DEFAULT) searches every image in the process.

SkiaSharedInstance.spec.ts checks the contract end to end on Android:
mapAsync on the imported device settles. It skips on iOS until the
example app picks up a react-native-skia release with
wcandillon/react-native-skia#4306.
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