Skip to content
Draft
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
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,7 @@ ctx.canvas.height = ctx.canvas.clientHeight * PixelRatio.get();

### Frame Scheduling

In React Native, we want to keep frame presentation as a manual operation as we plan to provide more advanced rendering options that are React Native specific.
This means that when you are ready to present a frame, you need to call `present` on the context.
In React Native, frame presentation is a manual operation: when you are ready to present a frame, call `present()` on the context after submitting your commands to the queue. This works the same on every runtime: the main JS runtime, the Reanimated UI runtime, and dedicated worklet runtimes (`createWorkletRuntime` / `runOnRuntime`, or a Vision Camera frame processor). `present()` runs synchronously on the calling thread, so the frame is presented from whichever thread did the rendering.

```tsx
// draw
Expand Down Expand Up @@ -293,10 +292,10 @@ const render = () => {

// ... encode a pass that samples `externalTexture`, then:
device.queue.submit([encoder.finish()]);
context.present();

// Release the surface's access window right after the submit that sampled it.
externalTexture.destroy();
context.present();
};
```

Expand Down
10 changes: 5 additions & 5 deletions apps/example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1924,7 +1924,7 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- react-native-wgpu (0.5.12):
- react-native-webgpu (0.5.14):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -2812,7 +2812,7 @@ DEPENDENCIES:
- React-microtasksnativemodule (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
- react-native-safe-area-context (from `../../../node_modules/react-native-safe-area-context`)
- "react-native-skia (from `../../../node_modules/@shopify/react-native-skia`)"
- react-native-wgpu (from `../../../node_modules/react-native-wgpu`)
- react-native-webgpu (from `../../../node_modules/react-native-webgpu`)
- React-NativeModulesApple (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
- React-oscompat (from `../../../node_modules/react-native/ReactCommon/oscompat`)
- React-perflogger (from `../../../node_modules/react-native/ReactCommon/reactperflogger`)
Expand Down Expand Up @@ -2948,8 +2948,8 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/react-native-safe-area-context"
react-native-skia:
:path: "../../../node_modules/@shopify/react-native-skia"
react-native-wgpu:
:path: "../../../node_modules/react-native-wgpu"
react-native-webgpu:
:path: "../../../node_modules/react-native-webgpu"
React-NativeModulesApple:
:path: "../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios"
React-oscompat:
Expand Down Expand Up @@ -3074,7 +3074,7 @@ SPEC CHECKSUMS:
React-microtasksnativemodule: 75b6604b667d297292345302cc5bfb6b6aeccc1b
react-native-safe-area-context: c00143b4823773bba23f2f19f85663ae89ceb460
react-native-skia: fc73e9bdc46ebb420a98c9c2be29fee80f565e79
react-native-wgpu: 274ffec11ee3a082260d9f3d1fb54030a5ca0873
react-native-webgpu: ea7239ee381b4937d8e971f648cdcf6b9ff4de7e
React-NativeModulesApple: 879fbdc5dcff7136abceb7880fe8a2022a1bd7c3
React-oscompat: 93b5535ea7f7dff46aaee4f78309a70979bdde9d
React-perflogger: 5536d2df3d18fe0920263466f7b46a56351c0510
Expand Down
1 change: 0 additions & 1 deletion apps/example/src/CanvasAPI/CanvasAPI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ export const CanvasAPI = () => {
passEncoder.end();

device.queue.submit([commandEncoder.finish()]);

context.present();
})()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,10 @@ export const ImportExternalTexture = () => {

pass.end();
device.queue.submit([encoder.finish()]);
context.present();
// Now that the work sampling it has been submitted, end the external
// texture's access window so the frame's surface is released promptly.
externalTex?.destroy();
context.present();
rafRef.current = requestAnimationFrame(render);
};
rafRef.current = requestAnimationFrame(render);
Expand Down
4 changes: 3 additions & 1 deletion apps/example/src/Reanimated/Reanimated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ export const webGPUDemo = (
passEncoder.end();

device.queue.submit([commandEncoder.finish()]);

// Needed on a dedicated worklet runtime (DedicatedThread); a no-op on the
// UI runtime (UIThread), where present is automatic.
context.present();

if (runAnimation.value) {
requestAnimationFrame(frame);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,6 @@ export function StorageBufferVertices() {

const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(context as any).present();
});

return (
Expand Down
2 changes: 1 addition & 1 deletion apps/example/src/ThreeJS/Backdrop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export const Backdrop = () => {
}

renderer.render(scene, camera);
context!.present();
context.present();
}
return () => {
renderer.setAnimationLoop(null);
Expand Down
2 changes: 1 addition & 1 deletion apps/example/src/ThreeJS/Helmet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const Helmet = () => {
function animate() {
animateCamera();
renderer.render(scene, camera);
context!.present();
context.present();
}

return () => {
Expand Down
2 changes: 1 addition & 1 deletion apps/example/src/ThreeJS/InstancedMesh.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ export const InstancedMesh = () => {

function animate() {
render();
context!.present();
}

function render() {
Expand Down Expand Up @@ -88,6 +87,7 @@ export const InstancedMesh = () => {
}

renderer.render(scene, camera);
context.present();
}
return () => {
renderer.setAnimationLoop(null);
Expand Down
2 changes: 1 addition & 1 deletion apps/example/src/ThreeJS/PostProcessing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const PostProcessing = () => {
mixer.update(delta);
}
postProcessing.render();
context!.present();
context.present();
}
return () => {
renderer.setAnimationLoop(null);
Expand Down
2 changes: 1 addition & 1 deletion apps/example/src/ThreeJS/components/FiberCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const FiberCanvas = ({
const renderFrame = state.gl.render.bind(state.gl);
state.gl.render = (s: THREE.Scene, c: THREE.Camera) => {
renderFrame(s, c);
context?.present();
context.present();
};
},
});
Expand Down
1 change: 0 additions & 1 deletion apps/example/src/Triangle/HelloTriangle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ export function HelloTriangle() {
passEncoder.end();

device.queue.submit([commandEncoder.finish()]);

context.present();
})();
}, [ref]);
Expand Down
2 changes: 1 addition & 1 deletion apps/example/src/Triangle/HelloTriangleMSAA.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ export function HelloTriangleMSAA() {
passEncoder.end();

device.queue.submit([commandEncoder.finish()]);
context.present();
}

frame();
context.present();
})();
}, [ref]);

