@shopify/react-native-skia 2.11.2 → 2.12.0
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/Package.swift +117 -0
- package/android/CMakeLists.txt +1 -2
- package/cpp/api/JsiSkSVGFactory.h +4 -0
- package/cpp/jsi/JSICache.cpp +99 -0
- package/cpp/jsi/JSICache.h +90 -0
- package/cpp/jsi/NativeObject.h +30 -97
- package/cpp/jsi/ViewProperty.h +0 -24
- package/cpp/rnskia/RNDawnContext.h +2 -1
- package/cpp/rnskia/RNDawnWindowContext.h +22 -0
- package/cpp/rnskia/RNSkManager.cpp +0 -11
- package/cpp/skia/include/core/RasterContext.h +25 -0
- package/cpp/skia/include/core/SkCanvas.h +0 -1
- package/cpp/skia/include/core/SkContext.h +62 -0
- package/cpp/skia/include/core/SkContextOptions.h +15 -0
- package/cpp/skia/include/core/SkMilestone.h +1 -1
- package/cpp/skia/include/core/SkPicture.h +36 -18
- package/cpp/skia/include/core/SkRecorder.h +8 -0
- package/cpp/skia/include/core/SkSerialProcs.h +2 -6
- package/cpp/skia/include/effects/SkImageFilters.h +15 -2
- package/cpp/skia/include/gpu/ganesh/GrDirectContext.h +73 -47
- package/cpp/skia/include/gpu/ganesh/SkSurfaceGanesh.h +7 -4
- package/cpp/skia/include/gpu/ganesh/d3d/GrD3DDirectContext.h +12 -0
- package/cpp/skia/include/gpu/ganesh/gl/GrGLDirectContext.h +11 -0
- package/cpp/skia/include/gpu/ganesh/mtl/GrMtlDirectContext.h +11 -0
- package/cpp/skia/include/gpu/ganesh/vk/GrVkDirectContext.h +13 -0
- package/cpp/skia/include/gpu/graphite/ContextOptions.h +6 -0
- package/cpp/skia/include/gpu/graphite/GraphiteTypes.h +5 -4
- package/cpp/skia/include/gpu/graphite/PrecompileContext.h +9 -1
- package/cpp/skia/include/gpu/graphite/Recorder.h +0 -2
- package/cpp/skia/include/gpu/graphite/Recording.h +3 -0
- package/cpp/skia/include/gpu/graphite/dawn/DawnBackendContext.h +8 -0
- package/cpp/skia/include/gpu/graphite/mtl/MtlBackendContext.h +11 -0
- package/cpp/skia/include/gpu/graphite/vk/VulkanGraphiteContext.h +11 -0
- package/cpp/skia/include/private/SkLog.h +1 -19
- package/cpp/skia/include/private/chromium/GrVkSecondaryCBDrawContext.h +2 -1
- package/cpp/skia/include/utils/SkLogHandler.h +53 -0
- package/cpp/skia/include/utils/SkNWayCanvas.h +1 -0
- package/cpp/skia/modules/skcms/src/Transform_inl.h +9 -10
- package/cpp/skia/modules/skcms/src/skcms_internals.h +8 -3
- package/cpp/skia/modules/skottie/include/SkottieProperty.h +2 -0
- package/cpp/skia/modules/skottie/include/TextShaper.h +1 -0
- package/cpp/skia/modules/skshaper/include/SkShaper.h +23 -1
- package/include/ReactNativeSkia.h +6 -0
- package/lib/commonjs/skia/web/JsiSkCanvas.js +4 -4
- package/lib/commonjs/skia/web/JsiSkCanvas.js.map +1 -1
- package/lib/commonjs/skia/web/JsiSkImage.js +6 -3
- package/lib/commonjs/skia/web/JsiSkImage.js.map +1 -1
- package/lib/commonjs/skia/web/JsiSkPathBuilder.d.ts +1 -1
- package/lib/commonjs/skia/web/JsiSkPathBuilder.js +2 -2
- package/lib/commonjs/skia/web/JsiSkPathBuilder.js.map +1 -1
- package/lib/commonjs/skia/web/JsiSkTextStyle.js +5 -5
- package/lib/commonjs/skia/web/JsiSkTextStyle.js.map +1 -1
- package/lib/commonjs/skia/web/JsiSkottieAnimation.js +8 -8
- package/lib/commonjs/skia/web/JsiSkottieAnimation.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/commands/Drawing.js +2 -1
- package/lib/commonjs/sksg/Recorder/commands/Drawing.js.map +1 -1
- package/lib/commonjs/views/SkiaPictureView.web.js +228 -43
- package/lib/commonjs/views/SkiaPictureView.web.js.map +1 -1
- package/lib/module/skia/web/JsiSkCanvas.js +4 -4
- package/lib/module/skia/web/JsiSkCanvas.js.map +1 -1
- package/lib/module/skia/web/JsiSkImage.js +6 -3
- package/lib/module/skia/web/JsiSkImage.js.map +1 -1
- package/lib/module/skia/web/JsiSkPathBuilder.d.ts +1 -1
- package/lib/module/skia/web/JsiSkPathBuilder.js +2 -2
- package/lib/module/skia/web/JsiSkPathBuilder.js.map +1 -1
- package/lib/module/skia/web/JsiSkTextStyle.js +5 -5
- package/lib/module/skia/web/JsiSkTextStyle.js.map +1 -1
- package/lib/module/skia/web/JsiSkottieAnimation.js +8 -8
- package/lib/module/skia/web/JsiSkottieAnimation.js.map +1 -1
- package/lib/module/sksg/Recorder/commands/Drawing.js +2 -1
- package/lib/module/sksg/Recorder/commands/Drawing.js.map +1 -1
- package/lib/module/views/SkiaPictureView.web.js +228 -43
- package/lib/module/views/SkiaPictureView.web.js.map +1 -1
- package/lib/typescript/lib/commonjs/skia/web/JsiSkPathBuilder.d.ts +1 -1
- package/lib/typescript/lib/module/skia/web/JsiSkPathBuilder.d.ts +1 -1
- package/lib/typescript/react-native.config.d.ts +3 -0
- package/lib/typescript/src/skia/web/JsiSkPathBuilder.d.ts +1 -1
- package/package.json +12 -9
- package/react-native.config.js +5 -0
- package/src/skia/web/JsiSkCanvas.ts +11 -6
- package/src/skia/web/JsiSkImage.ts +6 -3
- package/src/skia/web/JsiSkPathBuilder.ts +2 -2
- package/src/skia/web/JsiSkTextStyle.ts +20 -15
- package/src/skia/web/JsiSkottieAnimation.ts +8 -8
- package/src/sksg/Recorder/commands/Drawing.ts +2 -1
- package/src/views/SkiaPictureView.web.tsx +228 -43
- package/cpp/jsi/RuntimeAwareCache.cpp +0 -8
- package/cpp/jsi/RuntimeAwareCache.h +0 -146
- package/cpp/jsi/RuntimeLifecycleMonitor.cpp +0 -72
- package/cpp/jsi/RuntimeLifecycleMonitor.h +0 -32
|
@@ -36,32 +36,191 @@ interface Renderer {
|
|
|
36
36
|
dispose(): void;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// A <canvas> element owns at most one WebGL context for its whole lifetime.
|
|
40
|
+
// A renderer, on the other hand, lives as long as the layout effect that
|
|
41
|
+
// creates it, which is shorter: React re-runs layout effects on a *preserved*
|
|
42
|
+
// host node under StrictMode's DEV double-invoke and when an Activity/offscreen
|
|
43
|
+
// subtree is hidden and revealed (#3976). What belongs to the element rather
|
|
44
|
+
// than to a renderer is tracked here, per element:
|
|
45
|
+
// - the context, so that a new renderer can tell whether the element already
|
|
46
|
+
// has one (and whether it is lost) without calling getContext() itself,
|
|
47
|
+
// which on a fresh element would create a context with the wrong attributes
|
|
48
|
+
// (CanvasKit.GetWebGLContext requests its own);
|
|
49
|
+
// - the WEBGL_lose_context extension, captured while the context is healthy:
|
|
50
|
+
// getExtension() returns null on a lost context, and this object is the only
|
|
51
|
+
// way to lose a context on purpose or to ask the browser to restore one;
|
|
52
|
+
// - whether the browser has announced the loss of the context yet: a lost
|
|
53
|
+
// context can only be restored once its webglcontextlost event has been
|
|
54
|
+
// dispatched, and that event is asynchronous;
|
|
55
|
+
// - the renderer currently attached to the element, if any.
|
|
56
|
+
interface CanvasWebGL {
|
|
57
|
+
gl: WebGLRenderingContext | WebGL2RenderingContext;
|
|
58
|
+
// The WEBGL_lose_context extension.
|
|
59
|
+
loseContext: { loseContext(): void; restoreContext(): void } | null;
|
|
60
|
+
lossAnnounced: boolean;
|
|
61
|
+
owner: WebGLRenderer | null;
|
|
62
|
+
}
|
|
63
|
+
const canvasWebGL = new WeakMap<HTMLCanvasElement, CanvasWebGL>();
|
|
64
|
+
|
|
65
|
+
// Unregisters a context from CanvasKit's registry, which otherwise retains the
|
|
66
|
+
// canvas element (and its detached DOM tree) forever (#3924).
|
|
67
|
+
const deleteContextHandle = (handle: WebGLContextHandle) => {
|
|
68
|
+
CanvasKit.deleteContext(handle);
|
|
69
|
+
// Making the now-deleted handle current clears CanvasKit's current-context
|
|
70
|
+
// globals (GLctx/Module.ctx), which would otherwise keep referencing the
|
|
71
|
+
// context (and the canvas) until another surface becomes current. With a
|
|
72
|
+
// deleted handle this is a no-op that returns null without creating
|
|
73
|
+
// anything.
|
|
74
|
+
CanvasKit.MakeWebGLContext(handle);
|
|
75
|
+
};
|
|
76
|
+
|
|
39
77
|
class WebGLRenderer implements Renderer {
|
|
40
78
|
private surface: JsiSkSurface | null = null;
|
|
41
79
|
private grContext: GrDirectContext | null = null;
|
|
42
80
|
private contextHandle: WebGLContextHandle = 0;
|
|
43
81
|
private pd = 1;
|
|
82
|
+
// Set while the renderer is waiting for a lost context to be restored.
|
|
83
|
+
private restoreWanted = false;
|
|
84
|
+
private restoreRequest: ReturnType<typeof setTimeout> | null = null;
|
|
85
|
+
|
|
86
|
+
constructor(
|
|
87
|
+
private canvas: HTMLCanvasElement,
|
|
88
|
+
// Called when the renderer becomes able to paint again and the current
|
|
89
|
+
// picture should be drawn.
|
|
90
|
+
private requestRedraw: () => void
|
|
91
|
+
) {
|
|
92
|
+
const entry = canvasWebGL.get(canvas);
|
|
93
|
+
if (entry) {
|
|
94
|
+
entry.owner = this;
|
|
95
|
+
}
|
|
96
|
+
canvas.addEventListener("webglcontextlost", this.onContextLost);
|
|
97
|
+
canvas.addEventListener("webglcontextrestored", this.onContextRestored);
|
|
98
|
+
this.acquire();
|
|
99
|
+
}
|
|
44
100
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
101
|
+
// Builds the GrDirectContext and the surface on the element's WebGL
|
|
102
|
+
// context, creating the context first if the element has none.
|
|
103
|
+
private acquire() {
|
|
104
|
+
const { canvas } = this;
|
|
105
|
+
const known = canvasWebGL.get(canvas);
|
|
106
|
+
if (known?.gl.isContextLost()) {
|
|
107
|
+
// Nothing can be built on a lost context: CanvasKit.GetWebGLContext
|
|
108
|
+
// happily registers it, and MakeWebGLContext then faults inside wasm
|
|
109
|
+
// (#3976). Ask the browser for it back and finish in onContextRestored;
|
|
110
|
+
// if the loss hasn't been announced yet, onContextLost asks.
|
|
111
|
+
this.restoreWanted = true;
|
|
112
|
+
if (known.lossAnnounced) {
|
|
113
|
+
known.loseContext?.restoreContext();
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const handle = CanvasKit.GetWebGLContext(canvas);
|
|
118
|
+
if (!handle) {
|
|
48
119
|
throw new Error("Could not create a WebGL context");
|
|
49
120
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
121
|
+
const entry = known ?? this.trackContext();
|
|
122
|
+
let grContext: GrDirectContext | null = null;
|
|
123
|
+
try {
|
|
124
|
+
grContext = CanvasKit.MakeWebGLContext(handle);
|
|
125
|
+
} finally {
|
|
126
|
+
if (!grContext) {
|
|
127
|
+
// Whether it returned null or threw, don't leave the registry
|
|
128
|
+
// holding on to the canvas.
|
|
129
|
+
deleteContextHandle(handle);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (!grContext) {
|
|
54
133
|
throw new Error("Could not create a graphics context");
|
|
55
134
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
135
|
+
this.restoreWanted = false;
|
|
136
|
+
this.contextHandle = handle;
|
|
137
|
+
this.grContext = grContext;
|
|
138
|
+
if (entry) {
|
|
139
|
+
entry.gl.drawingBufferColorSpace = "display-p3";
|
|
59
140
|
}
|
|
60
141
|
this.onResize();
|
|
61
142
|
}
|
|
62
143
|
|
|
144
|
+
// CanvasKit just created the element's context; asking for it again hands
|
|
145
|
+
// back the same object (the attributes are only honored the first time).
|
|
146
|
+
private trackContext(): CanvasWebGL | null {
|
|
147
|
+
const { canvas } = this;
|
|
148
|
+
const gl = canvas.getContext("webgl2") ?? canvas.getContext("webgl");
|
|
149
|
+
if (!gl) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const entry: CanvasWebGL = {
|
|
153
|
+
gl,
|
|
154
|
+
loseContext: gl.getExtension("WEBGL_lose_context"),
|
|
155
|
+
lossAnnounced: false,
|
|
156
|
+
owner: this,
|
|
157
|
+
};
|
|
158
|
+
canvasWebGL.set(canvas, entry);
|
|
159
|
+
// Browsers restore a lost context (evicted as the oldest one when the
|
|
160
|
+
// page exceeds the active-context limit, or lost to a GPU reset) only if
|
|
161
|
+
// the page called preventDefault() on the webglcontextlost event. A
|
|
162
|
+
// renderer isn't necessarily attached when that happens (the canvas may
|
|
163
|
+
// be in a hidden Activity), so these listeners live as long as the
|
|
164
|
+
// element does. They are registered ahead of any renderer's, and the
|
|
165
|
+
// loss can't be announced before they exist since the event is
|
|
166
|
+
// asynchronous.
|
|
167
|
+
canvas.addEventListener("webglcontextlost", (event) => {
|
|
168
|
+
event.preventDefault();
|
|
169
|
+
entry.lossAnnounced = true;
|
|
170
|
+
});
|
|
171
|
+
canvas.addEventListener("webglcontextrestored", () => {
|
|
172
|
+
entry.lossAnnounced = false;
|
|
173
|
+
});
|
|
174
|
+
return entry;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Frees everything built on the context. The context itself belongs to the
|
|
178
|
+
// element, see dispose().
|
|
179
|
+
private release() {
|
|
180
|
+
this.surface?.ref.delete();
|
|
181
|
+
this.surface = null;
|
|
182
|
+
if (this.grContext) {
|
|
183
|
+
this.grContext.releaseResourcesAndAbandonContext();
|
|
184
|
+
this.grContext.delete();
|
|
185
|
+
this.grContext = null;
|
|
186
|
+
}
|
|
187
|
+
if (this.contextHandle) {
|
|
188
|
+
deleteContextHandle(this.contextHandle);
|
|
189
|
+
this.contextHandle = 0;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private isContextLost() {
|
|
194
|
+
return canvasWebGL.get(this.canvas)?.gl.isContextLost() ?? false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private onContextLost = () => {
|
|
198
|
+
// The browser already dropped every GL object; drop our references to
|
|
199
|
+
// them. Restoration is left to the browser (it restores an evicted
|
|
200
|
+
// context as soon as a slot frees up), except when this renderer was
|
|
201
|
+
// built on an already-lost context and is waiting to paint for the first
|
|
202
|
+
// time: chasing an eviction from here would evict the next-oldest context
|
|
203
|
+
// in turn, and with more live canvases than the browser allows the
|
|
204
|
+
// canvases would endlessly evict each other.
|
|
205
|
+
this.release();
|
|
206
|
+
if (this.restoreWanted) {
|
|
207
|
+
// The permission granted by preventDefault() is only recorded by the
|
|
208
|
+
// browser once the event dispatch is over: a restoreContext() call
|
|
209
|
+
// from inside the handler is refused, so ask from a later task.
|
|
210
|
+
this.restoreRequest = setTimeout(() => {
|
|
211
|
+
this.restoreRequest = null;
|
|
212
|
+
canvasWebGL.get(this.canvas)?.loseContext?.restoreContext();
|
|
213
|
+
}, 0);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
private onContextRestored = () => {
|
|
218
|
+
this.acquire();
|
|
219
|
+
this.requestRedraw();
|
|
220
|
+
};
|
|
221
|
+
|
|
63
222
|
makeImageSnapshot(picture: SkPicture, rect?: SkRect): SkImage | null {
|
|
64
|
-
if (!this.surface) {
|
|
223
|
+
if (!this.surface || this.isContextLost()) {
|
|
65
224
|
return null;
|
|
66
225
|
}
|
|
67
226
|
const canvas = this.surface.getCanvas();
|
|
@@ -73,7 +232,10 @@ class WebGLRenderer implements Renderer {
|
|
|
73
232
|
|
|
74
233
|
onResize() {
|
|
75
234
|
const { canvas } = this;
|
|
76
|
-
if (!this.grContext) {
|
|
235
|
+
if (!this.grContext || this.isContextLost()) {
|
|
236
|
+
// Waiting for the context to be restored (the restore path resizes),
|
|
237
|
+
// or the loss hasn't been announced yet and onContextLost is about to
|
|
238
|
+
// release everything: either way there is nothing to build on.
|
|
77
239
|
return;
|
|
78
240
|
}
|
|
79
241
|
this.pd = window.devicePixelRatio;
|
|
@@ -101,41 +263,57 @@ class WebGLRenderer implements Renderer {
|
|
|
101
263
|
}
|
|
102
264
|
|
|
103
265
|
draw(picture: SkPicture) {
|
|
104
|
-
if (this.surface) {
|
|
105
|
-
|
|
106
|
-
canvas.clear(Float32Array.of(0, 0, 0, 0));
|
|
107
|
-
canvas.save();
|
|
108
|
-
canvas.scale(this.pd, this.pd);
|
|
109
|
-
canvas.drawPicture(picture);
|
|
110
|
-
canvas.restore();
|
|
111
|
-
this.surface.ref.flush();
|
|
266
|
+
if (!this.surface || this.isContextLost()) {
|
|
267
|
+
return;
|
|
112
268
|
}
|
|
269
|
+
const canvas = this.surface.getCanvas();
|
|
270
|
+
canvas.clear(Float32Array.of(0, 0, 0, 0));
|
|
271
|
+
canvas.save();
|
|
272
|
+
canvas.scale(this.pd, this.pd);
|
|
273
|
+
canvas.drawPicture(picture);
|
|
274
|
+
canvas.restore();
|
|
275
|
+
this.surface.ref.flush();
|
|
113
276
|
}
|
|
114
277
|
|
|
115
278
|
dispose(): void {
|
|
116
|
-
this
|
|
117
|
-
this.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
this.
|
|
121
|
-
this.
|
|
279
|
+
const { canvas } = this;
|
|
280
|
+
canvas.removeEventListener("webglcontextlost", this.onContextLost);
|
|
281
|
+
canvas.removeEventListener("webglcontextrestored", this.onContextRestored);
|
|
282
|
+
if (this.restoreRequest !== null) {
|
|
283
|
+
clearTimeout(this.restoreRequest);
|
|
284
|
+
this.restoreRequest = null;
|
|
122
285
|
}
|
|
123
|
-
this.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// null without creating anything.
|
|
136
|
-
CanvasKit.MakeWebGLContext(this.contextHandle);
|
|
137
|
-
this.contextHandle = 0;
|
|
286
|
+
this.restoreWanted = false;
|
|
287
|
+
this.release();
|
|
288
|
+
// Free the drawing buffer, which CanvasKit requests with depth and stencil
|
|
289
|
+
// attachments (about 8 bytes per pixel): a canvas that stays in the
|
|
290
|
+
// document without a renderer, hidden in an Activity say, would otherwise
|
|
291
|
+
// keep a full-size buffer around. The next renderer's onResize() sizes it
|
|
292
|
+
// again.
|
|
293
|
+
canvas.width = 0;
|
|
294
|
+
canvas.height = 0;
|
|
295
|
+
const entry = canvasWebGL.get(canvas);
|
|
296
|
+
if (!entry) {
|
|
297
|
+
return;
|
|
138
298
|
}
|
|
299
|
+
entry.owner = null;
|
|
300
|
+
// Whether the element is going away is only known once the commit is
|
|
301
|
+
// over: on a real unmount React runs this cleanup *before* removing the
|
|
302
|
+
// node from the document, while StrictMode's double-invoke and an
|
|
303
|
+
// Activity hide keep the node in place (and the former builds the next
|
|
304
|
+
// renderer on it right away). Losing the context of a detached element
|
|
305
|
+
// matters: it would otherwise stay alive until the element is garbage
|
|
306
|
+
// collected, and browsers cap the number of live contexts per page
|
|
307
|
+
// (16 in Chrome, which then evicts the oldest one, visible or not).
|
|
308
|
+
queueMicrotask(() => {
|
|
309
|
+
if (
|
|
310
|
+
entry.owner === null &&
|
|
311
|
+
!canvas.isConnected &&
|
|
312
|
+
!entry.gl.isContextLost()
|
|
313
|
+
) {
|
|
314
|
+
entry.loseContext?.loseContext();
|
|
315
|
+
}
|
|
316
|
+
});
|
|
139
317
|
}
|
|
140
318
|
}
|
|
141
319
|
|
|
@@ -382,6 +560,9 @@ export const SkiaPictureView = (props: SkiaPictureViewProps) => {
|
|
|
382
560
|
// - A ResizeObserver on the canvas itself recreates the surface and repaints
|
|
383
561
|
// synchronously (its callbacks run after layout, before paint), and is
|
|
384
562
|
// also the source of the user-facing onLayout event.
|
|
563
|
+
// - The renderer belongs to the layout effect, the WebGL context to the
|
|
564
|
+
// <canvas> element, whose lifetime is longer; see WebGLRenderer for how
|
|
565
|
+
// the two are reconciled.
|
|
385
566
|
const redrawPendingRef = useRef(false);
|
|
386
567
|
const flushScheduledRef = useRef(false);
|
|
387
568
|
const onLayoutRef = useRef(onLayout);
|
|
@@ -488,7 +669,7 @@ export const SkiaPictureView = (props: SkiaPictureViewProps) => {
|
|
|
488
669
|
}
|
|
489
670
|
const renderer = isStatic
|
|
490
671
|
? new StaticWebGLRenderer(canvas)
|
|
491
|
-
: new WebGLRenderer(canvas);
|
|
672
|
+
: new WebGLRenderer(canvas, redraw);
|
|
492
673
|
rendererRef.current = renderer;
|
|
493
674
|
|
|
494
675
|
const drawPicture = () => {
|
|
@@ -572,7 +753,7 @@ export const SkiaPictureView = (props: SkiaPictureViewProps) => {
|
|
|
572
753
|
rendererRef.current = null;
|
|
573
754
|
renderer.dispose();
|
|
574
755
|
};
|
|
575
|
-
}, [isStatic]);
|
|
756
|
+
}, [isStatic, redraw]);
|
|
576
757
|
|
|
577
758
|
// No flush cancellation is needed on unmount: a microtask queued before
|
|
578
759
|
// unmount runs within the same task, and flushRedraw no-ops once the
|
|
@@ -635,6 +816,10 @@ export const SkiaPictureView = (props: SkiaPictureViewProps) => {
|
|
|
635
816
|
return (
|
|
636
817
|
<Platform.View {...viewProps}>
|
|
637
818
|
<canvas
|
|
819
|
+
// A canvas element is bound to one context kind for life (WebGL for
|
|
820
|
+
// the live renderer, 2D for the static one), so switching renderers
|
|
821
|
+
// needs a fresh element.
|
|
822
|
+
key={isStatic ? "static" : "webgl"}
|
|
638
823
|
ref={canvasRef}
|
|
639
824
|
style={{ display: "block", width: "100%", height: "100%" }}
|
|
640
825
|
/>
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
|
|
3
|
-
#include <jsi/jsi.h>
|
|
4
|
-
|
|
5
|
-
#include <atomic>
|
|
6
|
-
#include <cstdint>
|
|
7
|
-
#include <memory>
|
|
8
|
-
#include <mutex>
|
|
9
|
-
#include <unordered_map>
|
|
10
|
-
#include <utility>
|
|
11
|
-
|
|
12
|
-
#include "RuntimeLifecycleMonitor.h"
|
|
13
|
-
|
|
14
|
-
namespace RNJsi {
|
|
15
|
-
|
|
16
|
-
namespace jsi = facebook::jsi;
|
|
17
|
-
|
|
18
|
-
class BaseRuntimeAwareCache {
|
|
19
|
-
public:
|
|
20
|
-
static void setMainJsRuntime(jsi::Runtime *rt) {
|
|
21
|
-
// Atomic: hot reload rewrites this from the JS thread while worklet
|
|
22
|
-
// runtime threads read it concurrently.
|
|
23
|
-
_mainRuntime.store(rt, std::memory_order_release);
|
|
24
|
-
// Every (re)installation of the native module starts a new generation.
|
|
25
|
-
// Callers that hold process-wide state derived from the main runtime
|
|
26
|
-
// (see StaticRuntimeAwareCache) must key their invalidation on this
|
|
27
|
-
// counter and NOT on the runtime pointer: when the JS runtime is
|
|
28
|
-
// destroyed and recreated in-process (expo-updates OTA apply,
|
|
29
|
-
// DevSettings.reload) the new jsi::Runtime is frequently allocated at
|
|
30
|
-
// the exact address of the freed one, so a pointer comparison cannot
|
|
31
|
-
// tell a reload from the steady state.
|
|
32
|
-
_mainRuntimeGeneration.fetch_add(1, std::memory_order_acq_rel);
|
|
33
|
-
}
|
|
34
|
-
static uint64_t getMainJsRuntimeGeneration() {
|
|
35
|
-
return _mainRuntimeGeneration.load(std::memory_order_acquire);
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Called when the main runtime is about to go away (RNSkManager destruction).
|
|
39
|
-
* Bumps the generation so process-wide caches keyed on it
|
|
40
|
-
* (StaticRuntimeAwareCache) stop trusting their contents before the next
|
|
41
|
-
* install(). This closes the window where a runtime allocated at the dead
|
|
42
|
-
* runtime's address would otherwise be taken for the main runtime and be
|
|
43
|
-
* handed the stale jsi::Objects of the previous one.
|
|
44
|
-
*
|
|
45
|
-
* The pointer itself is intentionally left in place: worklet runtime
|
|
46
|
-
* threads may still call getMainJsRuntime() while teardown overlaps, and
|
|
47
|
-
* that accessor asserts on nullptr.
|
|
48
|
-
*/
|
|
49
|
-
static void invalidateMainJsRuntime() {
|
|
50
|
-
_mainRuntimeGeneration.fetch_add(1, std::memory_order_acq_rel);
|
|
51
|
-
}
|
|
52
|
-
static jsi::Runtime *getMainJsRuntime() {
|
|
53
|
-
auto *rt = _mainRuntime.load(std::memory_order_acquire);
|
|
54
|
-
assert(rt != nullptr && "Expected main Javascript runtime to be set in the "
|
|
55
|
-
"BaseRuntimeAwareCache class.");
|
|
56
|
-
|
|
57
|
-
return rt;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
private:
|
|
61
|
-
static std::atomic<jsi::Runtime *> _mainRuntime;
|
|
62
|
-
static std::atomic<uint64_t> _mainRuntimeGeneration;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Provides a way to keep data specific to a jsi::Runtime instance that gets
|
|
67
|
-
* cleaned up when that runtime is destroyed. This is necessary because JSI does
|
|
68
|
-
* not allow for its associated objects to be retained past the runtime
|
|
69
|
-
* lifetime. If an object (e.g. jsi::Values or jsi::Function instances) is kept
|
|
70
|
-
* after the runtime is torn down, its destructor (once it is destroyed
|
|
71
|
-
* eventually) will result in a crash (JSI objects keep a pointer to memory
|
|
72
|
-
* managed by the runtime, accessing that portion of the memory after runtime is
|
|
73
|
-
* deleted is the root cause of that crash).
|
|
74
|
-
*
|
|
75
|
-
* In order to provide an efficient implementation that does not add an overhead
|
|
76
|
-
* for the cases when only a single runtiome is used, which is the primary
|
|
77
|
-
* usecase, the following assumption has been made: Only for secondary runtimes
|
|
78
|
-
* we track destruction and clean up the store associated with that runtime. For
|
|
79
|
-
* the first runtime we assume that the object holding the store is destroyed
|
|
80
|
-
* prior to the destruction of that runtime.
|
|
81
|
-
*
|
|
82
|
-
* The above assumption makes it work without any overhead when only single
|
|
83
|
-
* runtime is in use. Specifically, we don't perform any additional operations
|
|
84
|
-
* related to tracking runtime lifecycle when only a single runtime is used.
|
|
85
|
-
*/
|
|
86
|
-
template <typename T>
|
|
87
|
-
class RuntimeAwareCache : public BaseRuntimeAwareCache,
|
|
88
|
-
public RuntimeLifecycleListener {
|
|
89
|
-
|
|
90
|
-
public:
|
|
91
|
-
void onRuntimeDestroyed(jsi::Runtime *rt) override {
|
|
92
|
-
if (getMainJsRuntime() != rt) {
|
|
93
|
-
// We are removing a secondary runtime. This is invoked by
|
|
94
|
-
// RuntimeLifecycleMonitor on the destroyed runtime's thread, which may
|
|
95
|
-
// run concurrently with get() on another runtime's thread.
|
|
96
|
-
std::lock_guard<std::mutex> lock(_secondaryCachesMutex);
|
|
97
|
-
_secondaryRuntimeCaches.erase(rt);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
~RuntimeAwareCache() {
|
|
102
|
-
for (auto &cache : _secondaryRuntimeCaches) {
|
|
103
|
-
RuntimeLifecycleMonitor::removeListener(
|
|
104
|
-
*static_cast<jsi::Runtime *>(cache.first), this);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
T &get(jsi::Runtime &rt) {
|
|
109
|
-
// We check if we're accessing the main runtime - this is the happy path
|
|
110
|
-
// to avoid us having to lookup by runtime for caches that only has a single
|
|
111
|
-
// runtime
|
|
112
|
-
if (getMainJsRuntime() == &rt) {
|
|
113
|
-
return _primaryCache;
|
|
114
|
-
} else {
|
|
115
|
-
// Guard the secondary map: it can be mutated concurrently by get()
|
|
116
|
-
// on a worklet runtime's thread and by onRuntimeDestroyed() when
|
|
117
|
-
// another secondary runtime is torn down. The main-runtime path above
|
|
118
|
-
// stays lock-free. References into the map remain valid after the
|
|
119
|
-
// lock is released (unordered_map never invalidates references on
|
|
120
|
-
// insert/erase of other keys, and this runtime's entry can only be
|
|
121
|
-
// erased from this runtime's own thread).
|
|
122
|
-
std::lock_guard<std::mutex> lock(_secondaryCachesMutex);
|
|
123
|
-
if (_secondaryRuntimeCaches.count(&rt) == 0) {
|
|
124
|
-
// we only add listener when the secondary runtime is used, this assumes
|
|
125
|
-
// that the secondary runtime is terminated first. This lets us avoid
|
|
126
|
-
// additional complexity for the majority of cases when objects are not
|
|
127
|
-
// shared between runtimes. Otherwise we'd have to register all objecrts
|
|
128
|
-
// with the RuntimeMonitor as opposed to only registering ones that are
|
|
129
|
-
// used in secondary runtime. Note that we can't register listener here
|
|
130
|
-
// with the primary runtime as it may run on a separate thread.
|
|
131
|
-
RuntimeLifecycleMonitor::addListener(rt, this);
|
|
132
|
-
|
|
133
|
-
T cache;
|
|
134
|
-
_secondaryRuntimeCaches.emplace(&rt, std::move(cache));
|
|
135
|
-
}
|
|
136
|
-
return _secondaryRuntimeCaches.at(&rt);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
private:
|
|
141
|
-
std::mutex _secondaryCachesMutex;
|
|
142
|
-
std::unordered_map<void *, T> _secondaryRuntimeCaches;
|
|
143
|
-
T _primaryCache;
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
} // namespace RNJsi
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
#include "RuntimeLifecycleMonitor.h"
|
|
2
|
-
|
|
3
|
-
#include <mutex>
|
|
4
|
-
#include <unordered_map>
|
|
5
|
-
#include <unordered_set>
|
|
6
|
-
#include <utility>
|
|
7
|
-
|
|
8
|
-
namespace RNJsi {
|
|
9
|
-
|
|
10
|
-
static std::unordered_map<jsi::Runtime *,
|
|
11
|
-
std::unordered_set<RuntimeLifecycleListener *>>
|
|
12
|
-
listeners;
|
|
13
|
-
static std::mutex listenersMutex;
|
|
14
|
-
|
|
15
|
-
struct RuntimeLifecycleMonitorObject : public jsi::HostObject {
|
|
16
|
-
jsi::Runtime *_rt;
|
|
17
|
-
explicit RuntimeLifecycleMonitorObject(jsi::Runtime *rt) : _rt(rt) {}
|
|
18
|
-
~RuntimeLifecycleMonitorObject() {
|
|
19
|
-
std::unordered_set<RuntimeLifecycleListener *> listenersCopy;
|
|
20
|
-
{
|
|
21
|
-
std::lock_guard<std::mutex> lock(listenersMutex);
|
|
22
|
-
auto listenersSet = listeners.find(_rt);
|
|
23
|
-
if (listenersSet != listeners.end()) {
|
|
24
|
-
listenersCopy = listenersSet->second;
|
|
25
|
-
listeners.erase(listenersSet);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
for (auto listener : listenersCopy) {
|
|
29
|
-
listener->onRuntimeDestroyed(_rt);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
void RuntimeLifecycleMonitor::addListener(jsi::Runtime &rt,
|
|
35
|
-
RuntimeLifecycleListener *listener) {
|
|
36
|
-
bool shouldInstallMonitor = false;
|
|
37
|
-
{
|
|
38
|
-
std::lock_guard<std::mutex> lock(listenersMutex);
|
|
39
|
-
auto listenersSet = listeners.find(&rt);
|
|
40
|
-
if (listenersSet == listeners.end()) {
|
|
41
|
-
std::unordered_set<RuntimeLifecycleListener *> newSet;
|
|
42
|
-
newSet.insert(listener);
|
|
43
|
-
listeners.emplace(&rt, std::move(newSet));
|
|
44
|
-
shouldInstallMonitor = true;
|
|
45
|
-
} else {
|
|
46
|
-
listenersSet->second.insert(listener);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
if (shouldInstallMonitor) {
|
|
50
|
-
// We install a global host object in the provided runtime, this way we can
|
|
51
|
-
// use that host object destructor to get notified when the runtime is being
|
|
52
|
-
// terminated. We use a unique name for the object as it gets saved with the
|
|
53
|
-
// runtime's global object.
|
|
54
|
-
rt.global().setProperty(
|
|
55
|
-
rt, "__rnskia_rt_lifecycle_monitor",
|
|
56
|
-
jsi::Object::createFromHostObject(
|
|
57
|
-
rt, std::make_shared<RuntimeLifecycleMonitorObject>(&rt)));
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
void RuntimeLifecycleMonitor::removeListener(
|
|
62
|
-
jsi::Runtime &rt, RuntimeLifecycleListener *listener) {
|
|
63
|
-
std::lock_guard<std::mutex> lock(listenersMutex);
|
|
64
|
-
auto listenersSet = listeners.find(&rt);
|
|
65
|
-
if (listenersSet == listeners.end()) {
|
|
66
|
-
// nothing to do here
|
|
67
|
-
} else {
|
|
68
|
-
listenersSet->second.erase(listener);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
} // namespace RNJsi
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
|
|
3
|
-
#include <jsi/jsi.h>
|
|
4
|
-
|
|
5
|
-
#include <memory>
|
|
6
|
-
|
|
7
|
-
namespace RNJsi {
|
|
8
|
-
|
|
9
|
-
namespace jsi = facebook::jsi;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Listener interface that allows for getting notified when a jsi::Runtime
|
|
13
|
-
* instance is destroyed.
|
|
14
|
-
*/
|
|
15
|
-
struct RuntimeLifecycleListener {
|
|
16
|
-
virtual ~RuntimeLifecycleListener() {}
|
|
17
|
-
virtual void onRuntimeDestroyed(jsi::Runtime *) = 0;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* This class provides an API via static methods for registering and
|
|
22
|
-
* unregistering runtime lifecycle listeners. The listeners can be used to
|
|
23
|
-
* cleanup any data that references a given jsi::Runtime instance before it gets
|
|
24
|
-
* destroyed.
|
|
25
|
-
*/
|
|
26
|
-
struct RuntimeLifecycleMonitor {
|
|
27
|
-
static void addListener(jsi::Runtime &rt, RuntimeLifecycleListener *listener);
|
|
28
|
-
static void removeListener(jsi::Runtime &rt,
|
|
29
|
-
RuntimeLifecycleListener *listener);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
} // namespace RNJsi
|