@shopify/react-native-skia 2.6.9 → 2.8.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.
Files changed (247) hide show
  1. package/android/cpp/jni/include/JniSkiaBaseView.h +6 -4
  2. package/android/cpp/jni/include/JniSkiaPictureView.h +8 -6
  3. package/android/cpp/rnskia-android/OpenGLContext.h +10 -1
  4. package/android/cpp/rnskia-android/RNSkAndroidView.h +8 -8
  5. package/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp +9 -5
  6. package/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.h +4 -2
  7. package/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseView.java +48 -12
  8. package/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseViewManager.java +5 -0
  9. package/android/src/main/java/com/shopify/reactnative/skia/SkiaPictureView.java +2 -2
  10. package/android/src/main/java/com/shopify/reactnative/skia/ViewScreenshotService.java +11 -7
  11. package/apple/MetalContext.h +7 -6
  12. package/apple/MetalLayerColorSpace.mm +39 -0
  13. package/apple/MetalLayerColorSpaceUtils.h +34 -0
  14. package/apple/MetalWindowContext.h +3 -1
  15. package/apple/MetalWindowContext.mm +9 -8
  16. package/apple/RNSkAppleView.h +6 -0
  17. package/apple/RNSkMetalCanvasProvider.h +2 -0
  18. package/apple/RNSkMetalCanvasProvider.mm +15 -2
  19. package/apple/SkiaPictureView.mm +1 -0
  20. package/apple/SkiaPictureViewManager.mm +5 -0
  21. package/apple/SkiaUIView.h +1 -0
  22. package/apple/SkiaUIView.mm +9 -0
  23. package/cpp/api/JsiSkParagraph.h +114 -0
  24. package/cpp/api/JsiSkSurface.h +8 -2
  25. package/cpp/rnskia/RNDawnContext.h +4 -3
  26. package/cpp/rnskia/RNDawnUtils.h +25 -6
  27. package/cpp/rnskia/RNDawnWindowContext.h +44 -10
  28. package/cpp/rnskia/RNMetalLayerColorSpace.h +16 -0
  29. package/cpp/rnskia/RNSkJsiViewApi.h +46 -24
  30. package/cpp/rnwgpu/SurfaceRegistry.h +10 -4
  31. package/lib/commonjs/Platform/Platform.web.js +2 -90
  32. package/lib/commonjs/Platform/Platform.web.js.map +1 -1
  33. package/lib/commonjs/external/reanimated/useVideoLoading.js +11 -2
  34. package/lib/commonjs/external/reanimated/useVideoLoading.js.map +1 -1
  35. package/lib/commonjs/mock/index.js +1 -0
  36. package/lib/commonjs/mock/index.js.map +1 -1
  37. package/lib/commonjs/renderer/Canvas.d.ts +10 -1
  38. package/lib/commonjs/renderer/Canvas.js +2 -0
  39. package/lib/commonjs/renderer/Canvas.js.map +1 -1
  40. package/lib/commonjs/skia/core/SVG.web.js +4 -5
  41. package/lib/commonjs/skia/core/SVG.web.js.map +1 -1
  42. package/lib/commonjs/skia/types/Paragraph/Paragraph.d.ts +62 -0
  43. package/lib/commonjs/skia/types/Paragraph/Paragraph.js.map +1 -1
  44. package/lib/commonjs/skia/types/Surface/Surface.d.ts +5 -1
  45. package/lib/commonjs/skia/types/Surface/Surface.js.map +1 -1
  46. package/lib/commonjs/skia/web/Host.js +7 -0
  47. package/lib/commonjs/skia/web/Host.js.map +1 -1
  48. package/lib/commonjs/skia/web/JsiSkPaint.js +5 -0
  49. package/lib/commonjs/skia/web/JsiSkPaint.js.map +1 -1
  50. package/lib/commonjs/skia/web/JsiSkParagraph.d.ts +3 -1
  51. package/lib/commonjs/skia/web/JsiSkParagraph.js +6 -0
  52. package/lib/commonjs/skia/web/JsiSkParagraph.js.map +1 -1
  53. package/lib/commonjs/skia/web/JsiSkParagraphStyle.js +14 -3
  54. package/lib/commonjs/skia/web/JsiSkParagraphStyle.js.map +1 -1
  55. package/lib/commonjs/skia/web/JsiSkSurface.d.ts +1 -1
  56. package/lib/commonjs/skia/web/JsiSkSurface.js +2 -1
  57. package/lib/commonjs/skia/web/JsiSkSurface.js.map +1 -1
  58. package/lib/commonjs/sksg/Container.native.d.ts +1 -0
  59. package/lib/commonjs/sksg/Container.native.js +11 -0
  60. package/lib/commonjs/sksg/Container.native.js.map +1 -1
  61. package/lib/commonjs/sksg/Container.web.d.ts +1 -0
  62. package/lib/commonjs/sksg/Container.web.js +45 -3
  63. package/lib/commonjs/sksg/Container.web.js.map +1 -1
  64. package/lib/commonjs/sksg/Recorder/DrawingContext.d.ts +2 -0
  65. package/lib/commonjs/sksg/Recorder/DrawingContext.js +22 -6
  66. package/lib/commonjs/sksg/Recorder/DrawingContext.js.map +1 -1
  67. package/lib/commonjs/sksg/Recorder/FrameScope.d.ts +18 -0
  68. package/lib/commonjs/sksg/Recorder/FrameScope.js +66 -0
  69. package/lib/commonjs/sksg/Recorder/FrameScope.js.map +1 -0
  70. package/lib/commonjs/sksg/Recorder/FrameScope.native.d.ts +10 -0
  71. package/lib/commonjs/sksg/Recorder/FrameScope.native.js +26 -0
  72. package/lib/commonjs/sksg/Recorder/FrameScope.native.js.map +1 -0
  73. package/lib/commonjs/sksg/Recorder/Player.js +1 -2
  74. package/lib/commonjs/sksg/Recorder/Player.js.map +1 -1
  75. package/lib/commonjs/sksg/Recorder/Recorder.d.ts +3 -1
  76. package/lib/commonjs/sksg/Recorder/Recorder.js +12 -1
  77. package/lib/commonjs/sksg/Recorder/Recorder.js.map +1 -1
  78. package/lib/commonjs/sksg/Recorder/commands/Drawing.js +3 -3
  79. package/lib/commonjs/sksg/Recorder/commands/Drawing.js.map +1 -1
  80. package/lib/commonjs/sksg/Recorder/commands/Shaders.js +3 -3
  81. package/lib/commonjs/sksg/Recorder/commands/Shaders.js.map +1 -1
  82. package/lib/commonjs/sksg/StaticContainer.d.ts +1 -0
  83. package/lib/commonjs/sksg/StaticContainer.js +27 -1
  84. package/lib/commonjs/sksg/StaticContainer.js.map +1 -1
  85. package/lib/commonjs/specs/NativeSkiaModule.web.d.ts +2 -0
  86. package/lib/commonjs/specs/NativeSkiaModule.web.js +32 -7
  87. package/lib/commonjs/specs/NativeSkiaModule.web.js.map +1 -1
  88. package/lib/commonjs/specs/SkiaPictureViewNativeComponent.d.ts +1 -0
  89. package/lib/commonjs/specs/SkiaPictureViewNativeComponent.js.map +1 -1
  90. package/lib/commonjs/views/SkiaPictureView.js +2 -0
  91. package/lib/commonjs/views/SkiaPictureView.js.map +1 -1
  92. package/lib/commonjs/views/SkiaPictureView.web.js +279 -94
  93. package/lib/commonjs/views/SkiaPictureView.web.js.map +1 -1
  94. package/lib/commonjs/views/formats.d.ts +1 -0
  95. package/lib/commonjs/views/formats.js +28 -0
  96. package/lib/commonjs/views/formats.js.map +1 -0
  97. package/lib/commonjs/views/index.d.ts +1 -0
  98. package/lib/commonjs/views/index.js +11 -0
  99. package/lib/commonjs/views/index.js.map +1 -1
  100. package/lib/commonjs/views/types.d.ts +6 -0
  101. package/lib/commonjs/views/types.js.map +1 -1
  102. package/lib/module/Platform/Platform.web.js +3 -91
  103. package/lib/module/Platform/Platform.web.js.map +1 -1
  104. package/lib/module/external/reanimated/useVideoLoading.js +12 -2
  105. package/lib/module/external/reanimated/useVideoLoading.js.map +1 -1
  106. package/lib/module/mock/index.js +1 -0
  107. package/lib/module/mock/index.js.map +1 -1
  108. package/lib/module/renderer/Canvas.d.ts +10 -1
  109. package/lib/module/renderer/Canvas.js +2 -0
  110. package/lib/module/renderer/Canvas.js.map +1 -1
  111. package/lib/module/skia/core/SVG.web.js +4 -5
  112. package/lib/module/skia/core/SVG.web.js.map +1 -1
  113. package/lib/module/skia/types/Paragraph/Paragraph.d.ts +62 -0
  114. package/lib/module/skia/types/Paragraph/Paragraph.js.map +1 -1
  115. package/lib/module/skia/types/Surface/Surface.d.ts +5 -1
  116. package/lib/module/skia/types/Surface/Surface.js.map +1 -1
  117. package/lib/module/skia/web/Host.js +7 -0
  118. package/lib/module/skia/web/Host.js.map +1 -1
  119. package/lib/module/skia/web/JsiSkPaint.js +5 -0
  120. package/lib/module/skia/web/JsiSkPaint.js.map +1 -1
  121. package/lib/module/skia/web/JsiSkParagraph.d.ts +3 -1
  122. package/lib/module/skia/web/JsiSkParagraph.js +7 -1
  123. package/lib/module/skia/web/JsiSkParagraph.js.map +1 -1
  124. package/lib/module/skia/web/JsiSkParagraphStyle.js +14 -3
  125. package/lib/module/skia/web/JsiSkParagraphStyle.js.map +1 -1
  126. package/lib/module/skia/web/JsiSkSurface.d.ts +1 -1
  127. package/lib/module/skia/web/JsiSkSurface.js +2 -1
  128. package/lib/module/skia/web/JsiSkSurface.js.map +1 -1
  129. package/lib/module/sksg/Container.native.d.ts +1 -0
  130. package/lib/module/sksg/Container.native.js +11 -0
  131. package/lib/module/sksg/Container.native.js.map +1 -1
  132. package/lib/module/sksg/Container.web.d.ts +1 -0
  133. package/lib/module/sksg/Container.web.js +46 -4
  134. package/lib/module/sksg/Container.web.js.map +1 -1
  135. package/lib/module/sksg/Recorder/DrawingContext.d.ts +2 -0
  136. package/lib/module/sksg/Recorder/DrawingContext.js +22 -6
  137. package/lib/module/sksg/Recorder/DrawingContext.js.map +1 -1
  138. package/lib/module/sksg/Recorder/FrameScope.d.ts +18 -0
  139. package/lib/module/sksg/Recorder/FrameScope.js +59 -0
  140. package/lib/module/sksg/Recorder/FrameScope.js.map +1 -0
  141. package/lib/module/sksg/Recorder/FrameScope.native.d.ts +10 -0
  142. package/lib/module/sksg/Recorder/FrameScope.native.js +19 -0
  143. package/lib/module/sksg/Recorder/FrameScope.native.js.map +1 -0
  144. package/lib/module/sksg/Recorder/Player.js +1 -2
  145. package/lib/module/sksg/Recorder/Player.js.map +1 -1
  146. package/lib/module/sksg/Recorder/Recorder.d.ts +3 -1
  147. package/lib/module/sksg/Recorder/Recorder.js +10 -0
  148. package/lib/module/sksg/Recorder/Recorder.js.map +1 -1
  149. package/lib/module/sksg/Recorder/commands/Drawing.js +3 -3
  150. package/lib/module/sksg/Recorder/commands/Drawing.js.map +1 -1
  151. package/lib/module/sksg/Recorder/commands/Shaders.js +3 -3
  152. package/lib/module/sksg/Recorder/commands/Shaders.js.map +1 -1
  153. package/lib/module/sksg/StaticContainer.d.ts +1 -0
  154. package/lib/module/sksg/StaticContainer.js +28 -2
  155. package/lib/module/sksg/StaticContainer.js.map +1 -1
  156. package/lib/module/specs/NativeSkiaModule.web.d.ts +2 -0
  157. package/lib/module/specs/NativeSkiaModule.web.js +32 -7
  158. package/lib/module/specs/NativeSkiaModule.web.js.map +1 -1
  159. package/lib/module/specs/SkiaPictureViewNativeComponent.d.ts +1 -0
  160. package/lib/module/specs/SkiaPictureViewNativeComponent.js.map +1 -1
  161. package/lib/module/views/SkiaPictureView.js +2 -0
  162. package/lib/module/views/SkiaPictureView.js.map +1 -1
  163. package/lib/module/views/SkiaPictureView.web.js +280 -95
  164. package/lib/module/views/SkiaPictureView.web.js.map +1 -1
  165. package/lib/module/views/formats.d.ts +1 -0
  166. package/lib/module/views/formats.js +22 -0
  167. package/lib/module/views/formats.js.map +1 -0
  168. package/lib/module/views/index.d.ts +1 -0
  169. package/lib/module/views/index.js +1 -0
  170. package/lib/module/views/index.js.map +1 -1
  171. package/lib/module/views/types.d.ts +6 -0
  172. package/lib/module/views/types.js.map +1 -1
  173. package/lib/typescript/lib/commonjs/Platform/Platform.web.d.ts +1 -2
  174. package/lib/typescript/lib/commonjs/mock/index.d.ts +1 -0
  175. package/lib/typescript/lib/commonjs/renderer/Canvas.d.ts +2 -1
  176. package/lib/typescript/lib/commonjs/skia/web/JsiSkParagraph.d.ts +2 -0
  177. package/lib/typescript/lib/commonjs/skia/web/JsiSkSurface.d.ts +1 -1
  178. package/lib/typescript/lib/commonjs/sksg/Container.native.d.ts +1 -1
  179. package/lib/typescript/lib/commonjs/sksg/Container.web.d.ts +3 -3
  180. package/lib/typescript/lib/commonjs/sksg/Recorder/DrawingContext.d.ts +2 -0
  181. package/lib/typescript/lib/commonjs/sksg/Recorder/FrameScope.d.ts +17 -0
  182. package/lib/typescript/lib/commonjs/sksg/Recorder/FrameScope.native.d.ts +13 -0
  183. package/lib/typescript/lib/commonjs/sksg/Recorder/Recorder.d.ts +1 -0
  184. package/lib/typescript/lib/commonjs/sksg/StaticContainer.d.ts +2 -2
  185. package/lib/typescript/lib/commonjs/views/formats.d.ts +2 -0
  186. package/lib/typescript/lib/module/Platform/Platform.web.d.ts +1 -3
  187. package/lib/typescript/lib/module/mock/index.d.ts +2 -2
  188. package/lib/typescript/lib/module/renderer/Canvas.d.ts +2 -1
  189. package/lib/typescript/lib/module/skia/web/JsiSkParagraph.d.ts +2 -0
  190. package/lib/typescript/lib/module/skia/web/JsiSkSurface.d.ts +1 -1
  191. package/lib/typescript/lib/module/sksg/Container.native.d.ts +1 -1
  192. package/lib/typescript/lib/module/sksg/Container.web.d.ts +3 -3
  193. package/lib/typescript/lib/module/sksg/Recorder/DrawingContext.d.ts +2 -0
  194. package/lib/typescript/lib/module/sksg/Recorder/FrameScope.d.ts +5 -0
  195. package/lib/typescript/lib/module/sksg/Recorder/FrameScope.native.d.ts +5 -0
  196. package/lib/typescript/lib/module/sksg/Recorder/Recorder.d.ts +1 -0
  197. package/lib/typescript/lib/module/sksg/StaticContainer.d.ts +2 -2
  198. package/lib/typescript/lib/module/views/formats.d.ts +1 -0
  199. package/lib/typescript/lib/module/views/index.d.ts +1 -0
  200. package/lib/typescript/src/renderer/Canvas.d.ts +10 -1
  201. package/lib/typescript/src/skia/types/Paragraph/Paragraph.d.ts +62 -0
  202. package/lib/typescript/src/skia/types/Surface/Surface.d.ts +5 -1
  203. package/lib/typescript/src/skia/web/JsiSkParagraph.d.ts +3 -1
  204. package/lib/typescript/src/skia/web/JsiSkSurface.d.ts +1 -1
  205. package/lib/typescript/src/sksg/Container.native.d.ts +1 -0
  206. package/lib/typescript/src/sksg/Container.web.d.ts +1 -0
  207. package/lib/typescript/src/sksg/Recorder/DrawingContext.d.ts +2 -0
  208. package/lib/typescript/src/sksg/Recorder/FrameScope.d.ts +18 -0
  209. package/lib/typescript/src/sksg/Recorder/FrameScope.native.d.ts +10 -0
  210. package/lib/typescript/src/sksg/Recorder/Recorder.d.ts +3 -1
  211. package/lib/typescript/src/sksg/StaticContainer.d.ts +1 -0
  212. package/lib/typescript/src/specs/NativeSkiaModule.web.d.ts +2 -0
  213. package/lib/typescript/src/specs/SkiaPictureViewNativeComponent.d.ts +1 -0
  214. package/lib/typescript/src/views/formats.d.ts +1 -0
  215. package/lib/typescript/src/views/index.d.ts +1 -0
  216. package/lib/typescript/src/views/types.d.ts +6 -0
  217. package/package.json +4 -1
  218. package/react-native-skia.podspec +1 -0
  219. package/src/Platform/Platform.web.tsx +7 -99
  220. package/src/external/reanimated/useVideoLoading.ts +12 -2
  221. package/src/mock/index.ts +1 -0
  222. package/src/renderer/Canvas.tsx +11 -0
  223. package/src/skia/core/SVG.web.ts +5 -14
  224. package/src/skia/types/Paragraph/Paragraph.ts +67 -0
  225. package/src/skia/types/Surface/Surface.ts +5 -1
  226. package/src/skia/web/Host.ts +7 -0
  227. package/src/skia/web/JsiSkPaint.ts +5 -0
  228. package/src/skia/web/JsiSkParagraph.ts +9 -1
  229. package/src/skia/web/JsiSkParagraphStyle.ts +16 -2
  230. package/src/skia/web/JsiSkSurface.ts +2 -1
  231. package/src/sksg/Container.native.ts +12 -0
  232. package/src/sksg/Container.web.ts +43 -4
  233. package/src/sksg/Recorder/DrawingContext.ts +19 -6
  234. package/src/sksg/Recorder/FrameScope.native.ts +21 -0
  235. package/src/sksg/Recorder/FrameScope.ts +69 -0
  236. package/src/sksg/Recorder/Player.ts +1 -2
  237. package/src/sksg/Recorder/Recorder.ts +15 -1
  238. package/src/sksg/Recorder/commands/Drawing.ts +3 -3
  239. package/src/sksg/Recorder/commands/Shaders.ts +22 -16
  240. package/src/sksg/StaticContainer.ts +29 -2
  241. package/src/specs/NativeSkiaModule.web.ts +39 -7
  242. package/src/specs/SkiaPictureViewNativeComponent.ts +1 -0
  243. package/src/views/SkiaPictureView.tsx +2 -0
  244. package/src/views/SkiaPictureView.web.tsx +318 -106
  245. package/src/views/formats.ts +21 -0
  246. package/src/views/index.ts +1 -0
  247. package/src/views/types.ts +7 -0
