@shopify/react-native-skia 2.13.1 → 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/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/JsiSkPath.h +0 -431
- package/cpp/api/JsiSkSkottie.h +2 -3
- package/cpp/api/JsiSkStrutStyle.h +1 -1
- package/cpp/api/JsiSkSurfaceFactory.h +41 -0
- package/cpp/api/recorder/Convertor.h +152 -131
- package/cpp/api/recorder/DataTypes.h +11 -11
- package/cpp/api/recorder/ImageFit.h +8 -5
- package/cpp/api/recorder/JsiRecorder.h +9 -29
- package/cpp/api/recorder/Paint.h +3 -3
- package/cpp/api/recorder/RNRecorder.h +56 -0
- package/cpp/jsi/ViewProperty.cpp +23 -0
- package/cpp/jsi/ViewProperty.h +31 -10
- package/cpp/rnskia/RNDawnContext.h +33 -0
- 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/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/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/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")) {
|
|
@@ -81,8 +81,8 @@ SkPoint processPoint(jsi::Runtime &runtime, const jsi::Value &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
|
|
|
@@ -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,13 +354,16 @@ 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
360
|
// The recorder itself is a small command list. The resources it references
|
|
383
361
|
// (images, pictures, paths) report their own size through their wrappers
|
|
384
362
|
// and the pictures produced by play() report theirs. Do not put a made-up
|
|
385
|
-
// number here: a new recorder is created on every React commit and the
|
|
386
|
-
//
|
|
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.
|
|
387
367
|
size_t getMemoryPressure() override { return kMinMemoryPressure; }
|
|
388
368
|
|
|
389
369
|
static const jsi::HostFunctionType
|
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) {
|
|
@@ -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
|
|
@@ -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) {
|
|
@@ -73,6 +73,19 @@ public:
|
|
|
73
73
|
return func(info);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// Runs func on an existing entry under the registry's shared lock. Unlike
|
|
77
|
+
// withViewInfo(), this never creates an entry: func is not called for an
|
|
78
|
+
// unknown id. Other readers may hold the entry at the same time, so func
|
|
79
|
+
// must not modify it; the objects its props reference may be modified
|
|
80
|
+
// under their own lock.
|
|
81
|
+
template <typename F> void withExistingViewInfo(size_t id, F &&func) {
|
|
82
|
+
std::shared_lock<std::shared_mutex> lock(_mutex);
|
|
83
|
+
auto it = _registry.find(id);
|
|
84
|
+
if (it != _registry.end()) {
|
|
85
|
+
func(it->second);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
76
89
|
// Read-only lookup: never creates a registry entry.
|
|
77
90
|
std::shared_ptr<RNSkView> getView(size_t id) {
|
|
78
91
|
std::shared_lock<std::shared_mutex> lock(_mutex);
|
|
@@ -130,8 +143,7 @@ public:
|
|
|
130
143
|
nativeId, [&](std::shared_ptr<RNSkViewInfo> info) {
|
|
131
144
|
auto name = arguments[1].asString(runtime).utf8(runtime);
|
|
132
145
|
info->props.insert_or_assign(
|
|
133
|
-
arguments[
|
|
134
|
-
RNJsi::ViewProperty(runtime, arguments[2]));
|
|
146
|
+
name, RNJsi::ViewProperty(runtime, arguments[2]));
|
|
135
147
|
// Now let's see if we have a view that we can update
|
|
136
148
|
if (info->view != nullptr) {
|
|
137
149
|
// Update view!
|
|
@@ -170,6 +182,59 @@ public:
|
|
|
170
182
|
return jsi::Value::undefined();
|
|
171
183
|
}
|
|
172
184
|
|
|
185
|
+
/**
|
|
186
|
+
Reads the given shared values into the recording held for the view and
|
|
187
|
+
schedules a redraw. Called from the Reanimated mapper on every frame; the
|
|
188
|
+
only things crossing runtimes are the view id, the recording id and the
|
|
189
|
+
shared values. The recording is either owned by the view or, until the
|
|
190
|
+
view registers, queued for it in the registry. Updates for a recording
|
|
191
|
+
that is neither (a stale mapper, a view that has been torn down) are
|
|
192
|
+
ignored.
|
|
193
|
+
*/
|
|
194
|
+
JSI_HOST_FUNCTION(applyUpdates) {
|
|
195
|
+
if (count != 3 || !arguments[0].isNumber() || !arguments[1].isNumber() ||
|
|
196
|
+
!arguments[2].isObject() ||
|
|
197
|
+
!arguments[2].asObject(runtime).isArray(runtime)) {
|
|
198
|
+
_platformContext->raiseError(
|
|
199
|
+
"applyUpdates: expected (nativeId: number, recorderId: number, "
|
|
200
|
+
"values: SharedValue[])");
|
|
201
|
+
return jsi::Value::undefined();
|
|
202
|
+
}
|
|
203
|
+
int nativeId = arguments[0].asNumber();
|
|
204
|
+
auto recorderId = arguments[1].asNumber();
|
|
205
|
+
auto values = arguments[2].asObject(runtime).asArray(runtime);
|
|
206
|
+
std::shared_ptr<RNSkView> view;
|
|
207
|
+
ViewRegistry::getInstance().withExistingViewInfo(
|
|
208
|
+
nativeId, [&](const std::shared_ptr<RNSkViewInfo> &info) {
|
|
209
|
+
view = info->view;
|
|
210
|
+
if (view != nullptr) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
// The view has not registered yet (or is detached, which on iOS
|
|
214
|
+
// destroys it): the recording waits in the registry and is drawn,
|
|
215
|
+
// with whatever values it holds by then, once a view registers.
|
|
216
|
+
// Keep it current, under the registry lock so that a registration
|
|
217
|
+
// cannot slip in between; the first frame would otherwise show
|
|
218
|
+
// the values from recording time until the next change.
|
|
219
|
+
auto it = info->props.find("recorder");
|
|
220
|
+
if (it != info->props.end() && it->second.isRecorder()) {
|
|
221
|
+
RNSkPictureRenderer::applyUpdatesTo(it->second.getRecorder(),
|
|
222
|
+
runtime, recorderId, values);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
if (view == nullptr) {
|
|
226
|
+
return jsi::Value::undefined();
|
|
227
|
+
}
|
|
228
|
+
// Outside the registry lock: the update must not hold up the commits
|
|
229
|
+
// and registrations of other views.
|
|
230
|
+
auto renderer =
|
|
231
|
+
std::static_pointer_cast<RNSkPictureRenderer>(view->getRenderer());
|
|
232
|
+
if (renderer->applyUpdates(runtime, recorderId, values)) {
|
|
233
|
+
view->requestRedraw();
|
|
234
|
+
}
|
|
235
|
+
return jsi::Value::undefined();
|
|
236
|
+
}
|
|
237
|
+
|
|
173
238
|
JSI_HOST_FUNCTION(makeImageSnapshot) {
|
|
174
239
|
if (count < 1) {
|
|
175
240
|
_platformContext->raiseError(
|
|
@@ -293,6 +358,8 @@ public:
|
|
|
293
358
|
&RNSkJsiViewApi::setJsiProperty);
|
|
294
359
|
installHostMethod(runtime, prototype, "requestRedraw",
|
|
295
360
|
&RNSkJsiViewApi::requestRedraw);
|
|
361
|
+
installHostMethod(runtime, prototype, "applyUpdates",
|
|
362
|
+
&RNSkJsiViewApi::applyUpdates);
|
|
296
363
|
installHostMethod(runtime, prototype, "makeImageSnapshotAsync",
|
|
297
364
|
&RNSkJsiViewApi::makeImageSnapshotAsync);
|
|
298
365
|
installHostMethod(runtime, prototype, "makeImageSnapshot",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// The recorder headers depend on the include order established by
|
|
2
|
+
// JsiSkApi.h, so everything that needs a complete Recorder lives here rather
|
|
3
|
+
// than in RNSkPictureView.h (which platform views include on its own).
|
|
4
|
+
#include "api/JsiSkApi.h"
|
|
5
|
+
|
|
6
|
+
#include "RNSkPictureView.h"
|
|
7
|
+
|
|
8
|
+
#include "api/recorder/DrawingCtx.h"
|
|
9
|
+
#include "api/recorder/RNRecorder.h"
|
|
10
|
+
|
|
11
|
+
namespace RNSkia {
|
|
12
|
+
|
|
13
|
+
void RNSkPictureRenderer::setRecorder(std::shared_ptr<Recorder> recorder) {
|
|
14
|
+
sk_sp<SkPicture> picture;
|
|
15
|
+
if (recorder != nullptr && recorder->variables.empty()) {
|
|
16
|
+
picture = recorder->makePicture();
|
|
17
|
+
recorder = nullptr;
|
|
18
|
+
}
|
|
19
|
+
std::shared_ptr<Recorder> retired;
|
|
20
|
+
{
|
|
21
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
22
|
+
retired = std::move(_recorder);
|
|
23
|
+
_recorder = std::move(recorder);
|
|
24
|
+
_picture = std::move(picture);
|
|
25
|
+
}
|
|
26
|
+
retired = nullptr;
|
|
27
|
+
_requestRedraw();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
bool RNSkPictureRenderer::applyUpdatesTo(
|
|
31
|
+
const std::shared_ptr<Recorder> &recorder, jsi::Runtime &runtime,
|
|
32
|
+
double recorderId, const jsi::Array &values) {
|
|
33
|
+
if (recorder == nullptr || recorder->id != recorderId) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
recorder->applyUpdates(runtime, values);
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
bool RNSkPictureRenderer::applyUpdates(jsi::Runtime &runtime, double recorderId,
|
|
41
|
+
const jsi::Array &values) {
|
|
42
|
+
std::shared_ptr<Recorder> recorder;
|
|
43
|
+
{
|
|
44
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
45
|
+
recorder = _recorder;
|
|
46
|
+
}
|
|
47
|
+
// Outside the renderer lock: a commit replacing the recorder must not wait
|
|
48
|
+
// for the update. Should it land while this runs, the update goes into the
|
|
49
|
+
// retired recorder and the redraw that follows draws the new one.
|
|
50
|
+
return applyUpdatesTo(recorder, runtime, recorderId, values);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
void RNSkPictureRenderer::replay(SkCanvas *canvas, Recorder *recorder) {
|
|
54
|
+
DrawingCtx ctx(canvas);
|
|
55
|
+
recorder->play(&ctx);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
} // namespace RNSkia
|