Expand Down
4 changes: 3 additions & 1 deletion apps/example/src/VisionCamera/VisionCamera.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -613,11 +613,13 @@ const CameraView = () => {
pass.draw(3);
pass.end();
device.queue.submit([encoder.finish()]);
// Vision Camera frame processors run on a dedicated worklet runtime,
// so present explicitly (auto-present only covers the JS/UI runtime).
context.present();
// The work sampling it is submitted, so end the external texture's
// access window now to release the camera frame's surface promptly
// (don't wait for GC, which would starve the frame buffer pool).
externalTex.destroy();
context.present();
} finally {
videoFrame.release();
}
Expand Down
5 changes: 2 additions & 3 deletions packages/webgpu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,7 @@ ctx.canvas.height = ctx.canvas.clientHeight * PixelRatio.get();

### Frame Scheduling

In React Native, we want to keep frame presentation as a manual operation as we plan to provide more advanced rendering options that are React Native specific.
This means that when you are ready to present a frame, you need to call `present` on the context.
In React Native, frame presentation is a manual operation: when you are ready to present a frame, call `present()` on the context after submitting your commands to the queue. This works the same on every runtime: the main JS runtime, the Reanimated UI runtime, and dedicated worklet runtimes (`createWorkletRuntime` / `runOnRuntime`, or a Vision Camera frame processor). `present()` runs synchronously on the calling thread, so the frame is presented from whichever thread did the rendering.

```tsx
// draw
Expand Down Expand Up @@ -293,10 +292,10 @@ const render = () => {

// ... encode a pass that samples `externalTexture`, then:
device.queue.submit([encoder.finish()]);
context.present();

// Release the surface's access window right after the submit that sampled it.
externalTexture.destroy();
context.present();
};
```

Expand Down
5 changes: 3 additions & 2 deletions packages/webgpu/android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ add_library(${PACKAGE_NAME} SHARED
../cpp/jsi/Promise.cpp
../cpp/jsi/RuntimeLifecycleMonitor.cpp
../cpp/jsi/RuntimeAwareCache.cpp
../cpp/rnwgpu/async/AsyncRunner.cpp
../cpp/rnwgpu/async/RuntimeContext.cpp
../cpp/rnwgpu/async/AsyncTaskHandle.cpp
../cpp/rnwgpu/async/JSIMicrotaskDispatcher.cpp
../cpp/rnwgpu/async/CallInvokerScheduler.cpp
../cpp/rnwgpu/async/GpuEventLoop.cpp
)

