react-native-skia 3.0.3 → 3.0.4
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/build.gradle +15 -7
- package/android/src/main/java/com/reactnative/skia/BackingViewKind.java +24 -0
- package/android/src/main/java/com/reactnative/skia/FrameScheduler.java +104 -0
- package/android/src/main/java/com/reactnative/skia/SkiaView.java +55 -44
- package/android/src/test/java/com/reactnative/skia/FrameSchedulerTest.java +186 -0
- package/cpp/rnskia/RNSkView.h +10 -7
- package/package.json +1 -1
package/android/build.gradle
CHANGED
|
@@ -147,7 +147,7 @@ logger.warn("react-native-skia: buildDir: ${buildDir}")
|
|
|
147
147
|
logger.warn("react-native-skia: node_modules: ${nodeModules}")
|
|
148
148
|
|
|
149
149
|
android {
|
|
150
|
-
namespace "com.reactnative.skia"
|
|
150
|
+
namespace = "com.reactnative.skia"
|
|
151
151
|
|
|
152
152
|
compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION)
|
|
153
153
|
buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION)
|
|
@@ -155,10 +155,10 @@ android {
|
|
|
155
155
|
// Used to override the NDK path/version on internal CI or by allowing
|
|
156
156
|
// users to customize the NDK path/version from their root project.
|
|
157
157
|
if (rootProject.hasProperty("ndkPath")) {
|
|
158
|
-
ndkPath rootProject.ext.ndkPath
|
|
158
|
+
ndkPath = rootProject.ext.ndkPath
|
|
159
159
|
}
|
|
160
160
|
if (rootProject.hasProperty("ndkVersion")) {
|
|
161
|
-
ndkVersion rootProject.ext.ndkVersion
|
|
161
|
+
ndkVersion = rootProject.ext.ndkVersion
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
defaultConfig {
|
|
@@ -181,8 +181,15 @@ android {
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
+
// The Java sources use switch expressions (Java 14+). React Native's Gradle
|
|
185
|
+
// plugin aligns every module on 17 from the app's plugin block, but a host
|
|
186
|
+
// that opts out of that alignment must still compile this library.
|
|
187
|
+
compileOptions {
|
|
188
|
+
sourceCompatibility = JavaVersion.VERSION_17
|
|
189
|
+
targetCompatibility = JavaVersion.VERSION_17
|
|
190
|
+
}
|
|
184
191
|
lintOptions {
|
|
185
|
-
abortOnError false
|
|
192
|
+
abortOnError = false
|
|
186
193
|
}
|
|
187
194
|
|
|
188
195
|
publishing {
|
|
@@ -214,12 +221,12 @@ android {
|
|
|
214
221
|
}
|
|
215
222
|
|
|
216
223
|
buildFeatures {
|
|
217
|
-
prefab true
|
|
218
|
-
prefabPublishing true
|
|
224
|
+
prefab = true
|
|
225
|
+
prefabPublishing = true
|
|
219
226
|
}
|
|
220
227
|
prefab {
|
|
221
228
|
rnskia {
|
|
222
|
-
headers "${project.buildDir}/headers/rnskia/"
|
|
229
|
+
headers = "${project.buildDir}/headers/rnskia/"
|
|
223
230
|
}
|
|
224
231
|
}
|
|
225
232
|
}
|
|
@@ -238,6 +245,7 @@ repositories {
|
|
|
238
245
|
dependencies {
|
|
239
246
|
//noinspection GradleDynamicVersion
|
|
240
247
|
implementation 'com.facebook.react:react-native:+' // From node_modules
|
|
248
|
+
testImplementation 'junit:junit:4.13.2'
|
|
241
249
|
}
|
|
242
250
|
|
|
243
251
|
// Package all the cpp code in a flattened directory structure
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package com.reactnative.skia;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.Nullable;
|
|
4
|
+
|
|
5
|
+
/** The Android view a SkiaView draws into, see SkiaView.updateView(). */
|
|
6
|
+
enum BackingViewKind {
|
|
7
|
+
SURFACE_VIEW,
|
|
8
|
+
TEXTURE_VIEW;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The kind the androidSurfaceType prop asks for, or null for "auto", which
|
|
12
|
+
* leaves the choice to the opaque prop.
|
|
13
|
+
*/
|
|
14
|
+
@Nullable
|
|
15
|
+
static BackingViewKind fromSurfaceType(@Nullable String surfaceType) {
|
|
16
|
+
if ("SurfaceView".equals(surfaceType)) {
|
|
17
|
+
return SURFACE_VIEW;
|
|
18
|
+
}
|
|
19
|
+
if ("TextureView".equals(surfaceType)) {
|
|
20
|
+
return TEXTURE_VIEW;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
package com.reactnative.skia;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.Nullable;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Decides when a SkiaView presents the recordings queued for it: a SurfaceView
|
|
7
|
+
* on the next vsync, a TextureView behind the draw its window has pending, so
|
|
8
|
+
* that the window has taken the previous frame by then. Keeps at most one
|
|
9
|
+
* frame callback and one posted present outstanding. Main thread.
|
|
10
|
+
*/
|
|
11
|
+
final class FrameScheduler {
|
|
12
|
+
/** What the scheduler asks of the view. */
|
|
13
|
+
interface Host {
|
|
14
|
+
/** Calls onFrame() on the next Choreographer frame. */
|
|
15
|
+
void postFrameCallback();
|
|
16
|
+
|
|
17
|
+
/** Calls onPosted() from the main looper, behind any draw the window has pending. */
|
|
18
|
+
void postBehindPendingDraw();
|
|
19
|
+
|
|
20
|
+
/** Presents the queued recordings, returning whether any are left. */
|
|
21
|
+
boolean presentFrame();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private final Host mHost;
|
|
25
|
+
private boolean mFrameCallbackPosted = false;
|
|
26
|
+
private boolean mPresentPosted = false;
|
|
27
|
+
|
|
28
|
+
FrameScheduler(Host host) {
|
|
29
|
+
mHost = host;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A recording was submitted. Without a backing view there is no surface to present on yet. */
|
|
33
|
+
void requestFrame(@Nullable BackingViewKind kind) {
|
|
34
|
+
if (kind == null) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// A frame callback still outstanding (a retry, see presentOrRetry) presents
|
|
38
|
+
// the new recording along with the leftovers on that vsync.
|
|
39
|
+
if (mFrameCallbackPosted) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (presentsOnVsync(kind)) {
|
|
43
|
+
postFrameCallback();
|
|
44
|
+
} else {
|
|
45
|
+
postBehindPendingDraw();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The Choreographer frame asked for with Host.postFrameCallback(). */
|
|
50
|
+
void onFrame(@Nullable BackingViewKind kind) {
|
|
51
|
+
mFrameCallbackPosted = false;
|
|
52
|
+
if (kind == null) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (presentsOnVsync(kind)) {
|
|
56
|
+
presentOrRetry();
|
|
57
|
+
} else {
|
|
58
|
+
postBehindPendingDraw();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The message posted with Host.postBehindPendingDraw(). */
|
|
63
|
+
void onPosted() {
|
|
64
|
+
mPresentPosted = false;
|
|
65
|
+
presentOrRetry();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The view left its window, and the host removed both callbacks. */
|
|
69
|
+
void cancel() {
|
|
70
|
+
mFrameCallbackPosted = false;
|
|
71
|
+
mPresentPosted = false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// The compositor takes a SurfaceView's buffer at the vsync. A TextureView's frame
|
|
75
|
+
// shows when its window draws, and its buffer queue replaces a frame not drawn yet.
|
|
76
|
+
private static boolean presentsOnVsync(BackingViewKind kind) {
|
|
77
|
+
return switch (kind) {
|
|
78
|
+
case SURFACE_VIEW -> true;
|
|
79
|
+
case TEXTURE_VIEW -> false;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Recordings left over (the present failed, or more were submitted meanwhile)
|
|
84
|
+
// are tried again on the next vsync, never straight away.
|
|
85
|
+
private void presentOrRetry() {
|
|
86
|
+
if (mHost.presentFrame()) {
|
|
87
|
+
postFrameCallback();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private void postFrameCallback() {
|
|
92
|
+
if (!mFrameCallbackPosted) {
|
|
93
|
+
mFrameCallbackPosted = true;
|
|
94
|
+
mHost.postFrameCallback();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private void postBehindPendingDraw() {
|
|
99
|
+
if (!mPresentPosted) {
|
|
100
|
+
mPresentPosted = true;
|
|
101
|
+
mHost.postBehindPendingDraw();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -8,6 +8,8 @@ import android.view.MotionEvent;
|
|
|
8
8
|
import android.view.Surface;
|
|
9
9
|
import android.view.View;
|
|
10
10
|
|
|
11
|
+
import androidx.annotation.Nullable;
|
|
12
|
+
|
|
11
13
|
import com.facebook.jni.HybridData;
|
|
12
14
|
import com.facebook.jni.annotations.DoNotStrip;
|
|
13
15
|
import com.facebook.react.bridge.ReactContext;
|
|
@@ -17,14 +19,10 @@ import com.facebook.react.views.view.ReactViewGroup;
|
|
|
17
19
|
/**
|
|
18
20
|
* The Android view behind <Canvas>, <SkiaPictureView> and <SkiaGraphiteView>.
|
|
19
21
|
* It is backed by a SurfaceView or a TextureView (see updateView()) that the
|
|
20
|
-
* native side draws into with Graphite, presenting the queued recordings
|
|
21
|
-
* the
|
|
22
|
+
* native side draws into with Graphite, presenting the queued recordings when
|
|
23
|
+
* the backing view can show them (see FrameScheduler).
|
|
22
24
|
*/
|
|
23
25
|
public class SkiaView extends ReactViewGroup implements SkiaViewAPI, Choreographer.FrameCallback {
|
|
24
|
-
// Backing view kinds, see updateView().
|
|
25
|
-
private static final int KIND_SURFACE_VIEW = 0;
|
|
26
|
-
private static final int KIND_TEXTURE_VIEW = 1;
|
|
27
|
-
|
|
28
26
|
private static final String TAG = "SkiaView";
|
|
29
27
|
|
|
30
28
|
@DoNotStrip
|
|
@@ -32,18 +30,36 @@ public class SkiaView extends ReactViewGroup implements SkiaViewAPI, Choreograph
|
|
|
32
30
|
|
|
33
31
|
private View mView;
|
|
34
32
|
|
|
35
|
-
// Props, applied together in updateView().
|
|
33
|
+
// Props, applied together in updateView(). A null kind is "auto".
|
|
36
34
|
private boolean mOpaque = false;
|
|
37
|
-
|
|
35
|
+
@Nullable
|
|
36
|
+
private BackingViewKind mRequestedKind = null;
|
|
38
37
|
private boolean mZOrderOnTop = false;
|
|
39
38
|
private boolean mHighBitDepth = false;
|
|
40
39
|
|
|
41
|
-
// What the current backing view was created with.
|
|
42
|
-
|
|
40
|
+
// What the current backing view was created with; no kind before the first one.
|
|
41
|
+
@Nullable
|
|
42
|
+
private BackingViewKind mAppliedKind = null;
|
|
43
43
|
private boolean mAppliedZOrderOnTop;
|
|
44
44
|
private boolean mAppliedHighBitDepth;
|
|
45
45
|
|
|
46
|
-
private
|
|
46
|
+
private final Runnable mPostedPresent = this::onPostedPresent;
|
|
47
|
+
private final FrameScheduler mFrameScheduler = new FrameScheduler(new FrameScheduler.Host() {
|
|
48
|
+
@Override
|
|
49
|
+
public void postFrameCallback() {
|
|
50
|
+
Choreographer.getInstance().postFrameCallback(SkiaView.this);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Override
|
|
54
|
+
public void postBehindPendingDraw() {
|
|
55
|
+
post(mPostedPresent);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@Override
|
|
59
|
+
public boolean presentFrame() {
|
|
60
|
+
return SkiaView.this.presentFrame();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
47
63
|
|
|
48
64
|
public SkiaView(Context context) {
|
|
49
65
|
super(context);
|
|
@@ -65,8 +81,8 @@ public class SkiaView extends ReactViewGroup implements SkiaViewAPI, Choreograph
|
|
|
65
81
|
mOpaque = value;
|
|
66
82
|
}
|
|
67
83
|
|
|
68
|
-
public void setSurfaceType(String value) {
|
|
69
|
-
|
|
84
|
+
public void setSurfaceType(@Nullable String value) {
|
|
85
|
+
mRequestedKind = BackingViewKind.fromSurfaceType(value);
|
|
70
86
|
}
|
|
71
87
|
|
|
72
88
|
public void setZOrderOnTop(boolean value) {
|
|
@@ -79,25 +95,22 @@ public class SkiaView extends ReactViewGroup implements SkiaViewAPI, Choreograph
|
|
|
79
95
|
|
|
80
96
|
// Resolve the backing view from the props. "auto" picks SurfaceView for an
|
|
81
97
|
// opaque canvas and TextureView for a non-opaque one.
|
|
82
|
-
private
|
|
83
|
-
if (
|
|
84
|
-
return
|
|
98
|
+
private BackingViewKind resolveKind() {
|
|
99
|
+
if (mRequestedKind != null) {
|
|
100
|
+
return mRequestedKind;
|
|
85
101
|
}
|
|
86
|
-
|
|
87
|
-
return KIND_TEXTURE_VIEW;
|
|
88
|
-
}
|
|
89
|
-
return mOpaque ? KIND_SURFACE_VIEW : KIND_TEXTURE_VIEW;
|
|
102
|
+
return mOpaque ? BackingViewKind.SURFACE_VIEW : BackingViewKind.TEXTURE_VIEW;
|
|
90
103
|
}
|
|
91
104
|
|
|
92
105
|
// The 10-bit buffer format only has 2 bits of alpha, which would visibly
|
|
93
106
|
// break translucency, and the extra precision would be lost in the 8-bit
|
|
94
107
|
// composition pass of a TextureView anyway. So the flag only applies to an
|
|
95
108
|
// opaque SurfaceView.
|
|
96
|
-
private boolean resolveHighBitDepth(
|
|
109
|
+
private boolean resolveHighBitDepth(BackingViewKind kind) {
|
|
97
110
|
if (!mHighBitDepth) {
|
|
98
111
|
return false;
|
|
99
112
|
}
|
|
100
|
-
if (kind !=
|
|
113
|
+
if (kind != BackingViewKind.SURFACE_VIEW || !mOpaque) {
|
|
101
114
|
Log.w(TAG, "highBitDepth requires an opaque SurfaceView on Android, falling back to the 8-bit format");
|
|
102
115
|
return false;
|
|
103
116
|
}
|
|
@@ -109,8 +122,8 @@ public class SkiaView extends ReactViewGroup implements SkiaViewAPI, Choreograph
|
|
|
109
122
|
// it attaches (zOrderOnTop, the buffer format), replaces the child; opacity
|
|
110
123
|
// is applied in place.
|
|
111
124
|
void updateView() {
|
|
112
|
-
|
|
113
|
-
boolean zOrderOnTop = kind ==
|
|
125
|
+
BackingViewKind kind = resolveKind();
|
|
126
|
+
boolean zOrderOnTop = kind == BackingViewKind.SURFACE_VIEW && mZOrderOnTop;
|
|
114
127
|
boolean highBitDepth = resolveHighBitDepth(kind);
|
|
115
128
|
if (mView == null
|
|
116
129
|
|| kind != mAppliedKind
|
|
@@ -122,9 +135,10 @@ public class SkiaView extends ReactViewGroup implements SkiaViewAPI, Choreograph
|
|
|
122
135
|
mAppliedKind = kind;
|
|
123
136
|
mAppliedZOrderOnTop = zOrderOnTop;
|
|
124
137
|
mAppliedHighBitDepth = highBitDepth;
|
|
125
|
-
mView = kind
|
|
126
|
-
|
|
127
|
-
|
|
138
|
+
mView = switch (kind) {
|
|
139
|
+
case SURFACE_VIEW -> new SkiaSurfaceView(getContext(), this, zOrderOnTop, mOpaque);
|
|
140
|
+
case TEXTURE_VIEW -> new SkiaTextureView(getContext(), this, mOpaque);
|
|
141
|
+
};
|
|
128
142
|
addView(mView);
|
|
129
143
|
// React Native sizes native children explicitly through onLayout, so
|
|
130
144
|
// the requestLayout triggered by addView is ignored; size the new
|
|
@@ -132,10 +146,11 @@ public class SkiaView extends ReactViewGroup implements SkiaViewAPI, Choreograph
|
|
|
132
146
|
if (getWidth() > 0 || getHeight() > 0) {
|
|
133
147
|
mView.layout(0, 0, getWidth(), getHeight());
|
|
134
148
|
}
|
|
135
|
-
} else if (kind == KIND_SURFACE_VIEW) {
|
|
136
|
-
((SkiaSurfaceView) mView).setOpaque(mOpaque);
|
|
137
149
|
} else {
|
|
138
|
-
(
|
|
150
|
+
switch (kind) {
|
|
151
|
+
case SURFACE_VIEW -> ((SkiaSurfaceView) mView).setOpaque(mOpaque);
|
|
152
|
+
case TEXTURE_VIEW -> ((SkiaTextureView) mView).setOpaque(mOpaque);
|
|
153
|
+
}
|
|
139
154
|
}
|
|
140
155
|
}
|
|
141
156
|
|
|
@@ -157,31 +172,27 @@ public class SkiaView extends ReactViewGroup implements SkiaViewAPI, Choreograph
|
|
|
157
172
|
|
|
158
173
|
// Frames --------------------------------------------------------------
|
|
159
174
|
|
|
160
|
-
/**
|
|
161
|
-
* Presents the queued recordings on the next vsync. Main thread; called
|
|
162
|
-
* from native when a recording is submitted.
|
|
163
|
-
*/
|
|
175
|
+
/** A recording was submitted. Main thread; called from native. */
|
|
164
176
|
@DoNotStrip
|
|
165
177
|
public void scheduleFrame() {
|
|
166
|
-
|
|
167
|
-
mFramePending = true;
|
|
168
|
-
Choreographer.getInstance().postFrameCallback(this);
|
|
169
|
-
}
|
|
178
|
+
mFrameScheduler.requestFrame(mAppliedKind);
|
|
170
179
|
}
|
|
171
180
|
|
|
172
181
|
@Override
|
|
173
182
|
public void doFrame(long frameTimeNanos) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
183
|
+
mFrameScheduler.onFrame(mAppliedKind);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private void onPostedPresent() {
|
|
187
|
+
mFrameScheduler.onPosted();
|
|
178
188
|
}
|
|
179
189
|
|
|
180
190
|
@Override
|
|
181
191
|
protected void onDetachedFromWindow() {
|
|
182
192
|
super.onDetachedFromWindow();
|
|
183
193
|
Choreographer.getInstance().removeFrameCallback(this);
|
|
184
|
-
|
|
194
|
+
removeCallbacks(mPostedPresent);
|
|
195
|
+
mFrameScheduler.cancel();
|
|
185
196
|
}
|
|
186
197
|
|
|
187
198
|
@Override
|
|
@@ -242,6 +253,6 @@ public class SkiaView extends ReactViewGroup implements SkiaViewAPI, Choreograph
|
|
|
242
253
|
|
|
243
254
|
private native void unregisterView();
|
|
244
255
|
|
|
245
|
-
/**
|
|
256
|
+
/** Presents the queued recordings, returning whether any are left. */
|
|
246
257
|
private native boolean presentFrame();
|
|
247
258
|
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
package com.reactnative.skia;
|
|
2
|
+
|
|
3
|
+
import static org.junit.Assert.assertEquals;
|
|
4
|
+
|
|
5
|
+
import java.util.ArrayDeque;
|
|
6
|
+
import java.util.ArrayList;
|
|
7
|
+
import java.util.Arrays;
|
|
8
|
+
import java.util.Deque;
|
|
9
|
+
import java.util.List;
|
|
10
|
+
|
|
11
|
+
import org.junit.Test;
|
|
12
|
+
|
|
13
|
+
public class FrameSchedulerTest {
|
|
14
|
+
private enum Call {
|
|
15
|
+
POST_FRAME_CALLBACK,
|
|
16
|
+
POST_BEHIND_PENDING_DRAW,
|
|
17
|
+
PRESENT
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private static final class RecordingHost implements FrameScheduler.Host {
|
|
21
|
+
final List<Call> calls = new ArrayList<>();
|
|
22
|
+
final Deque<Boolean> leftOverAfterPresent = new ArrayDeque<>();
|
|
23
|
+
|
|
24
|
+
@Override
|
|
25
|
+
public void postFrameCallback() {
|
|
26
|
+
calls.add(Call.POST_FRAME_CALLBACK);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@Override
|
|
30
|
+
public void postBehindPendingDraw() {
|
|
31
|
+
calls.add(Call.POST_BEHIND_PENDING_DRAW);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@Override
|
|
35
|
+
public boolean presentFrame() {
|
|
36
|
+
calls.add(Call.PRESENT);
|
|
37
|
+
return !leftOverAfterPresent.isEmpty() && leftOverAfterPresent.removeFirst();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
List<Call> takeCalls() {
|
|
41
|
+
List<Call> taken = new ArrayList<>(calls);
|
|
42
|
+
calls.clear();
|
|
43
|
+
return taken;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private final RecordingHost mHost = new RecordingHost();
|
|
48
|
+
private final FrameScheduler mScheduler = new FrameScheduler(mHost);
|
|
49
|
+
|
|
50
|
+
@Test
|
|
51
|
+
public void surfaceViewFrameIsPresentedOnTheNextVsync() {
|
|
52
|
+
mScheduler.requestFrame(BackingViewKind.SURFACE_VIEW);
|
|
53
|
+
assertEquals(Arrays.asList(Call.POST_FRAME_CALLBACK), mHost.takeCalls());
|
|
54
|
+
|
|
55
|
+
mScheduler.onFrame(BackingViewKind.SURFACE_VIEW);
|
|
56
|
+
assertEquals(Arrays.asList(Call.PRESENT), mHost.takeCalls());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@Test
|
|
60
|
+
public void textureViewFrameIsPresentedBehindThePendingDraw() {
|
|
61
|
+
mScheduler.requestFrame(BackingViewKind.TEXTURE_VIEW);
|
|
62
|
+
assertEquals(Arrays.asList(Call.POST_BEHIND_PENDING_DRAW), mHost.takeCalls());
|
|
63
|
+
|
|
64
|
+
mScheduler.onPosted();
|
|
65
|
+
assertEquals(Arrays.asList(Call.PRESENT), mHost.takeCalls());
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@Test
|
|
69
|
+
public void textureViewIsNeverPresentedInsideAChoreographerFrame() {
|
|
70
|
+
mScheduler.onFrame(BackingViewKind.TEXTURE_VIEW);
|
|
71
|
+
assertEquals(Arrays.asList(Call.POST_BEHIND_PENDING_DRAW), mHost.takeCalls());
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@Test
|
|
75
|
+
public void textureViewLeftoverIsPresentedBehindTheDrawOfTheNextVsync() {
|
|
76
|
+
mHost.leftOverAfterPresent.add(true);
|
|
77
|
+
mScheduler.requestFrame(BackingViewKind.TEXTURE_VIEW);
|
|
78
|
+
mScheduler.onPosted();
|
|
79
|
+
assertEquals(
|
|
80
|
+
Arrays.asList(Call.POST_BEHIND_PENDING_DRAW, Call.PRESENT, Call.POST_FRAME_CALLBACK),
|
|
81
|
+
mHost.takeCalls());
|
|
82
|
+
|
|
83
|
+
mScheduler.onFrame(BackingViewKind.TEXTURE_VIEW);
|
|
84
|
+
mScheduler.onPosted();
|
|
85
|
+
assertEquals(Arrays.asList(Call.POST_BEHIND_PENDING_DRAW, Call.PRESENT), mHost.takeCalls());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@Test
|
|
89
|
+
public void textureViewRequestWhileARetryIsOutstandingWaitsForThatVsync() {
|
|
90
|
+
mHost.leftOverAfterPresent.add(true);
|
|
91
|
+
mScheduler.requestFrame(BackingViewKind.TEXTURE_VIEW);
|
|
92
|
+
mScheduler.onPosted();
|
|
93
|
+
assertEquals(
|
|
94
|
+
Arrays.asList(Call.POST_BEHIND_PENDING_DRAW, Call.PRESENT, Call.POST_FRAME_CALLBACK),
|
|
95
|
+
mHost.takeCalls());
|
|
96
|
+
|
|
97
|
+
mScheduler.requestFrame(BackingViewKind.TEXTURE_VIEW);
|
|
98
|
+
assertEquals(Arrays.asList(), mHost.takeCalls());
|
|
99
|
+
|
|
100
|
+
mScheduler.onFrame(BackingViewKind.TEXTURE_VIEW);
|
|
101
|
+
mScheduler.onPosted();
|
|
102
|
+
assertEquals(Arrays.asList(Call.POST_BEHIND_PENDING_DRAW, Call.PRESENT), mHost.takeCalls());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@Test
|
|
106
|
+
public void surfaceViewLeftoverIsPresentedOnTheNextVsync() {
|
|
107
|
+
mHost.leftOverAfterPresent.add(true);
|
|
108
|
+
mScheduler.requestFrame(BackingViewKind.SURFACE_VIEW);
|
|
109
|
+
mScheduler.onFrame(BackingViewKind.SURFACE_VIEW);
|
|
110
|
+
assertEquals(
|
|
111
|
+
Arrays.asList(Call.POST_FRAME_CALLBACK, Call.PRESENT, Call.POST_FRAME_CALLBACK),
|
|
112
|
+
mHost.takeCalls());
|
|
113
|
+
|
|
114
|
+
mScheduler.onFrame(BackingViewKind.SURFACE_VIEW);
|
|
115
|
+
assertEquals(Arrays.asList(Call.PRESENT), mHost.takeCalls());
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@Test
|
|
119
|
+
public void requestsWaitingForTheSameFrameArePostedOnce() {
|
|
120
|
+
mScheduler.requestFrame(BackingViewKind.SURFACE_VIEW);
|
|
121
|
+
mScheduler.requestFrame(BackingViewKind.SURFACE_VIEW);
|
|
122
|
+
mScheduler.onFrame(BackingViewKind.SURFACE_VIEW);
|
|
123
|
+
mScheduler.requestFrame(BackingViewKind.TEXTURE_VIEW);
|
|
124
|
+
mScheduler.requestFrame(BackingViewKind.TEXTURE_VIEW);
|
|
125
|
+
assertEquals(
|
|
126
|
+
Arrays.asList(Call.POST_FRAME_CALLBACK, Call.PRESENT, Call.POST_BEHIND_PENDING_DRAW),
|
|
127
|
+
mHost.takeCalls());
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@Test
|
|
131
|
+
public void requestAfterTheFrameRanIsPostedAgain() {
|
|
132
|
+
mScheduler.requestFrame(BackingViewKind.SURFACE_VIEW);
|
|
133
|
+
mScheduler.onFrame(BackingViewKind.SURFACE_VIEW);
|
|
134
|
+
mScheduler.requestFrame(BackingViewKind.SURFACE_VIEW);
|
|
135
|
+
mScheduler.onFrame(BackingViewKind.SURFACE_VIEW);
|
|
136
|
+
mScheduler.requestFrame(BackingViewKind.TEXTURE_VIEW);
|
|
137
|
+
mScheduler.onPosted();
|
|
138
|
+
mScheduler.requestFrame(BackingViewKind.TEXTURE_VIEW);
|
|
139
|
+
assertEquals(
|
|
140
|
+
Arrays.asList(
|
|
141
|
+
Call.POST_FRAME_CALLBACK,
|
|
142
|
+
Call.PRESENT,
|
|
143
|
+
Call.POST_FRAME_CALLBACK,
|
|
144
|
+
Call.PRESENT,
|
|
145
|
+
Call.POST_BEHIND_PENDING_DRAW,
|
|
146
|
+
Call.PRESENT,
|
|
147
|
+
Call.POST_BEHIND_PENDING_DRAW),
|
|
148
|
+
mHost.takeCalls());
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@Test
|
|
152
|
+
public void kindSwitchWhileAFrameCallbackIsOutstandingPresentsBehindThePendingDraw() {
|
|
153
|
+
mScheduler.requestFrame(BackingViewKind.SURFACE_VIEW);
|
|
154
|
+
// updateView() replaced the SurfaceView with a TextureView meanwhile.
|
|
155
|
+
mScheduler.requestFrame(BackingViewKind.TEXTURE_VIEW);
|
|
156
|
+
mScheduler.onFrame(BackingViewKind.TEXTURE_VIEW);
|
|
157
|
+
mScheduler.onPosted();
|
|
158
|
+
assertEquals(
|
|
159
|
+
Arrays.asList(Call.POST_FRAME_CALLBACK, Call.POST_BEHIND_PENDING_DRAW, Call.PRESENT),
|
|
160
|
+
mHost.takeCalls());
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@Test
|
|
164
|
+
public void withoutABackingViewNothingIsPostedOrPresented() {
|
|
165
|
+
mScheduler.requestFrame(null);
|
|
166
|
+
mScheduler.onFrame(null);
|
|
167
|
+
assertEquals(Arrays.asList(), mHost.takeCalls());
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@Test
|
|
171
|
+
public void cancelDropsOutstandingRequests() {
|
|
172
|
+
for (BackingViewKind kind : BackingViewKind.values()) {
|
|
173
|
+
mScheduler.requestFrame(kind);
|
|
174
|
+
mScheduler.cancel();
|
|
175
|
+
mScheduler.requestFrame(kind);
|
|
176
|
+
mScheduler.cancel();
|
|
177
|
+
}
|
|
178
|
+
assertEquals(
|
|
179
|
+
Arrays.asList(
|
|
180
|
+
Call.POST_FRAME_CALLBACK,
|
|
181
|
+
Call.POST_FRAME_CALLBACK,
|
|
182
|
+
Call.POST_BEHIND_PENDING_DRAW,
|
|
183
|
+
Call.POST_BEHIND_PENDING_DRAW),
|
|
184
|
+
mHost.takeCalls());
|
|
185
|
+
}
|
|
186
|
+
}
|
package/cpp/rnskia/RNSkView.h
CHANGED
|
@@ -38,7 +38,8 @@ namespace jsi = facebook::jsi;
|
|
|
38
38
|
* (SkiaViewApi.makeGraphiteContext), or <Canvas> hands over a recorder (or a
|
|
39
39
|
* picture) that the render thread pool records for the view. Either way the
|
|
40
40
|
* recordings are queued on the target; the view presents them onto its canvas
|
|
41
|
-
* provider on the main thread,
|
|
41
|
+
* provider on the main thread, once the platform view's surface can show a new
|
|
42
|
+
* frame (see setFrameScheduler).
|
|
42
43
|
*
|
|
43
44
|
* Threading: the registry side (RNSkJsiViewApi) runs on the JS thread, except
|
|
44
45
|
* applyUpdates() which a Reanimated mapper calls on the UI runtime; snapshots
|
|
@@ -248,8 +249,10 @@ public:
|
|
|
248
249
|
// Platform view side -------------------------------------------------------
|
|
249
250
|
|
|
250
251
|
/**
|
|
251
|
-
Installed by the platform view
|
|
252
|
-
|
|
252
|
+
Installed by the platform view, which calls presentFrame() once its surface
|
|
253
|
+
can show a new frame: on the display link on iOS, straight away on macOS,
|
|
254
|
+
and on Android as its FrameScheduler decides. Main thread. Without one,
|
|
255
|
+
frames are presented as soon as the main thread gets to them.
|
|
253
256
|
*/
|
|
254
257
|
void setFrameScheduler(std::function<void()> scheduler) {
|
|
255
258
|
_frameScheduler = std::move(scheduler);
|
|
@@ -265,10 +268,10 @@ public:
|
|
|
265
268
|
}
|
|
266
269
|
|
|
267
270
|
/**
|
|
268
|
-
|
|
269
|
-
are waiting, so that the
|
|
270
|
-
present failed (the app is in the background), so that the recordings
|
|
271
|
-
tried again on the next frame rather than left waiting for a redraw.
|
|
271
|
+
Presents the queued recordings (see setFrameScheduler for when). Returns
|
|
272
|
+
whether more are waiting, so that the platform view asks again: also when
|
|
273
|
+
the present failed (the app is in the background), so that the recordings
|
|
274
|
+
are tried again on the next frame rather than left waiting for a redraw.
|
|
272
275
|
Without a surface the queue is left alone: the surface presents it when
|
|
273
276
|
it appears.
|
|
274
277
|
*/
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"setup-skia-web": "scripts/setup-canvaskit.js"
|
|
9
9
|
},
|
|
10
10
|
"title": "React Native Skia",
|
|
11
|
-
"version": "3.0.
|
|
11
|
+
"version": "3.0.4",
|
|
12
12
|
"description": "High-performance React Native Graphics using Skia",
|
|
13
13
|
"main": "lib/module/index.js",
|
|
14
14
|
"react-native": "src/index.ts",
|