@shopify/react-native-skia 0.1.200 → 0.1.202

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. package/android/CMakeLists.txt +1 -1
  2. package/android/cpp/jni/include/JniSkiaBaseView.h +74 -13
  3. package/android/cpp/jni/include/JniSkiaDomView.h +20 -12
  4. package/android/cpp/jni/include/JniSkiaDrawView.h +20 -14
  5. package/android/cpp/jni/include/JniSkiaPictureView.h +24 -15
  6. package/android/cpp/rnskia-android/RNSkAndroidPlatformContext.h +2 -2
  7. package/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp +41 -44
  8. package/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.h +4 -6
  9. package/android/cpp/rnskia-android/SkiaOpenGLHelper.h +310 -0
  10. package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.cpp +132 -0
  11. package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h +125 -0
  12. package/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseView.java +80 -11
  13. package/android/src/main/java/com/shopify/reactnative/skia/SkiaDomView.java +2 -0
  14. package/android/src/main/java/com/shopify/reactnative/skia/SkiaDrawView.java +2 -0
  15. package/android/src/main/java/com/shopify/reactnative/skia/SkiaPictureView.java +3 -0
  16. package/android/src/main/java/com/shopify/reactnative/skia/ViewScreenshotService.java +7 -5
  17. package/cpp/api/JsiSkHostObjects.h +0 -10
  18. package/cpp/rnskia/RNSkJsView.cpp +35 -10
  19. package/cpp/rnskia/RNSkJsiViewApi.h +1 -1
  20. package/cpp/rnskia/RNSkView.h +14 -11
  21. package/ios/RNSkia-iOS/RNSkMetalCanvasProvider.h +5 -14
  22. package/ios/RNSkia-iOS/RNSkMetalCanvasProvider.mm +7 -56
  23. package/ios/RNSkia-iOS/RNSkiOSPlatformContext.mm +2 -2
  24. package/ios/RNSkia-iOS/SkiaMetalSurfaceFactory.h +31 -0
  25. package/ios/RNSkia-iOS/SkiaMetalSurfaceFactory.mm +105 -0
  26. package/ios/RNSkia-iOS/ViewScreenshotService.mm +1 -0
  27. package/package.json +1 -1
  28. package/android/cpp/rnskia-android/SkiaOpenGLRenderer.cpp +0 -347
  29. package/android/cpp/rnskia-android/SkiaOpenGLRenderer.h +0 -124
  30. package/ios/RNSkia-iOS/SkiaMetalRenderer.h +0 -5
  31. package/ios/RNSkia-iOS/SkiaMetalRenderer.mm +0 -55
@@ -2,14 +2,13 @@ package com.shopify.reactnative.skia;
2
2
 
3
3
  import android.content.Context;
4
4
  import android.graphics.SurfaceTexture;
5
+ import android.util.Log;
5
6
  import android.view.MotionEvent;
6
7
  import android.view.Surface;
7
8
  import android.view.TextureView;
8
9
 
9
10
  import com.facebook.jni.annotations.DoNotStrip;
10
- import com.facebook.react.uimanager.PointerEvents;
11
11
  import com.facebook.react.views.view.ReactViewGroup;
