@shopify/react-native-skia 2.12.0-next.1 → 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/android/cpp/rnskia-android/RNSkAndroidPlatformContext.h +0 -10
- package/android/src/main/java/com/shopify/reactnative/skia/ViewScreenshotService.java +9 -12
- package/apple/RNSkApplePlatformContext.h +0 -3
- package/apple/RNSkApplePlatformContext.mm +0 -11
- package/cpp/api/JsiSkApi.h +0 -6
- package/cpp/api/JsiSkFont.h +5 -10
- package/cpp/api/JsiSkMatrix.h +4 -2
- package/cpp/api/JsiSkSVGFactory.h +4 -0
- package/cpp/api/recorder/Convertor.h +5 -2
- package/cpp/api/recorder/Drawings.h +13 -3
- package/cpp/api/recorder/ImageFilters.h +15 -1
- package/cpp/api/recorder/Paint.h +8 -1
- package/cpp/jsi/JSICache.cpp +99 -0
- package/cpp/jsi/JSICache.h +90 -0
- package/cpp/jsi/NativeObject.h +38 -87
- 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 -6
- package/cpp/rnskia/RNSkPlatformContext.h +0 -3
- 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/dom/nodes/datatypes/Rect.js +5 -1
- package/lib/commonjs/dom/nodes/datatypes/Rect.js.map +1 -1
- package/lib/commonjs/external/reanimated/useVideo.js +8 -2
- package/lib/commonjs/external/reanimated/useVideo.js.map +1 -1
- package/lib/commonjs/renderer/components/Mask.js +3 -9
- package/lib/commonjs/renderer/components/Mask.js.map +1 -1
- package/lib/commonjs/skia/types/JsiInstance.d.ts +1 -0
- package/lib/commonjs/skia/types/JsiInstance.js.map +1 -1
- package/lib/commonjs/skia/types/Matrix4.js +7 -2
- package/lib/commonjs/skia/types/Matrix4.js.map +1 -1
- package/lib/commonjs/skia/types/Skia.d.ts +1 -6
- package/lib/commonjs/skia/types/Skia.js.map +1 -1
- 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/JsiSkia.js +0 -3
- package/lib/commonjs/skia/web/JsiSkia.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/DrawingContext.d.ts +1 -0
- package/lib/commonjs/sksg/Recorder/DrawingContext.js +15 -0
- package/lib/commonjs/sksg/Recorder/DrawingContext.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/Player.js +1 -1
- package/lib/commonjs/sksg/Recorder/Player.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/Visitor.js +6 -1
- package/lib/commonjs/sksg/Recorder/Visitor.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/commands/Drawing.js +12 -9
- package/lib/commonjs/sksg/Recorder/commands/Drawing.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/commands/ImageFilters.js +15 -1
- package/lib/commonjs/sksg/Recorder/commands/ImageFilters.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/commands/Shaders.js +1 -1
- package/lib/commonjs/sksg/Recorder/commands/Shaders.js.map +1 -1
- package/lib/commonjs/views/SkiaPictureView.web.js +247 -57
- package/lib/commonjs/views/SkiaPictureView.web.js.map +1 -1
- package/lib/module/dom/nodes/datatypes/Rect.js +5 -1
- package/lib/module/dom/nodes/datatypes/Rect.js.map +1 -1
- package/lib/module/external/reanimated/useVideo.js +8 -2
- package/lib/module/external/reanimated/useVideo.js.map +1 -1
- package/lib/module/renderer/components/Mask.js +3 -9
- package/lib/module/renderer/components/Mask.js.map +1 -1
- package/lib/module/skia/types/JsiInstance.d.ts +1 -0
- package/lib/module/skia/types/JsiInstance.js.map +1 -1
- package/lib/module/skia/types/Matrix4.js +7 -2
- package/lib/module/skia/types/Matrix4.js.map +1 -1
- package/lib/module/skia/types/Skia.d.ts +1 -6
- package/lib/module/skia/types/Skia.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/JsiSkia.js +0 -3
- package/lib/module/skia/web/JsiSkia.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/DrawingContext.d.ts +1 -0
- package/lib/module/sksg/Recorder/DrawingContext.js +15 -0
- package/lib/module/sksg/Recorder/DrawingContext.js.map +1 -1
- package/lib/module/sksg/Recorder/Player.js +1 -1
- package/lib/module/sksg/Recorder/Player.js.map +1 -1
- package/lib/module/sksg/Recorder/Visitor.js +6 -1
- package/lib/module/sksg/Recorder/Visitor.js.map +1 -1
- package/lib/module/sksg/Recorder/commands/Drawing.js +12 -9
- package/lib/module/sksg/Recorder/commands/Drawing.js.map +1 -1
- package/lib/module/sksg/Recorder/commands/ImageFilters.js +15 -1
- package/lib/module/sksg/Recorder/commands/ImageFilters.js.map +1 -1
- package/lib/module/sksg/Recorder/commands/Shaders.js +1 -1
- package/lib/module/sksg/Recorder/commands/Shaders.js.map +1 -1
- package/lib/module/views/SkiaPictureView.web.js +247 -57
- 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/commonjs/skia/web/JsiSkia.d.ts +0 -1
- package/lib/typescript/lib/commonjs/sksg/Recorder/DrawingContext.d.ts +1 -0
- package/lib/typescript/lib/module/skia/Skia.web.d.ts +0 -1
- package/lib/typescript/lib/module/skia/web/JsiSkPathBuilder.d.ts +1 -1
- package/lib/typescript/lib/module/skia/web/JsiSkia.d.ts +0 -1
- package/lib/typescript/lib/module/sksg/Recorder/DrawingContext.d.ts +1 -0
- package/lib/typescript/react-native.config.d.ts +3 -0
- package/lib/typescript/src/skia/types/JsiInstance.d.ts +1 -0
- package/lib/typescript/src/skia/types/Skia.d.ts +1 -6
- package/lib/typescript/src/skia/web/JsiSkPathBuilder.d.ts +1 -1
- package/lib/typescript/src/sksg/Recorder/DrawingContext.d.ts +1 -0
- package/package.json +13 -10
- package/react-native.config.js +5 -0
- package/src/dom/nodes/datatypes/Rect.ts +6 -1
- package/src/external/reanimated/useVideo.ts +7 -2
- package/src/renderer/components/Mask.tsx +10 -3
- package/src/skia/types/JsiInstance.ts +2 -0
- package/src/skia/types/Matrix4.ts +6 -2
- package/src/skia/types/Skia.ts +1 -7
- 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/JsiSkia.ts +0 -4
- package/src/skia/web/JsiSkottieAnimation.ts +8 -8
- package/src/sksg/Recorder/DrawingContext.ts +15 -0
- package/src/sksg/Recorder/Player.ts +1 -1
- package/src/sksg/Recorder/Visitor.ts +9 -6
- package/src/sksg/Recorder/commands/Drawing.ts +12 -9
- package/src/sksg/Recorder/commands/ImageFilters.ts +20 -1
- package/src/sksg/Recorder/commands/Shaders.ts +1 -1
- package/src/views/SkiaPictureView.web.tsx +245 -57
- package/cpp/api/JsiSkiaContext.h +0 -94
- package/cpp/dawn/include/dawn/dawn_proc.h +0 -53
- package/cpp/dawn/include/dawn/dawn_proc_table.h +0 -330
- package/cpp/dawn/include/dawn/dawn_thread_dispatch_proc.h +0 -47
- package/cpp/dawn/include/dawn/dawn_version.h +0 -41
- package/cpp/dawn/include/dawn/native/D3D11Backend.h +0 -65
- package/cpp/dawn/include/dawn/native/D3D12Backend.h +0 -103
- package/cpp/dawn/include/dawn/native/D3DBackend.h +0 -56
- package/cpp/dawn/include/dawn/native/DawnNative.h +0 -368
- package/cpp/dawn/include/dawn/native/MetalBackend.h +0 -56
- package/cpp/dawn/include/dawn/native/NullBackend.h +0 -39
- package/cpp/dawn/include/dawn/native/OpenGLBackend.h +0 -89
- package/cpp/dawn/include/dawn/native/VulkanBackend.h +0 -183
- package/cpp/dawn/include/dawn/native/WebGPUBackend.h +0 -49
- package/cpp/dawn/include/dawn/native/dawn_native_export.h +0 -49
- package/cpp/dawn/include/dawn/platform/DawnPlatform.h +0 -207
- package/cpp/dawn/include/dawn/platform/dawn_platform_export.h +0 -49
- package/cpp/dawn/include/dawn/replay/Replay.h +0 -99
- package/cpp/dawn/include/dawn/replay/dawn_replay_export.h +0 -49
- package/cpp/dawn/include/dawn/webgpu_cpp_print.h +0 -2856
- package/cpp/dawn/include/tint/tint.h +0 -91
- package/cpp/dawn/include/webgpu/webgpu.h +0 -5029
- package/cpp/dawn/include/webgpu/webgpu_cpp.h +0 -10353
- package/cpp/dawn/include/webgpu/webgpu_cpp_chained_struct.h +0 -57
- package/cpp/dawn/include/webgpu/webgpu_enum_class_bitmasks.h +0 -162
- package/cpp/dawn/include/webgpu/webgpu_glfw.h +0 -88
- package/cpp/jsi/RuntimeAwareCache.cpp +0 -7
- package/cpp/jsi/RuntimeAwareCache.h +0 -117
- package/cpp/jsi/RuntimeLifecycleMonitor.cpp +0 -72
- package/cpp/jsi/RuntimeLifecycleMonitor.h +0 -32
- package/cpp/skia/src/gpu/graphite/ContextOptionsPriv.h +0 -45
- package/cpp/skia/src/gpu/graphite/ResourceTypes.h +0 -360
- package/cpp/skia/src/gpu/graphite/TextureProxyView.h +0 -105
- package/libs/.graphite +0 -1
|
@@ -9,23 +9,27 @@ export class JsiSkTextStyle {
|
|
|
9
9
|
color: value.color,
|
|
10
10
|
decoration: value.decoration,
|
|
11
11
|
decorationColor: value.decorationColor,
|
|
12
|
-
decorationStyle:
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
decorationStyle:
|
|
13
|
+
value.decorationStyle !== undefined
|
|
14
|
+
? { value: value.decorationStyle }
|
|
15
|
+
: undefined,
|
|
15
16
|
decorationThickness: value.decorationThickness,
|
|
16
17
|
fontFamilies: value.fontFamilies,
|
|
17
18
|
fontSize: value.fontSize,
|
|
18
19
|
fontStyle: value.fontStyle
|
|
19
20
|
? {
|
|
20
|
-
slant:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
slant:
|
|
22
|
+
value.fontStyle.slant !== undefined
|
|
23
|
+
? { value: value.fontStyle.slant }
|
|
24
|
+
: undefined,
|
|
25
|
+
weight:
|
|
26
|
+
value.fontStyle.weight !== undefined
|
|
27
|
+
? { value: value.fontStyle.weight }
|
|
28
|
+
: undefined,
|
|
29
|
+
width:
|
|
30
|
+
value.fontStyle.width !== undefined
|
|
31
|
+
? { value: value.fontStyle.width }
|
|
32
|
+
: undefined,
|
|
29
33
|
}
|
|
30
34
|
: undefined,
|
|
31
35
|
fontFeatures: value.fontFeatures,
|
|
@@ -44,9 +48,10 @@ export class JsiSkTextStyle {
|
|
|
44
48
|
: undefined,
|
|
45
49
|
}))
|
|
46
50
|
: undefined,
|
|
47
|
-
textBaseline:
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
textBaseline:
|
|
52
|
+
value.textBaseline !== undefined
|
|
53
|
+
? { value: value.textBaseline }
|
|
54
|
+
: undefined,
|
|
50
55
|
wordSpacing: value.wordSpacing,
|
|
51
56
|
};
|
|
52
57
|
}
|
package/src/skia/web/JsiSkia.ts
CHANGED
|
@@ -4,7 +4,6 @@ import type {
|
|
|
4
4
|
JsiRecorder,
|
|
5
5
|
SkContourMeasureIter,
|
|
6
6
|
Skia,
|
|
7
|
-
SkiaContext,
|
|
8
7
|
SkPath,
|
|
9
8
|
SkRect,
|
|
10
9
|
SkRuntimeEffect,
|
|
@@ -137,9 +136,6 @@ export const JsiSkApi = (CanvasKit: CanvasKit): Skia => ({
|
|
|
137
136
|
NativeBuffer: new JsiSkNativeBufferFactory(CanvasKit),
|
|
138
137
|
Skottie: new JsiSkottieFactory(CanvasKit),
|
|
139
138
|
Video: createVideo.bind(null, CanvasKit),
|
|
140
|
-
Context: (_surface: bigint, _width: number, _height: number) => {
|
|
141
|
-
return throwNotImplementedOnRNWeb<SkiaContext>();
|
|
142
|
-
},
|
|
143
139
|
Recorder: () => {
|
|
144
140
|
return throwNotImplementedOnRNWeb<JsiRecorder>();
|
|
145
141
|
},
|
|
@@ -164,28 +164,28 @@ export class JsiSkottieAnimation
|
|
|
164
164
|
if (result.text) {
|
|
165
165
|
textSlot.text = result.text;
|
|
166
166
|
}
|
|
167
|
-
if (result.textSize) {
|
|
167
|
+
if (result.textSize !== undefined) {
|
|
168
168
|
textSlot.textSize = result.textSize;
|
|
169
169
|
}
|
|
170
|
-
if (result.minTextSize) {
|
|
170
|
+
if (result.minTextSize !== undefined) {
|
|
171
171
|
textSlot.minTextSize = result.minTextSize;
|
|
172
172
|
}
|
|
173
|
-
if (result.maxTextSize) {
|
|
173
|
+
if (result.maxTextSize !== undefined) {
|
|
174
174
|
textSlot.maxTextSize = result.maxTextSize;
|
|
175
175
|
}
|
|
176
|
-
if (result.strokeWidth) {
|
|
176
|
+
if (result.strokeWidth !== undefined) {
|
|
177
177
|
textSlot.strokeWidth = result.strokeWidth;
|
|
178
178
|
}
|
|
179
|
-
if (result.lineHeight) {
|
|
179
|
+
if (result.lineHeight !== undefined) {
|
|
180
180
|
textSlot.lineHeight = result.lineHeight;
|
|
181
181
|
}
|
|
182
|
-
if (result.lineShift) {
|
|
182
|
+
if (result.lineShift !== undefined) {
|
|
183
183
|
textSlot.lineShift = result.lineShift;
|
|
184
184
|
}
|
|
185
|
-
if (result.ascent) {
|
|
185
|
+
if (result.ascent !== undefined) {
|
|
186
186
|
textSlot.ascent = result.ascent;
|
|
187
187
|
}
|
|
188
|
-
if (result.maxLines) {
|
|
188
|
+
if (result.maxLines !== undefined) {
|
|
189
189
|
textSlot.maxLines = result.maxLines;
|
|
190
190
|
}
|
|
191
191
|
// if (result.horizAlign) {
|
|
@@ -37,7 +37,10 @@ export const createDrawingContext = (
|
|
|
37
37
|
if (paintPool.length === 0) {
|
|
38
38
|
paintPool.push(Skia.Paint());
|
|
39
39
|
} else {
|
|
40
|
+
// reset() produces an anti-alias false paint, unlike the Skia.Paint()
|
|
41
|
+
// factory: restore the default so reused pools render like the first frame.
|
|
40
42
|
paintPool[0].reset();
|
|
43
|
+
paintPool[0].setAntiAlias(true);
|
|
41
44
|
}
|
|
42
45
|
paints.push(paintPool[0]);
|
|
43
46
|
opacities.push(1);
|
|
@@ -56,6 +59,17 @@ export const createDrawingContext = (
|
|
|
56
59
|
nextPaintIndex++;
|
|
57
60
|
};
|
|
58
61
|
|
|
62
|
+
// Pushes an externally owned paint (the `paint` prop) onto the stack. It
|
|
63
|
+
// pushes a frame-scoped copy, like the C++ DrawingCtx: materializePaint()
|
|
64
|
+
// mutates the top of the stack, and those mutations must not leak into the
|
|
65
|
+
// user-owned paint. It must also push an opacity alongside it: restorePaint()
|
|
66
|
+
// pops both, so pushing only the paint would underflow the opacity stack and
|
|
67
|
+
// leak the enclosing group's opacity onto everything drawn afterwards.
|
|
68
|
+
const pushPaint = (paint: SkPaint) => {
|
|
69
|
+
paints.push(track(paint.copy()));
|
|
70
|
+
opacities.push(opacities[opacities.length - 1]);
|
|
71
|
+
};
|
|
72
|
+
|
|
59
73
|
const getOpacity = () => {
|
|
60
74
|
return opacities[opacities.length - 1];
|
|
61
75
|
};
|
|
@@ -144,6 +158,7 @@ export const createDrawingContext = (
|
|
|
144
158
|
|
|
145
159
|
// Public methods
|
|
146
160
|
savePaint,
|
|
161
|
+
pushPaint,
|
|
147
162
|
saveBackdropFilter,
|
|
148
163
|
get paint() {
|
|
149
164
|
return paints[paints.length - 1];
|
|
@@ -123,7 +123,7 @@ const play = (ctx: DrawingContext, _command: Command) => {
|
|
|
123
123
|
ctx.canvas.saveLayer(paint);
|
|
124
124
|
} else if (isDrawCommand(command, CommandType.SavePaint)) {
|
|
125
125
|
if (command.props.paint) {
|
|
126
|
-
ctx.
|
|
126
|
+
ctx.pushPaint(command.props.paint);
|
|
127
127
|
} else {
|
|
128
128
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
129
129
|
const { standalone } = command as any;
|
|
@@ -104,13 +104,16 @@ const processCTM = ({
|
|
|
104
104
|
if (layer) {
|
|
105
105
|
ctm.layer = layer;
|
|
106
106
|
}
|
|
107
|
+
// Only record a CTM command when saveCTM() will actually save the canvas.
|
|
108
|
+
// saveCTM() saves on transform/matrix, clip or layer; restoreCTM() always
|
|
109
|
+
// restores, so recording a CTM that saves nothing - `origin` with no
|
|
110
|
+
// transform, `clip={false}` from a conditional, a bare `invertClip` - pops
|
|
111
|
+
// an enclosing save that belongs to an ancestor.
|
|
107
112
|
if (
|
|
108
|
-
clip !== undefined ||
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
matrix !== undefined ||
|
|
113
|
-
layer !== undefined
|
|
113
|
+
ctm.clip !== undefined ||
|
|
114
|
+
ctm.transform !== undefined ||
|
|
115
|
+
ctm.matrix !== undefined ||
|
|
116
|
+
ctm.layer !== undefined
|
|
114
117
|
) {
|
|
115
118
|
return ctm;
|
|
116
119
|
}
|
|
@@ -217,6 +217,16 @@ export const drawPath = (ctx: DrawingContext, props: PathProps) => {
|
|
|
217
217
|
|
|
218
218
|
let path = processPath(ctx.Skia, pathProps.path);
|
|
219
219
|
|
|
220
|
+
// Trim runs first, so start/end address the path the caller drew. Stroking
|
|
221
|
+
// first would replace it with its outline and leave the offsets walking that
|
|
222
|
+
// outline's perimeter instead. The native recorder orders these the same way.
|
|
223
|
+
if (hasStartOffset || hasEndOffset) {
|
|
224
|
+
const trimmed = ctx.Skia.Path.Trim(path, start, end, false);
|
|
225
|
+
if (trimmed) {
|
|
226
|
+
path = trimmed;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
220
230
|
// Apply fill type using PathBuilder
|
|
221
231
|
if (hasFillType) {
|
|
222
232
|
const builder = ctx.Skia.PathBuilder.MakeFromPath(path);
|
|
@@ -232,14 +242,6 @@ export const drawPath = (ctx: DrawingContext, props: PathProps) => {
|
|
|
232
242
|
}
|
|
233
243
|
}
|
|
234
244
|
|
|
235
|
-
// Apply trim using static Path.Trim
|
|
236
|
-
if (hasStartOffset || hasEndOffset) {
|
|
237
|
-
const trimmed = ctx.Skia.Path.Trim(path, start, end, false);
|
|
238
|
-
if (trimmed) {
|
|
239
|
-
path = trimmed;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
245
|
ctx.canvas.drawPath(path, ctx.paint);
|
|
244
246
|
};
|
|
245
247
|
|
|
@@ -295,7 +297,8 @@ export const drawImageSVG = (ctx: DrawingContext, props: ImageSVGProps) => {
|
|
|
295
297
|
return;
|
|
296
298
|
}
|
|
297
299
|
canvas.save();
|
|
298
|
-
|
|
300
|
+
// An offset of 0 is a valid position, so it must not be read as "unset".
|
|
301
|
+
if (x !== undefined && y !== undefined) {
|
|
299
302
|
canvas.translate(x, y);
|
|
300
303
|
}
|
|
301
304
|
canvas.drawSvg(svg, width, height);
|
|
@@ -31,6 +31,14 @@ export enum MorphologyOperator {
|
|
|
31
31
|
|
|
32
32
|
const Black = Float32Array.of(0, 0, 0, 1);
|
|
33
33
|
|
|
34
|
+
// Turns the source graphic into a hard silhouette: alpha is multiplied by 255
|
|
35
|
+
// and clamped, so any pixel the shape covers at all becomes fully opaque, and
|
|
36
|
+
// the colour channels are dropped. 255 is the saturation point of an 8-bit
|
|
37
|
+
// alpha channel, so one unit of coverage is enough to reach 1.
|
|
38
|
+
const AlphaSaturate = [
|
|
39
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0,
|
|
40
|
+
];
|
|
41
|
+
|
|
34
42
|
const MakeInnerShadow = (
|
|
35
43
|
Skia: Skia,
|
|
36
44
|
shadowOnly: boolean | undefined,
|
|
@@ -50,9 +58,20 @@ const MakeInnerShadow = (
|
|
|
50
58
|
Skia.ColorFilter.MakeBlend(Black, BlendMode.SrcIn),
|
|
51
59
|
null
|
|
52
60
|
);
|
|
61
|
+
// The shadow is generated outside the shape and then clipped back into it, so
|
|
62
|
+
// "outside" has to be the complement of the shape's silhouette. Taking SrcOut
|
|
63
|
+
// against the source graphic itself makes it the complement of the source's
|
|
64
|
+
// *alpha*: inside a translucent shape 1 - alpha is non-zero, so the shadow is
|
|
65
|
+
// generated across the whole interior and tints it, no matter how small the
|
|
66
|
+
// blur and the offset are (issue #2990). Saturating alpha first keeps the
|
|
67
|
+
// shadow tied to the shape's outline instead of to its opacity.
|
|
68
|
+
const coverage = Skia.ImageFilter.MakeColorFilter(
|
|
69
|
+
Skia.ColorFilter.MakeMatrix(AlphaSaturate),
|
|
70
|
+
null
|
|
71
|
+
);
|
|
53
72
|
const f1 = Skia.ImageFilter.MakeColorFilter(
|
|
54
73
|
Skia.ColorFilter.MakeBlend(color, BlendMode.SrcOut),
|
|
55
|
-
|
|
74
|
+
coverage
|
|
56
75
|
);
|
|
57
76
|
const f2 = Skia.ImageFilter.MakeOffset(dx, dy, f1);
|
|
58
77
|
const f3 = Skia.ImageFilter.MakeBlur(sigmaX, sigmaY, TileMode.Decal, f2);
|
|
@@ -221,7 +221,7 @@ const declareImageShader = (ctx: DrawingContext, props: ImageShaderProps) => {
|
|
|
221
221
|
);
|
|
222
222
|
} else {
|
|
223
223
|
shader = ctx.track(
|
|
224
|
-
image.
|
|
224
|
+
image.makeShaderOptions(
|
|
225
225
|
TileMode[enumKey(tx)],
|
|
226
226
|
TileMode[enumKey(ty)],
|
|
227
227
|
sampling?.filter ?? FilterMode.Linear,
|
|
@@ -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
|
|
|
@@ -370,22 +548,30 @@ export const SkiaPictureView = (props: SkiaPictureViewProps) => {
|
|
|
370
548
|
// - The renderer is created synchronously on mount (the canvas element is
|
|
371
549
|
// guaranteed to exist in useLayoutEffect; a zero size at that point is
|
|
372
550
|
// fine, the surface is created once the canvas is measurable).
|
|
373
|
-
// - Redraw requests coalesce into a single
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
//
|
|
551
|
+
// - Redraw requests coalesce into a single microtask. A microtask (not an
|
|
552
|
+
// animation frame) so that a picture produced inside a rAF callback (the
|
|
553
|
+
// Reanimated mapper) is drawn before the current frame paints: deferring
|
|
554
|
+
// to the next rAF alternates between "flush pending in this frame" and
|
|
555
|
+
// "flush scheduled for the next frame", drawing on every other frame
|
|
556
|
+
// only and halving the effective frame rate. A picture dispatched before
|
|
557
|
+
// the canvas has a size stays in pictureRef (drawing while unmeasured is
|
|
558
|
+
// a no-op) and is painted by the resize path below once the canvas
|
|
559
|
+
// becomes measurable, so it is never lost.
|
|
377
560
|
// - A ResizeObserver on the canvas itself recreates the surface and repaints
|
|
378
561
|
// synchronously (its callbacks run after layout, before paint), and is
|
|
379
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.
|
|
380
566
|
const redrawPendingRef = useRef(false);
|
|
381
|
-
const
|
|
567
|
+
const flushScheduledRef = useRef(false);
|
|
382
568
|
const onLayoutRef = useRef(onLayout);
|
|
383
569
|
useLayoutEffect(() => {
|
|
384
570
|
onLayoutRef.current = onLayout;
|
|
385
571
|
}, [onLayout]);
|
|
386
572
|
|
|
387
573
|
const flushRedraw = useCallback(() => {
|
|
388
|
-
|
|
574
|
+
flushScheduledRef.current = false;
|
|
389
575
|
if (redrawPendingRef.current && rendererRef.current && pictureRef.current) {
|
|
390
576
|
redrawPendingRef.current = false;
|
|
391
577
|
rendererRef.current.draw(pictureRef.current);
|
|
@@ -396,8 +582,9 @@ export const SkiaPictureView = (props: SkiaPictureViewProps) => {
|
|
|
396
582
|
|
|
397
583
|
const redraw = useCallback(() => {
|
|
398
584
|
redrawPendingRef.current = true;
|
|
399
|
-
if (
|
|
400
|
-
|
|
585
|
+
if (!flushScheduledRef.current) {
|
|
586
|
+
flushScheduledRef.current = true;
|
|
587
|
+
queueMicrotask(flushRedraw);
|
|
401
588
|
}
|
|
402
589
|
}, [flushRedraw]);
|
|
403
590
|
|
|
@@ -482,7 +669,7 @@ export const SkiaPictureView = (props: SkiaPictureViewProps) => {
|
|
|
482
669
|
}
|
|
483
670
|
const renderer = isStatic
|
|
484
671
|
? new StaticWebGLRenderer(canvas)
|
|
485
|
-
: new WebGLRenderer(canvas);
|
|
672
|
+
: new WebGLRenderer(canvas, redraw);
|
|
486
673
|
rendererRef.current = renderer;
|
|
487
674
|
|
|
488
675
|
const drawPicture = () => {
|
|
@@ -566,14 +753,11 @@ export const SkiaPictureView = (props: SkiaPictureViewProps) => {
|
|
|
566
753
|
rendererRef.current = null;
|
|
567
754
|
renderer.dispose();
|
|
568
755
|
};
|
|
569
|
-
}, [isStatic]);
|
|
756
|
+
}, [isStatic, redraw]);
|
|
570
757
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
frameRef.current = 0;
|
|
575
|
-
};
|
|
576
|
-
}, []);
|
|
758
|
+
// No flush cancellation is needed on unmount: a microtask queued before
|
|
759
|
+
// unmount runs within the same task, and flushRedraw no-ops once the
|
|
760
|
+
// layout-effect cleanup has nulled rendererRef.
|
|
577
761
|
|
|
578
762
|
useImperativeHandle(
|
|
579
763
|
ref,
|
|
@@ -632,6 +816,10 @@ export const SkiaPictureView = (props: SkiaPictureViewProps) => {
|
|
|
632
816
|
return (
|
|
633
817
|
<Platform.View {...viewProps}>
|
|
634
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"}
|
|
635
823
|
ref={canvasRef}
|
|
636
824
|
style={{ display: "block", width: "100%", height: "100%" }}
|
|
637
825
|
/>
|