target_include_directories(
Expand Down
1 change: 1 addition & 0 deletions packages/webgpu/apple/WebGPUModule.mm
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ - (void)invalidate {
std::make_shared<rnwgpu::ApplePlatformContext>();
webgpuManager = std::make_shared<rnwgpu::RNWebGPUManager>(runtime, jsInvoker,
platformContext);

return @true;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/webgpu/cpp/rnwgpu/RNWebGPUManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "GPUSharedFence.h"
#include "GPUSharedTextureMemory.h"
#include "GPUShaderModule.h"
#include "GPUSharedTextureMemory.h"
#include "GPUSupportedLimits.h"
#include "GPUTexture.h"
#include "GPUTextureView.h"
Expand Down Expand Up @@ -64,7 +65,7 @@ RNWebGPUManager::RNWebGPUManager(
// Register main runtime for RuntimeAwareCache
BaseRuntimeAwareCache::setMainJsRuntime(_jsRuntime);

auto gpu = std::make_shared<GPU>(*_jsRuntime);
auto gpu = std::make_shared<GPU>(*_jsRuntime, _jsCallInvoker);
auto rnWebGPU =
std::make_shared<RNWebGPU>(gpu, _platformContext, _jsCallInvoker);
_gpu = gpu->get();
Expand Down
30 changes: 29 additions & 1 deletion packages/webgpu/cpp/rnwgpu/SurfaceRegistry.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@

#include "webgpu/webgpu_cpp.h"

#ifdef __APPLE__
namespace dawn::native::metal {
void WaitForCommandsToBeScheduled(WGPUDevice device);
} // namespace dawn::native::metal
#endif

namespace rnwgpu {

struct NativeInfo {
Expand Down Expand Up @@ -113,7 +119,23 @@ class SurfaceInfo {
height = newHeight;
}

void present() {
// Present the current surface texture. Called synchronously from the thread
// that did getCurrentTexture / submit (via GPUCanvasContext::present), so it
// preserves Dawn surface thread-affinity. No-op when offscreen / unconfigured
// (no surface).
void presentFrame() {
#ifdef __APPLE__
// Ensure command buffers are scheduled before presenting. Read the device
// under a shared lock, then wait without holding it (the wait can block).
wgpu::Device device;
{
std::shared_lock<std::shared_mutex> lock(_mutex);
device = config.device;
}
if (device) {
dawn::native::metal::WaitForCommandsToBeScheduled(device.Get());
}
#endif
std::unique_lock<std::shared_mutex> lock(_mutex);
if (surface) {
surface.Present();
Expand All @@ -131,6 +153,12 @@ class SurfaceInfo {
}
}

// True when an on-screen wgpu::Surface is attached (vs offscreen texture).
bool hasSurface() {
std::shared_lock<std::shared_mutex> lock(_mutex);
return surface != nullptr;
}

NativeInfo getNativeInfo() {
std::shared_lock<std::shared_mutex> lock(_mutex);
return {.nativeSurface = nativeSurface, .width = width, .height = height};
Expand Down
27 changes: 17 additions & 10 deletions packages/webgpu/cpp/rnwgpu/api/GPU.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@

#include "Convertors.h"
#include "JSIConverter.h"
#include "rnwgpu/async/JSIMicrotaskDispatcher.h"
#include "rnwgpu/async/CallInvokerScheduler.h"
#include "rnwgpu/async/GpuEventLoop.h"

namespace rnwgpu {

GPU::GPU(jsi::Runtime &runtime) : NativeObject(CLASS_NAME) {
GPU::GPU(jsi::Runtime &runtime,
std::shared_ptr<facebook::react::CallInvoker> callInvoker)
: NativeObject(CLASS_NAME) {
static const auto kTimedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny;
wgpu::InstanceDescriptor instanceDesc{.requiredFeatureCount = 1,
.requiredFeatures = &kTimedWaitAny};
Expand Down Expand Up @@ -49,8 +52,11 @@ GPU::GPU(jsi::Runtime &runtime) : NativeObject(CLASS_NAME) {

_instance = wgpu::CreateInstance(&instanceDesc);

auto dispatcher = std::make_shared<async::JSIMicrotaskDispatcher>(runtime);
_async = async::AsyncRunner::getOrCreate(runtime, _instance, dispatcher);
auto scheduler =
std::make_shared<async::CallInvokerScheduler>(std::move(callInvoker));
auto eventLoop = std::make_shared<async::GpuEventLoop>(_instance);
_async = async::RuntimeContext::getOrCreate(runtime, std::move(scheduler),
std::move(eventLoop));
}

async::AsyncTaskHandle GPU::requestAdapter(
Expand All @@ -68,19 +74,20 @@ async::AsyncTaskHandle GPU::requestAdapter(
aOptions.backendType = kDefaultBackendType;
return _async->postTask(
[this, aOptions](const async::AsyncTaskHandle::ResolveFunction &resolve,
const async::AsyncTaskHandle::RejectFunction &reject) {
_instance.RequestAdapter(
&aOptions, wgpu::CallbackMode::AllowProcessEvents,
[asyncRunner = _async, resolve,
const async::AsyncTaskHandle::RejectFunction &reject)
-> wgpu::Future {
return _instance.RequestAdapter(
&aOptions, wgpu::CallbackMode::WaitAnyOnly,
[context = _async, resolve,
reject](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter,
wgpu::StringView message) {
if (message.length) {
fprintf(stderr, "%s", message.data);
}

if (status == wgpu::RequestAdapterStatus::Success && adapter) {
auto adapterHost = std::make_shared<GPUAdapter>(
std::move(adapter), asyncRunner);
auto adapterHost =
std::make_shared<GPUAdapter>(std::move(adapter), context);
auto result =
std::variant<std::nullptr_t, std::shared_ptr<GPUAdapter>>(
adapterHost);
Expand Down
Loading
Loading