Skip to content

Commit f4db5f5

Browse files
authored
fix(🖼️): fix thread safety in SkImage and viewRef.makeImageSnapshot (#2761)
Add thread boundaries on images that could be used in a different thread than the one they were created on. The solution is to use the graphic context available in the thread instead of using the "saved" within the image. Skia now has a `SK_IMAGE_READ_PIXELS_DISABLE_LEGACY_API` that helps with catching these (however APIs like `makeImageSnapshot(GrDirectContext* gr = nullptr)` are not caught by the flag).
1 parent d314745 commit f4db5f5

15 files changed

+60
-81
lines changed

apps/paper/ios/Podfile.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -1935,7 +1935,7 @@ SPEC CHECKSUMS:
19351935
React-Mapbuffer: 1c08607305558666fd16678b85ef135e455d5c96
19361936
React-microtasksnativemodule: 87b8de96f937faefece8afd2cb3a518321b2ef99
19371937
react-native-safe-area-context: ab8f4a3d8180913bd78ae75dd599c94cce3d5e9a
1938-
react-native-skia: 1f36fe252564881d77ad424f84a8986098cc73b7
1938+
react-native-skia: 1549ee5068efc5a004b84b2e0ba109c6234e2fde
19391939
react-native-slider: 97ce0bd921f40de79cead9754546d5e4e7ba44f8
19401940
react-native-wgpu: 8d0437a304318e0e3d6ccbfed2a39880f8eae4dd
19411941
React-nativeconfig: 57781b79e11d5af7573e6f77cbf1143b71802a6d

packages/skia/android/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.4.1)
44
set (CMAKE_VERBOSE_MAKEFILE ON)
55
set (CMAKE_CXX_STANDARD 17)
66
set(SK_GRAPHITE OFF)
7-
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSK_BUILD_FOR_ANDROID -DFOLLY_NO_CONFIG=1 -DFOLLY_HAVE_CLOCK_GETTIME=1 -DFOLLY_HAVE_MEMRCHR=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_MOBILE=1 -DON_ANDROID -DONANDROID")
7+
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSK_BUILD_FOR_ANDROID -DSK_IMAGE_READ_PIXELS_DISABLE_LEGACY_API -DFOLLY_NO_CONFIG=1 -DFOLLY_HAVE_CLOCK_GETTIME=1 -DFOLLY_HAVE_MEMRCHR=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_MOBILE=1 -DON_ANDROID -DONANDROID")
88
set (PACKAGE_NAME "rnskia")
99
set (SKIA_LIB "skia")
1010
set (SKIA_SVG_LIB "svg")

packages/skia/android/cpp/rnskia-android/OpenGLContext.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,11 @@ class OpenGLContext {
129129
std::unique_ptr<WindowContext> MakeWindow(ANativeWindow *window, int width,
130130
int height) {
131131
return std::make_unique<OpenGLWindowContext>(
132-
_directContext, _glDisplay.get(), _glContext.get(), window);
132+
_directContext.get(), _glDisplay.get(), _glContext.get(), window);
133133
}
134134

135+
GrDirectContext *getDirectContext() { return _directContext.get(); }
136+
135137
private:
136138
EGLConfig _glConfig;
137139
std::unique_ptr<gl::Display> _glDisplay;

packages/skia/android/cpp/rnskia-android/OpenGLWindowContext.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ sk_sp<SkSurface> OpenGLWindowContext::getSurface() {
4545
sk_sp<SkColorSpace> colorSpace(nullptr);
4646
SkSurfaceProps surfaceProps(0, kRGB_H_SkPixelGeometry);
4747
_skSurface = SkSurfaces::WrapBackendRenderTarget(
48-
_directContext.get(), backendRT, kBottomLeft_GrSurfaceOrigin,
48+
_directContext, backendRT, kBottomLeft_GrSurfaceOrigin,
4949
kRGBA_8888_SkColorType, colorSpace, &surfaceProps);
5050
}
5151
return _skSurface;

packages/skia/android/cpp/rnskia-android/OpenGLWindowContext.h

