react-native-webgpu 0.14.2 → 0.15.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/AndroidColorSpace.h +107 -0
- package/android/cpp/AndroidPlatformContext.h +100 -9
- package/android/cpp/AndroidVideoPlayer.h +46 -1
- package/android/cpp/cpp-adapter.cpp +53 -1
- package/android/src/main/java/com/webgpu/SyncFences.java +88 -0
- package/android/src/main/java/com/webgpu/ViewSnapshot.java +66 -23
- package/android/src/main/java/com/webgpu/WebGPUModule.java +69 -5
- package/android/src/main/java/com/webgpu/WebGPUVideoPlayer.java +109 -0
- package/apple/ApplePlatformContext.h +5 -5
- package/apple/ApplePlatformContext.mm +301 -90
- package/apple/AppleVideoPlayer.mm +68 -35
- package/cpp/dawn/common/ExternalTextureParams.h +67 -0
- package/cpp/rnwgpu/PlatformContext.h +71 -16
- package/cpp/rnwgpu/api/GPUAdapter.cpp +35 -0
- package/cpp/rnwgpu/api/GPUExternalTexture.cpp +172 -156
- package/cpp/rnwgpu/api/GPUExternalTexture.h +2 -19
- package/cpp/rnwgpu/api/GPUQueue.cpp +101 -3
- package/cpp/rnwgpu/api/RNWebGPU.h +9 -3
- package/cpp/rnwgpu/api/VideoFrameBlit.h +39 -24
- package/cpp/rnwgpu/api/descriptors/GPUDrawElementImageSource.h +7 -0
- package/cpp/rnwgpu/api/descriptors/GPUExternalTextureDescriptor.h +10 -3
- package/lib/commonjs/Canvas.js +32 -5
- package/lib/commonjs/Canvas.js.map +1 -1
- package/lib/commonjs/WebPolyfillGPUModule.js +6 -3
- package/lib/commonjs/WebPolyfillGPUModule.js.map +1 -1
- package/lib/commonjs/constants.js +6 -3
- package/lib/commonjs/constants.js.map +1 -1
- package/lib/commonjs/index.js +1 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/install.js +9 -22
- package/lib/commonjs/install.js.map +1 -1
- package/lib/commonjs/main/index.js +8 -7
- package/lib/commonjs/main/index.js.map +1 -1
- package/lib/commonjs/main/index.web.js +1 -1
- package/lib/commonjs/main/installNative.js +20 -0
- package/lib/commonjs/main/installNative.js.map +1 -0
- package/lib/commonjs/main/installNative.web.js +4 -0
- package/lib/commonjs/main/installNative.web.js.map +1 -0
- package/lib/commonjs/mock.js +60 -13
- package/lib/commonjs/mock.js.map +1 -1
- package/lib/module/Canvas.js +33 -6
- package/lib/module/Canvas.js.map +1 -1
- package/lib/module/WebPolyfillGPUModule.js +6 -3
- package/lib/module/WebPolyfillGPUModule.js.map +1 -1
- package/lib/module/constants.js +6 -3
- package/lib/module/constants.js.map +1 -1
- package/lib/module/index.js +8 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/install.js +8 -20
- package/lib/module/install.js.map +1 -1
- package/lib/module/main/index.js +6 -7
- package/lib/module/main/index.js.map +1 -1
- package/lib/module/main/index.web.js +1 -1
- package/lib/module/main/index.web.js.map +1 -1
- package/lib/module/main/installNative.js +18 -0
- package/lib/module/main/installNative.js.map +1 -0
- package/lib/module/main/installNative.web.js +6 -0
- package/lib/module/main/installNative.web.js.map +1 -0
- package/lib/module/mock.js +55 -1
- package/lib/module/mock.js.map +1 -1
- package/lib/typescript/lib/commonjs/Canvas.d.ts.map +1 -1
- package/lib/typescript/lib/commonjs/install.d.ts +41 -3
- package/lib/typescript/lib/commonjs/install.d.ts.map +1 -1
- package/lib/typescript/lib/commonjs/main/installNative.d.ts +2 -0
- package/lib/typescript/lib/commonjs/main/installNative.d.ts.map +1 -0
- package/lib/typescript/lib/commonjs/main/installNative.web.d.ts +2 -0
- package/lib/typescript/lib/commonjs/main/installNative.web.d.ts.map +1 -0
- package/lib/typescript/lib/commonjs/mock.d.ts +39 -0
- package/lib/typescript/lib/commonjs/mock.d.ts.map +1 -1
- package/lib/typescript/lib/module/Canvas.d.ts.map +1 -1
- package/lib/typescript/lib/module/install.d.ts +0 -1
- package/lib/typescript/lib/module/install.d.ts.map +1 -1
- package/lib/typescript/lib/module/main/installNative.d.ts +2 -0
- package/lib/typescript/lib/module/main/installNative.d.ts.map +1 -0
- package/lib/typescript/lib/module/main/installNative.web.d.ts +2 -0
- package/lib/typescript/lib/module/main/installNative.web.d.ts.map +1 -0
- package/lib/typescript/lib/module/mock.d.ts +39 -1
- package/lib/typescript/lib/module/mock.d.ts.map +1 -1
- package/lib/typescript/src/Canvas.d.ts.map +1 -1
- package/lib/typescript/src/constants.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/install.d.ts +0 -5
- package/lib/typescript/src/install.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 -1
- package/lib/typescript/src/main/index.web.d.ts.map +1 -1
- package/lib/typescript/src/main/installNative.d.ts +2 -0
- package/lib/typescript/src/main/installNative.d.ts.map +1 -0
- package/lib/typescript/src/main/installNative.web.d.ts +2 -0
- package/lib/typescript/src/main/installNative.web.d.ts.map +1 -0
- package/lib/typescript/src/mock.d.ts +5 -1
- package/lib/typescript/src/mock.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +1 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Canvas.tsx +44 -28
- package/src/WebPolyfillGPUModule.ts +3 -1
- package/src/constants.ts +6 -3
- package/src/index.tsx +18 -10
- package/src/install.ts +9 -18
- package/src/main/index.tsx +7 -10
- package/src/main/index.web.tsx +1 -1
- package/src/main/installNative.ts +20 -0
- package/src/main/installNative.web.ts +5 -0
- package/src/mock.ts +60 -1
- package/src/types.ts +6 -0
|
@@ -42,156 +42,279 @@ ApplePlatformContext::ApplePlatformContext(ViewLookup viewLookup)
|
|
|
42
42
|
|
|
43
43
|
namespace {
|
|
44
44
|
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
// A view that UIKit declined to render (it is not in a window). The pixel
|
|
46
|
+
// path would fail the same way, so there is no point falling back to it.
|
|
47
|
+
struct ViewNotRenderable : std::runtime_error {
|
|
48
|
+
using std::runtime_error::runtime_error;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// The view, the output size and the rectangle to draw it in, resolved from a
|
|
52
|
+
// request on the main thread. Throws std::runtime_error with a user-facing
|
|
53
|
+
// message.
|
|
54
|
+
struct ResolvedSnapshot {
|
|
55
|
+
RNWGPlatformView *view = nil;
|
|
56
|
+
CGRect bounds = CGRectZero;
|
|
57
|
+
// The source rectangle in points (view space).
|
|
58
|
+
double sourceX = 0;
|
|
59
|
+
double sourceY = 0;
|
|
60
|
+
double sourceWidth = 0;
|
|
61
|
+
double sourceHeight = 0;
|
|
62
|
+
// The output size in pixels.
|
|
63
|
+
uint32_t width = 0;
|
|
64
|
+
uint32_t height = 0;
|
|
65
|
+
CGFloat scaleX = 1;
|
|
66
|
+
CGFloat scaleY = 1;
|
|
67
|
+
// The view's bounds, scaled and offset so that the source rectangle covers
|
|
68
|
+
// the whole output, in pixels and with UIKit's top-left origin.
|
|
69
|
+
CGRect drawRect = CGRectZero;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
ResolvedSnapshot resolveSnapshot(const ApplePlatformContext::ViewLookup &lookup,
|
|
73
|
+
const ViewSnapshotRequest &request) {
|
|
49
74
|
if (!lookup) {
|
|
50
75
|
throw std::runtime_error(
|
|
51
76
|
"drawElementImageToTexture: no view registry is available");
|
|
52
77
|
}
|
|
53
|
-
|
|
54
|
-
|
|
78
|
+
ResolvedSnapshot snapshot;
|
|
79
|
+
snapshot.view = (__bridge RNWGPlatformView *)lookup(request.viewTag);
|
|
80
|
+
if (snapshot.view == nil) {
|
|
55
81
|
throw std::runtime_error(
|
|
56
82
|
"drawElementImageToTexture: no native view found for tag " +
|
|
57
83
|
std::to_string(request.viewTag) +
|
|
58
84
|
" (is the view mounted, and rendered with collapsable={false}?)");
|
|
59
85
|
}
|
|
60
|
-
|
|
86
|
+
snapshot.bounds = snapshot.view.bounds;
|
|
87
|
+
const CGRect bounds = snapshot.bounds;
|
|
61
88
|
if (bounds.size.width <= 0 || bounds.size.height <= 0) {
|
|
62
89
|
throw std::runtime_error(
|
|
63
90
|
"drawElementImageToTexture: the view has no size yet");
|
|
64
91
|
}
|
|
65
92
|
|
|
66
93
|
// Source rectangle in points (view space); the whole view by default.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
94
|
+
snapshot.sourceX = request.sourceX;
|
|
95
|
+
snapshot.sourceY = request.sourceY;
|
|
96
|
+
snapshot.sourceWidth =
|
|
70
97
|
request.sourceWidth > 0 ? request.sourceWidth : bounds.size.width;
|
|
71
|
-
|
|
98
|
+
snapshot.sourceHeight =
|
|
72
99
|
request.sourceHeight > 0 ? request.sourceHeight : bounds.size.height;
|
|
73
100
|
|
|
74
101
|
// Natural pixel size: points times the screen scale the view is shown at.
|
|
75
102
|
#if !TARGET_OS_OSX
|
|
76
|
-
CGFloat scale = view.window.screen.scale;
|
|
103
|
+
CGFloat scale = snapshot.view.window.screen.scale;
|
|
77
104
|
if (scale <= 0) {
|
|
78
105
|
scale = UIScreen.mainScreen.scale;
|
|
79
106
|
}
|
|
80
107
|
#else
|
|
81
|
-
CGFloat scale = view.window.backingScaleFactor;
|
|
108
|
+
CGFloat scale = snapshot.view.window.backingScaleFactor;
|
|
82
109
|
if (scale <= 0) {
|
|
83
110
|
scale = NSScreen.mainScreen.backingScaleFactor;
|
|
84
111
|
}
|
|
85
112
|
#endif
|
|
86
|
-
|
|
113
|
+
snapshot.width =
|
|
87
114
|
request.width > 0
|
|
88
115
|
? request.width
|
|
89
|
-
: static_cast<uint32_t>(std::llround(sourceWidth * scale));
|
|
90
|
-
|
|
116
|
+
: static_cast<uint32_t>(std::llround(snapshot.sourceWidth * scale));
|
|
117
|
+
snapshot.height =
|
|
91
118
|
request.height > 0
|
|
92
119
|
? request.height
|
|
93
|
-
: static_cast<uint32_t>(std::llround(sourceHeight * scale));
|
|
94
|
-
if (width == 0 || height == 0) {
|
|
120
|
+
: static_cast<uint32_t>(std::llround(snapshot.sourceHeight * scale));
|
|
121
|
+
if (snapshot.width == 0 || snapshot.height == 0) {
|
|
95
122
|
throw std::runtime_error(
|
|
96
123
|
"drawElementImageToTexture: the source rectangle is empty");
|
|
97
124
|
}
|
|
98
125
|
|
|
99
126
|
// The view's bounds are drawn scaled so that the source rectangle covers
|
|
100
127
|
// the whole output.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
128
|
+
snapshot.scaleX = static_cast<CGFloat>(snapshot.width) / snapshot.sourceWidth;
|
|
129
|
+
snapshot.scaleY =
|
|
130
|
+
static_cast<CGFloat>(snapshot.height) / snapshot.sourceHeight;
|
|
131
|
+
snapshot.drawRect = CGRectMake(-snapshot.sourceX * snapshot.scaleX,
|
|
132
|
+
-snapshot.sourceY * snapshot.scaleY,
|
|
133
|
+
bounds.size.width * snapshot.scaleX,
|
|
134
|
+
bounds.size.height * snapshot.scaleY);
|
|
135
|
+
return snapshot;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// A premultiplied BGRA8 Core Graphics context over `pixels`, the layout both
|
|
139
|
+
// snapshot paths produce. Returns NULL when Core Graphics rejects it.
|
|
140
|
+
CGContextRef createBgraContext(void *pixels, uint32_t width, uint32_t height,
|
|
141
|
+
size_t bytesPerRow) {
|
|
142
|
+
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
143
|
+
CGContextRef context = CGBitmapContextCreate(
|
|
144
|
+
pixels, width, height, 8, bytesPerRow, colorSpace,
|
|
145
|
+
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
|
|
146
|
+
CGColorSpaceRelease(colorSpace);
|
|
147
|
+
return context;
|
|
148
|
+
}
|
|
106
149
|
|
|
107
|
-
CGImageRef cgImage = NULL;
|
|
108
150
|
#if !TARGET_OS_OSX
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
151
|
+
// Draws the view hierarchy through `context`, a bitmap context of exactly
|
|
152
|
+
// `snapshot.width` x `snapshot.height` pixels (Core Graphics orientation; the
|
|
153
|
+
// flip to UIKit's top-left origin is applied here).
|
|
154
|
+
//
|
|
155
|
+
// drawViewHierarchyInRect renders what is on screen (including Metal /
|
|
156
|
+
// CAMetalLayer content), which is what "the element as displayed" means.
|
|
157
|
+
// afterScreenUpdates:YES flushes pending layout and layer changes first, so a
|
|
158
|
+
// snapshot taken right after a state change reflects that change; NO draws
|
|
159
|
+
// the last committed frame and skips that round trip to the render server.
|
|
160
|
+
//
|
|
161
|
+
// drawViewHierarchyInRect returns NO and draws nothing when the view is not
|
|
162
|
+
// in a window (e.g. a screen pushed under another one). The output would then
|
|
163
|
+
// be fully transparent, so that is reported as a failure instead.
|
|
164
|
+
void drawViewHierarchy(const ResolvedSnapshot &snapshot, CGContextRef context,
|
|
165
|
+
bool afterScreenUpdates) {
|
|
166
|
+
CGContextSaveGState(context);
|
|
167
|
+
CGContextTranslateCTM(context, 0, snapshot.height);
|
|
168
|
+
CGContextScaleCTM(context, 1, -1);
|
|
169
|
+
UIGraphicsPushContext(context);
|
|
170
|
+
const BOOL drawn =
|
|
171
|
+
[snapshot.view drawViewHierarchyInRect:snapshot.drawRect
|
|
172
|
+
afterScreenUpdates:afterScreenUpdates ? YES : NO];
|
|
173
|
+
UIGraphicsPopContext();
|
|
174
|
+
CGContextRestoreGState(context);
|
|
129
175
|
if (!drawn) {
|
|
130
|
-
throw
|
|
176
|
+
throw ViewNotRenderable(
|
|
131
177
|
"drawElementImageToTexture: the view could not be rendered (is it "
|
|
132
178
|
"attached to a window?)");
|
|
133
179
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
cgImage = rep.CGImage;
|
|
143
|
-
if (cgImage != NULL) {
|
|
144
|
-
CGImageRetain(cgImage);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Releases a pixel buffer when it goes out of scope.
|
|
183
|
+
struct PixelBufferRelease {
|
|
184
|
+
CVPixelBufferRef buffer;
|
|
185
|
+
~PixelBufferRelease() {
|
|
186
|
+
if (buffer != NULL) {
|
|
187
|
+
CFRelease(buffer);
|
|
145
188
|
}
|
|
146
189
|
}
|
|
147
|
-
|
|
148
|
-
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// The zero-copy path: renders the view into an IOSurface-backed BGRA pixel
|
|
193
|
+
// buffer, which the caller imports as shared texture memory and draws into
|
|
194
|
+
// the destination on the GPU (VideoFrameBlit, which also converts the
|
|
195
|
+
// premultiplied pixels to straight alpha when asked). UIKit rasterizes
|
|
196
|
+
// straight into the surface's memory through a Core Graphics context, so the
|
|
197
|
+
// pixels are never copied or converted on the CPU. Throws on an allocation
|
|
198
|
+
// failure, which the caller answers with the pixel path.
|
|
199
|
+
VideoFrameHandle renderViewIntoSurface(const ResolvedSnapshot &snapshot,
|
|
200
|
+
bool afterScreenUpdates) {
|
|
201
|
+
NSDictionary *attributes = @{
|
|
202
|
+
(NSString *)kCVPixelBufferIOSurfacePropertiesKey : @{},
|
|
203
|
+
(NSString *)kCVPixelBufferMetalCompatibilityKey : @YES,
|
|
204
|
+
};
|
|
205
|
+
CVPixelBufferRef pixelBuffer = NULL;
|
|
206
|
+
const CVReturn created =
|
|
207
|
+
CVPixelBufferCreate(kCFAllocatorDefault, snapshot.width, snapshot.height,
|
|
208
|
+
kCVPixelFormatType_32BGRA,
|
|
209
|
+
(__bridge CFDictionaryRef)attributes, &pixelBuffer);
|
|
210
|
+
if (created != kCVReturnSuccess || pixelBuffer == NULL) {
|
|
149
211
|
throw std::runtime_error(
|
|
150
|
-
"drawElementImageToTexture:
|
|
212
|
+
"drawElementImageToTexture: could not allocate an IOSurface for the "
|
|
213
|
+
"view (CVPixelBufferCreate returned " +
|
|
214
|
+
std::to_string(created) + ")");
|
|
215
|
+
}
|
|
216
|
+
// wrapCVPixelBuffer retains its own reference for the frame.
|
|
217
|
+
PixelBufferRelease release{pixelBuffer};
|
|
218
|
+
|
|
219
|
+
if (CVPixelBufferLockBaseAddress(pixelBuffer, 0) != kCVReturnSuccess) {
|
|
220
|
+
throw std::runtime_error(
|
|
221
|
+
"drawElementImageToTexture: could not lock the IOSurface");
|
|
222
|
+
}
|
|
223
|
+
CGContextRef context = createBgraContext(
|
|
224
|
+
CVPixelBufferGetBaseAddress(pixelBuffer), snapshot.width, snapshot.height,
|
|
225
|
+
CVPixelBufferGetBytesPerRow(pixelBuffer));
|
|
226
|
+
if (context == NULL) {
|
|
227
|
+
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
|
|
228
|
+
throw std::runtime_error(
|
|
229
|
+
"drawElementImageToTexture: could not draw into the IOSurface");
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
// A fresh surface holds whatever was in that memory, and the view may be
|
|
233
|
+
// transparent.
|
|
234
|
+
CGContextClearRect(context,
|
|
235
|
+
CGRectMake(0, 0, snapshot.width, snapshot.height));
|
|
236
|
+
drawViewHierarchy(snapshot, context, afterScreenUpdates);
|
|
237
|
+
} catch (...) {
|
|
238
|
+
CGContextRelease(context);
|
|
239
|
+
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
|
|
240
|
+
throw;
|
|
151
241
|
}
|
|
242
|
+
CGContextRelease(context);
|
|
243
|
+
// Unlocking publishes the CPU writes to the GPU.
|
|
244
|
+
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
|
|
245
|
+
return wrapCVPixelBuffer(pixelBuffer);
|
|
246
|
+
}
|
|
247
|
+
#endif
|
|
152
248
|
|
|
153
|
-
|
|
154
|
-
|
|
249
|
+
// The pixel path: rasterizes the view into tightly packed, premultiplied
|
|
250
|
+
// BGRA8 pixels that the caller uploads with writeTexture.
|
|
251
|
+
ImageData renderViewIntoPixels(const ResolvedSnapshot &snapshot,
|
|
252
|
+
bool afterScreenUpdates) {
|
|
155
253
|
ImageData result;
|
|
156
|
-
result.width = width;
|
|
157
|
-
result.height = height;
|
|
254
|
+
result.width = snapshot.width;
|
|
255
|
+
result.height = snapshot.height;
|
|
158
256
|
result.format = wgpu::TextureFormat::BGRA8Unorm;
|
|
159
257
|
result.premultiplied = true;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
258
|
+
// Zeroed: the view may be transparent.
|
|
259
|
+
result.data.assign(static_cast<size_t>(snapshot.width) * snapshot.height * 4,
|
|
260
|
+
0);
|
|
261
|
+
CGContextRef context =
|
|
262
|
+
createBgraContext(result.data.data(), snapshot.width, snapshot.height,
|
|
263
|
+
static_cast<size_t>(snapshot.width) * 4);
|
|
166
264
|
if (context == NULL) {
|
|
167
|
-
CGImageRelease(cgImage);
|
|
168
265
|
throw std::runtime_error(
|
|
169
266
|
"drawElementImageToTexture: could not allocate the pixel buffer");
|
|
170
267
|
}
|
|
171
268
|
#if !TARGET_OS_OSX
|
|
172
|
-
//
|
|
173
|
-
|
|
269
|
+
// UIKit draws straight into the vector; no intermediate image.
|
|
270
|
+
try {
|
|
271
|
+
drawViewHierarchy(snapshot, context, afterScreenUpdates);
|
|
272
|
+
} catch (...) {
|
|
273
|
+
CGContextRelease(context);
|
|
274
|
+
throw;
|
|
275
|
+
}
|
|
174
276
|
#else
|
|
277
|
+
(void)afterScreenUpdates;
|
|
278
|
+
CGImageRef cgImage = NULL;
|
|
279
|
+
NSBitmapImageRep *rep =
|
|
280
|
+
[snapshot.view bitmapImageRepForCachingDisplayInRect:snapshot.bounds];
|
|
281
|
+
if (rep != nil) {
|
|
282
|
+
[snapshot.view cacheDisplayInRect:snapshot.bounds toBitmapImageRep:rep];
|
|
283
|
+
cgImage = rep.CGImage;
|
|
284
|
+
if (cgImage != NULL) {
|
|
285
|
+
CGImageRetain(cgImage);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (cgImage == NULL) {
|
|
289
|
+
CGContextRelease(context);
|
|
290
|
+
throw std::runtime_error(
|
|
291
|
+
"drawElementImageToTexture: the view could not be rendered");
|
|
292
|
+
}
|
|
175
293
|
// The cached display covers the whole view at the backing scale; draw it so
|
|
176
294
|
// that the source rectangle fills the output. CoreGraphics' origin is the
|
|
177
295
|
// bottom-left corner, hence the flipped vertical offset.
|
|
178
|
-
const CGRect imageRect =
|
|
179
|
-
-sourceX * scaleX,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
296
|
+
const CGRect imageRect =
|
|
297
|
+
CGRectMake(-snapshot.sourceX * snapshot.scaleX,
|
|
298
|
+
-(snapshot.bounds.size.height - snapshot.sourceY -
|
|
299
|
+
snapshot.sourceHeight) *
|
|
300
|
+
snapshot.scaleY,
|
|
301
|
+
snapshot.bounds.size.width * snapshot.scaleX,
|
|
302
|
+
snapshot.bounds.size.height * snapshot.scaleY);
|
|
183
303
|
CGContextSetBlendMode(context, kCGBlendModeCopy);
|
|
184
304
|
CGContextDrawImage(context, imageRect, cgImage);
|
|
185
|
-
CGContextRelease(context);
|
|
186
305
|
CGImageRelease(cgImage);
|
|
306
|
+
#endif
|
|
307
|
+
CGContextRelease(context);
|
|
187
308
|
return result;
|
|
188
309
|
}
|
|
189
310
|
|
|
190
311
|
} // namespace
|
|
191
312
|
|
|
313
|
+
// On iOS a caller that can draw a GPU frame (allowFrame) gets the view
|
|
314
|
+
// rendered into an IOSurface; everything else, and macOS, gets pixels.
|
|
192
315
|
void ApplePlatformContext::snapshotView(
|
|
193
|
-
const ViewSnapshotRequest &request,
|
|
194
|
-
std::function<void(
|
|
316
|
+
const ViewSnapshotRequest &request, std::function<void(ImageData)> onPixels,
|
|
317
|
+
std::function<void(VideoFrameHandle)> onFrame,
|
|
195
318
|
std::function<void(std::string)> onError) {
|
|
196
319
|
// Copies: the block outlives this call.
|
|
197
320
|
ViewLookup lookup = _viewLookup;
|
|
@@ -199,7 +322,29 @@ void ApplePlatformContext::snapshotView(
|
|
|
199
322
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
200
323
|
@autoreleasepool {
|
|
201
324
|
try {
|
|
202
|
-
|
|
325
|
+
const ResolvedSnapshot snapshot =
|
|
326
|
+
resolveSnapshot(lookup, snapshotRequest);
|
|
327
|
+
#if !TARGET_OS_OSX
|
|
328
|
+
if (snapshotRequest.allowFrame) {
|
|
329
|
+
try {
|
|
330
|
+
onFrame(renderViewIntoSurface(snapshot,
|
|
331
|
+
snapshotRequest.afterScreenUpdates));
|
|
332
|
+
return;
|
|
333
|
+
} catch (const ViewNotRenderable &) {
|
|
334
|
+
throw;
|
|
335
|
+
} catch (const std::exception &error) {
|
|
336
|
+
// An IOSurface could not be set up; the pixel path draws the
|
|
337
|
+
// same view.
|
|
338
|
+
NSLog(@"[WebGPU] drawElementImageToTexture: %s; reading the "
|
|
339
|
+
@"pixels back instead",
|
|
340
|
+
error.what());
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
#else
|
|
344
|
+
(void)onFrame;
|
|
345
|
+
#endif
|
|
346
|
+
onPixels(
|
|
347
|
+
renderViewIntoPixels(snapshot, snapshotRequest.afterScreenUpdates));
|
|
203
348
|
} catch (const std::exception &error) {
|
|
204
349
|
onError(error.what());
|
|
205
350
|
} catch (...) {
|
|
@@ -322,12 +467,10 @@ void ApplePlatformContext::createImageBitmapFromDataAsync(
|
|
|
322
467
|
});
|
|
323
468
|
}
|
|
324
469
|
|
|
325
|
-
VideoFrameHandle
|
|
326
|
-
ApplePlatformContext::loadVideoFrame(const std::string &path) {
|
|
470
|
+
VideoFrameHandle ApplePlatformContext::loadVideoFrame(const std::string &path) {
|
|
327
471
|
NSString *nsPath = [NSString stringWithUTF8String:path.c_str()];
|
|
328
|
-
NSURL *url = [nsPath hasPrefix:@"file://"]
|
|
329
|
-
|
|
330
|
-
: [NSURL fileURLWithPath:nsPath];
|
|
472
|
+
NSURL *url = [nsPath hasPrefix:@"file://"] ? [NSURL URLWithString:nsPath]
|
|
473
|
+
: [NSURL fileURLWithPath:nsPath];
|
|
331
474
|
AVURLAsset *asset = [AVURLAsset assetWithURL:url];
|
|
332
475
|
|
|
333
476
|
NSArray<AVAssetTrack *> *videoTracks =
|
|
@@ -347,8 +490,7 @@ ApplePlatformContext::loadVideoFrame(const std::string &path) {
|
|
|
347
490
|
}
|
|
348
491
|
|
|
349
492
|
NSDictionary *outputSettings = @{
|
|
350
|
-
(NSString *)kCVPixelBufferPixelFormatTypeKey :
|
|
351
|
-
@(kCVPixelFormatType_32BGRA),
|
|
493
|
+
(NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA),
|
|
352
494
|
(NSString *)kCVPixelBufferIOSurfacePropertiesKey : @{},
|
|
353
495
|
(NSString *)kCVPixelBufferMetalCompatibilityKey : @YES,
|
|
354
496
|
};
|
|
@@ -402,7 +544,7 @@ ApplePlatformContext::loadVideoFrame(const std::string &path) {
|
|
|
402
544
|
|
|
403
545
|
std::unique_ptr<IVideoPlayer>
|
|
404
546
|
ApplePlatformContext::createVideoPlayer(const std::string &path,
|
|
405
|
-
|
|
547
|
+
VideoPixelFormat format) {
|
|
406
548
|
return createAppleVideoPlayer(path, format);
|
|
407
549
|
}
|
|
408
550
|
|
|
@@ -414,8 +556,77 @@ VideoFrameHandle ApplePlatformContext::wrapNativeBuffer(void *pointer) {
|
|
|
414
556
|
return wrapCVPixelBuffer(static_cast<CVPixelBufferRef>(pointer));
|
|
415
557
|
}
|
|
416
558
|
|
|
559
|
+
// The NV12 variant of createTestVideoFrame: a biplanar BT.709 limited-range
|
|
560
|
+
// frame, red on the left half and green on the right, whose attachments
|
|
561
|
+
// declare its color space the way the decoder's frames do, so that the
|
|
562
|
+
// conversion of importExternalTexture is fully determined.
|
|
563
|
+
static VideoFrameHandle createTestYuvFrame(uint32_t width, uint32_t height) {
|
|
564
|
+
NSDictionary *attrs = @{
|
|
565
|
+
(NSString *)kCVPixelBufferIOSurfacePropertiesKey : @{},
|
|
566
|
+
(NSString *)kCVPixelBufferMetalCompatibilityKey : @YES,
|
|
567
|
+
};
|
|
568
|
+
CVPixelBufferRef pixelBuffer = NULL;
|
|
569
|
+
CVReturn err =
|
|
570
|
+
CVPixelBufferCreate(kCFAllocatorDefault, width, height,
|
|
571
|
+
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
|
|
572
|
+
(__bridge CFDictionaryRef)attrs, &pixelBuffer);
|
|
573
|
+
if (err != kCVReturnSuccess || !pixelBuffer) {
|
|
574
|
+
throw std::runtime_error("createTestVideoFrame: unsupported: "
|
|
575
|
+
"CVPixelBufferCreate failed for a biplanar "
|
|
576
|
+
"YUV buffer");
|
|
577
|
+
}
|
|
578
|
+
CVBufferSetAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey,
|
|
579
|
+
kCVImageBufferYCbCrMatrix_ITU_R_709_2,
|
|
580
|
+
kCVAttachmentMode_ShouldPropagate);
|
|
581
|
+
CVBufferSetAttachment(pixelBuffer, kCVImageBufferColorPrimariesKey,
|
|
582
|
+
kCVImageBufferColorPrimaries_ITU_R_709_2,
|
|
583
|
+
kCVAttachmentMode_ShouldPropagate);
|
|
584
|
+
CVBufferSetAttachment(pixelBuffer, kCVImageBufferTransferFunctionKey,
|
|
585
|
+
kCVImageBufferTransferFunction_ITU_R_709_2,
|
|
586
|
+
kCVAttachmentMode_ShouldPropagate);
|
|
587
|
+
|
|
588
|
+
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
|
|
589
|
+
uint8_t *luma = static_cast<uint8_t *>(
|
|
590
|
+
CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0));
|
|
591
|
+
size_t lumaRowBytes = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
|
|
592
|
+
for (uint32_t y = 0; y < height; ++y) {
|
|
593
|
+
uint8_t *row = luma + y * lumaRowBytes;
|
|
594
|
+
for (uint32_t x = 0; x < width; ++x) {
|
|
595
|
+
row[x] = x < width / 2 ? test_yuv_frame::kRedY : test_yuv_frame::kGreenY;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
// The chroma plane is subsampled by two on both axes and interleaves Cb, Cr.
|
|
599
|
+
uint8_t *chroma = static_cast<uint8_t *>(
|
|
600
|
+
CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1));
|
|
601
|
+
size_t chromaRowBytes = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
|
|
602
|
+
size_t chromaWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
|
|
603
|
+
size_t chromaHeight = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
|
|
604
|
+
for (size_t y = 0; y < chromaHeight; ++y) {
|
|
605
|
+
uint8_t *row = chroma + y * chromaRowBytes;
|
|
606
|
+
for (size_t x = 0; x < chromaWidth; ++x) {
|
|
607
|
+
bool red = x < chromaWidth / 2;
|
|
608
|
+
row[x * 2 + 0] = red ? test_yuv_frame::kRedCb : test_yuv_frame::kGreenCb;
|
|
609
|
+
row[x * 2 + 1] = red ? test_yuv_frame::kRedCr : test_yuv_frame::kGreenCr;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
|
|
613
|
+
|
|
614
|
+
try {
|
|
615
|
+
auto handle = wrapCVPixelBuffer(pixelBuffer);
|
|
616
|
+
CFRelease(pixelBuffer); // wrapCVPixelBuffer retained its own reference
|
|
617
|
+
return handle;
|
|
618
|
+
} catch (...) {
|
|
619
|
+
CFRelease(pixelBuffer);
|
|
620
|
+
throw;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
417
624
|
VideoFrameHandle
|
|
418
|
-
ApplePlatformContext::createTestVideoFrame(uint32_t width, uint32_t height
|
|
625
|
+
ApplePlatformContext::createTestVideoFrame(uint32_t width, uint32_t height,
|
|
626
|
+
VideoPixelFormat format) {
|
|
627
|
+
if (format == VideoPixelFormat::NV12) {
|
|
628
|
+
return createTestYuvFrame(width, height);
|
|
629
|
+
}
|
|
419
630
|
NSDictionary *attrs = @{
|
|
420
631
|
(NSString *)kCVPixelBufferIOSurfacePropertiesKey : @{},
|
|
421
632
|
(NSString *)kCVPixelBufferMetalCompatibilityKey : @YES,
|
|
@@ -12,47 +12,80 @@ namespace rnwgpu {
|
|
|
12
12
|
|
|
13
13
|
namespace {
|
|
14
14
|
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
1.164383f, 2.112402f, 0.000000f, -1.133402f, //
|
|
25
|
-
};
|
|
26
|
-
static constexpr float kBT601LimitedToRgb[12] = {
|
|
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, //
|
|
30
|
-
};
|
|
31
|
-
static constexpr float kBT2020LimitedToRgb[12] = {
|
|
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, //
|
|
35
|
-
};
|
|
15
|
+
// The string attachment of a pixel buffer, or null when absent or not a string.
|
|
16
|
+
static CFStringRef stringAttachment(CVPixelBufferRef pixelBuffer,
|
|
17
|
+
CFStringRef key) {
|
|
18
|
+
CFTypeRef value = CVBufferGetAttachment(pixelBuffer, key, nullptr);
|
|
19
|
+
if (value == nullptr || CFGetTypeID(value) != CFStringGetTypeID()) {
|
|
20
|
+
return nullptr;
|
|
21
|
+
}
|
|
22
|
+
return static_cast<CFStringRef>(value);
|
|
23
|
+
}
|
|
36
24
|
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
25
|
+
// The color space of a YUV pixel buffer, from its attachments: the YCbCr matrix
|
|
26
|
+
// (kCVImageBufferYCbCrMatrixKey), the range (the full-range pixel formats),
|
|
27
|
+
// the primaries and the transfer function. What the buffer does not declare
|
|
28
|
+
// stays unset and resolves at import time to BT.709 limited range, the norm
|
|
29
|
+
// for HD video (GPUExternalTexture.cpp).
|
|
30
|
+
static VideoFrameColorSpace
|
|
31
|
+
colorSpaceFromCVPixelBuffer(CVPixelBufferRef pixelBuffer) {
|
|
32
|
+
VideoFrameColorSpace colorSpace;
|
|
33
|
+
if (CFStringRef matrix =
|
|
34
|
+
stringAttachment(pixelBuffer, kCVImageBufferYCbCrMatrixKey)) {
|
|
46
35
|
if (CFEqual(matrix, kCVImageBufferYCbCrMatrix_ITU_R_601_4) ||
|
|
47
36
|
CFEqual(matrix, kCVImageBufferYCbCrMatrix_SMPTE_240M_1995)) {
|
|
48
|
-
|
|
37
|
+
colorSpace.matrix = wgpu::ColorSpaceYCbCrMatrixDawn::Rec601;
|
|
38
|
+
} else if (CFEqual(matrix, kCVImageBufferYCbCrMatrix_ITU_R_709_2)) {
|
|
39
|
+
colorSpace.matrix = wgpu::ColorSpaceYCbCrMatrixDawn::Rec709;
|
|
49
40
|
} else if (CFEqual(matrix, kCVImageBufferYCbCrMatrix_ITU_R_2020)) {
|
|
50
|
-
|
|
41
|
+
colorSpace.matrix = wgpu::ColorSpaceYCbCrMatrixDawn::Rec2020;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
switch (CVPixelBufferGetPixelFormatType(pixelBuffer)) {
|
|
45
|
+
case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange:
|
|
46
|
+
colorSpace.range = wgpu::ColorSpaceYCbCrRangeDawn::Full;
|
|
47
|
+
break;
|
|
48
|
+
case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
|
|
49
|
+
colorSpace.range = wgpu::ColorSpaceYCbCrRangeDawn::Narrow;
|
|
50
|
+
break;
|
|
51
|
+
default:
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
if (CFStringRef primaries =
|
|
55
|
+
stringAttachment(pixelBuffer, kCVImageBufferColorPrimariesKey)) {
|
|
56
|
+
if (CFEqual(primaries, kCVImageBufferColorPrimaries_ITU_R_709_2)) {
|
|
57
|
+
// BT.709 shares the sRGB primaries (Dawn's helper only knows them under
|
|
58
|
+
// that name).
|
|
59
|
+
colorSpace.primaries = wgpu::ColorSpacePrimariesDawn::SRGB;
|
|
60
|
+
} else if (CFEqual(primaries, kCVImageBufferColorPrimaries_EBU_3213) ||
|
|
61
|
+
CFEqual(primaries, kCVImageBufferColorPrimaries_SMPTE_C)) {
|
|
62
|
+
colorSpace.primaries = wgpu::ColorSpacePrimariesDawn::Rec601;
|
|
63
|
+
} else if (CFEqual(primaries, kCVImageBufferColorPrimaries_ITU_R_2020)) {
|
|
64
|
+
colorSpace.primaries = wgpu::ColorSpacePrimariesDawn::Rec2020;
|
|
65
|
+
} else if (CFEqual(primaries, kCVImageBufferColorPrimaries_P3_D65)) {
|
|
66
|
+
colorSpace.primaries = wgpu::ColorSpacePrimariesDawn::DisplayP3;
|
|
51
67
|
}
|
|
52
68
|
}
|
|
53
|
-
|
|
54
|
-
|
|
69
|
+
if (CFStringRef transfer =
|
|
70
|
+
stringAttachment(pixelBuffer, kCVImageBufferTransferFunctionKey)) {
|
|
71
|
+
if (CFEqual(transfer, kCVImageBufferTransferFunction_ITU_R_709_2) ||
|
|
72
|
+
CFEqual(transfer, kCVImageBufferTransferFunction_SMPTE_240M_1995) ||
|
|
73
|
+
CFEqual(transfer, kCVImageBufferTransferFunction_ITU_R_2020)) {
|
|
74
|
+
// The SDR video OETF; SMPTE 170M is Dawn's name for its inverse.
|
|
75
|
+
colorSpace.transfer = wgpu::ColorSpaceTransferDawn::SMPTE_170M;
|
|
76
|
+
} else if (CFEqual(transfer, kCVImageBufferTransferFunction_sRGB)) {
|
|
77
|
+
colorSpace.transfer = wgpu::ColorSpaceTransferDawn::SRGB;
|
|
78
|
+
} else if (CFEqual(transfer, kCVImageBufferTransferFunction_Linear)) {
|
|
79
|
+
colorSpace.transfer = wgpu::ColorSpaceTransferDawn::Identity;
|
|
80
|
+
} else if (CFEqual(transfer,
|
|
81
|
+
kCVImageBufferTransferFunction_SMPTE_ST_2084_PQ)) {
|
|
82
|
+
colorSpace.transfer = wgpu::ColorSpaceTransferDawn::PQ;
|
|
83
|
+
} else if (CFEqual(transfer,
|
|
84
|
+
kCVImageBufferTransferFunction_ITU_R_2100_HLG)) {
|
|
85
|
+
colorSpace.transfer = wgpu::ColorSpaceTransferDawn::HLG;
|
|
86
|
+
}
|
|
55
87
|
}
|
|
88
|
+
return colorSpace;
|
|
56
89
|
}
|
|
57
90
|
|
|
58
91
|
// Map a CVPixelBuffer's pixel format to our VideoPixelFormat enum.
|
|
@@ -257,7 +290,7 @@ VideoFrameHandle wrapCVPixelBuffer(CVPixelBufferRef pixelBuffer) {
|
|
|
257
290
|
handle.height = static_cast<uint32_t>(CVPixelBufferGetHeight(pixelBuffer));
|
|
258
291
|
handle.pixelFormat = pixelFormatFromCVPixelBuffer(pixelBuffer);
|
|
259
292
|
if (handle.pixelFormat == VideoPixelFormat::NV12) {
|
|
260
|
-
|
|
293
|
+
handle.colorSpace = colorSpaceFromCVPixelBuffer(pixelBuffer);
|
|
261
294
|
}
|
|
262
295
|
handle.deleter = [pixelBuffer]() { CFRelease(pixelBuffer); };
|
|
263
296
|
return handle;
|