@shopify/react-native-skia 2.6.6 → 2.6.8

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 (41) hide show
  1. package/android/CMakeLists.txt +1 -2
  2. package/apple/RNSkiaModule.mm +8 -2
  3. package/apple/SkiaManager.mm +10 -5
  4. package/cpp/api/JsiSkApi.h +6 -6
  5. package/cpp/api/JsiSkMaskFilter.h +1 -0
  6. package/cpp/jsi2/NativeObject.h +39 -0
  7. package/cpp/rnskia/RNSkManager.cpp +27 -11
  8. package/cpp/rnwgpu/SurfaceRegistry.h +33 -1
  9. package/cpp/rnwgpu/api/GPU.cpp +15 -12
  10. package/cpp/rnwgpu/api/GPU.h +6 -3
  11. package/cpp/rnwgpu/api/GPUAdapter.cpp +2 -2
  12. package/cpp/rnwgpu/api/GPUAdapter.h +3 -3
  13. package/cpp/rnwgpu/api/GPUBuffer.h +3 -3
  14. package/cpp/rnwgpu/api/GPUCanvasContext.cpp +13 -16
  15. package/cpp/rnwgpu/api/GPUCanvasContext.h +3 -0
  16. package/cpp/rnwgpu/api/GPUDevice.cpp +48 -13
  17. package/cpp/rnwgpu/api/GPUDevice.h +12 -3
  18. package/cpp/rnwgpu/api/GPUQueue.h +3 -3
  19. package/cpp/rnwgpu/api/GPUShaderModule.h +3 -3
  20. package/cpp/rnwgpu/api/WebGPUConstants.h +36 -0
  21. package/cpp/rnwgpu/async/AsyncTaskHandle.cpp +55 -22
  22. package/cpp/rnwgpu/async/AsyncTaskHandle.h +8 -5
  23. package/cpp/rnwgpu/async/RuntimeContext.cpp +194 -0
  24. package/cpp/rnwgpu/async/RuntimeContext.h +121 -0
  25. package/lib/commonjs/skia/types/WebGPU.d.ts +31 -0
  26. package/lib/commonjs/skia/types/WebGPU.js +99 -0
  27. package/lib/commonjs/skia/types/WebGPU.js.map +1 -1
  28. package/lib/module/skia/types/WebGPU.d.ts +31 -0
  29. package/lib/module/skia/types/WebGPU.js +98 -1
  30. package/lib/module/skia/types/WebGPU.js.map +1 -1
  31. package/lib/typescript/lib/commonjs/skia/types/WebGPU.d.ts +1 -0
  32. package/lib/typescript/lib/module/mock/index.d.ts +1 -0
  33. package/lib/typescript/lib/module/skia/types/WebGPU.d.ts +31 -1
  34. package/lib/typescript/src/skia/types/WebGPU.d.ts +31 -0
  35. package/package.json +1 -1
  36. package/src/skia/types/WebGPU.ts +40 -0
  37. package/cpp/rnwgpu/async/AsyncDispatcher.h +0 -28
  38. package/cpp/rnwgpu/async/AsyncRunner.cpp +0 -182
  39. package/cpp/rnwgpu/async/AsyncRunner.h +0 -57
  40. package/cpp/rnwgpu/async/JSIMicrotaskDispatcher.cpp +0 -23
  41. package/cpp/rnwgpu/async/JSIMicrotaskDispatcher.h +0 -22
@@ -120,9 +120,8 @@ if(SK_GRAPHITE)
120
120
  "${PROJECT_SOURCE_DIR}/../cpp/jsi2/Promise.cpp"
121
121
 
122
122
  # WebGPU async system
123
- "${PROJECT_SOURCE_DIR}/../cpp/rnwgpu/async/AsyncRunner.cpp"
124
123
  "${PROJECT_SOURCE_DIR}/../cpp/rnwgpu/async/AsyncTaskHandle.cpp"
125
- "${PROJECT_SOURCE_DIR}/../cpp/rnwgpu/async/JSIMicrotaskDispatcher.cpp"
124
+ "${PROJECT_SOURCE_DIR}/../cpp/rnwgpu/async/RuntimeContext.cpp"
126
125
 
127
126
  # WebGPU API
128
127
  "${PROJECT_SOURCE_DIR}/../cpp/rnwgpu/api/GPU.cpp"
@@ -35,11 +35,17 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install) {
35
35
  // Already initialized, ignore call.
36
36
  return @true;
37
37
  }
