react-native-webgpu 0.14.2 → 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.
Files changed (104) hide show
  1. package/android/cpp/AndroidColorSpace.h +107 -0
  2. package/android/cpp/AndroidPlatformContext.h +100 -9
  3. package/android/cpp/AndroidVideoPlayer.h +46 -1
  4. package/android/cpp/cpp-adapter.cpp +53 -1
  5. package/android/src/main/java/com/webgpu/SyncFences.java +88 -0
  6. package/android/src/main/java/com/webgpu/ViewSnapshot.java +66 -23
  7. package/android/src/main/java/com/webgpu/WebGPUModule.java +69 -5
  8. package/android/src/main/java/com/webgpu/WebGPUVideoPlayer.java +109 -0
  9. package/apple/ApplePlatformContext.h +5 -5
  10. package/apple/ApplePlatformContext.mm +75 -3
  11. package/apple/AppleVideoPlayer.mm +68 -35
  12. package/cpp/dawn/common/ExternalTextureParams.h +67 -0
  13. package/cpp/rnwgpu/PlatformContext.h +65 -16
  14. package/cpp/rnwgpu/api/GPUAdapter.cpp +35 -0
  15. package/cpp/rnwgpu/api/GPUExternalTexture.cpp +172 -156
  16. package/cpp/rnwgpu/api/GPUExternalTexture.h +2 -19
  17. package/cpp/rnwgpu/api/GPUQueue.cpp +98 -3
  18. package/cpp/rnwgpu/api/RNWebGPU.h +9 -3
  19. package/cpp/rnwgpu/api/VideoFrameBlit.h +39 -24
  20. package/cpp/rnwgpu/api/descriptors/GPUExternalTextureDescriptor.h +10 -3
  21. package/lib/commonjs/Canvas.js +32 -5
  22. package/lib/commonjs/Canvas.js.map +1 -1
  23. package/lib/commonjs/WebPolyfillGPUModule.js +6 -3
  24. package/lib/commonjs/WebPolyfillGPUModule.js.map +1 -1
  25. package/lib/commonjs/constants.js +6 -3
  26. package/lib/commonjs/constants.js.map +1 -1
  27. package/lib/commonjs/index.js +1 -0
  28. package/lib/commonjs/index.js.map +1 -1
  29. package/lib/commonjs/install.js +9 -22
  30. package/lib/commonjs/install.js.map +1 -1
  31. package/lib/commonjs/main/index.js +8 -7
  32. package/lib/commonjs/main/index.js.map +1 -1
  33. package/lib/commonjs/main/index.web.js +1 -1
  34. package/lib/commonjs/main/installNative.js +20 -0
  35. package/lib/commonjs/main/installNative.js.map +1 -0
  36. package/lib/commonjs/main/installNative.web.js +4 -0
  37. package/lib/commonjs/main/installNative.web.js.map +1 -0
  38. package/lib/commonjs/mock.js +60 -13
  39. package/lib/commonjs/mock.js.map +1 -1
  40. package/lib/module/Canvas.js +33 -6
  41. package/lib/module/Canvas.js.map +1 -1
  42. package/lib/module/WebPolyfillGPUModule.js +6 -3
  43. package/lib/module/WebPolyfillGPUModule.js.map +1 -1
  44. package/lib/module/constants.js +6 -3
  45. package/lib/module/constants.js.map +1 -1
  46. package/lib/module/index.js +8 -1
  47. package/lib/module/index.js.map +1 -1
  48. package/lib/module/install.js +8 -20
  49. package/lib/module/install.js.map +1 -1
  50. package/lib/module/main/index.js +6 -7
  51. package/lib/module/main/index.js.map +1 -1
  52. package/lib/module/main/index.web.js +1 -1
  53. package/lib/module/main/index.web.js.map +1 -1
  54. package/lib/module/main/installNative.js +18 -0
  55. package/lib/module/main/installNative.js.map +1 -0
  56. package/lib/module/main/installNative.web.js +6 -0
  57. package/lib/module/main/installNative.web.js.map +1 -0
  58. package/lib/module/mock.js +55 -1
  59. package/lib/module/mock.js.map +1 -1
  60. package/lib/typescript/lib/commonjs/Canvas.d.ts.map +1 -1
  61. package/lib/typescript/lib/commonjs/install.d.ts +41 -3
  62. package/lib/typescript/lib/commonjs/install.d.ts.map +1 -1
  63. package/lib/typescript/lib/commonjs/main/installNative.d.ts +2 -0
  64. package/lib/typescript/lib/commonjs/main/installNative.d.ts.map +1 -0
  65. package/lib/typescript/lib/commonjs/main/installNative.web.d.ts +2 -0
  66. package/lib/typescript/lib/commonjs/main/installNative.web.d.ts.map +1 -0
  67. package/lib/typescript/lib/commonjs/mock.d.ts +39 -0
  68. package/lib/typescript/lib/commonjs/mock.d.ts.map +1 -1
  69. package/lib/typescript/lib/module/Canvas.d.ts.map +1 -1
  70. package/lib/typescript/lib/module/install.d.ts +0 -1
  71. package/lib/typescript/lib/module/install.d.ts.map +1 -1
  72. package/lib/typescript/lib/module/main/installNative.d.ts +2 -0
  73. package/lib/typescript/lib/module/main/installNative.d.ts.map +1 -0
  74. package/lib/typescript/lib/module/main/installNative.web.d.ts +2 -0
  75. package/lib/typescript/lib/module/main/installNative.web.d.ts.map +1 -0
  76. package/lib/typescript/lib/module/mock.d.ts +39 -1
  77. package/lib/typescript/lib/module/mock.d.ts.map +1 -1
  78. package/lib/typescript/src/Canvas.d.ts.map +1 -1
  79. package/lib/typescript/src/constants.d.ts.map +1 -1
  80. package/lib/typescript/src/index.d.ts +2 -2
  81. package/lib/typescript/src/index.d.ts.map +1 -1
  82. package/lib/typescript/src/install.d.ts +0 -5
  83. package/lib/typescript/src/install.d.ts.map +1 -1
  84. package/lib/typescript/src/main/index.d.ts +1 -0
  85. package/lib/typescript/src/main/index.d.ts.map +1 -1
  86. package/lib/typescript/src/main/index.web.d.ts +1 -1
  87. package/lib/typescript/src/main/index.web.d.ts.map +1 -1
  88. package/lib/typescript/src/main/installNative.d.ts +2 -0
  89. package/lib/typescript/src/main/installNative.d.ts.map +1 -0
  90. package/lib/typescript/src/main/installNative.web.d.ts +2 -0
  91. package/lib/typescript/src/main/installNative.web.d.ts.map +1 -0
  92. package/lib/typescript/src/mock.d.ts +5 -1
  93. package/lib/typescript/src/mock.d.ts.map +1 -1
  94. package/package.json +1 -1
  95. package/src/Canvas.tsx +44 -28
  96. package/src/WebPolyfillGPUModule.ts +3 -1
  97. package/src/constants.ts +6 -3
  98. package/src/index.tsx +18 -10
  99. package/src/install.ts +9 -18
  100. package/src/main/index.tsx +7 -10
  101. package/src/main/index.web.tsx +1 -1
  102. package/src/main/installNative.ts +20 -0
  103. package/src/main/installNative.web.ts +5 -0
  104. package/src/mock.ts +60 -1
