react-native-wgpu 0.5.11 → 0.5.13
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/README.md +84 -0
- package/android/CMakeLists.txt +2 -0
- package/android/cpp/AndroidPlatformContext.h +121 -0
- package/apple/ApplePlatformContext.h +13 -0
- package/apple/ApplePlatformContext.mm +145 -0
- package/apple/AppleVideoPlayer.h +31 -0
- package/apple/AppleVideoPlayer.mm +314 -0
- package/cpp/rnwgpu/ArrayBuffer.h +51 -7
- package/cpp/rnwgpu/PlatformContext.h +81 -0
- package/cpp/rnwgpu/RNWebGPUManager.cpp +12 -0
- package/cpp/rnwgpu/api/Convertors.h +33 -11
- package/cpp/rnwgpu/api/GPU.cpp +27 -0
- package/cpp/rnwgpu/api/GPUAdapter.cpp +109 -33
- package/cpp/rnwgpu/api/GPUDevice.cpp +58 -5
- package/cpp/rnwgpu/api/GPUDevice.h +6 -0
- package/cpp/rnwgpu/api/GPUExternalTexture.cpp +335 -0
- package/cpp/rnwgpu/api/GPUExternalTexture.h +47 -2
- package/cpp/rnwgpu/api/GPUFeatures.h +4 -1
- package/cpp/rnwgpu/api/GPUShaderModule.cpp +2 -3
- package/cpp/rnwgpu/api/GPUSharedTextureMemory.cpp +80 -0
- package/cpp/rnwgpu/api/GPUSharedTextureMemory.h +71 -0
- package/cpp/rnwgpu/api/ImageBitmap.h +8 -0
- package/cpp/rnwgpu/api/RNWebGPU.h +63 -21
- package/cpp/rnwgpu/api/RnFeatures.h +53 -0
- package/cpp/rnwgpu/api/VideoFrame.h +76 -0
- package/cpp/rnwgpu/api/VideoPlayer.h +69 -0
- package/cpp/rnwgpu/api/descriptors/GPUBindGroupEntry.h +4 -1
- package/cpp/rnwgpu/api/descriptors/GPUDawnTogglesDescriptor.h +57 -0
- package/cpp/rnwgpu/api/descriptors/GPUDeviceDescriptor.h +18 -3
- package/cpp/rnwgpu/api/descriptors/GPUExternalTextureDescriptor.h +35 -33
- package/cpp/rnwgpu/api/descriptors/GPUSharedTextureMemoryDescriptor.h +62 -0
- package/cpp/rnwgpu/api/descriptors/Unions.h +10 -0
- package/cpp/webgpu/webgpu.h +78 -17
- package/cpp/webgpu/webgpu_cpp.h +1014 -1249
- package/cpp/webgpu/webgpu_cpp_print.h +99 -10
- 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 +0 -10
- package/lib/typescript/src/Canvas.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +20 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +32 -1
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/libs/android/arm64-v8a/libwebgpu_dawn.so +0 -0
- package/libs/android/armeabi-v7a/libwebgpu_dawn.so +0 -0
- package/libs/android/x86/libwebgpu_dawn.so +0 -0
- package/libs/android/x86_64/libwebgpu_dawn.so +0 -0
- package/libs/apple/libwebgpu_dawn.xcframework/Info.plist +6 -6
- package/libs/apple/libwebgpu_dawn.xcframework/ios-arm64/libwebgpu_dawn.a +0 -0
- package/libs/apple/libwebgpu_dawn.xcframework/ios-arm64_x86_64-simulator/libwebgpu_dawn.a +0 -0
- package/libs/apple/libwebgpu_dawn.xcframework/macos-arm64_x86_64/libwebgpu_dawn.a +0 -0
- package/package.json +5 -5
- package/src/Canvas.tsx +0 -15
- package/src/index.tsx +62 -2
- package/src/types.ts +83 -1
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#include "AppleVideoPlayer.h"
|
|
2
|
+
|
|
3
|
+
#import <AVFoundation/AVFoundation.h>
|
|
4
|
+
#import <CoreVideo/CoreVideo.h>
|
|
5
|
+
|
|
6
|
+
#include <stdexcept>
|
|
7
|
+
|
|
8
|
+
namespace rnwgpu {
|
|
9
|
+
|
|
10
|
+
namespace {
|
|
11
|
+
|
|
12
|
+
// 3x4 row-major matrices mapping [Y, Cb, Cr, 1] to gamma-encoded R'G'B' (NOT
|
|
13
|
+
// linear): YCbCr is derived from gamma-encoded RGB, so this conversion stays in
|
|
14
|
+
// the encoded domain. The sRGB decode in srcTransferFunctionParameters
|
|
15
|
+
// linearizes afterward (see GPUExternalTexture.cpp). Limited-range (video
|
|
16
|
+
// range) means luma is 16..235, chroma is 16..240 (8-bit).
|
|
17
|
+
// Reference: https://en.wikipedia.org/wiki/YCbCr (BT.601 / BT.709).
|
|
18
|
+
static constexpr float kBT709LimitedToRgb[12] = {
|
|
19
|
+
1.164383f, 0.000000f, 1.792741f, -0.972945f, //
|
|
20
|
+
1.164383f, -0.213249f, -0.532909f, 0.301517f, //
|
|
21
|
+
1.164383f, 2.112402f, 0.000000f, -1.133402f, //
|
|
22
|
+
};
|
|
23
|
+
static constexpr float kBT601LimitedToRgb[12] = {
|
|
24
|
+
1.164383f, 0.000000f, 1.596027f, -0.874202f, //
|
|
25
|
+
1.164383f, -0.391762f, -0.812968f, 0.531668f, //
|
|
26
|
+
1.164383f, 2.017232f, 0.000000f, -1.085631f, //
|
|
27
|
+
};
|
|
28
|
+
static constexpr float kBT2020LimitedToRgb[12] = {
|
|
29
|
+
1.164383f, 0.000000f, 1.678674f, -0.915688f, //
|
|
30
|
+
1.164383f, -0.187326f, -0.650424f, 0.347459f, //
|
|
31
|
+
1.164383f, 2.141772f, 0.000000f, -1.148145f, //
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Pick the right YUV→RGB matrix from the pixel buffer's color attachments.
|
|
35
|
+
// Falls back to BT.709 limited range (the right call for ≥720p H.264, which
|
|
36
|
+
// is what AVPlayer hands us for Big Buck Bunny and most streamed media).
|
|
37
|
+
static void fillYuvMatrix(CVPixelBufferRef pixelBuffer, float out[12]) {
|
|
38
|
+
CFTypeRef matrixKey = CVBufferGetAttachment(
|
|
39
|
+
pixelBuffer, kCVImageBufferYCbCrMatrixKey, nullptr);
|
|
40
|
+
const float *src = kBT709LimitedToRgb;
|
|
41
|
+
if (matrixKey) {
|
|
42
|
+
auto matrix = (CFStringRef)matrixKey;
|
|
43
|
+
if (CFEqual(matrix, kCVImageBufferYCbCrMatrix_ITU_R_601_4) ||
|
|
44
|
+
CFEqual(matrix, kCVImageBufferYCbCrMatrix_SMPTE_240M_1995)) {
|
|
45
|
+
src = kBT601LimitedToRgb;
|
|
46
|
+
} else if (CFEqual(matrix, kCVImageBufferYCbCrMatrix_ITU_R_2020)) {
|
|
47
|
+
src = kBT2020LimitedToRgb;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (int i = 0; i < 12; ++i) {
|
|
51
|
+
out[i] = src[i];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Map a CVPixelBuffer's pixel format to our VideoPixelFormat enum.
|
|
56
|
+
static VideoPixelFormat pixelFormatFromCVPixelBuffer(
|
|
57
|
+
CVPixelBufferRef pixelBuffer) {
|
|
58
|
+
OSType type = CVPixelBufferGetPixelFormatType(pixelBuffer);
|
|
59
|
+
switch (type) {
|
|
60
|
+
case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
|
|
61
|
+
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
|
|
62
|
+
return VideoPixelFormat::NV12;
|
|
63
|
+
default:
|
|
64
|
+
return VideoPixelFormat::BGRA8;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
class AppleVideoPlayer : public IVideoPlayer {
|
|
69
|
+
public:
|
|
70
|
+
AppleVideoPlayer(AVPlayer *player, AVPlayerItemVideoOutput *output,
|
|
71
|
+
id loopObserver)
|
|
72
|
+
: _player(player), _output(output), _loopObserver(loopObserver) {}
|
|
73
|
+
|
|
74
|
+
~AppleVideoPlayer() override {
|
|
75
|
+
if (_loopObserver) {
|
|
76
|
+
[[NSNotificationCenter defaultCenter] removeObserver:_loopObserver];
|
|
77
|
+
_loopObserver = nil;
|
|
78
|
+
}
|
|
79
|
+
[_player pause];
|
|
80
|
+
_player = nil;
|
|
81
|
+
_output = nil;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
VideoFrameHandle copyLatestFrame() override {
|
|
85
|
+
CMTime currentTime = [_output itemTimeForHostTime:CACurrentMediaTime()];
|
|
86
|
+
if (![_output hasNewPixelBufferForItemTime:currentTime]) {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
// copyPixelBufferForItemTime returns a +1 retained CVPixelBuffer; we then
|
|
90
|
+
// hand it to wrapCVPixelBuffer which adds another retain. Balance with a
|
|
91
|
+
// CFRelease here so we don't leak.
|
|
92
|
+
CVPixelBufferRef pixelBuffer =
|
|
93
|
+
[_output copyPixelBufferForItemTime:currentTime
|
|
94
|
+
itemTimeForDisplay:nullptr];
|
|
95
|
+
if (!pixelBuffer) {
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
auto handle = wrapCVPixelBuffer(pixelBuffer);
|
|
100
|
+
CFRelease(pixelBuffer);
|
|
101
|
+
return handle;
|
|
102
|
+
} catch (...) {
|
|
103
|
+
CFRelease(pixelBuffer);
|
|
104
|
+
throw;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
void play() override { [_player play]; }
|
|
109
|
+
void pause() override { [_player pause]; }
|
|
110
|
+
|
|
111
|
+
private:
|
|
112
|
+
AVPlayer *_player;
|
|
113
|
+
AVPlayerItemVideoOutput *_output;
|
|
114
|
+
id _loopObserver;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
} // namespace
|
|
118
|
+
|
|
119
|
+
VideoFrameHandle wrapCVPixelBuffer(CVPixelBufferRef pixelBuffer) {
|
|
120
|
+
if (!pixelBuffer) {
|
|
121
|
+
throw std::runtime_error("wrapCVPixelBuffer: pointer is null");
|
|
122
|
+
}
|
|
123
|
+
IOSurfaceRef ioSurface = CVPixelBufferGetIOSurface(pixelBuffer);
|
|
124
|
+
if (!ioSurface) {
|
|
125
|
+
throw std::runtime_error(
|
|
126
|
+
"wrapCVPixelBuffer: pixel buffer is not IOSurface-backed (was the "
|
|
127
|
+
"camera/video pipeline configured for Metal/IOSurface output?)");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Retain the pixel buffer so the caller can release theirs immediately.
|
|
131
|
+
CFRetain(pixelBuffer);
|
|
132
|
+
|
|
133
|
+
VideoFrameHandle handle;
|
|
134
|
+
handle.handle = (void *)ioSurface;
|
|
135
|
+
handle.width = static_cast<uint32_t>(CVPixelBufferGetWidth(pixelBuffer));
|
|
136
|
+
handle.height = static_cast<uint32_t>(CVPixelBufferGetHeight(pixelBuffer));
|
|
137
|
+
handle.pixelFormat = pixelFormatFromCVPixelBuffer(pixelBuffer);
|
|
138
|
+
if (handle.pixelFormat == VideoPixelFormat::NV12) {
|
|
139
|
+
fillYuvMatrix(pixelBuffer, handle.yuvToRgbMatrix);
|
|
140
|
+
}
|
|
141
|
+
handle.deleter = [pixelBuffer]() { CFRelease(pixelBuffer); };
|
|
142
|
+
return handle;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
std::unique_ptr<IVideoPlayer>
|
|
146
|
+
createAppleVideoPlayer(const std::string &path, VideoPixelFormat format) {
|
|
147
|
+
NSString *nsPath = [NSString stringWithUTF8String:path.c_str()];
|
|
148
|
+
NSURL *url;
|
|
149
|
+
if ([nsPath hasPrefix:@"http://"] || [nsPath hasPrefix:@"https://"] ||
|
|
150
|
+
[nsPath hasPrefix:@"file://"]) {
|
|
151
|
+
url = [NSURL URLWithString:nsPath];
|
|
152
|
+
} else {
|
|
153
|
+
url = [NSURL fileURLWithPath:nsPath];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:url];
|
|
157
|
+
if (!item) {
|
|
158
|
+
throw std::runtime_error("createAppleVideoPlayer: failed to create "
|
|
159
|
+
"AVPlayerItem");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// NV12 (kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) lets us hand the
|
|
163
|
+
// IOSurface straight to Dawn as a multi-planar texture for
|
|
164
|
+
// importExternalTexture. BGRA is the "decode + convert" path for the
|
|
165
|
+
// single-plane SharedTextureMemory demo.
|
|
166
|
+
OSType pixelFormat = format == VideoPixelFormat::NV12
|
|
167
|
+
? kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
|
|
168
|
+
: kCVPixelFormatType_32BGRA;
|
|
169
|
+
NSDictionary *outputSettings = @{
|
|
170
|
+
(NSString *)kCVPixelBufferPixelFormatTypeKey : @(pixelFormat),
|
|
171
|
+
(NSString *)kCVPixelBufferIOSurfacePropertiesKey : @{},
|
|
172
|
+
(NSString *)kCVPixelBufferMetalCompatibilityKey : @YES,
|
|
173
|
+
};
|
|
174
|
+
AVPlayerItemVideoOutput *output = [[AVPlayerItemVideoOutput alloc]
|
|
175
|
+
initWithPixelBufferAttributes:outputSettings];
|
|
176
|
+
[item addOutput:output];
|
|
177
|
+
|
|
178
|
+
AVPlayer *player = [AVPlayer playerWithPlayerItem:item];
|
|
179
|
+
player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
|
|
180
|
+
|
|
181
|
+
// Loop on end-of-stream by seeking back to zero.
|
|
182
|
+
__weak AVPlayer *weakPlayer = player;
|
|
183
|
+
id loopObserver = [[NSNotificationCenter defaultCenter]
|
|
184
|
+
addObserverForName:AVPlayerItemDidPlayToEndTimeNotification
|
|
185
|
+
object:item
|
|
186
|
+
queue:[NSOperationQueue mainQueue]
|
|
187
|
+
usingBlock:^(NSNotification * /*note*/) {
|
|
188
|
+
[weakPlayer seekToTime:kCMTimeZero];
|
|
189
|
+
[weakPlayer play];
|
|
190
|
+
}];
|
|
191
|
+
|
|
192
|
+
return std::make_unique<AppleVideoPlayer>(player, output, loopObserver);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
std::string writeAppleTestVideoFile() {
|
|
196
|
+
NSString *tmpDir = NSTemporaryDirectory();
|
|
197
|
+
NSString *outputPath =
|
|
198
|
+
[tmpDir stringByAppendingPathComponent:@"rnwgpu-test-video.mp4"];
|
|
199
|
+
NSURL *outputURL = [NSURL fileURLWithPath:outputPath];
|
|
200
|
+
|
|
201
|
+
// If the file already exists, reuse it. This makes the example zero-cost on
|
|
202
|
+
// subsequent runs.
|
|
203
|
+
if ([[NSFileManager defaultManager] fileExistsAtPath:outputPath]) {
|
|
204
|
+
return [outputPath UTF8String];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
NSError *error = nil;
|
|
208
|
+
AVAssetWriter *writer = [AVAssetWriter assetWriterWithURL:outputURL
|
|
209
|
+
fileType:AVFileTypeMPEG4
|
|
210
|
+
error:&error];
|
|
211
|
+
if (error || !writer) {
|
|
212
|
+
throw std::runtime_error(
|
|
213
|
+
std::string("writeTestVideoFile: AVAssetWriter init failed: ") +
|
|
214
|
+
[[error localizedDescription] UTF8String]);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const int kWidth = 256;
|
|
218
|
+
const int kHeight = 256;
|
|
219
|
+
const int kFps = 30;
|
|
220
|
+
const int kSeconds = 3;
|
|
221
|
+
const int kTotalFrames = kFps * kSeconds;
|
|
222
|
+
|
|
223
|
+
NSDictionary *videoSettings = @{
|
|
224
|
+
AVVideoCodecKey : AVVideoCodecTypeH264,
|
|
225
|
+
AVVideoWidthKey : @(kWidth),
|
|
226
|
+
AVVideoHeightKey : @(kHeight),
|
|
227
|
+
};
|
|
228
|
+
AVAssetWriterInput *writerInput =
|
|
229
|
+
[AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
|
|
230
|
+
outputSettings:videoSettings];
|
|
231
|
+
writerInput.expectsMediaDataInRealTime = NO;
|
|
232
|
+
|
|
233
|
+
NSDictionary *bufferAttrs = @{
|
|
234
|
+
(NSString *)kCVPixelBufferPixelFormatTypeKey :
|
|
235
|
+
@(kCVPixelFormatType_32BGRA),
|
|
236
|
+
(NSString *)kCVPixelBufferWidthKey : @(kWidth),
|
|
237
|
+
(NSString *)kCVPixelBufferHeightKey : @(kHeight),
|
|
238
|
+
(NSString *)kCVPixelBufferIOSurfacePropertiesKey : @{},
|
|
239
|
+
};
|
|
240
|
+
AVAssetWriterInputPixelBufferAdaptor *adaptor =
|
|
241
|
+
[AVAssetWriterInputPixelBufferAdaptor
|
|
242
|
+
assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput
|
|
243
|
+
sourcePixelBufferAttributes:bufferAttrs];
|
|
244
|
+
[writer addInput:writerInput];
|
|
245
|
+
|
|
246
|
+
if (![writer startWriting]) {
|
|
247
|
+
throw std::runtime_error(
|
|
248
|
+
std::string("writeTestVideoFile: startWriting failed: ") +
|
|
249
|
+
[[writer.error localizedDescription] UTF8String]);
|
|
250
|
+
}
|
|
251
|
+
[writer startSessionAtSourceTime:kCMTimeZero];
|
|
252
|
+
|
|
253
|
+
for (int i = 0; i < kTotalFrames; ++i) {
|
|
254
|
+
// Spin briefly if the input is not ready (the adaptor pool fills up).
|
|
255
|
+
while (!writerInput.isReadyForMoreMediaData) {
|
|
256
|
+
[NSThread sleepForTimeInterval:0.005];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
CVPixelBufferRef pixelBuffer = NULL;
|
|
260
|
+
CVReturn err = CVPixelBufferPoolCreatePixelBuffer(
|
|
261
|
+
kCFAllocatorDefault, adaptor.pixelBufferPool, &pixelBuffer);
|
|
262
|
+
if (err != kCVReturnSuccess || !pixelBuffer) {
|
|
263
|
+
throw std::runtime_error("writeTestVideoFile: pool exhausted");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
|
|
267
|
+
uint8_t *base =
|
|
268
|
+
static_cast<uint8_t *>(CVPixelBufferGetBaseAddress(pixelBuffer));
|
|
269
|
+
size_t rowBytes = CVPixelBufferGetBytesPerRow(pixelBuffer);
|
|
270
|
+
// Procedural pattern: scrolling diagonal stripes + per-frame color shift.
|
|
271
|
+
int phase = i * 6;
|
|
272
|
+
for (int y = 0; y < kHeight; ++y) {
|
|
273
|
+
uint8_t *row = base + y * rowBytes;
|
|
274
|
+
for (int x = 0; x < kWidth; ++x) {
|
|
275
|
+
uint8_t r = static_cast<uint8_t>((x + phase) & 0xFF);
|
|
276
|
+
uint8_t g = static_cast<uint8_t>((y + phase * 2) & 0xFF);
|
|
277
|
+
uint8_t b =
|
|
278
|
+
static_cast<uint8_t>(((x + y + phase) & 0x40) ? 220 : 30);
|
|
279
|
+
row[x * 4 + 0] = b;
|
|
280
|
+
row[x * 4 + 1] = g;
|
|
281
|
+
row[x * 4 + 2] = r;
|
|
282
|
+
row[x * 4 + 3] = 0xFF;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
|
|
286
|
+
|
|
287
|
+
CMTime pts = CMTimeMake(i, kFps);
|
|
288
|
+
if (![adaptor appendPixelBuffer:pixelBuffer withPresentationTime:pts]) {
|
|
289
|
+
CFRelease(pixelBuffer);
|
|
290
|
+
throw std::runtime_error(
|
|
291
|
+
std::string("writeTestVideoFile: appendPixelBuffer failed: ") +
|
|
292
|
+
[[writer.error localizedDescription] UTF8String]);
|
|
293
|
+
}
|
|
294
|
+
CFRelease(pixelBuffer);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
[writerInput markAsFinished];
|
|
298
|
+
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
|
|
299
|
+
[writer finishWritingWithCompletionHandler:^{
|
|
300
|
+
dispatch_semaphore_signal(sem);
|
|
301
|
+
}];
|
|
302
|
+
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
|
|
303
|
+
|
|
304
|
+
if (writer.status != AVAssetWriterStatusCompleted) {
|
|
305
|
+
throw std::runtime_error(
|
|
306
|
+
std::string("writeTestVideoFile: writer finished with status ") +
|
|
307
|
+
std::to_string(static_cast<long>(writer.status)) + ": " +
|
|
308
|
+
[[writer.error localizedDescription] UTF8String]);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return [outputPath UTF8String];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
} // namespace rnwgpu
|
package/cpp/rnwgpu/ArrayBuffer.h
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
#include <jsi/jsi.h>
|
|
3
3
|
|
|
4
|
+
#include <cmath>
|
|
5
|
+
#include <cstdint>
|
|
4
6
|
#include <memory>
|
|
5
7
|
|
|
6
8
|
#include "JSIConverter.h"
|
|
@@ -47,16 +49,58 @@ template <> struct JSIConverter<std::shared_ptr<ArrayBuffer>> {
|
|
|
47
49
|
if (bufferProp.isObject() &&
|
|
48
50
|
bufferProp.getObject(runtime).isArrayBuffer(runtime)) {
|
|
49
51
|
auto buff = bufferProp.getObject(runtime);
|
|
50
|
-
auto bytesPerElements =
|
|
51
|
-
obj.getProperty(runtime, "BYTES_PER_ELEMENT").asNumber();
|
|
52
52
|
auto arrayBuffer = buff.getArrayBuffer(runtime);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
const size_t bufferSize = arrayBuffer.size(runtime);
|
|
54
|
+
|
|
55
|
+
// byteOffset / byteLength are user-readable JS properties, not values
|
|
56
|
+
// the engine guarantees (unlike Dawn's node binding, which reads them
|
|
57
|
+
// off the engine's typed-array view). Read them as doubles so we can
|
|
58
|
+
// reject negative, non-integral, NaN/Inf, or oversized values before
|
|
59
|
+
// they wrap around when cast to size_t.
|
|
60
|
+
const double byteOffsetValue =
|
|
61
|
+
obj.getProperty(runtime, "byteOffset").asNumber();
|
|
62
|
+
const double byteLengthValue =
|
|
63
|
+
obj.getProperty(runtime, "byteLength").asNumber();
|
|
64
|
+
|
|
65
|
+
auto isValidByteIndex = [](double value) {
|
|
66
|
+
return std::isfinite(value) && value >= 0.0 &&
|
|
67
|
+
value <= static_cast<double>(SIZE_MAX) &&
|
|
68
|
+
std::floor(value) == value;
|
|
69
|
+
};
|
|
70
|
+
if (!isValidByteIndex(byteOffsetValue) ||
|
|
71
|
+
!isValidByteIndex(byteLengthValue)) {
|
|
72
|
+
throw std::runtime_error(
|
|
73
|
+
"ArrayBuffer::fromJSI: invalid byteOffset/byteLength");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const size_t byteOffset = static_cast<size_t>(byteOffsetValue);
|
|
77
|
+
const size_t byteLength = static_cast<size_t>(byteLengthValue);
|
|
78
|
+
|
|
79
|
+
// Overflow-safe bounds check: byteOffset + byteLength <= bufferSize.
|
|
80
|
+
if (byteOffset > bufferSize ||
|
|
81
|
+
byteLength > bufferSize - byteOffset) {
|
|
82
|
+
throw std::runtime_error(
|
|
83
|
+
"ArrayBuffer::fromJSI: view bounds [byteOffset, byteOffset + "
|
|
84
|
+
"byteLength) exceed the backing ArrayBuffer size");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// BYTES_PER_ELEMENT is absent on a DataView; default to 1. A spoofed
|
|
88
|
+
// object could report 0 (or a negative/NaN value), so clamp to a
|
|
89
|
+
// minimum of 1 to avoid a later division by zero in writeBuffer.
|
|
90
|
+
size_t bytesPerElements = 1;
|
|
91
|
+
if (obj.hasProperty(runtime, "BYTES_PER_ELEMENT")) {
|
|
92
|
+
auto bpe = obj.getProperty(runtime, "BYTES_PER_ELEMENT");
|
|
93
|
+
if (bpe.isNumber()) {
|
|
94
|
+
const double value = bpe.asNumber();
|
|
95
|
+
if (std::isfinite(value) && value >= 1.0) {
|
|
96
|
+
bytesPerElements = static_cast<size_t>(value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
57
101
|
return std::make_shared<ArrayBuffer>(
|
|
58
102
|
arrayBuffer.data(runtime) + byteOffset, byteLength,
|
|
59
|
-
|
|
103
|
+
bytesPerElements);
|
|
60
104
|
}
|
|
61
105
|
}
|
|
62
106
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include <memory>
|
|
5
5
|
#include <span>
|
|
6
6
|
#include <string>
|
|
7
|
+
#include <utility>
|
|
7
8
|
#include <vector>
|
|
8
9
|
|
|
9
10
|
#include "webgpu/webgpu_cpp.h"
|
|
@@ -17,6 +18,53 @@ struct ImageData {
|
|
|
17
18
|
wgpu::TextureFormat format;
|
|
18
19
|
};
|
|
19
20
|
|
|
21
|
+
// Pixel layout of a VideoFrame. Determines whether the underlying surface is
|
|
22
|
+
// a single RGBA plane or a biplanar Y / CbCr pair.
|
|
23
|
+
enum class VideoPixelFormat {
|
|
24
|
+
// Single-plane 8-bit BGRA (default; what RGBA-style sampling expects).
|
|
25
|
+
BGRA8,
|
|
26
|
+
// Biplanar 4:2:0 8-bit Y + interleaved CbCr (NV12). Used for the
|
|
27
|
+
// importExternalTexture path; needs the YUV→RGB conversion matrix below.
|
|
28
|
+
NV12,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// A native handle to a video frame that can be imported into a
|
|
32
|
+
// GPUSharedTextureMemory.
|
|
33
|
+
//
|
|
34
|
+
// - handle is an IOSurfaceRef on Apple platforms
|
|
35
|
+
// - handle is an AHardwareBuffer* on Android
|
|
36
|
+
//
|
|
37
|
+
// The deleter is responsible for releasing the underlying backing object
|
|
38
|
+
// (CVPixelBuffer / AHardwareBuffer) and must be called exactly once. The
|
|
39
|
+
// VideoFrame JS wrapper handles this on destruction.
|
|
40
|
+
struct VideoFrameHandle {
|
|
41
|
+
void *handle = nullptr;
|
|
42
|
+
uint32_t width = 0;
|
|
43
|
+
uint32_t height = 0;
|
|
44
|
+
VideoPixelFormat pixelFormat = VideoPixelFormat::BGRA8;
|
|
45
|
+
// 3x4 row-major matrix mapping [Y, U, V, 1] → linear [R, G, B]. Pre-computed
|
|
46
|
+
// at decode time from CVPixelBuffer attachments (kCVImageBufferYCbCrMatrixKey
|
|
47
|
+
// + range), with a BT.709 limited-range default. Only meaningful when
|
|
48
|
+
// pixelFormat == NV12.
|
|
49
|
+
float yuvToRgbMatrix[12] = {};
|
|
50
|
+
std::function<void()> deleter;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Platform-implemented video source that hands out fresh IOSurface /
|
|
54
|
+
// AHardwareBuffer-backed frames as a video plays.
|
|
55
|
+
class IVideoPlayer {
|
|
56
|
+
public:
|
|
57
|
+
virtual ~IVideoPlayer() = default;
|
|
58
|
+
|
|
59
|
+
// Returns the latest decoded frame, or an empty handle (handle == nullptr)
|
|
60
|
+
// when no new frame is ready yet. Each non-empty return retains its backing
|
|
61
|
+
// surface; the VideoFrame wrapper releases it on destruction.
|
|
62
|
+
virtual VideoFrameHandle copyLatestFrame() = 0;
|
|
63
|
+
|
|
64
|
+
virtual void play() = 0;
|
|
65
|
+
virtual void pause() = 0;
|
|
66
|
+
};
|
|
67
|
+
|
|
20
68
|
class PlatformContext {
|
|
21
69
|
public:
|
|
22
70
|
PlatformContext() = default;
|
|
@@ -41,6 +89,39 @@ public:
|
|
|
41
89
|
createImageBitmapFromDataAsync(std::span<const uint8_t> data,
|
|
42
90
|
std::function<void(ImageData)> onSuccess,
|
|
43
91
|
std::function<void(std::string)> onError) = 0;
|
|
92
|
+
|
|
93
|
+
// Decode the first video frame of `path` (a local file path) into a
|
|
94
|
+
// native, GPU-shareable surface. Used by the SharedTextureMemory example;
|
|
95
|
+
// not intended as a long-term media-loading API.
|
|
96
|
+
virtual VideoFrameHandle loadVideoFrame(const std::string &path) = 0;
|
|
97
|
+
|
|
98
|
+
// Create a synthetic, GPU-shareable IOSurface/AHardwareBuffer filled with a
|
|
99
|
+
// generated test pattern. Avoids the need to bundle a video asset for the
|
|
100
|
+
// SharedTextureMemory example.
|
|
101
|
+
virtual VideoFrameHandle createTestVideoFrame(uint32_t width,
|
|
102
|
+
uint32_t height) = 0;
|
|
103
|
+
|
|
104
|
+
// Open a video file at `path` for playback. The returned player yields
|
|
105
|
+
// IOSurface / AHardwareBuffer-backed frames via copyLatestFrame().
|
|
106
|
+
//
|
|
107
|
+
// `format` selects the requested pixel layout. BGRA8 is the easiest target
|
|
108
|
+
// for a regular sampled GPUTexture; NV12 is the right shape for the
|
|
109
|
+
// importExternalTexture path (zero-copy biplanar YUV).
|
|
110
|
+
virtual std::unique_ptr<IVideoPlayer>
|
|
111
|
+
createVideoPlayer(const std::string &path, VideoPixelFormat format) = 0;
|
|
112
|
+
|
|
113
|
+
// Wrap a CVPixelBufferRef (Apple) or AHardwareBuffer* (Android) pointer
|
|
114
|
+
// obtained from another library (typically VisionCamera's
|
|
115
|
+
// Frame.getNativeBuffer().pointer) as one of our VideoFrame handles.
|
|
116
|
+
//
|
|
117
|
+
// We CFRetain / AHardwareBuffer_acquire on the way in, so callers can
|
|
118
|
+
// safely release their own reference immediately after.
|
|
119
|
+
virtual VideoFrameHandle wrapNativeBuffer(void *pointer) = 0;
|
|
120
|
+
|
|
121
|
+
// Write a small procedurally-generated test video to a temporary location
|
|
122
|
+
// and return its absolute path. Lets the SharedTextureMemory example play
|
|
123
|
+
// a real decoded video without bundling an asset.
|
|
124
|
+
virtual std::string writeTestVideoFile() = 0;
|
|
44
125
|
};
|
|
45
126
|
|
|
46
127
|
} // namespace rnwgpu
|
|
@@ -31,12 +31,15 @@
|
|
|
31
31
|
#include "GPURenderPassEncoder.h"
|
|
32
32
|
#include "GPURenderPipeline.h"
|
|
33
33
|
#include "GPUSampler.h"
|
|
34
|
+
#include "GPUSharedTextureMemory.h"
|
|
34
35
|
#include "GPUShaderModule.h"
|
|
35
36
|
#include "GPUSupportedLimits.h"
|
|
36
37
|
#include "GPUTexture.h"
|
|
37
38
|
#include "GPUTextureView.h"
|
|
38
39
|
#include "GPUUncapturedErrorEvent.h"
|
|
39
40
|
#include "GPUValidationError.h"
|
|
41
|
+
#include "VideoFrame.h"
|
|
42
|
+
#include "VideoPlayer.h"
|
|
40
43
|
|
|
41
44
|
// Enums
|
|
42
45
|
#include "GPUBufferUsage.h"
|
|
@@ -64,6 +67,12 @@ RNWebGPUManager::RNWebGPUManager(
|
|
|
64
67
|
auto rnWebGPU =
|
|
65
68
|
std::make_shared<RNWebGPU>(gpu, _platformContext, _jsCallInvoker);
|
|
66
69
|
_gpu = gpu->get();
|
|
70
|
+
|
|
71
|
+
// RNWebGPU needs its brand registered in NativeObjectRegistry so the boxing
|
|
72
|
+
// path can install the prototype on worklet runtimes. installConstructor
|
|
73
|
+
// does that registration but also sets globalThis.RNWebGPU = ctor, so we
|
|
74
|
+
// call it FIRST and then overwrite the global with the actual instance.
|
|
75
|
+
RNWebGPU::installConstructor(*_jsRuntime);
|
|
67
76
|
_jsRuntime->global().setProperty(*_jsRuntime, "RNWebGPU",
|
|
68
77
|
RNWebGPU::create(*_jsRuntime, rnWebGPU));
|
|
69
78
|
|
|
@@ -97,10 +106,13 @@ RNWebGPUManager::RNWebGPUManager(
|
|
|
97
106
|
GPURenderPassEncoder::installConstructor(*_jsRuntime);
|
|
98
107
|
GPURenderPipeline::installConstructor(*_jsRuntime);
|
|
99
108
|
GPUSampler::installConstructor(*_jsRuntime);
|
|
109
|
+
GPUSharedTextureMemory::installConstructor(*_jsRuntime);
|
|
100
110
|
GPUShaderModule::installConstructor(*_jsRuntime);
|
|
101
111
|
GPUSupportedLimits::installConstructor(*_jsRuntime);
|
|
102
112
|
GPUTexture::installConstructor(*_jsRuntime);
|
|
103
113
|
GPUTextureView::installConstructor(*_jsRuntime);
|
|
114
|
+
VideoFrame::installConstructor(*_jsRuntime);
|
|
115
|
+
VideoPlayer::installConstructor(*_jsRuntime);
|
|
104
116
|
|
|
105
117
|
// Install constant objects as plain JS objects with own properties
|
|
106
118
|
_jsRuntime->global().setProperty(*_jsRuntime, "GPUBufferUsage",
|
|
@@ -252,13 +252,24 @@ public:
|
|
|
252
252
|
|
|
253
253
|
[[nodiscard]] bool Convert(wgpu::BindGroupLayoutEntry &out,
|
|
254
254
|
const GPUBindGroupLayoutEntry &in) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
255
|
+
out = {};
|
|
256
|
+
if (!Convert(out.binding, in.binding) ||
|
|
257
|
+
!Convert(out.visibility, in.visibility) ||
|
|
258
|
+
!Convert(out.buffer, in.buffer) || !Convert(out.sampler, in.sampler) ||
|
|
259
|
+
!Convert(out.texture, in.texture) ||
|
|
260
|
+
!Convert(out.storageTexture, in.storageTexture)) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
if (in.externalTexture.has_value() &&
|
|
264
|
+
in.externalTexture.value() != nullptr) {
|
|
265
|
+
// External texture layouts bind via a chained struct rather than a
|
|
266
|
+
// direct field on BindGroupLayoutEntry. The chained struct must outlive
|
|
267
|
+
// the BindGroupLayoutEntry until Device::CreateBindGroupLayout returns,
|
|
268
|
+
// so we allocate it on the Convertor's arena.
|
|
269
|
+
auto *chain = Allocate<wgpu::ExternalTextureBindingLayout>();
|
|
270
|
+
out.nextInChain = chain;
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
262
273
|
}
|
|
263
274
|
|
|
264
275
|
[[nodiscard]] bool Convert(wgpu::BlendComponent &out,
|
|
@@ -422,9 +433,11 @@ public:
|
|
|
422
433
|
}
|
|
423
434
|
|
|
424
435
|
[[nodiscard]] bool Convert(wgpu::ExternalTextureBindingLayout &out,
|
|
425
|
-
const GPUExternalTextureBindingLayout &in) {
|
|
426
|
-
// no
|
|
427
|
-
|
|
436
|
+
const GPUExternalTextureBindingLayout & /*in*/) {
|
|
437
|
+
// ExternalTextureBindingLayout carries no fields of its own; its presence
|
|
438
|
+
// (as a chained struct) is what marks the entry as an external texture.
|
|
439
|
+
out = {};
|
|
440
|
+
return true;
|
|
428
441
|
}
|
|
429
442
|
|
|
430
443
|
[[nodiscard]] bool Convert(wgpu::ConstantEntry &out, const std::string &key,
|
|
@@ -729,7 +742,16 @@ public:
|
|
|
729
742
|
out.buffer = buffer->get();
|
|
730
743
|
return true;
|
|
731
744
|
}
|
|
732
|
-
|
|
745
|
+
if (in.externalTexture != nullptr) {
|
|
746
|
+
// External textures bind via a chained struct rather than a direct field
|
|
747
|
+
// on BindGroupEntry. The chained struct must outlive the
|
|
748
|
+
// BindGroupEntry until Device::CreateBindGroup returns, so we allocate
|
|
749
|
+
// it on the Convertor's arena.
|
|
750
|
+
auto *chain = Allocate<wgpu::ExternalTextureBindingEntry>();
|
|
751
|
+
chain->externalTexture = in.externalTexture->get();
|
|
752
|
+
out.nextInChain = chain;
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
733
755
|
return false;
|
|
734
756
|
}
|
|
735
757
|
|
package/cpp/rnwgpu/api/GPU.cpp
CHANGED
|
@@ -20,6 +20,33 @@ GPU::GPU(jsi::Runtime &runtime) : NativeObject(CLASS_NAME) {
|
|
|
20
20
|
|
|
21
21
|
wgpu::InstanceLimits limits{.timedWaitAnyMaxCount = 64};
|
|
22
22
|
instanceDesc.requiredLimits = &limits;
|
|
23
|
+
|
|
24
|
+
// Expose Dawn's experimental adapter features. Several features needed by
|
|
25
|
+
// our Android external-texture path (YCbCrVulkanSamplers,
|
|
26
|
+
// OpaqueYCbCrAndroidForExternalTexture) are tagged Experimental in Dawn's
|
|
27
|
+
// feature table and are otherwise filtered out of adapter.features by
|
|
28
|
+
// PhysicalDeviceBase::GetSupportedFeatures. The allow_unsafe_apis toggle
|
|
29
|
+
// disables that filter so the features become visible;
|
|
30
|
+
// expose_wgsl_experimental_features is the parallel toggle for WGSL language
|
|
31
|
+
// features.
|
|
32
|
+
//
|
|
33
|
+
// Trade-off: these are instance-level Dawn toggles, so they apply to every
|
|
34
|
+
// adapter/device created from this instance, not just the external-texture
|
|
35
|
+
// path. There is no finer-grained, per-feature mechanism to surface these.
|
|
36
|
+
// The exposure is acceptable because the toggle only *un-hides* experimental
|
|
37
|
+
// features in adapter.features; it does not enable any of them. Nothing
|
|
38
|
+
// experimental becomes active unless application code explicitly lists that
|
|
39
|
+
// feature in requiredFeatures at device creation, so the default behavior of
|
|
40
|
+
// a device that asks for no experimental features is unchanged.
|
|
41
|
+
static const char *const kEnabledToggles[] = {
|
|
42
|
+
"allow_unsafe_apis",
|
|
43
|
+
"expose_wgsl_experimental_features",
|
|
44
|
+
};
|
|
45
|
+
wgpu::DawnTogglesDescriptor toggles;
|
|
46
|
+
toggles.enabledToggleCount = std::size(kEnabledToggles);
|
|
47
|
+
toggles.enabledToggles = kEnabledToggles;
|
|
48
|
+
instanceDesc.nextInChain = &toggles;
|
|
49
|
+
|
|
23
50
|
_instance = wgpu::CreateInstance(&instanceDesc);
|
|
24
51
|
|
|
25
52
|
auto dispatcher = std::make_shared<async::JSIMicrotaskDispatcher>(runtime);
|