12
-
13
12
  public abstract class SkiaBaseView extends ReactViewGroup implements TextureView.SurfaceTextureListener {
14
13
 
15
14
  @DoNotStrip
@@ -18,12 +17,43 @@ public abstract class SkiaBaseView extends ReactViewGroup implements TextureView
18
17
 
19
18
  public SkiaBaseView(Context context) {
20
19
  super(context);
20
+ // TODO: Remove if we find another solution for first frame rendering
21
+ //setWillNotDraw(!shouldRenderFirstFrameAsBitmap());
21
22
  mTexture = new TextureView(context);
22
23
  mTexture.setSurfaceTextureListener(this);
23
24
  mTexture.setOpaque(false);
24
25
  addView(mTexture);
25
26
  }
26
27
 
28
+ /*@Override
29
+ TODO: Remove if we find another solution for first frame rendering
30
+ protected void onDraw(Canvas canvas) {
31
+ super.onDraw(canvas);
32
+
33
+ // If we haven't got a surface yet, let's ask the view to
34
+ // draw into a bitmap and then render the bitmap. This method
35
+ // is typically only called once - for the first frame, and
36
+ // then the surface will be available and all rendering will
37
+ // be done directly to the surface itself.
38
+ if (shouldRenderFirstFrameAsBitmap() && mSurface == null) {
39
+ int width = getWidth();
40
+ int height = getHeight();
41
+
42
+ if (width > 0 && height > 0) {
43
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
44
+ Bitmap result = (Bitmap) renderToBitmap(bitmap, width, height);
45
+
46
+ canvas.drawBitmap(
47
+ result,
48
+ new Rect(0, 0, width, height),
49
+ new Rect(0, 0, width, height),
50
+ null);
51
+
52
+ bitmap.recycle();
53
+ }
54
+ }
55
+ }*/
56
+
27
57
  @Override
28
58
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
29
59
  super.onLayout(changed, left, top, right, bottom);
@@ -102,28 +132,53 @@ public abstract class SkiaBaseView extends ReactViewGroup implements TextureView
102
132
 
103
133
  @Override
104
134
  public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
135
+ Log.i("SkiaBaseView", "onSurfaceTextureAvailable " + width + "/" + height);
105
136
  mSurface = new Surface(surface);
106
137
  surfaceAvailable(mSurface, width, height);
138
+
139
+ /*
140
+ TODO: Remove if we find another solution for first frame rendering
141
+ // Clear rendered bitmap when the surface texture has rendered
142
+ // We'll post a message to the main loop asking to invalidate
143
+ if (shouldRenderFirstFrameAsBitmap()) {
144
+ postUpdate(new AtomicInteger());
145
+ }*/
107
146
  }
108
147
 
148
+ /**
149
+ * This method is a way for us to clear the bitmap rendered on the first frame
150
+ * after at least 16 frames have passed - to avoid seeing blinks on the screen caused by
151
+ * TextureView frame sync issues. This is a hack to avoid those pesky blinks. Have no
152
+ * idea on how to sync the TextureView OpenGL updates.
153
+ * @param counter
154
+ */
155
+ /*
156
+ TODO: Remove if we find another solution for first frame rendering
157
+ void postUpdate(AtomicInteger counter) {
158
+ counter.getAndIncrement();
159
+ if (counter.get() > 16) {
160
+ invalidate();
161
+ } else {
162
+ this.post(() -> {
163
+ postUpdate(counter);
164
+ });
165
+ }
166
+ }*/
167
+
109
168
  @Override
110
169
  public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
170
+ Log.i("SkiaBaseView", "onSurfaceTextureSizeChanged " + width + "/" + height);
111
171
  surfaceSizeChanged(width, height);
112
172
  }
113
173
 
114
174
  @Override
115
175
  public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
116
- // Notify the native side
117
- surfaceDestroyed();
176
+ Log.i("SkiaBaseView", "onSurfaceTextureDestroyed");
118
177
  // https://developer.android.com/reference/android/view/TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed(android.graphics.SurfaceTexture)
119
- // Invoked when the specified SurfaceTexture is about to be destroyed. If returns true,
120
- // no rendering should happen inside the surface texture after this method is invoked.
121
- // We've measured this and it seems like we need to call release and return true - and
122
- // then handle the issue with this being ripped out underneath the native layer in the C++
123
- // code.
178
+ surfaceDestroyed();
124
179
  mSurface.release();
125
- // Return true - we promise that no more rendering will be done now.
126
- return true;
180
+ mSurface = null;
181
+ return false;
127
182
  }
128
183
 
129
184
  @Override
@@ -131,6 +186,17 @@ public abstract class SkiaBaseView extends ReactViewGroup implements TextureView
131
186
  // Nothing special to do here
132
187
  }
133
188
 
189
+ /**
190
+ * Returns true if the view is able to directly render on the
191
+ * main thread. This can f.ex then be used to create a first frame
192
+ * render of the view. Returns true by default - override if not.
193
+ */
194
+ /*
195
+ TODO: Remove if we find another solution for first frame rendering
196
+ protected boolean shouldRenderFirstFrameAsBitmap() {
197
+ return false;
198
+ }*/
199
+
134
200
  protected abstract void surfaceAvailable(Object surface, int width, int height);
