react-native-webgpu 0.10.2 → 0.10.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/cpp/cpp-adapter.cpp +142 -1
- package/android/src/main/java/com/webgpu/WebGPUAPI.java +2 -0
- package/android/src/main/java/com/webgpu/WebGPUHardwareBufferView.java +278 -0
- package/android/src/main/java/com/webgpu/WebGPUView.java +49 -12
- package/cpp/jsi/JSICache.cpp +23 -0
- package/cpp/jsi/JSICache.h +28 -1
- package/cpp/jsi/NativeObject.h +86 -50
- package/cpp/rnwgpu/SurfaceRegistry.h +613 -0
- package/cpp/rnwgpu/api/GPUFeatures.h +1 -1
- package/lib/commonjs/Canvas.js +1 -1
- package/lib/commonjs/Canvas.js.map +1 -1
- package/lib/module/Canvas.js +1 -1
- package/lib/module/Canvas.js.map +1 -1
- package/lib/typescript/src/Canvas.d.ts +6 -4
- package/lib/typescript/src/Canvas.d.ts.map +1 -1
- package/lib/typescript/src/WebGPUViewNativeComponent.d.ts +1 -1
- package/lib/typescript/src/WebGPUViewNativeComponent.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Canvas.tsx +10 -5
- package/src/WebGPUViewNativeComponent.ts +1 -1
- package/android/src/main/java/com/webgpu/WebGPUAHBView.java +0 -298
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
#include <algorithm>
|
|
4
|
+
#include <atomic>
|
|
5
|
+
#include <cerrno>
|
|
6
|
+
#include <chrono>
|
|
7
|
+
#include <condition_variable>
|
|
8
|
+
#include <cstdint>
|
|
9
|
+
#include <deque>
|
|
4
10
|
#include <functional>
|
|
5
11
|
#include <memory>
|
|
6
12
|
#include <mutex>
|
|
7
13
|
#include <shared_mutex>
|
|
8
14
|
#include <stdexcept>
|
|
15
|
+
#include <thread>
|
|
9
16
|
#include <unordered_map>
|
|
10
17
|
#include <utility>
|
|
11
18
|
#include <vector>
|
|
12
19
|
|
|
13
20
|
#include "webgpu/webgpu_cpp.h"
|
|
14
21
|
|
|
22
|
+
#if defined(__ANDROID__)
|
|
23
|
+
#include <android/hardware_buffer.h>
|
|
24
|
+
#include <android/log.h>
|
|
25
|
+
#include <poll.h>
|
|
26
|
+
#include <unistd.h>
|
|
27
|
+
#endif
|
|
28
|
+
|
|
15
29
|
#ifdef __APPLE__
|
|
16
30
|
namespace dawn::native::metal {
|
|
17
31
|
void WaitForCommandsToBeScheduled(WGPUDevice device);
|
|
@@ -44,6 +58,64 @@ struct Size {
|
|
|
44
58
|
// CAMetalLayer on Apple platforms). May run on any thread.
|
|
45
59
|
using NativeSurfaceReleaser = std::function<void(void *)>;
|
|
46
60
|
|
|
61
|
+
#if defined(__ANDROID__)
|
|
62
|
+
// --- AHB-pool presentation mode (WebGPUHardwareBufferView)
|
|
63
|
+
// -----------------------------
|
|
64
|
+
//
|
|
65
|
+
// A third presentation backend (besides the on-screen wgpu::Surface swapchain
|
|
66
|
+
// and the offscreen texture). WebGPU renders into a small pool of native
|
|
67
|
+
// AHardwareBuffers imported as Dawn SharedTextureMemory; the view draws each
|
|
68
|
+
// finished buffer inline via Bitmap.wrapHardwareBuffer so it behaves like a
|
|
69
|
+
// normal RN view. The pool is sized from the JS canvas drawing buffer
|
|
70
|
+
// (_canvas->getWidth()/Height()) and reallocated when that changes, exactly
|
|
71
|
+
// like the swapchain, so the canvas texture always matches the app's other
|
|
72
|
+
// attachments (e.g. its depth texture).
|
|
73
|
+
|
|
74
|
+
#define RNWGPU_LOG_TAG "WebGPUHardwareBufferView"
|
|
75
|
+
// Failure / anomaly reporting only (never per-frame).
|
|
76
|
+
#define RNWGPU_POOL_WARN(...) \
|
|
77
|
+
__android_log_print(ANDROID_LOG_WARN, RNWGPU_LOG_TAG, __VA_ARGS__)
|
|
78
|
+
|
|
79
|
+
enum class SlotState : uint8_t {
|
|
80
|
+
Free, // available for poolGetCurrentTexture
|
|
81
|
+
Rendering, // BeginAccess done, JS rendering into it
|
|
82
|
+
Presented, // EndAccess done, fence(s) queued for the waiter
|
|
83
|
+
Ready, // fence signaled, awaiting UI pickup
|
|
84
|
+
Displayed, // handed to the consumer, held until released (held-ring)
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
struct PoolSlot {
|
|
88
|
+
AHardwareBuffer *ahb = nullptr; // owned by the pool (allocated natively)
|
|
89
|
+
wgpu::SharedTextureMemory memory = nullptr;
|
|
90
|
+
wgpu::Texture texture = nullptr;
|
|
91
|
+
SlotState state = SlotState::Free;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// One generation of the pool (one buffer size). Reference-counted (shared_ptr)
|
|
95
|
+
// so an in-flight render / present / ready slot keeps the whole generation
|
|
96
|
+
// alive across a resize. The destructor frees every AHB and tears down the Dawn
|
|
97
|
+
// imports. The Java side keeps its own ref (via wrapHardwareBuffer) for
|
|
98
|
+
// anything it is still drawing, so a generation can be freed here without
|
|
99
|
+
// disturbing a frame still on screen.
|
|
100
|
+
struct AHBPool {
|
|
101
|
+
std::vector<PoolSlot> slots;
|
|
102
|
+
uint32_t generation = 0;
|
|
103
|
+
int width = 0;
|
|
104
|
+
int height = 0;
|
|
105
|
+
|
|
106
|
+
~AHBPool() {
|
|
107
|
+
for (auto &slot : slots) {
|
|
108
|
+
slot.texture = nullptr;
|
|
109
|
+
slot.memory = nullptr;
|
|
110
|
+
if (slot.ahb != nullptr) {
|
|
111
|
+
AHardwareBuffer_release(slot.ahb);
|
|
112
|
+
slot.ahb = nullptr;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
#endif
|
|
118
|
+
|
|
47
119
|
// Bridges the asynchronous native surface lifecycle (surfaces appear and
|
|
48
120
|
// disappear on the platform UI thread) with the synchronous WebGPU canvas API
|
|
49
121
|
// (the JS render loop must always be able to acquire a texture).
|
|
@@ -79,6 +151,16 @@ public:
|
|
|
79
151
|
: _gpu(std::move(gpu)), _width(width), _height(height) {}
|
|
80
152
|
|
|
81
153
|
~SurfaceInfo() {
|
|
154
|
+
#if defined(__ANDROID__)
|
|
155
|
+
// Stop the fence waiter before any pool generation is freed.
|
|
156
|
+
stopWaiter();
|
|
157
|
+
{
|
|
158
|
+
std::lock_guard<std::mutex> poolLock(_poolMutex);
|
|
159
|
+
_presentQueue.clear();
|
|
160
|
+
poolResetLocked();
|
|
161
|
+
_poolDevice = nullptr;
|
|
162
|
+
}
|
|
163
|
+
#endif
|
|
82
164
|
// Drop the Dawn objects before releasing the native surfaces they borrow.
|
|
83
165
|
_surface = nullptr;
|
|
84
166
|
#if defined(__ANDROID__)
|
|
@@ -250,6 +332,20 @@ public:
|
|
|
250
332
|
_config.presentMode = wgpu::PresentMode::Fifo;
|
|
251
333
|
_texture = nullptr;
|
|
252
334
|
_frameEpoch++;
|
|
335
|
+
#if defined(__ANDROID__)
|
|
336
|
+
{
|
|
337
|
+
std::lock_guard<std::mutex> poolLock(_poolMutex);
|
|
338
|
+
if (_poolDevice.Get() != _config.device.Get()) {
|
|
339
|
+
// Buffers imported for another device cannot be reused.
|
|
340
|
+
poolResetLocked();
|
|
341
|
+
}
|
|
342
|
+
_poolDevice = _config.device;
|
|
343
|
+
_poolFormat = _config.format;
|
|
344
|
+
_poolUsage = _config.usage;
|
|
345
|
+
// A new device may support AHB sharing even if the previous one did not.
|
|
346
|
+
_poolUnsupported = false;
|
|
347
|
+
}
|
|
348
|
+
#endif
|
|
253
349
|
_configureLocked();
|
|
254
350
|
}
|
|
255
351
|
|
|
@@ -276,6 +372,13 @@ public:
|
|
|
276
372
|
_viewFormats.clear();
|
|
277
373
|
_acquiredFromSurface = false;
|
|
278
374
|
_frameEpoch++;
|
|
375
|
+
#if defined(__ANDROID__)
|
|
376
|
+
{
|
|
377
|
+
std::lock_guard<std::mutex> poolLock(_poolMutex);
|
|
378
|
+
poolResetLocked();
|
|
379
|
+
_poolDevice = nullptr;
|
|
380
|
+
}
|
|
381
|
+
#endif
|
|
279
382
|
}
|
|
280
383
|
|
|
281
384
|
bool isConfigured() {
|
|
@@ -303,6 +406,16 @@ public:
|
|
|
303
406
|
void releaseSurfaceForDevice(const wgpu::Device &device) {
|
|
304
407
|
#if defined(__ANDROID__)
|
|
305
408
|
std::unique_lock<std::shared_mutex> lock(_mutex);
|
|
409
|
+
{
|
|
410
|
+
// The pool's SharedTextureMemory imports belong to the device too: drop
|
|
411
|
+
// them while it is alive. The next getCurrentTexture() after a configure
|
|
412
|
+
// with a live device reallocates the pool.
|
|
413
|
+
std::lock_guard<std::mutex> poolLock(_poolMutex);
|
|
414
|
+
if (_poolDevice && _poolDevice.Get() == device.Get()) {
|
|
415
|
+
poolResetLocked();
|
|
416
|
+
_poolDevice = nullptr;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
306
419
|
if (!_surface || !_surfaceDevice || _surfaceDevice.Get() != device.Get()) {
|
|
307
420
|
return;
|
|
308
421
|
}
|
|
@@ -321,6 +434,13 @@ public:
|
|
|
321
434
|
// otherwise (see RNWebGPU::destroyContext).
|
|
322
435
|
bool hasNativeSurface() {
|
|
323
436
|
std::shared_lock<std::shared_mutex> lock(_mutex);
|
|
437
|
+
#if defined(__ANDROID__)
|
|
438
|
+
// A WebGPUHardwareBufferView in pool mode owns presentation the same way a
|
|
439
|
+
// surface-backed view does, and retires the entry on its own teardown.
|
|
440
|
+
if (_poolMode.load()) {
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
#endif
|
|
324
444
|
return _nativeSurface != nullptr || _hasPendingAttach;
|
|
325
445
|
}
|
|
326
446
|
|
|
@@ -331,6 +451,34 @@ public:
|
|
|
331
451
|
// Start-of-frame boundary; a new acquire supersedes any previous frame
|
|
332
452
|
// that never presented.
|
|
333
453
|
applyPendingAttach(/* supersedeInFlightFrame = */ true);
|
|
454
|
+
#if defined(__ANDROID__)
|
|
455
|
+
if (_poolMode.load()) {
|
|
456
|
+
// The pool tracks the drawing buffer (_config.width/height, kept in sync
|
|
457
|
+
// with canvas.width/height by reconfigure()), like the swapchain, so the
|
|
458
|
+
// canvas texture always matches the app's other attachments.
|
|
459
|
+
int poolW = 0;
|
|
460
|
+
int poolH = 0;
|
|
461
|
+
{
|
|
462
|
+
std::shared_lock<std::shared_mutex> lock(_mutex);
|
|
463
|
+
if (_config.device != nullptr) {
|
|
464
|
+
poolW = static_cast<int>(_config.width);
|
|
465
|
+
poolH = static_cast<int>(_config.height);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
poolResize(poolW, poolH);
|
|
469
|
+
if (auto texture = poolGetCurrentTexture()) {
|
|
470
|
+
std::unique_lock<std::shared_mutex> lock(_mutex);
|
|
471
|
+
_frameInFlight = true;
|
|
472
|
+
_acquiredFromSurface = false;
|
|
473
|
+
_frameEpoch++;
|
|
474
|
+
return texture;
|
|
475
|
+
}
|
|
476
|
+
// No slot available (pool not allocated yet, unsupported device, view
|
|
477
|
+
// detached mid-frame, or a free-slot timeout): fall through to the
|
|
478
|
+
// offscreen fallback so the render loop survives; this frame is simply
|
|
479
|
+
// not shown.
|
|
480
|
+
}
|
|
481
|
+
#endif
|
|
334
482
|
std::unique_lock<std::shared_mutex> lock(_mutex);
|
|
335
483
|
if (_config.device == nullptr) {
|
|
336
484
|
throw std::runtime_error(
|
|
@@ -363,6 +511,13 @@ public:
|
|
|
363
511
|
// acquire failure) are dropped. This is also the end-of-frame boundary: it
|
|
364
512
|
// adopts a surface that attached while the frame was in flight.
|
|
365
513
|
void presentFrame() {
|
|
514
|
+
#if defined(__ANDROID__)
|
|
515
|
+
if (_poolMode.load()) {
|
|
516
|
+
// Ends access on the pool slot and hands it to the fence waiter; the
|
|
517
|
+
// view picks it up once its render-complete fence signals.
|
|
518
|
+
poolPresent();
|
|
519
|
+
}
|
|
520
|
+
#endif
|
|
366
521
|
#ifdef __APPLE__
|
|
367
522
|
// Ensure command buffers are scheduled before presenting. Read the device
|
|
368
523
|
// under a shared lock, then wait without holding it (the wait can block).
|
|
@@ -404,12 +559,161 @@ public:
|
|
|
404
559
|
return _config;
|
|
405
560
|
}
|
|
406
561
|
|
|
562
|
+
#if defined(__ANDROID__)
|
|
563
|
+
// --- AHB-pool API (called from the JNI layer, see cpp-adapter.cpp) ---------
|
|
564
|
+
|
|
565
|
+
// Turn on pool mode for this context. dpW/dpH is the canvas-client (dp) size
|
|
566
|
+
// reported to JS via getSize(); the actual buffers are sized in pool px from
|
|
567
|
+
// the canvas drawing buffer in poolResize().
|
|
568
|
+
void enablePool(int dpW, int dpH) {
|
|
569
|
+
{
|
|
570
|
+
std::unique_lock<std::shared_mutex> lock(_mutex);
|
|
571
|
+
_width = dpW;
|
|
572
|
+
_height = dpH;
|
|
573
|
+
}
|
|
574
|
+
std::lock_guard<std::mutex> poolLock(_poolMutex);
|
|
575
|
+
_poolMode.store(true);
|
|
576
|
+
_shutdown = false;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Set the canvas-client (dp) size without changing pool mode. Keeps getSize()
|
|
580
|
+
// (and thus the JS canvas.clientWidth/Height) in sync on resize.
|
|
581
|
+
void setPoolClientSize(int dpW, int dpH) {
|
|
582
|
+
std::unique_lock<std::shared_mutex> lock(_mutex);
|
|
583
|
+
_width = dpW;
|
|
584
|
+
_height = dpH;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// (Re)allocate the pool to match the canvas drawing buffer (px). Called from
|
|
588
|
+
// getCurrentTexture() on the rendering thread before acquiring a slot, so the
|
|
589
|
+
// canvas texture size always tracks the app's other attachments.
|
|
590
|
+
void poolResize(int pxW, int pxH) {
|
|
591
|
+
std::lock_guard<std::mutex> poolLock(_poolMutex);
|
|
592
|
+
if (!_poolMode.load() || _poolDevice == nullptr || pxW <= 0 || pxH <= 0) {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (_pool != nullptr && _pool->width == pxW && _pool->height == pxH) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (_poolUnsupported) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (!_poolDevice.HasFeature(
|
|
602
|
+
wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer)) {
|
|
603
|
+
_poolUnsupported = true;
|
|
604
|
+
RNWGPU_POOL_WARN("device lacks SharedTextureMemoryAHardwareBuffer; the "
|
|
605
|
+
"transparent canvas cannot present and stays blank");
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
auto pool = allocatePoolLocked(pxW, pxH);
|
|
609
|
+
if (pool == nullptr) {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
_pool = pool;
|
|
613
|
+
_pools[pool->generation] = pool;
|
|
614
|
+
// Keep current + previous generation (in-flight frames of the previous size
|
|
615
|
+
// may still be on screen during the cross-fade); drop anything older.
|
|
616
|
+
for (auto it = _pools.begin(); it != _pools.end();) {
|
|
617
|
+
if (it->first + 1 < pool->generation) {
|
|
618
|
+
it = _pools.erase(it);
|
|
619
|
+
} else {
|
|
620
|
+
++it;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
_freeCv.notify_all();
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Latest signaled frame awaiting display, encoded as (generation << 32 |
|
|
627
|
+
// slot). Returns -1 when there is nothing new. Marks the slot Displayed so
|
|
628
|
+
// the producer will not re-render into it until releaseSlot() is called.
|
|
629
|
+
// Called on the UI thread after the frame-ready wake-up.
|
|
630
|
+
int64_t poolPollReady() {
|
|
631
|
+
std::lock_guard<std::mutex> poolLock(_poolMutex);
|
|
632
|
+
if (_readySlot < 0 || _readyPool == nullptr) {
|
|
633
|
+
return -1;
|
|
634
|
+
}
|
|
635
|
+
int idx = _readySlot;
|
|
636
|
+
uint32_t gen = _readyPool->generation;
|
|
637
|
+
_readyPool->slots[idx].state = SlotState::Displayed;
|
|
638
|
+
_readyPool = nullptr;
|
|
639
|
+
_readySlot = -1;
|
|
640
|
+
return (static_cast<int64_t>(gen) << 32) | static_cast<uint32_t>(idx);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// The AHardwareBuffer backing a (generation, slot), for the consumer to wrap
|
|
644
|
+
// in a Bitmap. Returns nullptr for a retired generation. Called on the UI
|
|
645
|
+
// thread; the JNI layer converts it to a HardwareBuffer jobject.
|
|
646
|
+
void *poolBufferForDisplay(uint32_t gen, int slot) {
|
|
647
|
+
std::lock_guard<std::mutex> poolLock(_poolMutex);
|
|
648
|
+
auto it = _pools.find(gen);
|
|
649
|
+
if (it == _pools.end() || slot < 0 ||
|
|
650
|
+
slot >= static_cast<int>(it->second->slots.size())) {
|
|
651
|
+
return nullptr;
|
|
652
|
+
}
|
|
653
|
+
return it->second->slots[slot].ahb;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// The consumer is done displaying (and holding) a slot: return it to the free
|
|
657
|
+
// list so the producer may render into it again. No-op for a retired
|
|
658
|
+
// generation (those buffers are never reused). Called on the UI thread.
|
|
659
|
+
void poolReleaseSlot(uint32_t gen, int idx) {
|
|
660
|
+
std::lock_guard<std::mutex> poolLock(_poolMutex);
|
|
661
|
+
if (_pool != nullptr && _pool->generation == gen && idx >= 0 &&
|
|
662
|
+
idx < static_cast<int>(_pool->slots.size())) {
|
|
663
|
+
_pool->slots[idx].state = SlotState::Free;
|
|
664
|
+
_freeCv.notify_one();
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Invoked from the waiter thread (never under _poolMutex) each time a new
|
|
669
|
+
// frame becomes ready, so the consumer can wake up instead of polling every
|
|
670
|
+
// vsync. Pass nullptr to unregister.
|
|
671
|
+
void setPoolFrameReadyCallback(std::function<void()> cb) {
|
|
672
|
+
std::lock_guard<std::mutex> poolLock(_poolMutex);
|
|
673
|
+
_poolFrameReady = std::move(cb);
|
|
674
|
+
}
|
|
675
|
+
#endif
|
|
676
|
+
|
|
407
677
|
private:
|
|
408
678
|
void detach(bool createFallbackTexture) {
|
|
409
679
|
void *releasedSurfaces[2] = {nullptr, nullptr};
|
|
410
680
|
NativeSurfaceReleaser releasers[2];
|
|
681
|
+
#if defined(__ANDROID__)
|
|
682
|
+
// Destroyed outside the lock: it releases a JNI reference.
|
|
683
|
+
std::function<void()> releasedFrameReady;
|
|
684
|
+
#endif
|
|
411
685
|
{
|
|
412
686
|
std::unique_lock<std::shared_mutex> lock(_mutex);
|
|
687
|
+
#if defined(__ANDROID__)
|
|
688
|
+
// Leaving pool mode (the WebGPUHardwareBufferView detached): drop the
|
|
689
|
+
// pool. A configured context keeps rendering into the offscreen
|
|
690
|
+
// fallback, exactly like the surface path.
|
|
691
|
+
if (_poolMode.load()) {
|
|
692
|
+
_poolMode.store(false);
|
|
693
|
+
{
|
|
694
|
+
std::lock_guard<std::mutex> poolLock(_poolMutex);
|
|
695
|
+
// A frame between getCurrentTexture and present still has an open
|
|
696
|
+
// BeginAccess; end it and queue it so the waiter drains its fences
|
|
697
|
+
// before the generation is freed (presentFrame() no-ops for it
|
|
698
|
+
// because pool mode is already off).
|
|
699
|
+
if (_renderPool != nullptr && _currentRenderSlot >= 0 &&
|
|
700
|
+
_renderPool->slots[_currentRenderSlot].state ==
|
|
701
|
+
SlotState::Rendering) {
|
|
702
|
+
poolQueuePresentLocked();
|
|
703
|
+
}
|
|
704
|
+
// _presentQueue is intentionally left alone: the waiter still has
|
|
705
|
+
// to wait on the queued fences before the generations they pin can
|
|
706
|
+
// be released; it discards them on publish (no live pool).
|
|
707
|
+
poolResetLocked();
|
|
708
|
+
releasedFrameReady = std::move(_poolFrameReady);
|
|
709
|
+
_poolFrameReady = nullptr;
|
|
710
|
+
}
|
|
711
|
+
if (createFallbackTexture && _config.device != nullptr && !_texture) {
|
|
712
|
+
_texture = createOffscreenTextureLocked();
|
|
713
|
+
}
|
|
714
|
+
_frameEpoch++;
|
|
715
|
+
}
|
|
716
|
+
#endif
|
|
413
717
|
// The platform is tearing surfaces down; a not-yet-adopted attach is
|
|
414
718
|
// stale, cancel it.
|
|
415
719
|
if (_hasPendingAttach) {
|
|
@@ -583,6 +887,281 @@ private:
|
|
|
583
887
|
}
|
|
584
888
|
#endif
|
|
585
889
|
|
|
890
|
+
#if defined(__ANDROID__)
|
|
891
|
+
// Drop every pool generation and the ready / in-flight slot bookkeeping.
|
|
892
|
+
// Caller holds _poolMutex. Generations pinned by a queued present survive
|
|
893
|
+
// through their PendingPresent until the waiter is done with them.
|
|
894
|
+
void poolResetLocked() {
|
|
895
|
+
_pool = nullptr;
|
|
896
|
+
_pools.clear();
|
|
897
|
+
_readyPool = nullptr;
|
|
898
|
+
_readySlot = -1;
|
|
899
|
+
_renderPool = nullptr;
|
|
900
|
+
_currentRenderSlot = -1;
|
|
901
|
+
_freeCv.notify_all();
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Allocate + import one generation of the pool. Caller holds _poolMutex.
|
|
905
|
+
std::shared_ptr<AHBPool> allocatePoolLocked(int w, int h) {
|
|
906
|
+
auto pool = std::make_shared<AHBPool>();
|
|
907
|
+
pool->generation = ++_genCounter;
|
|
908
|
+
pool->width = w;
|
|
909
|
+
pool->height = h;
|
|
910
|
+
pool->slots.resize(kPoolSize);
|
|
911
|
+
for (auto &slot : pool->slots) {
|
|
912
|
+
AHardwareBuffer_Desc desc = {};
|
|
913
|
+
desc.width = static_cast<uint32_t>(w);
|
|
914
|
+
desc.height = static_cast<uint32_t>(h);
|
|
915
|
+
desc.layers = 1;
|
|
916
|
+
desc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
|
|
917
|
+
desc.usage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
|
|
918
|
+
AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT;
|
|
919
|
+
int err = AHardwareBuffer_allocate(&desc, &slot.ahb);
|
|
920
|
+
if (err != 0 || slot.ahb == nullptr) {
|
|
921
|
+
RNWGPU_POOL_WARN("AHardwareBuffer_allocate failed (%d) %dx%d", err, w,
|
|
922
|
+
h);
|
|
923
|
+
return nullptr;
|
|
924
|
+
}
|
|
925
|
+
wgpu::SharedTextureMemoryDescriptor memDesc{};
|
|
926
|
+
wgpu::SharedTextureMemoryAHardwareBufferDescriptor ahbDesc{};
|
|
927
|
+
ahbDesc.handle = slot.ahb;
|
|
928
|
+
memDesc.nextInChain = &ahbDesc;
|
|
929
|
+
slot.memory = _poolDevice.ImportSharedTextureMemory(&memDesc);
|
|
930
|
+
// A failed import yields a non-null Dawn error object, so probing the
|
|
931
|
+
// properties is the actual validity check.
|
|
932
|
+
wgpu::SharedTextureMemoryProperties props{};
|
|
933
|
+
if (slot.memory == nullptr ||
|
|
934
|
+
slot.memory.GetProperties(&props) != wgpu::Status::Success) {
|
|
935
|
+
RNWGPU_POOL_WARN("ImportSharedTextureMemory failed (%dx%d)", w, h);
|
|
936
|
+
return nullptr;
|
|
937
|
+
}
|
|
938
|
+
// The AHB is rgba8unorm; the pool cannot honor another configured format.
|
|
939
|
+
// The canvas is configured rgba8unorm on Android (the preferred format),
|
|
940
|
+
// so this only fires for apps hardcoding e.g. bgra8unorm.
|
|
941
|
+
if (_poolFormat != wgpu::TextureFormat::Undefined &&
|
|
942
|
+
_poolFormat != wgpu::TextureFormat::RGBA8Unorm) {
|
|
943
|
+
RNWGPU_POOL_WARN("configured canvas format %d is not supported by the "
|
|
944
|
+
"transparent canvas; using rgba8unorm (use "
|
|
945
|
+
"navigator.gpu.getPreferredCanvasFormat())",
|
|
946
|
+
static_cast<int>(_poolFormat));
|
|
947
|
+
}
|
|
948
|
+
// Honor the configured usage flags as far as the imported memory allows.
|
|
949
|
+
wgpu::TextureUsage wanted =
|
|
950
|
+
_poolUsage | wgpu::TextureUsage::RenderAttachment;
|
|
951
|
+
wgpu::TextureUsage usage = wanted & props.usage;
|
|
952
|
+
if (usage != wanted) {
|
|
953
|
+
RNWGPU_POOL_WARN("configured canvas usage 0x%llx narrowed to 0x%llx "
|
|
954
|
+
"(unsupported by the transparent canvas buffers)",
|
|
955
|
+
static_cast<unsigned long long>(wanted),
|
|
956
|
+
static_cast<unsigned long long>(usage));
|
|
957
|
+
}
|
|
958
|
+
wgpu::TextureDescriptor texDesc{};
|
|
959
|
+
texDesc.format = props.format;
|
|
960
|
+
texDesc.usage = usage;
|
|
961
|
+
texDesc.size.width = static_cast<uint32_t>(w);
|
|
962
|
+
texDesc.size.height = static_cast<uint32_t>(h);
|
|
963
|
+
slot.texture = slot.memory.CreateTexture(&texDesc);
|
|
964
|
+
slot.state = SlotState::Free;
|
|
965
|
+
}
|
|
966
|
+
return pool;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
wgpu::Texture poolGetCurrentTexture() {
|
|
970
|
+
std::unique_lock<std::mutex> lock(_poolMutex);
|
|
971
|
+
// Per the WebGPU spec, getCurrentTexture returns the same texture until the
|
|
972
|
+
// frame is presented; a repeat call must not consume another slot.
|
|
973
|
+
if (_renderPool != nullptr && _currentRenderSlot >= 0) {
|
|
974
|
+
return _renderPool->slots[_currentRenderSlot].texture;
|
|
975
|
+
}
|
|
976
|
+
if (_pool == nullptr) {
|
|
977
|
+
return nullptr; // allocation failed / unsupported (already reported)
|
|
978
|
+
}
|
|
979
|
+
auto pool = _pool;
|
|
980
|
+
int idx = -1;
|
|
981
|
+
// Bounded wait: if no slot frees up (e.g. a wedged fence), skip the frame
|
|
982
|
+
// instead of parking the JS/worklet thread forever.
|
|
983
|
+
bool ready = _freeCv.wait_for(lock, std::chrono::seconds(1), [&]() {
|
|
984
|
+
if (_shutdown || pool != _pool) {
|
|
985
|
+
return true; // pool replaced or shutting down: bail out
|
|
986
|
+
}
|
|
987
|
+
for (size_t i = 0; i < pool->slots.size(); i++) {
|
|
988
|
+
if (pool->slots[i].state == SlotState::Free) {
|
|
989
|
+
idx = static_cast<int>(i);
|
|
990
|
+
return true;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return false;
|
|
994
|
+
});
|
|
995
|
+
if (!ready) {
|
|
996
|
+
RNWGPU_POOL_WARN("poolGetCurrentTexture: timed out waiting for a free "
|
|
997
|
+
"slot; skipping frame");
|
|
998
|
+
return nullptr;
|
|
999
|
+
}
|
|
1000
|
+
if (idx < 0 || _shutdown || pool != _pool) {
|
|
1001
|
+
return nullptr;
|
|
1002
|
+
}
|
|
1003
|
+
auto &slot = pool->slots[idx];
|
|
1004
|
+
slot.state = SlotState::Rendering;
|
|
1005
|
+
_renderPool = pool;
|
|
1006
|
+
_currentRenderSlot = idx;
|
|
1007
|
+
poolBeginAccess(slot);
|
|
1008
|
+
return slot.texture;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
void poolPresent() {
|
|
1012
|
+
std::lock_guard<std::mutex> lock(_poolMutex);
|
|
1013
|
+
if (_currentRenderSlot < 0 || _renderPool == nullptr) {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
poolQueuePresentLocked();
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Ends access on the in-flight render slot and hands it (with its fences) to
|
|
1020
|
+
// the waiter. Caller holds _poolMutex and guarantees _renderPool /
|
|
1021
|
+
// _currentRenderSlot are valid.
|
|
1022
|
+
void poolQueuePresentLocked() {
|
|
1023
|
+
auto pool = _renderPool;
|
|
1024
|
+
int idx = _currentRenderSlot;
|
|
1025
|
+
auto &slot = pool->slots[idx];
|
|
1026
|
+
|
|
1027
|
+
wgpu::SharedTextureMemoryEndAccessState state{};
|
|
1028
|
+
wgpu::SharedTextureMemoryVkImageLayoutEndState vkLayout{};
|
|
1029
|
+
state.nextInChain = &vkLayout;
|
|
1030
|
+
slot.memory.EndAccess(slot.texture, &state);
|
|
1031
|
+
|
|
1032
|
+
std::vector<wgpu::SharedFence> fences;
|
|
1033
|
+
fences.reserve(state.fenceCount);
|
|
1034
|
+
for (size_t i = 0; i < state.fenceCount; i++) {
|
|
1035
|
+
fences.push_back(state.fences[i]);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
slot.state = SlotState::Presented;
|
|
1039
|
+
_presentQueue.push_back({pool, idx, std::move(fences)});
|
|
1040
|
+
_renderPool = nullptr;
|
|
1041
|
+
_currentRenderSlot = -1;
|
|
1042
|
+
startWaiterLocked();
|
|
1043
|
+
_presentCv.notify_one();
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
void poolBeginAccess(PoolSlot &slot) {
|
|
1047
|
+
wgpu::SharedTextureMemoryBeginAccessDescriptor desc{};
|
|
1048
|
+
desc.initialized = false; // canvas contents are fully redrawn each frame
|
|
1049
|
+
desc.concurrentRead = false;
|
|
1050
|
+
desc.fenceCount = 0;
|
|
1051
|
+
desc.fences = nullptr;
|
|
1052
|
+
desc.signaledValues = nullptr;
|
|
1053
|
+
wgpu::SharedTextureMemoryVkImageLayoutBeginState vkLayout{};
|
|
1054
|
+
vkLayout.oldLayout = 0;
|
|
1055
|
+
vkLayout.newLayout = 0;
|
|
1056
|
+
desc.nextInChain = &vkLayout;
|
|
1057
|
+
slot.memory.BeginAccess(slot.texture, &desc);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
void startWaiterLocked() {
|
|
1061
|
+
if (_waiterRunning) {
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
_waiterRunning = true;
|
|
1065
|
+
_waiter = std::thread([this]() { waiterLoop(); });
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
void stopWaiter() {
|
|
1069
|
+
{
|
|
1070
|
+
std::lock_guard<std::mutex> lock(_poolMutex);
|
|
1071
|
+
_shutdown = true;
|
|
1072
|
+
_presentCv.notify_all();
|
|
1073
|
+
_freeCv.notify_all();
|
|
1074
|
+
}
|
|
1075
|
+
if (_waiter.joinable()) {
|
|
1076
|
+
_waiter.join();
|
|
1077
|
+
}
|
|
1078
|
+
_waiterRunning = false;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// Blocks on each presented frame's render-complete fence (off the UI and JS
|
|
1082
|
+
// threads), then publishes it as the latest ready slot. This is the rigorous
|
|
1083
|
+
// acquire-side wait: HWUI only samples a buffer after the UI thread picks it
|
|
1084
|
+
// up via poolPollReady, which happens strictly after this wait returns.
|
|
1085
|
+
void waiterLoop() {
|
|
1086
|
+
while (true) {
|
|
1087
|
+
PendingPresent pending;
|
|
1088
|
+
{
|
|
1089
|
+
std::unique_lock<std::mutex> lock(_poolMutex);
|
|
1090
|
+
_presentCv.wait(lock,
|
|
1091
|
+
[&]() { return _shutdown || !_presentQueue.empty(); });
|
|
1092
|
+
if (_shutdown && _presentQueue.empty()) {
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
pending = std::move(_presentQueue.front());
|
|
1096
|
+
_presentQueue.pop_front();
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// Wait outside the lock. Each fd is owned by its SharedFence and closed
|
|
1100
|
+
// when `pending.fences` is destroyed at the end of this iteration, so we
|
|
1101
|
+
// never dup / double-close (avoids the fdsan abort, see
|
|
1102
|
+
// GPUSharedFence.cpp).
|
|
1103
|
+
for (auto &fence : pending.fences) {
|
|
1104
|
+
wgpu::SharedFenceExportInfo info{};
|
|
1105
|
+
wgpu::SharedFenceSyncFDExportInfo fdInfo{};
|
|
1106
|
+
info.nextInChain = &fdInfo;
|
|
1107
|
+
fence.ExportInfo(&info);
|
|
1108
|
+
if (info.type != wgpu::SharedFenceType::SyncFD) {
|
|
1109
|
+
// fdInfo was not populated (its default handle 0 is NOT a fence fd);
|
|
1110
|
+
// we have no way to wait on this fence type.
|
|
1111
|
+
RNWGPU_POOL_WARN("waiter: unexpected shared fence type %d, cannot "
|
|
1112
|
+
"wait for render completion",
|
|
1113
|
+
static_cast<int>(info.type));
|
|
1114
|
+
continue;
|
|
1115
|
+
}
|
|
1116
|
+
if (fdInfo.handle >= 0) {
|
|
1117
|
+
// Sync-fence fds become readable (POLLIN) when signaled; poll()
|
|
1118
|
+
// avoids any libsync linkage concern. Poll in bounded slices so
|
|
1119
|
+
// teardown (which flips _shutdown) cannot hang here on a wedged GPU.
|
|
1120
|
+
struct pollfd pfd;
|
|
1121
|
+
pfd.fd = fdInfo.handle;
|
|
1122
|
+
pfd.events = POLLIN;
|
|
1123
|
+
while (!_shutdown.load()) {
|
|
1124
|
+
pfd.revents = 0;
|
|
1125
|
+
int r = poll(&pfd, 1, 100);
|
|
1126
|
+
if (r > 0) {
|
|
1127
|
+
break; // signaled (or POLLERR): stop waiting
|
|
1128
|
+
}
|
|
1129
|
+
if (r < 0 && errno != EINTR) {
|
|
1130
|
+
break; // real poll error; EINTR just retries
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
std::function<void()> frameReady;
|
|
1137
|
+
{
|
|
1138
|
+
std::lock_guard<std::mutex> lock(_poolMutex);
|
|
1139
|
+
// Pool torn down (switchToOffscreen) while this frame was in flight:
|
|
1140
|
+
// discard it now that its fences have been drained. Publishing it would
|
|
1141
|
+
// pin the retired generation forever (nothing polls anymore).
|
|
1142
|
+
if (_pool == nullptr) {
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
// Supersede a still-unclaimed ready frame: drop it back to Free if it
|
|
1146
|
+
// belongs to the live pool (a retired pool is simply discarded).
|
|
1147
|
+
if (_readyPool != nullptr && _readySlot >= 0) {
|
|
1148
|
+
if (_readyPool == _pool) {
|
|
1149
|
+
_readyPool->slots[_readySlot].state = SlotState::Free;
|
|
1150
|
+
_freeCv.notify_one();
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
pending.pool->slots[pending.slot].state = SlotState::Ready;
|
|
1154
|
+
_readyPool = pending.pool;
|
|
1155
|
+
_readySlot = pending.slot;
|
|
1156
|
+
frameReady = _poolFrameReady;
|
|
1157
|
+
}
|
|
1158
|
+
if (frameReady) {
|
|
1159
|
+
frameReady(); // outside the lock: it calls into Java
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
#endif
|
|
1164
|
+
|
|
586
1165
|
mutable std::shared_mutex _mutex;
|
|
587
1166
|
#if defined(__ANDROID__)
|
|
588
1167
|
// DAWN_WORKAROUND_DEVICE_DESTROY_BEFORE_SURFACE_RELEASE
|
|
@@ -628,6 +1207,40 @@ private:
|
|
|
628
1207
|
// canvas).
|
|
629
1208
|
int _width;
|
|
630
1209
|
int _height;
|
|
1210
|
+
|
|
1211
|
+
#if defined(__ANDROID__)
|
|
1212
|
+
// Pool state. Guarded by _poolMutex (never nested under _mutex while
|
|
1213
|
+
// blocking).
|
|
1214
|
+
struct PendingPresent {
|
|
1215
|
+
std::shared_ptr<AHBPool> pool;
|
|
1216
|
+
int slot;
|
|
1217
|
+
std::vector<wgpu::SharedFence> fences;
|
|
1218
|
+
};
|
|
1219
|
+
|
|
1220
|
+
static constexpr int kPoolSize =
|
|
1221
|
+
5; // 2 held + 1 rendering + 1 waiting + 1 ready
|
|
1222
|
+
|
|
1223
|
+
std::atomic<bool> _poolMode{false};
|
|
1224
|
+
std::mutex _poolMutex;
|
|
1225
|
+
std::condition_variable _freeCv; // a slot returned to Free
|
|
1226
|
+
std::condition_variable _presentCv; // a frame was queued for the waiter
|
|
1227
|
+
wgpu::Device _poolDevice = nullptr;
|
|
1228
|
+
wgpu::TextureFormat _poolFormat = wgpu::TextureFormat::Undefined;
|
|
1229
|
+
wgpu::TextureUsage _poolUsage = wgpu::TextureUsage::RenderAttachment;
|
|
1230
|
+
bool _poolUnsupported = false; // device lacks AHB shared-texture support
|
|
1231
|
+
std::shared_ptr<AHBPool> _pool; // current generation
|
|
1232
|
+
std::unordered_map<uint32_t, std::shared_ptr<AHBPool>> _pools; // gen -> pool
|
|
1233
|
+
std::shared_ptr<AHBPool> _renderPool; // generation of the in-flight render
|
|
1234
|
+
int _currentRenderSlot = -1;
|
|
1235
|
+
std::shared_ptr<AHBPool> _readyPool; // generation of the latest ready frame
|
|
1236
|
+
int _readySlot = -1;
|
|
1237
|
+
std::deque<PendingPresent> _presentQueue;
|
|
1238
|
+
std::thread _waiter;
|
|
1239
|
+
bool _waiterRunning = false;
|
|
1240
|
+
std::atomic<bool> _shutdown{false};
|
|
1241
|
+
uint32_t _genCounter = 0;
|
|
1242
|
+
std::function<void()> _poolFrameReady; // consumer wake-up, see setter
|
|
1243
|
+
#endif
|
|
631
1244
|
};
|
|
632
1245
|
|
|
633
1246
|
class SurfaceRegistry {
|