@shopify/react-native-skia 2.9.1-next.1 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/android/cpp/jni/JniWebGPUView.cpp +52 -7
  2. package/android/src/main/java/com/shopify/reactnative/skia/WebGPUSurfaceView.java +3 -1
  3. package/android/src/main/java/com/shopify/reactnative/skia/WebGPUTextureView.java +11 -5
  4. package/android/src/main/java/com/shopify/reactnative/skia/WebGPUView.java +10 -7
  5. package/android/src/main/java/com/shopify/reactnative/skia/WebGPUViewAPI.java +7 -2
  6. package/android/src/main/java/com/shopify/reactnative/skia/WebGPUViewManager.java +1 -1
  7. package/apple/WebGPUMetalView.mm +49 -6
  8. package/cpp/api/JsiSkParagraphStyle.h +20 -10
  9. package/cpp/api/JsiSkStrutStyle.h +18 -8
  10. package/cpp/api/JsiSkTextStyle.h +34 -24
  11. package/cpp/rnskia/RNSkManager.cpp +6 -0
  12. package/cpp/rnwgpu/SurfaceRegistry.h +469 -131
  13. package/cpp/rnwgpu/api/GPUAdapter.cpp +10 -3
  14. package/cpp/rnwgpu/api/GPUAdapter.h +5 -4
  15. package/cpp/rnwgpu/api/GPUBuffer.cpp +12 -2
  16. package/cpp/rnwgpu/api/GPUBuffer.h +3 -2
  17. package/cpp/rnwgpu/api/GPUCanvasContext.cpp +33 -19
  18. package/cpp/rnwgpu/api/GPUCanvasContext.h +3 -1
  19. package/cpp/rnwgpu/api/GPUDevice.cpp +32 -17
  20. package/cpp/rnwgpu/api/GPUDevice.h +9 -7
  21. package/cpp/rnwgpu/api/GPUQueue.cpp +6 -2
  22. package/cpp/rnwgpu/api/GPUQueue.h +3 -3
  23. package/cpp/rnwgpu/api/GPUShaderModule.cpp +7 -2
  24. package/cpp/rnwgpu/api/GPUShaderModule.h +3 -3
  25. package/cpp/rnwgpu/api/RNWebGPU.h +20 -0
  26. package/cpp/rnwgpu/async/RuntimeContext.cpp +5 -0
  27. package/cpp/rnwgpu/async/RuntimeContext.h +15 -2
  28. package/lib/commonjs/views/WebGPUCanvas.d.ts +1 -0
  29. package/lib/commonjs/views/WebGPUCanvas.js +13 -0
  30. package/lib/commonjs/views/WebGPUCanvas.js.map +1 -1
  31. package/lib/module/views/WebGPUCanvas.d.ts +1 -0
  32. package/lib/module/views/WebGPUCanvas.js +14 -1
  33. package/lib/module/views/WebGPUCanvas.js.map +1 -1
  34. package/lib/typescript/src/views/WebGPUCanvas.d.ts +1 -0
  35. package/package.json +7 -7
  36. package/src/views/WebGPUCanvas.tsx +17 -1
  37. package/cpp/dawn/include/dawn/dawn_proc.h +0 -53
  38. package/cpp/dawn/include/dawn/dawn_proc_table.h +0 -330
  39. package/cpp/dawn/include/dawn/dawn_thread_dispatch_proc.h +0 -47
  40. package/cpp/dawn/include/dawn/dawn_version.h +0 -41
  41. package/cpp/dawn/include/dawn/native/D3D11Backend.h +0 -65
  42. package/cpp/dawn/include/dawn/native/D3D12Backend.h +0 -86
  43. package/cpp/dawn/include/dawn/native/D3DBackend.h +0 -56
  44. package/cpp/dawn/include/dawn/native/DawnNative.h +0 -369
  45. package/cpp/dawn/include/dawn/native/MetalBackend.h +0 -56
  46. package/cpp/dawn/include/dawn/native/NullBackend.h +0 -39
  47. package/cpp/dawn/include/dawn/native/OpenGLBackend.h +0 -89
  48. package/cpp/dawn/include/dawn/native/VulkanBackend.h +0 -183
  49. package/cpp/dawn/include/dawn/native/WebGPUBackend.h +0 -49
  50. package/cpp/dawn/include/dawn/native/dawn_native_export.h +0 -49
  51. package/cpp/dawn/include/dawn/platform/DawnPlatform.h +0 -210
  52. package/cpp/dawn/include/dawn/platform/dawn_platform_export.h +0 -49
  53. package/cpp/dawn/include/dawn/replay/Replay.h +0 -99
  54. package/cpp/dawn/include/dawn/replay/dawn_replay_export.h +0 -49
  55. package/cpp/dawn/include/dawn/webgpu_cpp_print.h +0 -2844
  56. package/cpp/dawn/include/tint/tint.h +0 -90
  57. package/cpp/dawn/include/webgpu/webgpu.h +0 -4987
  58. package/cpp/dawn/include/webgpu/webgpu_cpp.h +0 -10080
  59. package/cpp/dawn/include/webgpu/webgpu_cpp_chained_struct.h +0 -57
  60. package/cpp/dawn/include/webgpu/webgpu_enum_class_bitmasks.h +0 -161
  61. package/cpp/dawn/include/webgpu/webgpu_glfw.h +0 -88
  62. package/cpp/skia/src/gpu/graphite/ContextOptionsPriv.h +0 -45
  63. package/cpp/skia/src/gpu/graphite/ResourceTypes.h +0 -360
  64. package/cpp/skia/src/gpu/graphite/TextureProxyView.h +0 -105
  65. package/libs/.graphite +0 -1
