@shopify/react-native-skia 2.2.19 → 2.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/CMakeLists.txt +2 -0
- package/apple/SkiaCVPixelBufferUtils.mm +8 -4
- package/cpp/api/JsiSkCanvas.h +22 -0
- package/cpp/api/JsiSkDispatcher.cpp +9 -0
- package/cpp/api/JsiSkDispatcher.h +149 -0
- package/cpp/api/JsiSkImage.h +27 -9
- package/cpp/api/JsiSkPicture.h +22 -1
- package/cpp/api/JsiSkSurface.h +15 -10
- package/cpp/api/recorder/Drawings.h +43 -9
- package/cpp/api/recorder/JsiRecorder.h +3 -1
- package/cpp/api/recorder/RNRecorder.h +10 -4
- package/lib/commonjs/external/reanimated/textures.js +31 -28
- package/lib/commonjs/external/reanimated/textures.js.map +1 -1
- package/lib/commonjs/renderer/Offscreen.js +1 -7
- package/lib/commonjs/renderer/Offscreen.js.map +1 -1
- package/lib/commonjs/skia/core/SVG.web.js +9 -2
- package/lib/commonjs/skia/core/SVG.web.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.d.ts +4 -0
- package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.js +4 -0
- package/lib/commonjs/sksg/Recorder/ReanimatedRecorder.js.map +1 -1
- package/lib/module/external/reanimated/textures.js +33 -30
- package/lib/module/external/reanimated/textures.js.map +1 -1
- package/lib/module/renderer/Offscreen.js +1 -8
- package/lib/module/renderer/Offscreen.js.map +1 -1
- package/lib/module/skia/core/SVG.web.js +9 -2
- package/lib/module/skia/core/SVG.web.js.map +1 -1
- package/lib/module/sksg/Recorder/ReanimatedRecorder.d.ts +4 -0
- package/lib/module/sksg/Recorder/ReanimatedRecorder.js +5 -0
- package/lib/module/sksg/Recorder/ReanimatedRecorder.js.map +1 -1
- package/lib/typescript/lib/commonjs/sksg/Recorder/ReanimatedRecorder.d.ts +4 -0
- package/lib/typescript/lib/module/sksg/Recorder/ReanimatedRecorder.d.ts +4 -0
- package/lib/typescript/src/sksg/Recorder/ReanimatedRecorder.d.ts +4 -0
- package/package.json +6 -1
- package/src/external/reanimated/textures.tsx +36 -27
- package/src/renderer/Offscreen.tsx +1 -7
- package/src/skia/core/SVG.web.ts +12 -8
- package/src/sksg/Recorder/ReanimatedRecorder.ts +4 -0
- package/cpp/api/JsiSkThreadSafeDeletion.h +0 -105
@@ -2,6 +2,10 @@ import type { SharedValue } from "react-native-reanimated";
|
|
2
2
|
import type { BaseRecorder, JsiRecorder, Skia } from "../../skia/types";
|
3
3
|
import type { PaintProps, NodeType, BlurMaskFilterProps, CTMProps, BoxProps, BoxShadowProps, ImageProps, CircleProps, PointsProps, PathProps, RectProps, RoundedRectProps, OvalProps, LineProps, PatchProps, VerticesProps, DiffRectProps, TextProps, TextPathProps, TextBlobProps, GlyphsProps, PictureProps, ImageSVGProps, ParagraphProps, AtlasProps, SkottieProps } from "../../dom/types";
|
4
4
|
import type { AnimatedProps } from "../../renderer";
|
5
|
+
/**
|
6
|
+
* Currently the recorder only work if the GPU resources (e.g Images) are owned by the main thread.
|
7
|
+
* It will crash otherwise on Ganesh (iOS/Android).
|
8
|
+
*/
|
5
9
|
export declare class ReanimatedRecorder implements BaseRecorder {
|
6
10
|
private values;
|
7
11
|
private recorder;
|
package/package.json
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
"setup-skia-web": "scripts/setup-canvaskit.js"
|
9
9
|
},
|
10
10
|
"title": "React Native Skia",
|
11
|
-
"version": "2.2.
|
11
|
+
"version": "2.2.20",
|
12
12
|
"description": "High-performance React Native Graphics using Skia",
|
13
13
|
"main": "lib/module/index.js",
|
14
14
|
"react-native": "src/index.ts",
|
@@ -137,6 +137,11 @@
|
|
137
137
|
"jsSrcsDir": "src/specs",
|
138
138
|
"android": {
|
139
139
|
"javaPackageName": "com.shopify.reactnative.skia"
|
140
|
+
},
|
141
|
+
"ios": {
|
142
|
+
"componentProviders": [
|
143
|
+
"SkiaPictureViewNativeComponent"
|
144
|
+
]
|
140
145
|
}
|
141
146
|
},
|
142
147
|
"react-native-builder-bob": {
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { useEffect,
|
1
|
+
import { useEffect, useState } from "react";
|
2
2
|
import type { DependencyList, ReactElement } from "react";
|
3
3
|
import type { SharedValue } from "react-native-reanimated";
|
4
4
|
|
@@ -8,21 +8,45 @@ import type {
|
|
8
8
|
SkPicture,
|
9
9
|
SkSize,
|
10
10
|
} from "../../skia/types";
|
11
|
-
import {
|
12
|
-
drawAsImageFromPicture,
|
13
|
-
drawAsPicture,
|
14
|
-
} from "../../renderer/Offscreen";
|
11
|
+
import { drawAsPicture } from "../../renderer/Offscreen";
|
15
12
|
import { Skia, useImage } from "../../skia";
|
13
|
+
import { Platform } from "../../Platform";
|
16
14
|
|
17
15
|
import Rea from "./ReanimatedProxy";
|
18
16
|
|
17
|
+
const createTextureFromImage = (
|
18
|
+
texture: SharedValue<SkImage | null>,
|
19
|
+
image: SkImage
|
20
|
+
) => {
|
21
|
+
"worklet";
|
22
|
+
const surface = Skia.Surface.MakeOffscreen(image.width(), image.height());
|
23
|
+
if (!surface) {
|
24
|
+
texture.value = null;
|
25
|
+
return;
|
26
|
+
}
|
27
|
+
const canvas = surface.getCanvas();
|
28
|
+
canvas.drawImage(image, 0, 0);
|
29
|
+
surface.flush();
|
30
|
+
texture.value = surface.makeImageSnapshot();
|
31
|
+
if (Platform.OS === "web") {
|
32
|
+
texture.value = texture.value.makeNonTextureImage();
|
33
|
+
}
|
34
|
+
};
|
35
|
+
|
19
36
|
const createTexture = (
|
20
37
|
texture: SharedValue<SkImage | null>,
|
21
38
|
picture: SkPicture,
|
22
39
|
size: SkSize
|
23
40
|
) => {
|
24
41
|
"worklet";
|
25
|
-
|
42
|
+
const surface = Skia.Surface.MakeOffscreen(size.width, size.height)!;
|
43
|
+
const canvas = surface.getCanvas();
|
44
|
+
canvas.drawPicture(picture);
|
45
|
+
surface.flush();
|
46
|
+
texture.value = surface.makeImageSnapshot();
|
47
|
+
if (Platform.OS === "web") {
|
48
|
+
texture.value = texture.value.makeNonTextureImage();
|
49
|
+
}
|
26
50
|
};
|
27
51
|
|
28
52
|
export const useTexture = (
|
@@ -61,26 +85,11 @@ export const usePictureAsTexture = (
|
|
61
85
|
|
62
86
|
export const useImageAsTexture = (source: DataSourceParam) => {
|
63
87
|
const image = useImage(source);
|
64
|
-
const
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
return { width: 0, height: 0 };
|
69
|
-
}, [image]);
|
70
|
-
const picture = useMemo(() => {
|
71
|
-
if (image) {
|
72
|
-
const recorder = Skia.PictureRecorder();
|
73
|
-
const canvas = recorder.beginRecording({
|
74
|
-
x: 0,
|
75
|
-
y: 0,
|
76
|
-
width: size.width,
|
77
|
-
height: size.height,
|
78
|
-
});
|
79
|
-
canvas.drawImage(image, 0, 0);
|
80
|
-
return recorder.finishRecordingAsPicture();
|
81
|
-
} else {
|
82
|
-
return null;
|
88
|
+
const texture = Rea.useSharedValue<SkImage | null>(null);
|
89
|
+
useEffect(() => {
|
90
|
+
if (image !== null) {
|
91
|
+
Rea.runOnUI(createTextureFromImage)(texture, image);
|
83
92
|
}
|
84
|
-
}, [
|
85
|
-
return
|
93
|
+
}, [image, texture]);
|
94
|
+
return texture;
|
86
95
|
};
|
@@ -5,7 +5,6 @@ import { Skia } from "../skia";
|
|
5
5
|
import { Platform } from "../Platform";
|
6
6
|
import { SkiaSGRoot } from "../sksg/Reconciler";
|
7
7
|
|
8
|
-
// We call it main thread because on web main is JS thread
|
9
8
|
export const isOnMainThread = () => {
|
10
9
|
"worklet";
|
11
10
|
return (
|
@@ -36,10 +35,5 @@ export const drawAsImageFromPicture = (picture: SkPicture, size: SkSize) => {
|
|
36
35
|
canvas.drawPicture(picture);
|
37
36
|
surface.flush();
|
38
37
|
const image = surface.makeImageSnapshot();
|
39
|
-
|
40
|
-
if (!isOnMainThread() || Platform.OS === "web") {
|
41
|
-
return image.makeNonTextureImage();
|
42
|
-
} else {
|
43
|
-
return image;
|
44
|
-
}
|
38
|
+
return image.makeNonTextureImage();
|
45
39
|
};
|
package/src/skia/core/SVG.web.ts
CHANGED
@@ -8,14 +8,18 @@ export const useSVG = (
|
|
8
8
|
if (source === null || source === undefined) {
|
9
9
|
throw new Error(`Invalid svg data source. Got: ${source}`);
|
10
10
|
}
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
typeof source
|
16
|
-
|
17
|
-
typeof source.default
|
11
|
+
let src: string;
|
12
|
+
if (typeof source === "string") {
|
13
|
+
src = source;
|
14
|
+
} else if (
|
15
|
+
typeof source === "object" &&
|
16
|
+
"default" in source &&
|
17
|
+
typeof source.default === "string"
|
18
18
|
) {
|
19
|
+
src = source.default;
|
20
|
+
} else if (typeof source === "object" && "uri" in source) {
|
21
|
+
src = source.uri;
|
22
|
+
} else {
|
19
23
|
throw new Error(
|
20
24
|
`Invalid svg data source. Make sure that the source resolves to a string. Got: ${JSON.stringify(
|
21
25
|
source,
|
@@ -24,7 +28,7 @@ export const useSVG = (
|
|
24
28
|
)}`
|
25
29
|
);
|
26
30
|
}
|
27
|
-
const svg = Skia.SVG.MakeFromString(
|
31
|
+
const svg = Skia.SVG.MakeFromString(src);
|
28
32
|
if (svg === null && onError !== undefined) {
|
29
33
|
onError(new Error("Failed to create SVG from source."));
|
30
34
|
}
|
@@ -32,6 +32,10 @@ import type {
|
|
32
32
|
import type { AnimatedProps } from "../../renderer";
|
33
33
|
import { isSharedValue } from "../utils";
|
34
34
|
|
35
|
+
/**
|
36
|
+
* Currently the recorder only work if the GPU resources (e.g Images) are owned by the main thread.
|
37
|
+
* It will crash otherwise on Ganesh (iOS/Android).
|
38
|
+
*/
|
35
39
|
export class ReanimatedRecorder implements BaseRecorder {
|
36
40
|
private values = new Set<SharedValue<unknown>>();
|
37
41
|
private recorder: JsiRecorder;
|
@@ -1,105 +0,0 @@
|
|
1
|
-
#pragma once
|
2
|
-
|
3
|
-
#include <functional>
|
4
|
-
#include <memory>
|
5
|
-
#include <mutex>
|
6
|
-
#include <queue>
|
7
|
-
#include <thread>
|
8
|
-
|
9
|
-
// Define this to disable thread-safe deletion (for testing purposes)
|
10
|
-
// #define DISABLE_THREAD_SAFE_DELETION
|
11
|
-
|
12
|
-
namespace RNSkia {
|
13
|
-
|
14
|
-
/**
|
15
|
-
* Utility class for managing thread-safe deletion of Skia objects.
|
16
|
-
* Ensures objects are deleted on the thread that created them.
|
17
|
-
*/
|
18
|
-
template <typename T> class ThreadSafeDeletion {
|
19
|
-
private:
|
20
|
-
struct DeletionItem {
|
21
|
-
sk_sp<T> object;
|
22
|
-
std::thread::id creationThreadId;
|
23
|
-
};
|
24
|
-
|
25
|
-
static inline std::mutex _queueMutex;
|
26
|
-
static inline std::queue<DeletionItem> _deletionQueue;
|
27
|
-
|
28
|
-
std::thread::id _creationThreadId;
|
29
|
-
|
30
|
-
public:
|
31
|
-
ThreadSafeDeletion() : _creationThreadId(std::this_thread::get_id()) {}
|
32
|
-
|
33
|
-
/**
|
34
|
-
* Handles deletion of the object. If we're on the wrong thread,
|
35
|
-
* queues it for later deletion. Otherwise, allows immediate deletion.
|
36
|
-
* The object is always considered handled after this call.
|
37
|
-
*/
|
38
|
-
void handleDeletion(sk_sp<T> object) {
|
39
|
-
if (!object) {
|
40
|
-
return;
|
41
|
-
}
|
42
|
-
|
43
|
-
#ifdef DISABLE_THREAD_SAFE_DELETION
|
44
|
-
// When disabled, allow immediate deletion
|
45
|
-
// This will likely cause crashes when objects are deleted on wrong thread
|
46
|
-
return;
|
47
|
-
#else
|
48
|
-
// Always try to drain the queue when handling deletions
|
49
|
-
drainDeletionQueue();
|
50
|
-
|
51
|
-
// Check if we're on the creation thread
|
52
|
-
if (std::this_thread::get_id() != _creationThreadId) {
|
53
|
-
// Queue for deletion on the correct thread
|
54
|
-
std::lock_guard<std::mutex> lock(_queueMutex);
|
55
|
-
_deletionQueue.push({object, _creationThreadId});
|
56
|
-
return;
|
57
|
-
}
|
58
|
-
|
59
|
-
// We're on the correct thread, allow immediate deletion via destructor
|
60
|
-
return;
|
61
|
-
#endif
|
62
|
-
}
|
63
|
-
|
64
|
-
/**
|
65
|
-
* Drains the deletion queue for objects created on the current thread.
|
66
|
-
* Should be called periodically (e.g., when creating new objects).
|
67
|
-
*/
|
68
|
-
static void drainDeletionQueue() {
|
69
|
-
#ifndef DISABLE_THREAD_SAFE_DELETION
|
70
|
-
auto currentThreadId = std::this_thread::get_id();
|
71
|
-
std::queue<DeletionItem> remainingItems;
|
72
|
-
|
73
|
-
std::lock_guard<std::mutex> lock(_queueMutex);
|
74
|
-
|
75
|
-
// Process all items in the queue
|
76
|
-
while (!_deletionQueue.empty()) {
|
77
|
-
auto item = _deletionQueue.front();
|
78
|
-
_deletionQueue.pop();
|
79
|
-
|
80
|
-
// If this item belongs to the current thread, let it be deleted
|
81
|
-
if (item.creationThreadId == currentThreadId) {
|
82
|
-
// The sk_sp destructor will handle the cleanup
|
83
|
-
// Just let it go out of scope
|
84
|
-
} else {
|
85
|
-
// Keep items that belong to other threads
|
86
|
-
remainingItems.push(item);
|
87
|
-
}
|
88
|
-
}
|
89
|
-
|
90
|
-
// Put back items that couldn't be deleted
|
91
|
-
_deletionQueue = std::move(remainingItems);
|
92
|
-
#endif
|
93
|
-
}
|
94
|
-
|
95
|
-
/**
|
96
|
-
* Returns the number of items waiting for deletion.
|
97
|
-
* Useful for debugging and testing.
|
98
|
-
*/
|
99
|
-
static size_t getPendingDeletionCount() {
|
100
|
-
std::lock_guard<std::mutex> lock(_queueMutex);
|
101
|
-
return _deletionQueue.size();
|
102
|
-
}
|
103
|
-
};
|
104
|
-
|
105
|
-
} // namespace RNSkia
|