@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.
- package/android/CMakeLists.txt +1 -2
- package/apple/RNSkiaModule.mm +8 -2
- package/apple/SkiaManager.mm +10 -5
- package/cpp/api/JsiSkApi.h +6 -6
- package/cpp/api/JsiSkMaskFilter.h +1 -0
- package/cpp/jsi2/NativeObject.h +39 -0
- package/cpp/rnskia/RNSkManager.cpp +27 -11
- package/cpp/rnwgpu/SurfaceRegistry.h +33 -1
- package/cpp/rnwgpu/api/GPU.cpp +15 -12
- package/cpp/rnwgpu/api/GPU.h +6 -3
- package/cpp/rnwgpu/api/GPUAdapter.cpp +2 -2
- package/cpp/rnwgpu/api/GPUAdapter.h +3 -3
- package/cpp/rnwgpu/api/GPUBuffer.h +3 -3
- package/cpp/rnwgpu/api/GPUCanvasContext.cpp +13 -16
- package/cpp/rnwgpu/api/GPUCanvasContext.h +3 -0
- package/cpp/rnwgpu/api/GPUDevice.cpp +48 -13
- package/cpp/rnwgpu/api/GPUDevice.h +12 -3
- package/cpp/rnwgpu/api/GPUQueue.h +3 -3
- package/cpp/rnwgpu/api/GPUShaderModule.h +3 -3
- package/cpp/rnwgpu/api/WebGPUConstants.h +36 -0
- package/cpp/rnwgpu/async/AsyncTaskHandle.cpp +55 -22
- package/cpp/rnwgpu/async/AsyncTaskHandle.h +8 -5
- package/cpp/rnwgpu/async/RuntimeContext.cpp +194 -0
- package/cpp/rnwgpu/async/RuntimeContext.h +121 -0
- package/lib/commonjs/skia/types/WebGPU.d.ts +31 -0
- package/lib/commonjs/skia/types/WebGPU.js +99 -0
- package/lib/commonjs/skia/types/WebGPU.js.map +1 -1
- package/lib/module/skia/types/WebGPU.d.ts +31 -0
- package/lib/module/skia/types/WebGPU.js +98 -1
- package/lib/module/skia/types/WebGPU.js.map +1 -1
- package/lib/typescript/lib/commonjs/skia/types/WebGPU.d.ts +1 -0
- package/lib/typescript/lib/module/mock/index.d.ts +1 -0
- package/lib/typescript/lib/module/skia/types/WebGPU.d.ts +31 -1
- package/lib/typescript/src/skia/types/WebGPU.d.ts +31 -0
- package/package.json +1 -1
- package/src/skia/types/WebGPU.ts +40 -0
- package/cpp/rnwgpu/async/AsyncDispatcher.h +0 -28
- package/cpp/rnwgpu/async/AsyncRunner.cpp +0 -182
- package/cpp/rnwgpu/async/AsyncRunner.h +0 -57
- package/cpp/rnwgpu/async/JSIMicrotaskDispatcher.cpp +0 -23
- package/cpp/rnwgpu/async/JSIMicrotaskDispatcher.h +0 -22
|
@@ -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
|
|
|
@@ -31,7 +31,7 @@ public:
|
|
|
31
31
|
static constexpr const char *CLASS_NAME = "GPUQueue";
|
|
32
32
|
|
|
33
33
|
explicit GPUQueue(wgpu::Queue instance,
|
|
34
|
-
std::shared_ptr<async::
|
|
34
|
+
std::shared_ptr<async::RuntimeContext> async,
|
|
35
35
|
std::string label)
|
|
36
36
|
: NativeObject(CLASS_NAME), _instance(instance), _async(async),
|
|
37
37
|
_label(label) {}
|
|
@@ -77,7 +77,7 @@ public:
|
|
|
77
77
|
|
|
78
78
|
private:
|
|
79
79
|
wgpu::Queue _instance;
|
|
80
|
-
std::shared_ptr<async::
|
|
80
|
+
std::shared_ptr<async::RuntimeContext> _async;
|
|
81
81
|
std::string _label;
|
|
82
82
|
};
|
|
83
83
|
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
#include "jsi2/NativeObject.h"
|
|
9
9
|
|
|
10
|
-
#include "rnwgpu/async/AsyncRunner.h"
|
|
11
10
|
#include "rnwgpu/async/AsyncTaskHandle.h"
|
|
11
|
+
#include "rnwgpu/async/RuntimeContext.h"
|
|
12
12
|
|
|
13
13
|
#include "webgpu/webgpu_cpp.h"
|
|
14
14
|
|
|
@@ -23,7 +23,7 @@ public:
|
|
|
23
23
|
static constexpr const char *CLASS_NAME = "GPUShaderModule";
|
|
24
24
|
|
|
25
25
|
explicit GPUShaderModule(wgpu::ShaderModule instance,
|
|
26
|
-
std::shared_ptr<async::
|
|
26
|
+
std::shared_ptr<async::RuntimeContext> async,
|
|
27
27
|
std::string label)
|
|
28
28
|
: NativeObject(CLASS_NAME), _instance(instance), _async(async),
|
|
29
29
|
_label(label) {}
|
|
@@ -59,7 +59,7 @@ public:
|
|
|
59
59
|
|
|
60
60
|
private:
|
|
61
61
|
wgpu::ShaderModule _instance;
|
|
62
|
-
std::shared_ptr<async::
|
|
62
|
+
std::shared_ptr<async::RuntimeContext> _async;
|
|
63
63
|
std::string _label;
|
|
64
64
|
};
|
|
65
65
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <jsi/jsi.h>
|
|
4
|
+
|
|
5
|
+
#include "descriptors/GPUBufferUsage.h"
|
|
6
|
+
#include "descriptors/GPUColorWrite.h"
|
|
7
|
+
#include "descriptors/GPUMapMode.h"
|
|
8
|
+
#include "descriptors/GPUShaderStage.h"
|
|
9
|
+
#include "descriptors/GPUTextureUsage.h"
|
|
10
|
+
|
|
11
|
+
namespace rnwgpu {
|
|
12
|
+
|
|
13
|
+
namespace jsi = facebook::jsi;
|
|
14
|
+
|
|
15
|
+
// Installs the WebGPU flag constants (GPUBufferUsage, GPUColorWrite, GPUMapMode,
|
|
16
|
+
// GPUShaderStage, GPUTextureUsage) as plain JS objects on `runtime`'s global.
|
|
17
|
+
//
|
|
18
|
+
// The numeric values are derived from the Dawn wgpu::*Usage enums (single source
|
|
19
|
+
// of truth in C++), so this is safe to call on ANY runtime: the main JS runtime
|
|
20
|
+
// at install time, and any worklet runtime (Reanimated UI, dedicated worklet
|
|
21
|
+
// runtimes, Vision Camera frame processors) via the global `installWebGPU()`
|
|
22
|
+
// host function. It is idempotent: re-installing overwrites the globals with
|
|
23
|
+
// equal values.
|
|
24
|
+
inline void installWebGPUConstants(jsi::Runtime &runtime) {
|
|
25
|
+
auto global = runtime.global();
|
|
26
|
+
global.setProperty(runtime, "GPUBufferUsage",
|
|
27
|
+
GPUBufferUsage::create(runtime));
|
|
28
|
+
global.setProperty(runtime, "GPUColorWrite", GPUColorWrite::create(runtime));
|
|
29
|
+
global.setProperty(runtime, "GPUMapMode", GPUMapMode::create(runtime));
|
|
30
|
+
global.setProperty(runtime, "GPUShaderStage",
|
|
31
|
+
GPUShaderStage::create(runtime));
|
|
32
|
+
global.setProperty(runtime, "GPUTextureUsage",
|
|
33
|
+
GPUTextureUsage::create(runtime));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
} // namespace rnwgpu
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#include "AsyncTaskHandle.h"
|
|
2
2
|
|
|
3
|
+
#include <memory>
|
|
3
4
|
#include <string>
|
|
4
5
|
#include <utility>
|
|
5
6
|
|
|
7
|
+
#include <ReactCommon/CallInvoker.h>
|
|
8
|
+
|
|
6
9
|
#include "jsi2/Promise.h"
|
|
7
10
|
|
|
8
|
-
#include "
|
|
11
|
+
#include "RuntimeContext.h"
|
|
9
12
|
|
|
10
13
|
namespace rnwgpu::async {
|
|
11
14
|
|
|
@@ -13,8 +16,8 @@ using Action = std::function<void(jsi::Runtime &, rnwgpu::Promise &)>;
|
|
|
13
16
|
|
|
14
17
|
struct AsyncTaskHandle::State
|
|
15
18
|
: public std::enable_shared_from_this<AsyncTaskHandle::State> {
|
|
16
|
-
State(std::shared_ptr<
|
|
17
|
-
:
|
|
19
|
+
State(std::shared_ptr<RuntimeContext> context, bool keepPumping)
|
|
20
|
+
: context(std::move(context)), keepPumping(keepPumping) {}
|
|
18
21
|
|
|
19
22
|
void settle(Action action);
|
|
20
23
|
void attachPromise(const std::shared_ptr<rnwgpu::Promise> &promise);
|
|
@@ -26,12 +29,12 @@ struct AsyncTaskHandle::State
|
|
|
26
29
|
std::shared_ptr<rnwgpu::Promise> currentPromise();
|
|
27
30
|
|
|
28
31
|
std::mutex mutex;
|
|
29
|
-
std::
|
|
32
|
+
std::shared_ptr<RuntimeContext> context;
|
|
33
|
+
bool keepPumping;
|
|
30
34
|
std::shared_ptr<rnwgpu::Promise> promise;
|
|
31
35
|
std::optional<Action> pendingAction;
|
|
32
36
|
bool settled = false;
|
|
33
37
|
std::shared_ptr<State> keepAlive;
|
|
34
|
-
bool keepPumping;
|
|
35
38
|
};
|
|
36
39
|
|
|
37
40
|
// MARK: - State helpers
|
|
@@ -77,30 +80,60 @@ void AsyncTaskHandle::State::attachPromise(
|
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
void AsyncTaskHandle::State::schedule(Action action) {
|
|
80
|
-
auto
|
|
81
|
-
if (!
|
|
83
|
+
auto promiseRef = currentPromise();
|
|
84
|
+
if (!promiseRef) {
|
|
82
85
|
return;
|
|
83
86
|
}
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
if (!context) {
|
|
89
|
+
// No context (shouldn't happen): best-effort inline settle.
|
|
90
|
+
action(promiseRef->runtime, *promiseRef);
|
|
91
|
+
std::lock_guard<std::mutex> lock(mutex);
|
|
92
|
+
keepAlive.reset();
|
|
88
93
|
return;
|
|
89
94
|
}
|
|
90
95
|
|
|
91
|
-
auto
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
auto self = shared_from_this();
|
|
97
|
+
|
|
98
|
+
if (!keepPumping) {
|
|
99
|
+
// Spontaneous task (e.g. device.lost): not driven by the ProcessEvents pump.
|
|
100
|
+
// Settle on the owning runtime's JS thread via its CallInvoker, which is
|
|
101
|
+
// wired only for the main JS runtime. A device created on a worklet runtime
|
|
102
|
+
// has no invoker, so its device.lost is dropped (best-effort; see the
|
|
103
|
+
// Threading model). invokeAsync runs the closure on the main JS thread,
|
|
104
|
+
// where promiseRef->runtime lives for a main-runtime device.
|
|
105
|
+
auto invoker = context->callInvoker();
|
|
106
|
+
if (invoker) {
|
|
107
|
+
invoker->invokeAsync(
|
|
108
|
+
[self, action = std::move(action), promiseRef]() mutable {
|
|
109
|
+
action(promiseRef->runtime, *promiseRef);
|
|
110
|
+
std::lock_guard<std::mutex> lock(self->mutex);
|
|
111
|
+
self->keepAlive.reset();
|
|
112
|
+
});
|
|
113
|
+
} else {
|
|
114
|
+
std::lock_guard<std::mutex> lock(mutex);
|
|
115
|
+
keepAlive.reset();
|
|
116
|
+
}
|
|
94
117
|
return;
|
|
95
118
|
}
|
|
96
119
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
120
|
+
// Pumping task (request/response op). The resolve/reject callback may fire on
|
|
121
|
+
// a thread that is NOT the owning runtime's thread: with a shared
|
|
122
|
+
// wgpu::Instance, another runtime's ProcessEvents() pump can consume this Dawn
|
|
123
|
+
// event. Touching the Promise's runtime off-thread would corrupt Hermes. So we
|
|
124
|
+
// deposit the actual settle (the only JSI-touching work) into the owning
|
|
125
|
+
// context's mailbox; the context drains it on its own thread during its next
|
|
126
|
+
// tick. The deposited closure captures only C++ state and runs no JSI until
|
|
127
|
+
// drained, so depositing from any thread is safe.
|
|
128
|
+
context->postSettle(
|
|
129
|
+
[self, action = std::move(action), promiseRef]() mutable {
|
|
130
|
+
action(promiseRef->runtime, *promiseRef);
|
|
131
|
+
if (self->context) {
|
|
132
|
+
self->context->onTaskSettled(/*keepPumping=*/true);
|
|
133
|
+
}
|
|
134
|
+
std::lock_guard<std::mutex> lock(self->mutex);
|
|
135
|
+
self->keepAlive.reset();
|
|
136
|
+
});
|
|
104
137
|
}
|
|
105
138
|
|
|
106
139
|
AsyncTaskHandle::ResolveFunction
|
|
@@ -149,9 +182,9 @@ AsyncTaskHandle::AsyncTaskHandle(std::shared_ptr<State> state)
|
|
|
149
182
|
bool AsyncTaskHandle::valid() const { return _state != nullptr; }
|
|
150
183
|
|
|
151
184
|
AsyncTaskHandle
|
|
152
|
-
AsyncTaskHandle::create(const std::shared_ptr<
|
|
185
|
+
AsyncTaskHandle::create(const std::shared_ptr<RuntimeContext> &context,
|
|
153
186
|
bool keepPumping) {
|
|
154
|
-
auto state = std::make_shared<State>(
|
|
187
|
+
auto state = std::make_shared<State>(context, keepPumping);
|
|
155
188
|
state->keepAlive = state;
|
|
156
189
|
return AsyncTaskHandle(std::move(state));
|
|
157
190
|
}
|
|
@@ -8,19 +8,22 @@
|
|
|
8
8
|
|
|
9
9
|
#include <jsi/jsi.h>
|
|
10
10
|
|
|
11
|
-
#include "AsyncDispatcher.h"
|
|
12
|
-
|
|
13
11
|
namespace rnwgpu {
|
|
14
12
|
class Promise;
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
namespace rnwgpu::async {
|
|
18
16
|
|
|
19
|
-
class
|
|
17
|
+
class RuntimeContext;
|
|
20
18
|
|
|
21
19
|
/**
|
|
22
20
|
* Represents a pending asynchronous WebGPU operation that can be converted into
|
|
23
21
|
* a JavaScript Promise.
|
|
22
|
+
*
|
|
23
|
+
* In the ProcessEvents model the resolve/reject callbacks are invoked on the
|
|
24
|
+
* owning runtime's own thread (synchronously from instance.ProcessEvents()
|
|
25
|
+
* during the RuntimeContext tick, or synchronously from postTask), so the
|
|
26
|
+
* Promise is settled directly without any thread marshalling.
|
|
24
27
|
*/
|
|
25
28
|
class AsyncTaskHandle {
|
|
26
29
|
public:
|
|
@@ -34,7 +37,7 @@ public:
|
|
|
34
37
|
AsyncTaskHandle();
|
|
35
38
|
|
|
36
39
|
/**
|
|
37
|
-
* Internal constructor used by
|
|
40
|
+
* Internal constructor used by RuntimeContext.
|
|
38
41
|
*/
|
|
39
42
|
explicit AsyncTaskHandle(std::shared_ptr<State> state);
|
|
40
43
|
|
|
@@ -45,7 +48,7 @@ public:
|
|
|
45
48
|
|
|
46
49
|
void attachPromise(const std::shared_ptr<rnwgpu::Promise> &promise) const;
|
|
47
50
|
|
|
48
|
-
static AsyncTaskHandle create(const std::shared_ptr<
|
|
51
|
+
static AsyncTaskHandle create(const std::shared_ptr<RuntimeContext> &context,
|
|
49
52
|
bool keepPumping);
|
|
50
53
|
|
|
51
54
|
private:
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#include "RuntimeContext.h"
|
|
2
|
+
|
|
3
|
+
#include <memory>
|
|
4
|
+
#include <stdexcept>
|
|
5
|
+
#include <utility>
|
|
6
|
+
|
|
7
|
+
#include <ReactCommon/CallInvoker.h>
|
|
8
|
+
|
|
9
|
+
#include "AsyncTaskHandle.h"
|
|
10
|
+
|
|
11
|
+
namespace rnwgpu::async {
|
|
12
|
+
|
|
13
|
+
namespace {
|
|
14
|
+
struct RuntimeData {
|
|
15
|
+
std::shared_ptr<RuntimeContext> context;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// The main JS runtime and its CallInvoker, registered once on install. The
|
|
19
|
+
// context created for sMainRuntime gets sMainInvoker; spontaneous events
|
|
20
|
+
// (device.lost) on a main-runtime device are delivered through it without the
|
|
21
|
+
// pump. Worklet runtimes have no invoker (best-effort, see the header doc).
|
|
22
|
+
jsi::Runtime *sMainRuntime = nullptr;
|
|
23
|
+
std::shared_ptr<facebook::react::CallInvoker> sMainInvoker;
|
|
24
|
+
|
|
25
|
+
// Serializes ProcessEvents() across all runtimes that share a wgpu::Instance.
|
|
26
|
+
// Held only across the ProcessEvents call itself, never while running JS /
|
|
27
|
+
// mailbox settle-actions, so it cannot deadlock against the per-context mailbox
|
|
28
|
+
// mutex.
|
|
29
|
+
std::mutex &processEventsMutex() {
|
|
30
|
+
static std::mutex mutex;
|
|
31
|
+
return mutex;
|
|
32
|
+
}
|
|
33
|
+
} // namespace
|
|
34
|
+
|
|
35
|
+
void RuntimeContext::registerMainRuntime(
|
|
36
|
+
jsi::Runtime *runtime,
|
|
37
|
+
std::shared_ptr<facebook::react::CallInvoker> invoker) {
|
|
38
|
+
sMainRuntime = runtime;
|
|
39
|
+
sMainInvoker = std::move(invoker);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
RuntimeContext::RuntimeContext(jsi::Runtime &runtime, wgpu::Instance instance)
|
|
43
|
+
: _runtime(runtime), _instance(std::move(instance)) {}
|
|
44
|
+
|
|
45
|
+
std::shared_ptr<RuntimeContext> RuntimeContext::get(jsi::Runtime &runtime) {
|
|
46
|
+
auto data = runtime.getRuntimeData(runtimeDataUUID());
|
|
47
|
+
if (!data) {
|
|
48
|
+
return nullptr;
|
|
49
|
+
}
|
|
50
|
+
return std::static_pointer_cast<RuntimeData>(data)->context;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
std::shared_ptr<RuntimeContext>
|
|
54
|
+
RuntimeContext::getOrCreate(jsi::Runtime &runtime, wgpu::Instance instance) {
|
|
55
|
+
if (auto existing = get(runtime)) {
|
|
56
|
+
return existing;
|
|
57
|
+
}
|
|
58
|
+
auto context = std::make_shared<RuntimeContext>(runtime, std::move(instance));
|
|
59
|
+
// Only the main JS runtime's context carries the CallInvoker; it is used to
|
|
60
|
+
// deliver spontaneous events (device.lost) without the pump.
|
|
61
|
+
if (&runtime == sMainRuntime) {
|
|
62
|
+
context->_callInvoker = sMainInvoker;
|
|
63
|
+
}
|
|
64
|
+
auto data = std::make_shared<RuntimeData>();
|
|
65
|
+
data->context = context;
|
|
66
|
+
runtime.setRuntimeData(runtimeDataUUID(), data);
|
|
67
|
+
return context;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
AsyncTaskHandle RuntimeContext::postTask(const TaskCallback &callback,
|
|
71
|
+
bool keepPumping) {
|
|
72
|
+
auto handle = AsyncTaskHandle::create(shared_from_this(), keepPumping);
|
|
73
|
+
if (!handle.valid()) {
|
|
74
|
+
throw std::runtime_error("Failed to create AsyncTaskHandle.");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Only pumping tasks (request/response ops) drive the ProcessEvents pump.
|
|
78
|
+
// Spontaneous tasks (keepPumping == false, e.g. device.lost) never touch the
|
|
79
|
+
// pump: they settle via the CallInvoker (see AsyncTaskHandle::State::schedule).
|
|
80
|
+
if (keepPumping) {
|
|
81
|
+
_pumpTasks.fetch_add(1, std::memory_order_acq_rel);
|
|
82
|
+
requestTick();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
auto resolve = handle.createResolveFunction();
|
|
86
|
+
auto reject = handle.createRejectFunction();
|
|
87
|
+
try {
|
|
88
|
+
callback(resolve, reject);
|
|
89
|
+
} catch (const std::exception &exception) {
|
|
90
|
+
reject(exception.what());
|
|
91
|
+
} catch (...) {
|
|
92
|
+
reject("Unknown native error in RuntimeContext::postTask.");
|
|
93
|
+
}
|
|
94
|
+
return handle;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
void RuntimeContext::onTaskSettled(bool keepPumping) {
|
|
98
|
+
if (keepPumping) {
|
|
99
|
+
_pumpTasks.fetch_sub(1, std::memory_order_acq_rel);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
void RuntimeContext::postSettle(std::function<void()> job) {
|
|
104
|
+
if (!job) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
std::lock_guard<std::mutex> lock(_mailboxMutex);
|
|
108
|
+
_mailbox.push_back(std::move(job));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
void RuntimeContext::drainMailbox() {
|
|
112
|
+
std::vector<std::function<void()>> jobs;
|
|
113
|
+
{
|
|
114
|
+
std::lock_guard<std::mutex> lock(_mailboxMutex);
|
|
115
|
+
jobs.swap(_mailbox);
|
|
116
|
+
}
|
|
117
|
+
// Run settle-actions on this (the owning) thread, NOT under the ProcessEvents
|
|
118
|
+
// mutex, so JS continuations never execute while the pump lock is held.
|
|
119
|
+
for (auto &job : jobs) {
|
|
120
|
+
job();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
void RuntimeContext::requestTick() {
|
|
125
|
+
bool expected = false;
|
|
126
|
+
if (!_tickScheduled.compare_exchange_strong(expected, true,
|
|
127
|
+
std::memory_order_acq_rel)) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// The pump only ever runs while a request/response op is outstanding, so it
|
|
132
|
+
// always schedules as soon as possible (delay 0). postTask and tick both run
|
|
133
|
+
// on the owning runtime's thread, so we schedule the next tick directly via
|
|
134
|
+
// that runtime's own timer. setTimeout is available on the main RN runtime and
|
|
135
|
+
// on worklet runtimes (backed by the worklets EventLoop); setImmediate /
|
|
136
|
+
// queueMicrotask are fallbacks. We do NOT use queueMicrotask as the primary
|
|
137
|
+
// mechanism: a self-rescheduling microtask never yields the microtask
|
|
138
|
+
// checkpoint, starving the runtime's task loop.
|
|
139
|
+
auto self = shared_from_this();
|
|
140
|
+
jsi::Runtime &rt = _runtime;
|
|
141
|
+
auto tickCallback = jsi::Function::createFromHostFunction(
|
|
142
|
+
rt, jsi::PropNameID::forAscii(rt, "RNWGPUAsyncTick"), 0,
|
|
143
|
+
[self](jsi::Runtime & /*runtime*/, const jsi::Value & /*thisVal*/,
|
|
144
|
+
const jsi::Value * /*args*/, size_t /*count*/) -> jsi::Value {
|
|
145
|
+
self->tick();
|
|
146
|
+
return jsi::Value::undefined();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
auto global = rt.global();
|
|
150
|
+
auto setTimeoutValue = global.getProperty(rt, "setTimeout");
|
|
151
|
+
if (setTimeoutValue.isObject() &&
|
|
152
|
+
setTimeoutValue.asObject(rt).isFunction(rt)) {
|
|
153
|
+
setTimeoutValue.asObject(rt).asFunction(rt).call(
|
|
154
|
+
rt, jsi::Value(rt, tickCallback), jsi::Value(0.0));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
auto setImmediateValue = global.getProperty(rt, "setImmediate");
|
|
158
|
+
if (setImmediateValue.isObject() &&
|
|
159
|
+
setImmediateValue.asObject(rt).isFunction(rt)) {
|
|
160
|
+
setImmediateValue.asObject(rt).asFunction(rt).call(
|
|
161
|
+
rt, jsi::Value(rt, tickCallback));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
rt.queueMicrotask(std::move(tickCallback));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
void RuntimeContext::tick() {
|
|
168
|
+
_tickScheduled.store(false, std::memory_order_release);
|
|
169
|
+
{
|
|
170
|
+
// Serialize ProcessEvents across runtimes sharing this instance. Callbacks
|
|
171
|
+
// fired here only deposit into mailboxes (postSettle), they do not run JS.
|
|
172
|
+
std::lock_guard<std::mutex> lock(processEventsMutex());
|
|
173
|
+
_instance.ProcessEvents();
|
|
174
|
+
}
|
|
175
|
+
// Settle this runtime's ready promises on this thread, outside the pump lock.
|
|
176
|
+
drainMailbox();
|
|
177
|
+
// Keep pumping only while a "pumping" task (active async work) is outstanding.
|
|
178
|
+
// Non-pumping tasks (e.g. device.lost) intentionally do NOT keep the pump
|
|
179
|
+
// alive: we prioritise battery over catching a device.lost fired while idle.
|
|
180
|
+
if (_pumpTasks.load(std::memory_order_acquire) > 0) {
|
|
181
|
+
requestTick();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
jsi::UUID RuntimeContext::runtimeDataUUID() {
|
|
186
|
+
// Fixed, unique key for storing the RuntimeContext in the runtime's
|
|
187
|
+
// runtimeData. Must not collide with other runtimeData consumers (e.g.
|
|
188
|
+
// react-native-worklets' weakRuntimeUUID).
|
|
189
|
+
static const jsi::UUID uuid{0x7b9a3c10, 0x4d2e, 0x4f8a, 0x9c3d,
|
|
190
|
+
0x1f6e5a2b8c40};
|
|
191
|
+
return uuid;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
} // namespace rnwgpu::async
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <atomic>
|
|
4
|
+
#include <cstddef>
|
|
5
|
+
#include <functional>
|
|
6
|
+
#include <memory>
|
|
7
|
+
#include <mutex>
|
|
8
|
+
#include <vector>
|
|
9
|
+
|
|
10
|
+
#include <jsi/jsi.h>
|
|
11
|
+
|
|
12
|
+
#include "AsyncTaskHandle.h"
|
|
13
|
+
|
|
14
|
+
#include "webgpu/webgpu_cpp.h"
|
|
15
|
+
|
|
16
|
+
namespace jsi = facebook::jsi;
|
|
17
|
+
|
|
18
|
+
namespace facebook::react {
|
|
19
|
+
class CallInvoker;
|
|
20
|
+
} // namespace facebook::react
|
|
21
|
+
|
|
22
|
+
namespace rnwgpu::async {
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Per-runtime coordinator for asynchronous WebGPU operations.
|
|
26
|
+
*
|
|
27
|
+
* Each JS runtime that uses WebGPU gets its own RuntimeContext, stored in the
|
|
28
|
+
* runtime's runtimeData. Async Dawn operations are registered with
|
|
29
|
+
* CallbackMode::AllowProcessEvents and driven to completion by pumping
|
|
30
|
+
* `instance.ProcessEvents()` on the runtime's OWN thread via a self-
|
|
31
|
+
* rescheduling tick (scheduled through that runtime's setTimeout). Because
|
|
32
|
+
* ProcessEvents invokes the Dawn callbacks synchronously on the pumping thread,
|
|
33
|
+
* the JS Promise is settled directly on the owning runtime, with no background
|
|
34
|
+
* thread and no cross-thread hop.
|
|
35
|
+
*
|
|
36
|
+
* The pump only runs while at least one "pumping" task is outstanding, so it
|
|
37
|
+
* costs nothing when idle and stops cleanly.
|
|
38
|
+
*
|
|
39
|
+
* Spontaneous events (keepPumping = false): events that may fire at any time,
|
|
40
|
+
* independent of any request/response op (today only GPUDevice::getLost, whose
|
|
41
|
+
* Dawn callback is registered AllowSpontaneous). These are NOT driven by the
|
|
42
|
+
* pump. Instead their settle is marshalled onto the owning runtime's JS thread
|
|
43
|
+
* via that runtime's CallInvoker, which is wired only for the MAIN JS runtime
|
|
44
|
+
* (callInvoker()). A device created on a worklet runtime has no invoker, so its
|
|
45
|
+
* device.lost is best-effort and may never fire.
|
|
46
|
+
*
|
|
47
|
+
* Shared-instance safety (mailbox): multiple runtimes may share one
|
|
48
|
+
* wgpu::Instance. ProcessEvents() drains the whole instance queue and fires
|
|
49
|
+
* callbacks on the calling thread, which may NOT be the owning runtime's thread
|
|
50
|
+
* for a given promise. So a settled callback never touches JSI inline; it
|
|
51
|
+
* deposits a settle-action (a plain C++ closure, no JSI) into the OWNING
|
|
52
|
+
* context's thread-safe mailbox via postSettle(), and each context drains its
|
|
53
|
+
* own mailbox on its own thread during tick(). ProcessEvents() itself is
|
|
54
|
+
* serialized across runtimes by a process-wide mutex, since concurrent
|
|
55
|
+
* ProcessEvents on one instance is not guaranteed reentrant.
|
|
56
|
+
*
|
|
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.
|
|
60
|
+
*/
|
|
61
|
+
class RuntimeContext : public std::enable_shared_from_this<RuntimeContext> {
|
|
62
|
+
public:
|
|
63
|
+
using TaskCallback =
|
|
64
|
+
std::function<void(const AsyncTaskHandle::ResolveFunction &,
|
|
65
|
+
const AsyncTaskHandle::RejectFunction &)>;
|
|
66
|
+
|
|
67
|
+
RuntimeContext(jsi::Runtime &runtime, wgpu::Instance instance);
|
|
68
|
+
|
|
69
|
+
static std::shared_ptr<RuntimeContext> get(jsi::Runtime &runtime);
|
|
70
|
+
static std::shared_ptr<RuntimeContext> getOrCreate(jsi::Runtime &runtime,
|
|
71
|
+
wgpu::Instance instance);
|
|
72
|
+
|
|
73
|
+
// Register the main JS runtime and its CallInvoker. The RuntimeContext created
|
|
74
|
+
// for this runtime gets the invoker (callInvoker() returns it); every other
|
|
75
|
+
// runtime's context returns null. Called once from RNSkManager on install.
|
|
76
|
+
static void
|
|
77
|
+
registerMainRuntime(jsi::Runtime *runtime,
|
|
78
|
+
std::shared_ptr<facebook::react::CallInvoker> invoker);
|
|
79
|
+
|
|
80
|
+
// CallInvoker for this runtime's JS thread, or null. Non-null only for the
|
|
81
|
+
// main JS runtime; used to deliver spontaneous events (device.lost) without
|
|
82
|
+
// the pump. See the class doc.
|
|
83
|
+
const std::shared_ptr<facebook::react::CallInvoker> &callInvoker() const {
|
|
84
|
+
return _callInvoker;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// The wgpu::Instance bound to this runtime.
|
|
88
|
+
wgpu::Instance instance() const { return _instance; }
|
|
89
|
+
|
|
90
|
+
AsyncTaskHandle postTask(const TaskCallback &callback,
|
|
91
|
+
bool keepPumping = true);
|
|
92
|
+
|
|
93
|
+
// Deposit a settle-action to run on THIS context's runtime thread. Thread-safe
|
|
94
|
+
// (callable from any thread, e.g. another runtime that pumped ProcessEvents).
|
|
95
|
+
// The job must not touch JSI until it runs (it runs during drainMailbox on the
|
|
96
|
+
// owning thread).
|
|
97
|
+
void postSettle(std::function<void()> job);
|
|
98
|
+
|
|
99
|
+
// Invoked by a drained settle-action when its task settles. Runs on the owning
|
|
100
|
+
// runtime's thread.
|
|
101
|
+
void onTaskSettled(bool keepPumping);
|
|
102
|
+
|
|
103
|
+
private:
|
|
104
|
+
static jsi::UUID runtimeDataUUID();
|
|
105
|
+
|
|
106
|
+
void requestTick();
|
|
107
|
+
void tick();
|
|
108
|
+
void drainMailbox();
|
|
109
|
+
|
|
110
|
+
jsi::Runtime &_runtime;
|
|
111
|
+
wgpu::Instance _instance;
|
|
112
|
+
// Non-null only for the main JS runtime's context (see registerMainRuntime).
|
|
113
|
+
std::shared_ptr<facebook::react::CallInvoker> _callInvoker;
|
|
114
|
+
std::atomic<std::size_t> _pumpTasks{0};
|
|
115
|
+
std::atomic<bool> _tickScheduled{false};
|
|
116
|
+
|
|
117
|
+
std::mutex _mailboxMutex;
|
|
118
|
+
std::vector<std::function<void()>> _mailbox;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
} // namespace rnwgpu::async
|
|
@@ -1,4 +1,35 @@
|
|
|
1
1
|
import type { NativeBuffer } from "./NativeBuffer";
|
|
2
|
+
/**
|
|
3
|
+
* Make the WebGPU flag constants (`GPUBufferUsage`, `GPUColorWrite`,
|
|
4
|
+
* `GPUMapMode`, `GPUShaderStage`, `GPUTextureUsage`) available on the runtime
|
|
5
|
+
* that calls this.
|
|
6
|
+
*
|
|
7
|
+
* The native module installs these globals on the main JS runtime, but worklet
|
|
8
|
+
* runtimes (Reanimated UI, dedicated worklet runtimes, Vision Camera frame
|
|
9
|
+
* processors) start without them, so referencing the bare global inside a
|
|
10
|
+
* worklet yields `undefined`. Call `installWebGPU()` once at the top of a
|
|
11
|
+
* worklet to install them there:
|
|
12
|
+
*
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import { installWebGPU } from "@shopify/react-native-skia";
|
|
15
|
+
*
|
|
16
|
+
* const work = (device: GPUDevice) => {
|
|
17
|
+
* "worklet";
|
|
18
|
+
* installWebGPU();
|
|
19
|
+
* device.createBuffer({
|
|
20
|
+
* usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
|
|
21
|
+
* });
|
|
22
|
+
* };
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* `installWebGPU` is a native host function. When captured into a worklet, the
|
|
26
|
+
* Worklets serializer re-creates it on the worklet runtime, so calling it there
|
|
27
|
+
* installs the constants on that runtime. The values come from the native
|
|
28
|
+
* `wgpu::*Usage` enums, so they stay a single source of truth across runtimes.
|
|
29
|
+
* Calling it on a runtime that already has the constants is a safe no-op, and on
|
|
30
|
+
* web (where the constants are always global) it is a no-op too.
|
|
31
|
+
*/
|
|
32
|
+
export declare const installWebGPU: () => void;
|
|
2
33
|
/**
|
|
3
34
|
* Descriptor for {@link GPUDevice.importExternalTexture} when the source is a
|
|
4
35
|
* Skia NativeBuffer (Skia has no WebCodecs `VideoFrame`).
|