@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.
- 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
|
+
}
|