react-native-webgpu 0.10.5 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/cpp/AndroidPlatformContext.h +100 -9
- package/android/cpp/AndroidVideoPlayer.h +307 -0
- package/android/cpp/cpp-adapter.cpp +66 -6
- package/android/src/main/java/com/webgpu/ViewSnapshot.java +162 -0
- package/android/src/main/java/com/webgpu/WebGPUModule.java +78 -0
- package/android/src/main/java/com/webgpu/WebGPUVideoPlayer.java +420 -0
- package/apple/ApplePlatformContext.h +14 -1
- package/apple/ApplePlatformContext.mm +176 -1
- package/apple/AppleVideoPlayer.mm +157 -47
- package/apple/WebGPUModule.mm +20 -1
- package/cpp/rnwgpu/PlatformContext.h +76 -1
- package/cpp/rnwgpu/RNWebGPUManager.cpp +7 -0
- package/cpp/rnwgpu/api/GPUDevice.cpp +2 -1
- package/cpp/rnwgpu/api/GPUDevice.h +6 -0
- package/cpp/rnwgpu/api/GPUQueue.cpp +261 -4
- package/cpp/rnwgpu/api/GPUQueue.h +29 -2
- package/cpp/rnwgpu/api/VideoFrameBlit.h +355 -0
- package/cpp/rnwgpu/api/VideoPlayer.h +50 -3
- package/cpp/rnwgpu/api/descriptors/GPUDrawElementImageDestination.h +60 -0
- package/cpp/rnwgpu/api/descriptors/GPUDrawElementImageSource.h +106 -0
- package/cpp/rnwgpu/api/descriptors/GPUImageCopyExternalImage.h +30 -3
- package/lib/commonjs/importDevice.js +2 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/main/index.js +45 -0
- package/lib/commonjs/main/index.js.map +1 -1
- package/lib/commonjs/main/index.web.js +11 -0
- package/lib/commonjs/main/index.web.js.map +1 -1
- package/lib/commonjs/video.js +58 -0
- package/lib/commonjs/video.js.map +1 -0
- package/lib/module/importDevice.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/main/index.js +34 -0
- package/lib/module/main/index.js.map +1 -1
- package/lib/module/main/index.web.js +1 -0
- package/lib/module/main/index.web.js.map +1 -1
- package/lib/module/video.js +49 -0
- package/lib/module/video.js.map +1 -0
- package/lib/typescript/lib/commonjs/video.d.ts +30 -0
- package/lib/typescript/lib/commonjs/video.d.ts.map +1 -0
- package/lib/typescript/lib/module/main/index.d.ts +1 -0
- package/lib/typescript/lib/module/main/index.web.d.ts +1 -0
- package/lib/typescript/lib/module/video.d.ts +4 -0
- package/lib/typescript/lib/module/video.d.ts.map +1 -0
- package/lib/typescript/src/importDevice.d.ts +2 -2
- package/lib/typescript/src/index.d.ts +8 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/main/index.d.ts +1 -0
- package/lib/typescript/src/main/index.d.ts.map +1 -1
- package/lib/typescript/src/main/index.web.d.ts +1 -0
- package/lib/typescript/src/main/index.web.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +32 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/video.d.ts +41 -0
- package/lib/typescript/src/video.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/importDevice.ts +2 -2
- package/src/index.tsx +57 -3
- package/src/main/index.tsx +50 -0
- package/src/main/index.web.tsx +1 -0
- package/src/types.ts +89 -7
- package/src/video.ts +69 -0
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
#include "PlatformContext.h"
|
|
4
|
+
#include <functional>
|
|
4
5
|
#include <string>
|
|
5
6
|
|
|
6
7
|
namespace rnwgpu {
|
|
7
8
|
|
|
8
9
|
class ApplePlatformContext : public PlatformContext {
|
|
9
10
|
public:
|
|
10
|
-
|
|
11
|
+
// Resolves a React tag to its native view (an unretained
|
|
12
|
+
// UIView* / NSView* bridged to void*, or nullptr). Called on the main thread
|
|
13
|
+
// only. Provided by WebGPUModule from React Native's view registry.
|
|
14
|
+
using ViewLookup = std::function<void *(int)>;
|
|
15
|
+
|
|
16
|
+
explicit ApplePlatformContext(ViewLookup viewLookup = nullptr);
|
|
11
17
|
~ApplePlatformContext() = default;
|
|
12
18
|
|
|
19
|
+
void snapshotView(const ViewSnapshotRequest &request,
|
|
20
|
+
std::function<void(ImageData)> onSuccess,
|
|
21
|
+
std::function<void(std::string)> onError) override;
|
|
22
|
+
|
|
13
23
|
wgpu::Surface makeSurface(wgpu::Instance instance, void *surface, int width,
|
|
14
24
|
int height) override;
|
|
15
25
|
|
|
@@ -39,6 +49,9 @@ public:
|
|
|
39
49
|
std::string writeTestVideoFile() override;
|
|
40
50
|
|
|
41
51
|
VideoFrameHandle wrapNativeBuffer(void *pointer) override;
|
|
52
|
+
|
|
53
|
+
private:
|
|
54
|
+
ViewLookup _viewLookup;
|
|
42
55
|
};
|
|
43
56
|
|
|
44
57
|
} // namespace rnwgpu
|
|
@@ -10,10 +10,15 @@
|
|
|
10
10
|
#import <ReactCommon/RCTTurboModule.h>
|
|
11
11
|
|
|
12
12
|
#include "AppleVideoPlayer.h"
|
|
13
|
+
#import "RNWGUIKit.h"
|
|
13
14
|
|
|
14
15
|
#include "RNWebGPUManager.h"
|
|
15
16
|
#include "WebGPUModule.h"
|
|
16
17
|
|
|
18
|
+
#include <cmath>
|
|
19
|
+
#include <string>
|
|
20
|
+
#include <utility>
|
|
21
|
+
|
|
17
22
|
namespace rnwgpu {
|
|
18
23
|
|
|
19
24
|
void checkIfUsingSimulatorWithAPIValidation() {
|
|
@@ -30,10 +35,180 @@ void checkIfUsingSimulatorWithAPIValidation() {
|
|
|
30
35
|
#endif
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
ApplePlatformContext::ApplePlatformContext()
|
|
38
|
+
ApplePlatformContext::ApplePlatformContext(ViewLookup viewLookup)
|
|
39
|
+
: _viewLookup(std::move(viewLookup)) {
|
|
34
40
|
checkIfUsingSimulatorWithAPIValidation();
|
|
35
41
|
}
|
|
36
42
|
|
|
43
|
+
namespace {
|
|
44
|
+
|
|
45
|
+
// Rasterize `request` on the main thread into tightly packed, premultiplied
|
|
46
|
+
// BGRA8 pixels. Throws std::runtime_error with a user-facing message.
|
|
47
|
+
ImageData snapshotViewOnMainThread(const ApplePlatformContext::ViewLookup &lookup,
|
|
48
|
+
const ViewSnapshotRequest &request) {
|
|
49
|
+
if (!lookup) {
|
|
50
|
+
throw std::runtime_error(
|
|
51
|
+
"drawElementImageToTexture: no view registry is available");
|
|
52
|
+
}
|
|
53
|
+
RNWGPlatformView *view = (__bridge RNWGPlatformView *)lookup(request.viewTag);
|
|
54
|
+
if (view == nil) {
|
|
55
|
+
throw std::runtime_error(
|
|
56
|
+
"drawElementImageToTexture: no native view found for tag " +
|
|
57
|
+
std::to_string(request.viewTag) +
|
|
58
|
+
" (is the view mounted, and rendered with collapsable={false}?)");
|
|
59
|
+
}
|
|
60
|
+
const CGRect bounds = view.bounds;
|
|
61
|
+
if (bounds.size.width <= 0 || bounds.size.height <= 0) {
|
|
62
|
+
throw std::runtime_error(
|
|
63
|
+
"drawElementImageToTexture: the view has no size yet");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Source rectangle in points (view space); the whole view by default.
|
|
67
|
+
const double sourceX = request.sourceX;
|
|
68
|
+
const double sourceY = request.sourceY;
|
|
69
|
+
const double sourceWidth =
|
|
70
|
+
request.sourceWidth > 0 ? request.sourceWidth : bounds.size.width;
|
|
71
|
+
const double sourceHeight =
|
|
72
|
+
request.sourceHeight > 0 ? request.sourceHeight : bounds.size.height;
|
|
73
|
+
|
|
74
|
+
// Natural pixel size: points times the screen scale the view is shown at.
|
|
75
|
+
#if !TARGET_OS_OSX
|
|
76
|
+
CGFloat scale = view.window.screen.scale;
|
|
77
|
+
if (scale <= 0) {
|
|
78
|
+
scale = UIScreen.mainScreen.scale;
|
|
79
|
+
}
|
|
80
|
+
#else
|
|
81
|
+
CGFloat scale = view.window.backingScaleFactor;
|
|
82
|
+
if (scale <= 0) {
|
|
83
|
+
scale = NSScreen.mainScreen.backingScaleFactor;
|
|
84
|
+
}
|
|
85
|
+
#endif
|
|
86
|
+
const uint32_t width =
|
|
87
|
+
request.width > 0
|
|
88
|
+
? request.width
|
|
89
|
+
: static_cast<uint32_t>(std::llround(sourceWidth * scale));
|
|
90
|
+
const uint32_t height =
|
|
91
|
+
request.height > 0
|
|
92
|
+
? request.height
|
|
93
|
+
: static_cast<uint32_t>(std::llround(sourceHeight * scale));
|
|
94
|
+
if (width == 0 || height == 0) {
|
|
95
|
+
throw std::runtime_error(
|
|
96
|
+
"drawElementImageToTexture: the source rectangle is empty");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// The view's bounds are drawn scaled so that the source rectangle covers
|
|
100
|
+
// the whole output.
|
|
101
|
+
const CGFloat scaleX = static_cast<CGFloat>(width) / sourceWidth;
|
|
102
|
+
const CGFloat scaleY = static_cast<CGFloat>(height) / sourceHeight;
|
|
103
|
+
const CGRect drawRect =
|
|
104
|
+
CGRectMake(-sourceX * scaleX, -sourceY * scaleY,
|
|
105
|
+
bounds.size.width * scaleX, bounds.size.height * scaleY);
|
|
106
|
+
|
|
107
|
+
CGImageRef cgImage = NULL;
|
|
108
|
+
#if !TARGET_OS_OSX
|
|
109
|
+
// drawViewHierarchyInRect renders what is on screen (including Metal /
|
|
110
|
+
// CAMetalLayer content), which is what "the element as displayed" means.
|
|
111
|
+
// afterScreenUpdates:YES flushes pending layout and layer changes first, so
|
|
112
|
+
// a snapshot taken right after a state change reflects that change.
|
|
113
|
+
UIGraphicsImageRendererFormat *format =
|
|
114
|
+
[UIGraphicsImageRendererFormat defaultFormat];
|
|
115
|
+
format.scale = 1.0;
|
|
116
|
+
format.opaque = NO;
|
|
117
|
+
format.preferredRange = UIGraphicsImageRendererFormatRangeStandard;
|
|
118
|
+
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc]
|
|
119
|
+
initWithSize:CGSizeMake(width, height)
|
|
120
|
+
format:format];
|
|
121
|
+
// drawViewHierarchyInRect returns NO and draws nothing when the view is not
|
|
122
|
+
// in a window (e.g. a screen pushed under another one). The image would
|
|
123
|
+
// then be fully transparent, so treat it as a failure instead.
|
|
124
|
+
__block BOOL drawn = NO;
|
|
125
|
+
UIImage *image = [renderer
|
|
126
|
+
imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull context) {
|
|
127
|
+
drawn = [view drawViewHierarchyInRect:drawRect afterScreenUpdates:YES];
|
|
128
|
+
}];
|
|
129
|
+
if (!drawn) {
|
|
130
|
+
throw std::runtime_error(
|
|
131
|
+
"drawElementImageToTexture: the view could not be rendered (is it "
|
|
132
|
+
"attached to a window?)");
|
|
133
|
+
}
|
|
134
|
+
cgImage = image.CGImage;
|
|
135
|
+
if (cgImage != NULL) {
|
|
136
|
+
CGImageRetain(cgImage);
|
|
137
|
+
}
|
|
138
|
+
#else
|
|
139
|
+
NSBitmapImageRep *rep = [view bitmapImageRepForCachingDisplayInRect:bounds];
|
|
140
|
+
if (rep != nil) {
|
|
141
|
+
[view cacheDisplayInRect:bounds toBitmapImageRep:rep];
|
|
142
|
+
cgImage = rep.CGImage;
|
|
143
|
+
if (cgImage != NULL) {
|
|
144
|
+
CGImageRetain(cgImage);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
#endif
|
|
148
|
+
if (cgImage == NULL) {
|
|
149
|
+
throw std::runtime_error(
|
|
150
|
+
"drawElementImageToTexture: the view could not be rendered");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Normalize into premultiplied BGRA8 with no row padding, whatever the
|
|
154
|
+
// renderer produced.
|
|
155
|
+
ImageData result;
|
|
156
|
+
result.width = width;
|
|
157
|
+
result.height = height;
|
|
158
|
+
result.format = wgpu::TextureFormat::BGRA8Unorm;
|
|
159
|
+
result.premultiplied = true;
|
|
160
|
+
result.data.assign(static_cast<size_t>(width) * height * 4, 0);
|
|
161
|
+
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
162
|
+
CGContextRef context = CGBitmapContextCreate(
|
|
163
|
+
result.data.data(), width, height, 8, static_cast<size_t>(width) * 4,
|
|
164
|
+
colorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
|
|
165
|
+
CGColorSpaceRelease(colorSpace);
|
|
166
|
+
if (context == NULL) {
|
|
167
|
+
CGImageRelease(cgImage);
|
|
168
|
+
throw std::runtime_error(
|
|
169
|
+
"drawElementImageToTexture: could not allocate the pixel buffer");
|
|
170
|
+
}
|
|
171
|
+
#if !TARGET_OS_OSX
|
|
172
|
+
// The renderer already produced exactly width x height pixels.
|
|
173
|
+
const CGRect imageRect = CGRectMake(0, 0, width, height);
|
|
174
|
+
#else
|
|
175
|
+
// The cached display covers the whole view at the backing scale; draw it so
|
|
176
|
+
// that the source rectangle fills the output. CoreGraphics' origin is the
|
|
177
|
+
// bottom-left corner, hence the flipped vertical offset.
|
|
178
|
+
const CGRect imageRect = CGRectMake(
|
|
179
|
+
-sourceX * scaleX,
|
|
180
|
+
-(bounds.size.height - sourceY - sourceHeight) * scaleY,
|
|
181
|
+
bounds.size.width * scaleX, bounds.size.height * scaleY);
|
|
182
|
+
#endif
|
|
183
|
+
CGContextSetBlendMode(context, kCGBlendModeCopy);
|
|
184
|
+
CGContextDrawImage(context, imageRect, cgImage);
|
|
185
|
+
CGContextRelease(context);
|
|
186
|
+
CGImageRelease(cgImage);
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
} // namespace
|
|
191
|
+
|
|
192
|
+
void ApplePlatformContext::snapshotView(
|
|
193
|
+
const ViewSnapshotRequest &request,
|
|
194
|
+
std::function<void(ImageData)> onSuccess,
|
|
195
|
+
std::function<void(std::string)> onError) {
|
|
196
|
+
// Copies: the block outlives this call.
|
|
197
|
+
ViewLookup lookup = _viewLookup;
|
|
198
|
+
ViewSnapshotRequest snapshotRequest = request;
|
|
199
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
200
|
+
@autoreleasepool {
|
|
201
|
+
try {
|
|
202
|
+
onSuccess(snapshotViewOnMainThread(lookup, snapshotRequest));
|
|
203
|
+
} catch (const std::exception &error) {
|
|
204
|
+
onError(error.what());
|
|
205
|
+
} catch (...) {
|
|
206
|
+
onError("drawElementImageToTexture: unknown native error");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
37
212
|
wgpu::Surface ApplePlatformContext::makeSurface(wgpu::Instance instance,
|
|
38
213
|
void *surface, int width,
|
|
39
214
|
int height) {
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
#import <AVFoundation/AVFoundation.h>
|
|
4
4
|
#import <CoreVideo/CoreVideo.h>
|
|
5
5
|
|
|
6
|
+
#include <algorithm>
|
|
7
|
+
#include <atomic>
|
|
8
|
+
#include <memory>
|
|
6
9
|
#include <stdexcept>
|
|
7
10
|
|
|
8
11
|
namespace rnwgpu {
|
|
@@ -16,27 +19,27 @@ namespace {
|
|
|
16
19
|
// range) means luma is 16..235, chroma is 16..240 (8-bit).
|
|
17
20
|
// Reference: https://en.wikipedia.org/wiki/YCbCr (BT.601 / BT.709).
|
|
18
21
|
static constexpr float kBT709LimitedToRgb[12] = {
|
|
19
|
-
1.164383f, 0.000000f,
|
|
20
|
-
1.164383f, -0.213249f, -0.532909f, 0.301517f,
|
|
21
|
-
1.164383f, 2.112402f,
|
|
22
|
+
1.164383f, 0.000000f, 1.792741f, -0.972945f, //
|
|
23
|
+
1.164383f, -0.213249f, -0.532909f, 0.301517f, //
|
|
24
|
+
1.164383f, 2.112402f, 0.000000f, -1.133402f, //
|
|
22
25
|
};
|
|
23
26
|
static constexpr float kBT601LimitedToRgb[12] = {
|
|
24
|
-
1.164383f, 0.000000f,
|
|
25
|
-
1.164383f, -0.391762f, -0.812968f, 0.531668f,
|
|
26
|
-
1.164383f, 2.017232f,
|
|
27
|
+
1.164383f, 0.000000f, 1.596027f, -0.874202f, //
|
|
28
|
+
1.164383f, -0.391762f, -0.812968f, 0.531668f, //
|
|
29
|
+
1.164383f, 2.017232f, 0.000000f, -1.085631f, //
|
|
27
30
|
};
|
|
28
31
|
static constexpr float kBT2020LimitedToRgb[12] = {
|
|
29
|
-
1.164383f, 0.000000f,
|
|
30
|
-
1.164383f, -0.187326f, -0.650424f, 0.347459f,
|
|
31
|
-
1.164383f, 2.141772f,
|
|
32
|
+
1.164383f, 0.000000f, 1.678674f, -0.915688f, //
|
|
33
|
+
1.164383f, -0.187326f, -0.650424f, 0.347459f, //
|
|
34
|
+
1.164383f, 2.141772f, 0.000000f, -1.148145f, //
|
|
32
35
|
};
|
|
33
36
|
|
|
34
37
|
// Pick the right YUV→RGB matrix from the pixel buffer's color attachments.
|
|
35
38
|
// Falls back to BT.709 limited range (the right call for ≥720p H.264, which
|
|
36
39
|
// is what AVPlayer hands us for Big Buck Bunny and most streamed media).
|
|
37
40
|
static void fillYuvMatrix(CVPixelBufferRef pixelBuffer, float out[12]) {
|
|
38
|
-
CFTypeRef matrixKey =
|
|
39
|
-
pixelBuffer, kCVImageBufferYCbCrMatrixKey, nullptr);
|
|
41
|
+
CFTypeRef matrixKey =
|
|
42
|
+
CVBufferGetAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey, nullptr);
|
|
40
43
|
const float *src = kBT709LimitedToRgb;
|
|
41
44
|
if (matrixKey) {
|
|
42
45
|
auto matrix = (CFStringRef)matrixKey;
|
|
@@ -53,28 +56,108 @@ static void fillYuvMatrix(CVPixelBufferRef pixelBuffer, float out[12]) {
|
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
// Map a CVPixelBuffer's pixel format to our VideoPixelFormat enum.
|
|
56
|
-
static VideoPixelFormat
|
|
57
|
-
|
|
59
|
+
static VideoPixelFormat
|
|
60
|
+
pixelFormatFromCVPixelBuffer(CVPixelBufferRef pixelBuffer) {
|
|
58
61
|
OSType type = CVPixelBufferGetPixelFormatType(pixelBuffer);
|
|
59
62
|
switch (type) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
|
|
64
|
+
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
|
|
65
|
+
return VideoPixelFormat::NV12;
|
|
66
|
+
default:
|
|
67
|
+
return VideoPixelFormat::BGRA8;
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
|
|
71
|
+
// The clockwise rotation, in degrees, that a track's preferredTransform
|
|
72
|
+
// applies to its frames: a video recorded in portrait stores landscape frames
|
|
73
|
+
// with a 90 or 270 degree transform.
|
|
74
|
+
static int rotationFromTransform(CGAffineTransform t) {
|
|
75
|
+
if (t.a == 0 && t.b == 1 && t.c == -1 && t.d == 0) {
|
|
76
|
+
return 90;
|
|
77
|
+
}
|
|
78
|
+
if (t.a == 0 && t.b == -1 && t.c == 1 && t.d == 0) {
|
|
79
|
+
return 270;
|
|
80
|
+
}
|
|
81
|
+
if (t.a == -1 && t.b == 0 && t.c == 0 && t.d == -1) {
|
|
82
|
+
return 180;
|
|
83
|
+
}
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// The state the AVFoundation callbacks update. It is shared with the blocks
|
|
88
|
+
// observing the asset and the item, so a callback that fires after the player
|
|
89
|
+
// was destroyed only touches memory that outlives it.
|
|
90
|
+
struct AppleVideoPlayerState {
|
|
91
|
+
std::atomic<bool> playing{false};
|
|
92
|
+
std::atomic<bool> loop{true};
|
|
93
|
+
std::atomic<double> duration{0};
|
|
94
|
+
std::atomic<double> frameRate{0};
|
|
95
|
+
std::atomic<uint32_t> width{0};
|
|
96
|
+
std::atomic<uint32_t> height{0};
|
|
97
|
+
std::atomic<int> rotation{0};
|
|
98
|
+
};
|
|
99
|
+
|
|
68
100
|
class AppleVideoPlayer : public IVideoPlayer {
|
|
69
101
|
public:
|
|
70
|
-
AppleVideoPlayer(AVPlayer *player,
|
|
71
|
-
|
|
72
|
-
: _player(player), _output(output),
|
|
102
|
+
AppleVideoPlayer(AVPlayer *player, AVPlayerItem *item,
|
|
103
|
+
AVPlayerItemVideoOutput *output)
|
|
104
|
+
: _player(player), _output(output),
|
|
105
|
+
_state(std::make_shared<AppleVideoPlayerState>()) {
|
|
106
|
+
auto state = _state;
|
|
107
|
+
|
|
108
|
+
// The metadata loads asynchronously, so that a remote asset never blocks
|
|
109
|
+
// the calling thread; the getters report 0 until it has.
|
|
110
|
+
AVAsset *asset = item.asset;
|
|
111
|
+
#pragma clang diagnostic push
|
|
112
|
+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
113
|
+
[asset
|
|
114
|
+
loadValuesAsynchronouslyForKeys:@[ @"duration", @"tracks" ]
|
|
115
|
+
completionHandler:^{
|
|
116
|
+
if ([asset statusOfValueForKey:@"duration" error:nil] ==
|
|
117
|
+
AVKeyValueStatusLoaded) {
|
|
118
|
+
CMTime duration = asset.duration;
|
|
119
|
+
if (CMTIME_IS_NUMERIC(duration)) {
|
|
120
|
+
state->duration = CMTimeGetSeconds(duration);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if ([asset statusOfValueForKey:@"tracks" error:nil] ==
|
|
124
|
+
AVKeyValueStatusLoaded) {
|
|
125
|
+
AVAssetTrack *track =
|
|
126
|
+
[[asset tracksWithMediaType:AVMediaTypeVideo]
|
|
127
|
+
firstObject];
|
|
128
|
+
if (track) {
|
|
129
|
+
CGSize size = track.naturalSize;
|
|
130
|
+
state->width = static_cast<uint32_t>(size.width);
|
|
131
|
+
state->height = static_cast<uint32_t>(size.height);
|
|
132
|
+
state->rotation =
|
|
133
|
+
rotationFromTransform(track.preferredTransform);
|
|
134
|
+
state->frameRate = track.nominalFrameRate;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}];
|
|
138
|
+
#pragma clang diagnostic pop
|
|
139
|
+
|
|
140
|
+
// At the end of the stream, either loop or stop. The player keeps showing
|
|
141
|
+
// the last frame either way (actionAtItemEnd is None).
|
|
142
|
+
__weak AVPlayer *weakPlayer = player;
|
|
143
|
+
_endObserver = [[NSNotificationCenter defaultCenter]
|
|
144
|
+
addObserverForName:AVPlayerItemDidPlayToEndTimeNotification
|
|
145
|
+
object:item
|
|
146
|
+
queue:[NSOperationQueue mainQueue]
|
|
147
|
+
usingBlock:^(NSNotification * /*note*/) {
|
|
148
|
+
if (state->loop) {
|
|
149
|
+
[weakPlayer seekToTime:kCMTimeZero];
|
|
150
|
+
[weakPlayer play];
|
|
151
|
+
} else {
|
|
152
|
+
state->playing = false;
|
|
153
|
+
}
|
|
154
|
+
}];
|
|
155
|
+
}
|
|
73
156
|
|
|
74
157
|
~AppleVideoPlayer() override {
|
|
75
|
-
if (
|
|
76
|
-
[[NSNotificationCenter defaultCenter] removeObserver:
|
|
77
|
-
|
|
158
|
+
if (_endObserver) {
|
|
159
|
+
[[NSNotificationCenter defaultCenter] removeObserver:_endObserver];
|
|
160
|
+
_endObserver = nil;
|
|
78
161
|
}
|
|
79
162
|
[_player pause];
|
|
80
163
|
_player = nil;
|
|
@@ -105,13 +188,51 @@ public:
|
|
|
105
188
|
}
|
|
106
189
|
}
|
|
107
190
|
|
|
108
|
-
void play() override {
|
|
109
|
-
|
|
191
|
+
void play() override {
|
|
192
|
+
_state->playing = true;
|
|
193
|
+
[_player play];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
void pause() override {
|
|
197
|
+
_state->playing = false;
|
|
198
|
+
[_player pause];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
bool paused() override { return !_state->playing; }
|
|
202
|
+
|
|
203
|
+
double currentTime() override {
|
|
204
|
+
CMTime time = [_player currentTime];
|
|
205
|
+
return CMTIME_IS_NUMERIC(time) ? CMTimeGetSeconds(time) : 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// An exact seek: the output then holds the frame at that position, also
|
|
209
|
+
// while paused, so the next copyLatestFrame() returns it.
|
|
210
|
+
void seek(double seconds) override {
|
|
211
|
+
[_player seekToTime:CMTimeMakeWithSeconds(seconds, NSEC_PER_SEC)
|
|
212
|
+
toleranceBefore:kCMTimeZero
|
|
213
|
+
toleranceAfter:kCMTimeZero];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
double duration() override { return _state->duration; }
|
|
217
|
+
|
|
218
|
+
double volume() override { return _player.volume; }
|
|
219
|
+
void setVolume(double volume) override {
|
|
220
|
+
_player.volume = static_cast<float>(std::clamp(volume, 0.0, 1.0));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
bool loop() override { return _state->loop; }
|
|
224
|
+
void setLoop(bool loop) override { _state->loop = loop; }
|
|
225
|
+
|
|
226
|
+
uint32_t videoWidth() override { return _state->width; }
|
|
227
|
+
uint32_t videoHeight() override { return _state->height; }
|
|
228
|
+
int rotation() override { return _state->rotation; }
|
|
229
|
+
double frameRate() override { return _state->frameRate; }
|
|
110
230
|
|
|
111
231
|
private:
|
|
112
232
|
AVPlayer *_player;
|
|
113
233
|
AVPlayerItemVideoOutput *_output;
|
|
114
|
-
|
|
234
|
+
std::shared_ptr<AppleVideoPlayerState> _state;
|
|
235
|
+
id _endObserver = nil;
|
|
115
236
|
};
|
|
116
237
|
|
|
117
238
|
} // namespace
|
|
@@ -142,8 +263,8 @@ VideoFrameHandle wrapCVPixelBuffer(CVPixelBufferRef pixelBuffer) {
|
|
|
142
263
|
return handle;
|
|
143
264
|
}
|
|
144
265
|
|
|
145
|
-
std::unique_ptr<IVideoPlayer>
|
|
146
|
-
|
|
266
|
+
std::unique_ptr<IVideoPlayer> createAppleVideoPlayer(const std::string &path,
|
|
267
|
+
VideoPixelFormat format) {
|
|
147
268
|
NSString *nsPath = [NSString stringWithUTF8String:path.c_str()];
|
|
148
269
|
NSURL *url;
|
|
149
270
|
if ([nsPath hasPrefix:@"http://"] || [nsPath hasPrefix:@"https://"] ||
|
|
@@ -164,8 +285,8 @@ createAppleVideoPlayer(const std::string &path, VideoPixelFormat format) {
|
|
|
164
285
|
// importExternalTexture. BGRA is the "decode + convert" path for the
|
|
165
286
|
// single-plane SharedTextureMemory demo.
|
|
166
287
|
OSType pixelFormat = format == VideoPixelFormat::NV12
|
|
167
|
-
|
|
168
|
-
|
|
288
|
+
? kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
|
|
289
|
+
: kCVPixelFormatType_32BGRA;
|
|
169
290
|
NSDictionary *outputSettings = @{
|
|
170
291
|
(NSString *)kCVPixelBufferPixelFormatTypeKey : @(pixelFormat),
|
|
171
292
|
(NSString *)kCVPixelBufferIOSurfacePropertiesKey : @{},
|
|
@@ -176,20 +297,11 @@ createAppleVideoPlayer(const std::string &path, VideoPixelFormat format) {
|
|
|
176
297
|
[item addOutput:output];
|
|
177
298
|
|
|
178
299
|
AVPlayer *player = [AVPlayer playerWithPlayerItem:item];
|
|
300
|
+
// Keep the last frame at the end of the stream; AppleVideoPlayer decides
|
|
301
|
+
// whether to loop.
|
|
179
302
|
player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
|
|
180
303
|
|
|
181
|
-
|
|
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);
|
|
304
|
+
return std::make_unique<AppleVideoPlayer>(player, item, output);
|
|
193
305
|
}
|
|
194
306
|
|
|
195
307
|
std::string writeAppleTestVideoFile() {
|
|
@@ -231,8 +343,7 @@ std::string writeAppleTestVideoFile() {
|
|
|
231
343
|
writerInput.expectsMediaDataInRealTime = NO;
|
|
232
344
|
|
|
233
345
|
NSDictionary *bufferAttrs = @{
|
|
234
|
-
(NSString *)kCVPixelBufferPixelFormatTypeKey :
|
|
235
|
-
@(kCVPixelFormatType_32BGRA),
|
|
346
|
+
(NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA),
|
|
236
347
|
(NSString *)kCVPixelBufferWidthKey : @(kWidth),
|
|
237
348
|
(NSString *)kCVPixelBufferHeightKey : @(kHeight),
|
|
238
349
|
(NSString *)kCVPixelBufferIOSurfacePropertiesKey : @{},
|
|
@@ -274,8 +385,7 @@ std::string writeAppleTestVideoFile() {
|
|
|
274
385
|
for (int x = 0; x < kWidth; ++x) {
|
|
275
386
|
uint8_t r = static_cast<uint8_t>((x + phase) & 0xFF);
|
|
276
387
|
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);
|
|
388
|
+
uint8_t b = static_cast<uint8_t>(((x + y + phase) & 0x40) ? 220 : 30);
|
|
279
389
|
row[x * 4 + 0] = b;
|
|
280
390
|
row[x * 4 + 1] = g;
|
|
281
391
|
row[x * 4 + 2] = r;
|
package/apple/WebGPUModule.mm
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#import "WebGPUModule.h"
|
|
2
2
|
#include "ApplePlatformContext.h"
|
|
3
3
|
#import "GPUCanvasContext.h"
|
|
4
|
+
#import "RNWGUIKit.h"
|
|
4
5
|
|
|
5
6
|
#import <React/RCTBridge+Private.h>
|
|
6
7
|
#import <React/RCTCallInvoker.h>
|
|
@@ -31,6 +32,11 @@ static std::shared_ptr<rnwgpu::RNWebGPUManager> webgpuManager;
|
|
|
31
32
|
// calls setCallInvoker: during module initialization.
|
|
32
33
|
@synthesize callInvoker = _callInvoker;
|
|
33
34
|
|
|
35
|
+
// Synthesize the view registry so the TurboModule infra injects it too. It
|
|
36
|
+
// resolves React tags to native views in both Bridge and Bridgeless mode
|
|
37
|
+
// (Fabric component views included), which drawElementImageToTexture needs.
|
|
38
|
+
@synthesize viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED;
|
|
39
|
+
|
|
34
40
|
+ (std::shared_ptr<rnwgpu::RNWebGPUManager>)getManager {
|
|
35
41
|
return webgpuManager;
|
|
36
42
|
}
|
|
@@ -74,8 +80,21 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install) {
|
|
|
74
80
|
return [NSNumber numberWithBool:NO];
|
|
75
81
|
}
|
|
76
82
|
|
|
83
|
+
// Main-thread only (see ApplePlatformContext::ViewLookup). Weak: the module
|
|
84
|
+
// is invalidated on reload, after which lookups simply fail.
|
|
85
|
+
__weak WebGPUModule *weakSelf = self;
|
|
86
|
+
rnwgpu::ApplePlatformContext::ViewLookup viewLookup =
|
|
87
|
+
[weakSelf](int tag) -> void * {
|
|
88
|
+
WebGPUModule *strongSelf = weakSelf;
|
|
89
|
+
if (strongSelf == nil) {
|
|
90
|
+
return nullptr;
|
|
91
|
+
}
|
|
92
|
+
RNWGPlatformView *view =
|
|
93
|
+
[strongSelf.viewRegistry_DEPRECATED viewForReactTag:@(tag)];
|
|
94
|
+
return (__bridge void *)view;
|
|
95
|
+
};
|
|
77
96
|
std::shared_ptr<rnwgpu::PlatformContext> platformContext =
|
|
78
|
-
std::make_shared<rnwgpu::ApplePlatformContext>();
|
|
97
|
+
std::make_shared<rnwgpu::ApplePlatformContext>(std::move(viewLookup));
|
|
79
98
|
webgpuManager = std::make_shared<rnwgpu::RNWebGPUManager>(runtime, jsInvoker,
|
|
80
99
|
platformContext);
|
|
81
100
|
return @true;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
#include <functional>
|
|
4
4
|
#include <memory>
|
|
5
|
+
#include <mutex>
|
|
5
6
|
#include <span>
|
|
6
7
|
#include <string>
|
|
7
8
|
#include <utility>
|
|
@@ -58,7 +59,8 @@ struct VideoFrameHandle {
|
|
|
58
59
|
};
|
|
59
60
|
|
|
60
61
|
// Platform-implemented video source that hands out fresh IOSurface /
|
|
61
|
-
// AHardwareBuffer-backed frames as a video plays
|
|
62
|
+
// AHardwareBuffer-backed frames as a video plays, with the playback controls
|
|
63
|
+
// of an HTMLMediaElement (times in seconds).
|
|
62
64
|
class IVideoPlayer {
|
|
63
65
|
public:
|
|
64
66
|
virtual ~IVideoPlayer() = default;
|
|
@@ -70,6 +72,44 @@ public:
|
|
|
70
72
|
|
|
71
73
|
virtual void play() = 0;
|
|
72
74
|
virtual void pause() = 0;
|
|
75
|
+
virtual bool paused() = 0;
|
|
76
|
+
|
|
77
|
+
// Playback position and length, in seconds. The duration is 0 until the
|
|
78
|
+
// metadata of the media has loaded.
|
|
79
|
+
virtual double currentTime() = 0;
|
|
80
|
+
virtual void seek(double seconds) = 0;
|
|
81
|
+
virtual double duration() = 0;
|
|
82
|
+
|
|
83
|
+
virtual double volume() = 0;
|
|
84
|
+
virtual void setVolume(double volume) = 0;
|
|
85
|
+
virtual bool loop() = 0;
|
|
86
|
+
virtual void setLoop(bool loop) = 0;
|
|
87
|
+
|
|
88
|
+
// The geometry of the frames, 0 until the metadata has loaded: their coded
|
|
89
|
+
// size, the clockwise rotation (0, 90, 180 or 270) to apply when displaying
|
|
90
|
+
// them, and the nominal frame rate of the video track.
|
|
91
|
+
virtual uint32_t videoWidth() = 0;
|
|
92
|
+
virtual uint32_t videoHeight() = 0;
|
|
93
|
+
virtual int rotation() = 0;
|
|
94
|
+
virtual double frameRate() = 0;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// A request to rasterize a native React Native view (looked up by its React
|
|
98
|
+
// tag) for GPUQueue.drawElementImageToTexture.
|
|
99
|
+
//
|
|
100
|
+
// The source rectangle is expressed in the view's own coordinate space, in
|
|
101
|
+
// density-independent points (the units of React Native layout). A
|
|
102
|
+
// non-positive sourceWidth/sourceHeight means "the whole view". The
|
|
103
|
+
// destination size is in pixels: the platform rasterizes the source rectangle
|
|
104
|
+
// scaled to exactly width x height.
|
|
105
|
+
struct ViewSnapshotRequest {
|
|
106
|
+
int viewTag = 0;
|
|
107
|
+
double sourceX = 0;
|
|
108
|
+
double sourceY = 0;
|
|
109
|
+
double sourceWidth = 0;
|
|
110
|
+
double sourceHeight = 0;
|
|
111
|
+
uint32_t width = 0;
|
|
112
|
+
uint32_t height = 0;
|
|
73
113
|
};
|
|
74
114
|
|
|
75
115
|
class PlatformContext {
|
|
@@ -77,6 +117,31 @@ public:
|
|
|
77
117
|
PlatformContext() = default;
|
|
78
118
|
virtual ~PlatformContext() = default;
|
|
79
119
|
|
|
120
|
+
// The platform context of the currently installed RNWebGPUManager, for API
|
|
121
|
+
// objects (e.g. GPUQueue) that are created without a reference to it. Set on
|
|
122
|
+
// install, cleared on teardown; a dev reload replaces it.
|
|
123
|
+
static void setCurrent(std::shared_ptr<PlatformContext> context) {
|
|
124
|
+
std::lock_guard<std::mutex> lock(currentMutex());
|
|
125
|
+
currentSlot() = std::move(context);
|
|
126
|
+
}
|
|
127
|
+
static std::shared_ptr<PlatformContext> current() {
|
|
128
|
+
std::lock_guard<std::mutex> lock(currentMutex());
|
|
129
|
+
return currentSlot();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Rasterize a native view into CPU pixels (see ViewSnapshotRequest). The
|
|
133
|
+
// implementation hops to the platform UI thread, so this is asynchronous:
|
|
134
|
+
// exactly one of onSuccess / onError is invoked, from an arbitrary thread
|
|
135
|
+
// (typically the UI thread). Callers must not touch JSI in the callbacks.
|
|
136
|
+
//
|
|
137
|
+
// The returned ImageData is tightly packed (no row padding), 4 bytes per
|
|
138
|
+
// pixel, `format` is rgba8unorm or bgra8unorm depending on what the platform
|
|
139
|
+
// rasterizer produces natively, and `premultiplied` reports its alpha
|
|
140
|
+
// representation.
|
|
141
|
+
virtual void snapshotView(const ViewSnapshotRequest &request,
|
|
142
|
+
std::function<void(ImageData)> onSuccess,
|
|
143
|
+
std::function<void(std::string)> onError) = 0;
|
|
144
|
+
|
|
80
145
|
virtual wgpu::Surface makeSurface(wgpu::Instance instance, void *surface,
|
|
81
146
|
int width, int height) = 0;
|
|
82
147
|
virtual ImageData createImageBitmap(std::string blobId, double offset,
|
|
@@ -129,6 +194,16 @@ public:
|
|
|
129
194
|
// and return its absolute path. Lets the SharedTextureMemory example play
|
|
130
195
|
// a real decoded video without bundling an asset.
|
|
131
196
|
virtual std::string writeTestVideoFile() = 0;
|
|
197
|
+
|
|
198
|
+
private:
|
|
199
|
+
static std::mutex ¤tMutex() {
|
|
200
|
+
static std::mutex mutex;
|
|
201
|
+
return mutex;
|
|
202
|
+
}
|
|
203
|
+
static std::shared_ptr<PlatformContext> ¤tSlot() {
|
|
204
|
+
static std::shared_ptr<PlatformContext> slot;
|
|
205
|
+
return slot;
|
|
206
|
+
}
|
|
132
207
|
};
|
|
133
208
|
|
|
134
209
|
} // namespace rnwgpu
|