react-native-webgpu 0.15.1 → 0.16.0
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/cpp/cpp-adapter.cpp +160 -49
- package/android/src/main/java/com/webgpu/SurfaceFrameRateVote.java +196 -0
- package/android/src/main/java/com/webgpu/WebGPUView.java +29 -2
- package/apple/ApplePlatformContext.h +1 -0
- package/cpp/jsi/JSICache.cpp +2 -0
- package/cpp/jsi/NativeObject.h +36 -12
- package/cpp/rnwgpu/RNWebGPUManager.cpp +4 -0
- package/cpp/rnwgpu/SurfaceRegistry.h +28 -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 +142 -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.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/Canvas.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/Canvas.d.ts +2 -31
- package/lib/typescript/src/Canvas.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/types.d.ts +33 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Canvas.tsx +3 -36
- package/src/index.tsx +24 -0
- package/src/types.ts +97 -0
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
|
|
@@ -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>
|
|
@@ -156,9 +160,131 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceChanged(
|
|
|
156
160
|
}
|
|
157
161
|
}
|
|
158
162
|
|
|
163
|
+
namespace {
|
|
164
|
+
|
|
165
|
+
// Weak handle on a Java view and one of its no-argument methods, for native
|
|
166
|
+
// threads to call into it: the fence waiter wakes a WebGPUHardwareBufferView
|
|
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).
|
|
171
|
+
struct WeakViewCall {
|
|
172
|
+
jweak view = nullptr;
|
|
173
|
+
jmethodID method = nullptr;
|
|
174
|
+
|
|
175
|
+
WeakViewCall(JNIEnv *env, jobject thiz, const char *methodName) {
|
|
176
|
+
view = env->NewWeakGlobalRef(thiz);
|
|
177
|
+
jclass cls = env->GetObjectClass(thiz);
|
|
178
|
+
method = env->GetMethodID(cls, methodName, "()V");
|
|
179
|
+
env->DeleteLocalRef(cls);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
~WeakViewCall() {
|
|
183
|
+
// May run on any thread; ThreadScope attaches if needed.
|
|
184
|
+
facebook::jni::ThreadScope scope;
|
|
185
|
+
JNIEnv *env = facebook::jni::Environment::current();
|
|
186
|
+
if (env != nullptr && view != nullptr) {
|
|
187
|
+
env->DeleteWeakGlobalRef(view);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Called on a native thread, outside the SurfaceInfo locks.
|
|
192
|
+
void operator()() const {
|
|
193
|
+
facebook::jni::ThreadScope scope;
|
|
194
|
+
JNIEnv *env = facebook::jni::Environment::current();
|
|
195
|
+
if (env == nullptr || method == nullptr) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
jobject local = env->NewLocalRef(view);
|
|
199
|
+
if (local == nullptr) {
|
|
200
|
+
return; // the view was garbage collected
|
|
201
|
+
}
|
|
202
|
+
env->CallVoidMethod(local, method);
|
|
203
|
+
if (env->ExceptionCheck()) {
|
|
204
|
+
env->ExceptionClear();
|
|
205
|
+
}
|
|
206
|
+
env->DeleteLocalRef(local);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
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
|
+
|
|
283
|
+
} // namespace
|
|
284
|
+
|
|
159
285
|
extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceCreate(
|
|
160
286
|
JNIEnv *env, jobject thiz, jobject jSurface, jint contextId, jfloat width,
|
|
161
|
-
jfloat height) {
|
|
287
|
+
jfloat height, jboolean reportAnimation) {
|
|
162
288
|
if (manager == nullptr) {
|
|
163
289
|
return;
|
|
164
290
|
}
|
|
@@ -179,6 +305,17 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceCreate(
|
|
|
179
305
|
surface, [](void *nativeSurface) {
|
|
180
306
|
ANativeWindow_release(static_cast<ANativeWindow *>(nativeSurface));
|
|
181
307
|
});
|
|
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(); });
|
|
315
|
+
} else {
|
|
316
|
+
setAnimationReporter(contextId, nullptr);
|
|
317
|
+
info->setFramePresentedCallback(nullptr);
|
|
318
|
+
}
|
|
182
319
|
// The attach is adopted at the next frame boundary by the rendering thread;
|
|
183
320
|
// schedule a flush so contexts that are not currently rendering still pick
|
|
184
321
|
// it up (and present their last offscreen frame).
|
|
@@ -188,20 +325,41 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onSurfaceCreate(
|
|
|
188
325
|
extern "C" JNIEXPORT void JNICALL
|
|
189
326
|
Java_com_webgpu_WebGPUView_switchToOffscreenSurface(JNIEnv *env, jobject thiz,
|
|
190
327
|
jint contextId) {
|
|
328
|
+
setAnimationReporter(contextId, nullptr);
|
|
191
329
|
auto ®istry = rnwgpu::SurfaceRegistry::getInstance();
|
|
192
330
|
if (auto info = registry.getSurfaceInfo(contextId)) {
|
|
331
|
+
info->setFramePresentedCallback(nullptr);
|
|
193
332
|
info->switchToOffscreen();
|
|
194
333
|
}
|
|
195
334
|
}
|
|
196
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
|
+
|
|
197
353
|
extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onViewDestroyed(
|
|
198
354
|
JNIEnv *env, jobject thiz, jint contextId) {
|
|
199
355
|
// The view dies with its Canvas (contextIds are never reused), so view
|
|
200
356
|
// teardown retires the registry entry. The JS-side cleanup
|
|
201
357
|
// (RNWebGPU.destroyContext) only handles entries that never had a native
|
|
202
358
|
// surface; see RNWebGPU::destroyContext for the ownership split.
|
|
359
|
+
setAnimationReporter(contextId, nullptr);
|
|
203
360
|
auto ®istry = rnwgpu::SurfaceRegistry::getInstance();
|
|
204
361
|
if (auto info = registry.getSurfaceInfo(contextId)) {
|
|
362
|
+
info->setFramePresentedCallback(nullptr);
|
|
205
363
|
info->detachSurface();
|
|
206
364
|
}
|
|
207
365
|
registry.removeSurfaceInfo(contextId);
|
|
@@ -210,53 +368,6 @@ extern "C" JNIEXPORT void JNICALL Java_com_webgpu_WebGPUView_onViewDestroyed(
|
|
|
210
368
|
// --- WebGPUHardwareBufferView (AHB-pool presentation)
|
|
211
369
|
// ---------------------------------
|
|
212
370
|
|
|
213
|
-
namespace {
|
|
214
|
-
|
|
215
|
-
// Weak handle on a WebGPUHardwareBufferView used by the native fence waiter to
|
|
216
|
-
// wake the UI thread when a frame is ready. Holding it weakly means a view that
|
|
217
|
-
// RN has dropped is not kept alive by a SurfaceInfo that outlives it (the
|
|
218
|
-
// registry keeps the SurfaceInfo until onDropViewInstance).
|
|
219
|
-
struct HardwareBufferViewWaker {
|
|
220
|
-
jweak view = nullptr;
|
|
221
|
-
jmethodID onFrameReady = nullptr;
|
|
222
|
-
|
|
223
|
-
HardwareBufferViewWaker(JNIEnv *env, jobject thiz) {
|
|
224
|
-
view = env->NewWeakGlobalRef(thiz);
|
|
225
|
-
jclass cls = env->GetObjectClass(thiz);
|
|
226
|
-
onFrameReady = env->GetMethodID(cls, "onNativeFrameReady", "()V");
|
|
227
|
-
env->DeleteLocalRef(cls);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
~HardwareBufferViewWaker() {
|
|
231
|
-
// May run on the waiter or JS thread; ThreadScope attaches if needed.
|
|
232
|
-
facebook::jni::ThreadScope scope;
|
|
233
|
-
JNIEnv *env = facebook::jni::Environment::current();
|
|
234
|
-
if (env != nullptr && view != nullptr) {
|
|
235
|
-
env->DeleteWeakGlobalRef(view);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Called on the waiter thread, outside the pool lock.
|
|
240
|
-
void operator()() const {
|
|
241
|
-
facebook::jni::ThreadScope scope;
|
|
242
|
-
JNIEnv *env = facebook::jni::Environment::current();
|
|
243
|
-
if (env == nullptr || onFrameReady == nullptr) {
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
jobject local = env->NewLocalRef(view);
|
|
247
|
-
if (local == nullptr) {
|
|
248
|
-
return; // the view was garbage collected
|
|
249
|
-
}
|
|
250
|
-
env->CallVoidMethod(local, onFrameReady);
|
|
251
|
-
if (env->ExceptionCheck()) {
|
|
252
|
-
env->ExceptionClear();
|
|
253
|
-
}
|
|
254
|
-
env->DeleteLocalRef(local);
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
} // namespace
|
|
259
|
-
|
|
260
371
|
// Turn on pool mode for this context. dpW/dpH is the canvas-client (dp) size;
|
|
261
372
|
// the native pool buffers are sized from the canvas drawing buffer lazily.
|
|
262
373
|
extern "C" JNIEXPORT void JNICALL
|
|
@@ -267,7 +378,7 @@ Java_com_webgpu_WebGPUHardwareBufferView_nEnablePool(JNIEnv *env, jobject thiz,
|
|
|
267
378
|
auto info = registry.getSurfaceInfoOrCreate(
|
|
268
379
|
contextId, rnwgpu::WebGPURuntime::shared().instance(),
|
|
269
380
|
static_cast<int>(dpW), static_cast<int>(dpH));
|
|
270
|
-
auto waker = std::make_shared<
|
|
381
|
+
auto waker = std::make_shared<WeakViewCall>(env, thiz, "onNativeFrameReady");
|
|
271
382
|
info->setPoolFrameReadyCallback([waker]() { (*waker)(); });
|
|
272
383
|
info->enablePool(static_cast<int>(dpW), static_cast<int>(dpH));
|
|
273
384
|
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
package com.webgpu;
|
|
2
|
+
|
|
3
|
+
import android.os.Build;
|
|
4
|
+
import android.os.Handler;
|
|
5
|
+
import android.os.Looper;
|
|
6
|
+
import android.util.Log;
|
|
7
|
+
import android.view.Display;
|
|
8
|
+
import android.view.Surface;
|
|
9
|
+
import android.view.View;
|
|
10
|
+
|
|
11
|
+
import androidx.annotation.Nullable;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Votes the display's refresh rate for a SurfaceView canvas while it animates.
|
|
15
|
+
*
|
|
16
|
+
* A SurfaceView is composited in a layer of its own, outside the window that
|
|
17
|
+
* votes for the app's views, so SurfaceFlinger picks the layer's refresh rate
|
|
18
|
+
* from its present history alone: a canvas drawing every frame on a display
|
|
19
|
+
* idling at 60 Hz looks like 60 Hz content and never gets more. Since Android
|
|
20
|
+
* 11 a layer can say what it wants with Surface.setFrameRate. Once the canvas
|
|
21
|
+
* presents frames back to back, this asks for the highest rate the display can
|
|
22
|
+
* switch to seamlessly, as the window does for its own views, and takes the
|
|
23
|
+
* vote back once the animation stops, whether the canvas stops drawing or
|
|
24
|
+
* drops back to a few redraws a second: a layer with a vote counts as active
|
|
25
|
+
* for SurfaceFlinger even when it no longer draws.
|
|
26
|
+
*
|
|
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.
|
|
36
|
+
*/
|
|
37
|
+
final class SurfaceFrameRateVote {
|
|
38
|
+
private static final String TAG = "SurfaceFrameRateVote";
|
|
39
|
+
|
|
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
|
+
}
|
|
58
|
+
|
|
59
|
+
private final View mView;
|
|
60
|
+
private final AnimationSource mSource;
|
|
61
|
+
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
|
62
|
+
private final Runnable mIdleCheck = this::checkIdle;
|
|
63
|
+
|
|
64
|
+
@Nullable
|
|
65
|
+
private Surface mSurface;
|
|
66
|
+
private boolean mVoting = false;
|
|
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;
|
|
70
|
+
|
|
71
|
+
/** The view the surface belongs to; its display decides the rate to ask for. */
|
|
72
|
+
SurfaceFrameRateVote(View view, AnimationSource source) {
|
|
73
|
+
mView = view;
|
|
74
|
+
mSource = source;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The surface was created: it is voted on until detach(). Main thread. */
|
|
78
|
+
void attach(Surface surface) {
|
|
79
|
+
detach();
|
|
80
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
float refreshRate = resolveMaxRefreshRate();
|
|
84
|
+
synchronized (this) {
|
|
85
|
+
mSurface = surface;
|
|
86
|
+
mRefreshRate = refreshRate;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
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;
|
|
100
|
+
}
|
|
101
|
+
surface = mSurface;
|
|
102
|
+
refreshRate = mRefreshRate;
|
|
103
|
+
mVoting = refreshRate > 0;
|
|
104
|
+
}
|
|
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);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The surface is going away (it is still valid here) or the view left its window. Main thread. */
|
|
117
|
+
void detach() {
|
|
118
|
+
Surface surface;
|
|
119
|
+
boolean voting;
|
|
120
|
+
synchronized (this) {
|
|
121
|
+
surface = mSurface;
|
|
122
|
+
voting = mVoting;
|
|
123
|
+
mSurface = null;
|
|
124
|
+
mVoting = false;
|
|
125
|
+
}
|
|
126
|
+
mHandler.removeCallbacks(mIdleCheck);
|
|
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);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
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);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
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
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (surface != null && voting) {
|
|
155
|
+
setFrameRate(surface, 0);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private static void setFrameRate(Surface surface, float refreshRate) {
|
|
160
|
+
if (!surface.isValid()) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
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);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// The highest rate a seamless switch from the current mode can reach, which
|
|
171
|
+
// is all the vote may ask for. Resolved when the vote is cast, so that it
|
|
172
|
+
// follows the display the view is on.
|
|
173
|
+
private float resolveMaxRefreshRate() {
|
|
174
|
+
Display display = mView.getDisplay();
|
|
175
|
+
if (display == null) {
|
|
176
|
+
return 0f;
|
|
177
|
+
}
|
|
178
|
+
Display.Mode current = display.getMode();
|
|
179
|
+
float maxRefreshRate = current.getRefreshRate();
|
|
180
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
181
|
+
for (float refreshRate : current.getAlternativeRefreshRates()) {
|
|
182
|
+
maxRefreshRate = Math.max(maxRefreshRate, refreshRate);
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
// Android 11 does not tell which modes are a seamless switch away: the
|
|
186
|
+
// ones at the current resolution usually are.
|
|
187
|
+
for (Display.Mode mode : display.getSupportedModes()) {
|
|
188
|
+
if (mode.getPhysicalWidth() == current.getPhysicalWidth()
|
|
189
|
+
&& mode.getPhysicalHeight() == current.getPhysicalHeight()) {
|
|
190
|
+
maxRefreshRate = Math.max(maxRefreshRate, mode.getRefreshRate());
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return maxRefreshRate;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -24,6 +24,10 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
|
|
|
24
24
|
private boolean mAppliedZOrderOnTop;
|
|
25
25
|
private WebGPUModule mModule;
|
|
26
26
|
private View mView = null;
|
|
27
|
+
// Only a SurfaceView votes: a TextureView or HardwareBufferView is
|
|
28
|
+
// composited into the window, which votes for it.
|
|
29
|
+
private final SurfaceFrameRateVote mFrameRateVote = new SurfaceFrameRateVote(
|
|
30
|
+
this, idleAfterNanos -> releaseFrameRateVoteIfIdle(mContextId, idleAfterNanos));
|
|
27
31
|
|
|
28
32
|
WebGPUView(Context context) {
|
|
29
33
|
super(context);
|
|
@@ -134,7 +138,11 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
|
|
|
134
138
|
float density = getResources().getDisplayMetrics().density;
|
|
135
139
|
float width = getWidth() / density;
|
|
136
140
|
float height = getHeight() / density;
|
|
137
|
-
|
|
141
|
+
boolean votesFrameRate = mAppliedKind == KIND_SURFACE_VIEW;
|
|
142
|
+
if (votesFrameRate) {
|
|
143
|
+
mFrameRateVote.attach(surface);
|
|
144
|
+
}
|
|
145
|
+
onSurfaceCreate(surface, mContextId, width, height, votesFrameRate);
|
|
138
146
|
}
|
|
139
147
|
|
|
140
148
|
@Override
|
|
@@ -147,9 +155,20 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
|
|
|
147
155
|
|
|
148
156
|
@Override
|
|
149
157
|
public void surfaceOffscreen() {
|
|
158
|
+
// The surface is still valid here, so the vote can be taken back.
|
|
159
|
+
mFrameRateVote.detach();
|
|
150
160
|
switchToOffscreenSurface(mContextId);
|
|
151
161
|
}
|
|
152
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Called from native (AnimationReporter in cpp-adapter.cpp) on the thread
|
|
165
|
+
* that presents to the surface, once per animation, only for a SurfaceView.
|
|
166
|
+
*/
|
|
167
|
+
@DoNotStrip
|
|
168
|
+
void onAnimating() {
|
|
169
|
+
mFrameRateVote.onAnimating();
|
|
170
|
+
}
|
|
171
|
+
|
|
153
172
|
@Override
|
|
154
173
|
public int getContextId() {
|
|
155
174
|
return mContextId;
|
|
@@ -160,15 +179,19 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
|
|
|
160
179
|
* view: the view dies with its Canvas, so it retires the registry entry.
|
|
161
180
|
*/
|
|
162
181
|
public void destroy() {
|
|
182
|
+
mFrameRateVote.detach();
|
|
163
183
|
onViewDestroyed(mContextId);
|
|
164
184
|
}
|
|
165
185
|
|
|
186
|
+
// reportAnimation asks native to call onAnimating() when frames start being
|
|
187
|
+
// presented to this surface back to back.
|
|
166
188
|
@DoNotStrip
|
|
167
189
|
private native void onSurfaceCreate(
|
|
168
190
|
Surface surface,
|
|
169
191
|
int contextId,
|
|
170
192
|
float width,
|
|
171
|
-
float height
|
|
193
|
+
float height,
|
|
194
|
+
boolean reportAnimation
|
|
172
195
|
);
|
|
173
196
|
|
|
174
197
|
@DoNotStrip
|
|
@@ -185,4 +208,8 @@ public class WebGPUView extends ReactViewGroup implements WebGPUAPI {
|
|
|
185
208
|
@DoNotStrip
|
|
186
209
|
private native void onViewDestroyed(int contextId);
|
|
187
210
|
|
|
211
|
+
// See SurfaceFrameRateVote.AnimationSource.
|
|
212
|
+
@DoNotStrip
|
|
213
|
+
private native long releaseFrameRateVoteIfIdle(int contextId, long idleAfterNanos);
|
|
214
|
+
|
|
188
215
|
}
|
package/cpp/jsi/JSICache.cpp
CHANGED
package/cpp/jsi/NativeObject.h
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
#include <typeindex>
|
|
15
15
|
#include <unordered_map>
|
|
16
16
|
#include <utility>
|
|
17
|
+
#include <vector>
|
|
17
18
|
|
|
18
19
|
#include "EnforceRange.h"
|
|
19
20
|
#include "JSICache.h"
|
|
@@ -640,19 +641,26 @@ protected:
|
|
|
640
641
|
rt, std::move(result));
|
|
641
642
|
}
|
|
642
643
|
});
|
|
644
|
+
defineGetter(runtime, prototype, name, getterFunc);
|
|
645
|
+
}
|
|
643
646
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
647
|
+
/**
|
|
648
|
+
* Install a getter whose native implementation builds the JS value itself,
|
|
649
|
+
* from the calling jsi::Runtime: for values no JSIConverter produces (null
|
|
650
|
+
* or a BigInt, say).
|
|
651
|
+
*/
|
|
652
|
+
static void installGetter(jsi::Runtime &runtime, jsi::Object &prototype,
|
|
653
|
+
const char *name,
|
|
654
|
+
jsi::Value (Derived::*getter)(jsi::Runtime &)) {
|
|
655
|
+
auto getterFunc = jsi::Function::createFromHostFunction(
|
|
656
|
+
runtime, jsi::PropNameID::forUtf8(runtime, std::string("get_") + name),
|
|
657
|
+
0,
|
|
658
|
+
[getter](jsi::Runtime &rt, const jsi::Value &thisVal,
|
|
659
|
+
const jsi::Value *args, size_t count) -> jsi::Value {
|
|
660
|
+
auto native = Derived::fromValue(rt, thisVal);
|
|
661
|
+
return (native.get()->*getter)(rt);
|
|
662
|
+
});
|
|
663
|
+
defineGetter(runtime, prototype, name, getterFunc);
|
|
656
664
|
}
|
|
657
665
|
|
|
658
666
|
/**
|
|
@@ -757,6 +765,22 @@ protected:
|
|
|
757
765
|
}
|
|
758
766
|
|
|
759
767
|
private:
|
|
768
|
+
// Use Object.defineProperty to create a proper getter
|
|
769
|
+
static void defineGetter(jsi::Runtime &runtime, jsi::Object &prototype,
|
|
770
|
+
const char *name, const jsi::Function &getterFunc) {
|
|
771
|
+
auto objectCtor = runtime.global().getPropertyAsObject(runtime, "Object");
|
|
772
|
+
auto defineProperty =
|
|
773
|
+
objectCtor.getPropertyAsFunction(runtime, "defineProperty");
|
|
774
|
+
|
|
775
|
+
jsi::Object descriptor(runtime);
|
|
776
|
+
descriptor.setProperty(runtime, "get", getterFunc);
|
|
777
|
+
descriptor.setProperty(runtime, "enumerable", true);
|
|
778
|
+
descriptor.setProperty(runtime, "configurable", true);
|
|
779
|
+
|
|
780
|
+
defineProperty.call(runtime, prototype,
|
|
781
|
+
jsi::String::createFromUtf8(runtime, name), descriptor);
|
|
782
|
+
}
|
|
783
|
+
|
|
760
784
|
// Helper to call a method that takes the calling jsi::Runtime as its first
|
|
761
785
|
// parameter, with JSI argument conversion for the rest and JSI conversion of
|
|
762
786
|
// the result.
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
#include "GPUError.h"
|
|
23
23
|
#include "GPUExternalTexture.h"
|
|
24
24
|
#include "GPUInternalError.h"
|
|
25
|
+
#include "GPUNativeBufferExternalTexture.h"
|
|
26
|
+
#include "GPUNativeBufferTexture.h"
|
|
25
27
|
#include "GPUOutOfMemoryError.h"
|
|
26
28
|
#include "GPUPipelineLayout.h"
|
|
27
29
|
#include "GPUQuerySet.h"
|
|
@@ -102,6 +104,8 @@ RNWebGPUManager::RNWebGPUManager(
|
|
|
102
104
|
GPUDeviceLostInfo::installConstructor(*_jsRuntime);
|
|
103
105
|
GPUError::installConstructor(*_jsRuntime);
|
|
104
106
|
GPUExternalTexture::installConstructor(*_jsRuntime);
|
|
107
|
+
GPUNativeBufferExternalTexture::installConstructor(*_jsRuntime);
|
|
108
|
+
GPUNativeBufferTexture::installConstructor(*_jsRuntime);
|
|
105
109
|
GPUInternalError::installConstructor(*_jsRuntime);
|
|
106
110
|
GPUOutOfMemoryError::installConstructor(*_jsRuntime);
|
|
107
111
|
GPUValidationError::installConstructor(*_jsRuntime);
|