135
201
 
136
202
  protected abstract void surfaceSizeChanged(int width, int height);
@@ -146,4 +212,7 @@ public abstract class SkiaBaseView extends ReactViewGroup implements TextureView
146
212
  protected abstract void registerView(int nativeId);
147
213
 
148
214
  protected abstract void unregisterView();
215
+
216
+ // TODO: Remove if we find another solution for first frame rendering
217
+ // protected native Object renderToBitmap(Object bitmap, int width, int height);
149
218
  }
@@ -42,4 +42,6 @@ public class SkiaDomView extends SkiaBaseView {
42
42
 
43
43
  protected native void unregisterView();
44
44
 
45
+ // TODO: Remove if we find another solution for first frame rendering
46
+ // protected native Object renderToBitmap(Object bitmap, int width, int height);
45
47
  }
@@ -42,4 +42,6 @@ public class SkiaDrawView extends SkiaBaseView {
42
42
 
43
43
  protected native void unregisterView();
44
44
 
45
+ // TODO: Remove if we find another solution for first frame rendering
46
+ // protected native Object renderToBitmap(Object bitmap, int width, int height);
45
47
  }
@@ -42,4 +42,7 @@ public class SkiaPictureView extends SkiaBaseView {
42
42
 
43
43
  protected native void unregisterView();
44
44
 
45
+ // TODO: Remove if we find another solution for first frame rendering
46
+ // protected native Object renderToBitmap(Object bitmap, int width, int height);
47
+
45
48
  }
@@ -22,22 +22,24 @@ import com.facebook.react.bridge.ReactContext;
22
22
  import com.facebook.react.uimanager.UIManagerModule;
23
23
 
24
24
  import java.util.ArrayList;
25
- import java.util.Collections;
26
- import java.util.LinkedList;
27
25
  import java.util.List;
28
26
  import java.util.concurrent.CountDownLatch;
29
27
  import java.util.concurrent.TimeUnit;
30
28
 
