diff --git a/README.md b/README.md index 84905a85..388194be 100644 --- a/README.md +++ b/README.md @@ -118,8 +118,6 @@ export function HelloTriangle() { passEncoder.end(); device.queue.submit([commandEncoder.finish()]); - - context.present(); }; helloTriangle(); }, [ref]); @@ -164,16 +162,8 @@ 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. - -```tsx -// draw -// submit to the queue -device.queue.submit([commandEncoder.finish()]); -// This method is React Native only -context.present(); -``` +Frames are presented automatically once you submit command buffers to the device queue, matching the behaviour on the web. +The native context still exposes a `present()` method for backwards compatibility, but calling it is optional and typically a no-op. ### External Textures diff --git a/apps/example/src/ThreeJS/Cube.tsx b/apps/example/src/ThreeJS/Cube.tsx index d3e9707b..46c4154c 100644 --- a/apps/example/src/ThreeJS/Cube.tsx +++ b/apps/example/src/ThreeJS/Cube.tsx @@ -31,7 +31,7 @@ export const Cube = () => { mesh.rotation.y = time / 1000; renderer.render(scene, camera); - context.present(); + //context.present(); } renderer.setAnimationLoop(animate); return () => { diff --git a/apps/example/src/components/useWebGPU.ts b/apps/example/src/components/useWebGPU.ts index ac8a631a..c57d7974 100644 --- a/apps/example/src/components/useWebGPU.ts +++ b/apps/example/src/components/useWebGPU.ts @@ -57,7 +57,7 @@ export const useWebGPU = (scene: Scene) => { const render = () => { const timestamp = Date.now(); renderScene(timestamp); - context.present(); + //context.present(); animationFrameId.current = requestAnimationFrame(render); }; diff --git a/packages/webgpu/README.md b/packages/webgpu/README.md index 84905a85..388194be 100644 --- a/packages/webgpu/README.md +++ b/packages/webgpu/README.md @@ -118,8 +118,6 @@ export function HelloTriangle() { passEncoder.end(); device.queue.submit([commandEncoder.finish()]); - - context.present(); }; helloTriangle(); }, [ref]); @@ -164,16 +162,8 @@ 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. - -```tsx -// draw -// submit to the queue -device.queue.submit([commandEncoder.finish()]); -// This method is React Native only -context.present(); -``` +Frames are presented automatically once you submit command buffers to the device queue, matching the behaviour on the web. +The native context still exposes a `present()` method for backwards compatibility, but calling it is optional and typically a no-op. ### External Textures diff --git a/packages/webgpu/cpp/rnwgpu/SurfaceRegistry.h b/packages/webgpu/cpp/rnwgpu/SurfaceRegistry.h index 266fbd8a..f8bf7ecd 100644 --- a/packages/webgpu/cpp/rnwgpu/SurfaceRegistry.h +++ b/packages/webgpu/cpp/rnwgpu/SurfaceRegistry.h @@ -1,9 +1,11 @@ #pragma once #include +#include #include #include #include +#include #ifdef __APPLE__ #include @@ -12,6 +14,16 @@ #include "webgpu/webgpu_cpp.h" +#include "api/Canvas.h" + +#ifdef __APPLE__ +namespace dawn::native::metal { + +void WaitForCommandsToBeScheduled(WGPUDevice device); + +} // namespace dawn::native::metal +#endif + namespace rnwgpu { struct NativeInfo { @@ -32,6 +44,14 @@ class SurfaceInfo { ~SurfaceInfo() { surface = nullptr; } + struct PresentRequest { + wgpu::Device device; + wgpu::Surface surface; + int width; + int height; + std::weak_ptr canvas; + }; + void reconfigure(int newWidth, int newHeight) { std::unique_lock lock(_mutex); config.width = newWidth; @@ -45,6 +65,7 @@ class SurfaceInfo { config.width = width; config.height = height; config.presentMode = wgpu::PresentMode::Fifo; + _needsPresent = false; _configure(); } @@ -55,6 +76,7 @@ class SurfaceInfo { } else { texture = nullptr; } + _needsPresent = false; } void *switchToOffscreen() { @@ -72,6 +94,7 @@ class SurfaceInfo { texture = config.device.CreateTexture(&textureDesc); } surface = nullptr; + _needsPresent = false; return nativeSurface; } @@ -110,6 +133,7 @@ class SurfaceInfo { surface.Present(); texture = nullptr; } + _needsPresent = false; } void resize(int newWidth, int newHeight) { @@ -118,24 +142,39 @@ class SurfaceInfo { height = newHeight; } - void present() { + void setCanvas(std::weak_ptr canvas) { std::unique_lock lock(_mutex); - if (surface) { - surface.Present(); - } + _canvas = std::move(canvas); } wgpu::Texture getCurrentTexture() { - std::shared_lock lock(_mutex); + std::unique_lock lock(_mutex); if (surface) { wgpu::SurfaceTexture surfaceTexture; surface.GetCurrentTexture(&surfaceTexture); + _needsPresent = true; return surfaceTexture.texture; } else { + _needsPresent = false; return texture; } } + std::optional takePendingPresent() { + std::unique_lock lock(_mutex); + if (!_needsPresent || !surface) { + _needsPresent = false; + return std::nullopt; + } + PresentRequest request{.device = config.device, + .surface = surface, + .width = width, + .height = height, + .canvas = _canvas}; + _needsPresent = false; + return request; + } + NativeInfo getNativeInfo() { std::shared_lock lock(_mutex); return {.nativeSurface = nativeSurface, .width = width, .height = height}; @@ -191,6 +230,7 @@ class SurfaceInfo { } mutable std::shared_mutex _mutex; + std::weak_ptr _canvas; void *nativeSurface = nullptr; wgpu::Surface surface = nullptr; wgpu::Texture texture = nullptr; @@ -198,6 +238,7 @@ class SurfaceInfo { wgpu::SurfaceConfiguration config; int width; int height; + bool _needsPresent = false; }; class SurfaceRegistry { @@ -244,6 +285,35 @@ class SurfaceRegistry { return info; } + void presentPendingSurfaces() { + std::vector> infosCopy; + { + std::shared_lock lock(_mutex); + infosCopy.reserve(_registry.size()); + for (auto &entry : _registry) { + infosCopy.push_back(entry.second); + } + } + + for (auto &info : infosCopy) { + auto request = info->takePendingPresent(); + if (!request.has_value()) { + continue; + } + + auto presentRequest = std::move(request.value()); +#ifdef __APPLE__ + dawn::native::metal::WaitForCommandsToBeScheduled( + presentRequest.device.Get()); +#endif + if (auto canvas = presentRequest.canvas.lock()) { + canvas->setClientWidth(presentRequest.width); + canvas->setClientHeight(presentRequest.height); + } + presentRequest.surface.Present(); + } + } + private: SurfaceRegistry() = default; mutable std::shared_mutex _mutex; diff --git a/packages/webgpu/cpp/rnwgpu/api/GPUCanvasContext.cpp b/packages/webgpu/cpp/rnwgpu/api/GPUCanvasContext.cpp index 15e7f4ac..cc10e0a4 100644 --- a/packages/webgpu/cpp/rnwgpu/api/GPUCanvasContext.cpp +++ b/packages/webgpu/cpp/rnwgpu/api/GPUCanvasContext.cpp @@ -52,14 +52,17 @@ std::shared_ptr GPUCanvasContext::getCurrentTexture() { } void GPUCanvasContext::present() { + auto request = _surfaceInfo->takePendingPresent(); + if (!request.has_value()) { + return; + } #ifdef __APPLE__ dawn::native::metal::WaitForCommandsToBeScheduled( - _surfaceInfo->getDevice().Get()); + request->device.Get()); #endif - auto size = _surfaceInfo->getSize(); - _canvas->setClientWidth(size.width); - _canvas->setClientHeight(size.height); - _surfaceInfo->present(); + _canvas->setClientWidth(request->width); + _canvas->setClientHeight(request->height); + request->surface.Present(); } } // namespace rnwgpu diff --git a/packages/webgpu/cpp/rnwgpu/api/GPUCanvasContext.h b/packages/webgpu/cpp/rnwgpu/api/GPUCanvasContext.h index 976949f8..5c0da0d7 100644 --- a/packages/webgpu/cpp/rnwgpu/api/GPUCanvasContext.h +++ b/packages/webgpu/cpp/rnwgpu/api/GPUCanvasContext.h @@ -30,6 +30,7 @@ class GPUCanvasContext : public m::HybridObject { auto ®istry = rnwgpu::SurfaceRegistry::getInstance(); _surfaceInfo = registry.getSurfaceInfoOrCreate(contextId, _gpu->get(), width, height); + _surfaceInfo->setCanvas(_canvas); } public: diff --git a/packages/webgpu/cpp/rnwgpu/api/GPUQueue.cpp b/packages/webgpu/cpp/rnwgpu/api/GPUQueue.cpp index 3a7e9ab7..585c7b02 100644 --- a/packages/webgpu/cpp/rnwgpu/api/GPUQueue.cpp +++ b/packages/webgpu/cpp/rnwgpu/api/GPUQueue.cpp @@ -6,6 +6,8 @@ #include "Convertors.h" +#include "../SurfaceRegistry.h" + namespace rnwgpu { struct BufferSource { @@ -26,6 +28,7 @@ void GPUQueue::submit( return; } _instance.Submit(bufs_size, bufs.data()); + SurfaceRegistry::getInstance().presentPendingSurfaces(); } void GPUQueue::writeBuffer(std::shared_ptr buffer, diff --git a/packages/webgpu/src/Canvas.tsx b/packages/webgpu/src/Canvas.tsx index e311f0b4..6bdca1ee 100644 --- a/packages/webgpu/src/Canvas.tsx +++ b/packages/webgpu/src/Canvas.tsx @@ -35,7 +35,11 @@ export interface NativeCanvas { } export type RNCanvasContext = GPUCanvasContext & { - present: () => void; + /** + * @deprecated Presentation happens automatically after queue submission. + * This method is kept for backwards compatibility and is a no-op. + */ + present?: () => void; }; export interface CanvasRef {