react-native-webgpu 0.15.2 → 0.16.1
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 +5 -0
- package/android/build.gradle +11 -1
- package/android/cpp/cpp-adapter.cpp +109 -11
- package/android/src/main/java/com/webgpu/SurfaceFrameRateVote.java +97 -85
- package/android/src/main/java/com/webgpu/WebGPUView.java +97 -42
- package/apple/ApplePlatformContext.h +1 -0
- package/cpp/jsi/JSICache.cpp +2 -0
- package/cpp/jsi/NativeObject.h +81 -17
- package/cpp/rnwgpu/RNWebGPUManager.cpp +51 -41
- package/cpp/rnwgpu/RNWebGPUManager.h +10 -0
- package/cpp/rnwgpu/SurfaceRegistry.h +4 -4
- package/cpp/rnwgpu/api/GPUDevice.cpp +96 -0
- package/cpp/rnwgpu/api/GPUDevice.h +22 -0
- package/cpp/rnwgpu/api/GPUError.h +1 -0
- package/cpp/rnwgpu/api/GPUExtent2D.h +56 -0
- package/cpp/rnwgpu/api/GPUExternalTexture.cpp +32 -408
- package/cpp/rnwgpu/api/GPUExternalTexture.h +12 -37
- package/cpp/rnwgpu/api/GPUInternalError.h +1 -0
- package/cpp/rnwgpu/api/GPUNativeBufferExternalTexture.h +60 -0
- package/cpp/rnwgpu/api/GPUNativeBufferTexture.h +52 -0
- package/cpp/rnwgpu/api/GPUNativeBufferWrapper.cpp +144 -0
- package/cpp/rnwgpu/api/GPUNativeBufferWrapper.h +143 -0
- package/cpp/rnwgpu/api/GPUOutOfMemoryError.h +1 -0
- package/cpp/rnwgpu/api/GPUQueue.h +1 -0
- package/cpp/rnwgpu/api/GPUSharedFence.h +1 -0
- package/cpp/rnwgpu/api/GPUSharedTextureMemory.cpp +12 -44
- package/cpp/rnwgpu/api/GPUSharedTextureMemory.h +1 -0
- package/cpp/rnwgpu/api/GPUUncapturedErrorEvent.h +1 -0
- package/cpp/rnwgpu/api/GPUValidationError.h +1 -0
- package/cpp/rnwgpu/api/NativeFrameImport.cpp +638 -0
- package/cpp/rnwgpu/api/NativeFrameImport.h +86 -0
- package/cpp/rnwgpu/api/VideoFrame.h +21 -7
- package/cpp/rnwgpu/api/descriptors/GPUExternalTextureDescriptor.h +153 -1
- package/cpp/rnwgpu/api/descriptors/GPUNativeBufferTextureDescriptor.h +55 -0
- package/cpp/rnwgpu/async/RuntimeContext.cpp +1 -0
- package/lib/commonjs/Canvas.js +51 -0
- package/lib/commonjs/Canvas.js.map +1 -1
- package/lib/commonjs/external/reanimated/registerWebGPUForReanimated.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/install.js +26 -7
- package/lib/commonjs/install.js.map +1 -1
- package/lib/module/Canvas.js +51 -0
- package/lib/module/Canvas.js.map +1 -1
- package/lib/module/external/reanimated/registerWebGPUForReanimated.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/install.js +26 -7
- package/lib/module/install.js.map +1 -1
- package/lib/typescript/lib/commonjs/Canvas.d.ts.map +1 -1
- package/lib/typescript/lib/commonjs/install.d.ts +10 -7
- package/lib/typescript/lib/commonjs/install.d.ts.map +1 -1
- package/lib/typescript/lib/module/Canvas.d.ts.map +1 -1
- package/lib/typescript/lib/module/install.d.ts.map +1 -1
- package/lib/typescript/src/Canvas.d.ts +20 -35
- package/lib/typescript/src/Canvas.d.ts.map +1 -1
- package/lib/typescript/src/external/reanimated/registerWebGPUForReanimated.d.ts +1 -0
- package/lib/typescript/src/external/reanimated/registerWebGPUForReanimated.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/install.d.ts +10 -7
- package/lib/typescript/src/install.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +34 -1
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +6 -5
- package/plugin/build/index.js +2 -1
- package/plugin/build/setMinSdkVersion.d.ts +9 -0
- package/plugin/build/setMinSdkVersion.js +25 -0
- package/plugin/build/withMinSdkVersion.d.ts +11 -0
- package/plugin/build/withMinSdkVersion.js +20 -0
- package/src/Canvas.tsx +74 -40
- package/src/external/reanimated/registerWebGPUForReanimated.ts +4 -0
- package/src/index.tsx +24 -0
- package/src/install.ts +26 -7
- package/src/types.ts +98 -1
package/android/CMakeLists.txt
CHANGED
|
@@ -46,6 +46,8 @@ add_library(${PACKAGE_NAME} SHARED
|
|
|
46
46
|
../cpp/rnwgpu/api/GPUSharedFence.cpp
|
|
47
47
|
../cpp/rnwgpu/api/GPUSharedTextureMemory.cpp
|
|
48
48
|
../cpp/rnwgpu/api/GPUExternalTexture.cpp
|
|
49
|
+
../cpp/rnwgpu/api/GPUNativeBufferWrapper.cpp
|
|
50
|
+
../cpp/rnwgpu/api/NativeFrameImport.cpp
|
|
49
51
|
../cpp/rnwgpu/api/GPURenderBundleEncoder.cpp
|
|
50
52
|
../cpp/rnwgpu/api/GPURenderPassEncoder.cpp
|
|
51
53
|
../cpp/rnwgpu/api/GPURenderPipeline.cpp
|
|
@@ -96,6 +98,8 @@ if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76)
|
|
|
96
98
|
log
|
|
97
99
|
jnigraphics
|
|
98
100
|
android
|
|
101
|
+
# sync_merge, for the one fence endAccess() returns
|
|
102
|
+
sync
|
|
99
103
|
fbjni::fbjni
|
|
100
104
|
ReactAndroid::jsi
|
|
101
105
|
ReactAndroid::reactnative
|
|
@@ -108,6 +112,7 @@ else()
|
|
|
108
112
|
log
|
|
109
113
|
jnigraphics
|
|
110
114
|
android
|
|
115
|
+
sync
|
|
111
116
|
fbjni::fbjni
|
|
112
117
|
ReactAndroid::jsi
|
|
113
118
|
ReactAndroid::reactnativejni
|
package/android/build.gradle
CHANGED
|
@@ -75,6 +75,16 @@ def getExtOrIntegerDefault(name) {
|
|
|
75
75
|
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Wgpu_" + name]).toInteger()
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
// androidMinSdkVersion in package.json is the lowest API level the native code
|
|
79
|
+
// builds for: it calls the AHardwareBuffer API, from Android 8 (API 26). An
|
|
80
|
+
// app's lower minSdkVersion does not lower it, so such an app gets the
|
|
81
|
+
// manifest merger's error, with its suggested fix, instead of the compiler's.
|
|
82
|
+
def resolveMinSdkVersion() {
|
|
83
|
+
def floor = new groovy.json.JsonSlurper().parse(file("../package.json")).androidMinSdkVersion as Integer
|
|
84
|
+
def app = rootProject.ext.has("minSdkVersion") ? rootProject.ext.get("minSdkVersion").toString().toInteger() : floor
|
|
85
|
+
return Math.max(app, floor)
|
|
86
|
+
}
|
|
87
|
+
|
|
78
88
|
def supportsNamespace() {
|
|
79
89
|
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
|
|
80
90
|
def major = parsed[0].toInteger()
|
|
@@ -88,7 +98,7 @@ android {
|
|
|
88
98
|
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
89
99
|
|
|
90
100
|
defaultConfig {
|
|
91
|
-
minSdkVersion
|
|
101
|
+
minSdkVersion resolveMinSdkVersion()
|
|
92
102
|
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
93
103
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true"
|
|
94
104
|
externalNativeBuild {
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
#include <unistd.h>
|
|
2
2
|
|
|
3
|
+
#include <atomic>
|
|
4
|
+
#include <chrono>
|
|
5
|
+
#include <cstdint>
|
|
3
6
|
#include <cstring>
|
|
4
7
|
#include <memory>
|
|
8
|
+
#include <mutex>
|
|
5
9
|
#include <string>
|
|
6
10
|
#include <unordered_map>
|
|
7
11
|
#include <utility>
|
|
@@ -160,10 +164,10 @@ namespace {
|
|
|
160
164
|
|
|
161
165
|
// Weak handle on a Java view and one of its no-argument methods, for native
|
|
162
166
|
// threads to call into it: the fence waiter wakes a WebGPUHardwareBufferView
|
|
163
|
-
// when a frame is ready, the presenting thread
|
|
164
|
-
//
|
|
165
|
-
// is not kept alive by a SurfaceInfo that outlives it (the
|
|
166
|
-
// SurfaceInfo until onDropViewInstance).
|
|
167
|
+
// when a frame is ready, the presenting thread tells a SurfaceView it is
|
|
168
|
+
// animating (AnimationReporter). Holding the view weakly means a view that RN
|
|
169
|
+
// has dropped is not kept alive by a SurfaceInfo that outlives it (the
|
|
170
|
+
// registry keeps the SurfaceInfo until onDropViewInstance).
|
|
167
171
|
struct WeakViewCall {
|
|
168
172
|
jweak view = nullptr;
|
|
169
173
|
jmethodID method = nullptr;
|
|
@@ -203,11 +207,84 @@ struct WeakViewCall {
|
|
|
203
207
|
}
|
|
204
208
|
};
|
|
205
209
|
|
|
210
|
+
// Tells a SurfaceView it starts animating, for its refresh rate vote
|
|
211
|
+
// (WebGPUView.onAnimating), from the frames presented to its surface. A call
|
|
212
|
+
// into Java on the render path costs a JNI transition and attaches a presenting
|
|
213
|
+
// thread the JVM doesn't know for the length of the call, so there is one call
|
|
214
|
+
// per animation: the first frame presented right after its predecessor calls,
|
|
215
|
+
// the frames after it only record the time. The view polls that time from the
|
|
216
|
+
// main thread (releaseIfIdle) and, once the animation has stopped, takes its
|
|
217
|
+
// vote back and re-arms the call for the next animation. Lock-free, since it
|
|
218
|
+
// runs on every present.
|
|
219
|
+
class AnimationReporter {
|
|
220
|
+
public:
|
|
221
|
+
AnimationReporter(JNIEnv *env, jobject view)
|
|
222
|
+
: _onAnimating(env, view, "onAnimating") {}
|
|
223
|
+
|
|
224
|
+
void onFramePresented() {
|
|
225
|
+
const int64_t now = nowNanos();
|
|
226
|
+
const int64_t previous = _lastPresent.exchange(now);
|
|
227
|
+
if (now - previous > kAnimatingIntervalNanos) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
_lastAnimating.store(now);
|
|
231
|
+
if (!_reported.exchange(true)) {
|
|
232
|
+
_onAnimating();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Main thread. Returns how much longer the animation must stay stopped
|
|
237
|
+
// before it counts as idle, or 0 once it has: the next animating frame then
|
|
238
|
+
// calls into Java again.
|
|
239
|
+
int64_t releaseIfIdle(int64_t idleAfterNanos) {
|
|
240
|
+
const int64_t idle = nowNanos() - _lastAnimating.load();
|
|
241
|
+
if (idle < idleAfterNanos) {
|
|
242
|
+
return idleAfterNanos - idle;
|
|
243
|
+
}
|
|
244
|
+
_reported.store(false);
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private:
|
|
249
|
+
// Presents this close together are an animation (faster than 40 fps).
|
|
250
|
+
static constexpr int64_t kAnimatingIntervalNanos = 25'000'000;
|
|
251
|
+
|
|
252
|
+
static int64_t nowNanos() {
|
|
253
|
+
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
254
|
+
std::chrono::steady_clock::now().time_since_epoch())
|
|
255
|
+
.count();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
WeakViewCall _onAnimating;
|
|
259
|
+
// INT64_MIN / 2 rather than a sentinel: the first present is never "right
|
|
260
|
+
// after" it, and the subtractions above don't overflow.
|
|
261
|
+
std::atomic<int64_t> _lastPresent{INT64_MIN / 2};
|
|
262
|
+
std::atomic<int64_t> _lastAnimating{INT64_MIN / 2};
|
|
263
|
+
// Java has been told about the current animation.
|
|
264
|
+
std::atomic<bool> _reported{false};
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// The reporters by contextId, for WebGPUView.releaseFrameRateVoteIfIdle. The
|
|
268
|
+
// surface callbacks and the idle check all run on the main thread; the lock
|
|
269
|
+
// only guards against a caller that doesn't.
|
|
270
|
+
std::mutex animationReportersMutex;
|
|
271
|
+
std::unordered_map<int, std::shared_ptr<AnimationReporter>> animationReporters;
|
|
272
|
+
|
|
273
|
+
void setAnimationReporter(int contextId,
|
|
274
|
+
std::shared_ptr<AnimationReporter> reporter) {
|
|
275
|
+
std::lock_guard<std::mutex> lock(animationReportersMutex);
|
|
276
|
+
if (reporter) {
|
|
277
|
+
animationReporters[contextId] = std::move(reporter);
|
|
278
|
+
} else {
|
|
279
|
+
animationReporters.erase(contextId);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
206
283
|
} // namespace
|
|
207
284
|
|
|
208
285
|
extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceCreate(
|
|
209
286
|
JNIEnv *env, jobject thiz, jobject jSurface, jint contextId, jfloat width,
|
|
210
|
-
jfloat height, jboolean
|
|
287
|
+
jfloat height, jboolean reportAnimation) {
|
|
211
288
|
if (manager == nullptr) {
|
|
212
289
|
return;
|
|
213
290
|
}
|
|
@@ -228,13 +305,15 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceCreate(
|
|
|
228
305
|
surface, [](void *nativeSurface) {
|
|
229
306
|
ANativeWindow_release(static_cast<ANativeWindow *>(nativeSurface));
|
|
230
307
|
});
|
|
231
|
-
// A SurfaceView asks to hear
|
|
232
|
-
//
|
|
233
|
-
if (
|
|
234
|
-
auto reporter =
|
|
235
|
-
|
|
236
|
-
info->setFramePresentedCallback(
|
|
308
|
+
// A SurfaceView asks to hear when it animates, for its refresh rate vote
|
|
309
|
+
// (WebGPUView.onAnimating).
|
|
310
|
+
if (reportAnimation) {
|
|
311
|
+
auto reporter = std::make_shared<AnimationReporter>(env, thiz);
|
|
312
|
+
setAnimationReporter(contextId, reporter);
|
|
313
|
+
info->setFramePresentedCallback(
|
|
314
|
+
[reporter]() { reporter->onFramePresented(); });
|
|
237
315
|
} else {
|
|
316
|
+
setAnimationReporter(contextId, nullptr);
|
|
238
317
|
info->setFramePresentedCallback(nullptr);
|
|
239
318
|
}
|
|
240
319
|
// The attach is adopted at the next frame boundary by the rendering thread;
|
|
@@ -246,6 +325,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceCreate(
|
|
|
246
325
|
extern "C" JNIEXPORT void JNICALL
|
|
247
326
|
Java_com_webgpu_WebGPUView_switchToOffscreenSurface(JNIEnv *env, jobject thiz,
|
|
248
327
|
jint contextId) {
|
|
328
|
+
setAnimationReporter(contextId, nullptr);
|
|
249
329
|
auto ®istry = rnwgpu::SurfaceRegistry::getInstance();
|
|
250
330
|
if (auto info = registry.getSurfaceInfo(contextId)) {
|
|
251
331
|
info->setFramePresentedCallback(nullptr);
|
|
@@ -253,12 +333,30 @@ Java_com_webgpu_WebGPUView_switchToOffscreenSurface(JNIEnv *env, jobject thiz,
|
|
|
253
333
|
}
|
|
254
334
|
}
|
|
255
335
|
|
|
336
|
+
// See AnimationReporter::releaseIfIdle; 0 when the surface reports nothing.
|
|
337
|
+
extern "C" JNIEXPORT jlong JNICALL
|
|
338
|
+
Java_com_webgpu_WebGPUView_releaseFrameRateVoteIfIdle(JNIEnv *env, jobject thiz,
|
|
339
|
+
jint contextId,
|
|
340
|
+
jlong idleAfterNanos) {
|
|
341
|
+
std::shared_ptr<AnimationReporter> reporter;
|
|
342
|
+
{
|
|
343
|
+
std::lock_guard<std::mutex> lock(animationReportersMutex);
|
|
344
|
+
auto it = animationReporters.find(contextId);
|
|
345
|
+
if (it == animationReporters.end()) {
|
|
346
|
+
return 0;
|
|
347
|
+
}
|
|
348
|
+
reporter = it->second;
|
|
349
|
+
}
|
|
350
|
+
return static_cast<jlong>(reporter->releaseIfIdle(idleAfterNanos));
|
|
351
|
+
}
|
|
352
|
+
|
|
256
353
|
extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onViewDestroyed(
|
|
257
354
|
JNIEnv *env, jobject thiz, jint contextId) {
|
|
258
355
|
// The view dies with its Canvas (contextIds are never reused), so view
|
|
259
356
|
// teardown retires the registry entry. The JS-side cleanup
|
|
260
357
|
// (RNWebGPU.destroyContext) only handles entries that never had a native
|
|
261
358
|
// surface; see RNWebGPU::destroyContext for the ownership split.
|
|
359
|
+
setAnimationReporter(contextId, nullptr);
|
|
262
360
|
auto ®istry = rnwgpu::SurfaceRegistry::getInstance();
|
|
263
361
|
if (auto info = registry.getSurfaceInfo(contextId)) {
|
|
264
362
|
info->setFramePresentedCallback(nullptr);
|
|
@@ -24,134 +24,146 @@ import androidx.annotation.Nullable;
|
|
|
24
24
|
* drops back to a few redraws a second: a layer with a vote counts as active
|
|
25
25
|
* for SurfaceFlinger even when it no longer draws.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* Native code tells onAnimating() once per animation, on the thread that
|
|
28
|
+
* presents (the JS thread, the Reanimated UI runtime or a worklet runtime),
|
|
29
|
+
* and the vote is cast right there, with no hop to a main thread that may be
|
|
30
|
+
* busy. The rate is resolved ahead of time on the main thread, where the
|
|
31
|
+
* view's display can be read. The idle check runs on the main thread and asks
|
|
32
|
+
* native code how long ago the canvas last animated; once it has stopped, it
|
|
33
|
+
* takes the vote back and native code calls onAnimating() again at the next
|
|
34
|
+
* animation. The lock only guards the state: the binder calls
|
|
35
|
+
* (Surface.setFrameRate) are made outside it.
|
|
30
36
|
*/
|
|
31
37
|
final class SurfaceFrameRateVote {
|
|
32
38
|
private static final String TAG = "SurfaceFrameRateVote";
|
|
33
39
|
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// a
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
// The vote is taken back once the canvas has not animated for this long,
|
|
41
|
+
// whether it stops drawing or drops back to a few redraws a second. Native
|
|
42
|
+
// code decides what animating means (presents faster than 40 fps, see
|
|
43
|
+
// AnimationReporter in cpp-adapter.cpp). The threshold is absolute rather
|
|
44
|
+
// than relative to the current refresh rate, so that a canvas that only
|
|
45
|
+
// manages 60 fps at 120 Hz keeps its vote rather than oscillating between
|
|
46
|
+
// the two rates. A canvas that redraws a few times a second never votes.
|
|
47
|
+
static final long IDLE_AFTER_NANOS = 500_000_000L;
|
|
48
|
+
|
|
49
|
+
/** Native side of the vote, see AnimationReporter in cpp-adapter.cpp. */
|
|
50
|
+
interface AnimationSource {
|
|
51
|
+
/**
|
|
52
|
+
* How much longer the canvas must stay still before it counts as idle,
|
|
53
|
+
* or 0 once it does, in which case the next animation calls
|
|
54
|
+
* onAnimating() again. Main thread.
|
|
55
|
+
*/
|
|
56
|
+
long releaseIfIdle(long idleAfterNanos);
|
|
57
|
+
}
|
|
47
58
|
|
|
48
59
|
private final View mView;
|
|
60
|
+
private final AnimationSource mSource;
|
|
49
61
|
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
|
50
62
|
private final Runnable mIdleCheck = this::checkIdle;
|
|
51
63
|
|
|
52
64
|
@Nullable
|
|
53
65
|
private Surface mSurface;
|
|
54
66
|
private boolean mVoting = false;
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
private
|
|
58
|
-
// The last present that followed its predecessor within
|
|
59
|
-
// ANIMATING_INTERVAL_NANOS; 0 until the first.
|
|
60
|
-
private long mLastAnimatingNanos = 0;
|
|
67
|
+
// The rate to ask for, resolved on the main thread; 0 when unknown, in
|
|
68
|
+
// which case animations don't vote until the next idle check resolves it.
|
|
69
|
+
private float mRefreshRate = 0f;
|
|
61
70
|
|
|
62
71
|
/** The view the surface belongs to; its display decides the rate to ask for. */
|
|
63
|
-
SurfaceFrameRateVote(View view) {
|
|
72
|
+
SurfaceFrameRateVote(View view, AnimationSource source) {
|
|
64
73
|
mView = view;
|
|
74
|
+
mSource = source;
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
/** The surface was created: it is voted on until detach(). Main thread. */
|
|
68
|
-
|
|
78
|
+
void attach(Surface surface) {
|
|
69
79
|
detach();
|
|
70
80
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
|
71
81
|
return;
|
|
72
82
|
}
|
|
73
|
-
|
|
83
|
+
float refreshRate = resolveMaxRefreshRate();
|
|
84
|
+
synchronized (this) {
|
|
85
|
+
mSurface = surface;
|
|
86
|
+
mRefreshRate = refreshRate;
|
|
87
|
+
}
|
|
74
88
|
}
|
|
75
89
|
|
|
76
|
-
/**
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (!mVoting) {
|
|
87
|
-
vote();
|
|
90
|
+
/**
|
|
91
|
+
* The canvas started animating (presenting frames back to back). Called
|
|
92
|
+
* once per animation, on the presenting thread.
|
|
93
|
+
*/
|
|
94
|
+
void onAnimating() {
|
|
95
|
+
Surface surface;
|
|
96
|
+
float refreshRate;
|
|
97
|
+
synchronized (this) {
|
|
98
|
+
if (mSurface == null) {
|
|
99
|
+
return;
|
|
88
100
|
}
|
|
101
|
+
surface = mSurface;
|
|
102
|
+
refreshRate = mRefreshRate;
|
|
103
|
+
mVoting = refreshRate > 0;
|
|
89
104
|
}
|
|
90
|
-
|
|
91
|
-
|
|
105
|
+
// Whether it votes or not, the idle check re-arms native code once the
|
|
106
|
+
// animation stops.
|
|
107
|
+
mHandler.postDelayed(mIdleCheck, IDLE_AFTER_NANOS / 1_000_000);
|
|
108
|
+
if (refreshRate > 0) {
|
|
109
|
+
// This form only switches when the display can do so seamlessly, like
|
|
110
|
+
// the vote the window casts for its own views: a canvas animation is
|
|
111
|
+
// not worth a blank screen. Surface.setFrameRate is thread-safe.
|
|
112
|
+
setFrameRate(surface, refreshRate);
|
|
92
113
|
}
|
|
93
114
|
}
|
|
94
115
|
|
|
95
116
|
/** The surface is going away (it is still valid here) or the view left its window. Main thread. */
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
117
|
+
void detach() {
|
|
118
|
+
Surface surface;
|
|
119
|
+
boolean voting;
|
|
120
|
+
synchronized (this) {
|
|
121
|
+
surface = mSurface;
|
|
122
|
+
voting = mVoting;
|
|
123
|
+
mSurface = null;
|
|
124
|
+
mVoting = false;
|
|
99
125
|
}
|
|
100
|
-
withdraw();
|
|
101
126
|
mHandler.removeCallbacks(mIdleCheck);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private void vote() {
|
|
109
|
-
float refreshRate = resolveMaxRefreshRate();
|
|
110
|
-
if (refreshRate <= 0 || !mSurface.isValid()) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
try {
|
|
114
|
-
// This form only switches when the display can do so seamlessly, like
|
|
115
|
-
// the vote the window casts for its own views: a canvas animation is
|
|
116
|
-
// not worth a blank screen. Surface.setFrameRate is thread-safe.
|
|
117
|
-
mSurface.setFrameRate(refreshRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
|
|
118
|
-
mVoting = true;
|
|
119
|
-
} catch (RuntimeException e) {
|
|
120
|
-
Log.w(TAG, "Could not vote the refresh rate, leaving it to the display", e);
|
|
121
|
-
detach();
|
|
127
|
+
// A vote cast concurrently on the presenting thread may land after this;
|
|
128
|
+
// it dies with the surface, which is going away.
|
|
129
|
+
if (surface != null && voting) {
|
|
130
|
+
setFrameRate(surface, 0);
|
|
122
131
|
}
|
|
123
132
|
}
|
|
124
133
|
|
|
125
|
-
private void
|
|
126
|
-
|
|
134
|
+
private void checkIdle() {
|
|
135
|
+
long remainingNanos = mSource.releaseIfIdle(IDLE_AFTER_NANOS);
|
|
136
|
+
if (remainingNanos > 0) {
|
|
137
|
+
mHandler.postDelayed(mIdleCheck, (remainingNanos + 999_999) / 1_000_000);
|
|
127
138
|
return;
|
|
128
139
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
// Native code is re-armed: the next onAnimating() can only come after
|
|
141
|
+
// this point, so it doesn't race with the withdrawal below.
|
|
142
|
+
float refreshRate = resolveMaxRefreshRate();
|
|
143
|
+
Surface surface;
|
|
144
|
+
boolean voting;
|
|
145
|
+
synchronized (this) {
|
|
146
|
+
surface = mSurface;
|
|
147
|
+
voting = mVoting;
|
|
148
|
+
mVoting = false;
|
|
149
|
+
if (surface != null) {
|
|
150
|
+
// Follows the display the view is on, for the next animation.
|
|
151
|
+
mRefreshRate = refreshRate;
|
|
152
|
+
}
|
|
132
153
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
} catch (RuntimeException e) {
|
|
136
|
-
Log.w(TAG, "Could not withdraw the refresh rate vote", e);
|
|
154
|
+
if (surface != null && voting) {
|
|
155
|
+
setFrameRate(surface, 0);
|
|
137
156
|
}
|
|
138
157
|
}
|
|
139
158
|
|
|
140
|
-
private void
|
|
141
|
-
|
|
142
|
-
mHandler.postDelayed(mIdleCheck, (delayNanos + 999_999) / 1_000_000);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
private synchronized void checkIdle() {
|
|
146
|
-
mIdleCheckPending = false;
|
|
147
|
-
if (!mVoting) {
|
|
159
|
+
private static void setFrameRate(Surface surface, float refreshRate) {
|
|
160
|
+
if (!surface.isValid()) {
|
|
148
161
|
return;
|
|
149
162
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
scheduleIdleCheck(IDLE_AFTER_NANOS - idleNanos);
|
|
163
|
+
try {
|
|
164
|
+
surface.setFrameRate(refreshRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
|
|
165
|
+
} catch (RuntimeException e) {
|
|
166
|
+
Log.w(TAG, "Could not set the surface's frame rate, leaving it to the display", e);
|
|
155
167
|
}
|
|
156
168
|
}
|
|
157
169
|
|