38
- RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge;
38
+ #ifndef RCT_REMOVE_LEGACY_ARCH
39
39
  if (!jsInvoker) {
40
+ RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge;
40
41
  jsInvoker = cxxBridge.jsCallInvoker;
41
42
  }
42
- skiaManager = [[SkiaManager alloc] initWithBridge:cxxBridge
43
+ #endif
44
+ if (!jsInvoker) {
45
+ NSLog(@"[RNSkiaModule] Failed to install SkiaManager: jsInvoker is not initialized.");
46
+ return @false;
47
+ }
48
+ skiaManager = [[SkiaManager alloc] initWithBridge:self.bridge
43
49
  jsInvoker:jsInvoker];
44
50
  return @true;
45
51
  }
@@ -2,12 +2,19 @@
2
2
 
3
3
  #import <Foundation/Foundation.h>
4
4
 
5
- #import <React/RCTBridge+Private.h>
6
5
  #import <React/RCTBridge.h>
7
6
  #import <React/RCTUIManager.h>
8
7
 
9
8
  #import "RNSkApplePlatformContext.h"
10
9
 
10
+ // Forward-declared runtime accessor that is satisfied by RCTCxxBridge
11
+ // (legacy/transitional) and RCTBridgeProxy (bridgeless). Avoids referencing
12
+ // RCTCxxBridge directly, which is compiled out when RCT_REMOVE_LEGACY_ARCH
13
+ // is set (React Native 0.82+).
14
+ @interface RCTBridge (RNSkiaRuntime)
15
+ - (void *)runtime;
16
+ @end
17
+
11
18
  static __weak SkiaManager *sharedInstance = nil;
12
19
 