@@ -4,10 +4,12 @@ import type {
4
4
  SkRect,
5
5
  SkRectWithDirection,
6
6
  SkParagraph,
7
+ SkPath,
7
8
  LineMetrics,
9
+ ParagraphExtendedVisitor,
8
10
  } from "../types";
9
11
 
10
- import { HostObject } from "./Host";
12
+ import { HostObject, throwNotImplementedOnRNWeb } from "./Host";
11
13
  import type { JsiSkCanvas } from "./JsiSkCanvas";
12
14
  import { JsiSkRect } from "./JsiSkRect";
13
15
 
@@ -64,4 +66,10 @@ export class JsiSkParagraph
64
66
  getLineMetrics(): LineMetrics[] {
65
67
  return this.ref.getLineMetrics();
66
68
  }
69
+ getPath(_lineNumber: number): SkPath | null {
70
+ return throwNotImplementedOnRNWeb<SkPath | null>();
71
+ }
72
+ extendedVisit(_visitor: ParagraphExtendedVisitor): void {
73
+ return throwNotImplementedOnRNWeb<void>();
74
+ }
67
75
  }
@@ -1,8 +1,10 @@
1
- import type { CanvasKit, ParagraphStyle } from "canvaskit-wasm";
1
+ import type { CanvasKit, ParagraphStyle, TextStyle } from "canvaskit-wasm";
2
2
 
