@shopify/react-native-skia 0.1.200 → 0.1.202
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/android/CMakeLists.txt +1 -1
- package/android/cpp/jni/include/JniSkiaBaseView.h +74 -13
- package/android/cpp/jni/include/JniSkiaDomView.h +20 -12
- package/android/cpp/jni/include/JniSkiaDrawView.h +20 -14
- package/android/cpp/jni/include/JniSkiaPictureView.h +24 -15
- package/android/cpp/rnskia-android/RNSkAndroidPlatformContext.h +2 -2
- package/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp +41 -44
- package/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.h +4 -6
- package/android/cpp/rnskia-android/SkiaOpenGLHelper.h +310 -0
- package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.cpp +132 -0
- package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h +125 -0
- package/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseView.java +80 -11
- package/android/src/main/java/com/shopify/reactnative/skia/SkiaDomView.java +2 -0
- package/android/src/main/java/com/shopify/reactnative/skia/SkiaDrawView.java +2 -0
- package/android/src/main/java/com/shopify/reactnative/skia/SkiaPictureView.java +3 -0
- package/android/src/main/java/com/shopify/reactnative/skia/ViewScreenshotService.java +7 -5
- package/cpp/api/JsiSkHostObjects.h +0 -10
- package/cpp/rnskia/RNSkJsView.cpp +35 -10
- package/cpp/rnskia/RNSkJsiViewApi.h +1 -1
- package/cpp/rnskia/RNSkView.h +14 -11
- package/ios/RNSkia-iOS/RNSkMetalCanvasProvider.h +5 -14
- package/ios/RNSkia-iOS/RNSkMetalCanvasProvider.mm +7 -56
- package/ios/RNSkia-iOS/RNSkiOSPlatformContext.mm +2 -2
- package/ios/RNSkia-iOS/SkiaMetalSurfaceFactory.h +31 -0
- package/ios/RNSkia-iOS/SkiaMetalSurfaceFactory.mm +105 -0
- package/ios/RNSkia-iOS/ViewScreenshotService.mm +1 -0
- package/package.json +1 -1
- package/android/cpp/rnskia-android/SkiaOpenGLRenderer.cpp +0 -347
- package/android/cpp/rnskia-android/SkiaOpenGLRenderer.h +0 -124
- package/ios/RNSkia-iOS/SkiaMetalRenderer.h +0 -5
- 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
|
-
|
117
|
-
surfaceDestroyed();
|
176
|
+
Log.i("SkiaBaseView", "onSurfaceTextureDestroyed");
|
118
177
|
// https://developer.android.com/reference/android/view/TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed(android.graphics.SurfaceTexture)
|
119
|
-
|
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
|
-
|
126
|
-
return
|
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,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 =
|
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
|
-
|
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
|
-
|
48
|
-
|
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
|
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->
|
106
|
-
|
107
|
-
|
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
|
}
|
package/cpp/rnskia/RNSkView.h
CHANGED
@@ -86,23 +86,25 @@ protected:
|
|
86
86
|
bool _showDebugOverlays;
|
87
87
|
};
|
88
88
|
|
89
|
-
class
|
89
|
+
class RNSkOffscreenCanvasProvider : public RNSkCanvasProvider {
|
90
90
|
public:
|
91
|
-
|
92
|
-
|
93
|
-
|
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(
|
102
|
+
sk_sp<SkImage> makeSnapshot(SkRect *bounds) {
|
102
103
|
sk_sp<SkImage> image;
|
103
104
|
if (bounds != nullptr) {
|
104
|
-
SkIRect b =
|
105
|
-
|
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(
|
279
|
+
sk_sp<SkImage> makeImageSnapshot(SkRect *bounds) {
|
277
280
|
|
278
|
-
auto provider = std::make_shared<
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
#pragma clang diagnostic push
|
10
|
+
#pragma clang diagnostic ignored "-Wdocumentation"
|
11
|
+
|
12
|
+
#import <include/gpu/GrDirectContext.h>
|
13
13
|
|
14
|
-
|
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 =
|
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
|
-
|
122
|
-
|
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
|
-
[
|
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 <
|
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
|
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
|
+
}
|