@@ -3,6 +3,7 @@
3
3
  #include <functional>
4
4
  #include <memory>
5
5
  #include <mutex>
6
+ #include <optional>
6
7
  #include <span>
7
8
  #include <string>
8
9
  #include <utility>
@@ -36,6 +37,20 @@ enum class VideoPixelFormat {
36
37
  NV12,
37
38
  };
38
39
 
40
+ // The color space of a YUV frame, as its producer reports it: the YCbCr matrix
41
+ // and range that encode the samples, and the primaries and transfer function of
42
+ // the RGB they encode. Each member is optional: what the producer does not know
43
+ // (a camera buffer carries no color metadata, a container may record only some
44
+ // of it) is resolved at import time (GPUExternalTexture::Create) from the
45
+ // buffer and the platform's norm. Dawn converts the samples to RGB in the
46
+ // destination color space while sampling the external texture.
47
+ struct VideoFrameColorSpace {
48
+ std::optional<wgpu::ColorSpacePrimariesDawn> primaries;
49
+ std::optional<wgpu::ColorSpaceTransferDawn> transfer;
50
+ std::optional<wgpu::ColorSpaceYCbCrRangeDawn> range;
51
+ std::optional<wgpu::ColorSpaceYCbCrMatrixDawn> matrix;
52
+ };
53
+
39
54
  // A native handle to a video frame that can be imported into a