3
3
  import { TextDirection } from "../types";
4
4
  import type { SkParagraphStyle } from "../types";
5
5
 
6
+ import { JsiSkTextStyle } from "./JsiSkTextStyle";
7
+
6
8
  export class JsiSkParagraphStyle {
7
9
  static toParagraphStyle(
8
10
  ck: CanvasKit,
@@ -10,7 +12,19 @@ export class JsiSkParagraphStyle {
10
12
  ): ParagraphStyle {
11
13
  // Seems like we need to provide the textStyle.color value, otherwise
12
14
  // the constructor crashes.
13
- const ps = new ck.ParagraphStyle({ textStyle: { color: ck.BLACK } });
15
+ const textStyle: TextStyle = { color: ck.BLACK };
16
+ if (value.textStyle) {
17
+ // Only merge the properties that are set; toTextStyle() emits undefined
18
+ // for the others and the ParagraphStyle constructor requires a color.
19
+ Object.entries(JsiSkTextStyle.toTextStyle(value.textStyle)).forEach(
20
+ ([key, v]) => {
21
+ if (v !== undefined) {
22
+ (textStyle as Record<string, unknown>)[key] = v;
23
+ }
24
+ }
25
+ );
26
+ }
27
+ const ps = new ck.ParagraphStyle({ textStyle });
14
28
 
15
29
  ps.disableHinting = value.disableHinting ?? ps.disableHinting;
16
30
  ps.ellipsis = value.ellipsis ?? ps.ellipsis;
@@ -19,7 +19,8 @@ export class JsiSkSurface
19
19
  this.ref.dispose();
20
20
  }
21
21
 
22
- flush() {
22
+ flush(_sync?: boolean) {
23
+ // CanvasKit has no separate CPU sync; flush() is sufficient on the web backend.
23
24
  this.ref.flush();
24
25
  }
25
26
 
@@ -39,9 +39,21 @@ class NativeReanimatedContainer extends Container {
39
39
  this.picture = Skia.Picture.MakePicture(null)!;
40
40
  }
41
41
 
42
+ unmount() {
43
+ super.unmount();
44
+ if (this.mapperId !== null) {
45
+ // The mapper closure retains the recorder and its resources (e.g.
46
+ // images) on the UI runtime and keeps updating the picture of an
47
+ // unmounted view — stop it or it leaks for the lifetime of the app.
48
+ Rea.stopMapper(this.mapperId);
49
+ this.mapperId = null;
50
+ }
51
+ }
52
+
42
53
  redraw() {
43
54
  if (this.mapperId !== null) {
44
55
  Rea.stopMapper(this.mapperId);
56
+ this.mapperId = null;
45
57
  }
46
58
  if (this.unmounted) {
47
59
  return;
@@ -3,7 +3,7 @@ import type { Skia } from "../skia/types";
3
3
  import { HAS_REANIMATED_3 } from "../external/reanimated/renderHelpers";
4
4
 
5
5
  import type { Recording } from "./Recorder/Recorder";
6
- import { Recorder } from "./Recorder/Recorder";
6
+ import { Recorder, disposeRecording } from "./Recorder/Recorder";
7
7
  import { visit } from "./Recorder/Visitor";
8
8
  import { replay } from "./Recorder/Player";
9
9
  import { createDrawingContext } from "./Recorder/DrawingContext";
@@ -22,12 +22,24 @@ const drawOnscreen = (Skia: Skia, nativeId: number, recording: Recording) => {
22
22
  //const start = performance.now();
23
23
 
24
24
  const ctx = createDrawingContext(Skia, recording.paintPool, canvas);
25
- replay(ctx, recording.commands);
26
- const picture = rec.finishRecordingAsPicture();
27
- rec.dispose();
25
+ let picture;
26
+ try {
27
+ replay(ctx, recording.commands);
28
+ picture = rec.finishRecordingAsPicture();
29
+ } finally {
30
+ // Frame-scoped objects (shaders, filters, paint copies, ...) are
31
+ // referenced by the picture once it is finished — delete the JS handles,
32
+ // otherwise they leak on Web where the GC cannot see WASM memory.
33
+ ctx.dispose();
34
+ rec.dispose();
35
+ }
28
36
  //const end = performance.now();
29
37
  //console.log("Recording time: ", end - start);
30
38
  SkiaViewApi.setJsiProperty(nativeId, "picture", picture);
39
+ // The view now references the new picture; the previous one is ours to
40
+ // delete.
41
+ recording.lastPicture?.dispose();
42
+ recording.lastPicture = picture;
31
43
  };
32
44
 
33
45
  class ReanimatedContainer extends Container {
@@ -40,13 +52,36 @@ class ReanimatedContainer extends Container {
40
52
  super(Skia);
41
53
  }
42
54
 
55
+ unmount() {
56
+ super.unmount();
57
+ if (this.mapperId !== null) {
58
+ // The mapper closure retains the recording and keeps updating the
59
+ // picture of an unmounted view — stop it or it leaks for the
60
+ // lifetime of the app.
61
+ Rea.stopMapper(this.mapperId);
62
+ this.mapperId = null;
63
+ }
64
+ const { recording } = this;
65
+ if (recording) {
66
+ this.recording = null;
67
+ // Deferred through the same channel as the draw calls so it runs after
68
+ // any of them that are still queued.
69
+ Rea.runOnUI(() => {
70
+ "worklet";
71
+ disposeRecording(recording);
72
+ })();
73
+ }
74
+ }
75
+
43
76
  redraw() {
44
77
  if (this.mapperId !== null) {
45
78
  Rea.stopMapper(this.mapperId);
79
+ this.mapperId = null;
46
80
  }
47
81
  if (this.unmounted) {
48
82
  return;
49
83
  }
84
+ const previousRecording = this.recording;
50
85
  const recorder = new Recorder();
51
86
  visit(recorder, this.root);
52
87
  const record = recorder.getRecording();
@@ -64,6 +99,10 @@ class ReanimatedContainer extends Container {
64
99
  }
65
100
  Rea.runOnUI(() => {
66
101
  "worklet";
102
+ if (previousRecording) {
103
+ // Runs after any still-queued draw of the previous recording.
104
+ disposeRecording(previousRecording);
105
+ }
67
106
  drawOnscreen(Skia, nativeId, recording!);
68
107
  })();
69
108
  }
@@ -8,6 +8,8 @@ import type {
8
8
  SkPathEffect,
9
9
  } from "../../skia/types";
10
10
 
11
+ import { createFrameScope } from "./FrameScope";
12
+
11
13
  export const createDrawingContext = (
12
14
  Skia: Skia,
13
15
  paintPool: SkPaint[],
@@ -15,6 +17,11 @@ export const createDrawingContext = (
15
17
  ) => {
16
18
  "worklet";
17
19
 
20
+ // Everything the renderer creates through frameSkia is frame-scoped and
21
+ // deleted by dispose() once the frame has been recorded. The paint pool is
22
+ // recording-scoped and deliberately allocated with the raw Skia instance.
23
+ const { Skia: frameSkia, track, dispose } = createFrameScope(Skia);
24
+
18
25
  // State (formerly class fields)
19
26
  const paints: SkPaint[] = [];
20
27
  const colorFilters: SkColorFilter[] = [];
@@ -27,7 +34,11 @@ export const createDrawingContext = (
27
34
  let nextPaintIndex = 1;
28
35
 
29
36
  // Initialize first paint and opacity
30
- paintPool[0] = Skia.Paint();
37
+ if (paintPool.length === 0) {
38
+ paintPool.push(Skia.Paint());
39
+ } else {
40
+ paintPool[0].reset();
41
+ }
31
42
  paints.push(paintPool[0]);
32
43
  opacities.push(1);
33
44
 
@@ -61,7 +72,7 @@ export const createDrawingContext = (
61
72
  } else {
62
73
  const cf = colorFilters.pop();
63
74
  if (cf) {
64
- imageFilter = Skia.ImageFilter.MakeColorFilter(cf, null);
75
+ imageFilter = frameSkia.ImageFilter.MakeColorFilter(cf, null);
65
76
  }
66
77
  }
67
78
  canvas.saveLayer(undefined, null, imageFilter);
@@ -83,7 +94,7 @@ export const createDrawingContext = (
83
94
  if (colorFilters.length > 0) {
84
95
  getCurrentPaint().setColorFilter(
85
96
  colorFilters.reduceRight((inner, outer) =>
86
- inner ? Skia.ColorFilter.MakeCompose(outer, inner) : outer
97
+ inner ? frameSkia.ColorFilter.MakeCompose(outer, inner) : outer
87
98
  )
88
99
  );
89
100
  }
@@ -95,7 +106,7 @@ export const createDrawingContext = (
95
106
  if (imageFilters.length > 0) {
96
107
  getCurrentPaint().setImageFilter(
97
108
  imageFilters.reduceRight((inner, outer) =>
98
- inner ? Skia.ImageFilter.MakeCompose(outer, inner) : outer
109
+ inner ? frameSkia.ImageFilter.MakeCompose(outer, inner) : outer
99
110
  )
100
111
  );
101
112
  }
@@ -104,7 +115,7 @@ export const createDrawingContext = (
104
115
  if (pathEffects.length > 0) {
105
116
  getCurrentPaint().setPathEffect(
106
117
  pathEffects.reduceRight((inner, outer) =>
107
- inner ? Skia.PathEffect.MakeCompose(outer, inner) : outer
118
+ inner ? frameSkia.PathEffect.MakeCompose(outer, inner) : outer
108
119
  )
109
120
  );
110
121
  }
@@ -119,8 +130,10 @@ export const createDrawingContext = (
119
130
  // Return an object containing the Skia reference, the canvas, and the methods
120
131
  return {
121
132
  // Public fields
122
- Skia,
133
+ Skia: frameSkia,
123
134
  canvas,
135
+ track,
136
+ dispose,
124
137
  paints,
125
138
  colorFilters,
126
139
  shaders,
@@ -0,0 +1,21 @@
1
+ import type { Skia } from "../../skia/types";
2
+
3
+ import type { FrameScope } from "./FrameScope";
4
+
5
+ /**
6
+ * No-op on native. The JSI wrappers report the size of the native objects
7
+ * they hold to the garbage collector (setExternalMemoryPressure), so the GC
8
+ * is aware of the real cost of the objects the renderer creates and reclaims
9
+ * them on its own. The tracking scope is only needed on Web, where CanvasKit
10
+ * objects live in WASM memory the GC cannot see (see FrameScope.ts).
11
+ */
12
+ export const createFrameScope = (Skia: Skia): FrameScope => {
13
+ "worklet";
14
+ return {
15
+ Skia,
16
+ track: <T>(value: T): T => value,
17
+ dispose: () => {
18
+ // nothing to dispose: no objects are tracked on native
19
+ },
20
+ };
21
+ };
@@ -0,0 +1,69 @@
1
+ import type { Skia } from "../../skia/types";
2
+
3
+ interface Disposable {
4
+ dispose: () => void;
5
+ }
6
+
7
+ const isDisposable = (value: unknown): value is Disposable => {
8
+ "worklet";
9
+ return (
10
+ value !== null &&
11
+ typeof value === "object" &&
12
+ typeof (value as Disposable).dispose === "function"
13
+ );
14
+ };
15
+
16
+ export interface FrameScope {
17
+ Skia: Skia;
18
+ track: <T>(value: T) => T;
19
+ dispose: () => void;
20
+ }
21
+
22
+ /**
23
+ * Wraps a Skia instance so that every object created through its factories is
24
+ * collected and deleted when the scope is disposed. The renderer only keeps
25
+ * the objects it creates alive for the duration of a frame: once the frame
26
+ * has been recorded they can be deleted. This is required on Web where
27
+ * CanvasKit objects live in WASM memory that the JS garbage collector does
28
+ * not perceive, so it (almost) never reclaims them on its own.
29
+ *
30
+ * Objects passed in via props are user-owned: they are not created through
31
+ * this facade and are therefore never tracked nor disposed.
32
+ */
33
+ export const createFrameScope = (Skia: Skia): FrameScope => {
34
+ "worklet";
35
+ const disposables: Disposable[] = [];
36
+ const track = <T>(value: T): T => {
37
+ if (isDisposable(value)) {
38
+ disposables.push(value);
39
+ }
40
+ return value;
41
+ };
42
+ const wrap = <T extends object>(obj: T): T => {
43
+ const cache = new Map<PropertyKey, unknown>();
44
+ return new Proxy(obj, {
45
+ get(target, prop) {
46
+ if (cache.has(prop)) {
47
+ return cache.get(prop);
48
+ }
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ const value = (target as any)[prop];
51
+ let wrapped: unknown = value;
52
+ if (typeof value === "function") {
53
+ wrapped = (...args: unknown[]) => track(value.apply(target, args));
54
+ } else if (value !== null && typeof value === "object") {
55
+ wrapped = wrap(value);
56
+ }
57
+ cache.set(prop, wrapped);
58
+ return wrapped;
59
+ },
60
+ }) as T;
61
+ };
62
+ const dispose = () => {
63
+ for (let i = 0; i < disposables.length; i++) {
64
+ disposables[i].dispose();
65
+ }
66
+ disposables.length = 0;
67
+ };
68
+ return { Skia: wrap(Skia), track, dispose };
69
+ };
@@ -166,9 +166,8 @@ const play = (ctx: DrawingContext, _command: Command) => {
166
166
  } else if (isCommand(command, CommandType.RestoreCTM)) {
167
167
  ctx.canvas.restore();
168
168
  } else {
169
- // TODO: is a copy needed here?
170
169
  // apply opacity to the current paint.
171
- const paint = ctx.paint.copy();
170
+ const paint = ctx.track(ctx.paint.copy());
172
171
  paint.setAlphaf(paint.getAlphaf() * ctx.getOpacity());
173
172
  const paints = [paint, ...ctx.paintDeclarations];
174
173
  ctx.paintDeclarations = [];
@@ -32,7 +32,7 @@ import type {
32
32
  import type { AnimatedProps } from "../../renderer";
33
33
  import { isSharedValue } from "../utils";
34
34
  import { isColorFilter, isImageFilter, isPathEffect, isShader } from "../Node";
35
- import type { SkPaint, BaseRecorder } from "../../skia/types";
35
+ import type { SkPaint, SkPicture, BaseRecorder } from "../../skia/types";
36
36
 
37
37
  import { CommandType } from "./Core";
38
38
  import type { Command } from "./Core";
@@ -40,8 +40,22 @@ import type { Command } from "./Core";
40
40
  export interface Recording {
41
41
  commands: Command[];
42
42
  paintPool: SkPaint[];
43
+ // Last picture produced from this recording. The view keeps its own
44
+ // reference to the picture it displays, so the renderer owns this one and
45
+ // disposes it when the next frame replaces it or the recording is disposed.
46
+ lastPicture?: SkPicture | null;
43
47
  }
44
48
 
49
+ export const disposeRecording = (recording: Recording) => {
50
+ "worklet";
51
+ recording.paintPool.forEach((paint) => paint.dispose());
52
+ recording.paintPool.length = 0;
53
+ if (recording.lastPicture) {
54
+ recording.lastPicture.dispose();
55
+ recording.lastPicture = null;
56
+ }
57
+ };
58
+
45
59
  interface AnimationValues {
46
60
  animationValues: Set<SharedValue<unknown>>;
47
61
  }
@@ -131,14 +131,14 @@ export const drawTextPath = (ctx: DrawingContext, props: TextPathProps) => {
131
131
  const widths = font.getGlyphWidths(ids);
132
132
  const rsx: SkRSXform[] = [];
133
133
  const meas = ctx.Skia.ContourMeasureIter(path, false, 1);
134
- let cont = meas.next();
134
+ let cont = ctx.track(meas.next());
135
135
  let dist = initialOffset;
136
136
  for (let i = 0; i < text.length && cont; i++) {
137
137
  const width = widths[i];
138
138
  dist += width / 2;
139
139
  if (dist > cont.length()) {
140
140
  // jump to next contour
141
- cont = meas.next();
141
+ cont = ctx.track(meas.next());
142
142
  if (!cont) {
143
143
  // We have come to the end of the path - terminate the string
144
144
  // right here.
@@ -221,7 +221,7 @@ export const drawPath = (ctx: DrawingContext, props: PathProps) => {
221
221
  if (hasFillType) {
222
222
  const builder = ctx.Skia.PathBuilder.MakeFromPath(path);
223
223
  builder.setFillType(FillType[enumKey(fillType)]);
224
- path = builder.build();
224
+ path = ctx.track(builder.build());
225
225
  }
226
226
 
227
227
  // Apply stroke using static Path.Stroke
@@ -43,10 +43,12 @@ const declareShader = (
43
43
  const { source, uniforms, ...transform } = props;
44
44
  const m3 = ctx.Skia.Matrix();
45
45
  processTransformProps(m3, transform);
46
- const shader = source.makeShaderWithChildren(
47
- processUniforms(source, uniforms),
48
- ctx.shaders.splice(0, children),
49
- m3
46
+ const shader = ctx.track(
47
+ source.makeShaderWithChildren(
48
+ processUniforms(source, uniforms),
49
+ ctx.shaders.splice(0, children),
50
+ m3
51
+ )
50
52
  );
51
53
  ctx.shaders.push(shader);
52
54
  };
@@ -208,20 +210,24 @@ const declareImageShader = (ctx: DrawingContext, props: ImageShaderProps) => {
208
210
  processTransformProps(lm, imageShaderProps);
209
211
  let shader: SkShader;
210
212
  if (sampling && isCubicSampling(sampling)) {
211
- shader = image.makeShaderCubic(
212
- TileMode[enumKey(tx)],
213
- TileMode[enumKey(ty)],
214
- sampling.B,
215
- sampling.C,
216
- lm
213
+ shader = ctx.track(
214
+ image.makeShaderCubic(
215
+ TileMode[enumKey(tx)],
216
+ TileMode[enumKey(ty)],
217
+ sampling.B,
218
+ sampling.C,
219
+ lm
220
+ )
217
221
  );
218
222
  } else {
219
- shader = image.makeShaderCubic(
220
- TileMode[enumKey(tx)],
221
- TileMode[enumKey(ty)],
222
- sampling?.filter ?? FilterMode.Linear,
223
- sampling?.mipmap ?? MipmapMode.None,
224
- lm
223
+ shader = ctx.track(
224
+ image.makeShaderCubic(
225
+ TileMode[enumKey(tx)],
226
+ TileMode[enumKey(ty)],
227
+ sampling?.filter ?? FilterMode.Linear,
228
+ sampling?.mipmap ?? MipmapMode.None,
229
+ lm
230
+ )
225
231
  );
226
232
  }
227
233
  ctx.shaders.push(shader);
@@ -2,7 +2,7 @@ import type { Skia, SkCanvas } from "../skia/types";
2
2
 
3
3
  import type { Node } from "./Node";
4
4
  import type { Recording } from "./Recorder/Recorder";
5
- import { Recorder } from "./Recorder/Recorder";
5
+ import { Recorder, disposeRecording } from "./Recorder/Recorder";
6
6
  import { visit } from "./Recorder/Visitor";
7
7
  import { replay } from "./Recorder/Player";
8
8
  import { createDrawingContext } from "./Recorder/DrawingContext";
@@ -41,7 +41,14 @@ export abstract class Container {
41
41
  this.recording.paintPool,
42
42
  canvas
43
43
  );
44
- replay(ctx, this.recording.commands);
44
+ try {
45
+ replay(ctx, this.recording.commands);
46
+ } finally {
47
+ // Frame-scoped objects created during the replay are no longer needed
48
+ // once drawn — delete them, otherwise they leak on Web where the GC
49
+ // cannot see WASM memory.
50
+ ctx.dispose();
51
+ }
45
52
  }
46
53
 
47
54
  abstract redraw(): void;
@@ -55,7 +62,24 @@ export class StaticContainer extends Container {
55
62
  super(Skia);
56
63
  }
57
64
 
65
+ unmount() {
66
+ super.unmount();
67
+ if (this.recording) {
68
+ disposeRecording(this.recording);
69
+ this.recording = null;
70
+ }
71
+ }
72
+
58
73
  redraw() {
74
+ if (this.unmounted) {
75
+ // The unmount commit triggers a last redraw — recording it would
76
+ // resurrect a recording for a container that has just disposed it.
77
+ return;
78
+ }
79
+ if (this.recording) {
80
+ disposeRecording(this.recording);
81
+ this.recording = null;
82
+ }
59
83
  const recorder = new Recorder();
60
84
  visit(recorder, this.root);
61
85
  this.recording = recorder.getRecording();
@@ -67,6 +91,9 @@ export class StaticContainer extends Container {
67
91
  const picture = rec.finishRecordingAsPicture();
68
92
  rec.dispose();
69
93
  SkiaViewApi.setJsiProperty(this.nativeId, "picture", picture);
94
+ // The view keeps its own reference to the picture it displays; this
95
+ // one is renderer-owned and disposed with the recording.
96
+ this.recording.lastPicture = picture;
70
97
  }
71
98
  }
72
99
  }
@@ -6,29 +6,45 @@ import type { SkiaPictureViewHandle } from "../views/SkiaPictureView.web";
6
6
  export type ISkiaViewApiWeb = ISkiaViewApi & {
7
7
  views: Record<string, SkiaPictureViewHandle>;
8
8
  deferedPictures: Record<string, SkPicture>;
9
+ unregisteredViews: Set<string>;
9
10
  registerView(nativeId: string, view: SkiaPictureViewHandle): void;
11
+ unregisterView(nativeId: string): void;
10
12
  };
11
13
 
12
14
  global.SkiaViewApi = {
13
15
  views: {},
14
16
  deferedPictures: {},
17
+ unregisteredViews: new Set<string>(),
15
18
  deferedOnSize: {},
16
19
  web: true,
17
20
  registerView(nativeId: string, view: SkiaPictureViewHandle) {
21
+ this.unregisteredViews.delete(nativeId);
18
22
  // Maybe a picture for this view was already set
19
23
  if (this.deferedPictures[nativeId]) {
20
24
  view.setPicture(this.deferedPictures[nativeId] as SkPicture);
25
+ delete this.deferedPictures[nativeId];
21
26
  }
22
27
  this.views[nativeId] = view;
23
28
  },
29
+ unregisterView(nativeId: string) {
30
+ // Views must be removed on unmount: the handle's closures capture the
31
+ // canvas element, so a stale entry retains the whole detached DOM tree.
32
+ this.unregisteredViews.add(nativeId);
33
+ delete this.views[nativeId];
34
+ delete this.deferedPictures[nativeId];
35
+ },
24
36
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
37
  setJsiProperty(nativeId: number, name: string, value: any) {
26
38
  if (name === "picture") {
27
- if (!this.views[`${nativeId}`]) {
28
- this.deferedPictures[`${nativeId}`] = value;
29
- } else {
30
- this.views[`${nativeId}`].setPicture(value);
39
+ const id = `${nativeId}`;
40
+ if (this.views[id]) {
41
+ this.views[id].setPicture(value);
42
+ } else if (!this.unregisteredViews.has(id)) {
43
+ this.deferedPictures[id] = value;
31
44
  }
45
+ // Otherwise the view has unmounted (e.g. a trailing animation frame):
46
+ // drop the picture instead of deferring it for an id that will never
47
+ // register again, which would retain it forever.
32
48
  }
33
49
  },
34
50
  size(nativeId: number) {
@@ -39,14 +55,30 @@ global.SkiaViewApi = {
39
55
  }
40
56
  },
41
57
  requestRedraw(nativeId: number) {
42
- this.views[`${nativeId}`].redraw();
58
+ // The view may already have unmounted (e.g. a trailing animation frame).
59
+ this.views[`${nativeId}`]?.redraw();
43
60
  },
44
61
  makeImageSnapshot(nativeId: number, rect?: SkRect) {
45
- return this.views[`${nativeId}`].makeImageSnapshot(rect);
62
+ const view = this.views[`${nativeId}`];
63
+ if (!view) {
64
+ throw new Error(
65
+ `Cannot make image snapshot: view with nativeID ${nativeId} is not registered (it may have unmounted)`
66
+ );
67
+ }
68
+ return view.makeImageSnapshot(rect);
46
69
  },
47
70
  makeImageSnapshotAsync(nativeId: number, rect?: SkRect) {
48
71
  return new Promise((resolve, reject) => {
49
- const result = this.views[`${nativeId}`].makeImageSnapshot(rect);
72
+ const view = this.views[`${nativeId}`];
73
+ if (!view) {
74
+ reject(
75
+ new Error(
76
+ `Cannot make image snapshot: view with nativeID ${nativeId} is not registered (it may have unmounted)`
77
+ )
78
+ );
79
+ return;
80
+ }
81
+ const result = view.makeImageSnapshot(rect);
50
82
  if (result) {
51
83
  resolve(result);
52
84
  } else {
@@ -7,6 +7,7 @@ export interface NativeProps extends ViewProps {
7
7
  debug?: boolean;
8
8
  opaque?: boolean;
9
9
  colorSpace?: string;
10
+ highBitDepth?: boolean;
10
11
  androidWarmup?: boolean;
11
12
  pointerEvents?: WithDefault<
12
13
  "auto" | "none" | "box-none" | "box-only",
@@ -87,6 +87,7 @@ export class SkiaPictureView extends React.Component<SkiaPictureViewProps> {
87
87
  mode,
88
88
  debug = false,
89
89
  opaque = false,
90
+ highBitDepth = false,
90
91
  androidWarmup = false,
91
92
  ...viewProps
92
93
  } = this.props;
@@ -96,6 +97,7 @@ export class SkiaPictureView extends React.Component<SkiaPictureViewProps> {
96
97
  nativeID={`${this._nativeId}`}
97
98
  debug={debug}
98
99
  opaque={opaque}
100
+ highBitDepth={highBitDepth}
99
101
  androidWarmup={androidWarmup}
100
102
  {...viewProps}
101
103
  />