+4-5
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,8 @@ namespace RNSkia {
3333

3434
class OpenGLWindowContext : public WindowContext {
3535
public:
36-
OpenGLWindowContext(sk_sp<GrDirectContext> directContext,
37-
gl::Display *display, gl::Context *glContext,
38-
ANativeWindow *window)
36+
OpenGLWindowContext(GrDirectContext *directContext, gl::Display *display,
37+
gl::Context *glContext, ANativeWindow *window)
3938
: _directContext(directContext), _display(display), _glContext(glContext),
4039
_window(window) {
4140
ANativeWindow_acquire(_window);
@@ -60,11 +59,11 @@ class OpenGLWindowContext : public WindowContext {
6059
void resize(int width, int height) override { _skSurface = nullptr; }
6160

6261
private:
63-
sk_sp<GrDirectContext> _directContext;
62+
GrDirectContext *_directContext;
6463
gl::Display *_display;
64+
gl::Context *_glContext = nullptr;
6565
ANativeWindow *_window;
6666
sk_sp<SkSurface> _skSurface = nullptr;
67-
gl::Context *_glContext = nullptr;
6867
std::unique_ptr<gl::Surface> _glSurface = nullptr;
6968
};
7069

packages/skia/android/cpp/rnskia-android/RNSkAndroidPlatformContext.h

+6
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ class RNSkAndroidPlatformContext : public RNSkPlatformContext {
152152
#endif
153153
}
154154

155+
#if !defined(SK_GRAPHITE)
156+
GrDirectContext *getDirectContext() override {
157+
return OpenGLContext::getInstance().getDirectContext();
158+
}
159+
#endif
160+
155161
sk_sp<SkFontMgr> createFontMgr() override {
156162
return SkFontMgr_New_Android(nullptr);
157163
}

packages/skia/cpp/api/JsiSkImage.h

+20-4
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,11 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject<SkImage> {
9292
image = DawnContext::getInstance().MakeRasterImage(image);
9393
#else
9494
if (image->isTextureBacked()) {
95-
image = image->makeNonTextureImage();
95+
auto grContext = getContext()->getDirectContext();
96+
image = image->makeRasterImage(grContext);
97+
if (!image) {
98+
return nullptr;
99+
}
96100
}
97101
#endif
98102
sk_sp<SkData> data;
@@ -121,6 +125,9 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject<SkImage> {
121125

122126
JSI_HOST_FUNCTION(encodeToBytes) {
123127
auto data = encodeImageData(arguments, count);
128+
if (!data) {
129+
return jsi::Value::null();
130+
}
124131

125132
auto arrayCtor =
126133
runtime.global().getPropertyAsFunction(runtime, "Uint8Array");
@@ -141,6 +148,9 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject<SkImage> {
141148

142149
JSI_HOST_FUNCTION(encodeToBase64) {
143150
auto data = encodeImageData(arguments, count);
151+
if (!data) {
152+
return jsi::Value::null();
153+
}
144154

145155
auto len = Base64::Encode(data->bytes(), data->size(), nullptr);
146156
auto buffer = std::string(len, 0);
@@ -182,18 +192,24 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject<SkImage> {
182192
.asObject(runtime)
183193
.getArrayBuffer(runtime);
184194
auto bfrPtr = reinterpret_cast<void *>(buffer.data(runtime));
185-
186-
if (!getObject()->readPixels(info, bfrPtr, bytesPerRow, srcX, srcY)) {
195+
#if defined(SK_GRAPHITE)
196+
throw std::runtime_error("Not implemented yet");
197+
#else
198+
auto grContext = getContext()->getDirectContext();
199+
if (!getObject()->readPixels(grContext, info, bfrPtr, bytesPerRow, srcX,
200+
srcY)) {
187201
return jsi::Value::null();
188202
}
203+
#endif
189204
return dest;
190205
}
191206

192207
JSI_HOST_FUNCTION(makeNonTextureImage) {
193208
#if defined(SK_GRAPHITE)
194209
auto rasterImage = DawnContext::getInstance().MakeRasterImage(getObject());
195210
#else
196-
auto rasterImage = getObject()->makeNonTextureImage();
211+
auto grContext = getContext()->getDirectContext();
212+
auto rasterImage = getObject()->makeRasterImage(grContext);
197213
#endif
198214
return jsi::Object::createFromHostObject(
199215
runtime, std::make_shared<JsiSkImage>(getContext(), rasterImage));

packages/skia/cpp/rnskia/RNSkPlatformContext.h

+4
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ class RNSkPlatformContext {
147147
*/
148148
virtual sk_sp<SkImage> makeImageFromNativeBuffer(void *buffer) = 0;
149149

150+
#if !defined(SK_GRAPHITE)
151+
virtual GrDirectContext *getDirectContext() = 0;
152+
#endif
153+
150154
virtual void releaseNativeBuffer(uint64_t pointer) = 0;
151155

152156
virtual uint64_t makeNativeBuffer(sk_sp<SkImage> image) = 0;

packages/skia/cpp/rnskia/RNSkView.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ class RNSkOffscreenCanvasProvider : public RNSkCanvasProvider {
9090
RNSkOffscreenCanvasProvider(std::shared_ptr<RNSkPlatformContext> context,
9191
std::function<void()> requestRedraw, float width,
9292
float height)
93-
: RNSkCanvasProvider(requestRedraw), _width(width), _height(height) {
93+
: RNSkCanvasProvider(requestRedraw), _context(context), _width(width),
94+
_height(height) {
9495
_surface = context->makeOffscreenSurface(_width, _height);
9596
_pd = context->getPixelDensity();
9697
}
@@ -113,7 +114,8 @@ class RNSkOffscreenCanvasProvider : public RNSkCanvasProvider {
113114
_surface->recorder()->snap().get());
114115
return DawnContext::getInstance().MakeRasterImage(image);
115116
#else
116-
return image->makeNonTextureImage();
117+
auto grContext = _context->getDirectContext();
118+
return image->makeRasterImage(grContext);
117119
#endif
118120
}
119121

@@ -140,6 +142,7 @@ class RNSkOffscreenCanvasProvider : public RNSkCanvasProvider {
140142
float _height;
141143
float _pd = 1.0f;
142144
sk_sp<SkSurface> _surface;
145+
std::shared_ptr<RNSkPlatformContext> _context;
143146
};
144147

145148
enum RNSkDrawingMode { Default, Continuous };

packages/skia/ios/RNSkia-iOS/MetalContext.h

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class MetalContext {
3737
height);
3838
}
3939

40+
GrDirectContext *getDirectContext() { return _context.skContext.get(); }
41+
4042
private:
4143
friend class RNSkia::RNSkiOSPlatformContext;
4244
id<MTLDevice> _device;

packages/skia/ios/RNSkia-iOS/RNSkiOSPlatformContext.h

+3
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ class RNSkiOSPlatformContext : public RNSkPlatformContext {
7676

7777
void raiseError(const std::exception &err) override;
7878
sk_sp<SkSurface> makeOffscreenSurface(int width, int height) override;
79+
#if !defined(SK_GRAPHITE)
80+
GrDirectContext *getDirectContext() override;
81+
#endif
7982
sk_sp<SkFontMgr> createFontMgr() override;
8083

8184
void willInvalidateModules() {

packages/skia/ios/RNSkia-iOS/RNSkiOSPlatformContext.mm

+6
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,12 @@
192192
#endif
193193
}
194194

195+
#if !defined(SK_GRAPHITE)
196+
GrDirectContext *RNSkiOSPlatformContext::getDirectContext() {
197+
return MetalContext::getInstance().getDirectContext();
198+
}
199+
#endif
200+
195201
sk_sp<SkFontMgr> RNSkiOSPlatformContext::createFontMgr() {
196202
return SkFontMgr_New_CoreText(nullptr);
197203
}

packages/skia/react-native-skia.podspec

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use_graphite = ENV['SK_GRAPHITE'] == '1'
99

1010
# Set preprocessor definitions based on GRAPHITE flag
1111
preprocessor_defs = use_graphite ?
12-
'$(inherited) SK_GRAPHITE=1' :
13-
'$(inherited) SK_METAL=1 SK_GANESH=1'
12+
'$(inherited) SK_GRAPHITE=1 SK_IMAGE_READ_PIXELS_DISABLE_LEGACY_API=1' :
13+
'$(inherited) SK_METAL=1 SK_GANESH=1 SK_IMAGE_READ_PIXELS_DISABLE_LEGACY_API=1'
1414

1515
# Define base frameworks
1616
base_frameworks = ['libs/ios/libskia.xcframework',

packages/skia/src/renderer/__tests__/e2e/Image.spec.tsx

+2-64
Original file line numberDiff line numberDiff line change
@@ -43,51 +43,17 @@ describe("Image loading from bundles", () => {
4343
},
4444
{
4545
data: Array.from(
46-
loadImage("skia/__tests__/assets/oslo.jpg").encodeToBytes()
46+
loadImage("skia/__tests__/assets/oslo-mini.jpg").encodeToBytes()
4747
),
4848
}
4949
);
5050
expect(pixels).toBeDefined();
5151
expect(pixels).toEqual([
52-
170, 186, 199, 255, 170, 186, 199, 255, 170, 186, 199, 255, 170, 186, 199,
52+
171, 188, 198, 255, 171, 188, 198, 255, 171, 188, 198, 255, 171, 188, 198,
5353
255,
5454
]);
5555
});
5656

57-
// it("should read pixels from an image using a preallocated buffer", async () => {
58-
// const pixels = await surface.eval(
59-
// (Skia, { colorType, alphaType, data }) => {
60-
// const image = Skia.Image.MakeImageFromEncoded(
61-
// Skia.Data.fromBytes(new Uint8Array(data))
62-
// )!;
63-
// const result = new Uint8Array(16);
64-
// image.readPixels(
65-
// 0,
66-
// 0,
67-
// {
68-
// width: 2,
69-
// height: 2,
70-
// colorType,
71-
// alphaType,
72-
// },
73-
// result
74-
// );
75-
// return result;
76-
// },
77-
// {
78-
// colorType: ColorType.RGBA_8888,
79-
// alphaType: AlphaType.Unpremul,
80-
// data: Array.from(
81-
// loadImage("skia/__tests__/assets/oslo.jpg").encodeToBytes()
82-
// ),
83-
// }
84-
// );
85-
// expect(pixels).toBeDefined();
86-
// expect(Array.from(pixels!)).toEqual([
87-
// 170, 186, 199, 255, 170, 186, 199, 255, 170, 186, 199, 255, 170, 186, 199,
88-
// 255,
89-
// ]);
90-
// });
9157
it("should read pixels from a canvas", async () => {
9258
const pixels = await surface.eval(
9359
(Skia, { colorType, alphaType }) => {
@@ -108,32 +74,4 @@ describe("Image loading from bundles", () => {
10874
expect(pixels).toBeDefined();
10975
expect(Array.from(pixels!)).toEqual([255, 0, 0, 255]);
11076
});
111-
// it("should read pixels from a canvas using a preallocated buffer", async () => {
112-
// const pixels = await surface.eval(
113-
// (Skia, { colorType, alphaType }) => {
114-
// const offscreen = Skia.Surface.MakeOffscreen(10, 10)!;
115-
// const canvas = offscreen.getCanvas();
116-
// canvas.drawColor(Skia.Color("red"));
117-
// const result = new Uint8Array(4);
118-
// canvas.readPixels(0, 0, {
119-
// width: 1,
120-
// height: 1,
121-
// colorType,
122-
// alphaType,
123-
// }, result);
124-
// },
125-
// { colorType: ColorType.RGBA_8888, alphaType: AlphaType.Unpremul }
126-
// );
127-
// expect(pixels).toBeDefined();
128-
// expect(Array.from(pixels!)).toEqual([255, 0, 0, 255]);
129-
// });
130-
// This test should only run on CI because it will trigger a redbox.
131-
// While this is fine on CI, it is undesirable on local dev.
132-
// it("should not crash with an invalid viewTag", async () => {
133-
// const result = await surface.eval((Skia) => {
134-
// Skia.Image.MakeImageFromViewTag(-1);
135-
// return true;
136-
// });
137-
// expect(result).toBe(true);
138-
// });
13977
});
Loading

0 commit comments

Comments
 (0)