40
55
  // GPUSharedTextureMemory.
41
56
  //
@@ -50,14 +65,33 @@ struct VideoFrameHandle {
50
65
  uint32_t width = 0;
51
66
  uint32_t height = 0;
52
67
  VideoPixelFormat pixelFormat = VideoPixelFormat::BGRA8;
53
- // 3x4 row-major matrix mapping [Y, U, V, 1] → linear [R, G, B]. Pre-computed
54
- // at decode time from CVPixelBuffer attachments (kCVImageBufferYCbCrMatrixKey
55
- // + range), with a BT.709 limited-range default. Only meaningful when
56
- // pixelFormat == NV12.
57
- float yuvToRgbMatrix[12] = {};
68
+ // The color space of the samples. Only meaningful when pixelFormat == NV12:
69
+ // from the CVPixelBuffer attachments on Apple platforms, from the Image's
70
+ // data space (API 33) or the container's color info on Android.
71
+ VideoFrameColorSpace colorSpace;
72
+ // Android: the acquire fence of the frame as a sync file descriptor, or -1.
73
+ // The producer (video decoder, camera) may still be writing the buffer when
74
+ // the consumer gets it; the GPU work that reads the frame waits on this fence
75
+ // (imported as a shared-fence-sync-fd into BeginAccess). Owned by the handle:
76
+ // the deleter closes it. Importing it into Dawn duplicates it, so the frame
77
+ // can be imported more than once.
78
+ int acquireFenceFd = -1;
58
79
  std::function<void()> deleter;
59
80
  };
60
81
 
82
+ // The samples of the NV12 test frame of createTestVideoFrame: pure red and
83
+ // pure green in BT.709 limited range (Y 16..235, Cb/Cr 16..240), so the frame
84
+ // converts back to exactly those colors in sRGB. Shared by both platforms so
85
+ // the e2e spec sees the same frame on each.
86
+ namespace test_yuv_frame {
87
+ constexpr uint8_t kRedY = 63;
88
+ constexpr uint8_t kRedCb = 102;
89
+ constexpr uint8_t kRedCr = 240;
90
+ constexpr uint8_t kGreenY = 173;
91
+ constexpr uint8_t kGreenCb = 42;
92
+ constexpr uint8_t kGreenCr = 26;
93
+ } // namespace test_yuv_frame
94
+
61
95
  // Platform-implemented video source that hands out fresh IOSurface /
62
96
  // AHardwareBuffer-backed frames as a video plays, with the playback controls
63
97
  // of an HTMLMediaElement (times in seconds).
@@ -110,6 +144,10 @@ struct ViewSnapshotRequest {
110
144
  double sourceHeight = 0;
111
145
  uint32_t width = 0;
112
146
  uint32_t height = 0;
147
+ // Whether the caller can draw a GPU frame (a VideoFrameHandle) into the
148
+ // destination itself. A platform that renders the view into a GPU buffer
149
+ // then hands that buffer over instead of reading the pixels back.
150
+ bool allowFrame = false;
113
151
  };
114
152
 
115
153
  class PlatformContext {
@@ -129,17 +167,24 @@ public:
129
167
  return currentSlot();
130
168
  }
131
169
 
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
170
+ // Rasterize a native view (see ViewSnapshotRequest). The implementation hops
171
+ // to the platform UI thread, so this is asynchronous: exactly one of
172
+ // onPixels / onFrame / onError is invoked, from an arbitrary thread
135
173
  // (typically the UI thread). Callers must not touch JSI in the callbacks.
136
174
  //
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.
175
+ // onPixels receives the view as CPU pixels: tightly packed (no row
176
+ // padding), 4 bytes per pixel, `format` rgba8unorm or bgra8unorm depending
177
+ // on what the platform rasterizer produces natively, and `premultiplied`
178
+ // reporting its alpha representation.
179
+ //
180
+ // onFrame, only when the request allows it, receives the GPU buffer the
181
+ // platform rendered the view into (Android 13 and above: the
182
+ // HardwareRenderer's AHardwareBuffer, with the fence that signals when the
183
+ // drawing is done), as an RGBA frame of premultiplied pixels. The receiver
184
+ // owns the frame and releases it through its deleter.
141
185
  virtual void snapshotView(const ViewSnapshotRequest &request,
142
- std::function<void(ImageData)> onSuccess,
186
+ std::function<void(ImageData)> onPixels,
187
+ std::function<void(VideoFrameHandle)> onFrame,
143
188
  std::function<void(std::string)> onError) = 0;
