@shopify/react-native-skia 0.1.200 → 0.1.201
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 +9 -9
- 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
@@ -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,11 +86,11 @@ 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
96
|
}
|
@@ -98,7 +98,7 @@ public:
|
|
98
98
|
/**
|
99
99
|
Returns a snapshot of the current surface/canvas
|
100
100
|
*/
|
101
|
-
sk_sp<SkImage> makeSnapshot(
|
101
|
+
sk_sp<SkImage> makeSnapshot(SkRect *bounds) {
|
102
102
|
sk_sp<SkImage> image;
|
103
103
|
if (bounds != nullptr) {
|
104
104
|
SkIRect b = SkIRect::MakeXYWH(bounds->x(), bounds->y(), bounds->width(),
|
@@ -273,9 +273,9 @@ public:
|
|
273
273
|
/**
|
274
274
|
Renders the view into an SkImage instead of the screen.
|
275
275
|
*/
|
276
|
-
sk_sp<SkImage> makeImageSnapshot(
|
276
|
+
sk_sp<SkImage> makeImageSnapshot(SkRect *bounds) {
|
277
277
|
|
278
|
-
auto provider = std::make_shared<
|
278
|
+
auto provider = std::make_shared<RNSkOffscreenCanvasProvider>(
|
279
279
|
getPlatformContext(), std::bind(&RNSkView::requestRedraw, this),
|
280
280
|
_canvasProvider->getScaledWidth(), _canvasProvider->getScaledHeight());
|
281
281
|
|
@@ -283,6 +283,8 @@ public:
|
|
283
283
|
return provider->makeSnapshot(bounds);
|
284
284
|
}
|
285
285
|
|
286
|
+
std::shared_ptr<RNSkRenderer> getRenderer() { return _renderer; }
|
287
|
+
|
286
288
|
protected:
|
287
289
|
std::shared_ptr<RNSkPlatformContext> getPlatformContext() {
|
288
290
|
return _platformContext;
|
@@ -290,7 +292,6 @@ protected:
|
|
290
292
|
std::shared_ptr<RNSkCanvasProvider> getCanvasProvider() {
|
291
293
|
return _canvasProvider;
|
292
294
|
}
|
293
|
-
std::shared_ptr<RNSkRenderer> getRenderer() { return _renderer; }
|
294
295
|
|
295
296
|
/**
|
296
297
|
Ends an ongoing beginDrawCallback loop for this view. This method is made
|
@@ -399,7 +400,6 @@ private:
|
|
399
400
|
|
400
401
|
size_t _drawingLoopId = 0;
|
401
402
|
std::atomic<int> _redrawRequestCounter = {1};
|
402
|
-
bool _initialDrawingDone = false;
|
403
403
|
};
|
404
404
|
|
405
405
|
} // namespace RNSkia
|
package/package.json
CHANGED