Skip to content

Commit d282af7

Browse files
authored
Fix async WebGPU ops to settle promises on the calling runtime (#425)
1 parent 9e0f2e6 commit d282af7

11 files changed

Lines changed: 99 additions & 47 deletions

File tree

packages/webgpu/cpp/rnwgpu/api/GPUAdapter.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
namespace rnwgpu {
2424

2525
async::AsyncTaskHandle GPUAdapter::requestDevice(
26+
jsi::Runtime &runtime,
2627
std::optional<std::shared_ptr<GPUDeviceDescriptor>> descriptor) {
2728
// Enable the react-native-wgpu "native-texture" umbrella by default, mirroring
2829
// the web where importExternalTexture is core and needs no feature request.
@@ -135,9 +136,15 @@ async::AsyncTaskHandle GPUAdapter::requestDevice(
135136
descriptor.has_value() ? descriptor.value()->label.value_or("") : "";
136137

137138
auto creationRuntime = getCreationRuntime();
138-
return _async->postTask(
139+
// Post to the CALLING runtime's context so the promise settles on the
140+
// thread that requested it (see GPUBuffer::mapAsync). The GPUDevice is also
141+
// bound to this context, honoring the contract that a device belongs to the
142+
// runtime that requested it.
143+
auto context =
144+
async::RuntimeContext::getOrCreate(runtime, _async->instance());
145+
return context->postTask(
139146
[this, aDescriptor, descriptor, label = std::move(label),
140-
deviceLostBinding,
147+
deviceLostBinding, context,
141148
creationRuntime](const async::AsyncTaskHandle::ResolveFunction &resolve,
142149
const async::AsyncTaskHandle::RejectFunction &reject) {
143150
// Build a local mutable copy so we can chain Dawn's device toggles.
@@ -193,7 +200,7 @@ async::AsyncTaskHandle GPUAdapter::requestDevice(
193200
}
194201
_instance.RequestDevice(
195202
&deviceDesc, wgpu::CallbackMode::AllowProcessEvents,
196-
[context = _async, resolve, reject, label, creationRuntime,
203+
[context, resolve, reject, label, creationRuntime,
197204
deviceLostBinding](wgpu::RequestDeviceStatus status,
198205
wgpu::Device device, wgpu::StringView message) {
199206
if (message.length) {

packages/webgpu/cpp/rnwgpu/api/GPUAdapter.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,18 @@ class GPUAdapter : public NativeObject<GPUAdapter> {
3333
public:
3434
std::string getBrand() { return CLASS_NAME; }
3535

36-
async::AsyncTaskHandle
37-
requestDevice(std::optional<std::shared_ptr<GPUDeviceDescriptor>> descriptor);
36+
async::AsyncTaskHandle requestDevice(
37+
jsi::Runtime &runtime,
38+
std::optional<std::shared_ptr<GPUDeviceDescriptor>> descriptor);
3839

3940
std::unordered_set<std::string> getFeatures();
4041
std::shared_ptr<GPUSupportedLimits> getLimits();
4142
std::shared_ptr<GPUAdapterInfo> getInfo();
4243

4344
static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
4445
installGetter(runtime, prototype, "__brand", &GPUAdapter::getBrand);
45-
installMethod(runtime, prototype, "requestDevice",
46-
&GPUAdapter::requestDevice);
46+
installMethodWithRuntime(runtime, prototype, "requestDevice",
47+
&GPUAdapter::requestDevice);
4748
installGetter(runtime, prototype, "features", &GPUAdapter::getFeatures);
4849
installGetter(runtime, prototype, "limits", &GPUAdapter::getLimits);
4950
installGetter(runtime, prototype, "info", &GPUAdapter::getInfo);

packages/webgpu/cpp/rnwgpu/api/GPUBuffer.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ GPUBuffer::getMappedRange(std::optional<size_t> o, std::optional<size_t> size) {
3737

3838
void GPUBuffer::destroy() { _instance.Destroy(); }
3939

40-
async::AsyncTaskHandle GPUBuffer::mapAsync(uint64_t modeIn,
40+
async::AsyncTaskHandle GPUBuffer::mapAsync(jsi::Runtime &runtime,
41+
uint64_t modeIn,
4142
std::optional<uint64_t> offset,
4243
std::optional<uint64_t> size) {
4344
Convertor conv;
@@ -51,7 +52,16 @@ async::AsyncTaskHandle GPUBuffer::mapAsync(uint64_t modeIn,
5152
auto bufferHandle = _instance;
5253
uint64_t resolvedOffset = offset.value_or(0);
5354

54-
return _async->postTask(
55+
// Post to the CALLING runtime's context, not the one captured at buffer
56+
// creation (_async): the buffer may have been created on another runtime and
57+
// boxed across (e.g. device created on the main JS runtime, mapAsync called
58+
// from a worklet). The returned Promise lives on the calling runtime, so it
59+
// must be settled from that runtime's own thread — and postTask itself
60+
// schedules the pump through its context's runtime (setTimeout), which is
61+
// only safe for the runtime we are currently executing on.
62+
auto context =
63+
async::RuntimeContext::getOrCreate(runtime, _async->instance());
64+
return context->postTask(
5565
[bufferHandle, mode, resolvedOffset,
5666
rangeSize](const async::AsyncTaskHandle::ResolveFunction &resolve,
5767
const async::AsyncTaskHandle::RejectFunction &reject) {

packages/webgpu/cpp/rnwgpu/api/GPUBuffer.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class GPUBuffer : public NativeObject<GPUBuffer> {
3333
public:
3434
std::string getBrand() { return CLASS_NAME; }
3535

36-
async::AsyncTaskHandle mapAsync(uint64_t modeIn,
36+
async::AsyncTaskHandle mapAsync(jsi::Runtime &runtime, uint64_t modeIn,
3737
std::optional<uint64_t> offset,
3838
std::optional<uint64_t> size);
3939
std::shared_ptr<ArrayBuffer> getMappedRange(std::optional<size_t> offset,
@@ -53,7 +53,8 @@ class GPUBuffer : public NativeObject<GPUBuffer> {
5353

5454
static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
5555
installGetter(runtime, prototype, "__brand", &GPUBuffer::getBrand);
56-
installMethod(runtime, prototype, "mapAsync", &GPUBuffer::mapAsync);
56+
installMethodWithRuntime(runtime, prototype, "mapAsync",
57+
&GPUBuffer::mapAsync);
5758
installMethod(runtime, prototype, "getMappedRange",
5859
&GPUBuffer::getMappedRange);
5960
installMethod(runtime, prototype, "unmap", &GPUBuffer::unmap);

packages/webgpu/cpp/rnwgpu/api/GPUDevice.cpp

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ std::shared_ptr<GPUSharedFence> GPUDevice::importSharedFence(
348348
}
349349

350350
async::AsyncTaskHandle GPUDevice::createComputePipelineAsync(
351+
jsi::Runtime &runtime,
351352
std::shared_ptr<GPUComputePipelineDescriptor> descriptor) {
352353
wgpu::ComputePipelineDescriptor desc{};
353354
Convertor conv;
@@ -360,12 +361,16 @@ async::AsyncTaskHandle GPUDevice::createComputePipelineAsync(
360361
descriptor->label.has_value() ? descriptor->label.value() : "");
361362
auto pipelineHolder = std::make_shared<GPUComputePipeline>(nullptr, label);
362363

363-
return _async->postTask([device = _instance, desc, descriptor,
364-
pipelineHolder](
365-
const async::AsyncTaskHandle::ResolveFunction
366-
&resolve,
367-
const async::AsyncTaskHandle::RejectFunction
368-
&reject) {
364+
// Post to the CALLING runtime's context so the promise settles on the
365+
// thread that requested it (see GPUBuffer::mapAsync).
366+
auto context =
367+
async::RuntimeContext::getOrCreate(runtime, _async->instance());
368+
return context->postTask([device = _instance, desc, descriptor,
369+
pipelineHolder](
370+
const async::AsyncTaskHandle::ResolveFunction
371+
&resolve,
372+
const async::AsyncTaskHandle::RejectFunction
373+
&reject) {
369374
(void)descriptor;
370375
device.CreateComputePipelineAsync(
371376
&desc, wgpu::CallbackMode::AllowProcessEvents,
@@ -389,6 +394,7 @@ async::AsyncTaskHandle GPUDevice::createComputePipelineAsync(
389394
}
390395

391396
async::AsyncTaskHandle GPUDevice::createRenderPipelineAsync(
397+
jsi::Runtime &runtime,
392398
std::shared_ptr<GPURenderPipelineDescriptor> descriptor) {
393399
wgpu::RenderPipelineDescriptor desc{};
394400
Convertor conv;
@@ -402,12 +408,16 @@ async::AsyncTaskHandle GPUDevice::createRenderPipelineAsync(
402408
descriptor->label.has_value() ? descriptor->label.value() : "");
403409
auto pipelineHolder = std::make_shared<GPURenderPipeline>(nullptr, label);
404410

405-
return _async->postTask([device = _instance, desc, descriptor,
406-
pipelineHolder](
407-
const async::AsyncTaskHandle::ResolveFunction
408-
&resolve,
409-
const async::AsyncTaskHandle::RejectFunction
410-
&reject) {
411+
// Post to the CALLING runtime's context so the promise settles on the
412+
// thread that requested it (see GPUBuffer::mapAsync).
413+
auto context =
414+
async::RuntimeContext::getOrCreate(runtime, _async->instance());
415+
return context->postTask([device = _instance, desc, descriptor,
416+
pipelineHolder](
417+
const async::AsyncTaskHandle::ResolveFunction
418+
&resolve,
419+
const async::AsyncTaskHandle::RejectFunction
420+
&reject) {
411421
(void)descriptor;
412422
device.CreateRenderPipelineAsync(
413423
&desc, wgpu::CallbackMode::AllowProcessEvents,
@@ -433,13 +443,18 @@ void GPUDevice::pushErrorScope(wgpu::ErrorFilter filter) {
433443
_instance.PushErrorScope(filter);
434444
}
435445

436-
async::AsyncTaskHandle GPUDevice::popErrorScope() {
446+
async::AsyncTaskHandle GPUDevice::popErrorScope(jsi::Runtime &runtime) {
437447
auto device = _instance;
438448

439-
return _async->postTask([device](const async::AsyncTaskHandle::ResolveFunction
440-
&resolve,
441-
const async::AsyncTaskHandle::RejectFunction
442-
&reject) {
449+
// Post to the CALLING runtime's context so the promise settles on the
450+
// thread that requested it (see GPUBuffer::mapAsync).
451+
auto context =
452+
async::RuntimeContext::getOrCreate(runtime, _async->instance());
453+
return context->postTask([device](
454+
const async::AsyncTaskHandle::ResolveFunction
455+
&resolve,
456+
const async::AsyncTaskHandle::RejectFunction
457+
&reject) {
443458
device.PopErrorScope(
444459
wgpu::CallbackMode::AllowProcessEvents,
445460
[resolve, reject](wgpu::PopErrorScopeStatus status,

packages/webgpu/cpp/rnwgpu/api/GPUDevice.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,10 @@ class GPUDevice : public NativeObject<GPUDevice> {
136136
std::shared_ptr<GPURenderPipeline>
137137
createRenderPipeline(std::shared_ptr<GPURenderPipelineDescriptor> descriptor);
138138
async::AsyncTaskHandle createComputePipelineAsync(
139+
jsi::Runtime &runtime,
139140
std::shared_ptr<GPUComputePipelineDescriptor> descriptor);
140141
async::AsyncTaskHandle createRenderPipelineAsync(
142+
jsi::Runtime &runtime,
141143
std::shared_ptr<GPURenderPipelineDescriptor> descriptor);
142144
std::shared_ptr<GPUCommandEncoder> createCommandEncoder(
143145
std::optional<std::shared_ptr<GPUCommandEncoderDescriptor>> descriptor);
@@ -146,7 +148,7 @@ class GPUDevice : public NativeObject<GPUDevice> {
146148
std::shared_ptr<GPUQuerySet>
147149
createQuerySet(std::shared_ptr<GPUQuerySetDescriptor> descriptor);
148150
void pushErrorScope(wgpu::ErrorFilter filter);
149-
async::AsyncTaskHandle popErrorScope();
151+
async::AsyncTaskHandle popErrorScope(jsi::Runtime &runtime);
150152

151153
std::unordered_set<std::string> getFeatures();
152154
std::shared_ptr<GPUSupportedLimits> getLimits();
@@ -192,10 +194,10 @@ class GPUDevice : public NativeObject<GPUDevice> {
192194
&GPUDevice::createComputePipeline);
193195
installMethod(runtime, prototype, "createRenderPipeline",
194196
&GPUDevice::createRenderPipeline);
195-
installMethod(runtime, prototype, "createComputePipelineAsync",
196-
&GPUDevice::createComputePipelineAsync);
197-
installMethod(runtime, prototype, "createRenderPipelineAsync",
198-
&GPUDevice::createRenderPipelineAsync);
197+
installMethodWithRuntime(runtime, prototype, "createComputePipelineAsync",
198+
&GPUDevice::createComputePipelineAsync);
199+
installMethodWithRuntime(runtime, prototype, "createRenderPipelineAsync",
200+
&GPUDevice::createRenderPipelineAsync);
199201
installMethod(runtime, prototype, "createCommandEncoder",
200202
&GPUDevice::createCommandEncoder);
201203
installMethod(runtime, prototype, "createRenderBundleEncoder",
@@ -204,8 +206,8 @@ class GPUDevice : public NativeObject<GPUDevice> {
204206
&GPUDevice::createQuerySet);
205207
installMethod(runtime, prototype, "pushErrorScope",
206208
&GPUDevice::pushErrorScope);
207-
installMethod(runtime, prototype, "popErrorScope",
208-
&GPUDevice::popErrorScope);
209+
installMethodWithRuntime(runtime, prototype, "popErrorScope",
210+
&GPUDevice::popErrorScope);
209211
installGetter(runtime, prototype, "features", &GPUDevice::getFeatures);
210212
installGetter(runtime, prototype, "limits", &GPUDevice::getLimits);
211213
installGetter(runtime, prototype, "queue", &GPUDevice::getQueue);

packages/webgpu/cpp/rnwgpu/api/GPUQueue.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,13 @@ void GPUQueue::writeBuffer(std::shared_ptr<GPUBuffer> buffer,
7878
static_cast<size_t>(size64));
7979
}
8080

81-
async::AsyncTaskHandle GPUQueue::onSubmittedWorkDone() {
81+
async::AsyncTaskHandle GPUQueue::onSubmittedWorkDone(jsi::Runtime &runtime) {
8282
auto queue = _instance;
83-
return _async->postTask(
83+
// Post to the CALLING runtime's context so the promise settles on the
84+
// thread that requested it (see GPUBuffer::mapAsync).
85+
auto context =
86+
async::RuntimeContext::getOrCreate(runtime, _async->instance());
87+
return context->postTask(
8488
[queue](const async::AsyncTaskHandle::ResolveFunction &resolve,
8589
const async::AsyncTaskHandle::RejectFunction &reject) {
8690
queue.OnSubmittedWorkDone(

packages/webgpu/cpp/rnwgpu/api/GPUQueue.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class GPUQueue : public NativeObject<GPUQueue> {
3737
std::string getBrand() { return CLASS_NAME; }
3838

3939
void submit(std::vector<std::shared_ptr<GPUCommandBuffer>> commandBuffers);
40-
async::AsyncTaskHandle onSubmittedWorkDone();
40+
async::AsyncTaskHandle onSubmittedWorkDone(jsi::Runtime &runtime);
4141
void writeBuffer(std::shared_ptr<GPUBuffer> buffer, uint64_t bufferOffset,
4242
std::shared_ptr<ArrayBuffer> data,
4343
std::optional<uint64_t> dataOffsetElements,
@@ -60,8 +60,8 @@ class GPUQueue : public NativeObject<GPUQueue> {
6060
static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
6161
installGetter(runtime, prototype, "__brand", &GPUQueue::getBrand);
6262
installMethod(runtime, prototype, "submit", &GPUQueue::submit);
63-
installMethod(runtime, prototype, "onSubmittedWorkDone",
64-
&GPUQueue::onSubmittedWorkDone);
63+
installMethodWithRuntime(runtime, prototype, "onSubmittedWorkDone",
64+
&GPUQueue::onSubmittedWorkDone);
6565
installMethod(runtime, prototype, "writeBuffer", &GPUQueue::writeBuffer);
6666
installMethod(runtime, prototype, "writeTexture", &GPUQueue::writeTexture);
6767
installMethod(runtime, prototype, "copyExternalImageToTexture",

packages/webgpu/cpp/rnwgpu/api/GPUShaderModule.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@
77

88
namespace rnwgpu {
99

10-
async::AsyncTaskHandle GPUShaderModule::getCompilationInfo() {
10+
async::AsyncTaskHandle
11+
GPUShaderModule::getCompilationInfo(jsi::Runtime &runtime) {
1112
auto module = _instance;
1213

13-
return _async->postTask(
14+
// Post to the CALLING runtime's context so the promise settles on the
15+
// thread that requested it (see GPUBuffer::mapAsync).
16+
auto context =
17+
async::RuntimeContext::getOrCreate(runtime, _async->instance());
18+
return context->postTask(
1419
[module](const async::AsyncTaskHandle::ResolveFunction &resolve,
1520
const async::AsyncTaskHandle::RejectFunction &reject) {
1621
auto result = std::make_shared<GPUCompilationInfo>();

packages/webgpu/cpp/rnwgpu/api/GPUShaderModule.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class GPUShaderModule : public NativeObject<GPUShaderModule> {
3131
public:
3232
std::string getBrand() { return CLASS_NAME; }
3333

34-
async::AsyncTaskHandle getCompilationInfo();
34+
async::AsyncTaskHandle getCompilationInfo(jsi::Runtime &runtime);
3535

3636
std::string getLabel() { return _label; }
3737
void setLabel(const std::string &label) {
@@ -41,8 +41,8 @@ class GPUShaderModule : public NativeObject<GPUShaderModule> {
4141

4242
static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
4343
installGetter(runtime, prototype, "__brand", &GPUShaderModule::getBrand);
44-
installMethod(runtime, prototype, "getCompilationInfo",
45-
&GPUShaderModule::getCompilationInfo);
44+
installMethodWithRuntime(runtime, prototype, "getCompilationInfo",
45+
&GPUShaderModule::getCompilationInfo);
4646
installGetterSetter(runtime, prototype, "label", &GPUShaderModule::getLabel,
4747
&GPUShaderModule::setLabel);
4848
}

0 commit comments

Comments
 (0)