31
-
32
29
  public class ViewScreenshotService {
33
30
  private static final long SURFACE_VIEW_READ_PIXELS_TIMEOUT = 5;
34
31
  private static final String TAG = "SkiaScreenshot";
35
32
 
36
33
  public static Bitmap makeViewScreenshotFromTag(ReactContext context, int tag) {
37
34
  UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
38
- View view = uiManager.resolveView(tag);
35
+ View view = null;
36
+ try {
37
+ view = uiManager.resolveView(tag);
38
+ } catch (RuntimeException e) {
39
+ context.handleException(e);
40
+ }
39
41
  if (view == null) {
40
- throw new RuntimeException("Could not resolve view from view tag " + tag);
42
+ return null;
41
43
  }
42
44
 
43
45
  // Measure and get size of view
@@ -81,16 +81,6 @@ protected:
81
81
  */
82
82
  virtual void releaseResources() = 0;
83
83
 
84
- /**
85
- Throws a runtime error if this method is called after the object has been
86
- disposed.
87
- */
88
- void ensureNotDisposed() {
89
- if (_isDisposed) {
90
- throw std::runtime_error("API Object accessed after it was disposed");
91
- }
92
- }
93
-
94
84
  private:
95
85
  void safeDispose() {
96
86
  if (!_isDisposed) {
@@ -36,17 +36,41 @@ bool RNSkJsRenderer::tryRender(
36
36
 
37
37
  void RNSkJsRenderer::renderImmediate(
38
38
  std::shared_ptr<RNSkCanvasProvider> canvasProvider) {
39
+ // Get start time to be able to calculate animations etc.
39
40
  std::chrono::milliseconds ms =
40
41
  std::chrono::duration_cast<std::chrono::milliseconds>(
41
42
  std::chrono::system_clock::now().time_since_epoch());
42
- canvasProvider->renderToCanvas([&](SkCanvas *canvas) {
43
- // Create jsi canvas
44
- auto jsiCanvas = std::make_shared<JsiSkCanvas>(_platformContext);
45
- jsiCanvas->setCanvas(canvas);
46
43
 
47
- drawInJsiCanvas(std::move(jsiCanvas), canvasProvider->getScaledWidth(),
48
- canvasProvider->getScaledHeight(), ms.count() / 1000);
44
+ std::condition_variable cv;
45
+ std::mutex m;
46
+ std::unique_lock<std::mutex> lock(m);
47
+
48
+ // We need to render on the javascript thread but block
49
+ // until we're done rendering. Render immediate is used
50
+ // to make images from the canvas.
51
+ _platformContext->runOnJavascriptThread([canvasProvider, ms, &cv, &m,
52
+ weakSelf = weak_from_this()]() {
53
+ // Lock
54
+ std::unique_lock<std::mutex> lock(m);
55
+
56
+ auto self = weakSelf.lock();
57
+ if (self) {
58
+ canvasProvider->renderToCanvas([self, ms,
59
+ canvasProvider](SkCanvas *canvas) {
60
+ // Create jsi canvas
61
+ auto jsiCanvas = std::make_shared<JsiSkCanvas>(self->_platformContext);
62
+ jsiCanvas->setCanvas(canvas);
63
+
64
+ self->drawInJsiCanvas(
65
+ std::move(jsiCanvas), canvasProvider->getScaledWidth(),
66
+ canvasProvider->getScaledHeight(), ms.count() / 1000);
67
+ });
68
+ }
69
+
70
+ cv.notify_one();
49
71
  });
72
+
73
+ cv.wait(lock);
50
74
  }
51
75
 
52
76
  void RNSkJsRenderer::setDrawCallback(
@@ -99,12 +123,13 @@ void RNSkJsRenderer::performDraw(
99
123
 
100
124
  if (_gpuDrawingLock->try_lock()) {
101
125
 
102
- // Post drawing message to the render thread where the picture recorded
126
+ // Post drawing message to the main thread where the picture recorded
103
127
  // will be sent to the GPU/backend for rendering to screen.
128
+ // TODO: Which thread should we render on? I think it should be main thread!
104
129
  auto gpuLock = _gpuDrawingLock;
105
- _platformContext->runOnRenderThread([weakSelf = weak_from_this(),
106
- p = std::move(p), gpuLock,
107
- canvasProvider]() {
130
+ _platformContext->runOnMainThread([weakSelf = weak_from_this(),
131
+ p = std::move(p), gpuLock,
132
+ canvasProvider]() {
108
133
  auto self = weakSelf.lock();
109
134
  if (self) {
110
135
  // Draw the picture recorded on the real GPU canvas
@@ -162,7 +162,7 @@ public:
162
162
  if (info->view != nullptr) {
163
163
  if (count > 1 && !arguments[1].isUndefined() && !arguments[1].isNull()) {
164
164
  auto rect = JsiSkRect::fromValue(runtime, arguments[1]);
165
- image = info->view->makeImageSnapshot(rect);
165
+ image = info->view->makeImageSnapshot(rect.get());
166
166
  } else {
167
167
  image = info->view->makeImageSnapshot(nullptr);
168
168
  }
@@ -86,23 +86,25 @@ protected:
86
86
  bool _showDebugOverlays;
87
87
  };
88
88
 
89
- class RNSkImageCanvasProvider : public RNSkCanvasProvider {
89
+ class RNSkOffscreenCanvasProvider : public RNSkCanvasProvider {
90
90
  public:
91
- RNSkImageCanvasProvider(std::shared_ptr<RNSkPlatformContext> context,
92
- std::function<void()> requestRedraw, float width,
93
- float height)
91
+ RNSkOffscreenCanvasProvider(std::shared_ptr<RNSkPlatformContext> context,
92
+ std::function<void()> requestRedraw, float width,
93
+ float height)
94
94
  : RNSkCanvasProvider(requestRedraw), _width(width), _height(height) {
95
95
  _surface = context->makeOffscreenSurface(_width, _height);
96
+ _pd = context->getPixelDensity();
96
97
  }
97
98
 
98
99
  /**
99
100
  Returns a snapshot of the current surface/canvas
100
101
  */
101
- sk_sp<SkImage> makeSnapshot(std::shared_ptr<SkRect> bounds) {
102
+ sk_sp<SkImage> makeSnapshot(SkRect *bounds) {
102
103
  sk_sp<SkImage> image;
103
104
  if (bounds != nullptr) {
104
- SkIRect b = SkIRect::MakeXYWH(bounds->x(), bounds->y(), bounds->width(),
105
- bounds->height());
105
+ SkIRect b =
106
+ SkIRect::MakeXYWH(bounds->x() * _pd, bounds->y() * _pd,
107
+ bounds->width() * _pd, bounds->height() * _pd);
106
108
  image = _surface->makeImageSnapshot(b);
107
109
  } else {
108
110
  image = _surface->makeImageSnapshot();
@@ -131,6 +133,7 @@ public:
131
133
  private:
132
134
  float _width;
133
135
  float _height;
136
+ float _pd = 1.0f;
134
137
  sk_sp<SkSurface> _surface;
135
138
  };
136
139
 
@@ -273,9 +276,9 @@ public:
273
276
  /**
274
277
  Renders the view into an SkImage instead of the screen.
275
278
  */
276
- sk_sp<SkImage> makeImageSnapshot(std::shared_ptr<SkRect> bounds) {
279
+ sk_sp<SkImage> makeImageSnapshot(SkRect *bounds) {
277
280
 
278
- auto provider = std::make_shared<RNSkImageCanvasProvider>(
281
+ auto provider = std::make_shared<RNSkOffscreenCanvasProvider>(
279
282
  getPlatformContext(), std::bind(&RNSkView::requestRedraw, this),
280
283
  _canvasProvider->getScaledWidth(), _canvasProvider->getScaledHeight());
281
284
 
@@ -283,6 +286,8 @@ public:
283
286
  return provider->makeSnapshot(bounds);
284
287
  }
285
288
 
289
+ std::shared_ptr<RNSkRenderer> getRenderer() { return _renderer; }
290
+
286
291
  protected:
287
292
  std::shared_ptr<RNSkPlatformContext> getPlatformContext() {
288
293
  return _platformContext;
@@ -290,7 +295,6 @@ protected:
290
295
  std::shared_ptr<RNSkCanvasProvider> getCanvasProvider() {
291
296
  return _canvasProvider;
292
297
  }
293
- std::shared_ptr<RNSkRenderer> getRenderer() { return _renderer; }
294
298
 
295
299
  /**
296
300
  Ends an ongoing beginDrawCallback loop for this view. This method is made
@@ -399,7 +403,6 @@ private:
399
403
 
400
404
  size_t _drawingLoopId = 0;
401
405
  std::atomic<int> _redrawRequestCounter = {1};
402
- bool _initialDrawingDone = false;
403
406
  };
404
407
 
405
408
  } // namespace RNSkia
@@ -6,13 +6,12 @@
6
6
  #import <MetalKit/MetalKit.h>
7
7
  #import <QuartzCore/CAMetalLayer.h>
8
8
 
9
- using MetalRenderContext = struct {
10
- id<MTLCommandQueue> commandQueue;
11
- sk_sp<GrDirectContext> skContext;
12
- };
9
+ #pragma clang diagnostic push
10
+ #pragma clang diagnostic ignored "-Wdocumentation"
11
+
12
+ #import <include/gpu/GrDirectContext.h>
13
13
 
14
- static std::unordered_map<std::thread::id, std::shared_ptr<MetalRenderContext>>
15
- renderContexts;
14
+ #pragma clang diagnostic pop
16
15
 
17
16
  class RNSkMetalCanvasProvider : public RNSkia::RNSkCanvasProvider {
18
17
  public:
@@ -27,17 +26,9 @@ public:
27
26
  bool renderToCanvas(const std::function<void(SkCanvas *)> &cb) override;
28
27
 
29
28
  void setSize(int width, int height);
30
-
31
29
  CALayer *getLayer();
32
30
 
33
31
  private:
34
- /**
35
- * To be able to use static contexts (and avoid reloading the skia context for
36
- * each new view, we track the Skia drawing context per thread.
37
- * @return The drawing context for the current thread
38
- */
39
- static std::shared_ptr<MetalRenderContext> getMetalRenderContext();
40
-
41
32
  std::shared_ptr<RNSkia::RNSkPlatformContext> _context;
42
33
  float _width = -1;
43
34
  float _height = -1;
@@ -1,5 +1,6 @@
1
1
  #import <RNSkLog.h>
2
2
  #import <RNSkMetalCanvasProvider.h>
3
+ #import <SkiaMetalSurfaceFactory.h>
3
4
 
4
5
  #pragma clang diagnostic push
5
6
  #pragma clang diagnostic ignored "-Wdocumentation"
@@ -14,19 +15,6 @@
14
15
 
15
16
  #pragma clang diagnostic pop
16
17
 
17
- /** Static members */
18
- std::shared_ptr<MetalRenderContext>
19
- RNSkMetalCanvasProvider::getMetalRenderContext() {
20
- auto threadId = std::this_thread::get_id();
21
- if (renderContexts.count(threadId) == 0) {
22
- auto drawingContext = std::make_shared<MetalRenderContext>();
23
- drawingContext->commandQueue = nullptr;
24
- drawingContext->skContext = nullptr;
25
- renderContexts.emplace(threadId, drawingContext);
26
- }
27
- return renderContexts.at(threadId);
28
- }
29
-
30
18
  RNSkMetalCanvasProvider::RNSkMetalCanvasProvider(
31
19
  std::function<void()> requestRedraw,
32
20
  std::shared_ptr<RNSkia::RNSkPlatformContext> context)
@@ -35,11 +23,8 @@ RNSkMetalCanvasProvider::RNSkMetalCanvasProvider(
35
23
  #pragma clang diagnostic ignored "-Wunguarded-availability-new"
36
24
  _layer = [CAMetalLayer layer];
37
25
  #pragma clang diagnostic pop
38
-
39
- auto device = MTLCreateSystemDefaultDevice();
40
-
41
26
  _layer.framebufferOnly = NO;
42
- _layer.device = device;
27
+ _layer.device = MTLCreateSystemDefaultDevice();
43
28
  _layer.opaque = false;
44
29
  _layer.contentsScale = _context->getPixelDensity();
45
30
  _layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
@@ -87,52 +72,18 @@ bool RNSkMetalCanvasProvider::renderToCanvas(
87
72
  return false;
88
73
  }
89
74
  }
90
-
91
- // Get render context for current thread
92
- auto renderContext = getMetalRenderContext();
93
-
94
- if (renderContext->skContext == nullptr) {
95
- auto device = MTLCreateSystemDefaultDevice();
96
- renderContext->commandQueue =
97
- id<MTLCommandQueue>(CFRetain((GrMTLHandle)[device newCommandQueue]));
98
- renderContext->skContext = GrDirectContext::MakeMetal(
99
- (__bridge void *)device, (__bridge void *)renderContext->commandQueue);
100
- }
101
-
102
75
  // Wrap in auto release pool since we want the system to clean up after
103
76
  // rendering and not wait until later - we've seen some example of memory
104
77
  // usage growing very fast in the simulator without this.
105
78
  @autoreleasepool {
106
-
107
- /* It is super important that we use the pattern of calling nextDrawable
108
- inside this autoreleasepool and not depend on Skia's
109
- SkSurface::MakeFromCAMetalLayer to encapsulate since we're seeing a lot of
110
- drawables leaking if they're not done this way.
111
-
112
- This is now reverted from:
113
- (https://github.com/Shopify/react-native-skia/commit/2e2290f8e6dfc6921f97b79f779d920fbc1acceb)
114
- back to the original implementation.
115
- */
116
79
  id<CAMetalDrawable> currentDrawable = [_layer nextDrawable];
117
80
  if (currentDrawable == nullptr) {
118
81
  return false;
119
82
  }
120
83
 
121
- GrMtlTextureInfo fbInfo;
122
- fbInfo.fTexture.retain((__bridge void *)currentDrawable.texture);
123
-
124
- GrBackendRenderTarget backendRT(_layer.drawableSize.width,
125
- _layer.drawableSize.height, 1, fbInfo);
126
-
127
- auto skSurface = SkSurfaces::WrapBackendRenderTarget(
128
- renderContext->skContext.get(), backendRT, kTopLeft_GrSurfaceOrigin,
129
- kBGRA_8888_SkColorType, nullptr, nullptr);
130
-
131
- if (skSurface == nullptr || skSurface->getCanvas() == nullptr) {
132
- RNSkia::RNSkLogger::logToConsole(
133
- "Skia surface could not be created from parameters.");
134
- return false;
135
- }
84
+ auto skSurface = SkiaMetalSurfaceFactory::makeWindowedSurface(
85
+ currentDrawable.texture, _layer.drawableSize.width,
86
+ _layer.drawableSize.height);
136
87
 
137
88
  SkCanvas *canvas = skSurface->getCanvas();
138
89
  cb(canvas);
@@ -140,11 +91,11 @@ bool RNSkMetalCanvasProvider::renderToCanvas(
140
91
  skSurface->flushAndSubmit();
141
92
 
142
93
  id<MTLCommandBuffer> commandBuffer(
143
- [renderContext->commandQueue commandBuffer]);
94
+ [ThreadContextHolder::ThreadSkiaMetalContext
95
+ .commandQueue commandBuffer]);
144
96
  [commandBuffer presentDrawable:currentDrawable];
145
97
  [commandBuffer commit];
146
98
  }
147
-
148
99
  return true;
149
100
  };
150
101
 
@@ -4,7 +4,7 @@
4
4
  #include <thread>
5
5
  #include <utility>
6
6
 
7
- #include <SkiaMetalRenderer.h>
7
+ #include <SkiaMetalSurfaceFactory.h>
8
8
 
9
9
  #pragma clang diagnostic push
10
10
  #pragma clang diagnostic ignored "-Wdocumentation"
@@ -59,7 +59,7 @@ void RNSkiOSPlatformContext::raiseError(const std::exception &err) {
59
59
 
60
60
  sk_sp<SkSurface> RNSkiOSPlatformContext::makeOffscreenSurface(int width,
61
61
  int height) {
62
- return MakeOffscreenMetalSurface(width, height);
62
+ return SkiaMetalSurfaceFactory::makeOffscreenSurface(width, height);
63
63
  }
64
64
 
65
65
  void RNSkiOSPlatformContext::runOnMainThread(std::function<void()> func) {
@@ -0,0 +1,31 @@
1
+ #import <MetalKit/MetalKit.h>
2
+
3
+ #pragma clang diagnostic push
4
+ #pragma clang diagnostic ignored "-Wdocumentation"
5
+
6
+ #import "SkCanvas.h"
7
+ #import <include/gpu/GrDirectContext.h>
8
+
9
+ #pragma clang diagnostic pop
10
+
11
+ using SkiaMetalContext = struct SkiaMetalContext {
12
+ id<MTLCommandQueue> commandQueue = nullptr;
13
+ sk_sp<GrDirectContext> skContext = nullptr;
14
+ };
15
+
16
+ class ThreadContextHolder {
17
+ public:
18
+ static thread_local SkiaMetalContext ThreadSkiaMetalContext;
19
+ };
20
+
21
+ class SkiaMetalSurfaceFactory {
22
+ public:
23
+ static sk_sp<SkSurface> makeWindowedSurface(id<MTLTexture> texture, int width,
24
+ int height);
25
+ static sk_sp<SkSurface> makeOffscreenSurface(int width, int height);
26
+
27
+ private:
28
+ static id<MTLDevice> device;
29
+ static bool
30
+ createSkiaDirectContextIfNecessary(SkiaMetalContext *threadContext);
31
+ };
@@ -0,0 +1,105 @@
1
+ #import <RNSkLog.h>
2
+
3
+ #include <SkiaMetalSurfaceFactory.h>
4
+
5
+ #pragma clang diagnostic push
6
+ #pragma clang diagnostic ignored "-Wdocumentation"
7
+
8
+ #import "SkCanvas.h"
9
+ #import "SkColorSpace.h"
10
+ #import "SkSurface.h"
11
+
12
+ #import <include/gpu/GrBackendSurface.h>
13
+ #import <include/gpu/GrDirectContext.h>
14
+ #import <include/gpu/ganesh/SkSurfaceGanesh.h>
15
+
16
+ #pragma clang diagnostic pop
17
+
18
+ thread_local SkiaMetalContext ThreadContextHolder::ThreadSkiaMetalContext;
19
+
20
+ struct OffscreenRenderContext {
21
+ id<MTLTexture> texture;
22
+
23
+ OffscreenRenderContext(id<MTLDevice> device,
24
+ sk_sp<GrDirectContext> skiaContext,
25
+ id<MTLCommandQueue> commandQueue, int width,
26
+ int height) {
27
+ // Create a Metal texture descriptor
28
+ MTLTextureDescriptor *textureDescriptor = [MTLTextureDescriptor
29
+ texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
30
+ width:width
31
+ height:height
32
+ mipmapped:NO];
33
+ textureDescriptor.usage =
34
+ MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead;
35
+ texture = [device newTextureWithDescriptor:textureDescriptor];
36
+ }
37
+ };
38
+
39
+ id<MTLDevice> SkiaMetalSurfaceFactory::device = MTLCreateSystemDefaultDevice();
40
+
41
+ bool SkiaMetalSurfaceFactory::createSkiaDirectContextIfNecessary(
42
+ SkiaMetalContext *skiaMetalContext) {
43
+ if (skiaMetalContext->skContext == nullptr) {
44
+ skiaMetalContext->commandQueue =
45
+ id<MTLCommandQueue>(CFRetain((GrMTLHandle)[device newCommandQueue]));
46
+ skiaMetalContext->skContext = GrDirectContext::MakeMetal(
47
+ (__bridge void *)device,
48
+ (__bridge void *)skiaMetalContext->commandQueue);
49
+ if (skiaMetalContext->skContext == nullptr) {
50
+ RNSkia::RNSkLogger::logToConsole("Couldn't create a Skia Metal Context");
51
+ return false;
52
+ }
53
+ }
54
+ return true;
55
+ }
56
+
57
+ sk_sp<SkSurface>
58
+ SkiaMetalSurfaceFactory::makeWindowedSurface(id<MTLTexture> texture, int width,
59
+ int height) {
60
+ // Get render context for current thread
61
+ if (!SkiaMetalSurfaceFactory::createSkiaDirectContextIfNecessary(
62
+ &ThreadContextHolder::ThreadSkiaMetalContext)) {
63
+ return nullptr;
64
+ }
65
+ GrMtlTextureInfo fbInfo;
66
+ fbInfo.fTexture.retain((__bridge void *)texture);
67
+
68
+ GrBackendRenderTarget backendRT(width, height, 1, fbInfo);
69
+
70
+ auto skSurface = SkSurfaces::WrapBackendRenderTarget(
71
+ ThreadContextHolder::ThreadSkiaMetalContext.skContext.get(), backendRT,
72
+ kTopLeft_GrSurfaceOrigin, kBGRA_8888_SkColorType, nullptr, nullptr);
73
+
74
+ if (skSurface == nullptr || skSurface->getCanvas() == nullptr) {
75
+ RNSkia::RNSkLogger::logToConsole(
76
+ "Skia surface could not be created from parameters.");
77
+ return nullptr;
78
+ }
79
+ return skSurface;
80
+ }
81
+
82
+ sk_sp<SkSurface> SkiaMetalSurfaceFactory::makeOffscreenSurface(int width,
83
+ int height) {
84
+ if (!SkiaMetalSurfaceFactory::createSkiaDirectContextIfNecessary(
85
+ &ThreadContextHolder::ThreadSkiaMetalContext)) {
86
+ return nullptr;
87
+ }
88
+ auto ctx = new OffscreenRenderContext(
89
+ device, ThreadContextHolder::ThreadSkiaMetalContext.skContext,
90
+ ThreadContextHolder::ThreadSkiaMetalContext.commandQueue, width, height);
91
+
92
+ // Create a GrBackendTexture from the Metal texture
93
+ GrMtlTextureInfo info;
94
+ info.fTexture.retain((__bridge void *)ctx->texture);
95
+ GrBackendTexture backendTexture(width, height, GrMipMapped::kNo, info);
96
+
97
+ // Create a SkSurface from the GrBackendTexture
98
+ auto surface = SkSurfaces::WrapBackendTexture(
99
+ ThreadContextHolder::ThreadSkiaMetalContext.skContext.get(),
100
+ backendTexture, kTopLeft_GrSurfaceOrigin, 0, kBGRA_8888_SkColorType,
101
+ nullptr, nullptr,
102
+ [](void *addr) { delete (OffscreenRenderContext *)addr; }, ctx);
103
+
104
+ return surface;
105
+ }