13
20
  @implementation SkiaManager {
@@ -29,11 +36,9 @@ static __weak SkiaManager *sharedInstance = nil;
29
36
  self = [super init];
30
37
  if (self) {
31
38
  sharedInstance = self;
32
- RCTCxxBridge *cxxBridge = (RCTCxxBridge *)bridge;
33
- if (cxxBridge.runtime) {
34
-
39
+ if (bridge.runtime) {
35
40
  facebook::jsi::Runtime *jsRuntime =
36
- (facebook::jsi::Runtime *)cxxBridge.runtime;
41
+ (facebook::jsi::Runtime *)bridge.runtime;
37
42
 
38
43
  // Create the RNSkiaManager (cross platform)
39
44
  _skManager = std::make_shared<RNSkia::RNSkManager>(
@@ -9,7 +9,7 @@
9
9
  #ifdef SK_GRAPHITE
10
10
  #include "rnskia/RNDawnContext.h"
11
11
  #include "rnwgpu/api/GPUDevice.h"
12
- #include "rnwgpu/async/AsyncRunner.h"
12
+ #include "rnwgpu/async/RuntimeContext.h"
13
13
  #endif
14
14
 
15
15
  #include "JsiNativeBuffer.h"
@@ -164,12 +164,12 @@ public:
164
164
  "getDevice", JSI_HOST_FUNCTION_LAMBDA {
165
165
  #ifdef SK_GRAPHITE
166
166
  auto &dawnContext = DawnContext::getInstance();
167
- auto asyncRunner = rnwgpu::async::AsyncRunner::get(runtime);
168
- if (!asyncRunner) {
169
- throw jsi::JSError(runtime, "AsyncRunner not initialized");
170
- }
167
+ // Per-runtime context: async ops on this device resolve on the calling
168
+ // runtime's own thread (via its ProcessEvents pump).
169
+ auto context = rnwgpu::async::RuntimeContext::getOrCreate(
170
+ runtime, dawnContext.getWGPUInstance());
171
171
  auto device = std::make_shared<rnwgpu::GPUDevice>(
172
- dawnContext.getWGPUDevice(), asyncRunner, "Skia Device");
172
+ dawnContext.getWGPUDevice(), context, "Skia Device");
173
173
  return rnwgpu::GPUDevice::create(runtime, device);
174
174
  #else
175
175
  throw jsi::JSError(runtime,
@@ -30,6 +30,7 @@ public:
30
30
  std::string getObjectType() const override { return "JsiSkMaskFilter"; }
31
31
 
32
32
  EXPORT_JSI_API_TYPENAME(JsiSkMaskFilter, MaskFilter)
33
+ JSI_EXPORT_FUNCTIONS(JSI_EXPORT_FUNC(JsiSkMaskFilter, dispose))
33
34
  };
34
35
 
35
36
  } // namespace RNSkia
@@ -432,6 +432,29 @@ protected:
432
432
  prototype.setProperty(runtime, name, func);
433
433
  }
434
434
 
435
+ /**
436
+ * Install a method whose native implementation needs the calling jsi::Runtime
437
+ * as its first parameter. Used by entry points that must act per-runtime
438
+ * (e.g. GPU::requestAdapter, which creates a per-runtime RuntimeContext).
439
+ */
440
+ template <typename ReturnType, typename... Args>
441
+ static void
442
+ installMethodWithRuntime(jsi::Runtime &runtime, jsi::Object &prototype,
443
+ const char *name,
444
+ ReturnType (Derived::*method)(jsi::Runtime &,
445
+ Args...)) {
446
+ auto func = jsi::Function::createFromHostFunction(
447
+ runtime, jsi::PropNameID::forUtf8(runtime, name), sizeof...(Args),
448
+ [method](jsi::Runtime &rt, const jsi::Value &thisVal,
449
+ const jsi::Value *args, size_t count) -> jsi::Value {
450
+ auto native = Derived::fromValue(rt, thisVal);
451
+ return callMethodWithRuntime(native.get(), method, rt, args,
452
+ std::index_sequence_for<Args...>{},
453
+ count);
454
+ });
455
+ prototype.setProperty(runtime, name, func);
456
+ }
457
+
435
458
  /**
436
459
  * Install a getter on the prototype.
437
460
  */
@@ -567,6 +590,22 @@ protected:
567
590
  }
568
591
 
569
592
  private:
593
+ // Helper to call a method that takes the calling jsi::Runtime as its first
594
+ // parameter, with JSI argument conversion for the rest and JSI conversion of
595
+ // the result.
596
+ template <typename ReturnType, typename... Args, size_t... Is>
597
+ static jsi::Value
598
+ callMethodWithRuntime(Derived *obj,
599
+ ReturnType (Derived::*method)(jsi::Runtime &, Args...),
600
+ jsi::Runtime &runtime, const jsi::Value *args,
601
+ std::index_sequence<Is...>, size_t count) {
602
+ ReturnType result = (obj->*method)(
603
+ runtime, rnwgpu::JSIConverter<std::decay_t<Args>>::fromJSI(
604
+ runtime, args[Is], Is >= count)...);
605
+ return rnwgpu::JSIConverter<std::decay_t<ReturnType>>::toJSI(
606
+ runtime, std::move(result));
607
+ }
608
+
570
609
  // Helper to call a method with JSI argument conversion
571
610
  template <typename ReturnType, typename... Args, size_t... Is>
572
611
  static jsi::Value callMethod(Derived *obj,
@@ -23,6 +23,8 @@
23
23
  #include "rnwgpu/api/descriptors/GPUMapMode.h"
24
24
  #include "rnwgpu/api/descriptors/GPUShaderStage.h"
25
25
  #include "rnwgpu/api/descriptors/GPUTextureUsage.h"
26
+ #include "rnwgpu/api/WebGPUConstants.h"
27
+ #include "rnwgpu/async/RuntimeContext.h"
26
28
  #include "jsi2/Promise.h"
27
29
 
28
30
  #include "include/core/SkData.h"
@@ -82,6 +84,12 @@ void RNSkManager::installBindings() {
82
84
  jsi::Object::createFromHostObject(*_jsRuntime, _viewApi));
83
85
 
84
86
  #ifdef SK_GRAPHITE
87
+ // Register the main runtime + its CallInvoker so spontaneous events
88
+ // (device.lost / uncapturederror) on main-runtime devices can be delivered to
89
+ // the JS thread without the ProcessEvents pump. Worklet-runtime devices have
90
+ // no invoker (best-effort; see the RuntimeContext "Threading model" doc).
91
+ rnwgpu::async::RuntimeContext::registerMainRuntime(_jsRuntime, _jsCallInvoker);
92
+
85
93
  // Install WebGPU constructors
86
94
  rnwgpu::GPU::installConstructor(*_jsRuntime);
87
95
  rnwgpu::GPUUncapturedErrorEvent::installConstructor(*_jsRuntime);
@@ -104,18 +112,26 @@ void RNSkManager::installBindings() {
104
112
  std::move(navigator));
105
113
  }
106
114
 
107
- // Install WebGPU constant objects as plain JS objects
108
- _jsRuntime->global().setProperty(*_jsRuntime, "GPUBufferUsage",
109
- rnwgpu::GPUBufferUsage::create(*_jsRuntime));
110
- _jsRuntime->global().setProperty(*_jsRuntime, "GPUColorWrite",
111
- rnwgpu::GPUColorWrite::create(*_jsRuntime));
112
- _jsRuntime->global().setProperty(*_jsRuntime, "GPUMapMode",
113
- rnwgpu::GPUMapMode::create(*_jsRuntime));
114
- _jsRuntime->global().setProperty(*_jsRuntime, "GPUShaderStage",
115
- rnwgpu::GPUShaderStage::create(*_jsRuntime));
115
+ // Install WebGPU constant objects as plain JS objects on the main runtime.
116
+ rnwgpu::installWebGPUConstants(*_jsRuntime);
117
+
118
+ // Install a global `installWebGPU()` host function so worklet runtimes can get
119
+ // the same constants. A host function captured into a worklet is serialized as
120
+ // a SerializableHostFunction and re-created on the worklet runtime, so the body
121
+ // runs there (its `rt` is the worklet runtime) and installs the constants on
122
+ // that runtime. The constants come from the native wgpu::*Usage enums, so the
123
+ // values stay a single source of truth across every runtime. Calling it on a
124
+ // runtime that already has the globals is a safe, idempotent no-op.
116
125
  _jsRuntime->global().setProperty(
117
- *_jsRuntime, "GPUTextureUsage",
118
- rnwgpu::GPUTextureUsage::create(*_jsRuntime));
126
+ *_jsRuntime, "installWebGPU",
127
+ jsi::Function::createFromHostFunction(
128
+ *_jsRuntime, jsi::PropNameID::forAscii(*_jsRuntime, "installWebGPU"),
129
+ 0,
130
+ [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/,
131
+ const jsi::Value * /*args*/, size_t /*count*/) -> jsi::Value {
132
+ rnwgpu::installWebGPUConstants(rt);
133
+ return jsi::Value::undefined();
134
+ }));
119
135
 
120
136
  // Install RNWebGPU global object for WebGPU Canvas support
121
137
  auto rnWebGPU = std::make_shared<rnwgpu::RNWebGPU>(gpu, nullptr);
@@ -7,6 +7,12 @@
7
7
 
8
8
  #include "webgpu/webgpu_cpp.h"
9
9
 
10
+ #ifdef __APPLE__
11
+ namespace dawn::native::metal {
12
+ void WaitForCommandsToBeScheduled(WGPUDevice device);
13
+ } // namespace dawn::native::metal
14
+ #endif
15
+
10
16
  namespace rnwgpu {
11
17
 
12
18
  struct NativeInfo {
@@ -112,13 +118,39 @@ public:
112
118
  height = newHeight;
113
119
  }
114
120
 
115
- void present() {
121
+ // Present the current surface texture. Called synchronously from the thread
122
+ // that did getCurrentTexture / submit (via GPUCanvasContext::present), so it
123
+ // preserves Dawn surface thread-affinity. No-op when offscreen / unconfigured
124
+ // (no surface).
125
+ void presentFrame() {
126
+ #ifdef __APPLE__
127
+ // Ensure command buffers are scheduled before presenting. Read the device
128
+ // under a shared lock, then wait without holding it (the wait can block).
129
+ // The device may be reconfigured between the two locks; that is safe because
130
+ // present() is called on the rendering thread right after submit(), the wait
131
+ // just flushes that thread's already-submitted work, and the Present() below
132
+ // re-checks `surface` under the unique lock before touching it.
133
+ wgpu::Device device;
134
+ {
135
+ std::shared_lock<std::shared_mutex> lock(_mutex);
136
+ device = config.device;
137
+ }
138
+ if (device) {
139
+ dawn::native::metal::WaitForCommandsToBeScheduled(device.Get());
140
+ }
141
+ #endif
116
142
  std::unique_lock<std::shared_mutex> lock(_mutex);
117
143
  if (surface) {
118
144
  surface.Present();
119
145
  }
120
146
  }
121
147
 
148
+ // True when an on-screen wgpu::Surface is attached (vs offscreen texture).
149
+ bool hasSurface() {
150
+ std::shared_lock<std::shared_mutex> lock(_mutex);
151
+ return surface != nullptr;
152
+ }
153
+
122
154
  wgpu::Texture getCurrentTexture() {
123
155
  std::shared_lock<std::shared_mutex> lock(_mutex);
124
156
  if (surface) {
@@ -9,17 +9,15 @@
9
9
 
10
10
  #include "Convertors.h"
11
11
  #include "jsi2/JSIConverter.h"
12
- #include "rnwgpu/async/JSIMicrotaskDispatcher.h"
12
+ #include "rnwgpu/async/RuntimeContext.h"
13
13
 
14
14
  namespace rnwgpu {
15
15
 
16
- GPU::GPU(jsi::Runtime &runtime, wgpu::Instance instance)
17
- : NativeObject(CLASS_NAME), _instance(instance) {
18
- auto dispatcher = std::make_shared<async::JSIMicrotaskDispatcher>(runtime);
19
- _async = async::AsyncRunner::getOrCreate(runtime, _instance, dispatcher);
20
- }
16
+ GPU::GPU(jsi::Runtime & /*runtime*/, wgpu::Instance instance)
17
+ : NativeObject(CLASS_NAME), _instance(instance) {}
21
18
 
22
19
  async::AsyncTaskHandle GPU::requestAdapter(
20
+ jsi::Runtime &runtime,
23
21
  std::optional<std::shared_ptr<GPURequestAdapterOptions>> options) {
24
22
  wgpu::RequestAdapterOptions aOptions;
25
23
  Convertor conv;
@@ -32,12 +30,17 @@ async::AsyncTaskHandle GPU::requestAdapter(
32
30
  constexpr auto kDefaultBackendType = wgpu::BackendType::Vulkan;
33
31
  #endif
34
32
  aOptions.backendType = kDefaultBackendType;
35
- return _async->postTask(
36
- [this, aOptions](const async::AsyncTaskHandle::ResolveFunction &resolve,
37
- const async::AsyncTaskHandle::RejectFunction &reject) {
33
+
34
+ // Per-runtime context: async ops requested on this runtime resolve on this
35
+ // runtime's own thread (via its ProcessEvents pump).
36
+ auto context = async::RuntimeContext::getOrCreate(runtime, _instance);
37
+ return context->postTask(
38
+ [this, aOptions,
39
+ context](const async::AsyncTaskHandle::ResolveFunction &resolve,
40
+ const async::AsyncTaskHandle::RejectFunction &reject) {
38
41
  _instance.RequestAdapter(
39
42
  &aOptions, wgpu::CallbackMode::AllowProcessEvents,
40
- [asyncRunner = _async, resolve,
43
+ [context, resolve,
41
44
  reject](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter,
42
45
  wgpu::StringView message) {
43
46
  if (message.length) {
@@ -45,8 +48,8 @@ async::AsyncTaskHandle GPU::requestAdapter(
45
48
  }
46
49
 
47
50
  if (status == wgpu::RequestAdapterStatus::Success && adapter) {
48
- auto adapterHost = std::make_shared<GPUAdapter>(
49
- std::move(adapter), asyncRunner);
51
+ auto adapterHost =
52
+ std::make_shared<GPUAdapter>(std::move(adapter), context);
50
53
  auto result =
51
54
  std::variant<std::nullptr_t, std::shared_ptr<GPUAdapter>>(
52
55
  adapterHost);
@@ -9,8 +9,8 @@
9
9
 
10
10
  #include "jsi2/NativeObject.h"
11
11
 
12
- #include "rnwgpu/async/AsyncRunner.h"
13
12
  #include "rnwgpu/async/AsyncTaskHandle.h"
13
+ #include "rnwgpu/async/RuntimeContext.h"
14
14
 
15
15
  #include "webgpu/webgpu_cpp.h"
16
16
 
@@ -32,7 +32,10 @@ public:
32
32
  public:
33
33
  std::string getBrand() { return CLASS_NAME; }
34
34
 
35
+ // requestAdapter needs the calling runtime so each runtime gets its own
36
+ // RuntimeContext (and ProcessEvents pump on its own thread).
35
37
  async::AsyncTaskHandle requestAdapter(
38
+ jsi::Runtime &runtime,
36
39
  std::optional<std::shared_ptr<GPURequestAdapterOptions>> options);
37
40
  wgpu::TextureFormat getPreferredCanvasFormat();
38
41
 
@@ -40,7 +43,8 @@ public:
40
43
 
41
44
  static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
42
45
  installGetter(runtime, prototype, "__brand", &GPU::getBrand);
43
- installMethod(runtime, prototype, "requestAdapter", &GPU::requestAdapter);
46
+ installMethodWithRuntime(runtime, prototype, "requestAdapter",
47
+ &GPU::requestAdapter);
44
48
  installMethod(runtime, prototype, "getPreferredCanvasFormat",
45
49
  &GPU::getPreferredCanvasFormat);
46
50
  installGetter(runtime, prototype, "wgslLanguageFeatures",
@@ -51,7 +55,6 @@ public:
51
55
 
52
56
  private:
53
57
  wgpu::Instance _instance;
54
- std::shared_ptr<async::AsyncRunner> _async;
55
58
  };
56
59
 
57
60
  } // namespace rnwgpu
@@ -138,7 +138,7 @@ async::AsyncTaskHandle GPUAdapter::requestDevice(
138
138
  }
139
139
  _instance.RequestDevice(
140
140
  &deviceDesc, wgpu::CallbackMode::AllowProcessEvents,
141
- [asyncRunner = _async, resolve, reject, label, creationRuntime,
141
+ [context = _async, resolve, reject, label, creationRuntime,
142
142
  deviceLostBinding](wgpu::RequestDeviceStatus status,
143
143
  wgpu::Device device,
144
144
  wgpu::StringView message) {
@@ -190,7 +190,7 @@ async::AsyncTaskHandle GPUAdapter::requestDevice(
190
190
  creationRuntime);
191
191
 
192
192
  auto deviceHost = std::make_shared<GPUDevice>(std::move(device),
193
- asyncRunner, label);
193
+ context, label);
194
194
  *deviceLostBinding = deviceHost;
195
195
  resolve([deviceHost = std::move(deviceHost)](
196
196
  jsi::Runtime &runtime) mutable {
@@ -8,8 +8,8 @@
8
8
 
9
9
  #include "jsi2/NativeObject.h"
10
10
 
11
- #include "rnwgpu/async/AsyncRunner.h"
12
11
  #include "rnwgpu/async/AsyncTaskHandle.h"
12
+ #include "rnwgpu/async/RuntimeContext.h"
13
13
 
14
14
  #include "webgpu/webgpu_cpp.h"
15
15
 
@@ -27,7 +27,7 @@ public:
27
27
  static constexpr const char *CLASS_NAME = "GPUAdapter";
28
28
 
29
29
  explicit GPUAdapter(wgpu::Adapter instance,
30
- std::shared_ptr<async::AsyncRunner> async)
30
+ std::shared_ptr<async::RuntimeContext> async)
31
31
  : NativeObject(CLASS_NAME), _instance(instance), _async(async) {}
32
32
 
33
33
  public:
@@ -53,7 +53,7 @@ public:
53
53
 
54
54
  private:
55
55
  wgpu::Adapter _instance;
56
- std::shared_ptr<async::AsyncRunner> _async;
56
+ std::shared_ptr<async::RuntimeContext> _async;
57
57
  };
58
58
 
59
59
  } // namespace rnwgpu
@@ -9,8 +9,8 @@
9
9
 
10
10
  #include "jsi2/NativeObject.h"
11
11
 
12
- #include "rnwgpu/async/AsyncRunner.h"
13
12
  #include "rnwgpu/async/AsyncTaskHandle.h"
13
+ #include "rnwgpu/async/RuntimeContext.h"
14
14
 
15
15
  #include "webgpu/webgpu_cpp.h"
16
16
 
@@ -25,7 +25,7 @@ public:
25
25
  static constexpr const char *CLASS_NAME = "GPUBuffer";
26
26
 
27
27
  explicit GPUBuffer(wgpu::Buffer instance,
28
- std::shared_ptr<async::AsyncRunner> async,
28
+ std::shared_ptr<async::RuntimeContext> async,
29
29
  std::string label)
30
30
  : NativeObject(CLASS_NAME), _instance(instance), _async(async),
31
31
  _label(label) {}
@@ -71,7 +71,7 @@ public:
71
71
 
72
72
  private:
73
73
  wgpu::Buffer _instance;
74
- std::shared_ptr<async::AsyncRunner> _async;
74
+ std::shared_ptr<async::RuntimeContext> _async;
75
75
  std::string _label;
76
76
  struct Mapping {
77
77
  uint64_t start;
@@ -2,14 +2,6 @@
2
2
  #include "Convertors.h"
3
3
  #include <memory>
4
4
 
5
- #ifdef __APPLE__
6
- namespace dawn::native::metal {
7
-
8
- void WaitForCommandsToBeScheduled(WGPUDevice device);
9
-
10
- }
11
- #endif
12
-
13
5
  namespace rnwgpu {
14
6
 
15
7
  void GPUCanvasContext::configure(
@@ -46,19 +38,24 @@ std::shared_ptr<GPUTexture> GPUCanvasContext::getCurrentTexture() {
46
38
  if (sizeHasChanged) {
47
39
  _surfaceInfo->reconfigure(width, height);
48
40
  }
41
+
49
42
  auto texture = _surfaceInfo->getCurrentTexture();
50
- return std::make_shared<GPUTexture>(texture, "", false);
51
- }
52
43
 
53
- void GPUCanvasContext::present() {
54
- #ifdef __APPLE__
55
- dawn::native::metal::WaitForCommandsToBeScheduled(
56
- _surfaceInfo->getDevice().Get());
57
- #endif
58
44
  auto size = _surfaceInfo->getSize();
59
45
  _canvas->setClientWidth(size.width);
60
46
  _canvas->setClientHeight(size.height);
61
- _surfaceInfo->present();
47
+
48
+ return std::make_shared<GPUTexture>(texture, "", false);
49
+ }
50
+
51
+ 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
+ }
62
59
  }
63
60
 
64
61
  } // namespace rnwgpu
@@ -54,6 +54,9 @@ public:
54
54
  void configure(std::shared_ptr<GPUCanvasConfiguration> configuration);
55
55
  void unconfigure();
56
56
  std::shared_ptr<GPUTexture> getCurrentTexture();
57
+ // Present is explicit on every runtime (main JS, Reanimated UI, and dedicated
58
+ // worklet runtimes). It runs synchronously on the calling thread, preserving
59
+ // Dawn surface thread-affinity; offscreen surfaces no-op.
57
60
  void present();
58
61
 
59
62
  private:
@@ -6,6 +6,8 @@
6
6
  #include <utility>
7
7
  #include <vector>
8
8
 
9
+ #include <ReactCommon/CallInvoker.h>
10
+
9
11
  #include "Convertors.h"
10
12
  #include "NativeBufferUtils.h"
11
13
  #include "jsi2/JSIConverter.h"
@@ -19,23 +21,33 @@ namespace rnwgpu {
19
21
 
20
22
  void GPUDevice::notifyDeviceLost(wgpu::DeviceLostReason reason,
21
23
  std::string message) {
22
- if (_lostSettled) {
23
- return;
24
- }
24
+ std::optional<async::AsyncTaskHandle::ResolveFunction> resolveToCall;
25
+ std::shared_ptr<GPUDeviceLostInfo> info;
26
+ {
27
+ std::lock_guard<std::mutex> lock(_lostMutex);
28
+ if (_lostSettled) {
29
+ return;
30
+ }
31
+
32
+ _lostSettled = true;
33
+ _lostInfo = std::make_shared<GPUDeviceLostInfo>(reason, std::move(message));
34
+ info = _lostInfo;
25
35
 
26
- _lostSettled = true;
27
- _lostInfo = std::make_shared<GPUDeviceLostInfo>(reason, std::move(message));
36
+ if (_lostResolve.has_value()) {
37
+ resolveToCall = std::move(*_lostResolve);
38
+ _lostResolve.reset();
39
+ }
28
40
 
29
- if (_lostResolve.has_value()) {
30
- auto resolve = std::move(*_lostResolve);
31
- _lostResolve.reset();
32
- resolve([info = _lostInfo](jsi::Runtime &runtime) mutable {
41
+ _lostHandle.reset();
42
+ }
43
+
44
+ // Settle outside the lock: resolve() only enqueues onto the JS thread.
45
+ if (resolveToCall.has_value()) {
46
+ (*resolveToCall)([info](jsi::Runtime &runtime) mutable {
33
47
  return JSIConverter<std::shared_ptr<GPUDeviceLostInfo>>::toJSI(runtime,
34
48
  info);
35
49
  });
36
50
  }
37
-
38
- _lostHandle.reset();
39
51
  }
40
52
 
41
53
  void GPUDevice::forceLossForTesting() {
@@ -474,6 +486,11 @@ std::unordered_set<std::string> GPUDevice::getFeatures() {
474
486
  }
475
487
 
476
488
  async::AsyncTaskHandle GPUDevice::getLost() {
489
+ // Held across the whole body: the postTask callback below runs synchronously
490
+ // on this (JS) thread and touches the same _lost* fields, so it must not
491
+ // re-lock. notifyDeviceLost() takes the same lock from its (possibly worker)
492
+ // thread.
493
+ std::lock_guard<std::mutex> lock(_lostMutex);
477
494
  if (_lostHandle.has_value()) {
478
495
  return *_lostHandle;
479
496
  }
@@ -488,7 +505,7 @@ async::AsyncTaskHandle GPUDevice::getLost() {
488
505
  runtime, info);
489
506
  });
490
507
  },
491
- false);
508
+ /*keepPumping=*/false);
492
509
  }
493
510
 
494
511
  auto handle = _async->postTask(
@@ -502,9 +519,10 @@ async::AsyncTaskHandle GPUDevice::getLost() {
502
519
  return;
503
520
  }
504
521
 
522
+ // Resolved later from notifyDeviceLost().
505
523
  _lostResolve = resolve;
506
524
  },
507
- false);
525
+ /*keepPumping=*/false);
508
526
 
509
527
  _lostHandle = handle;
510
528
  return handle;
@@ -529,6 +547,23 @@ void GPUDevice::removeEventListener(std::string type, jsi::Function callback) {
529
547
  }
530
548
 
531
549
  void GPUDevice::notifyUncapturedError(GPUErrorVariant error) {
550
+ // Dawn can surface an uncaptured error from any ProcessEvents pump (a worklet
551
+ // runtime sharing this instance may pump it on the wrong thread). Marshal to
552
+ // the owning runtime's JS thread via its CallInvoker before touching JSI. The
553
+ // invoker is wired only for the main JS runtime, so a device created on a
554
+ // worklet runtime does not deliver uncaptured errors to JS (best-effort; see
555
+ // the Threading model).
556
+ auto invoker = _async ? _async->callInvoker() : nullptr;
557
+ if (!invoker) {
558
+ return;
559
+ }
560
+ auto self = shared_from_this();
561
+ invoker->invokeAsync([self, error = std::move(error)]() mutable {
562
+ self->deliverUncapturedError(std::move(error));
563
+ });
564
+ }
565
+
566
+ void GPUDevice::deliverUncapturedError(GPUErrorVariant error) {
532
567
  auto runtime = getCreationRuntime();
533
568
  if (runtime == nullptr) {
534
569
  return;
@@ -15,8 +15,8 @@
15
15
 
16
16
  #include "jsi2/NativeObject.h"
17
17
 
18
- #include "rnwgpu/async/AsyncRunner.h"
19
18
  #include "rnwgpu/async/AsyncTaskHandle.h"
19
+ #include "rnwgpu/async/RuntimeContext.h"
20
20
 
21
21
  #include "webgpu/webgpu_cpp.h"
22
22
 
@@ -99,7 +99,7 @@ public:
99
99
  static constexpr const char *CLASS_NAME = "GPUDevice";
100
100
 
101
101
  explicit GPUDevice(wgpu::Device instance,
102
- std::shared_ptr<async::AsyncRunner> async,
102
+ std::shared_ptr<async::RuntimeContext> async,
103
103
  std::string label)
104
104
  : NativeObject(CLASS_NAME), _instance(instance), _async(async),
105
105
  _label(label) {
@@ -230,9 +230,18 @@ public:
230
230
  private:
231
231
  friend class GPUAdapter;
232
232
 
233
+ // Runs the uncapturederror listeners on the creation runtime's JS thread.
234
+ // Invoked from notifyUncapturedError via the main CallInvoker.
235
+ void deliverUncapturedError(GPUErrorVariant error);
236
+
233
237
  wgpu::Device _instance;
234
- std::shared_ptr<async::AsyncRunner> _async;
238
+ std::shared_ptr<async::RuntimeContext> _async;
235
239
  std::string _label;
240
+ // Guards the device-lost state below. In the ProcessEvents model both
241
+ // notifyDeviceLost() (fired by Dawn during ProcessEvents) and getLost() run on
242
+ // the owning runtime's own thread, but device destruction can also trigger
243
+ // notifyDeviceLost() synchronously, so the mutex keeps these fields safe.
244
+ std::mutex _lostMutex;
236
245
  std::optional<async::AsyncTaskHandle> _lostHandle;
237
246
  std::shared_ptr<GPUDeviceLostInfo> _lostInfo;
238
247
  bool _lostSettled = false;