@shopify/react-native-skia 2.11.2 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Package.swift +117 -0
- package/android/CMakeLists.txt +1 -2
- package/cpp/api/JsiSkSVGFactory.h +4 -0
- package/cpp/jsi/JSICache.cpp +99 -0
- package/cpp/jsi/JSICache.h +90 -0
- package/cpp/jsi/NativeObject.h +30 -97
- package/cpp/jsi/ViewProperty.h +0 -24
- package/cpp/rnskia/RNDawnContext.h +2 -1
- package/cpp/rnskia/RNDawnWindowContext.h +22 -0
- package/cpp/rnskia/RNSkManager.cpp +0 -11
- package/cpp/skia/include/core/RasterContext.h +25 -0
- package/cpp/skia/include/core/SkCanvas.h +0 -1
- package/cpp/skia/include/core/SkContext.h +62 -0
- package/cpp/skia/include/core/SkContextOptions.h +15 -0
- package/cpp/skia/include/core/SkMilestone.h +1 -1
- package/cpp/skia/include/core/SkPicture.h +36 -18
- package/cpp/skia/include/core/SkRecorder.h +8 -0
- package/cpp/skia/include/core/SkSerialProcs.h +2 -6
- package/cpp/skia/include/effects/SkImageFilters.h +15 -2
- package/cpp/skia/include/gpu/ganesh/GrDirectContext.h +73 -47
- package/cpp/skia/include/gpu/ganesh/SkSurfaceGanesh.h +7 -4
- package/cpp/skia/include/gpu/ganesh/d3d/GrD3DDirectContext.h +12 -0
- package/cpp/skia/include/gpu/ganesh/gl/GrGLDirectContext.h +11 -0
- package/cpp/skia/include/gpu/ganesh/mtl/GrMtlDirectContext.h +11 -0
- package/cpp/skia/include/gpu/ganesh/vk/GrVkDirectContext.h +13 -0
- package/cpp/skia/include/gpu/graphite/ContextOptions.h +6 -0
- package/cpp/skia/include/gpu/graphite/GraphiteTypes.h +5 -4
- package/cpp/skia/include/gpu/graphite/PrecompileContext.h +9 -1
- package/cpp/skia/include/gpu/graphite/Recorder.h +0 -2
- package/cpp/skia/include/gpu/graphite/Recording.h +3 -0
- package/cpp/skia/include/gpu/graphite/dawn/DawnBackendContext.h +8 -0
- package/cpp/skia/include/gpu/graphite/mtl/MtlBackendContext.h +11 -0
- package/cpp/skia/include/gpu/graphite/vk/VulkanGraphiteContext.h +11 -0
- package/cpp/skia/include/private/SkLog.h +1 -19
- package/cpp/skia/include/private/chromium/GrVkSecondaryCBDrawContext.h +2 -1
- package/cpp/skia/include/utils/SkLogHandler.h +53 -0
- package/cpp/skia/include/utils/SkNWayCanvas.h +1 -0
- package/cpp/skia/modules/skcms/src/Transform_inl.h +9 -10
- package/cpp/skia/modules/skcms/src/skcms_internals.h +8 -3
- package/cpp/skia/modules/skottie/include/SkottieProperty.h +2 -0
- package/cpp/skia/modules/skottie/include/TextShaper.h +1 -0
- package/cpp/skia/modules/skshaper/include/SkShaper.h +23 -1
- package/include/ReactNativeSkia.h +6 -0
- package/lib/commonjs/skia/web/JsiSkCanvas.js +4 -4
- package/lib/commonjs/skia/web/JsiSkCanvas.js.map +1 -1
- package/lib/commonjs/skia/web/JsiSkImage.js +6 -3
- package/lib/commonjs/skia/web/JsiSkImage.js.map +1 -1
- package/lib/commonjs/skia/web/JsiSkPathBuilder.d.ts +1 -1
- package/lib/commonjs/skia/web/JsiSkPathBuilder.js +2 -2
- package/lib/commonjs/skia/web/JsiSkPathBuilder.js.map +1 -1
- package/lib/commonjs/skia/web/JsiSkTextStyle.js +5 -5
- package/lib/commonjs/skia/web/JsiSkTextStyle.js.map +1 -1
- package/lib/commonjs/skia/web/JsiSkottieAnimation.js +8 -8
- package/lib/commonjs/skia/web/JsiSkottieAnimation.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/commands/Drawing.js +2 -1
- package/lib/commonjs/sksg/Recorder/commands/Drawing.js.map +1 -1
- package/lib/commonjs/views/SkiaPictureView.web.js +228 -43
- package/lib/commonjs/views/SkiaPictureView.web.js.map +1 -1
- package/lib/module/skia/web/JsiSkCanvas.js +4 -4
- package/lib/module/skia/web/JsiSkCanvas.js.map +1 -1
- package/lib/module/skia/web/JsiSkImage.js +6 -3
- package/lib/module/skia/web/JsiSkImage.js.map +1 -1
- package/lib/module/skia/web/JsiSkPathBuilder.d.ts +1 -1
- package/lib/module/skia/web/JsiSkPathBuilder.js +2 -2
- package/lib/module/skia/web/JsiSkPathBuilder.js.map +1 -1
- package/lib/module/skia/web/JsiSkTextStyle.js +5 -5
- package/lib/module/skia/web/JsiSkTextStyle.js.map +1 -1
- package/lib/module/skia/web/JsiSkottieAnimation.js +8 -8
- package/lib/module/skia/web/JsiSkottieAnimation.js.map +1 -1
- package/lib/module/sksg/Recorder/commands/Drawing.js +2 -1
- package/lib/module/sksg/Recorder/commands/Drawing.js.map +1 -1
- package/lib/module/views/SkiaPictureView.web.js +228 -43
- package/lib/module/views/SkiaPictureView.web.js.map +1 -1
- package/lib/typescript/lib/commonjs/skia/web/JsiSkPathBuilder.d.ts +1 -1
- package/lib/typescript/lib/module/skia/web/JsiSkPathBuilder.d.ts +1 -1
- package/lib/typescript/react-native.config.d.ts +3 -0
- package/lib/typescript/src/skia/web/JsiSkPathBuilder.d.ts +1 -1
- package/package.json +12 -9
- package/react-native.config.js +5 -0
- package/src/skia/web/JsiSkCanvas.ts +11 -6
- package/src/skia/web/JsiSkImage.ts +6 -3
- package/src/skia/web/JsiSkPathBuilder.ts +2 -2
- package/src/skia/web/JsiSkTextStyle.ts +20 -15
- package/src/skia/web/JsiSkottieAnimation.ts +8 -8
- package/src/sksg/Recorder/commands/Drawing.ts +2 -1
- package/src/views/SkiaPictureView.web.tsx +228 -43
- package/cpp/jsi/RuntimeAwareCache.cpp +0 -8
- package/cpp/jsi/RuntimeAwareCache.h +0 -146
- package/cpp/jsi/RuntimeLifecycleMonitor.cpp +0 -72
- package/cpp/jsi/RuntimeLifecycleMonitor.h +0 -32
package/Package.swift
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// swift-tools-version: 6.0
|
|
2
|
+
//
|
|
3
|
+
// SwiftPM manifest for @shopify/react-native-skia. iOS, Ganesh.
|
|
4
|
+
// Additive: the CocoaPods podspec remains the supported default.
|
|
5
|
+
//
|
|
6
|
+
// React Native 0.87+ references a library that ships its own Package.swift
|
|
7
|
+
// through a symlink at <app>/ios/build/generated/autolinking/libs/<name>, and
|
|
8
|
+
// resolves the relative paths below against that symlink. They are the same
|
|
9
|
+
// for every standard app, because the symlink location is.
|
|
10
|
+
|
|
11
|
+
import Foundation
|
|
12
|
+
import PackageDescription
|
|
13
|
+
|
|
14
|
+
// Skia's xcframeworks are ~200MB and are not carried in this package. They come
|
|
15
|
+
// from the react-native-skia-apple-ios npm package, which @shopify/react-native-skia
|
|
16
|
+
// depends on, so a checkout that has installed its dependencies builds offline.
|
|
17
|
+
// Xcode passes the symlink as the package directory, hence resolvingSymlinks.
|
|
18
|
+
let packageRoot = URL(fileURLWithPath: Context.packageDirectory)
|
|
19
|
+
.resolvingSymlinksInPath().path
|
|
20
|
+
|
|
21
|
+
let binariesPath = [
|
|
22
|
+
"../../react-native-skia-apple-ios", // consumer: sibling in node_modules
|
|
23
|
+
"../../node_modules/react-native-skia-apple-ios", // this monorepo
|
|
24
|
+
]
|
|
25
|
+
.map { "\(packageRoot)/\($0)" }
|
|
26
|
+
.first { FileManager.default.fileExists(atPath: "\($0)/Package.swift") }
|
|
27
|
+
|
|
28
|
+
guard let binariesPath else {
|
|
29
|
+
// Reached when the package was skipped at install time (`--omit=optional`,
|
|
30
|
+
// or a non-Apple `os` filter), which SwiftPM would otherwise report as an
|
|
31
|
+
// unresolvable dependency path.
|
|
32
|
+
fatalError(
|
|
33
|
+
"""
|
|
34
|
+
react-native-skia-apple-ios was not found next to @shopify/react-native-skia. \
|
|
35
|
+
It ships the prebuilt Skia binaries this package links against. Reinstall \
|
|
36
|
+
dependencies without --omit=optional, on macOS.
|
|
37
|
+
""")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let package = Package(
|
|
41
|
+
name: "ReactNativeSkia",
|
|
42
|
+
platforms: [.iOS(.v15)],
|
|
43
|
+
products: [
|
|
44
|
+
// Autolinking looks this name up verbatim; react-native.config.js pins it.
|
|
45
|
+
.library(name: "ReactNativeSkia", targets: ["ReactNativeSkia"])
|
|
46
|
+
],
|
|
47
|
+
dependencies: [
|
|
48
|
+
.package(name: "React-GeneratedCode", path: "../../../ios"),
|
|
49
|
+
.package(name: "ReactNative", path: "../../../../xcframeworks"),
|
|
50
|
+
.package(path: binariesPath),
|
|
51
|
+
],
|
|
52
|
+
targets: [
|
|
53
|
+
.target(
|
|
54
|
+
name: "ReactNativeSkia",
|
|
55
|
+
dependencies: [
|
|
56
|
+
.product(name: "ReactHeaders", package: "ReactNative"),
|
|
57
|
+
.product(name: "ReactNativeHeaders", package: "ReactNative"),
|
|
58
|
+
.product(name: "ReactNativeDependenciesHeaders", package: "ReactNative"),
|
|
59
|
+
.product(name: "ReactAppHeaders", package: "React-GeneratedCode"),
|
|
60
|
+
.product(name: "react-native-skia-apple-ios", package: "react-native-skia-apple-ios"),
|
|
61
|
+
],
|
|
62
|
+
// apple/ and cpp/ have no common ancestor below the package root, and
|
|
63
|
+
// .headerSearchPath cannot escape the target path.
|
|
64
|
+
path: ".",
|
|
65
|
+
exclude: [
|
|
66
|
+
"cpp/rnskia/RNDawnWindowContext.cpp", // Graphite only
|
|
67
|
+
"cpp/rnskia/RNDawnInterop.cpp", // Graphite only
|
|
68
|
+
],
|
|
69
|
+
// Explicit, so SwiftPM never walks node_modules, lib, android, or the
|
|
70
|
+
// headers-only cpp/skia — which carries x86 AVX skcms sources that
|
|
71
|
+
// cannot build for arm64.
|
|
72
|
+
sources: [
|
|
73
|
+
"apple",
|
|
74
|
+
"cpp/api",
|
|
75
|
+
"cpp/jsi",
|
|
76
|
+
"cpp/rnskia",
|
|
77
|
+
"cpp/utils",
|
|
78
|
+
// libskottie.a needs skjson and Apple builds no libskjson archive.
|
|
79
|
+
// CocoaPods compiles this via its cpp/**/*.cpp glob.
|
|
80
|
+
"cpp/skia/modules/jsonreader/SkJSONReader.cpp",
|
|
81
|
+
],
|
|
82
|
+
cxxSettings: [
|
|
83
|
+
// Replaces the podspec's recursive cpp/** glob, which expands to 90.
|
|
84
|
+
.headerSearchPath("cpp"), // "api/…", "jsi/…", "utils/…"
|
|
85
|
+
.headerSearchPath("cpp/skia"), // "include/core/…", "modules/…", "src/…"
|
|
86
|
+
.headerSearchPath("cpp/rnskia"), // apple/ uses bare "RNSkView.h"
|
|
87
|
+
.headerSearchPath("cpp/utils"), // apple/ uses bare "RNSkLog.h"
|
|
88
|
+
|
|
89
|
+
// CocoaPods forces both project-wide; the SwiftPM path defines neither.
|
|
90
|
+
// Skia's Apple sources still gate on them: without them RNSkiaModule's
|
|
91
|
+
// legacy branch fails to compile and -getTurboModule: is dropped, so
|
|
92
|
+
// the JSI bindings never install.
|
|
93
|
+
.define("RCT_NEW_ARCH_ENABLED", to: "1"),
|
|
94
|
+
.define("RCT_REMOVE_LEGACY_ARCH", to: "1"),
|
|
95
|
+
|
|
96
|
+
.define("SK_METAL", to: "1"),
|
|
97
|
+
.define("SK_GANESH", to: "1"),
|
|
98
|
+
.define("SK_IMAGE_READ_PIXELS_DISABLE_LEGACY_API", to: "1"),
|
|
99
|
+
.define("SK_DISABLE_LEGACY_SHAPER_FACTORY", to: "1"),
|
|
100
|
+
|
|
101
|
+
// React's prebuilt C++ ABI is NDEBUG-gated, and Skia derives
|
|
102
|
+
// SK_RELEASE from it. Omitting either breaks the Release link.
|
|
103
|
+
.define("DEBUG", .when(configuration: .debug)),
|
|
104
|
+
.define("NDEBUG", .when(configuration: .release)),
|
|
105
|
+
],
|
|
106
|
+
linkerSettings: [
|
|
107
|
+
.linkedFramework("MetalKit"),
|
|
108
|
+
.linkedFramework("AVFoundation"),
|
|
109
|
+
.linkedFramework("AVKit"),
|
|
110
|
+
.linkedFramework("CoreMedia"),
|
|
111
|
+
]
|
|
112
|
+
)
|
|
113
|
+
],
|
|
114
|
+
// React Native's headers need C++20, and so does Skia m152: SkMathPriv.h
|
|
115
|
+
// calls std::countl_zero, std::countr_zero and std::popcount.
|
|
116
|
+
cxxLanguageStandard: .cxx20
|
|
117
|
+
)
|
package/android/CMakeLists.txt
CHANGED
|
@@ -100,8 +100,7 @@ add_library(
|
|
|
100
100
|
"${PROJECT_SOURCE_DIR}/cpp/rnskia-android/AHardwareBufferUtils.cpp"
|
|
101
101
|
"${PROJECT_SOURCE_DIR}/cpp/rnskia-android/RNSkAndroidVideo.cpp"
|
|
102
102
|
|
|
103
|
-
"${PROJECT_SOURCE_DIR}/../cpp/jsi/
|
|
104
|
-
"${PROJECT_SOURCE_DIR}/../cpp/jsi/RuntimeAwareCache.cpp"
|
|
103
|
+
"${PROJECT_SOURCE_DIR}/../cpp/jsi/JSICache.cpp"
|
|
105
104
|
"${PROJECT_SOURCE_DIR}/../cpp/jsi/JsiPromises.cpp"
|
|
106
105
|
"${PROJECT_SOURCE_DIR}/../cpp/jsi/Promise.cpp"
|
|
107
106
|
|
|
@@ -116,6 +116,10 @@ private:
|
|
|
116
116
|
builder.setResourceProvider(provider);
|
|
117
117
|
|
|
118
118
|
auto svg_dom = builder.make(*stream);
|
|
119
|
+
if (!svg_dom) {
|
|
120
|
+
return jsi::Value::null();
|
|
121
|
+
}
|
|
122
|
+
|
|
119
123
|
return makeJsiObject(
|
|
120
124
|
runtime, std::make_shared<JsiSkSVG>(getContext(), std::move(svg_dom)));
|
|
121
125
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#include "JSICache.h"
|
|
2
|
+
|
|
3
|
+
#include <iterator>
|
|
4
|
+
#include <memory>
|
|
5
|
+
#include <stdexcept>
|
|
6
|
+
#include <string>
|
|
7
|
+
#include <unordered_map>
|
|
8
|
+
#include <utility>
|
|
9
|
+
|
|
10
|
+
namespace RNJsi {
|
|
11
|
+
|
|
12
|
+
namespace {
|
|
13
|
+
// Hidden global holding the per-runtime cache object. Named so it is
|
|
14
|
+
// obviously ours if it ever shows up in a debugger or heap snapshot.
|
|
15
|
+
constexpr const char *kGlobalName = "__rnskiaJsiCache";
|
|
16
|
+
} // namespace
|
|
17
|
+
|
|
18
|
+
std::mutex JSICache::_registryMutex;
|
|
19
|
+
std::unordered_map<jsi::Runtime *, std::weak_ptr<JSICache>> JSICache::_registry;
|
|
20
|
+
|
|
21
|
+
JSICache &JSICache::get(jsi::Runtime &runtime) {
|
|
22
|
+
{
|
|
23
|
+
std::lock_guard<std::mutex> lock(_registryMutex);
|
|
24
|
+
auto it = _registry.find(&runtime);
|
|
25
|
+
if (it != _registry.end()) {
|
|
26
|
+
if (auto cache = it->second.lock()) {
|
|
27
|
+
// The cache is kept alive by `runtime`'s global, so the raw
|
|
28
|
+
// reference stays valid for as long as the caller may legally use
|
|
29
|
+
// `runtime` on this thread.
|
|
30
|
+
return *cache;
|
|
31
|
+
}
|
|
32
|
+
// Expired: the runtime that owned this entry is gone and `&runtime` is
|
|
33
|
+
// a new runtime reusing the address. Fall through and recreate.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
auto cache = getOrCreateOnGlobal(runtime);
|
|
37
|
+
{
|
|
38
|
+
std::lock_guard<std::mutex> lock(_registryMutex);
|
|
39
|
+
// Drop entries of runtimes that are gone so the map does not grow by one
|
|
40
|
+
// stale pointer per reload / worklet runtime.
|
|
41
|
+
for (auto it = _registry.begin(); it != _registry.end();) {
|
|
42
|
+
it = it->second.expired() ? _registry.erase(it) : std::next(it);
|
|
43
|
+
}
|
|
44
|
+
_registry[&runtime] = cache;
|
|
45
|
+
}
|
|
46
|
+
return *cache;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
std::shared_ptr<JSICache> JSICache::getOrCreateOnGlobal(jsi::Runtime &runtime) {
|
|
50
|
+
auto global = runtime.global();
|
|
51
|
+
auto existing = global.getProperty(runtime, kGlobalName);
|
|
52
|
+
if (existing.isObject()) {
|
|
53
|
+
auto obj = existing.getObject(runtime);
|
|
54
|
+
if (obj.hasNativeState<JSICache>(runtime)) {
|
|
55
|
+
return obj.getNativeState<JSICache>(runtime);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!existing.isUndefined()) {
|
|
59
|
+
// Something else already claimed our global. The most likely cause is two
|
|
60
|
+
// copies of react-native-skia loaded in one process (each has its own
|
|
61
|
+
// JSICache type, so the NativeState check above fails). Say so instead of
|
|
62
|
+
// letting Object.defineProperty throw a bare TypeError on the
|
|
63
|
+
// non-configurable property.
|
|
64
|
+
throw std::runtime_error(
|
|
65
|
+
std::string("react-native-skia: global.") + kGlobalName +
|
|
66
|
+
" already exists but is not our JSICache. Is react-native-skia "
|
|
67
|
+
"linked twice, or is another module using this global?");
|
|
68
|
+
}
|
|
69
|
+
auto cache = std::make_shared<JSICache>();
|
|
70
|
+
jsi::Object holder(runtime);
|
|
71
|
+
holder.setNativeState(runtime, cache);
|
|
72
|
+
// Non-enumerable, non-writable, non-configurable: JS code must not be able
|
|
73
|
+
// to drop the holder (which would finalize the cache under our feet).
|
|
74
|
+
auto objectCtor = global.getPropertyAsObject(runtime, "Object");
|
|
75
|
+
auto defineProperty =
|
|
76
|
+
objectCtor.getPropertyAsFunction(runtime, "defineProperty");
|
|
77
|
+
jsi::Object descriptor(runtime);
|
|
78
|
+
descriptor.setProperty(runtime, "value", std::move(holder));
|
|
79
|
+
descriptor.setProperty(runtime, "writable", false);
|
|
80
|
+
descriptor.setProperty(runtime, "enumerable", false);
|
|
81
|
+
descriptor.setProperty(runtime, "configurable", false);
|
|
82
|
+
defineProperty.call(runtime, global,
|
|
83
|
+
jsi::String::createFromAscii(runtime, kGlobalName),
|
|
84
|
+
descriptor);
|
|
85
|
+
return cache;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
jsi::Object *JSICache::getPrototype(PrototypeKey key) {
|
|
89
|
+
auto it = _prototypes.find(key);
|
|
90
|
+
return it == _prototypes.end() ? nullptr : &it->second;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
jsi::Object &JSICache::setPrototype(PrototypeKey key, jsi::Object prototype) {
|
|
94
|
+
auto [it, inserted] = _prototypes.insert_or_assign(key, std::move(prototype));
|
|
95
|
+
(void)inserted;
|
|
96
|
+
return it->second;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
} // namespace RNJsi
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <jsi/jsi.h>
|
|
4
|
+
|
|
5
|
+
#include <memory>
|
|
6
|
+
#include <mutex>
|
|
7
|
+
#include <typeindex>
|
|
8
|
+
#include <unordered_map>
|
|
9
|
+
|
|
10
|
+
namespace RNJsi {
|
|
11
|
+
|
|
12
|
+
namespace jsi = facebook::jsi;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Per-runtime store for JSI values that native code wants to keep across
|
|
16
|
+
* calls (currently: the prototype object of every NativeObject class).
|
|
17
|
+
*
|
|
18
|
+
* Ownership model (borrowed from react-native-nitro-modules' JSICache):
|
|
19
|
+
*
|
|
20
|
+
* - One JSICache exists per jsi::Runtime. It is attached as the NativeState
|
|
21
|
+
* of a hidden object stored on that runtime's `global`, so the RUNTIME owns
|
|
22
|
+
* the cache. When the runtime is destroyed the engine finalizes the object,
|
|
23
|
+
* ~JSICache runs while the runtime is still alive, and every cached
|
|
24
|
+
* jsi::Object is released correctly. Nothing outlives its runtime and
|
|
25
|
+
* nothing has to be leaked.
|
|
26
|
+
*
|
|
27
|
+
* - Native code reaches the cache through JSICache::get(runtime). A static
|
|
28
|
+
* map of std::weak_ptr, keyed by jsi::Runtime*, makes the lookup cheap; the
|
|
29
|
+
* pointer is only a map key, never proof that a runtime is alive. If the
|
|
30
|
+
* weak_ptr is expired (the runtime that owned it is gone) the entry is
|
|
31
|
+
* recreated on the runtime asking for it. This is what makes an in-process
|
|
32
|
+
* runtime recreate (expo-updates OTA apply, DevSettings.reload) safe even
|
|
33
|
+
* when the new runtime is allocated at the address of the freed one: the
|
|
34
|
+
* old cache was destroyed with the old runtime, so the reused address can
|
|
35
|
+
* only ever resolve to a fresh cache.
|
|
36
|
+
*
|
|
37
|
+
* - The cache itself is not locked: a jsi::Runtime is single-threaded, and
|
|
38
|
+
* the cache is only ever touched from its runtime's thread. Only the static
|
|
39
|
+
* runtime -> cache map is guarded.
|
|
40
|
+
*
|
|
41
|
+
* The same mechanism works for worklet runtimes (Reanimated UI runtime,
|
|
42
|
+
* createWorkletRuntime, ...); each gets its own JSICache.
|
|
43
|
+
*/
|
|
44
|
+
class JSICache final : public jsi::NativeState {
|
|
45
|
+
public:
|
|
46
|
+
/**
|
|
47
|
+
* Key identifying one cached prototype: the C++ type of the NativeObject
|
|
48
|
+
* subclass (std::type_index(typeid(Derived))), as in Nitro. It prints as
|
|
49
|
+
* the class name in a debugger.
|
|
50
|
+
*/
|
|
51
|
+
using PrototypeKey = std::type_index;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Returns the cache owned by `runtime`, creating it on first use. Must be
|
|
55
|
+
* called on the runtime's thread.
|
|
56
|
+
*/
|
|
57
|
+
static JSICache &get(jsi::Runtime &runtime);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Returns the cached prototype for `key` or nullptr if none was stored.
|
|
61
|
+
*
|
|
62
|
+
* The pointer points into `_prototypes`. std::unordered_map never
|
|
63
|
+
* invalidates pointers/references to elements on insert or rehash (only on
|
|
64
|
+
* erase of that element, which we never do), so it stays valid for as long
|
|
65
|
+
* as the runtime does. Do not swap the container for one without that
|
|
66
|
+
* guarantee (std::vector, absl::flat_hash_map, ...).
|
|
67
|
+
*/
|
|
68
|
+
jsi::Object *getPrototype(PrototypeKey key);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Stores (or replaces) the prototype for `key` and returns it.
|
|
72
|
+
*/
|
|
73
|
+
jsi::Object &setPrototype(PrototypeKey key, jsi::Object prototype);
|
|
74
|
+
|
|
75
|
+
JSICache() = default;
|
|
76
|
+
~JSICache() override = default;
|
|
77
|
+
JSICache(const JSICache &) = delete;
|
|
78
|
+
JSICache &operator=(const JSICache &) = delete;
|
|
79
|
+
|
|
80
|
+
private:
|
|
81
|
+
static std::shared_ptr<JSICache> getOrCreateOnGlobal(jsi::Runtime &runtime);
|
|
82
|
+
|
|
83
|
+
std::unordered_map<PrototypeKey, jsi::Object> _prototypes;
|
|
84
|
+
|
|
85
|
+
// Runtime -> cache. Weak on purpose: the runtime's global owns the cache.
|
|
86
|
+
static std::mutex _registryMutex;
|
|
87
|
+
static std::unordered_map<jsi::Runtime *, std::weak_ptr<JSICache>> _registry;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
} // namespace RNJsi
|
package/cpp/jsi/NativeObject.h
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
#include <jsi/jsi.h>
|
|
9
9
|
#include <memory>
|
|
10
10
|
#include <mutex>
|
|
11
|
-
#include <optional>
|
|
12
11
|
#include <string>
|
|
13
12
|
#include <type_traits>
|
|
13
|
+
#include <typeindex>
|
|
14
14
|
#include <unordered_map>
|
|
15
15
|
#include <utility>
|
|
16
16
|
|
|
17
17
|
#include "jsi/BoxedNativeObject.h"
|
|
18
|
-
#include "jsi/
|
|
18
|
+
#include "jsi/JSICache.h"
|
|
19
19
|
|
|
20
20
|
// Forward declare to avoid circular dependency
|
|
21
21
|
namespace RNJsi {
|
|
@@ -37,51 +37,6 @@ static constexpr size_t kMinMemoryPressure = 256;
|
|
|
37
37
|
// Forward declaration
|
|
38
38
|
template <typename Derived> class NativeObject;
|
|
39
39
|
|
|
40
|
-
/**
|
|
41
|
-
* Per-runtime cache entry for a prototype object.
|
|
42
|
-
* Uses std::optional<jsi::Object> so the prototype is stored directly
|
|
43
|
-
* without extra indirection.
|
|
44
|
-
*/
|
|
45
|
-
struct PrototypeCacheEntry {
|
|
46
|
-
std::optional<jsi::Object> prototype;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Wrapper for static RuntimeAwareCache that handles hot reload.
|
|
51
|
-
*
|
|
52
|
-
* When used with static storage (like prototype caches), the cache persists
|
|
53
|
-
* across hot reloads. But the JSI objects inside become invalid when the
|
|
54
|
-
* runtime is destroyed. This wrapper tracks which main-runtime generation the
|
|
55
|
-
* cache was created for and allocates a new cache when the native module is
|
|
56
|
-
* reinstalled (RNSkManager construction bumps the generation).
|
|
57
|
-
*
|
|
58
|
-
* The generation is used instead of the runtime pointer on purpose: on an
|
|
59
|
-
* in-process runtime recreate (expo-updates reloadAsync, DevSettings.reload)
|
|
60
|
-
* Hermes frequently allocates the new runtime at the address of the freed
|
|
61
|
-
* one. A pointer comparison then sees "same runtime", keeps the stale cache,
|
|
62
|
-
* and create()/installConstructor() end up passing a jsi::Object owned by the
|
|
63
|
-
* dead runtime to the new one (use-after-free).
|
|
64
|
-
*
|
|
65
|
-
* The old cache is intentionally leaked - we cannot safely destroy JSI
|
|
66
|
-
* objects after their runtime is gone.
|
|
67
|
-
*/
|
|
68
|
-
template <typename T> struct StaticRuntimeAwareCache {
|
|
69
|
-
RNJsi::RuntimeAwareCache<T> *cache = nullptr;
|
|
70
|
-
uint64_t cacheGeneration = 0;
|
|
71
|
-
|
|
72
|
-
RNJsi::RuntimeAwareCache<T> &get(jsi::Runtime & /*rt*/) {
|
|
73
|
-
auto generation =
|
|
74
|
-
RNJsi::BaseRuntimeAwareCache::getMainJsRuntimeGeneration();
|
|
75
|
-
if (cache == nullptr || cacheGeneration != generation) {
|
|
76
|
-
// First use, or the main runtime was reinstalled (hot reload / OTA):
|
|
77
|
-
// allocate a fresh cache and leak the old one.
|
|
78
|
-
cache = new RNJsi::RuntimeAwareCache<T>();
|
|
79
|
-
cacheGeneration = generation;
|
|
80
|
-
}
|
|
81
|
-
return *cache;
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
|
|
85
40
|
/**
|
|
86
41
|
* Base class for native objects using the NativeState pattern.
|
|
87
42
|
*
|
|
@@ -118,29 +73,20 @@ public:
|
|
|
118
73
|
using IsNativeObject = std::true_type;
|
|
119
74
|
|
|
120
75
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* Uses StaticRuntimeAwareCache to properly handle runtime lifecycle
|
|
124
|
-
* and hot reload (where the main runtime is destroyed and recreated).
|
|
125
|
-
*
|
|
126
|
-
* Callers must hold getPrototypeCacheMutex(): the cache is reached
|
|
127
|
-
* concurrently from the main JS thread (create()) and from worklet
|
|
128
|
-
* runtime threads (BoxedNativeObject::unbox() -> installPrototype()).
|
|
76
|
+
* Key under which this class's prototype is stored in the per-runtime
|
|
77
|
+
* JSICache: the C++ type of Derived.
|
|
129
78
|
*/
|
|
130
|
-
static
|
|
131
|
-
|
|
132
|
-
static StaticRuntimeAwareCache<PrototypeCacheEntry> cache;
|
|
133
|
-
return cache.get(runtime);
|
|
79
|
+
static JSICache::PrototypeKey prototypeKey() {
|
|
80
|
+
return std::type_index(typeid(Derived));
|
|
134
81
|
}
|
|
135
82
|
|
|
136
83
|
/**
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
84
|
+
* Returns this class's prototype on `runtime`, or nullptr if it has not
|
|
85
|
+
* been installed there yet. The prototype is owned by the runtime (see
|
|
86
|
+
* JSICache), so the pointer is valid for as long as `runtime` is.
|
|
140
87
|
*/
|
|
141
|
-
static
|
|
142
|
-
|
|
143
|
-
return mutex;
|
|
88
|
+
static jsi::Object *getCachedPrototype(jsi::Runtime &runtime) {
|
|
89
|
+
return JSICache::get(runtime).getPrototype(prototypeKey());
|
|
144
90
|
}
|
|
145
91
|
|
|
146
92
|
/**
|
|
@@ -148,30 +94,21 @@ public:
|
|
|
148
94
|
* Called automatically by create(), but can be called manually.
|
|
149
95
|
*/
|
|
150
96
|
static void installPrototype(jsi::Runtime &runtime) {
|
|
151
|
-
|
|
152
|
-
ensurePrototypeLocked(runtime);
|
|
97
|
+
ensurePrototype(runtime);
|
|
153
98
|
}
|
|
154
99
|
|
|
155
100
|
/**
|
|
156
|
-
* Look up (and install on first use) the prototype
|
|
157
|
-
*
|
|
158
|
-
* Callers must hold getPrototypeCacheMutex() and must consume the returned
|
|
159
|
-
* entry while still holding it: the lookup and the use of the prototype
|
|
160
|
-
* have to share one lock scope, because a reinstall of the native module
|
|
161
|
-
* (generation bump, see StaticRuntimeAwareCache) can swap the cache from
|
|
162
|
-
* any thread. A caller that released the lock in between would re-resolve
|
|
163
|
-
* a fresh cache with an empty entry and hand out an object without a
|
|
164
|
-
* prototype.
|
|
101
|
+
* Look up (and install on first use) the prototype for this runtime.
|
|
165
102
|
*
|
|
166
|
-
* The returned reference
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
103
|
+
* The returned reference is owned by the runtime's JSICache and stays
|
|
104
|
+
* valid for as long as the runtime does (std::unordered_map keeps
|
|
105
|
+
* references to its elements stable across rehashes). No locking is
|
|
106
|
+
* needed: a jsi::Runtime is single-threaded and its cache is only ever
|
|
107
|
+
* touched from its own thread.
|
|
170
108
|
*/
|
|
171
|
-
static
|
|
172
|
-
auto
|
|
173
|
-
|
|
174
|
-
return entry; // Already installed
|
|
109
|
+
static jsi::Object &ensurePrototype(jsi::Runtime &runtime) {
|
|
110
|
+
if (auto *cached = getCachedPrototype(runtime)) {
|
|
111
|
+
return *cached; // Already installed on this runtime
|
|
175
112
|
}
|
|
176
113
|
|
|
177
114
|
// Create prototype object
|
|
@@ -288,9 +225,9 @@ public:
|
|
|
288
225
|
});
|
|
289
226
|
});
|
|
290
227
|
|
|
291
|
-
//
|
|
292
|
-
|
|
293
|
-
|
|
228
|
+
// Hand the prototype to the runtime-owned cache
|
|
229
|
+
return JSICache::get(runtime).setPrototype(prototypeKey(),
|
|
230
|
+
std::move(prototype));
|
|
294
231
|
}
|
|
295
232
|
|
|
296
233
|
/**
|
|
@@ -301,8 +238,7 @@ public:
|
|
|
301
238
|
* created internally by the native code).
|
|
302
239
|
*/
|
|
303
240
|
static void installConstructor(jsi::Runtime &runtime) {
|
|
304
|
-
|
|
305
|
-
auto &entry = ensurePrototypeLocked(runtime);
|
|
241
|
+
auto &prototype = ensurePrototype(runtime);
|
|
306
242
|
|
|
307
243
|
// Create a constructor function that throws when called directly
|
|
308
244
|
auto ctor = jsi::Function::createFromHostFunction(
|
|
@@ -316,10 +252,10 @@ public:
|
|
|
316
252
|
|
|
317
253
|
// Set the prototype property on the constructor
|
|
318
254
|
// This is what makes `instanceof` work
|
|
319
|
-
ctor.setProperty(runtime, "prototype",
|
|
255
|
+
ctor.setProperty(runtime, "prototype", prototype);
|
|
320
256
|
|
|
321
257
|
// Set constructor property on prototype pointing back to constructor
|
|
322
|
-
|
|
258
|
+
prototype.setProperty(runtime, "constructor", ctor);
|
|
323
259
|
|
|
324
260
|
// Install on global
|
|
325
261
|
runtime.global().setProperty(runtime, Derived::CLASS_NAME, std::move(ctor));
|
|
@@ -339,16 +275,13 @@ public:
|
|
|
339
275
|
// Attach native state
|
|
340
276
|
obj.setNativeState(runtime, instance);
|
|
341
277
|
|
|
342
|
-
// Install (on first use) and apply the prototype
|
|
343
|
-
// so a concurrent cache swap cannot slip in between the two steps.
|
|
278
|
+
// Install (on first use) and apply the prototype
|
|
344
279
|
{
|
|
345
|
-
|
|
346
|
-
auto &entry = ensurePrototypeLocked(runtime);
|
|
347
|
-
// Use Object.setPrototypeOf to set the prototype
|
|
280
|
+
auto &prototype = ensurePrototype(runtime);
|
|
348
281
|
auto objectCtor = runtime.global().getPropertyAsObject(runtime, "Object");
|
|
349
282
|
auto setPrototypeOf =
|
|
350
283
|
objectCtor.getPropertyAsFunction(runtime, "setPrototypeOf");
|
|
351
|
-
setPrototypeOf.call(runtime, obj,
|
|
284
|
+
setPrototypeOf.call(runtime, obj, prototype);
|
|
352
285
|
}
|
|
353
286
|
|
|
354
287
|
// Set memory pressure hint for GC
|
package/cpp/jsi/ViewProperty.h
CHANGED
|
@@ -1,40 +1,16 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
-
#include <atomic>
|
|
4
3
|
#include <functional>
|
|
5
4
|
#include <jsi/jsi.h>
|
|
6
5
|
#include <memory>
|
|
7
6
|
#include <string>
|
|
8
7
|
#include <variant>
|
|
9
8
|
|
|
10
|
-
#include "RuntimeLifecycleMonitor.h"
|
|
11
9
|
#include "api/JsiSkPicture.h"
|
|
12
10
|
|
|
13
11
|
namespace RNJsi {
|
|
14
12
|
namespace jsi = facebook::jsi;
|
|
15
13
|
|
|
16
|
-
class RuntimeAwareRuntimeGuard : public RuntimeLifecycleListener {
|
|
17
|
-
public:
|
|
18
|
-
explicit RuntimeAwareRuntimeGuard(jsi::Runtime &runtime)
|
|
19
|
-
: _runtime(&runtime) {
|
|
20
|
-
RuntimeLifecycleMonitor::addListener(runtime, this);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
~RuntimeAwareRuntimeGuard() override {
|
|
24
|
-
auto runtime = _runtime.load();
|
|
25
|
-
if (runtime != nullptr) {
|
|
26
|
-
RuntimeLifecycleMonitor::removeListener(*runtime, this);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
void onRuntimeDestroyed(jsi::Runtime *) override { _runtime.store(nullptr); }
|
|
31
|
-
|
|
32
|
-
jsi::Runtime *getRuntime() const { return _runtime.load(); }
|
|
33
|
-
|
|
34
|
-
private:
|
|
35
|
-
std::atomic<jsi::Runtime *> _runtime;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
14
|
class ViewProperty {
|
|
39
15
|
public:
|
|
40
16
|
ViewProperty(jsi::Runtime &runtime, const jsi::Value &value) {
|
|
@@ -147,7 +147,8 @@ public:
|
|
|
147
147
|
vkBegin.newLayout = 0;
|
|
148
148
|
beginAccessDesc.nextInChain = &vkBegin;
|
|
149
149
|
#endif
|
|
150
|
-
bool success =
|
|
150
|
+
bool success =
|
|
151
|
+
memory.BeginAccess(texture, &beginAccessDesc) == wgpu::Status::Success;
|
|
151
152
|
|
|
152
153
|
if (success) {
|
|
153
154
|
skgpu::graphite::BackendTexture betFromView =
|
|
@@ -82,6 +82,7 @@ private:
|
|
|
82
82
|
config.width = _width;
|
|
83
83
|
config.height = _height;
|
|
84
84
|
config.presentMode = wgpu::PresentMode::Fifo;
|
|
85
|
+
config.usage = supportedSurfaceUsage();
|
|
85
86
|
#ifdef __APPLE__
|
|
86
87
|
config.alphaMode = wgpu::CompositeAlphaMode::Premultiplied;
|
|
87
88
|
#endif
|
|
@@ -93,6 +94,27 @@ private:
|
|
|
93
94
|
#endif
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
// Graphite needs more than RenderAttachment on the swapchain texture:
|
|
98
|
+
// TextureBinding so a render pass can reload the existing contents through
|
|
99
|
+
// LoadOp::ExpandResolveTexture (any backdrop filter or mid-frame readback
|
|
100
|
+
// splits the pass), and CopySrc for copy tasks. Only request what the
|
|
101
|
+
// surface reports as supported.
|
|
102
|
+
wgpu::TextureUsage supportedSurfaceUsage() {
|
|
103
|
+
wgpu::TextureUsage usage = wgpu::TextureUsage::RenderAttachment;
|
|
104
|
+
wgpu::SurfaceCapabilities capabilities;
|
|
105
|
+
if (_surface.GetCapabilities(_device.GetAdapter(), &capabilities) !=
|
|
106
|
+
wgpu::Status::Success) {
|
|
107
|
+
return usage;
|
|
108
|
+
}
|
|
109
|
+
for (auto extra :
|
|
110
|
+
{wgpu::TextureUsage::TextureBinding, wgpu::TextureUsage::CopySrc}) {
|
|
111
|
+
if (capabilities.usages & extra) {
|
|
112
|
+
usage |= extra;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return usage;
|
|
116
|
+
}
|
|
117
|
+
|
|
96
118
|
bool surfaceSupportsFormat(wgpu::TextureFormat format) {
|
|
97
119
|
wgpu::SurfaceCapabilities capabilities;
|
|
98
120
|
if (_surface.GetCapabilities(_device.GetAdapter(), &capabilities) !=
|
|
@@ -9,8 +9,6 @@
|
|
|
9
9
|
#include "RNSkView.h"
|
|
10
10
|
#include "api/JsiSkApi.h"
|
|
11
11
|
|
|
12
|
-
#include "jsi/RuntimeAwareCache.h"
|
|
13
|
-
|
|
14
12
|
namespace RNSkia {
|
|
15
13
|
namespace jsi = facebook::jsi;
|
|
16
14
|
|
|
@@ -21,20 +19,11 @@ RNSkManager::RNSkManager(
|
|
|
21
19
|
: _jsRuntime(jsRuntime), _platformContext(platformContext),
|
|
22
20
|
_jsCallInvoker(jsCallInvoker),
|
|
23
21
|
_viewApi(std::make_shared<RNSkJsiViewApi>(platformContext)) {
|
|
24
|
-
|
|
25
|
-
// Register main runtime
|
|
26
|
-
RNJsi::BaseRuntimeAwareCache::setMainJsRuntime(_jsRuntime);
|
|
27
|
-
|
|
28
22
|
// Install bindings
|
|
29
23
|
installBindings();
|
|
30
24
|
}
|
|
31
25
|
|
|
32
26
|
RNSkManager::~RNSkManager() {
|
|
33
|
-
// The main runtime is going away: key out the process-wide prototype
|
|
34
|
-
// caches now rather than at the next install(), so nothing allocated at
|
|
35
|
-
// this runtime's address in between can be mistaken for it.
|
|
36
|
-
RNJsi::BaseRuntimeAwareCache::invalidateMainJsRuntime();
|
|
37
|
-
|
|
38
27
|
// Free up any references
|
|
39
28
|
_viewApi = nullptr;
|
|
40
29
|
_jsRuntime = nullptr;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Google LLC
|
|
3
|
+
*
|
|
4
|
+
* Use of this source code is governed by a BSD-style license that can be
|
|
5
|
+
* found in the LICENSE file.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#ifndef RasterContext_DEFINED
|
|
9
|
+
#define RasterContext_DEFINED
|
|
10
|
+
|
|
11
|
+
#include "include/core/SkRefCnt.h"
|
|
12
|
+
|
|
13
|
+
#include <memory>
|
|
14
|
+
|
|
15
|
+
class SkContext;
|
|
16
|
+
struct SkContextOptions;
|
|
17
|
+
|
|
18
|
+
namespace SkContexts {
|
|
19
|
+
|
|
20
|
+
// Creates a context for SkContext with only Software Rasterization
|
|
21
|
+
std::unique_ptr<SkContext> MakeRaster(const SkContextOptions& options);
|
|
22
|
+
|
|
23
|
+
} // namespace SkContexts
|
|
24
|
+
|
|
25
|
+
#endif // RasterContext_DEFINED
|