@@ -18,6 +18,7 @@
18
18
  namespace rnwgpu {
19
19
 
20
20
  async::AsyncTaskHandle GPUAdapter::requestDevice(
21
+ jsi::Runtime &runtime,
21
22
  std::optional<std::shared_ptr<GPUDeviceDescriptor>> descriptor) {
22
23
  wgpu::DeviceDescriptor aDescriptor;
23
24
  Convertor conv;
@@ -104,9 +105,15 @@ async::AsyncTaskHandle GPUAdapter::requestDevice(
104
105
  descriptor.has_value() ? descriptor.value()->label.value_or("") : "";
105
106
 
106
107
  auto creationRuntime = getCreationRuntime();
107
- return _async->postTask(
108
+ // Post to the CALLING runtime's context so the promise settles on the
109
+ // thread that requested it (see GPUBuffer::mapAsync). The GPUDevice is also
110
+ // bound to this context, honoring the contract that a device belongs to the
111
+ // runtime that requested it.
112
+ auto context =
113
+ async::RuntimeContext::getOrCreate(runtime, _async->instance());
114
+ return context->postTask(
108
115
  [this, aDescriptor, descriptor, label = std::move(label),
109
- deviceLostBinding,
116
+ deviceLostBinding, context,
110
117
  creationRuntime](const async::AsyncTaskHandle::ResolveFunction &resolve,
111
118
  const async::AsyncTaskHandle::RejectFunction &reject) {
112
119
  // Build a local mutable copy so we can chain Dawn's device toggles.
@@ -138,7 +145,7 @@ async::AsyncTaskHandle GPUAdapter::requestDevice(
138
145
  }
139
146
  _instance.RequestDevice(
140
147
  &deviceDesc, wgpu::CallbackMode::AllowProcessEvents,
141
- [context = _async, resolve, reject, label, creationRuntime,
148
+ [context, resolve, reject, label, creationRuntime,
142
149
  deviceLostBinding](wgpu::RequestDeviceStatus status,
143
150
  wgpu::Device device,
144
151
  wgpu::StringView message) {
@@ -33,8 +33,9 @@ public:
33
33
  public:
34
34
  std::string getBrand() { return CLASS_NAME; }
35
35
 
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);
38
39
 
39
40
  std::unordered_set<std::string> getFeatures();
40
41
  std::shared_ptr<GPUSupportedLimits> getLimits();
@@ -42,8 +43,8 @@ public:
42
43
 
43
44
  static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
44
45
  installGetter(runtime, prototype, "__brand", &GPUAdapter::getBrand);
45
- installMethod(runtime, prototype, "requestDevice",
46
- &GPUAdapter::requestDevice);
46
+ installMethodWithRuntime(runtime, prototype, "requestDevice",
47
+ &GPUAdapter::requestDevice);
47
48
  installGetter(runtime, prototype, "features", &GPUAdapter::getFeatures);
48
49
  installGetter(runtime, prototype, "limits", &GPUAdapter::getLimits);
49
50
  installGetter(runtime, prototype, "info", &GPUAdapter::getInfo);
@@ -37,7 +37,8 @@ GPUBuffer::getMappedRange(std::optional<size_t> o, std::optional<size_t> size) {
37
37
 
38
38
  void GPUBuffer::destroy() { _instance.Destroy(); }
39
39
 
40
- async::AsyncTaskHandle GPUBuffer::mapAsync(uint64_t modeIn,
40
+ async::AsyncTaskHandle GPUBuffer::mapAsync(jsi::Runtime &runtime,
41
+ uint64_t modeIn,
41
42
  std::optional<uint64_t> offset,
42
43
  std::optional<uint64_t> size) {
43
44
  Convertor conv;
@@ -51,7 +52,16 @@ async::AsyncTaskHandle GPUBuffer::mapAsync(uint64_t modeIn,
51
52
  auto bufferHandle = _instance;
52
53
  uint64_t resolvedOffset = offset.value_or(0);
53
54
 
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(
55
65
  [bufferHandle, mode, resolvedOffset,
56
66
  rangeSize](const async::AsyncTaskHandle::ResolveFunction &resolve,
57
67
  const async::AsyncTaskHandle::RejectFunction &reject) {
@@ -33,7 +33,7 @@ public:
33
33
  public:
34
34
  std::string getBrand() { return CLASS_NAME; }
35
35
 
36
- async::AsyncTaskHandle mapAsync(uint64_t modeIn,
36
+ async::AsyncTaskHandle mapAsync(jsi::Runtime &runtime, uint64_t modeIn,
37
37
  std::optional<uint64_t> offset,
38
38
  std::optional<uint64_t> size);
39
39
  std::shared_ptr<ArrayBuffer> getMappedRange(std::optional<size_t> offset,
@@ -53,7 +53,8 @@ public:
53
53
 
54
54
  static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
55
55
  installGetter(runtime, prototype, "__brand", &GPUBuffer::getBrand);
56
- installMethod(runtime, prototype, "mapAsync", &GPUBuffer::mapAsync);
56
+ installMethodWithRuntime(runtime, prototype, "mapAsync",
57
+ &GPUBuffer::mapAsync);
57
58
  installMethod(runtime, prototype, "getMappedRange",
58
59
  &GPUBuffer::getMappedRange);
59
60
  installMethod(runtime, prototype, "unmap", &GPUBuffer::unmap);
@@ -1,6 +1,9 @@
1
1
  #include "GPUCanvasContext.h"
2
2
  #include "Convertors.h"
3
+ #include <algorithm>
3
4
  #include <memory>
5
+ #include <utility>
6
+ #include <vector>
4
7
 
5
8
  namespace rnwgpu {
6
9
 
@@ -9,37 +12,50 @@ void GPUCanvasContext::configure(
9
12
  Convertor conv;
10
13
  wgpu::SurfaceConfiguration surfaceConfiguration;
11
14
  surfaceConfiguration.device = configuration->device->get();
12
- if (configuration->viewFormats.has_value()) {
13
- if (!conv(surfaceConfiguration.viewFormats,
14
- surfaceConfiguration.viewFormatCount,
15
- configuration->viewFormats.value())) {
16
- throw std::runtime_error("Error with SurfaceConfiguration");
17
- }
18
- }
19
15
  if (!conv(surfaceConfiguration.usage, configuration->usage) ||
20
16
  !conv(surfaceConfiguration.format, configuration->format)) {
21
17
  throw std::runtime_error("Error with SurfaceConfiguration");
22
18
  }
19
+ // viewFormats are deep-copied into SurfaceInfo (which outlives this call);
20
+ // Convertor-allocated arrays would dangle.
21
+ std::vector<wgpu::TextureFormat> viewFormats;
22
+ if (configuration->viewFormats.has_value()) {
23
+ viewFormats = configuration->viewFormats.value();
24
+ }
23
25
 
24
26
  #ifdef __APPLE__
25
27
  surfaceConfiguration.alphaMode = configuration->alphaMode;
26
28
  #endif
27
29
  surfaceConfiguration.presentMode = wgpu::PresentMode::Fifo;
28
- _surfaceInfo->configure(surfaceConfiguration);
30
+ _surfaceInfo->configure(surfaceConfiguration, std::move(viewFormats));
29
31
  }
30
32
 
31
- void GPUCanvasContext::unconfigure() {}
33
+ void GPUCanvasContext::unconfigure() { _surfaceInfo->unconfigure(); }
32
34
 
33
35
  std::shared_ptr<GPUTexture> GPUCanvasContext::getCurrentTexture() {
36
+ if (!_surfaceInfo->isConfigured()) {
37
+ // Web parity: on the web this is an InvalidStateError, not a crash.
38
+ throw std::runtime_error(
39
+ "[WebGPU] getCurrentTexture() called on a canvas context that is not "
40
+ "configured; call context.configure() first");
41
+ }
42
+ // The drawing buffer tracks canvas.width/height (like on the web); resize it
43
+ // lazily when they changed. Sizes are clamped to 1 so a canvas that has not
44
+ // been laid out yet (0x0) keeps working.
34
45
  auto prevSize = _surfaceInfo->getConfig();
35
- auto width = _canvas->getWidth();
36
- auto height = _canvas->getHeight();
37
- auto sizeHasChanged = prevSize.width != width || prevSize.height != height;
46
+ auto width = std::max(1, _canvas->getWidth());
47
+ auto height = std::max(1, _canvas->getHeight());
48
+ auto sizeHasChanged = prevSize.width != static_cast<uint32_t>(width) ||
49
+ prevSize.height != static_cast<uint32_t>(height);
38
50
  if (sizeHasChanged) {
39
51
  _surfaceInfo->reconfigure(width, height);
40
52
  }
41
53
 
42
54
  auto texture = _surfaceInfo->getCurrentTexture();
55
+ if (texture == nullptr) {
56
+ throw std::runtime_error(
57
+ "[WebGPU] getCurrentTexture() failed to acquire a texture");
58
+ }
43
59
 
44
60
  auto size = _surfaceInfo->getSize();
45
61
  _canvas->setClientWidth(size.width);
@@ -49,13 +65,11 @@ std::shared_ptr<GPUTexture> GPUCanvasContext::getCurrentTexture() {
49
65
  }
50
66
 
51
67
  void GPUCanvasContext::present() {
52
- // Present runs synchronously on the calling thread (the one that did
53
- // getCurrentTexture / submit), preserving Dawn surface thread-affinity.
54
- // Required on every runtime (main JS, Reanimated UI, dedicated worklet);
55
- // offscreen surfaces have no wgpu::Surface so they no-op.
56
- if (_surfaceInfo->hasSurface()) {
57
- _surfaceInfo->presentFrame();
58
- }
68
+ // presentFrame() is the end-of-frame boundary: it presents when this frame's
69
+ // texture was acquired from the on-screen surface (offscreen and dropped
70
+ // frames are skipped), clears the frame state, and adopts any surface that
71
+ // attached while the frame was in flight.
72
+ _surfaceInfo->presentFrame();
59
73
  }
60
74
 
61
75
  } // namespace rnwgpu
@@ -56,7 +56,9 @@ public:
56
56
  std::shared_ptr<GPUTexture> getCurrentTexture();
57
57
  // Present is explicit on every runtime (main JS, Reanimated UI, and dedicated
58
58
  // worklet runtimes). It runs synchronously on the calling thread, preserving
59
- // Dawn surface thread-affinity; offscreen surfaces no-op.
59
+ // Dawn surface thread-affinity; frames rendered offscreen (no surface
60
+ // attached) are skipped. It is also the end-of-frame boundary at which a
61
+ // newly attached native surface is adopted (see SurfaceInfo).
60
62
  void present();
61
63
 
62
64
  private:
@@ -327,6 +327,7 @@ std::shared_ptr<GPUSharedFence> GPUDevice::importSharedFence(
327
327
  }
328
328
 
329
329
  async::AsyncTaskHandle GPUDevice::createComputePipelineAsync(
330
+ jsi::Runtime &runtime,
330
331
  std::shared_ptr<GPUComputePipelineDescriptor> descriptor) {
331
332
  wgpu::ComputePipelineDescriptor desc{};
332
333
  Convertor conv;
@@ -339,12 +340,16 @@ async::AsyncTaskHandle GPUDevice::createComputePipelineAsync(
339
340
  descriptor->label.has_value() ? descriptor->label.value() : "");
340
341
  auto pipelineHolder = std::make_shared<GPUComputePipeline>(nullptr, label);
341
342
 
342
- return _async->postTask([device = _instance, desc, descriptor,
343
- pipelineHolder](
344
- const async::AsyncTaskHandle::ResolveFunction
345
- &resolve,
346
- const async::AsyncTaskHandle::RejectFunction
347
- &reject) {
343
+ // Post to the CALLING runtime's context so the promise settles on the
344
+ // thread that requested it (see GPUBuffer::mapAsync).
345
+ auto context =
346
+ async::RuntimeContext::getOrCreate(runtime, _async->instance());
347
+ return context->postTask([device = _instance, desc, descriptor,
348
+ pipelineHolder](
349
+ const async::AsyncTaskHandle::ResolveFunction
350
+ &resolve,
351
+ const async::AsyncTaskHandle::RejectFunction
352
+ &reject) {
348
353
  (void)descriptor;
349
354
  device.CreateComputePipelineAsync(
350
355
  &desc, wgpu::CallbackMode::AllowProcessEvents,
@@ -368,6 +373,7 @@ async::AsyncTaskHandle GPUDevice::createComputePipelineAsync(
368
373
  }
369
374
 
370
375
  async::AsyncTaskHandle GPUDevice::createRenderPipelineAsync(
376
+ jsi::Runtime &runtime,
371
377
  std::shared_ptr<GPURenderPipelineDescriptor> descriptor) {
372
378
  wgpu::RenderPipelineDescriptor desc{};
373
379
  Convertor conv;
@@ -381,12 +387,16 @@ async::AsyncTaskHandle GPUDevice::createRenderPipelineAsync(
381
387
  descriptor->label.has_value() ? descriptor->label.value() : "");
382
388
  auto pipelineHolder = std::make_shared<GPURenderPipeline>(nullptr, label);
383
389
 
384
- return _async->postTask([device = _instance, desc, descriptor,
385
- pipelineHolder](
386
- const async::AsyncTaskHandle::ResolveFunction
387
- &resolve,
388
- const async::AsyncTaskHandle::RejectFunction
389
- &reject) {
390
+ // Post to the CALLING runtime's context so the promise settles on the
391
+ // thread that requested it (see GPUBuffer::mapAsync).
392
+ auto context =
393
+ async::RuntimeContext::getOrCreate(runtime, _async->instance());
394
+ return context->postTask([device = _instance, desc, descriptor,
395
+ pipelineHolder](
396
+ const async::AsyncTaskHandle::ResolveFunction
397
+ &resolve,
398
+ const async::AsyncTaskHandle::RejectFunction
399
+ &reject) {
390
400
  (void)descriptor;
391
401
  device.CreateRenderPipelineAsync(
392
402
  &desc, wgpu::CallbackMode::AllowProcessEvents,
@@ -413,13 +423,18 @@ void GPUDevice::pushErrorScope(wgpu::ErrorFilter filter) {
413
423
  _instance.PushErrorScope(filter);
414
424
  }
415
425
 
416
- async::AsyncTaskHandle GPUDevice::popErrorScope() {
426
+ async::AsyncTaskHandle GPUDevice::popErrorScope(jsi::Runtime &runtime) {
417
427
  auto device = _instance;
418
428
 
419
- return _async->postTask([device](const async::AsyncTaskHandle::ResolveFunction
420
- &resolve,
421
- const async::AsyncTaskHandle::RejectFunction
422
- &reject) {
429
+ // Post to the CALLING runtime's context so the promise settles on the
430
+ // thread that requested it (see GPUBuffer::mapAsync).
431
+ auto context =
432
+ async::RuntimeContext::getOrCreate(runtime, _async->instance());
433
+ return context->postTask([device](
434
+ const async::AsyncTaskHandle::ResolveFunction
435
+ &resolve,
436
+ const async::AsyncTaskHandle::RejectFunction
437
+ &reject) {
423
438
  device.PopErrorScope(
424
439
  wgpu::CallbackMode::AllowProcessEvents,
425
440
  [resolve, reject](wgpu::PopErrorScopeStatus status,
@@ -141,8 +141,10 @@ public:
141
141
  std::shared_ptr<GPURenderPipeline>
142
142
  createRenderPipeline(std::shared_ptr<GPURenderPipelineDescriptor> descriptor);
143
143
  async::AsyncTaskHandle createComputePipelineAsync(
144
+ jsi::Runtime &runtime,
144
145
  std::shared_ptr<GPUComputePipelineDescriptor> descriptor);
145
146
  async::AsyncTaskHandle createRenderPipelineAsync(
147
+ jsi::Runtime &runtime,
146
148
  std::shared_ptr<GPURenderPipelineDescriptor> descriptor);
147
149
  std::shared_ptr<GPUCommandEncoder> createCommandEncoder(
148
150
  std::optional<std::shared_ptr<GPUCommandEncoderDescriptor>> descriptor);
@@ -151,7 +153,7 @@ public:
151
153
  std::shared_ptr<GPUQuerySet>
152
154
  createQuerySet(std::shared_ptr<GPUQuerySetDescriptor> descriptor);
153
155
  void pushErrorScope(wgpu::ErrorFilter filter);
154
- async::AsyncTaskHandle popErrorScope();
156
+ async::AsyncTaskHandle popErrorScope(jsi::Runtime &runtime);
155
157
 
156
158
  std::unordered_set<std::string> getFeatures();
157
159
  std::shared_ptr<GPUSupportedLimits> getLimits();
@@ -197,10 +199,10 @@ public:
197
199
  &GPUDevice::createComputePipeline);
198
200
  installMethod(runtime, prototype, "createRenderPipeline",
199
201
  &GPUDevice::createRenderPipeline);
200
- installMethod(runtime, prototype, "createComputePipelineAsync",
201
- &GPUDevice::createComputePipelineAsync);
202
- installMethod(runtime, prototype, "createRenderPipelineAsync",
203
- &GPUDevice::createRenderPipelineAsync);
202
+ installMethodWithRuntime(runtime, prototype, "createComputePipelineAsync",
203
+ &GPUDevice::createComputePipelineAsync);
204
+ installMethodWithRuntime(runtime, prototype, "createRenderPipelineAsync",
205
+ &GPUDevice::createRenderPipelineAsync);
204
206
  installMethod(runtime, prototype, "createCommandEncoder",
205
207
  &GPUDevice::createCommandEncoder);
206
208
  installMethod(runtime, prototype, "createRenderBundleEncoder",
@@ -209,8 +211,8 @@ public:
209
211
  &GPUDevice::createQuerySet);
210
212
  installMethod(runtime, prototype, "pushErrorScope",
211
213
  &GPUDevice::pushErrorScope);
212
- installMethod(runtime, prototype, "popErrorScope",
213
- &GPUDevice::popErrorScope);
214
+ installMethodWithRuntime(runtime, prototype, "popErrorScope",
215
+ &GPUDevice::popErrorScope);
214
216
  installGetter(runtime, prototype, "features", &GPUDevice::getFeatures);
215
217
  installGetter(runtime, prototype, "limits", &GPUDevice::getLimits);
216
218
  installGetter(runtime, prototype, "queue", &GPUDevice::getQueue);
@@ -79,9 +79,13 @@ void GPUQueue::writeBuffer(std::shared_ptr<GPUBuffer> buffer,
79
79
  static_cast<size_t>(size64));
80
80
  }
81
81
 
82
- async::AsyncTaskHandle GPUQueue::onSubmittedWorkDone() {
82
+ async::AsyncTaskHandle GPUQueue::onSubmittedWorkDone(jsi::Runtime &runtime) {
83
83
  auto queue = _instance;
84
- return _async->postTask(
84
+ // Post to the CALLING runtime's context so the promise settles on the
85
+ // thread that requested it (see GPUBuffer::mapAsync).
86
+ auto context =
87
+ async::RuntimeContext::getOrCreate(runtime, _async->instance());
88
+ return context->postTask(
85
89
  [queue](const async::AsyncTaskHandle::ResolveFunction &resolve,
86
90
  const async::AsyncTaskHandle::RejectFunction &reject) {
87
91
  queue.OnSubmittedWorkDone(
@@ -40,7 +40,7 @@ public:
40
40
  std::string getBrand() { return CLASS_NAME; }
41
41
 
42
42
  void submit(std::vector<std::shared_ptr<GPUCommandBuffer>> commandBuffers);
43
- async::AsyncTaskHandle onSubmittedWorkDone();
43
+ async::AsyncTaskHandle onSubmittedWorkDone(jsi::Runtime &runtime);
44
44
  void writeBuffer(std::shared_ptr<GPUBuffer> buffer, uint64_t bufferOffset,
45
45
  std::shared_ptr<ArrayBuffer> data,
46
46
  std::optional<uint64_t> dataOffsetElements,
@@ -63,8 +63,8 @@ public:
63
63
  static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
64
64
  installGetter(runtime, prototype, "__brand", &GPUQueue::getBrand);
65
65
  installMethod(runtime, prototype, "submit", &GPUQueue::submit);
66
- installMethod(runtime, prototype, "onSubmittedWorkDone",
67
- &GPUQueue::onSubmittedWorkDone);
66
+ installMethodWithRuntime(runtime, prototype, "onSubmittedWorkDone",
67
+ &GPUQueue::onSubmittedWorkDone);
68
68
  installMethod(runtime, prototype, "writeBuffer", &GPUQueue::writeBuffer);
69
69
  installMethod(runtime, prototype, "writeTexture", &GPUQueue::writeTexture);
70
70
  installMethod(runtime, prototype, "copyExternalImageToTexture",
@@ -7,10 +7,15 @@
7
7
 
8
8
  namespace rnwgpu {
9
9
 
10
- async::AsyncTaskHandle GPUShaderModule::getCompilationInfo() {
10
+ async::AsyncTaskHandle
11
+ GPUShaderModule::getCompilationInfo(jsi::Runtime &runtime) {
11
12
  auto module = _instance;
12
13
 
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(
14
19
  [module](const async::AsyncTaskHandle::ResolveFunction &resolve,
15
20
  const async::AsyncTaskHandle::RejectFunction &reject) {
16
21
  auto result = std::make_shared<GPUCompilationInfo>();
@@ -31,7 +31,7 @@ public:
31
31
  public:
32
32
  std::string getBrand() { return CLASS_NAME; }
33
33
 
34
- async::AsyncTaskHandle getCompilationInfo();
34
+ async::AsyncTaskHandle getCompilationInfo(jsi::Runtime &runtime);
35
35
 
36
36
  std::string getLabel() { return _label; }
37
37
  void setLabel(const std::string &label) {
@@ -41,8 +41,8 @@ public:
41
41
 
42
42
  static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
43
43
  installGetter(runtime, prototype, "__brand", &GPUShaderModule::getBrand);
44
- installMethod(runtime, prototype, "getCompilationInfo",
45
- &GPUShaderModule::getCompilationInfo);
44
+ installMethodWithRuntime(runtime, prototype, "getCompilationInfo",
45
+ &GPUShaderModule::getCompilationInfo);
46
46
  installGetterSetter(runtime, prototype, "label", &GPUShaderModule::getLabel,
47
47
  &GPUShaderModule::setLabel);
48
48
  }
@@ -45,11 +45,31 @@ public:
45
45
  nativeInfo.height);
46
46
  }
47
47
 
48
+ // Retires a canvas context from the JS side (Canvas unmount cleanup).
49
+ // Registry entries have two owners, split by whether a native surface is
50
+ // attached:
51
+ // - A native view currently owns a surface (or one is pending): its own
52
+ // teardown (WebGPUMetalView dealloc / WebGPUViewManager.onDropViewInstance)
53
+ // removes the entry. Skipping here keeps React StrictMode safe: its
54
+ // simulated unmount re-runs JS effects without unmounting native views,
55
+ // and removing the entry then would orphan the still-attached surface.
56
+ // - No native surface: the JS side is the last owner and removes the entry.
57
+ // The check-and-erase runs atomically under the registry lock
58
+ // (removeSurfaceInfoIfDetached), serialized against the UI thread's
59
+ // find-or-create + attach (SurfaceRegistry::attachSurface), so a concurrent
60
+ // attach can never be orphaned by this removal.
61
+ void destroyContext(int contextId) {
62
+ auto &registry = rnwgpu::SurfaceRegistry::getInstance();
63
+ registry.removeSurfaceInfoIfDetached(contextId);
64
+ }
65
+
48
66
  static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
49
67
  installGetter(runtime, prototype, "fabric", &RNWebGPU::getFabric);
50
68
  installGetter(runtime, prototype, "gpu", &RNWebGPU::getGPU);
51
69
  installMethod(runtime, prototype, "getNativeSurface",
52
70
  &RNWebGPU::getNativeSurface);
71
+ installMethod(runtime, prototype, "destroyContext",
72
+ &RNWebGPU::destroyContext);
53
73
  installMethod(runtime, prototype, "MakeWebGPUCanvasContext",
54
74
  &RNWebGPU::MakeWebGPUCanvasContext);
55
75
  }
@@ -39,6 +39,11 @@ void RuntimeContext::registerMainRuntime(
39
39
  sMainInvoker = std::move(invoker);
40
40
  }
41
41
 
42
+ std::shared_ptr<facebook::react::CallInvoker>
43
+ RuntimeContext::mainCallInvoker() {
44
+ return sMainInvoker;
45
+ }
46
+
42
47
  RuntimeContext::RuntimeContext(jsi::Runtime &runtime, wgpu::Instance instance)
43
48
  : _runtime(runtime), _instance(std::move(instance)) {}
44
49
 
@@ -55,8 +55,15 @@ namespace rnwgpu::async {
55
55
  * ProcessEvents on one instance is not guaranteed reentrant.
56
56
  *
57
57
  * Threading contract: a RuntimeContext must only be pumped from the runtime it
58
- * was created for. Create and use a GPUDevice (and the buffers/queues derived
59
- * from it) on the same runtime that requested the adapter.
58
+ * was created for. Request/response async entry points (mapAsync,
59
+ * onSubmittedWorkDone, popErrorScope, createComputePipelineAsync,
60
+ * createRenderPipelineAsync, getCompilationInfo, requestAdapter,
61
+ * requestDevice) must NOT use a context captured when the object was created —
62
+ * the object may have been boxed across to another runtime. They resolve the
63
+ * CALLING runtime's context via getOrCreate(runtime, instance) so the promise
64
+ * is settled on the thread it was created on. Spontaneous events (device.lost,
65
+ * uncapturederror) remain bound to the device's own context (best-effort, main
66
+ * runtime only; see the class doc above).
60
67
  */
61
68
  class RuntimeContext : public std::enable_shared_from_this<RuntimeContext> {
62
69
  public:
@@ -84,6 +91,12 @@ public:
84
91
  return _callInvoker;
85
92
  }
86
93
 
94
+ // The main JS runtime's CallInvoker (registered on install), or null before
95
+ // install. Used by the platform views to apply latched surface attaches from
96
+ // the JS thread for contexts that are not actively rendering (see
97
+ // SurfaceInfo::applyPendingAttach).
98
+ static std::shared_ptr<facebook::react::CallInvoker> mainCallInvoker();
99
+
87
100
  // The wgpu::Instance bound to this runtime.
88
101
  wgpu::Instance instance() const { return _instance; }
89
102
 
@@ -6,6 +6,7 @@ declare global {
6
6
  fabric: boolean;
7
7
  getNativeSurface: (contextId: number) => NativeCanvas;
8
8
  MakeWebGPUCanvasContext: (contextId: number, width: number, height: number) => RNCanvasContext;
9
+ destroyContext: (contextId: number) => void;
9
10
  };
10
11
  }
11
12
  type SurfacePointer = bigint;
@@ -21,6 +21,19 @@ const WebGPUCanvas = ({
21
21
  }) => {
22
22
  const viewRef = (0, _react.useRef)(null);
23
23
  const [contextId] = (0, _react.useState)(() => generateContextId());
24
+
25
+ // Retire the native registry entry for this contextId on unmount. When a
26
+ // native surface is still attached, this is a no-op and the native view's
27
+ // own teardown retires the entry instead — which keeps StrictMode's
28
+ // simulated unmount (which re-runs effects without unmounting native views)
29
+ // from orphaning a live surface.
30
+ (0, _react.useEffect)(() => {
31
+ return () => {
32
+ if (typeof RNWebGPU !== "undefined") {
33
+ RNWebGPU.destroyContext(contextId);
34
+ }
35
+ };
36
+ }, [contextId]);
24
37
  (0, _react.useImperativeHandle)(ref, () => ({
25
38
  getContextId: () => contextId,
26
39
  getNativeSurface: () => {
@@ -1 +1 @@
1
- {"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_WebGPUViewNativeComponent","_interopRequireDefault","e","__esModule","default","t","WeakMap","r","n","o","i","f","__proto__","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","_extends","assign","bind","arguments","length","apply","CONTEXT_COUNTER","generateContextId","WebGPUCanvas","transparent","ref","props","viewRef","useRef","contextId","useState","useImperativeHandle","getContextId","getNativeSurface","RNWebGPU","Error","getContext","contextName","current","view","size","getBoundingClientRect","unstable_getBoundingClientRect","MakeWebGPUCanvasContext","width","height","Platform","OS","createElement","View","collapsable","style","flex","exports"],"sources":["WebGPUCanvas.tsx"],"sourcesContent":["import React, { useImperativeHandle, useRef, useState } from \"react\";\nimport type { ViewProps } from \"react-native\";\nimport { View, Platform } from \"react-native\";\n\nimport WebGPUNativeView from \"../specs/WebGPUViewNativeComponent\";\n\nlet CONTEXT_COUNTER = 1;\nfunction generateContextId() {\n return CONTEXT_COUNTER++;\n}\n\ndeclare global {\n var RNWebGPU: {\n gpu: GPU;\n fabric: boolean;\n getNativeSurface: (contextId: number) => NativeCanvas;\n MakeWebGPUCanvasContext: (\n contextId: number,\n width: number,\n height: number\n ) => RNCanvasContext;\n };\n}\n\ntype SurfacePointer = bigint;\n\nexport interface NativeCanvas {\n surface: SurfacePointer;\n width: number;\n height: number;\n clientWidth: number;\n clientHeight: number;\n}\n\nexport type RNCanvasContext = GPUCanvasContext & {\n present: () => void;\n};\n\nexport interface WebGPUCanvasRef {\n getContextId: () => number;\n getContext(contextName: \"webgpu\"): RNCanvasContext | null;\n getNativeSurface: () => NativeCanvas;\n}\n\ninterface WebGPUCanvasProps extends ViewProps {\n transparent?: boolean;\n ref?: React.Ref<WebGPUCanvasRef>;\n}\n\nexport const WebGPUCanvas = ({\n transparent,\n ref,\n ...props\n}: WebGPUCanvasProps) => {\n const viewRef = useRef(null);\n const [contextId] = useState(() => generateContextId());\n\n useImperativeHandle(ref, () => ({\n getContextId: () => contextId,\n getNativeSurface: () => {\n if (typeof RNWebGPU === \"undefined\") {\n throw new Error(\n \"[WebGPU] RNWebGPU is not available. Make sure SK_GRAPHITE is enabled.\"\n );\n }\n return RNWebGPU.getNativeSurface(contextId);\n },\n getContext(contextName: \"webgpu\"): RNCanvasContext | null {\n if (contextName !== \"webgpu\") {\n throw new Error(`[WebGPU] Unsupported context: ${contextName}`);\n }\n if (!viewRef.current) {\n throw new Error(\"[WebGPU] Cannot get context before mount\");\n }\n if (typeof RNWebGPU === \"undefined\") {\n throw new Error(\n \"[WebGPU] RNWebGPU is not available. Make sure SK_GRAPHITE is enabled.\"\n );\n }\n // getBoundingClientRect became stable in RN 0.83\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const view = viewRef.current as any;\n const size =\n \"getBoundingClientRect\" in view\n ? view.getBoundingClientRect()\n : view.unstable_getBoundingClientRect();\n return RNWebGPU.MakeWebGPUCanvasContext(\n contextId,\n size.width,\n size.height\n );\n },\n }));\n\n // WebGPU Canvas is not supported on web\n if (Platform.OS === \"web\") {\n return <View {...props} />;\n }\n\n return (\n <View collapsable={false} ref={viewRef} {...props}>\n <WebGPUNativeView\n style={{ flex: 1 }}\n contextId={contextId}\n transparent={!!transparent}\n />\n </View>\n );\n};\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_WebGPUViewNativeComponent","_interopRequireDefault","e","__esModule","default","t","WeakMap","r","n","o","i","f","__proto__","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","_extends","assign","bind","arguments","length","apply","CONTEXT_COUNTER","generateContextId","WebGPUCanvas","transparent","ref","props","viewRef","useRef","contextId","useState","useEffect","RNWebGPU","destroyContext","useImperativeHandle","getContextId","getNativeSurface","Error","getContext","contextName","current","view","size","getBoundingClientRect","unstable_getBoundingClientRect","MakeWebGPUCanvasContext","width","height","Platform","OS","createElement","View","collapsable","style","flex","exports"],"sources":["WebGPUCanvas.tsx"],"sourcesContent":["import React, { useEffect, useImperativeHandle, useRef, useState } from \"react\";\nimport type { ViewProps } from \"react-native\";\nimport { View, Platform } from \"react-native\";\n\nimport WebGPUNativeView from \"../specs/WebGPUViewNativeComponent\";\n\nlet CONTEXT_COUNTER = 1;\nfunction generateContextId() {\n return CONTEXT_COUNTER++;\n}\n\ndeclare global {\n var RNWebGPU: {\n gpu: GPU;\n fabric: boolean;\n getNativeSurface: (contextId: number) => NativeCanvas;\n MakeWebGPUCanvasContext: (\n contextId: number,\n width: number,\n height: number\n ) => RNCanvasContext;\n // Retires a canvas context; called by the Canvas on unmount (the Canvas\n // owns the native registry entry for its contextId).\n destroyContext: (contextId: number) => void;\n };\n}\n\ntype SurfacePointer = bigint;\n\nexport interface NativeCanvas {\n surface: SurfacePointer;\n width: number;\n height: number;\n clientWidth: number;\n clientHeight: number;\n}\n\nexport type RNCanvasContext = GPUCanvasContext & {\n present: () => void;\n};\n\nexport interface WebGPUCanvasRef {\n getContextId: () => number;\n getContext(contextName: \"webgpu\"): RNCanvasContext | null;\n getNativeSurface: () => NativeCanvas;\n}\n\ninterface WebGPUCanvasProps extends ViewProps {\n transparent?: boolean;\n ref?: React.Ref<WebGPUCanvasRef>;\n}\n\nexport const WebGPUCanvas = ({\n transparent,\n ref,\n ...props\n}: WebGPUCanvasProps) => {\n const viewRef = useRef(null);\n const [contextId] = useState(() => generateContextId());\n\n // Retire the native registry entry for this contextId on unmount. When a\n // native surface is still attached, this is a no-op and the native view's\n // own teardown retires the entry instead — which keeps StrictMode's\n // simulated unmount (which re-runs effects without unmounting native views)\n // from orphaning a live surface.\n useEffect(() => {\n return () => {\n if (typeof RNWebGPU !== \"undefined\") {\n RNWebGPU.destroyContext(contextId);\n }\n };\n }, [contextId]);\n\n useImperativeHandle(ref, () => ({\n getContextId: () => contextId,\n getNativeSurface: () => {\n if (typeof RNWebGPU === \"undefined\") {\n throw new Error(\n \"[WebGPU] RNWebGPU is not available. Make sure SK_GRAPHITE is enabled.\"\n );\n }\n return RNWebGPU.getNativeSurface(contextId);\n },\n getContext(contextName: \"webgpu\"): RNCanvasContext | null {\n if (contextName !== \"webgpu\") {\n throw new Error(`[WebGPU] Unsupported context: ${contextName}`);\n }\n if (!viewRef.current) {\n throw new Error(\"[WebGPU] Cannot get context before mount\");\n }\n if (typeof RNWebGPU === \"undefined\") {\n throw new Error(\n \"[WebGPU] RNWebGPU is not available. Make sure SK_GRAPHITE is enabled.\"\n );\n }\n // getBoundingClientRect became stable in RN 0.83\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const view = viewRef.current as any;\n const size =\n \"getBoundingClientRect\" in view\n ? view.getBoundingClientRect()\n : view.unstable_getBoundingClientRect();\n return RNWebGPU.MakeWebGPUCanvasContext(\n contextId,\n size.width,\n size.height\n );\n },\n }));\n\n // WebGPU Canvas is not supported on web\n if (Platform.OS === \"web\") {\n return <View {...props} />;\n }\n\n return (\n <View collapsable={false} ref={viewRef} {...props}>\n <WebGPUNativeView\n style={{ flex: 1 }}\n contextId={contextId}\n transparent={!!transparent}\n />\n </View>\n );\n};\n"],"mappings":"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","ignoreList":[]}
@@ -6,6 +6,7 @@ declare global {
6
6
  fabric: boolean;
7
7
  getNativeSurface: (contextId: number) => NativeCanvas;
8
8
  MakeWebGPUCanvasContext: (contextId: number, width: number, height: number) => RNCanvasContext;
9
+ destroyContext: (contextId: number) => void;
9
10
  };
10
11
  }
11
12
  type SurfacePointer = bigint;
@@ -1,5 +1,5 @@
1
1
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
2
- import React, { useImperativeHandle, useRef, useState } from "react";
2
+ import React, { useEffect, useImperativeHandle, useRef, useState } from "react";
3
3
  import { View, Platform } from "react-native";
4
4
  import WebGPUNativeView from "../specs/WebGPUViewNativeComponent";
5
5
  let CONTEXT_COUNTER = 1;
@@ -13,6 +13,19 @@ export const WebGPUCanvas = ({
13
13
  }) => {
14
14
  const viewRef = useRef(null);
15
15
  const [contextId] = useState(() => generateContextId());
16
+
17
+ // Retire the native registry entry for this contextId on unmount. When a
18
+ // native surface is still attached, this is a no-op and the native view's
19
+ // own teardown retires the entry instead — which keeps StrictMode's
20
+ // simulated unmount (which re-runs effects without unmounting native views)
21
+ // from orphaning a live surface.
22
+ useEffect(() => {
23
+ return () => {
24
+ if (typeof RNWebGPU !== "undefined") {
25
+ RNWebGPU.destroyContext(contextId);
26
+ }
27
+ };
28
+ }, [contextId]);
16
29
  useImperativeHandle(ref, () => ({
17
30
  getContextId: () => contextId,
18
31
  getNativeSurface: () => {