@shopify/react-native-skia 2.13.0 → 2.14.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/android/CMakeLists.txt +2 -0
- package/android/cpp/jni/include/JniSkiaPictureView.h +16 -15
- package/android/cpp/rnskia-android/OpenGLContext.h +1 -1
- package/android/cpp/rnskia-android/gl/Surface.h +1 -1
- package/apple/RNSkApplePlatformContext.h +3 -4
- package/apple/RNSkiaModule.h +11 -3
- package/apple/RNSkiaModule.mm +46 -19
- package/apple/SkiaPictureView.mm +2 -2
- package/apple/SkiaUIView.h +0 -1
- package/apple/SkiaUIView.mm +1 -1
- package/apple/ViewScreenshotService.h +2 -2
- package/apple/ViewScreenshotService.mm +5 -4
- package/cpp/api/JsiSkImage.h +1 -1
- package/cpp/api/JsiSkImageFilter.h +3 -1
- package/cpp/api/JsiSkParagraph.h +2 -1
- package/cpp/api/JsiSkParagraphBuilder.h +1 -1
- package/cpp/api/JsiSkParagraphBuilderFactory.h +1 -1
- package/cpp/api/JsiSkPath.h +11 -435
- package/cpp/api/JsiSkPathBuilder.h +11 -4
- package/cpp/api/JsiSkPictureRecorder.h +2 -1
- package/cpp/api/JsiSkShader.h +3 -1
- package/cpp/api/JsiSkSkottie.h +29 -28
- package/cpp/api/JsiSkStrutStyle.h +1 -1
- package/cpp/api/JsiSkSurfaceFactory.h +41 -0
- package/cpp/api/recorder/Command.h +1 -1
- package/cpp/api/recorder/Convertor.h +153 -132
- package/cpp/api/recorder/DataTypes.h +12 -12
- package/cpp/api/recorder/DrawingCtx.h +1 -1
- package/cpp/api/recorder/ImageFit.h +8 -5
- package/cpp/api/recorder/JsiRecorder.h +14 -31
- package/cpp/api/recorder/Paint.h +3 -3
- package/cpp/api/recorder/RNRecorder.h +56 -0
- package/cpp/api/third_party/base64.cpp +1 -1
- package/cpp/jsi/JSICache.cpp +23 -0
- package/cpp/jsi/JSICache.h +28 -1
- package/cpp/jsi/NativeObject.h +36 -7
- package/cpp/jsi/ViewProperty.cpp +23 -0
- package/cpp/jsi/ViewProperty.h +31 -10
- package/cpp/rnskia/RNDawnContext.h +34 -1
- package/cpp/rnskia/RNSkJsiViewApi.h +69 -2
- package/cpp/rnskia/RNSkPictureView.cpp +58 -0
- package/cpp/rnskia/RNSkPictureView.h +110 -24
- package/lib/commonjs/Platform/IPlatform.js.map +1 -1
- package/lib/commonjs/animation/functions/interpolate.js.map +1 -1
- package/lib/commonjs/animation/functions/interpolatePaths.js +18 -0
- package/lib/commonjs/animation/functions/interpolatePaths.js.map +1 -1
- package/lib/commonjs/skia/core/Image.js.map +1 -1
- package/lib/commonjs/skia/types/NativeBuffer/NativeBufferFactory.js.map +1 -1
- package/lib/commonjs/skia/types/Path/Path.d.ts +2 -336
- package/lib/commonjs/skia/types/Path/Path.js.map +1 -1
- package/lib/commonjs/skia/types/Recorder.d.ts +10 -0
- package/lib/commonjs/skia/types/Recorder.js.map +1 -1
- package/lib/commonjs/skia/types/Shader/Shader.js.map +1 -1
- package/lib/commonjs/skia/types/Surface/SurfaceFactory.d.ts +16 -0
- package/lib/commonjs/skia/types/Surface/SurfaceFactory.js.map +1 -1
- package/lib/commonjs/skia/web/JsiSkPath.d.ts +4 -38
- package/lib/commonjs/skia/web/JsiSkPath.js +3 -275
- package/lib/commonjs/skia/web/JsiSkPath.js.map +1 -1
- package/lib/commonjs/skia/web/JsiSkSurfaceFactory.d.ts +2 -1
- package/lib/commonjs/skia/web/JsiSkSurfaceFactory.js +3 -0
- package/lib/commonjs/skia/web/JsiSkSurfaceFactory.js.map +1 -1
- package/lib/commonjs/sksg/Container.native.d.ts +15 -1
- package/lib/commonjs/sksg/Container.native.js +34 -33
- package/lib/commonjs/sksg/Container.native.js.map +1 -1
- package/lib/commonjs/specs/NativeSkiaModule.web.js +4 -0
- package/lib/commonjs/specs/NativeSkiaModule.web.js.map +1 -1
- package/lib/commonjs/views/types.d.ts +9 -0
- package/lib/commonjs/views/types.js.map +1 -1
- package/lib/module/Platform/IPlatform.js.map +1 -1
- package/lib/module/animation/functions/interpolate.js.map +1 -1
- package/lib/module/animation/functions/interpolatePaths.js +18 -0
- package/lib/module/animation/functions/interpolatePaths.js.map +1 -1
- package/lib/module/skia/core/Image.js.map +1 -1
- package/lib/module/skia/types/NativeBuffer/NativeBufferFactory.js.map +1 -1
- package/lib/module/skia/types/Path/Path.d.ts +2 -336
- package/lib/module/skia/types/Path/Path.js.map +1 -1
- package/lib/module/skia/types/Recorder.d.ts +10 -0
- package/lib/module/skia/types/Recorder.js.map +1 -1
- package/lib/module/skia/types/Shader/Shader.js.map +1 -1
- package/lib/module/skia/types/Surface/SurfaceFactory.d.ts +16 -0
- package/lib/module/skia/types/Surface/SurfaceFactory.js.map +1 -1
- package/lib/module/skia/web/JsiSkPath.d.ts +4 -38
- package/lib/module/skia/web/JsiSkPath.js +4 -276
- package/lib/module/skia/web/JsiSkPath.js.map +1 -1
- package/lib/module/skia/web/JsiSkSurfaceFactory.d.ts +2 -1
- package/lib/module/skia/web/JsiSkSurfaceFactory.js +4 -1
- package/lib/module/skia/web/JsiSkSurfaceFactory.js.map +1 -1
- package/lib/module/sksg/Container.native.d.ts +15 -1
- package/lib/module/sksg/Container.native.js +34 -33
- package/lib/module/sksg/Container.native.js.map +1 -1
- package/lib/module/specs/NativeSkiaModule.web.js +4 -0
- package/lib/module/specs/NativeSkiaModule.web.js.map +1 -1
- package/lib/module/views/types.d.ts +9 -0
- package/lib/module/views/types.js.map +1 -1
- package/lib/typescript/lib/commonjs/skia/web/JsiSkPath.d.ts +1 -35
- package/lib/typescript/lib/commonjs/skia/web/JsiSkSurfaceFactory.d.ts +1 -0
- package/lib/typescript/lib/commonjs/sksg/Container.native.d.ts +15 -1
- package/lib/typescript/lib/module/skia/web/JsiSkPath.d.ts +3 -37
- package/lib/typescript/lib/module/skia/web/JsiSkSurfaceFactory.d.ts +1 -0
- package/lib/typescript/lib/module/sksg/Container.native.d.ts +15 -1
- package/lib/typescript/src/skia/types/Path/Path.d.ts +2 -336
- package/lib/typescript/src/skia/types/Recorder.d.ts +10 -0
- package/lib/typescript/src/skia/types/Surface/SurfaceFactory.d.ts +16 -0
- package/lib/typescript/src/skia/web/JsiSkPath.d.ts +4 -38
- package/lib/typescript/src/skia/web/JsiSkSurfaceFactory.d.ts +2 -1
- package/lib/typescript/src/sksg/Container.native.d.ts +15 -1
- package/lib/typescript/src/views/types.d.ts +9 -0
- package/package.json +2 -2
- package/src/Platform/IPlatform.ts +1 -4
- package/src/animation/functions/interpolate.ts +1 -4
- package/src/animation/functions/interpolatePaths.ts +24 -0
- package/src/skia/core/Image.ts +1 -2
- package/src/skia/types/NativeBuffer/NativeBufferFactory.ts +2 -5
- package/src/skia/types/Path/Path.ts +2 -420
- package/src/skia/types/Recorder.ts +10 -0
- package/src/skia/types/Shader/Shader.ts +1 -5
- package/src/skia/types/Surface/SurfaceFactory.ts +17 -0
- package/src/skia/web/JsiSkPath.ts +4 -495
- package/src/skia/web/JsiSkSurfaceFactory.ts +6 -2
- package/src/sksg/Container.native.ts +34 -37
- package/src/specs/NativeSkiaModule.web.ts +4 -0
- package/src/views/types.ts +13 -0
- package/apple/SkiaManager.h +0 -23
- package/apple/SkiaManager.mm +0 -63
|
@@ -6,8 +6,8 @@ namespace RNSkia {
|
|
|
6
6
|
|
|
7
7
|
using Uniforms = std::map<std::string, std::vector<float>>;
|
|
8
8
|
|
|
9
|
-
std::vector<float> processArray(jsi::Runtime &runtime,
|
|
10
|
-
|
|
9
|
+
inline std::vector<float> processArray(jsi::Runtime &runtime,
|
|
10
|
+
const jsi::Array &array) {
|
|
11
11
|
std::vector<float> result;
|
|
12
12
|
size_t length = array.length(runtime);
|
|
13
13
|
result.reserve(length);
|
|
@@ -37,18 +37,18 @@ std::vector<float> processArray(jsi::Runtime &runtime,
|
|
|
37
37
|
return result;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
bool isJSPoint(jsi::Runtime &runtime, const jsi::Value &value) {
|
|
40
|
+
inline bool isJSPoint(jsi::Runtime &runtime, const jsi::Value &value) {
|
|
41
41
|
return value.isObject() &&
|
|
42
42
|
value.asObject(runtime).hasProperty(runtime, "x") &&
|
|
43
43
|
value.asObject(runtime).hasProperty(runtime, "y");
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
bool isIndexable(jsi::Runtime &runtime, const jsi::Value &value) {
|
|
46
|
+
inline bool isIndexable(jsi::Runtime &runtime, const jsi::Value &value) {
|
|
47
47
|
return value.isObject() && value.asObject(runtime).hasProperty(runtime, "0");
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
std::shared_ptr<SkRect> processRect(jsi::Runtime &runtime,
|
|
51
|
-
|
|
50
|
+
inline std::shared_ptr<SkRect> processRect(jsi::Runtime &runtime,
|
|
51
|
+
const jsi::Value &value) {
|
|
52
52
|
if (value.isObject()) {
|
|
53
53
|
auto object = value.asObject(runtime);
|
|
54
54
|
auto ptr = tryGetJsiObject<JsiSkRect>(runtime, object);
|
|
@@ -68,7 +68,7 @@ std::shared_ptr<SkRect> processRect(jsi::Runtime &runtime,
|
|
|
68
68
|
return nullptr;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
SkPoint processPoint(jsi::Runtime &runtime, const jsi::Value &value) {
|
|
71
|
+
inline SkPoint processPoint(jsi::Runtime &runtime, const jsi::Value &value) {
|
|
72
72
|
if (value.isObject()) {
|
|
73
73
|
auto object = value.asObject(runtime);
|
|
74
74
|
if (object.hasProperty(runtime, "x") && object.hasProperty(runtime, "y")) {
|
|
@@ -78,11 +78,11 @@ SkPoint processPoint(jsi::Runtime &runtime, const jsi::Value &value) {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
throw std::runtime_error("Couldn't read point value");
|
|
81
|
-
}
|
|
81
|
+
}
|
|
82
82
|
|
|
83
83
|
// TODO: return the SkRRect directly
|
|
84
|
-
std::shared_ptr<SkRRect> processRRect(jsi::Runtime &runtime,
|
|
85
|
-
|
|
84
|
+
inline std::shared_ptr<SkRRect> processRRect(jsi::Runtime &runtime,
|
|
85
|
+
const jsi::Value &value) {
|
|
86
86
|
if (value.isObject()) {
|
|
87
87
|
auto object = value.asObject(runtime);
|
|
88
88
|
auto ptr = tryGetJsiObject<JsiSkRRect>(runtime, object);
|
|
@@ -119,8 +119,8 @@ std::shared_ptr<SkRRect> processRRect(jsi::Runtime &runtime,
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
// Return SkPath instead of shared_ptr<SkPath>
|
|
122
|
-
std::shared_ptr<SkPath> processPath(jsi::Runtime &runtime,
|
|
123
|
-
|
|
122
|
+
inline std::shared_ptr<SkPath> processPath(jsi::Runtime &runtime,
|
|
123
|
+
const jsi::Value &value) {
|
|
124
124
|
if (value.isString()) {
|
|
125
125
|
auto pathString = value.getString(runtime).utf8(runtime);
|
|
126
126
|
SkPath result;
|
|
@@ -18,14 +18,14 @@ struct FitRects {
|
|
|
18
18
|
SkRect dst;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
SkRect inscribe(SkSize size, SkRect rect) {
|
|
21
|
+
inline SkRect inscribe(SkSize size, SkRect rect) {
|
|
22
22
|
auto halfWidthDelta = (rect.width() - size.width()) / 2.0;
|
|
23
23
|
auto halfHeightDelta = (rect.height() - size.height()) / 2.0;
|
|
24
24
|
return SkRect::MakeXYWH(rect.x() + halfWidthDelta, rect.y() + halfHeightDelta,
|
|
25
25
|
size.width(), size.height());
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
SkMatrix rect2rect(SkRect src, SkRect dst) {
|
|
28
|
+
inline SkMatrix rect2rect(SkRect src, SkRect dst) {
|
|
29
29
|
auto sx = dst.width() / src.width();
|
|
30
30
|
auto sy = dst.height() / src.height();
|
|
31
31
|
auto tx = dst.x() - src.x() * sx;
|
|
@@ -36,9 +36,12 @@ SkMatrix rect2rect(SkRect src, SkRect dst) {
|
|
|
36
36
|
return m3;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
SkSize size(double width, double height) {
|
|
39
|
+
inline SkSize size(double width, double height) {
|
|
40
|
+
return SkSize::Make(width, height);
|
|
41
|
+
}
|
|
40
42
|
|
|
41
|
-
FitSizes applyBoxFit(const std::string fit, SkSize input,
|
|
43
|
+
inline FitSizes applyBoxFit(const std::string fit, SkSize input,
|
|
44
|
+
SkSize output) {
|
|
42
45
|
SkSize src = size(0, 0);
|
|
43
46
|
SkSize dst = size(0, 0);
|
|
44
47
|
|
|
@@ -96,7 +99,7 @@ FitSizes applyBoxFit(const std::string fit, SkSize input, SkSize output) {
|
|
|
96
99
|
return {.src = src, .dst = dst};
|
|
97
100
|
}
|
|
98
101
|
|
|
99
|
-
FitRects fitRects(const std::string &fit, SkRect rect, SkRect rect2) {
|
|
102
|
+
inline FitRects fitRects(const std::string &fit, SkRect rect, SkRect rect2) {
|
|
100
103
|
auto sizes = applyBoxFit(fit, size(rect.width(), rect.height()),
|
|
101
104
|
size(rect2.width(), rect2.height()));
|
|
102
105
|
|
|
@@ -24,7 +24,7 @@ class JsiRecorder
|
|
|
24
24
|
public:
|
|
25
25
|
static constexpr const char *CLASS_NAME = "Recorder";
|
|
26
26
|
|
|
27
|
-
JsiRecorder(std::shared_ptr<RNSkPlatformContext> context)
|
|
27
|
+
explicit JsiRecorder(std::shared_ptr<RNSkPlatformContext> context)
|
|
28
28
|
: JsiSkWrappingSharedPtrNativeObject<JsiRecorder, Recorder>(
|
|
29
29
|
std::move(context), std::make_shared<Recorder>()) {
|
|
30
30
|
getObject()->_context = getContext();
|
|
@@ -70,18 +70,7 @@ public:
|
|
|
70
70
|
throw jsi::JSError(runtime, "Invalid Picture object provided to play()");
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
SkPictureRecorder pictureRecorder;
|
|
75
|
-
SkISize size = SkISize::Make(2'000'000, 2'000'000);
|
|
76
|
-
SkRect rect = SkRect::Make(size);
|
|
77
|
-
auto canvas = pictureRecorder.beginRecording(rect, nullptr);
|
|
78
|
-
|
|
79
|
-
// Play the recorded commands into the canvas
|
|
80
|
-
DrawingCtx ctx(canvas);
|
|
81
|
-
getObject()->play(&ctx);
|
|
82
|
-
|
|
83
|
-
// Finish recording and get the new picture
|
|
84
|
-
auto newPicture = pictureRecorder.finishRecordingAsPicture();
|
|
73
|
+
auto newPicture = getObject()->makePicture();
|
|
85
74
|
|
|
86
75
|
// Update the existing JsiSkPicture object with the new SkPicture
|
|
87
76
|
// This reuses the existing JavaScript object instead of creating a new one
|
|
@@ -95,24 +84,12 @@ public:
|
|
|
95
84
|
|
|
96
85
|
JSI_HOST_FUNCTION(applyUpdates) {
|
|
97
86
|
auto values = arguments[0].asObject(runtime).asArray(runtime);
|
|
98
|
-
|
|
99
|
-
auto recorder = getObject();
|
|
100
|
-
for (int i = 0; i < size; i++) {
|
|
101
|
-
auto sharedValue = values.getValueAtIndex(runtime, i).asObject(runtime);
|
|
102
|
-
auto name = "variable" + std::to_string(i);
|
|
103
|
-
// Look up the conversion functions for this name
|
|
104
|
-
auto it = recorder->variables.find(name);
|
|
105
|
-
if (it != recorder->variables.end()) {
|
|
106
|
-
// Execute each conversion function in the vector
|
|
107
|
-
const auto &conversionFunctions = it->second;
|
|
108
|
-
for (const auto &conversionFunc : conversionFunctions) {
|
|
109
|
-
conversionFunc(runtime, sharedValue);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
87
|
+
getObject()->applyUpdates(runtime, values);
|
|
113
88
|
return jsi::Value::undefined();
|
|
114
89
|
}
|
|
115
90
|
|
|
91
|
+
JSI_HOST_FUNCTION(getId) { return jsi::Value(getObject()->id); }
|
|
92
|
+
|
|
116
93
|
JSI_HOST_FUNCTION(saveGroup) {
|
|
117
94
|
const jsi::Value *value = count > 0 ? &arguments[0] : nullptr;
|
|
118
95
|
getObject()->saveGroup(runtime, value);
|
|
@@ -377,11 +354,17 @@ public:
|
|
|
377
354
|
installHostMethod(runtime, prototype, "applyUpdates",
|
|
378
355
|
&JsiRecorder::applyUpdates);
|
|
379
356
|
installHostMethod(runtime, prototype, "reset", &JsiRecorder::reset);
|
|
357
|
+
installHostMethod(runtime, prototype, "getId", &JsiRecorder::getId);
|
|
380
358
|
}
|
|
381
359
|
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
|
|
360
|
+
// The recorder itself is a small command list. The resources it references
|
|
361
|
+
// (images, pictures, paths) report their own size through their wrappers
|
|
362
|
+
// and the pictures produced by play() report theirs. Do not put a made-up
|
|
363
|
+
// number here: a new recorder is created on every React commit, and the
|
|
364
|
+
// wrapper is disposed as soon as the view takes ownership of the recording
|
|
365
|
+
// (see NativeReanimatedContainer), so the charge would never drive a
|
|
366
|
+
// collection anyway.
|
|
367
|
+
size_t getMemoryPressure() override { return kMinMemoryPressure; }
|
|
385
368
|
|
|
386
369
|
static const jsi::HostFunctionType
|
|
387
370
|
createCtor(std::shared_ptr<RNSkPlatformContext> context) {
|
package/cpp/api/recorder/Paint.h
CHANGED
|
@@ -18,9 +18,9 @@ struct TransformProps {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
// TODO: can we avoid a copy here?
|
|
21
|
-
SkMatrix processTransform(std::optional<SkMatrix> &matrix,
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
inline SkMatrix processTransform(std::optional<SkMatrix> &matrix,
|
|
22
|
+
std::optional<SkM44> &transform,
|
|
23
|
+
std::optional<SkPoint> &origin) {
|
|
24
24
|
SkMatrix m3;
|
|
25
25
|
if (matrix.has_value()) {
|
|
26
26
|
m3 = matrix.value();
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
#include <algorithm>
|
|
4
|
+
#include <atomic>
|
|
4
5
|
#include <cmath>
|
|
6
|
+
#include <cstdint>
|
|
5
7
|
#include <memory>
|
|
8
|
+
#include <mutex>
|
|
6
9
|
#include <optional>
|
|
7
10
|
#include <string>
|
|
8
11
|
#include <vector>
|
|
9
12
|
|
|
10
13
|
#include <jsi/jsi.h>
|
|
11
14
|
|
|
15
|
+
#include "include/core/SkPictureRecorder.h"
|
|
16
|
+
|
|
12
17
|
#include "ColorFilters.h"
|
|
13
18
|
#include "Command.h"
|
|
14
19
|
#include "Convertor.h"
|
|
@@ -26,6 +31,11 @@ class Recorder {
|
|
|
26
31
|
private:
|
|
27
32
|
using CommandList = std::vector<std::unique_ptr<Command>>;
|
|
28
33
|
|
|
34
|
+
static double nextId() {
|
|
35
|
+
static std::atomic<uint64_t> counter{0};
|
|
36
|
+
return static_cast<double>(++counter);
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
struct PendingGroup {
|
|
30
40
|
GroupCommand *group;
|
|
31
41
|
float zIndex;
|
|
@@ -35,6 +45,12 @@ private:
|
|
|
35
45
|
CommandList commands;
|
|
36
46
|
std::vector<CommandList *> commandStack;
|
|
37
47
|
|
|
48
|
+
// Once a view owns the recording, the Reanimated mapper writes the shared
|
|
49
|
+
// values into the commands (applyUpdates, UI thread) while the view replays
|
|
50
|
+
// them (play, main thread). Both hold this lock; nothing else does, so it
|
|
51
|
+
// never nests with the view's lock.
|
|
52
|
+
std::mutex _commandsMutex;
|
|
53
|
+
|
|
38
54
|
CommandList ¤tCommands() { return *commandStack.back(); }
|
|
39
55
|
|
|
40
56
|
void pushCommand(std::unique_ptr<Command> command) {
|
|
@@ -94,6 +110,11 @@ public:
|
|
|
94
110
|
std::shared_ptr<RNSkPlatformContext> _context;
|
|
95
111
|
Variables variables;
|
|
96
112
|
|
|
113
|
+
// Unique per recording. The Reanimated mapper tags its updates with it so a
|
|
114
|
+
// mapper that outlives its recording (stopMapper() is asynchronous) cannot
|
|
115
|
+
// write into the recording that replaced it.
|
|
116
|
+
const double id = nextId();
|
|
117
|
+
|
|
97
118
|
Recorder() { commandStack.push_back(&commands); }
|
|
98
119
|
~Recorder() {
|
|
99
120
|
if (!_context || commands.empty()) {
|
|
@@ -382,10 +403,45 @@ public:
|
|
|
382
403
|
}
|
|
383
404
|
|
|
384
405
|
void play(DrawingCtx *ctx) {
|
|
406
|
+
std::lock_guard<std::mutex> lock(_commandsMutex);
|
|
385
407
|
for (const auto &cmd : commands) {
|
|
386
408
|
playCommand(ctx, cmd.get());
|
|
387
409
|
}
|
|
388
410
|
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Plays the recording into a picture. The cull rect is deliberately huge:
|
|
414
|
+
* a recording is not tied to a view size.
|
|
415
|
+
*/
|
|
416
|
+
sk_sp<SkPicture> makePicture() {
|
|
417
|
+
SkPictureRecorder pictureRecorder;
|
|
418
|
+
SkISize size = SkISize::Make(2'000'000, 2'000'000);
|
|
419
|
+
SkRect rect = SkRect::Make(size);
|
|
420
|
+
auto canvas = pictureRecorder.beginRecording(rect, nullptr);
|
|
421
|
+
DrawingCtx ctx(canvas);
|
|
422
|
+
play(&ctx);
|
|
423
|
+
return pictureRecorder.finishRecordingAsPicture();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Reads the current value of each shared value (in the order they were
|
|
428
|
+
* registered as variable0, variable1, ...) on the calling runtime and
|
|
429
|
+
* writes it into the recorded commands.
|
|
430
|
+
*/
|
|
431
|
+
void applyUpdates(jsi::Runtime &runtime, const jsi::Array &values) {
|
|
432
|
+
std::lock_guard<std::mutex> lock(_commandsMutex);
|
|
433
|
+
auto size = values.size(runtime);
|
|
434
|
+
for (size_t i = 0; i < size; i++) {
|
|
435
|
+
auto sharedValue = values.getValueAtIndex(runtime, i).asObject(runtime);
|
|
436
|
+
auto name = "variable" + std::to_string(i);
|
|
437
|
+
auto it = variables.find(name);
|
|
438
|
+
if (it != variables.end()) {
|
|
439
|
+
for (const auto &conversionFunc : it->second) {
|
|
440
|
+
conversionFunc(runtime, sharedValue);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
389
445
|
};
|
|
390
446
|
|
|
391
447
|
inline void Recorder::playCommand(DrawingCtx *ctx, Command *cmd) {
|
package/cpp/jsi/JSICache.cpp
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#include "JSICache.h"
|
|
2
2
|
|
|
3
|
+
#include <algorithm>
|
|
3
4
|
#include <iterator>
|
|
4
5
|
#include <memory>
|
|
5
6
|
#include <stdexcept>
|
|
@@ -96,4 +97,26 @@ jsi::Object &JSICache::setPrototype(PrototypeKey key, jsi::Object prototype) {
|
|
|
96
97
|
return it->second;
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
jsi::Value JSICache::lockWrapper(jsi::Runtime &runtime,
|
|
101
|
+
const void *nativeObject) {
|
|
102
|
+
auto it = _wrappers.find(nativeObject);
|
|
103
|
+
if (it == _wrappers.end()) {
|
|
104
|
+
return jsi::Value::undefined();
|
|
105
|
+
}
|
|
106
|
+
return it->second.lock(runtime);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
void JSICache::setWrapper(jsi::Runtime &runtime, const void *nativeObject,
|
|
110
|
+
const jsi::Object &wrapper) {
|
|
111
|
+
_wrappers.insert_or_assign(nativeObject, jsi::WeakObject(runtime, wrapper));
|
|
112
|
+
if (_wrappers.size() < _pruneWrappersAt) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
for (auto it = _wrappers.begin(); it != _wrappers.end();) {
|
|
116
|
+
it = it->second.lock(runtime).isUndefined() ? _wrappers.erase(it)
|
|
117
|
+
: std::next(it);
|
|
118
|
+
}
|
|
119
|
+
_pruneWrappersAt = std::max(kMinPruneWrappersAt, _wrappers.size() * 2);
|
|
120
|
+
}
|
|
121
|
+
|
|
99
122
|
} // namespace RNJsi
|
package/cpp/jsi/JSICache.h
CHANGED
|
@@ -13,7 +13,8 @@ namespace jsi = facebook::jsi;
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Per-runtime store for JSI values that native code wants to keep across
|
|
16
|
-
* calls
|
|
16
|
+
* calls: the prototype object of every NativeObject class, and the JS
|
|
17
|
+
* wrapper of every native object that has one on the runtime.
|
|
17
18
|
*
|
|
18
19
|
* Ownership model (borrowed from react-native-nitro-modules' JSICache):
|
|
19
20
|
*
|
|
@@ -72,6 +73,26 @@ public:
|
|
|
72
73
|
*/
|
|
73
74
|
jsi::Object &setPrototype(PrototypeKey key, jsi::Object prototype);
|
|
74
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Returns the JS wrapper stored for `nativeObject` on this runtime, or
|
|
78
|
+
* undefined if none was stored or the wrapper has been collected.
|
|
79
|
+
*
|
|
80
|
+
* The key is the native object's address. A live wrapper keeps its native
|
|
81
|
+
* object alive (the object is the wrapper's NativeState), so as long as
|
|
82
|
+
* the entry locks it still belongs to that object. Once the wrapper has
|
|
83
|
+
* been collected the entry is stale and is skipped or replaced, so an
|
|
84
|
+
* address reused by a new object can never resolve to the old wrapper.
|
|
85
|
+
*/
|
|
86
|
+
jsi::Value lockWrapper(jsi::Runtime &runtime, const void *nativeObject);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Stores `wrapper` as the JS wrapper of `nativeObject` on this runtime.
|
|
90
|
+
* Only a weak reference is kept: the wrapper stays collectable, and the
|
|
91
|
+
* native object is released when it goes away.
|
|
92
|
+
*/
|
|
93
|
+
void setWrapper(jsi::Runtime &runtime, const void *nativeObject,
|
|
94
|
+
const jsi::Object &wrapper);
|
|
95
|
+
|
|
75
96
|
JSICache() = default;
|
|
76
97
|
~JSICache() override = default;
|
|
77
98
|
JSICache(const JSICache &) = delete;
|
|
@@ -81,6 +102,12 @@ private:
|
|
|
81
102
|
static std::shared_ptr<JSICache> getOrCreateOnGlobal(jsi::Runtime &runtime);
|
|
82
103
|
|
|
83
104
|
std::unordered_map<PrototypeKey, jsi::Object> _prototypes;
|
|
105
|
+
// Native object -> its wrapper on this runtime. Entries of collected
|
|
106
|
+
// wrappers are pruned in setWrapper once the map has doubled since the
|
|
107
|
+
// last pruning, so it stays proportional to the number of live wrappers.
|
|
108
|
+
std::unordered_map<const void *, jsi::WeakObject> _wrappers;
|
|
109
|
+
size_t _pruneWrappersAt = kMinPruneWrappersAt;
|
|
110
|
+
static constexpr size_t kMinPruneWrappersAt = 64;
|
|
84
111
|
|
|
85
112
|
// Runtime -> cache. Weak on purpose: the runtime's global owns the cache.
|
|
86
113
|
static std::mutex _registryMutex;
|
package/cpp/jsi/NativeObject.h
CHANGED
|
@@ -210,12 +210,12 @@ public:
|
|
|
210
210
|
if (instance == nullptr) {
|
|
211
211
|
throw jsi::JSError(rt, "Invalid boxed native object state");
|
|
212
212
|
}
|
|
213
|
-
// Unboxing
|
|
214
|
-
// Keep the runtime the object was
|
|
215
|
-
// native code (e.g. GPUDevice
|
|
216
|
-
// the creation runtime, and
|
|
217
|
-
//
|
|
218
|
-
// and thread.
|
|
213
|
+
// Unboxing hands back the wrapper cached for the target runtime,
|
|
214
|
+
// creating it on first use. Keep the runtime the object was
|
|
215
|
+
// originally created on: async native code (e.g. GPUDevice
|
|
216
|
+
// error/lost events) delivers into the creation runtime, and
|
|
217
|
+
// rebinding it to a worklet runtime would invoke main-runtime
|
|
218
|
+
// jsi::Functions on the wrong runtime and thread.
|
|
219
219
|
auto *originalRuntime = instance->getCreationRuntime();
|
|
220
220
|
auto value = Derived::create(rt, instance);
|
|
221
221
|
if (originalRuntime != nullptr) {
|
|
@@ -262,10 +262,37 @@ public:
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
/**
|
|
265
|
-
*
|
|
265
|
+
* Returns the JS wrapper of `instance` on `runtime`, creating it on first
|
|
266
|
+
* use.
|
|
267
|
+
*
|
|
268
|
+
* A native object has at most one live wrapper per runtime. The wrapper is
|
|
269
|
+
* cached (weakly, so it stays collectable) in the runtime's JSICache, and
|
|
270
|
+
* converting the same object again (returning it from another native
|
|
271
|
+
* call, unboxing it in a worklet on every frame, ...) hands back that
|
|
272
|
+
* wrapper instead of a new one. Each wrapper reports the native memory the
|
|
273
|
+
* object owns to the GC of its runtime through setExternalMemoryPressure,
|
|
274
|
+
* so the memory is charged exactly once per runtime that can reach the
|
|
275
|
+
* object, however often it is converted; charging every conversion used
|
|
276
|
+
* to multiply the amount by the number of captures in a worklet until
|
|
277
|
+
* Hermes hit its max heap size. The hint is refreshed on every round trip
|
|
278
|
+
* for objects whose size changes after creation (path and paragraph
|
|
279
|
+
* builders).
|
|
266
280
|
*/
|
|
267
281
|
static jsi::Value create(jsi::Runtime &runtime,
|
|
268
282
|
std::shared_ptr<Derived> instance) {
|
|
283
|
+
auto &cache = JSICache::get(runtime);
|
|
284
|
+
const void *key = instance.get();
|
|
285
|
+
|
|
286
|
+
auto existing = cache.lockWrapper(runtime, key);
|
|
287
|
+
if (existing.isObject()) {
|
|
288
|
+
auto pressure = instance->getMemoryPressure();
|
|
289
|
+
if (pressure > 0) {
|
|
290
|
+
existing.getObject(runtime).setExternalMemoryPressure(runtime,
|
|
291
|
+
pressure);
|
|
292
|
+
}
|
|
293
|
+
return existing;
|
|
294
|
+
}
|
|
295
|
+
|
|
269
296
|
// Store creation runtime for logging etc.
|
|
270
297
|
instance->setCreationRuntime(&runtime);
|
|
271
298
|
|
|
@@ -290,9 +317,11 @@ public:
|
|
|
290
317
|
obj.setExternalMemoryPressure(runtime, pressure);
|
|
291
318
|
}
|
|
292
319
|
|
|
320
|
+
cache.setWrapper(runtime, key, obj);
|
|
293
321
|
return std::move(obj);
|
|
294
322
|
}
|
|
295
323
|
|
|
324
|
+
public:
|
|
296
325
|
/**
|
|
297
326
|
* Get the native state from a JS value.
|
|
298
327
|
* Throws if the value doesn't have the expected native state.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// The recorder headers depend on the include order established by
|
|
2
|
+
// JsiSkApi.h, so the probe lives here rather than in ViewProperty.h (which
|
|
3
|
+
// every platform view includes on its own).
|
|
4
|
+
#include "api/JsiSkApi.h"
|
|
5
|
+
|
|
6
|
+
#include "jsi/ViewProperty.h"
|
|
7
|
+
|
|
8
|
+
#include "api/JsiSkPicture.h"
|
|
9
|
+
#include "api/recorder/JsiRecorder.h"
|
|
10
|
+
|
|
11
|
+
namespace RNJsi {
|
|
12
|
+
|
|
13
|
+
ViewProperty::ViewProperty(jsi::Runtime &runtime, const jsi::Value &value) {
|
|
14
|
+
if (auto picture =
|
|
15
|
+
RNSkia::tryGetJsiObject<RNSkia::JsiSkPicture>(runtime, value)) {
|
|
16
|
+
_value = picture->getObject();
|
|
17
|
+
} else if (auto recorder =
|
|
18
|
+
RNSkia::tryGetJsiObject<RNSkia::JsiRecorder>(runtime, value)) {
|
|
19
|
+
_value = recorder->getObject();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
} // namespace RNJsi
|
package/cpp/jsi/ViewProperty.h
CHANGED
|
@@ -4,28 +4,49 @@
|
|
|
4
4
|
#include <jsi/jsi.h>
|
|
5
5
|
#include <memory>
|
|
6
6
|
#include <string>
|
|
7
|
+
#include <utility>
|
|
7
8
|
#include <variant>
|
|
8
9
|
|
|
9
|
-
#
|
|
10
|
+
#pragma clang diagnostic push
|
|
11
|
+
#pragma clang diagnostic ignored "-Wdocumentation"
|
|
12
|
+
|
|
13
|
+
#include "include/core/SkPicture.h"
|
|
14
|
+
|
|
15
|
+
#pragma clang diagnostic pop
|
|
16
|
+
|
|
17
|
+
namespace RNSkia {
|
|
18
|
+
class Recorder;
|
|
19
|
+
}
|
|
10
20
|
|
|
11
21
|
namespace RNJsi {
|
|
12
22
|
namespace jsi = facebook::jsi;
|
|
13
23
|
|
|
14
24
|
class ViewProperty {
|
|
15
25
|
public:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Probes the JS value for the native objects a view can display (a picture
|
|
28
|
+
* or a recorder); anything else yields an empty property. Defined in
|
|
29
|
+
* ViewProperty.cpp: the recorder headers depend on the include order
|
|
30
|
+
* established by JsiSkApi.h, which this header (included by every platform
|
|
31
|
+
* view) must not pull in.
|
|
32
|
+
*/
|
|
33
|
+
ViewProperty(jsi::Runtime &runtime, const jsi::Value &value);
|
|
23
34
|
|
|
24
|
-
bool
|
|
35
|
+
bool isPicture() { return std::holds_alternative<sk_sp<SkPicture>>(_value); }
|
|
36
|
+
|
|
37
|
+
bool isRecorder() {
|
|
38
|
+
return std::holds_alternative<std::shared_ptr<RNSkia::Recorder>>(_value);
|
|
39
|
+
}
|
|
25
40
|
|
|
26
41
|
sk_sp<SkPicture> getPicture() { return std::get<sk_sp<SkPicture>>(_value); }
|
|
27
42
|
|
|
43
|
+
std::shared_ptr<RNSkia::Recorder> getRecorder() {
|
|
44
|
+
return std::get<std::shared_ptr<RNSkia::Recorder>>(_value);
|
|
45
|
+
}
|
|
46
|
+
|
|
28
47
|
private:
|
|
29
|
-
std::variant<std::nullptr_t, sk_sp<SkPicture
|
|
48
|
+
std::variant<std::nullptr_t, sk_sp<SkPicture>,
|
|
49
|
+
std::shared_ptr<RNSkia::Recorder>>
|
|
50
|
+
_value = nullptr;
|
|
30
51
|
};
|
|
31
52
|
} // namespace RNJsi
|
|
@@ -112,7 +112,7 @@ public:
|
|
|
112
112
|
int height = static_cast<int>(IOSurfaceGetHeight(ioSurface));
|
|
113
113
|
#else
|
|
114
114
|
wgpu::SharedTextureMemoryAHardwareBufferDescriptor platformDesc;
|
|
115
|
-
auto ahb =
|
|
115
|
+
auto ahb = reinterpret_cast<AHardwareBuffer *>(buffer);
|
|
116
116
|
platformDesc.handle = ahb;
|
|
117
117
|
AHardwareBuffer_Desc adesc;
|
|
118
118
|
AHardwareBuffer_describe(ahb, &adesc);
|
|
@@ -270,6 +270,39 @@ public:
|
|
|
270
270
|
textureRef);
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
// Create an SkSurface that draws straight into a WebGPU texture (zero-copy).
|
|
274
|
+
// The texture must have RenderAttachment usage; give it TextureBinding too
|
|
275
|
+
// to sample it from WebGPU (e.g. as a three.js texture) after each flush.
|
|
276
|
+
// The surface retains the texture for its lifetime.
|
|
277
|
+
sk_sp<SkSurface> MakeSurfaceFromTexture(wgpu::Texture texture) {
|
|
278
|
+
if (!texture) {
|
|
279
|
+
return nullptr;
|
|
280
|
+
}
|
|
281
|
+
if (!(texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)) {
|
|
282
|
+
throw std::runtime_error(
|
|
283
|
+
"MakeSurfaceFromTexture: the texture needs RenderAttachment usage");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
skgpu::graphite::BackendTexture backendTexture =
|
|
287
|
+
skgpu::graphite::BackendTextures::MakeDawn(texture.Get());
|
|
288
|
+
|
|
289
|
+
struct TextureRef {
|
|
290
|
+
wgpu::Texture texture;
|
|
291
|
+
};
|
|
292
|
+
auto textureRef = new TextureRef{texture};
|
|
293
|
+
|
|
294
|
+
// The color type is derived from the texture format.
|
|
295
|
+
return SkSurfaces::WrapBackendTexture(
|
|
296
|
+
getRecorder(), backendTexture,
|
|
297
|
+
nullptr, // colorspace
|
|
298
|
+
nullptr, // surfaceProps
|
|
299
|
+
[](void *context) {
|
|
300
|
+
auto ref = static_cast<TextureRef *>(context);
|
|
301
|
+
delete ref;
|
|
302
|
+
},
|
|
303
|
+
textureRef);
|
|
304
|
+
}
|
|
305
|
+
|
|
273
306
|
// Create a WebGPU texture from an SkImage
|
|
274
307
|
// Returns a texture with CopySrc and TextureBinding usage
|
|
275
308
|
wgpu::Texture MakeTextureFromImage(sk_sp<SkImage> image) {
|