Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion apps/docs/content/docs/integrations/react-native-skia.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,28 @@ A few things to know:

## Zero-copy texture sharing

Sharing textures between Skia images and WebGPU textures on the shared device (the equivalent of Skia's previous `MakeTextureFromImage` / `MakeImageFromTexture`) is being rebuilt on top of this interop and will be documented here when it ships. In the meantime, platform surfaces (`CVPixelBuffer` / `AHardwareBuffer`) can cross the boundary through [`importSharedTextureMemory`](/api/gpu-device-extensions#importsharedtexturememory).
Textures cross the package boundary the same way the device does: as raw pointers on the shared device.

**WebGPU texture as a Skia image.** Every `GPUTexture` exposes a non-spec `nativePointer` (a `BigInt` of the `WGPUTexture` handle, borrowed: keep the `GPUTexture` alive while the pointer is in use). `Skia.Image.MakeImageFromNativeTexture` wraps it into an `SkImage` without copying:

```tsx
const texture = device.createTexture({ ... });
// render into it with WebGPU, then draw it with Skia:
const image = Skia.Image.MakeImageFromNativeTexture(texture.nativePointer);
```

**Skia image as a WebGPU texture.** `Skia.Image.MakeNativeTextureFromImage` uploads an `SkImage` into a texture on the shared device and returns a `+1` pointer; `adoptTexture()` wraps it into a `GPUTexture` that owns that reference:

```tsx
import { adoptTexture } from "react-native-webgpu";

const texture = adoptTexture(Skia.Image.MakeNativeTextureFromImage(image));
// sample it in a render pipeline via texture.createView()
```

Adopt each pointer exactly once: the returned `GPUTexture` releases the reference when destroyed.

Platform surfaces (`CVPixelBuffer` / `AHardwareBuffer`) can also cross the boundary through [`importSharedTextureMemory`](/api/gpu-device-extensions#importsharedtexturememory), or as external textures via `RNWebGPU.createVideoFrameFromNativeBuffer` + `importExternalTexture`.

## See also

Expand Down
8 changes: 8 additions & 0 deletions packages/webgpu/cpp/rnwgpu/api/GPUTexture.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ class GPUTexture : public NativeObject<GPUTexture> {
wgpu::TextureFormat getFormat();
double getUsage();

// Non-spec RN extension: the raw WGPUTexture handle as a BigInt, for
// pointer-based interop (e.g. Skia's Image.MakeImageFromNativeTexture).
// Borrowed: this GPUTexture keeps ownership; the consumer must retain the
// JS object (or AddRef natively) for as long as it uses the pointer.
void *getNativePointer() { return _instance.Get(); }

std::string getLabel() { return _label; }
void setLabel(const std::string &label) {
_label = label;
Expand All @@ -50,6 +56,8 @@ class GPUTexture : public NativeObject<GPUTexture> {

static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
installGetter(runtime, prototype, "__brand", &GPUTexture::getBrand);
installGetter(runtime, prototype, "nativePointer",
&GPUTexture::getNativePointer);
installMethod(runtime, prototype, "createView", &GPUTexture::createView);
installMethod(runtime, prototype, "destroy", &GPUTexture::destroy);
installGetter(runtime, prototype, "width", &GPUTexture::getWidth);
Expand Down
16 changes: 16 additions & 0 deletions packages/webgpu/cpp/rnwgpu/api/RNWebGPU.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "GPU.h"
#include "GPUCanvasContext.h"
#include "GPUDevice.h"
#include "GPUTexture.h"
#include "rnwgpu/async/RuntimeContext.h"
#include "ImageBitmap.h"
#include "PlatformContext.h"
Expand Down Expand Up @@ -100,6 +101,20 @@ class RNWebGPU : public NativeObject<RNWebGPU> {
return std::make_shared<GPUDevice>(device, ctx, "Imported Device");
}

// Wrap an externally created WGPUTexture in a GPUTexture, TAKING OWNERSHIP
// of one reference (unlike importDevice, which borrows): the returned
// GPUTexture releases it when destroyed. Pair with producers that return a
// +1 pointer, e.g. Skia.Image.MakeNativeTextureFromImage().
std::shared_ptr<GPUTexture> adoptTexture(void *pointer) {
if (pointer == nullptr) {
throw std::runtime_error(
"adoptTexture: expected a non-null WGPUTexture pointer (BigInt)");
}
wgpu::Texture texture =
wgpu::Texture::Acquire(reinterpret_cast<WGPUTexture>(pointer));
return std::make_shared<GPUTexture>(texture, "Adopted Texture");
}

jsi::Value createImageBitmap(jsi::Runtime &runtime,
const jsi::Value & /*thisVal*/,
const jsi::Value *args, size_t count) {
Expand Down Expand Up @@ -309,6 +324,7 @@ class RNWebGPU : public NativeObject<RNWebGPU> {
&RNWebGPU::createVideoFrameFromNativeBuffer);
installMethodWithRuntime(runtime, prototype, "importDevice",
&RNWebGPU::importDevice);
installMethod(runtime, prototype, "adoptTexture", &RNWebGPU::adoptTexture);
installMethod(runtime, prototype, "createVideoPlayer",
&RNWebGPU::createVideoPlayer);
installMethod(runtime, prototype, "writeTestVideoFile",
Expand Down
20 changes: 20 additions & 0 deletions packages/webgpu/src/importDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,23 @@ export const importDevice = (pointer: bigint): GPUDevice => {
}
return RNWebGPU.importDevice(pointer);
};

/**
* Wraps an externally created WGPUTexture pointer into a GPUTexture, taking
* ownership of one reference: the returned texture releases it when destroyed.
*
* Pair with producers that hand out a +1 reference, such as
* `Skia.Image.MakeNativeTextureFromImage()`:
*
* ```ts
* const texture = adoptTexture(Skia.Image.MakeNativeTextureFromImage(image));
* ```
*/
export const adoptTexture = (pointer: bigint): GPUTexture => {
if (typeof RNWebGPU === "undefined") {
throw new Error(
"react-native-webgpu is not installed natively; adoptTexture is unavailable",
);
}
return RNWebGPU.adoptTexture(pointer);
};
11 changes: 11 additions & 0 deletions packages/webgpu/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,24 @@ declare global {
// device from Skia.getNativeDevice()) into a GPUDevice. Requires the
// exporter to share this process's single Dawn instance.
importDevice: (pointer: bigint) => GPUDevice;
// Wrap an externally created WGPUTexture pointer into a GPUTexture,
// taking ownership of one reference (pair with producers that return a
// +1 pointer, e.g. Skia.Image.MakeNativeTextureFromImage()).
adoptTexture: (pointer: bigint) => GPUTexture;
createVideoPlayer: (
path: string,
pixelFormat?: NativeVideoPixelFormat,
) => VideoPlayer;
writeTestVideoFile: () => string;
};

interface GPUTexture {
// Non-spec RN extension: raw WGPUTexture handle as a BigInt for
// pointer-based interop (e.g. Skia.Image.MakeImageFromNativeTexture).
// Borrowed: keep this GPUTexture alive while the pointer is in use.
readonly nativePointer: bigint;
}

interface GPUDevice {
importSharedTextureMemory(
descriptor: GPUSharedTextureMemoryDescriptor,
Expand Down
Loading