@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.
Files changed (90) hide show
  1. package/Package.swift +117 -0
  2. package/android/CMakeLists.txt +1 -2
  3. package/cpp/api/JsiSkSVGFactory.h +4 -0
  4. package/cpp/jsi/JSICache.cpp +99 -0
  5. package/cpp/jsi/JSICache.h +90 -0
  6. package/cpp/jsi/NativeObject.h +30 -97
  7. package/cpp/jsi/ViewProperty.h +0 -24
  8. package/cpp/rnskia/RNDawnContext.h +2 -1
  9. package/cpp/rnskia/RNDawnWindowContext.h +22 -0
  10. package/cpp/rnskia/RNSkManager.cpp +0 -11
  11. package/cpp/skia/include/core/RasterContext.h +25 -0
  12. package/cpp/skia/include/core/SkCanvas.h +0 -1
  13. package/cpp/skia/include/core/SkContext.h +62 -0
  14. package/cpp/skia/include/core/SkContextOptions.h +15 -0
  15. package/cpp/skia/include/core/SkMilestone.h +1 -1
  16. package/cpp/skia/include/core/SkPicture.h +36 -18
  17. package/cpp/skia/include/core/SkRecorder.h +8 -0
  18. package/cpp/skia/include/core/SkSerialProcs.h +2 -6
  19. package/cpp/skia/include/effects/SkImageFilters.h +15 -2
  20. package/cpp/skia/include/gpu/ganesh/GrDirectContext.h +73 -47
  21. package/cpp/skia/include/gpu/ganesh/SkSurfaceGanesh.h +7 -4
  22. package/cpp/skia/include/gpu/ganesh/d3d/GrD3DDirectContext.h +12 -0
  23. package/cpp/skia/include/gpu/ganesh/gl/GrGLDirectContext.h +11 -0
  24. package/cpp/skia/include/gpu/ganesh/mtl/GrMtlDirectContext.h +11 -0
  25. package/cpp/skia/include/gpu/ganesh/vk/GrVkDirectContext.h +13 -0
  26. package/cpp/skia/include/gpu/graphite/ContextOptions.h +6 -0
  27. package/cpp/skia/include/gpu/graphite/GraphiteTypes.h +5 -4
  28. package/cpp/skia/include/gpu/graphite/PrecompileContext.h +9 -1
  29. package/cpp/skia/include/gpu/graphite/Recorder.h +0 -2
  30. package/cpp/skia/include/gpu/graphite/Recording.h +3 -0
  31. package/cpp/skia/include/gpu/graphite/dawn/DawnBackendContext.h +8 -0
  32. package/cpp/skia/include/gpu/graphite/mtl/MtlBackendContext.h +11 -0
  33. package/cpp/skia/include/gpu/graphite/vk/VulkanGraphiteContext.h +11 -0
  34. package/cpp/skia/include/private/SkLog.h +1 -19
  35. package/cpp/skia/include/private/chromium/GrVkSecondaryCBDrawContext.h +2 -1
  36. package/cpp/skia/include/utils/SkLogHandler.h +53 -0
  37. package/cpp/skia/include/utils/SkNWayCanvas.h +1 -0
  38. package/cpp/skia/modules/skcms/src/Transform_inl.h +9 -10
  39. package/cpp/skia/modules/skcms/src/skcms_internals.h +8 -3
  40. package/cpp/skia/modules/skottie/include/SkottieProperty.h +2 -0
  41. package/cpp/skia/modules/skottie/include/TextShaper.h +1 -0
  42. package/cpp/skia/modules/skshaper/include/SkShaper.h +23 -1
  43. package/include/ReactNativeSkia.h +6 -0
  44. package/lib/commonjs/skia/web/JsiSkCanvas.js +4 -4
  45. package/lib/commonjs/skia/web/JsiSkCanvas.js.map +1 -1
  46. package/lib/commonjs/skia/web/JsiSkImage.js +6 -3
  47. package/lib/commonjs/skia/web/JsiSkImage.js.map +1 -1
  48. package/lib/commonjs/skia/web/JsiSkPathBuilder.d.ts +1 -1
  49. package/lib/commonjs/skia/web/JsiSkPathBuilder.js +2 -2
  50. package/lib/commonjs/skia/web/JsiSkPathBuilder.js.map +1 -1
  51. package/lib/commonjs/skia/web/JsiSkTextStyle.js +5 -5
  52. package/lib/commonjs/skia/web/JsiSkTextStyle.js.map +1 -1
  53. package/lib/commonjs/skia/web/JsiSkottieAnimation.js +8 -8
  54. package/lib/commonjs/skia/web/JsiSkottieAnimation.js.map +1 -1
  55. package/lib/commonjs/sksg/Recorder/commands/Drawing.js +2 -1
  56. package/lib/commonjs/sksg/Recorder/commands/Drawing.js.map +1 -1
  57. package/lib/commonjs/views/SkiaPictureView.web.js +228 -43
  58. package/lib/commonjs/views/SkiaPictureView.web.js.map +1 -1
  59. package/lib/module/skia/web/JsiSkCanvas.js +4 -4
  60. package/lib/module/skia/web/JsiSkCanvas.js.map +1 -1
  61. package/lib/module/skia/web/JsiSkImage.js +6 -3
  62. package/lib/module/skia/web/JsiSkImage.js.map +1 -1
  63. package/lib/module/skia/web/JsiSkPathBuilder.d.ts +1 -1
  64. package/lib/module/skia/web/JsiSkPathBuilder.js +2 -2
  65. package/lib/module/skia/web/JsiSkPathBuilder.js.map +1 -1
  66. package/lib/module/skia/web/JsiSkTextStyle.js +5 -5
  67. package/lib/module/skia/web/JsiSkTextStyle.js.map +1 -1
  68. package/lib/module/skia/web/JsiSkottieAnimation.js +8 -8
  69. package/lib/module/skia/web/JsiSkottieAnimation.js.map +1 -1
  70. package/lib/module/sksg/Recorder/commands/Drawing.js +2 -1
  71. package/lib/module/sksg/Recorder/commands/Drawing.js.map +1 -1
  72. package/lib/module/views/SkiaPictureView.web.js +228 -43
  73. package/lib/module/views/SkiaPictureView.web.js.map +1 -1
  74. package/lib/typescript/lib/commonjs/skia/web/JsiSkPathBuilder.d.ts +1 -1
  75. package/lib/typescript/lib/module/skia/web/JsiSkPathBuilder.d.ts +1 -1
  76. package/lib/typescript/react-native.config.d.ts +3 -0
  77. package/lib/typescript/src/skia/web/JsiSkPathBuilder.d.ts +1 -1
  78. package/package.json +12 -9
  79. package/react-native.config.js +5 -0
  80. package/src/skia/web/JsiSkCanvas.ts +11 -6
  81. package/src/skia/web/JsiSkImage.ts +6 -3
  82. package/src/skia/web/JsiSkPathBuilder.ts +2 -2
  83. package/src/skia/web/JsiSkTextStyle.ts +20 -15
  84. package/src/skia/web/JsiSkottieAnimation.ts +8 -8
  85. package/src/sksg/Recorder/commands/Drawing.ts +2 -1
  86. package/src/views/SkiaPictureView.web.tsx +228 -43
  87. package/cpp/jsi/RuntimeAwareCache.cpp +0 -8
  88. package/cpp/jsi/RuntimeAwareCache.h +0 -146
  89. package/cpp/jsi/RuntimeLifecycleMonitor.cpp +0 -72
  90. 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
