react-native-webgpu 0.14.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/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 +75 -3
- package/apple/AppleVideoPlayer.mm +68 -35
- package/cpp/dawn/common/ExternalTextureParams.h +67 -0
- package/cpp/rnwgpu/PlatformContext.h +65 -16
- package/cpp/rnwgpu/SurfaceRegistry.h +39 -5
- 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 +98 -3
- package/cpp/rnwgpu/api/RNWebGPU.h +12 -3
- package/cpp/rnwgpu/api/VideoFrameBlit.h +39 -24
- package/cpp/rnwgpu/api/descriptors/GPUExternalTextureDescriptor.h +10 -3
- package/lib/commonjs/Canvas.js +36 -6
- 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 +37 -7
- 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/package.json +1 -1
- package/src/Canvas.tsx +48 -29
- 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
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
#include <array>
|
|
4
4
|
#include <cmath>
|
|
5
5
|
#include <memory>
|
|
6
|
+
#include <optional>
|
|
6
7
|
#include <string>
|
|
7
8
|
#include <utility>
|
|
8
9
|
|
|
9
10
|
#include "GPUExternalTextureDescriptor.h"
|
|
10
11
|
|
|
12
|
+
#include "dawn/common/ExternalTextureParams.h"
|
|
13
|
+
|
|
11
14
|
#if defined(__ANDROID__)
|
|
12
15
|
#include <android/hardware_buffer.h>
|
|
16
|
+
#include <android/log.h>
|
|
17
|
+
#include <poll.h>
|
|
13
18
|
#endif
|
|
14
19
|
|
|
15
20
|
namespace rnwgpu {
|
|
@@ -49,117 +54,80 @@ static bool isOpaqueYCbCrFormat(uint32_t format) {
|
|
|
49
54
|
}
|
|
50
55
|
#endif
|
|
51
56
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Identity transfer (y = x). Used when the sampled surface is already in the
|
|
84
|
-
// render target's color space: a single-plane BGRA IOSurface, or the Android
|
|
85
|
-
// opaque-YCbCr path where the Vulkan sampler already produced RGB. Dawn
|
|
86
|
-
// dereferences the transfer-function arrays unconditionally
|
|
87
|
-
// (ComputeExternalTextureParams), so these must be non-null even when no
|
|
88
|
-
// conversion is wanted.
|
|
89
|
-
static const float kIdentityTransferParams[7] = {
|
|
90
|
-
1.0f, // G
|
|
91
|
-
1.0f, // A
|
|
92
|
-
0.0f, // B
|
|
93
|
-
0.0f, // C
|
|
94
|
-
0.0f, // D
|
|
95
|
-
0.0f, // E
|
|
96
|
-
0.0f, // F
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// Identity passthrough for GPUExternalTexture.yuvToRgbMatrix: the sampled
|
|
100
|
-
// texel is already RGB (Apple's biplanar path where Dawn converts in the
|
|
101
|
-
// sampler transform, RGBA surfaces, or an Android AHB whose driver reports
|
|
102
|
-
// RGB_IDENTITY). A 3x4 row-major matrix mapping [c.r, c.g, c.b, 1] to itself.
|
|
103
|
-
static constexpr std::array<float, 12> kYuvPassthroughMatrix = {
|
|
104
|
-
1.0f, 0.0f, 0.0f, 0.0f, //
|
|
105
|
-
0.0f, 1.0f, 0.0f, 0.0f, //
|
|
106
|
-
0.0f, 0.0f, 1.0f, 0.0f, //
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
// Build a 3x4 row-major matrix mapping the *sampled* gamma-encoded
|
|
110
|
-
// [Y, Cb, Cr, 1] (each normalized to [0,1]) to gamma-encoded R'G'B', from the
|
|
111
|
-
// Vulkan-suggested YCbCr model + range of an Android external-format buffer.
|
|
112
|
-
// Needed because Dawn's opaque-YCbCr sampling path hard-codes an RGB_IDENTITY
|
|
113
|
-
// conversion (SamplerVk.cpp::GetYCbCrForTextureView, crbug.com/497675620), so
|
|
114
|
-
// the shader receives raw Y/Cb/Cr and must convert itself.
|
|
115
|
-
// The VkSamplerYcbcrModelConversion / VkSamplerYcbcrRange values are inlined
|
|
116
|
-
// to avoid a Vulkan header dependency in this cross-platform file.
|
|
117
|
-
[[maybe_unused]] static std::array<float, 12>
|
|
118
|
-
makeYuvToRgbMatrix(uint32_t vkModel, uint32_t vkRange) {
|
|
119
|
-
constexpr uint32_t kModelRgbIdentity = 0; // VK_..._RGB_IDENTITY
|
|
120
|
-
constexpr uint32_t kModel709 = 2; // VK_..._YCBCR_709
|
|
121
|
-
constexpr uint32_t kModel601 = 3; // VK_..._YCBCR_601
|
|
122
|
-
constexpr uint32_t kModel2020 = 4; // VK_..._YCBCR_2020
|
|
123
|
-
constexpr uint32_t kRangeItuNarrow = 1; // VK_SAMPLER_YCBCR_RANGE_ITU_NARROW
|
|
124
|
-
|
|
125
|
-
if (vkModel == kModelRgbIdentity) {
|
|
126
|
-
// The buffer content is RGB; nothing to convert.
|
|
127
|
-
return kYuvPassthroughMatrix;
|
|
57
|
+
// The color space of a YUV frame with every member known. The producer reports
|
|
58
|
+
// what it knows (VideoFrameHandle.colorSpace); the matrix and range it leaves
|
|
59
|
+
// out come from `fallbackMatrix` / `fallbackRange`, the platform's best guess
|
|
60
|
+
// for the buffer, and the rest follows from the matrix: the primaries of the
|
|
61
|
+
// standard that defines it, and the SMPTE 170M transfer function that BT.601,
|
|
62
|
+
// BT.709 and BT.2020 SDR video is encoded with. An identity matrix means the
|
|
63
|
+
// samples are RGB already, which the identity transfer leaves untouched.
|
|
64
|
+
static wgpu::ColorSpaceDawn
|
|
65
|
+
resolveColorSpace(const VideoFrameColorSpace &reported,
|
|
66
|
+
wgpu::ColorSpaceYCbCrMatrixDawn fallbackMatrix,
|
|
67
|
+
wgpu::ColorSpaceYCbCrRangeDawn fallbackRange) {
|
|
68
|
+
wgpu::ColorSpaceDawn colorSpace{};
|
|
69
|
+
colorSpace.yCbCrMatrix = reported.matrix.value_or(fallbackMatrix);
|
|
70
|
+
colorSpace.yCbCrRange = reported.range.value_or(fallbackRange);
|
|
71
|
+
if (reported.primaries.has_value()) {
|
|
72
|
+
colorSpace.primaries = *reported.primaries;
|
|
73
|
+
} else {
|
|
74
|
+
switch (colorSpace.yCbCrMatrix) {
|
|
75
|
+
case wgpu::ColorSpaceYCbCrMatrixDawn::Rec601:
|
|
76
|
+
colorSpace.primaries = wgpu::ColorSpacePrimariesDawn::Rec601;
|
|
77
|
+
break;
|
|
78
|
+
case wgpu::ColorSpaceYCbCrMatrixDawn::Rec2020:
|
|
79
|
+
colorSpace.primaries = wgpu::ColorSpacePrimariesDawn::Rec2020;
|
|
80
|
+
break;
|
|
81
|
+
default:
|
|
82
|
+
// BT.709 shares the sRGB primaries (Dawn's helper only knows them under
|
|
83
|
+
// that name).
|
|
84
|
+
colorSpace.primaries = wgpu::ColorSpacePrimariesDawn::SRGB;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
128
87
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
case kModel2020:
|
|
137
|
-
kr = 0.2627f;
|
|
138
|
-
kb = 0.0593f;
|
|
139
|
-
break;
|
|
140
|
-
case kModel601:
|
|
141
|
-
default:
|
|
142
|
-
// YCBCR_IDENTITY and unknown models: Android camera streams are BT.601 in
|
|
143
|
-
// practice, so that is the sane default.
|
|
144
|
-
kr = 0.299f;
|
|
145
|
-
kb = 0.114f;
|
|
146
|
-
break;
|
|
88
|
+
if (reported.transfer.has_value()) {
|
|
89
|
+
colorSpace.transfer = *reported.transfer;
|
|
90
|
+
} else {
|
|
91
|
+
colorSpace.transfer =
|
|
92
|
+
colorSpace.yCbCrMatrix == wgpu::ColorSpaceYCbCrMatrixDawn::Identity
|
|
93
|
+
? wgpu::ColorSpaceTransferDawn::Identity
|
|
94
|
+
: wgpu::ColorSpaceTransferDawn::SMPTE_170M;
|
|
147
95
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
96
|
+
return colorSpace;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// The ExternalTextureDescriptor parameters that convert samples in `source` to
|
|
100
|
+
// RGB in the color space the descriptor asks for ("srgb" by default,
|
|
101
|
+
// "display-p3"): the YCbCr matrix, the range, the source transfer function,
|
|
102
|
+
// the gamut conversion and the destination transfer function. Dawn applies
|
|
103
|
+
// them while sampling the external texture. The helper is Dawn's own, the one
|
|
104
|
+
// Chromium feeds video frames through.
|
|
105
|
+
static dawn::ExternalTextureColorSpaceParams
|
|
106
|
+
colorSpaceParams(const wgpu::ColorSpaceDawn &source,
|
|
107
|
+
const std::optional<std::string> &destination) {
|
|
108
|
+
auto target = destination.has_value() && *destination == "display-p3"
|
|
109
|
+
? wgpu::PredefinedColorSpace::DisplayP3
|
|
110
|
+
: wgpu::PredefinedColorSpace::SRGB;
|
|
111
|
+
dawn::ExternalTextureColorSpaceParams params;
|
|
112
|
+
if (dawn::ComputeExternalTextureParams(source, target, ¶ms) !=
|
|
113
|
+
wgpu::Status::Success) {
|
|
114
|
+
throw std::runtime_error(
|
|
115
|
+
"GPUExternalTexture::Create(): the color space of the frame cannot be "
|
|
116
|
+
"converted to '" +
|
|
117
|
+
destination.value_or("srgb") + "'");
|
|
118
|
+
}
|
|
119
|
+
return params;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Dawn dereferences the four arrays unconditionally, so a descriptor always
|
|
123
|
+
// carries a full set, the no-op one for frames that are RGB already.
|
|
124
|
+
static void
|
|
125
|
+
applyColorSpaceParams(wgpu::ExternalTextureDescriptor &descriptor,
|
|
126
|
+
const dawn::ExternalTextureColorSpaceParams ¶ms) {
|
|
127
|
+
descriptor.yuvToRgbConversionMatrix = params.yuvToRgbConversionMatrix.data();
|
|
128
|
+
descriptor.srcTransferFunctionParameters = params.srcTransferFunction.data();
|
|
129
|
+
descriptor.dstTransferFunctionParameters = params.dstTransferFunction.data();
|
|
130
|
+
descriptor.gamutConversionMatrix = params.gamutConversionMatrix.data();
|
|
163
131
|
}
|
|
164
132
|
|
|
165
133
|
// Map a rotation in degrees (0 / 90 / 180 / 270) to Dawn's enum. Anything that
|
|
@@ -250,26 +218,27 @@ std::shared_ptr<GPUExternalTexture> GPUExternalTexture::Create(
|
|
|
250
218
|
plane1 = texture.CreateView(&v);
|
|
251
219
|
}
|
|
252
220
|
|
|
253
|
-
// 5. Build the ExternalTextureDescriptor.
|
|
254
|
-
//
|
|
255
|
-
//
|
|
221
|
+
// 5. Build the ExternalTextureDescriptor. Dawn converts the YUV planes to
|
|
222
|
+
// RGB in the destination color space while sampling, with the matrix,
|
|
223
|
+
// range, transfer function and gamut of the frame's color space (from the
|
|
224
|
+
// CVPixelBuffer attachments; BT.709 limited range, the norm for HD video,
|
|
225
|
+
// where they are missing). A BGRA frame is RGB already: no-op parameters.
|
|
226
|
+
dawn::ExternalTextureColorSpaceParams params =
|
|
227
|
+
isYuv ? colorSpaceParams(
|
|
228
|
+
resolveColorSpace(frame.colorSpace,
|
|
229
|
+
wgpu::ColorSpaceYCbCrMatrixDawn::Rec709,
|
|
230
|
+
wgpu::ColorSpaceYCbCrRangeDawn::Narrow),
|
|
231
|
+
descriptor->colorSpace)
|
|
232
|
+
: dawn::GetNoopColorSpaceParams();
|
|
256
233
|
wgpu::ExternalTextureDescriptor extDesc{};
|
|
257
234
|
if (!label.empty()) {
|
|
258
235
|
extDesc.label = wgpu::StringView(label.c_str(), label.size());
|
|
259
236
|
}
|
|
260
237
|
extDesc.plane0 = plane0;
|
|
261
|
-
extDesc.gamutConversionMatrix = kIdentityGamutMatrix;
|
|
262
238
|
if (isYuv) {
|
|
263
239
|
extDesc.plane1 = plane1;
|
|
264
|
-
extDesc.yuvToRgbConversionMatrix = frame.yuvToRgbMatrix;
|
|
265
|
-
extDesc.srcTransferFunctionParameters = kSrgbDecodeParams;
|
|
266
|
-
extDesc.dstTransferFunctionParameters = kSrgbEncodeParams;
|
|
267
|
-
} else {
|
|
268
|
-
// BGRA is already RGB in the target color space; pass it through. Dawn
|
|
269
|
-
// dereferences these arrays unconditionally, so they must be non-null.
|
|
270
|
-
extDesc.srcTransferFunctionParameters = kIdentityTransferParams;
|
|
271
|
-
extDesc.dstTransferFunctionParameters = kIdentityTransferParams;
|
|
272
240
|
}
|
|
241
|
+
applyColorSpaceParams(extDesc, params);
|
|
273
242
|
extDesc.cropOrigin = {0, 0};
|
|
274
243
|
extDesc.cropSize = {frame.width, frame.height};
|
|
275
244
|
extDesc.apparentSize = {frame.width, frame.height};
|
|
@@ -287,11 +256,7 @@ std::shared_ptr<GPUExternalTexture> GPUExternalTexture::Create(
|
|
|
287
256
|
|
|
288
257
|
return std::make_shared<GPUExternalTexture>(
|
|
289
258
|
std::move(external), std::move(memory), std::move(texture),
|
|
290
|
-
std::move(descriptor->source), std::move(label)
|
|
291
|
-
// Dawn's Metal path applies the YUV->RGB conversion (from
|
|
292
|
-
// frame.yuvToRgbMatrix above) inside the sampling transform, so the
|
|
293
|
-
// sampled texel is already RGB.
|
|
294
|
-
kYuvPassthroughMatrix);
|
|
259
|
+
std::move(descriptor->source), std::move(label));
|
|
295
260
|
#elif defined(__ANDROID__)
|
|
296
261
|
// 0. A YUV frame imports as an OpaqueYCbCrAndroid texture, which Dawn only
|
|
297
262
|
// accepts on a device with OpaqueYCbCrAndroidForExternalTexture.
|
|
@@ -344,49 +309,103 @@ std::shared_ptr<GPUExternalTexture> GPUExternalTexture::Create(
|
|
|
344
309
|
// 3. Begin access. Vulkan requires us to advertise the incoming VkImage
|
|
345
310
|
// layout (UNDEFINED is fine for the first acquisition of an AHB whose
|
|
346
311
|
// contents we expect Dawn to read as-is).
|
|
312
|
+
//
|
|
313
|
+
// The producer of the frame (the video decoder, the camera) may still be
|
|
314
|
+
// writing it: ImageReader hands a buffer out as soon as it is queued, and
|
|
315
|
+
// only waits on the producer's fence for CPU access. The fence travels
|
|
316
|
+
// with the frame as a sync fd; imported as a shared fence, BeginAccess
|
|
317
|
+
// makes the GPU wait on it before any work that samples the texture.
|
|
318
|
+
// Dawn duplicates the descriptor on import; the frame keeps its own.
|
|
347
319
|
wgpu::SharedTextureMemoryBeginAccessDescriptor begin{};
|
|
348
320
|
begin.initialized = true;
|
|
349
321
|
begin.concurrentRead = false;
|
|
350
322
|
wgpu::SharedTextureMemoryVkImageLayoutBeginState beginLayout{};
|
|
351
323
|
begin.nextInChain = &beginLayout;
|
|
324
|
+
wgpu::SharedFence acquireFence;
|
|
325
|
+
const uint64_t kBinaryFenceSignaled = 1;
|
|
326
|
+
if (frame.acquireFenceFd >= 0) {
|
|
327
|
+
if (device.HasFeature(wgpu::FeatureName::SharedFenceSyncFD)) {
|
|
328
|
+
wgpu::SharedFenceSyncFDDescriptor fdDesc{};
|
|
329
|
+
fdDesc.handle = frame.acquireFenceFd;
|
|
330
|
+
wgpu::SharedFenceDescriptor fenceDesc{};
|
|
331
|
+
fenceDesc.nextInChain = &fdDesc;
|
|
332
|
+
acquireFence = device.ImportSharedFence(&fenceDesc);
|
|
333
|
+
if (acquireFence) {
|
|
334
|
+
begin.fenceCount = 1;
|
|
335
|
+
begin.fences = &acquireFence;
|
|
336
|
+
begin.signaledValueCount = 1;
|
|
337
|
+
begin.signaledValues = &kBinaryFenceSignaled;
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
// A device without shared-fence-sync-fd (imported from another library
|
|
341
|
+
// that did not request it) cannot wait on the GPU: wait here instead. A
|
|
342
|
+
// sync fd becomes readable once it signals.
|
|
343
|
+
pollfd pfd{};
|
|
344
|
+
pfd.fd = frame.acquireFenceFd;
|
|
345
|
+
pfd.events = POLLIN;
|
|
346
|
+
int polled = poll(&pfd, 1, 3000);
|
|
347
|
+
if (polled <= 0) {
|
|
348
|
+
__android_log_print(ANDROID_LOG_WARN, "GPUExternalTexture",
|
|
349
|
+
"the acquire fence of the frame did not signal "
|
|
350
|
+
"within 3s; sampling it anyway");
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
352
354
|
if (memory.BeginAccess(texture, &begin) != wgpu::Status::Success) {
|
|
353
355
|
throw std::runtime_error(
|
|
354
356
|
"GPUExternalTexture::Create(): BeginAccess failed");
|
|
355
357
|
}
|
|
356
358
|
|
|
357
|
-
// 4.
|
|
358
|
-
//
|
|
359
|
-
// (
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
//
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
|
|
359
|
+
// 4. The color conversion. Dawn imports every YUV AHardwareBuffer as an
|
|
360
|
+
// OpaqueYCbCrAndroid texture and samples it through a Vulkan YCbCr
|
|
361
|
+
// sampler whose own conversion is the identity (SamplerVk.cpp), so the
|
|
362
|
+
// sample is raw Y, Cb, Cr; the external texture then applies the
|
|
363
|
+
// descriptor's YCbCr matrix, transfer functions and gamut conversion
|
|
364
|
+
// while sampling (Dawn's YUV AHB specialization), the same way it does
|
|
365
|
+
// for the biplanar textures of the other backends. The frame's color
|
|
366
|
+
// space comes from its producer (Image.getDataSpace on API 33, the
|
|
367
|
+
// container's color info before that); what it leaves unknown falls back
|
|
368
|
+
// to the YCbCr model and range the driver suggests for the buffer, read
|
|
369
|
+
// from the shared memory's properties, and to BT.601 limited range, the
|
|
370
|
+
// camera norm, when the driver has no opinion. A color-format AHB (RGBA8,
|
|
371
|
+
// R8, ...) samples as RGB directly: no-op parameters.
|
|
372
|
+
dawn::ExternalTextureColorSpaceParams params =
|
|
373
|
+
dawn::GetNoopColorSpaceParams();
|
|
369
374
|
if (texture.GetFormat() == wgpu::TextureFormat::OpaqueYCbCrAndroid) {
|
|
375
|
+
auto fallbackMatrix = wgpu::ColorSpaceYCbCrMatrixDawn::Rec601;
|
|
376
|
+
auto fallbackRange = wgpu::ColorSpaceYCbCrRangeDawn::Narrow;
|
|
370
377
|
wgpu::SharedTextureMemoryAHardwareBufferProperties ahbProps{};
|
|
371
378
|
wgpu::SharedTextureMemoryProperties props{};
|
|
372
379
|
props.nextInChain = &ahbProps;
|
|
373
380
|
if (memory.GetProperties(&props) == wgpu::Status::Success) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
381
|
+
// VkSamplerYcbcrModelConversion / VkSamplerYcbcrRange values, inlined
|
|
382
|
+
// to keep this cross-platform file free of Vulkan headers.
|
|
383
|
+
switch (ahbProps.yCbCrInfo.vkYCbCrModel) {
|
|
384
|
+
case 0: // VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY: RGB data
|
|
385
|
+
fallbackMatrix = wgpu::ColorSpaceYCbCrMatrixDawn::Identity;
|
|
386
|
+
fallbackRange = wgpu::ColorSpaceYCbCrRangeDawn::Identity;
|
|
387
|
+
break;
|
|
388
|
+
case 2: // VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709
|
|
389
|
+
fallbackMatrix = wgpu::ColorSpaceYCbCrMatrixDawn::Rec709;
|
|
390
|
+
break;
|
|
391
|
+
case 4: // VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020
|
|
392
|
+
fallbackMatrix = wgpu::ColorSpaceYCbCrMatrixDawn::Rec2020;
|
|
393
|
+
break;
|
|
394
|
+
default: // YCBCR_601, YCBCR_IDENTITY and unknown models
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
if (fallbackMatrix != wgpu::ColorSpaceYCbCrMatrixDawn::Identity) {
|
|
398
|
+
fallbackRange = ahbProps.yCbCrInfo.vkYCbCrRange == 0
|
|
399
|
+
? wgpu::ColorSpaceYCbCrRangeDawn::Full
|
|
400
|
+
: wgpu::ColorSpaceYCbCrRangeDawn::Narrow;
|
|
401
|
+
}
|
|
380
402
|
}
|
|
403
|
+
params = colorSpaceParams(
|
|
404
|
+
resolveColorSpace(frame.colorSpace, fallbackMatrix, fallbackRange),
|
|
405
|
+
descriptor->colorSpace);
|
|
381
406
|
}
|
|
382
407
|
|
|
383
|
-
// 5. Build the ExternalTextureDescriptor: a single (opaque or RGB) plane
|
|
384
|
-
// with identity transfer. The YUV->RGB conversion cannot run inside
|
|
385
|
-
// Dawn's external-texture transform here: the single-plane branch of the
|
|
386
|
-
// Tint transform ignores yuvToRgbConversionMatrix, which is why the
|
|
387
|
-
// matrix is surfaced to the shader instead. The gamut/transfer arrays
|
|
388
|
-
// must still be non-null: ComputeExternalTextureParams dereferences them
|
|
389
|
-
// unconditionally.
|
|
408
|
+
// 5. Build the ExternalTextureDescriptor: a single (opaque or RGB) plane.
|
|
390
409
|
wgpu::ExternalTextureDescriptor extDesc{};
|
|
391
410
|
if (!label.empty()) {
|
|
392
411
|
extDesc.label = wgpu::StringView(label.c_str(), label.size());
|
|
@@ -394,11 +413,9 @@ std::shared_ptr<GPUExternalTexture> GPUExternalTexture::Create(
|
|
|
394
413
|
extDesc.cropOrigin = {0, 0};
|
|
395
414
|
extDesc.cropSize = {frame.width, frame.height};
|
|
396
415
|
extDesc.apparentSize = {frame.width, frame.height};
|
|
397
|
-
extDesc.gamutConversionMatrix = kIdentityGamutMatrix;
|
|
398
416
|
wgpu::TextureView plane0 = texture.CreateView();
|
|
399
417
|
extDesc.plane0 = plane0;
|
|
400
|
-
extDesc
|
|
401
|
-
extDesc.dstTransferFunctionParameters = kIdentityTransferParams;
|
|
418
|
+
applyColorSpaceParams(extDesc, params);
|
|
402
419
|
extDesc.mirrored = descriptor->mirrored.value_or(false);
|
|
403
420
|
extDesc.rotation =
|
|
404
421
|
toExternalTextureRotation(descriptor->rotation.value_or(0));
|
|
@@ -413,8 +430,7 @@ std::shared_ptr<GPUExternalTexture> GPUExternalTexture::Create(
|
|
|
413
430
|
|
|
414
431
|
return std::make_shared<GPUExternalTexture>(
|
|
415
432
|
std::move(external), std::move(memory), std::move(texture),
|
|
416
|
-
std::move(descriptor->source), std::move(label)
|
|
417
|
-
std::move(yuvToRgbMatrix));
|
|
433
|
+
std::move(descriptor->source), std::move(label));
|
|
418
434
|
#else
|
|
419
435
|
throw std::runtime_error(
|
|
420
436
|
"GPUExternalTexture::Create(): not yet implemented on this "
|
|
@@ -38,12 +38,10 @@ public:
|
|
|
38
38
|
// the producer (e.g. AVPlayer) can reclaim the IOSurface.
|
|
39
39
|
GPUExternalTexture(wgpu::ExternalTexture instance,
|
|
40
40
|
wgpu::SharedTextureMemory memory, wgpu::Texture texture,
|
|
41
|
-
std::shared_ptr<VideoFrame> source, std::string label
|
|
42
|
-
std::array<float, 12> yuvToRgbMatrix)
|
|
41
|
+
std::shared_ptr<VideoFrame> source, std::string label)
|
|
43
42
|
: NativeObject(CLASS_NAME), _instance(std::move(instance)),
|
|
44
43
|
_memory(std::move(memory)), _texture(std::move(texture)),
|
|
45
|
-
_source(std::move(source)), _label(std::move(label))
|
|
46
|
-
_yuvToRgbMatrix(yuvToRgbMatrix) {}
|
|
44
|
+
_source(std::move(source)), _label(std::move(label)) {}
|
|
47
45
|
|
|
48
46
|
~GPUExternalTexture() override { destroy(); }
|
|
49
47
|
|
|
@@ -93,25 +91,11 @@ public:
|
|
|
93
91
|
}
|
|
94
92
|
}
|
|
95
93
|
|
|
96
|
-
// Non-spec extension. A 3x4 row-major matrix mapping the *sampled* texel
|
|
97
|
-
// [c.r, c.g, c.b, 1] to gamma-encoded R'G'B'. When Dawn's sampler already
|
|
98
|
-
// produces RGB (Apple's biplanar path, RGBA surfaces), this is the identity
|
|
99
|
-
// passthrough; on the Android opaque-YCbCr path the sample comes back as raw
|
|
100
|
-
// [Y, Cb, Cr] (Dawn hard-codes an RGB_IDENTITY Vulkan conversion, see
|
|
101
|
-
// crbug.com/497675620) and this matrix is derived from the driver's
|
|
102
|
-
// suggested YCbCr model + range for the buffer. Shaders can therefore apply
|
|
103
|
-
// it unconditionally after textureSampleBaseClampToEdge.
|
|
104
|
-
std::vector<double> getYuvToRgbMatrix() {
|
|
105
|
-
return std::vector<double>(_yuvToRgbMatrix.begin(), _yuvToRgbMatrix.end());
|
|
106
|
-
}
|
|
107
|
-
|
|
108
94
|
static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
|
|
109
95
|
installGetter(runtime, prototype, "__brand", &GPUExternalTexture::getBrand);
|
|
110
96
|
installGetterSetter(runtime, prototype, "label",
|
|
111
97
|
&GPUExternalTexture::getLabel,
|
|
112
98
|
&GPUExternalTexture::setLabel);
|
|
113
|
-
installGetter(runtime, prototype, "yuvToRgbMatrix",
|
|
114
|
-
&GPUExternalTexture::getYuvToRgbMatrix);
|
|
115
99
|
installMethod(runtime, prototype, "destroy", &GPUExternalTexture::destroy);
|
|
116
100
|
}
|
|
117
101
|
|
|
@@ -140,7 +124,6 @@ private:
|
|
|
140
124
|
wgpu::Texture _texture;
|
|
141
125
|
std::shared_ptr<VideoFrame> _source;
|
|
142
126
|
std::string _label;
|
|
143
|
-
std::array<float, 12> _yuvToRgbMatrix;
|
|
144
127
|
};
|
|
145
128
|
|
|
146
129
|
} // namespace rnwgpu
|
|
@@ -256,6 +256,26 @@ void GPUQueue::copyExternalImageToTexture(
|
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
// Releases a frame the platform handed over that is not going to be drawn.
|
|
260
|
+
static void releaseFrame(VideoFrameHandle &frame) {
|
|
261
|
+
if (frame.deleter) {
|
|
262
|
+
frame.deleter();
|
|
263
|
+
frame.deleter = nullptr;
|
|
264
|
+
}
|
|
265
|
+
frame.handle = nullptr;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// A rejected JS promise. A resolve factory cannot reject the task's promise
|
|
269
|
+
// itself; resolving it with this value makes it adopt the rejection.
|
|
270
|
+
static jsi::Value rejectedPromise(jsi::Runtime &runtime,
|
|
271
|
+
const std::string &message) {
|
|
272
|
+
auto promiseConstructor =
|
|
273
|
+
runtime.global().getPropertyAsObject(runtime, "Promise");
|
|
274
|
+
auto reject = promiseConstructor.getPropertyAsFunction(runtime, "reject");
|
|
275
|
+
jsi::JSError error(runtime, message);
|
|
276
|
+
return reject.callWithThis(runtime, promiseConstructor, error.value());
|
|
277
|
+
}
|
|
278
|
+
|
|
259
279
|
async::AsyncTaskHandle GPUQueue::drawElementImageToTexture(
|
|
260
280
|
jsi::Runtime &runtime, std::shared_ptr<GPUDrawElementImageSource> source,
|
|
261
281
|
std::shared_ptr<GPUDrawElementImageDestination> destination) {
|
|
@@ -337,6 +357,18 @@ async::AsyncTaskHandle GPUQueue::drawElementImageToTexture(
|
|
|
337
357
|
"a positive width and height");
|
|
338
358
|
}
|
|
339
359
|
|
|
360
|
+
// With RENDER_ATTACHMENT as well, a platform that renders the view into a
|
|
361
|
+
// GPU buffer (Android 13 and above, through HardwareRenderer) hands that
|
|
362
|
+
// buffer over instead of reading the pixels back, and the view is drawn into
|
|
363
|
+
// the destination on the GPU (VideoFrameBlit), which writes the color aspect
|
|
364
|
+
// of the first layer. An omitted aspect means "all" (dst.aspect itself stays
|
|
365
|
+
// at Dawn's Undefined default, which WriteTexture reads the same way).
|
|
366
|
+
request.allowFrame =
|
|
367
|
+
(texture.GetUsage() & wgpu::TextureUsage::RenderAttachment) != 0 &&
|
|
368
|
+
info->aspect.value_or(wgpu::TextureAspect::All) ==
|
|
369
|
+
wgpu::TextureAspect::All &&
|
|
370
|
+
dst.origin.z == 0;
|
|
371
|
+
|
|
340
372
|
// Destination size (pixels). Omitted means the source rectangle's natural
|
|
341
373
|
// pixel size (points times the device pixel ratio), which the platform
|
|
342
374
|
// computes; the bounds check then happens once the pixels are back.
|
|
@@ -372,13 +404,15 @@ async::AsyncTaskHandle GPUQueue::drawElementImageToTexture(
|
|
|
372
404
|
auto context =
|
|
373
405
|
async::RuntimeContext::getOrCreate(runtime, _async->instance());
|
|
374
406
|
auto queue = _instance;
|
|
407
|
+
auto device = _device;
|
|
408
|
+
auto blit = _videoFrameBlit;
|
|
375
409
|
// Keeps the destination texture alive until the write is issued.
|
|
376
410
|
auto gpuTexture = info->texture;
|
|
377
411
|
return context->postTask(
|
|
378
412
|
[platform, request, dst, mipWidth, mipHeight, destinationIsBgra,
|
|
379
|
-
destinationPremultiplied, queue,
|
|
380
|
-
|
|
381
|
-
|
|
413
|
+
destinationPremultiplied, device, queue, gpuTexture,
|
|
414
|
+
blit](const async::AsyncTaskHandle::ResolveFunction &resolve,
|
|
415
|
+
const async::AsyncTaskHandle::RejectFunction &reject) {
|
|
382
416
|
platform->snapshotView(
|
|
383
417
|
request,
|
|
384
418
|
[resolve, reject, dst, mipWidth, mipHeight, destinationIsBgra,
|
|
@@ -432,6 +466,67 @@ async::AsyncTaskHandle GPUQueue::drawElementImageToTexture(
|
|
|
432
466
|
return jsi::Value::undefined();
|
|
433
467
|
});
|
|
434
468
|
},
|
|
469
|
+
[resolve, reject, dst, mipWidth, mipHeight,
|
|
470
|
+
destinationPremultiplied, device, queue, gpuTexture,
|
|
471
|
+
blit](VideoFrameHandle frame) mutable {
|
|
472
|
+
// Arbitrary (UI) thread: validate without touching JSI. A frame
|
|
473
|
+
// that is not drawn is released here.
|
|
474
|
+
if (frame.handle == nullptr || frame.width == 0 ||
|
|
475
|
+
frame.height == 0) {
|
|
476
|
+
releaseFrame(frame);
|
|
477
|
+
reject("drawElementImageToTexture: the view produced no "
|
|
478
|
+
"pixels (is it mounted with a non-zero size?)");
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (frame.width >
|
|
482
|
+
mipWidth - std::min<uint32_t>(dst.origin.x, mipWidth) ||
|
|
483
|
+
frame.height >
|
|
484
|
+
mipHeight - std::min<uint32_t>(dst.origin.y, mipHeight)) {
|
|
485
|
+
auto message =
|
|
486
|
+
"drawElementImageToTexture: the view's natural pixel "
|
|
487
|
+
"size (" +
|
|
488
|
+
std::to_string(frame.width) + "x" +
|
|
489
|
+
std::to_string(frame.height) +
|
|
490
|
+
") does not fit in the destination texture at "
|
|
491
|
+
"destination.origin; pass destination.size to scale "
|
|
492
|
+
"it, or create a larger texture";
|
|
493
|
+
releaseFrame(frame);
|
|
494
|
+
reject(std::move(message));
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
resolve([frame = std::move(frame), dst, destinationPremultiplied,
|
|
498
|
+
device, queue, gpuTexture,
|
|
499
|
+
blit](jsi::Runtime &runtime) mutable -> jsi::Value {
|
|
500
|
+
// Owning runtime's thread. Draw the buffer into the
|
|
501
|
+
// destination region on the GPU: importing it as an external
|
|
502
|
+
// texture makes the GPU wait on the renderer's fence first,
|
|
503
|
+
// and the blit converts the renderer's premultiplied pixels to
|
|
504
|
+
// straight alpha unless the destination asks for premultiplied
|
|
505
|
+
// ones. The frame is released once the draw is submitted (Dawn
|
|
506
|
+
// keeps its own reference to the buffer for in-flight work).
|
|
507
|
+
auto videoFrame =
|
|
508
|
+
std::make_shared<VideoFrame>(std::move(frame));
|
|
509
|
+
VideoFrameBlit::Request blitRequest;
|
|
510
|
+
blitRequest.frame = videoFrame;
|
|
511
|
+
blitRequest.width = videoFrame->handle().width;
|
|
512
|
+
blitRequest.height = videoFrame->handle().height;
|
|
513
|
+
blitRequest.alpha = destinationPremultiplied
|
|
514
|
+
? VideoFrameBlit::Alpha::Premultiplied
|
|
515
|
+
: VideoFrameBlit::Alpha::Straight;
|
|
516
|
+
blitRequest.texture = dst.texture;
|
|
517
|
+
blitRequest.mipLevel = dst.mipLevel;
|
|
518
|
+
blitRequest.dstX = dst.origin.x;
|
|
519
|
+
blitRequest.dstY = dst.origin.y;
|
|
520
|
+
try {
|
|
521
|
+
blit->copy(device, queue, blitRequest);
|
|
522
|
+
} catch (const std::exception &error) {
|
|
523
|
+
videoFrame->release();
|
|
524
|
+
return rejectedPromise(runtime, error.what());
|
|
525
|
+
}
|
|
526
|
+
videoFrame->release();
|
|
527
|
+
return jsi::Value::undefined();
|
|
528
|
+
});
|
|
529
|
+
},
|
|
435
530
|
[reject](std::string error) { reject(std::move(error)); });
|
|
436
531
|
});
|
|
437
532
|
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
#include <memory>
|
|
4
4
|
#include <string>
|
|
5
|
+
#include <utility>
|
|
6
|
+
#include <vector>
|
|
5
7
|
|
|
6
8
|
#include "NativeObject.h"
|
|
7
9
|
|
|
@@ -248,10 +250,14 @@ public:
|
|
|
248
250
|
return std::make_shared<VideoFrame>(std::move(frame));
|
|
249
251
|
}
|
|
250
252
|
|
|
251
|
-
std::shared_ptr<VideoFrame>
|
|
252
|
-
|
|
253
|
+
std::shared_ptr<VideoFrame>
|
|
254
|
+
createTestVideoFrame(double width, double height,
|
|
255
|
+
std::optional<std::string> pixelFormat) {
|
|
256
|
+
auto format = (pixelFormat && pixelFormat.value() == "nv12")
|
|
257
|
+
? VideoPixelFormat::NV12
|
|
258
|
+
: VideoPixelFormat::BGRA8;
|
|
253
259
|
auto frame = _platformContext->createTestVideoFrame(
|
|
254
|
-
static_cast<uint32_t>(width), static_cast<uint32_t>(height));
|
|
260
|
+
static_cast<uint32_t>(width), static_cast<uint32_t>(height), format);
|
|
255
261
|
return std::make_shared<VideoFrame>(std::move(frame));
|
|
256
262
|
}
|
|
257
263
|
|
|
@@ -296,6 +302,9 @@ public:
|
|
|
296
302
|
// simulated unmount re-runs JS effects without unmounting native views,
|
|
297
303
|
// and removing the entry then would orphan the still-attached surface.
|
|
298
304
|
// - No native surface: the JS side is the last owner and removes the entry.
|
|
305
|
+
// The SurfaceInfo stays reachable by id for as long as a GPUCanvasContext
|
|
306
|
+
// holds it, so a surface attaching after StrictMode's early cleanup still
|
|
307
|
+
// lands in the context the first effect run made.
|
|
299
308
|
// The check-and-erase runs atomically under the registry lock
|
|
300
309
|
// (removeSurfaceInfoIfDetached), serialized against the UI thread's
|
|
301
310
|
// find-or-create + attach (SurfaceRegistry::attachSurface), so a concurrent
|