Skip to content
Open
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
12 changes: 6 additions & 6 deletions apps/example/src/SharedTextureMemory/SharedTextureMemory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,7 @@ export const SharedTextureMemory = () => {
label: "video-frame",
});
const texture = memory.createTexture();
if (!memory.beginAccess(texture, true)) {
texture.destroy();
frame.release();
return null;
}
memory.beginAccess(texture, true);
const uniformBuffer = device.createBuffer({
size: 16, // vec2<f32> padded to 16-byte uniform alignment
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
Expand All @@ -230,7 +226,11 @@ export const SharedTextureMemory = () => {
};

const releaseBound = (b: Bound) => {
b.memory.endAccess(b.texture);
try {
b.memory.endAccess(b.texture);
} catch (e) {
console.warn("[SharedTextureMemory] endAccess failed:", e);
}
b.texture.destroy();
b.uniformBuffer.destroy();
b.frame.release();
Expand Down
1 change: 1 addition & 0 deletions packages/webgpu/android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ add_library(${PACKAGE_NAME} SHARED
../cpp/rnwgpu/api/GPUCommandEncoder.cpp
../cpp/rnwgpu/api/GPUQuerySet.cpp
../cpp/rnwgpu/api/GPUTexture.cpp
../cpp/rnwgpu/api/GPUSharedFence.cpp
../cpp/rnwgpu/api/GPUSharedTextureMemory.cpp
../cpp/rnwgpu/api/GPUExternalTexture.cpp
../cpp/rnwgpu/api/GPURenderBundleEncoder.cpp
Expand Down
2 changes: 2 additions & 0 deletions packages/webgpu/cpp/rnwgpu/RNWebGPUManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "GPURenderPassEncoder.h"
#include "GPURenderPipeline.h"
#include "GPUSampler.h"
#include "GPUSharedFence.h"
#include "GPUSharedTextureMemory.h"
#include "GPUShaderModule.h"
#include "GPUSupportedLimits.h"
Expand Down Expand Up @@ -106,6 +107,7 @@ RNWebGPUManager::RNWebGPUManager(
GPURenderPassEncoder::installConstructor(*_jsRuntime);
GPURenderPipeline::installConstructor(*_jsRuntime);
GPUSampler::installConstructor(*_jsRuntime);
GPUSharedFence::installConstructor(*_jsRuntime);
GPUSharedTextureMemory::installConstructor(*_jsRuntime);
GPUShaderModule::installConstructor(*_jsRuntime);
GPUSupportedLimits::installConstructor(*_jsRuntime);
Expand Down
49 changes: 49 additions & 0 deletions packages/webgpu/cpp/rnwgpu/api/GPUDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,55 @@ std::shared_ptr<GPUSharedTextureMemory> GPUDevice::importSharedTextureMemory(
std::move(label));
}

std::shared_ptr<GPUSharedFence> GPUDevice::importSharedFence(
std::shared_ptr<GPUSharedFenceDescriptor> descriptor) {
if (!descriptor || descriptor->handle == nullptr) {
throw std::runtime_error("GPUDevice::importSharedFence(): handle must be a "
"non-null native handle");
}

wgpu::SharedFenceDescriptor desc{};
std::string label = descriptor->label.value_or("");
if (!label.empty()) {
desc.label = wgpu::StringView(label.c_str(), label.size());
}

// The chained platform descriptor must outlive the synchronous
// ImportSharedFence() below; declare them all and chain the matching one.
wgpu::SharedFenceMTLSharedEventDescriptor mtlDesc{};
wgpu::SharedFenceSyncFDDescriptor syncFdDesc{};
wgpu::SharedFenceVkSemaphoreOpaqueFDDescriptor vkFdDesc{};

const std::string &type = descriptor->type;
if (type == "mtl-shared-event") {
// handle is an id<MTLSharedEvent> pointer.
mtlDesc.sharedEvent = descriptor->handle;
desc.nextInChain = &mtlDesc;
} else if (type == "sync-fd") {
// handle is an OS file descriptor.
syncFdDesc.handle =
static_cast<int>(reinterpret_cast<uintptr_t>(descriptor->handle));
desc.nextInChain = &syncFdDesc;
} else if (type == "vk-semaphore-opaque-fd") {
vkFdDesc.handle =
static_cast<int>(reinterpret_cast<uintptr_t>(descriptor->handle));
desc.nextInChain = &vkFdDesc;
} else {
throw std::runtime_error(
"GPUDevice::importSharedFence(): unsupported fence type '" + type +
"' (expected 'mtl-shared-event', 'sync-fd' or "
"'vk-semaphore-opaque-fd')");
}

auto fence = _instance.ImportSharedFence(&desc);
if (fence == nullptr) {
throw std::runtime_error(
"GPUDevice::importSharedFence(): ImportSharedFence returned null - is "
"the matching 'shared-fence-*' feature enabled on the device?");
}
return std::make_shared<GPUSharedFence>(std::move(fence), std::move(label));
}

async::AsyncTaskHandle GPUDevice::createComputePipelineAsync(
std::shared_ptr<GPUComputePipelineDescriptor> descriptor) {
wgpu::ComputePipelineDescriptor desc{};
Expand Down
5 changes: 5 additions & 0 deletions packages/webgpu/cpp/rnwgpu/api/GPUDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include "GPURenderPipelineDescriptor.h"
#include "GPUSampler.h"
#include "GPUSamplerDescriptor.h"
#include "GPUSharedFenceDescriptor.h"
#include "GPUSharedTextureMemory.h"
#include "GPUSharedTextureMemoryDescriptor.h"
#include "GPUShaderModule.h"
Expand Down Expand Up @@ -120,6 +121,8 @@ class GPUDevice : public NativeObject<GPUDevice> {
std::shared_ptr<GPUExternalTextureDescriptor> descriptor);
std::shared_ptr<GPUSharedTextureMemory> importSharedTextureMemory(
std::shared_ptr<GPUSharedTextureMemoryDescriptor> descriptor);
std::shared_ptr<GPUSharedFence> importSharedFence(
std::shared_ptr<GPUSharedFenceDescriptor> descriptor);
std::shared_ptr<GPUBindGroupLayout> createBindGroupLayout(
std::shared_ptr<GPUBindGroupLayoutDescriptor> descriptor);
std::shared_ptr<GPUPipelineLayout>
Expand Down Expand Up @@ -175,6 +178,8 @@ class GPUDevice : public NativeObject<GPUDevice> {
&GPUDevice::importExternalTexture);
installMethod(runtime, prototype, "importSharedTextureMemory",
&GPUDevice::importSharedTextureMemory);
installMethod(runtime, prototype, "importSharedFence",
&GPUDevice::importSharedFence);
installMethod(runtime, prototype, "createBindGroupLayout",
&GPUDevice::createBindGroupLayout);
installMethod(runtime, prototype, "createPipelineLayout",
Expand Down
72 changes: 72 additions & 0 deletions packages/webgpu/cpp/rnwgpu/api/GPUSharedFence.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#include "GPUSharedFence.h"

#include <cstdint>
#include <string>

#if defined(__ANDROID__)
#include <fcntl.h>
#endif

namespace rnwgpu {

namespace {

// Kebab-case names matching the shared-fence-* feature strings (see Unions.h /
// GPUFeatures.h).
std::string sharedFenceTypeToString(wgpu::SharedFenceType type) {
switch (type) {
case wgpu::SharedFenceType::MTLSharedEvent:
return "mtl-shared-event";
case wgpu::SharedFenceType::SyncFD:
return "sync-fd";
case wgpu::SharedFenceType::VkSemaphoreOpaqueFD:
return "vk-semaphore-opaque-fd";
case wgpu::SharedFenceType::VkSemaphoreZirconHandle:
return "vk-semaphore-zircon-handle";
case wgpu::SharedFenceType::DXGISharedHandle:
return "dxgi-shared-handle";
case wgpu::SharedFenceType::EGLSync:
return "egl-sync";
default:
return "";
}
}

} // namespace

jsi::Value GPUSharedFence::exportInfo(jsi::Runtime &runtime, const jsi::Value &,
const jsi::Value *, size_t) {
wgpu::SharedFenceExportInfo info{};
uint64_t handle = 0;

#if defined(__APPLE__)
// Apple: the handle is an id<MTLSharedEvent> pointer.
wgpu::SharedFenceMTLSharedEventExportInfo mtlInfo{};
info.nextInChain = &mtlInfo;
_instance.ExportInfo(&info);
handle = reinterpret_cast<uint64_t>(mtlInfo.sharedEvent);
#elif defined(__ANDROID__)
// Android: the handle is an OS file descriptor (sync_fd). Dawn's ExportInfo returns a BORROWED fd — it is
// owned by the SharedFence and closed when the fence is destroyed. This exported handle is documented as
// caller-owned (the caller must close() it), so dup() it. Without the dup the same fd is closed twice —
// once by the caller and once by Dawn on fence destruction — tripping Android's fdsan (double-close abort).
wgpu::SharedFenceSyncFDExportInfo fdInfo{};
info.nextInChain = &fdInfo;
_instance.ExportInfo(&info);
int exportedFd =
fdInfo.handle >= 0 ? ::fcntl(fdInfo.handle, F_DUPFD_CLOEXEC, 0) : fdInfo.handle;
handle = static_cast<uint64_t>(static_cast<uint32_t>(exportedFd));
#else
_instance.ExportInfo(&info);
#endif

jsi::Object result(runtime);
result.setProperty(
runtime, "type",
jsi::String::createFromUtf8(runtime, sharedFenceTypeToString(info.type)));
result.setProperty(runtime, "handle",
jsi::BigInt::fromUint64(runtime, handle));
return result;
}

} // namespace rnwgpu
53 changes: 53 additions & 0 deletions packages/webgpu/cpp/rnwgpu/api/GPUSharedFence.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#pragma once

#include <memory>
#include <string>

#include "NativeObject.h"

#include "webgpu/webgpu_cpp.h"

namespace rnwgpu {

namespace jsi = facebook::jsi;

// Wraps a wgpu::SharedFence: a native GPU sync primitive (id<MTLSharedEvent> on
// Apple, sync-fd / VkSemaphore on Android).
class GPUSharedFence : public NativeObject<GPUSharedFence> {
public:
static constexpr const char *CLASS_NAME = "GPUSharedFence";

explicit GPUSharedFence(wgpu::SharedFence instance, std::string label)
: NativeObject(CLASS_NAME), _instance(std::move(instance)),
_label(std::move(label)) {}

public:
std::string getBrand() { return CLASS_NAME; }

// export() -> { type, handle }: exposes the native handle (as a BigInt) so
// app code can wait on or signal the fence. The caller owns the returned
// handle (e.g. an exported sync-fd must be close()d).
jsi::Value exportInfo(jsi::Runtime &runtime, const jsi::Value &thisVal,
const jsi::Value *args, size_t count);

std::string getLabel() { return _label; }
void setLabel(const std::string &label) {
_label = label;
_instance.SetLabel(_label.c_str());
}

static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
installGetter(runtime, prototype, "__brand", &GPUSharedFence::getBrand);
installMethod(runtime, prototype, "export", &GPUSharedFence::exportInfo);
installGetterSetter(runtime, prototype, "label", &GPUSharedFence::getLabel,
&GPUSharedFence::setLabel);
}

inline const wgpu::SharedFence get() { return _instance; }

private:
wgpu::SharedFence _instance;
std::string _label;
};

} // namespace rnwgpu
71 changes: 60 additions & 11 deletions packages/webgpu/cpp/rnwgpu/api/GPUSharedTextureMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,38 @@ std::shared_ptr<GPUTexture> GPUSharedTextureMemory::createTexture(
descriptor.value()->label.value_or(""));
}

bool GPUSharedTextureMemory::beginAccess(std::shared_ptr<GPUTexture> texture,
bool initialized) {
void GPUSharedTextureMemory::beginAccess(
std::shared_ptr<GPUTexture> texture, bool initialized,
std::optional<std::vector<std::shared_ptr<GPUSharedFenceState>>> fences) {
if (!texture) {
throw std::runtime_error(
"GPUSharedTextureMemory::beginAccess(): texture is null");
}
wgpu::SharedTextureMemoryBeginAccessDescriptor desc{};
desc.initialized = initialized;
desc.concurrentRead = false;
desc.fenceCount = 0;
desc.fences = nullptr;
desc.signaledValues = nullptr;

// Built in lockstep so fenceCount covers both arrays, and kept in locals so
// the raw pointers outlive the synchronous BeginAccess() below.
std::vector<wgpu::SharedFence> rawFences;
std::vector<uint64_t> values;
if (fences.has_value()) {
for (const auto &state : *fences) {
if (state && state->fence) {
rawFences.push_back(state->fence->get());
values.push_back(state->signaledValue);
}
}
}
if (!rawFences.empty()) {
desc.fenceCount = rawFences.size();
desc.fences = rawFences.data();
desc.signaledValues = values.data();
} else {
desc.fenceCount = 0;
desc.fences = nullptr;
desc.signaledValues = nullptr;
}

#if defined(__ANDROID__)
// Dawn's Vulkan backend (AHardwareBuffer) validates that the begin-access
Expand All @@ -55,14 +75,21 @@ bool GPUSharedTextureMemory::beginAccess(std::shared_ptr<GPUTexture> texture,
#endif

auto status = _instance.BeginAccess(texture->get(), &desc);
return static_cast<bool>(status);
if (!status) {
throw std::runtime_error("GPUSharedTextureMemory::beginAccess() failed");
}
}

bool GPUSharedTextureMemory::endAccess(std::shared_ptr<GPUTexture> texture) {
if (!texture) {
throw std::runtime_error(
"GPUSharedTextureMemory::endAccess(): texture is null");
jsi::Value GPUSharedTextureMemory::endAccess(jsi::Runtime &runtime,
const jsi::Value &,
const jsi::Value *args,
size_t count) {
if (count < 1 || !args[0].isObject()) {
throw jsi::JSError(
runtime, "GPUSharedTextureMemory::endAccess(): expected (texture)");
}
auto texture = GPUTexture::fromValue(runtime, args[0]);

wgpu::SharedTextureMemoryEndAccessState state{};

#if defined(__ANDROID__)
Expand All @@ -74,7 +101,29 @@ bool GPUSharedTextureMemory::endAccess(std::shared_ptr<GPUTexture> texture) {
#endif

auto status = _instance.EndAccess(texture->get(), &state);
return static_cast<bool>(status);
if (!status) {
throw jsi::JSError(runtime, "GPUSharedTextureMemory::endAccess() failed");
}

// Copy each wgpu::SharedFence (ref-counted) into its own GPUSharedFence
// wrapper before `state` is destroyed.
jsi::Array fences(runtime, state.fenceCount);
for (size_t i = 0; i < state.fenceCount; i++) {
wgpu::SharedFence fence = state.fences[i];
auto wrapper = std::make_shared<GPUSharedFence>(std::move(fence), "");
jsi::Object entry(runtime);
entry.setProperty(runtime, "fence",
GPUSharedFence::create(runtime, std::move(wrapper)));
entry.setProperty(runtime, "signaledValue",
jsi::BigInt::fromUint64(runtime, state.signaledValues[i]));
fences.setValueAtIndex(runtime, i, std::move(entry));
}

jsi::Object result(runtime);
result.setProperty(runtime, "initialized",
jsi::Value(static_cast<bool>(state.initialized)));
result.setProperty(runtime, "fences", std::move(fences));
return result;
}

} // namespace rnwgpu
Loading
Loading