+ )
@@ -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/RuntimeLifecycleMonitor.cpp"
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
@@ -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/RuntimeAwareCache.h" // Use Skia's RuntimeAwareCache
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
- * Get the prototype cache for this type.
122
- * Each NativeObject<Derived> type has its own static cache.
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 RNJsi::RuntimeAwareCache<PrototypeCacheEntry> &
131
- getPrototypeCache(jsi::Runtime &runtime) {
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
- * Per-class mutex guarding getPrototypeCache() and prototype
138
- * installation. Serializes the StaticRuntimeAwareCache pointer swap
139
- * (hot reload) and the per-runtime cache lookups.
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 std::mutex &getPrototypeCacheMutex() {
142
- static std::mutex mutex;
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
- std::lock_guard<std::mutex> lock(getPrototypeCacheMutex());
152
- ensurePrototypeLocked(runtime);
97
+ ensurePrototype(runtime);
153
98
  }
154
99
 
155
100
  /**
156
- * Look up (and install on first use) the prototype entry for this runtime.
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 stays valid for the runtime's lifetime: the
167
- * RuntimeAwareCache is heap-allocated and never freed, and
168
- * std::unordered_map keeps references to its elements stable across
169
- * rehashes.
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 PrototypeCacheEntry &ensurePrototypeLocked(jsi::Runtime &runtime) {
172
- auto &entry = getPrototypeCache(runtime).get(runtime);
173
- if (entry.prototype.has_value()) {
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
- // Cache the prototype
292
- entry.prototype = std::move(prototype);
293
- return entry;
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
- std::lock_guard<std::mutex> lock(getPrototypeCacheMutex());
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", *entry.prototype);
255
+ ctor.setProperty(runtime, "prototype", prototype);
320
256
 
321
257
  // Set constructor property on prototype pointing back to constructor
322
- entry.prototype->setProperty(runtime, "constructor", ctor);
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 in a single lock scope
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
- std::lock_guard<std::mutex> lock(getPrototypeCacheMutex());
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, *entry.prototype);
284
+ setPrototypeOf.call(runtime, obj, prototype);
352
285
  }
353
286
 
354
287
  // Set memory pressure hint for GC
@@ -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 = memory.BeginAccess(texture, &beginAccessDesc);
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