Shopify / Shopify/react-native-skia
SkiaApi.Context() fails if not ran on the main thread
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 8.6k
- Forks
- 647
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 35
Description
I'm using SkiaApi.Context() to render onto a surface obtained from WebGPU:
const nativeSurface = this.canvasRef.getNativeSurface();
const context = SkiaApi.Context(nativeSurface.surface, width, height);
const surface = context.getSurface();
runRendering(surface);
surface.flush();
context.present();
surface.dispose();
This works, but produces a stack trace on the debug console with a message "Modifying properties of a view's layer off the main thread is not allowed". This not only pollutes the logs, but it also is slow (~300ms).
Here is the diff that solved my problem:
diff --git a/node_modules/@shopify/react-native-skia/apple/MetalWindowContext.mm b/node_modules/@shopify/react-native-skia/apple/MetalWindowContext.mm
index 7230307..eb8725f 100644
--- a/node_modules/@shopify/react-native-skia/apple/MetalWindowContext.mm
+++ b/node_modules/@shopify/react-native-skia/apple/MetalWindowContext.mm
@@ -3,6 +3,36 @@
#include "MetalContext.h"
#include "RNSkLog.h"
+bool layerNeedsUpdate(CAMetalLayer *layer, id<MTLDevice> device, int width, int height) {
+ bool res = false;
+ res = res || layer.framebufferOnly != NO;
+ res = res || layer.device != device;
+ res = res || layer.opaque != false;
+#if !TARGET_OS_OSX
+ res = res || layer.contentsScale != [UIScreen mainScreen].scale;
+#else
+ res = res || layer.contentsScale != [NSScreen mainScreen].backingScaleFactor;
+#endif // !TARGET_OS_OSX
+ res = res || layer.pixelFormat != MTLPixelFormatBGRA8Unorm;
+ res = res || layer.contentsGravity != kCAGravityBottomLeft;
+ res = res || layer.drawableSize.width != width || layer.drawableSize.height != height;
+ return res;
+}
+
+void doUpdateLayer(CAMetalLayer *layer, id<MTLDevice> device, int width, int height) {
+ layer.framebufferOnly = NO;
+ layer.device = device;
+ layer.opaque = false;
+#if !TARGET_OS_OSX
+ layer.contentsScale = [UIScreen mainScreen].scale;
+#else
+ layer.contentsScale = [NSScreen mainScreen].backingScaleFactor;
+#endif // !TARGET_OS_OSX
+ layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
+ layer.contentsGravity = kCAGravityBottomLeft;
+ layer.drawableSize = CGSizeMake(width, height);
+}
+
MetalWindowContext::MetalWindowContext(GrDirectContext *directContext,
id<MTLDevice> device,
id<MTLCommandQueue> commandQueue,
@@ -12,17 +42,21 @@ MetalWindowContext::MetalWindowContext(GrDirectContext *directContext,
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
_layer = (CAMetalLayer *)layer;
#pragma clang diagnostic pop
- _layer.framebufferOnly = NO;
- _layer.device = device;
- _layer.opaque = false;
-#if !TARGET_OS_OSX
- _layer.contentsScale = [UIScreen mainScreen].scale;
-#else
- _layer.contentsScale = [NSScreen mainScreen].backingScaleFactor;
-#endif // !TARGET_OS_OSX
- _layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
- _layer.contentsGravity = kCAGravityBottomLeft;
- _layer.drawableSize = CGSizeMake(width, height);
+ // We can't just update the layer here, as this method will likely be called
+ // from a background thread. Updating the layer properties will then result in
+ // "Modifying properties of a view's layer off the main thread is not allowed"
+ // followed by a stack trace on the debug console.
+ if (layerNeedsUpdate(_layer, device, width, height)) {
+ // We need to check if we _are_ called on the main thread, as in this case
+ // the dispatch_sync will deadlock.
+ if ([[NSThread currentThread] isMainThread]) {
+ doUpdateLayer(_layer, device, width, height);
+ } else {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ doUpdateLayer(_layer, device, width, height);
+ });
+ }
+ }
}
sk_sp<SkSurface> MetalWindowContext::getSurface() {
This issue body was partially generated by patch-package.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in apple/MetalWindowContext.mm, especially MetalWindowContext::MetalWindowContext, and compare the reported patch with the current behavior. Reproduce SkiaApi.Context() rendering from a non-main thread, then verify that the debug warning and reported delay are gone without breaking calls made on the main thread.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- objective-c, react-native
- Domain
- computer-graphics, mobile-dev
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100