144
189
 
145
190
  virtual ImageData createImageBitmap(std::string blobId, double offset,
@@ -167,9 +212,13 @@ public:
167
212
 
168
213
  // Create a synthetic, GPU-shareable IOSurface/AHardwareBuffer filled with a
169
214
  // generated test pattern. Avoids the need to bundle a video asset for the
170
- // SharedTextureMemory example.
171
- virtual VideoFrameHandle createTestVideoFrame(uint32_t width,
172
- uint32_t height) = 0;
215
+ // SharedTextureMemory example. BGRA8 is an RGB gradient; NV12 is a biplanar
216
+ // YUV frame, BT.709 limited range, red on the left half and green on the
217
+ // right, which exercises the color conversion of importExternalTexture.
218
+ // Throws a message containing "unsupported" where the platform cannot
219
+ // allocate or fill the YUV layout (Android before API 29).
220
+ virtual VideoFrameHandle createTestVideoFrame(uint32_t width, uint32_t height,
221
+ VideoPixelFormat format) = 0;
173
222
 
174
223
  // Open a video file at `path` for playback. The returned player yields
175
224
  // IOSurface / AHardwareBuffer-backed frames via copyLatestFrame().
@@ -12,6 +12,9 @@
12
12
  #ifdef __APPLE__
13
13
  #include <TargetConditionals.h>
14
14
  #endif
15
+ #ifdef __ANDROID__
16
+ #include <android/log.h>
17
+ #endif
15
18
 
16
19
  #include "Convertors.h"
17
20
 
@@ -158,7 +161,14 @@ async::AsyncTaskHandle GPUAdapter::requestDevice(
158
161
  message.length > 0 ? std::string(message.data, message.length) : "";
159
162
  std::string fullMessage =
160
163
  msg.length() > 0 ? std::string(errorType) + ": " + msg : "no message";
164
+ #ifdef __ANDROID__
165
+ // stderr goes nowhere in an Android app; logcat is where the message can
166
+ // be read (and where CI collects it).
167
+ __android_log_print(ANDROID_LOG_ERROR, "WebGPU", "Uncaptured error: %s",
168
+ fullMessage.c_str());
169
+ #else
161
170
  fprintf(stderr, "%s\n", fullMessage.c_str());
171
+ #endif
162
172
 
163
173
  // Look up the GPUDevice from the registry and notify it
164
174
  if (auto gpuDevice = GPUDevice::lookupDevice(device.Get())) {
@@ -224,6 +234,31 @@ async::AsyncTaskHandle GPUAdapter::requestDevice(
224
234
  disabledToggles.push_back(name);
225
235
  }
226
236
  }
237
+ #endif
238
+ #if defined(__ANDROID__)
239
+ // Dawn checks the memory a VkImage needs for an imported
240
+ // AHardwareBuffer against the allocation size the driver reports for
241
+ // the buffer, and some drivers report it wrong: Dawn itself skips the
242
+ // check on Samsung, Qualcomm and Huawei drivers
243
+ // (https://crbug.com/333424893). The emulator (gfxstream) reports 0
244
+ // for the buffers HWUI draws into, which rejects every view snapshot
245
+ // and SurfaceControl import there, so skip it on all of Android unless
246
+ // the caller disables the toggle. Dawn drops unknown toggle names, so a
247
+ // build without this toggle is unaffected.
248
+ static const char *const kAndroidEnabledToggles[] = {
249
+ "ignore_imported_ahardwarebuffer_vulkan_image_size"};
250
+ for (const char *name : kAndroidEnabledToggles) {
251
+ const auto named = [name](const char *t) {
252
+ return std::strcmp(t, name) == 0;
253
+ };
254
+ const bool explicitlyDisabled = std::any_of(
255
+ disabledToggles.begin(), disabledToggles.end(), named);
256
+ const bool alreadyEnabled =
257
+ std::any_of(enabledToggles.begin(), enabledToggles.end(), named);
258
+ if (!explicitlyDisabled && !alreadyEnabled) {
259
+ enabledToggles.push_back(name);
260
+ }
261
+ }
227
262
  #endif
228
263
  if (!enabledToggles.empty() || !disabledToggles.empty()) {
229
264
  toggles.enabledToggleCount = enabledToggles.size();
@@ -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
- // Identity gamut (BT.709 -> sRGB, same primaries) as a 3x3 column-major matrix.
53
- static const float kIdentityGamutMatrix[9] = {
54
- 1.0f, 0.0f, 0.0f, //
55
- 0.0f, 1.0f, 0.0f, //
56
- 0.0f, 0.0f, 1.0f, //
57
- };
58
-
59
- // Piecewise gamma transfer-function parameters Dawn expects:
60
- // for |x| < D: y = sign(x) * (C * |x| + F)
61
- // else : y = sign(x) * (pow(A * |x| + B, G) + E)
62
- // sRGB decode (encoded -> linear).
63
- static const float kSrgbDecodeParams[7] = {
64
- 2.4f, // G
65
- 1.0f / 1.055f, // A
66
- 0.055f / 1.055f, // B
67
- 1.0f / 12.92f, // C
68
- 0.04045f, // D
69
- 0.0f, // E
70
- 0.0f, // F
71
- };
72
- // sRGB encode (linear -> encoded).
73
- static const float kSrgbEncodeParams[7] = {
74
- 1.0f / 2.4f, // G
75
- 1.055f, // A
76
- 0.0f, // B
77
- 12.92f, // C
78
- 0.0031308f, // D
79
- -0.055f, // E
80
- 0.0f, // F
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
- float kr;
130
- float kb;
131
- switch (vkModel) {
132
- case kModel709:
133
- kr = 0.2126f;
134
- kb = 0.0722f;
135
- break;
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
- const float kg = 1.0f - kr - kb;
149
- const bool narrow = vkRange == kRangeItuNarrow;
150
- const float yScale = narrow ? 255.0f / 219.0f : 1.0f;
151
- const float cScale = narrow ? 255.0f / 224.0f : 1.0f;
152
- const float yOffset = narrow ? 16.0f / 255.0f : 0.0f;
153
- const float cOffset = 128.0f / 255.0f;
154
- const float crR = 2.0f * (1.0f - kr) * cScale;
155
- const float cbG = -2.0f * kb * (1.0f - kb) / kg * cScale;
156
- const float crG = -2.0f * kr * (1.0f - kr) / kg * cScale;
157
- const float cbB = 2.0f * (1.0f - kb) * cScale;
158
- return {
159
- yScale, 0.0f, crR, -yScale * yOffset - crR * cOffset, //
160
- yScale, cbG, crG, -yScale * yOffset - (cbG + crG) * cOffset, //
161
- yScale, cbB, 0.0f, -yScale * yOffset - cbB * cOffset, //
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, &params) !=
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 &params) {
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. We hand Dawn explicit YUV→RGB and
254
- // sRGB transfer-function parameters so the sampler does the full color
255
- // conversion in hardware.
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. Resolve the YUV->RGB conversion the *shader* must apply. Dawn imports
358
- // every YUV / implementation-defined AHB format as OpaqueYCbCrAndroid
359
- // (AHBFunctions.cpp::FormatFromAHardwareBufferFormat) and samples it
360
- // through a Vulkan SamplerYcbcrConversion that is hard-coded to
361
- // RGB_IDENTITY (SamplerVk.cpp::GetYCbCrForTextureView,
362
- // crbug.com/497675620), so the sample always comes back as raw
363
- // [Y, Cb, Cr]. The driver's *suggested* model + range are captured at
364
- // import time though; read them back from the shared memory's properties
365
- // and derive the correct conversion matrix, exposed to JS as
366
- // GPUExternalTexture.yuvToRgbMatrix. Defined color formats (e.g. an RGBA
367
- // AHB -> RGBA8Unorm) sample as RGB directly and get the passthrough.
368
- std::array<float, 12> yuvToRgbMatrix = kYuvPassthroughMatrix;
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
- yuvToRgbMatrix = makeYuvToRgbMatrix(ahbProps.yCbCrInfo.vkYCbCrModel,
375
- ahbProps.yCbCrInfo.vkYCbCrRange);
376
- } else {
377
- // Fall back to the Android camera norm rather than passthrough.
378
- yuvToRgbMatrix = makeYuvToRgbMatrix(/* YCBCR_601 */ 3,
379
- /* ITU_NARROW */ 1);
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.srcTransferFunctionParameters = kIdentityTransferParams;
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