react-native-webgpu 0.5.11 → 0.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +84 -0
  2. package/android/CMakeLists.txt +2 -0
  3. package/android/cpp/AndroidPlatformContext.h +121 -0
  4. package/apple/ApplePlatformContext.h +13 -0
  5. package/apple/ApplePlatformContext.mm +145 -0
  6. package/apple/AppleVideoPlayer.h +31 -0
  7. package/apple/AppleVideoPlayer.mm +314 -0
  8. package/cpp/rnwgpu/ArrayBuffer.h +51 -7
  9. package/cpp/rnwgpu/PlatformContext.h +81 -0
  10. package/cpp/rnwgpu/RNWebGPUManager.cpp +12 -0
  11. package/cpp/rnwgpu/api/Convertors.h +33 -11
  12. package/cpp/rnwgpu/api/GPU.cpp +27 -0
  13. package/cpp/rnwgpu/api/GPUAdapter.cpp +109 -33
  14. package/cpp/rnwgpu/api/GPUDevice.cpp +58 -5
  15. package/cpp/rnwgpu/api/GPUDevice.h +6 -0
  16. package/cpp/rnwgpu/api/GPUExternalTexture.cpp +335 -0
  17. package/cpp/rnwgpu/api/GPUExternalTexture.h +47 -2
  18. package/cpp/rnwgpu/api/GPUFeatures.h +4 -1
  19. package/cpp/rnwgpu/api/GPUShaderModule.cpp +2 -3
  20. package/cpp/rnwgpu/api/GPUSharedTextureMemory.cpp +80 -0
  21. package/cpp/rnwgpu/api/GPUSharedTextureMemory.h +71 -0
  22. package/cpp/rnwgpu/api/ImageBitmap.h +8 -0
  23. package/cpp/rnwgpu/api/RNWebGPU.h +63 -21
  24. package/cpp/rnwgpu/api/RnFeatures.h +53 -0
  25. package/cpp/rnwgpu/api/VideoFrame.h +76 -0
  26. package/cpp/rnwgpu/api/VideoPlayer.h +69 -0
  27. package/cpp/rnwgpu/api/descriptors/GPUBindGroupEntry.h +4 -1
  28. package/cpp/rnwgpu/api/descriptors/GPUDawnTogglesDescriptor.h +57 -0
  29. package/cpp/rnwgpu/api/descriptors/GPUDeviceDescriptor.h +18 -3
  30. package/cpp/rnwgpu/api/descriptors/GPUExternalTextureDescriptor.h +35 -33
  31. package/cpp/rnwgpu/api/descriptors/GPUSharedTextureMemoryDescriptor.h +62 -0
  32. package/cpp/rnwgpu/api/descriptors/Unions.h +10 -0
  33. package/cpp/webgpu/webgpu.h +78 -17
  34. package/cpp/webgpu/webgpu_cpp.h +1014 -1249
  35. package/cpp/webgpu/webgpu_cpp_print.h +99 -10
  36. package/lib/commonjs/Canvas.js.map +1 -1
  37. package/lib/commonjs/index.js.map +1 -1
  38. package/lib/module/Canvas.js.map +1 -1
  39. package/lib/module/index.js.map +1 -1
  40. package/lib/typescript/src/Canvas.d.ts +0 -10
  41. package/lib/typescript/src/Canvas.d.ts.map +1 -1
  42. package/lib/typescript/src/index.d.ts +20 -1
  43. package/lib/typescript/src/index.d.ts.map +1 -1
  44. package/lib/typescript/src/types.d.ts +32 -1
  45. package/lib/typescript/src/types.d.ts.map +1 -1
  46. package/libs/android/arm64-v8a/libwebgpu_dawn.so +0 -0
  47. package/libs/android/armeabi-v7a/libwebgpu_dawn.so +0 -0
  48. package/libs/android/x86/libwebgpu_dawn.so +0 -0
  49. package/libs/android/x86_64/libwebgpu_dawn.so +0 -0
  50. package/libs/apple/libwebgpu_dawn.xcframework/Info.plist +6 -6
  51. package/libs/apple/libwebgpu_dawn.xcframework/ios-arm64/libwebgpu_dawn.a +0 -0
  52. package/libs/apple/libwebgpu_dawn.xcframework/ios-arm64_x86_64-simulator/libwebgpu_dawn.a +0 -0
  53. package/libs/apple/libwebgpu_dawn.xcframework/macos-arm64_x86_64/libwebgpu_dawn.a +0 -0
  54. package/package.json +3 -3
  55. package/src/Canvas.tsx +0 -15
  56. package/src/index.tsx +62 -2
  57. package/src/types.ts +83 -1
@@ -1,5 +1,6 @@
1
1
  #include "GPUAdapter.h"
2
2
 
3
+ #include <algorithm>
3
4
  #include <cstdio>
4
5
  #include <memory>
5
6
  #include <string>
@@ -11,12 +12,54 @@
11
12
 
12
13
  #include "GPUFeatures.h"
13
14
  #include "JSIConverter.h"
15
+ #include "RnFeatures.h"
14
16
  #include "WGPULogger.h"
15
17
 
16
18
  namespace rnwgpu {
17
19
 
18
20
  async::AsyncTaskHandle GPUAdapter::requestDevice(
19
21
  std::optional<std::shared_ptr<GPUDeviceDescriptor>> descriptor) {
22
+ // Enable the react-native-wgpu "native-texture" umbrella by default, mirroring
23
+ // the web where importExternalTexture is core and needs no feature request.
24
+ // We append the umbrella's backing Dawn features to requiredFeatures so the
25
+ // capability is on without the caller listing it. Two rules keep this safe:
26
+ // - All-or-nothing: only inject when the adapter supports *every* backing
27
+ // feature (same semantics as maybeSynthesizeRnNativeTextureFeature). On a
28
+ // web/fallback adapter the backing set is empty or unsupported, so this is
29
+ // a no-op and device creation is unaffected.
30
+ // - Requesting a feature the adapter doesn't support makes RequestDevice
31
+ // fail, hence the support check below.
32
+ // Callers can still pass "rnwebgpu/native-texture" explicitly; the dedupe
33
+ // keeps that idempotent.
34
+ {
35
+ auto backing = rnNativeTextureBackingFeatures();
36
+ if (!backing.empty()) {
37
+ wgpu::SupportedFeatures supported;
38
+ _instance.GetFeatures(&supported);
39
+ std::unordered_set<wgpu::FeatureName> supportedSet(
40
+ supported.features, supported.features + supported.featureCount);
41
+ bool allSupported = std::all_of(
42
+ backing.begin(), backing.end(),
43
+ [&](wgpu::FeatureName f) { return supportedSet.count(f) > 0; });
44
+ if (allSupported) {
45
+ if (!descriptor.has_value()) {
46
+ descriptor = std::make_shared<GPUDeviceDescriptor>();
47
+ }
48
+ auto &desc = descriptor.value();
49
+ if (!desc->requiredFeatures.has_value()) {
50
+ desc->requiredFeatures = std::vector<wgpu::FeatureName>{};
51
+ }
52
+ auto &features = desc->requiredFeatures.value();
53
+ for (auto f : backing) {
54
+ if (std::find(features.begin(), features.end(), f) ==
55
+ features.end()) {
56
+ features.push_back(f);
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
62
+
20
63
  wgpu::DeviceDescriptor aDescriptor;
21
64
  Convertor conv;
22
65
  if (!conv(aDescriptor, descriptor)) {
@@ -92,13 +135,39 @@ async::AsyncTaskHandle GPUAdapter::requestDevice(
92
135
  deviceLostBinding,
93
136
  creationRuntime](const async::AsyncTaskHandle::ResolveFunction &resolve,
94
137
  const async::AsyncTaskHandle::RejectFunction &reject) {
95
- (void)descriptor;
138
+ // Build a local mutable copy so we can chain Dawn's device toggles.
139
+ // The toggle name strings are owned by `descriptor` (captured above),
140
+ // and the const char* / DawnTogglesDescriptor locals live for the
141
+ // whole synchronous RequestDevice call below, which is when Dawn reads
142
+ // the chained struct.
143
+ wgpu::DeviceDescriptor deviceDesc = aDescriptor;
144
+ wgpu::DawnTogglesDescriptor toggles{};
145
+ std::vector<const char *> enabledToggles;
146
+ std::vector<const char *> disabledToggles;
147
+ if (descriptor.has_value() && descriptor.value()->dawnToggles) {
148
+ const auto &dawnToggles = descriptor.value()->dawnToggles.value();
149
+ if (dawnToggles->enabledToggles) {
150
+ for (const auto &t : dawnToggles->enabledToggles.value()) {
151
+ enabledToggles.push_back(t.c_str());
152
+ }
153
+ toggles.enabledToggleCount = enabledToggles.size();
154
+ toggles.enabledToggles = enabledToggles.data();
155
+ }
156
+ if (dawnToggles->disabledToggles) {
157
+ for (const auto &t : dawnToggles->disabledToggles.value()) {
158
+ disabledToggles.push_back(t.c_str());
159
+ }
160
+ toggles.disabledToggleCount = disabledToggles.size();
161
+ toggles.disabledToggles = disabledToggles.data();
162
+ }
163
+ deviceDesc.nextInChain = &toggles;
164
+ }
96
165
  _instance.RequestDevice(
97
- &aDescriptor, wgpu::CallbackMode::AllowProcessEvents,
166
+ &deviceDesc, wgpu::CallbackMode::AllowProcessEvents,
98
167
  [asyncRunner = _async, resolve, reject, label, creationRuntime,
99
168
  deviceLostBinding](wgpu::RequestDeviceStatus status,
100
169
  wgpu::Device device,
101
- wgpu::StringView message) mutable {
170
+ wgpu::StringView message) {
102
171
  if (message.length) {
103
172
  fprintf(stderr, "%s", message.data);
104
173
  }
@@ -111,36 +180,40 @@ async::AsyncTaskHandle GPUAdapter::requestDevice(
111
180
  return;
112
181
  }
113
182
 
114
- device.SetLoggingCallback([creationRuntime](
115
- wgpu::LoggingType type,
116
- wgpu::StringView msg) {
117
- if (creationRuntime == nullptr) {
118
- return;
119
- }
120
- const char *logLevel = "";
121
- switch (type) {
122
- case wgpu::LoggingType::Warning:
123
- logLevel = "Warning";
124
- Logger::warnToJavascriptConsole(
125
- *creationRuntime, std::string(msg.data, msg.length));
126
- break;
127
- case wgpu::LoggingType::Error:
128
- logLevel = "Error";
129
- Logger::errorToJavascriptConsole(
130
- *creationRuntime, std::string(msg.data, msg.length));
131
- break;
132
- case wgpu::LoggingType::Verbose:
133
- logLevel = "Verbose";
134
- break;
135
- case wgpu::LoggingType::Info:
136
- logLevel = "Info";
137
- break;
138
- default:
139
- logLevel = "Unknown";
140
- Logger::logToConsole("%s: %.*s", logLevel,
141
- static_cast<int>(msg.length), msg.data);
142
- }
143
- });
183
+ device.SetLoggingCallback(
184
+ [](wgpu::LoggingType type, wgpu::StringView msg,
185
+ jsi::Runtime *creationRuntime) {
186
+ if (creationRuntime == nullptr) {
187
+ return;
188
+ }
189
+ const char *logLevel = "";
190
+ switch (type) {
191
+ case wgpu::LoggingType::Warning:
192
+ logLevel = "Warning";
193
+ Logger::warnToJavascriptConsole(
194
+ *creationRuntime,
195
+ std::string(msg.data, msg.length));
196
+ break;
197
+ case wgpu::LoggingType::Error:
198
+ logLevel = "Error";
199
+ Logger::errorToJavascriptConsole(
200
+ *creationRuntime,
201
+ std::string(msg.data, msg.length));
202
+ break;
203
+ case wgpu::LoggingType::Verbose:
204
+ logLevel = "Verbose";
205
+ break;
206
+ case wgpu::LoggingType::Info:
207
+ logLevel = "Info";
208
+ break;
209
+ default:
210
+ logLevel = "Unknown";
211
+ Logger::logToConsole("%s: %.*s", logLevel,
212
+ static_cast<int>(msg.length),
213
+ msg.data);
214
+ }
215
+ },
216
+ creationRuntime);
144
217
 
145
218
  auto deviceHost = std::make_shared<GPUDevice>(std::move(device),
146
219
  asyncRunner, label);
@@ -163,14 +236,17 @@ std::unordered_set<std::string> GPUAdapter::getFeatures() {
163
236
  wgpu::SupportedFeatures supportedFeatures;
164
237
  _instance.GetFeatures(&supportedFeatures);
165
238
  std::unordered_set<std::string> result;
239
+ std::unordered_set<wgpu::FeatureName> enabled;
166
240
  for (size_t i = 0; i < supportedFeatures.featureCount; ++i) {
167
241
  auto feature = supportedFeatures.features[i];
242
+ enabled.insert(feature);
168
243
  std::string name;
169
244
  convertEnumToJSUnion(feature, &name);
170
245
  if (name != "") {
171
246
  result.insert(name);
172
247
  }
173
248
  }
249
+ maybeSynthesizeRnNativeTextureFeature(enabled, result);
174
250
  return result;
175
251
  }
176
252
 
@@ -13,6 +13,7 @@
13
13
  #include "GPUInternalError.h"
14
14
  #include "GPUOutOfMemoryError.h"
15
15
  #include "GPUValidationError.h"
16
+ #include "RnFeatures.h"
16
17
 
17
18
  namespace rnwgpu {
18
19
 
@@ -234,8 +235,55 @@ std::shared_ptr<GPUPipelineLayout> GPUDevice::createPipelineLayout(
234
235
 
235
236
  std::shared_ptr<GPUExternalTexture> GPUDevice::importExternalTexture(
236
237
  std::shared_ptr<GPUExternalTextureDescriptor> descriptor) {
238
+ // The import / begin-access / descriptor-build logic, plus the matching
239
+ // EndAccess, all live on GPUExternalTexture so the begin/end lifecycle stays
240
+ // in one translation unit (see GPUExternalTexture.cpp).
241
+ return GPUExternalTexture::Create(_instance, std::move(descriptor));
242
+ }
243
+
244
+ std::shared_ptr<GPUSharedTextureMemory> GPUDevice::importSharedTextureMemory(
245
+ std::shared_ptr<GPUSharedTextureMemoryDescriptor> descriptor) {
246
+ if (!descriptor || descriptor->handle == nullptr) {
247
+ throw std::runtime_error("GPUDevice::importSharedTextureMemory(): handle "
248
+ "must be a non-null native pointer");
249
+ }
250
+
251
+ wgpu::SharedTextureMemoryDescriptor desc{};
252
+ std::string label = descriptor->label.value_or("");
253
+ if (!label.empty()) {
254
+ desc.label = wgpu::StringView(label.c_str(), label.size());
255
+ }
256
+
257
+ #if defined(__APPLE__)
258
+ wgpu::SharedTextureMemoryIOSurfaceDescriptor platformDesc{};
259
+ platformDesc.ioSurface = descriptor->handle;
260
+ // Default off: enabling it propagates StorageBinding into properties.usage,
261
+ // which then forces memory.createTexture() (no-descriptor form) to validate
262
+ // the format against storage capabilities. bgra8unorm (the standard
263
+ // CVPixelBuffer format) only supports storage when the device opts into the
264
+ // bgra8unorm-storage feature, so unconditionally setting this here breaks
265
+ // the common sample-only case.
266
+ platformDesc.allowStorageBinding = false;
267
+ desc.nextInChain = &platformDesc;
268
+ #elif defined(__ANDROID__)
269
+ wgpu::SharedTextureMemoryAHardwareBufferDescriptor platformDesc{};
270
+ platformDesc.handle = descriptor->handle;
271
+ desc.nextInChain = &platformDesc;
272
+ #else
237
273
  throw std::runtime_error(
238
- "GPUDevice::importExternalTexture(): Not implemented");
274
+ "GPUDevice::importSharedTextureMemory(): unsupported platform");
275
+ #endif
276
+
277
+ auto memory = _instance.ImportSharedTextureMemory(&desc);
278
+ if (memory == nullptr) {
279
+ throw std::runtime_error("GPUDevice::importSharedTextureMemory(): "
280
+ "ImportSharedTextureMemory returned null - is the "
281
+ "'shared-texture-memory-iosurface' (Apple) or "
282
+ "'shared-texture-memory-ahardware-buffer' "
283
+ "(Android) feature enabled on the device?");
284
+ }
285
+ return std::make_shared<GPUSharedTextureMemory>(std::move(memory),
286
+ std::move(label));
239
287
  }
240
288
 
241
289
  async::AsyncTaskHandle GPUDevice::createComputePipelineAsync(
@@ -262,7 +310,7 @@ async::AsyncTaskHandle GPUDevice::createComputePipelineAsync(
262
310
  &desc, wgpu::CallbackMode::AllowProcessEvents,
263
311
  [pipelineHolder, resolve,
264
312
  reject](wgpu::CreatePipelineAsyncStatus status,
265
- wgpu::ComputePipeline pipeline, const char *msg) mutable {
313
+ wgpu::ComputePipeline pipeline, wgpu::StringView msg) {
266
314
  if (status == wgpu::CreatePipelineAsyncStatus::Success && pipeline) {
267
315
  pipelineHolder->_instance = pipeline;
268
316
  resolve([pipelineHolder](jsi::Runtime &runtime) mutable {
@@ -271,7 +319,8 @@ async::AsyncTaskHandle GPUDevice::createComputePipelineAsync(
271
319
  });
272
320
  } else {
273
321
  std::string error =
274
- msg ? std::string(msg) : "Failed to create compute pipeline";
322
+ msg.length ? std::string(msg.data, msg.length)
323
+ : "Failed to create compute pipeline";
275
324
  reject(std::move(error));
276
325
  }
277
326
  });
@@ -303,7 +352,7 @@ async::AsyncTaskHandle GPUDevice::createRenderPipelineAsync(
303
352
  &desc, wgpu::CallbackMode::AllowProcessEvents,
304
353
  [pipelineHolder, resolve,
305
354
  reject](wgpu::CreatePipelineAsyncStatus status,
306
- wgpu::RenderPipeline pipeline, const char *msg) mutable {
355
+ wgpu::RenderPipeline pipeline, wgpu::StringView msg) {
307
356
  if (status == wgpu::CreatePipelineAsyncStatus::Success && pipeline) {
308
357
  pipelineHolder->_instance = pipeline;
309
358
  resolve([pipelineHolder](jsi::Runtime &runtime) mutable {
@@ -312,7 +361,8 @@ async::AsyncTaskHandle GPUDevice::createRenderPipelineAsync(
312
361
  });
313
362
  } else {
314
363
  std::string error =
315
- msg ? std::string(msg) : "Failed to create render pipeline";
364
+ msg.length ? std::string(msg.data, msg.length)
365
+ : "Failed to create render pipeline";
316
366
  reject(std::move(error));
317
367
  }
318
368
  });
@@ -386,12 +436,15 @@ std::unordered_set<std::string> GPUDevice::getFeatures() {
386
436
  wgpu::SupportedFeatures supportedFeatures;
387
437
  _instance.GetFeatures(&supportedFeatures);
388
438
  std::unordered_set<std::string> result;
439
+ std::unordered_set<wgpu::FeatureName> enabled;
389
440
  for (size_t i = 0; i < supportedFeatures.featureCount; ++i) {
390
441
  auto feature = supportedFeatures.features[i];
442
+ enabled.insert(feature);
391
443
  std::string name;
392
444
  convertEnumToJSUnion(feature, &name);
393
445
  result.insert(name);
394
446
  }
447
+ maybeSynthesizeRnNativeTextureFeature(enabled, result);
395
448
  return result;
396
449
  }
397
450
 
@@ -45,6 +45,8 @@
45
45
  #include "GPURenderPipelineDescriptor.h"
46
46
  #include "GPUSampler.h"
47
47
  #include "GPUSamplerDescriptor.h"
48
+ #include "GPUSharedTextureMemory.h"
49
+ #include "GPUSharedTextureMemoryDescriptor.h"
48
50
  #include "GPUShaderModule.h"
49
51
  #include "GPUShaderModuleDescriptor.h"
50
52
  #include "GPUSupportedLimits.h"
@@ -116,6 +118,8 @@ public:
116
118
  std::optional<std::shared_ptr<GPUSamplerDescriptor>> descriptor);
117
119
  std::shared_ptr<GPUExternalTexture> importExternalTexture(
118
120
  std::shared_ptr<GPUExternalTextureDescriptor> descriptor);
121
+ std::shared_ptr<GPUSharedTextureMemory> importSharedTextureMemory(
122
+ std::shared_ptr<GPUSharedTextureMemoryDescriptor> descriptor);
119
123
  std::shared_ptr<GPUBindGroupLayout> createBindGroupLayout(
120
124
  std::shared_ptr<GPUBindGroupLayoutDescriptor> descriptor);
121
125
  std::shared_ptr<GPUPipelineLayout>
@@ -169,6 +173,8 @@ public:
169
173
  &GPUDevice::createSampler);
170
174
  installMethod(runtime, prototype, "importExternalTexture",
171
175
  &GPUDevice::importExternalTexture);
176
+ installMethod(runtime, prototype, "importSharedTextureMemory",
177
+ &GPUDevice::importSharedTextureMemory);
172
178
  installMethod(runtime, prototype, "createBindGroupLayout",
173
179
  &GPUDevice::createBindGroupLayout);
174
180
  installMethod(runtime, prototype, "createPipelineLayout",
@@ -0,0 +1,335 @@
1
+ #include "GPUExternalTexture.h"
2
+
3
+ #include <cmath>
4
+ #include <memory>
5
+ #include <string>
6
+ #include <utility>
7
+
8
+ #include "GPUExternalTextureDescriptor.h"
9
+
10
+ namespace rnwgpu {
11
+
12
+ // Identity gamut (BT.709 -> sRGB, same primaries) as a 3x3 column-major matrix.
13
+ static const float kIdentityGamutMatrix[9] = {
14
+ 1.0f, 0.0f, 0.0f, //
15
+ 0.0f, 1.0f, 0.0f, //
16
+ 0.0f, 0.0f, 1.0f, //
17
+ };
18
+
19
+ // Piecewise gamma transfer-function parameters Dawn expects:
20
+ // for |x| < D: y = sign(x) * (C * |x| + F)
21
+ // else : y = sign(x) * (pow(A * |x| + B, G) + E)
22
+ // sRGB decode (encoded -> linear).
23
+ static const float kSrgbDecodeParams[7] = {
24
+ 2.4f, // G
25
+ 1.0f / 1.055f, // A
26
+ 0.055f / 1.055f, // B
27
+ 1.0f / 12.92f, // C
28
+ 0.04045f, // D
29
+ 0.0f, // E
30
+ 0.0f, // F
31
+ };
32
+ // sRGB encode (linear -> encoded).
33
+ static const float kSrgbEncodeParams[7] = {
34
+ 1.0f / 2.4f, // G
35
+ 1.055f, // A
36
+ 0.0f, // B
37
+ 12.92f, // C
38
+ 0.0031308f, // D
39
+ -0.055f, // E
40
+ 0.0f, // F
41
+ };
42
+
43
+ // Identity transfer (y = x). Used when the sampled surface is already in the
44
+ // render target's color space: a single-plane BGRA IOSurface, or the Android
45
+ // opaque-YCbCr path where the Vulkan sampler already produced RGB. Dawn
46
+ // dereferences the transfer-function arrays unconditionally
47
+ // (ComputeExternalTextureParams), so these must be non-null even when no
48
+ // conversion is wanted.
49
+ static const float kIdentityTransferParams[7] = {
50
+ 1.0f, // G
51
+ 1.0f, // A
52
+ 0.0f, // B
53
+ 0.0f, // C
54
+ 0.0f, // D
55
+ 0.0f, // E
56
+ 0.0f, // F
57
+ };
58
+
59
+ // BT.709 limited-range YUV -> R'G'B' as a 3x4 row-major matrix mapping
60
+ // [Y, Cb, Cr, 1] to gamma-encoded R'G'B' (NOT linear; the sRGB decode in
61
+ // srcTransferFunctionParameters linearizes afterwards). Same values the Apple
62
+ // NV12 path computes from the CVPixelBuffer; used for Android buffers that
63
+ // arrive as a *defined* biplanar format (where we split the planes and convert
64
+ // ourselves) rather than an opaque external-format AHB. Camera streams are
65
+ // limited-range BT.709 in the overwhelming majority of cases; full-range /
66
+ // BT.601 would need different coefficients (refine from the buffer's suggested
67
+ // range if it matters).
68
+ [[maybe_unused]] static const float kBT709LimitedToRgb[12] = {
69
+ 1.164383f, 0.000000f, 1.792741f, -0.972945f, //
70
+ 1.164383f, -0.213249f, -0.532909f, 0.301517f, //
71
+ 1.164383f, 2.112402f, 0.000000f, -1.133402f, //
72
+ };
73
+
74
+ // True for the multi-planar Y + CbCr formats whose planes we can view as
75
+ // Plane0Only (luma) / Plane1Only (chroma) and convert with an explicit matrix.
76
+ // Excludes OpaqueYCbCrAndroid (external format, no plane views) and triplanar
77
+ // formats (would need a third plane). Only referenced on Android.
78
+ [[maybe_unused]] static bool isBiplanarYuvFormat(wgpu::TextureFormat format) {
79
+ switch (format) {
80
+ case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
81
+ case wgpu::TextureFormat::R8BG8Biplanar422Unorm:
82
+ case wgpu::TextureFormat::R8BG8Biplanar444Unorm:
83
+ case wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm:
84
+ case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm:
85
+ case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm:
86
+ return true;
87
+ default:
88
+ return false;
89
+ }
90
+ }
91
+
92
+ // Map a rotation in degrees (0 / 90 / 180 / 270) to Dawn's enum. Anything that
93
+ // isn't a clean multiple of 90 snaps to the nearest quadrant; Dawn only
94
+ // supports those four steps for external textures.
95
+ static wgpu::ExternalTextureRotation
96
+ toExternalTextureRotation(double degrees) {
97
+ int quadrant = static_cast<int>(std::lround(degrees / 90.0)) & 3;
98
+ switch (quadrant) {
99
+ case 1:
100
+ return wgpu::ExternalTextureRotation::Rotate90Degrees;
101
+ case 2:
102
+ return wgpu::ExternalTextureRotation::Rotate180Degrees;
103
+ case 3:
104
+ return wgpu::ExternalTextureRotation::Rotate270Degrees;
105
+ default:
106
+ return wgpu::ExternalTextureRotation::Rotate0Degrees;
107
+ }
108
+ }
109
+
110
+ std::shared_ptr<GPUExternalTexture> GPUExternalTexture::Create(
111
+ wgpu::Device device,
112
+ std::shared_ptr<GPUExternalTextureDescriptor> descriptor) {
113
+ if (!descriptor || !descriptor->source) {
114
+ throw std::runtime_error(
115
+ "GPUExternalTexture::Create(): descriptor.source (VideoFrame) "
116
+ "is required");
117
+ }
118
+ const auto &source = descriptor->source;
119
+ const auto &frame = source->handle();
120
+ if (frame.handle == nullptr) {
121
+ throw std::runtime_error(
122
+ "GPUExternalTexture::Create(): VideoFrame has been released");
123
+ }
124
+
125
+ #if defined(__APPLE__)
126
+ // 1. Import the IOSurface as SharedTextureMemory. For NV12 surfaces this
127
+ // yields a biplanar texture; for BGRA, a single-plane one.
128
+ wgpu::SharedTextureMemoryDescriptor memDesc{};
129
+ std::string label = descriptor->label.value_or("external-texture");
130
+ if (!label.empty()) {
131
+ memDesc.label = wgpu::StringView(label.c_str(), label.size());
132
+ }
133
+ wgpu::SharedTextureMemoryIOSurfaceDescriptor platformDesc{};
134
+ platformDesc.ioSurface = frame.handle;
135
+ // ExternalTexture views are sampled-only; storage binding isn't needed and
136
+ // for biplanar formats it would fail validation.
137
+ platformDesc.allowStorageBinding = false;
138
+ memDesc.nextInChain = &platformDesc;
139
+ auto memory = device.ImportSharedTextureMemory(&memDesc);
140
+ if (memory == nullptr) {
141
+ throw std::runtime_error(
142
+ "GPUExternalTexture::Create(): ImportSharedTextureMemory "
143
+ "returned null. Is 'shared-texture-memory-iosurface' enabled?");
144
+ }
145
+
146
+ // 2. Create the texture from the surface. We pass the right format
147
+ // explicitly so Dawn picks the multi-planar variant on NV12.
148
+ bool isYuv = frame.pixelFormat == VideoPixelFormat::NV12;
149
+ auto texture = memory.CreateTexture();
150
+ if (texture == nullptr) {
151
+ throw std::runtime_error(
152
+ "GPUExternalTexture::Create(): CreateTexture returned null");
153
+ }
154
+
155
+ // 3. Begin access on the underlying memory. The matching EndAccess runs when
156
+ // the GPUExternalTexture is destroyed (explicitly via destroy() or at GC).
157
+ wgpu::SharedTextureMemoryBeginAccessDescriptor begin{};
158
+ begin.initialized = true;
159
+ begin.concurrentRead = false;
160
+ if (!memory.BeginAccess(texture, &begin)) {
161
+ throw std::runtime_error(
162
+ "GPUExternalTexture::Create(): BeginAccess failed");
163
+ }
164
+
165
+ // 4. Build plane views. For NV12 we need plane0 = R8 luma and plane1 = RG8
166
+ // chroma; for BGRA we only set plane0.
167
+ wgpu::TextureView plane0;
168
+ wgpu::TextureView plane1;
169
+ {
170
+ wgpu::TextureViewDescriptor v{};
171
+ v.aspect =
172
+ isYuv ? wgpu::TextureAspect::Plane0Only : wgpu::TextureAspect::All;
173
+ plane0 = texture.CreateView(&v);
174
+ }
175
+ if (isYuv) {
176
+ wgpu::TextureViewDescriptor v{};
177
+ v.aspect = wgpu::TextureAspect::Plane1Only;
178
+ plane1 = texture.CreateView(&v);
179
+ }
180
+
181
+ // 5. Build the ExternalTextureDescriptor. We hand Dawn explicit YUV→RGB and
182
+ // sRGB transfer-function parameters so the sampler does the full color
183
+ // conversion in hardware.
184
+ wgpu::ExternalTextureDescriptor extDesc{};
185
+ if (!label.empty()) {
186
+ extDesc.label = wgpu::StringView(label.c_str(), label.size());
187
+ }
188
+ extDesc.plane0 = plane0;
189
+ extDesc.gamutConversionMatrix = kIdentityGamutMatrix;
190
+ if (isYuv) {
191
+ extDesc.plane1 = plane1;
192
+ extDesc.yuvToRgbConversionMatrix = frame.yuvToRgbMatrix;
193
+ extDesc.srcTransferFunctionParameters = kSrgbDecodeParams;
194
+ extDesc.dstTransferFunctionParameters = kSrgbEncodeParams;
195
+ } else {
196
+ // BGRA is already RGB in the target color space; pass it through. Dawn
197
+ // dereferences these arrays unconditionally, so they must be non-null.
198
+ extDesc.srcTransferFunctionParameters = kIdentityTransferParams;
199
+ extDesc.dstTransferFunctionParameters = kIdentityTransferParams;
200
+ }
201
+ extDesc.cropOrigin = {0, 0};
202
+ extDesc.cropSize = {frame.width, frame.height};
203
+ extDesc.apparentSize = {frame.width, frame.height};
204
+ extDesc.mirrored = descriptor->mirrored.value_or(false);
205
+ extDesc.rotation =
206
+ toExternalTextureRotation(descriptor->rotation.value_or(0));
207
+
208
+ auto external = device.CreateExternalTexture(&extDesc);
209
+ if (external == nullptr) {
210
+ wgpu::SharedTextureMemoryEndAccessState state{};
211
+ (void)memory.EndAccess(texture, &state);
212
+ throw std::runtime_error(
213
+ "GPUExternalTexture::Create(): CreateExternalTexture returned "
214
+ "null");
215
+ }
216
+
217
+ return std::make_shared<GPUExternalTexture>(
218
+ std::move(external), std::move(memory), std::move(texture),
219
+ std::move(descriptor->source), std::move(label));
220
+ #elif defined(__ANDROID__)
221
+ // 1. Import the AHardwareBuffer as SharedTextureMemory. For YUV AHBs this
222
+ // yields a Dawn texture in the implementation-defined OpaqueYCbCrAndroid
223
+ // format; for RGBA AHBs, a regular single-plane texture.
224
+ wgpu::SharedTextureMemoryDescriptor memDesc{};
225
+ std::string label = descriptor->label.value_or("external-texture");
226
+ if (!label.empty()) {
227
+ memDesc.label = wgpu::StringView(label.c_str(), label.size());
228
+ }
229
+ wgpu::SharedTextureMemoryAHardwareBufferDescriptor platformDesc{};
230
+ platformDesc.handle = frame.handle;
231
+ memDesc.nextInChain = &platformDesc;
232
+ auto memory = device.ImportSharedTextureMemory(&memDesc);
233
+ if (memory == nullptr) {
234
+ throw std::runtime_error(
235
+ "GPUExternalTexture::Create(): ImportSharedTextureMemory "
236
+ "returned null. Is 'shared-texture-memory-ahardware-buffer' enabled?");
237
+ }
238
+
239
+ // 2. Create the texture. No descriptor: Dawn picks the right format
240
+ // (OpaqueYCbCrAndroid for YUV, R8 / RGBA8 / ... for color AHBs).
241
+ auto texture = memory.CreateTexture();
242
+ if (texture == nullptr) {
243
+ throw std::runtime_error(
244
+ "GPUExternalTexture::Create(): CreateTexture returned null");
245
+ }
246
+
247
+ // 3. Begin access. Vulkan requires us to advertise the incoming VkImage
248
+ // layout (UNDEFINED is fine for the first acquisition of an AHB whose
249
+ // contents we expect Dawn to read as-is).
250
+ wgpu::SharedTextureMemoryBeginAccessDescriptor begin{};
251
+ begin.initialized = true;
252
+ begin.concurrentRead = false;
253
+ wgpu::SharedTextureMemoryVkImageLayoutBeginState beginLayout{};
254
+ begin.nextInChain = &beginLayout;
255
+ if (!memory.BeginAccess(texture, &begin)) {
256
+ throw std::runtime_error(
257
+ "GPUExternalTexture::Create(): BeginAccess failed");
258
+ }
259
+
260
+ // 4. Build the ExternalTextureDescriptor. There are two cases depending on
261
+ // how Dawn imported the AHB (see SharedTextureMemoryVk.cpp):
262
+ //
263
+ // a. *External* format (camera buffers whose layout has no Vulkan
264
+ // equivalent) -> OpaqueYCbCrAndroid, a single opaque plane. Sampling
265
+ // routes through a Vulkan SamplerYcbcrConversion whose model Dawn
266
+ // copies verbatim from the AHB's suggestedYcbcrModel. We pass a single
267
+ // plane + identity transfer and let that conversion (if any) run. NOTE:
268
+ // when the driver reports RGB_IDENTITY the sample comes back as raw
269
+ // Y/Cb/Cr; there is no public hook to override the model on this path.
270
+ //
271
+ // b. *Defined* biplanar format (e.g. R8BG8Biplanar420Unorm, exposed by the
272
+ // dawn-multi-planar-formats feature) -> we split Plane0Only (luma) /
273
+ // Plane1Only (chroma) and hand Dawn an explicit BT.709 matrix + sRGB
274
+ // transfer, exactly like the iOS NV12 path. This makes numPlanes == 2
275
+ // so the matrix is actually applied (the single-plane branch in Dawn's
276
+ // Tint transform ignores yuvToRgbConversionMatrix).
277
+ //
278
+ // Either way we must pass non-null gamut/transfer arrays:
279
+ // ComputeExternalTextureParams dereferences them unconditionally
280
+ // (kIdentityTransferParams is defined at file scope).
281
+ const bool isBiplanar = frame.pixelFormat == VideoPixelFormat::NV12 &&
282
+ isBiplanarYuvFormat(texture.GetFormat());
283
+
284
+ wgpu::TextureView plane0;
285
+ wgpu::TextureView plane1;
286
+ wgpu::ExternalTextureDescriptor extDesc{};
287
+ if (!label.empty()) {
288
+ extDesc.label = wgpu::StringView(label.c_str(), label.size());
289
+ }
290
+ extDesc.cropOrigin = {0, 0};
291
+ extDesc.cropSize = {frame.width, frame.height};
292
+ extDesc.apparentSize = {frame.width, frame.height};
293
+ extDesc.gamutConversionMatrix = kIdentityGamutMatrix;
294
+ if (isBiplanar) {
295
+ wgpu::TextureViewDescriptor v0{};
296
+ v0.aspect = wgpu::TextureAspect::Plane0Only;
297
+ plane0 = texture.CreateView(&v0);
298
+ wgpu::TextureViewDescriptor v1{};
299
+ v1.aspect = wgpu::TextureAspect::Plane1Only;
300
+ plane1 = texture.CreateView(&v1);
301
+ extDesc.plane0 = plane0;
302
+ extDesc.plane1 = plane1;
303
+ extDesc.yuvToRgbConversionMatrix = kBT709LimitedToRgb;
304
+ extDesc.srcTransferFunctionParameters = kSrgbDecodeParams;
305
+ extDesc.dstTransferFunctionParameters = kSrgbEncodeParams;
306
+ } else {
307
+ plane0 = texture.CreateView();
308
+ extDesc.plane0 = plane0;
309
+ extDesc.srcTransferFunctionParameters = kIdentityTransferParams;
310
+ extDesc.dstTransferFunctionParameters = kIdentityTransferParams;
311
+ }
312
+ extDesc.mirrored = descriptor->mirrored.value_or(false);
313
+ extDesc.rotation =
314
+ toExternalTextureRotation(descriptor->rotation.value_or(0));
315
+
316
+ auto external = device.CreateExternalTexture(&extDesc);
317
+ if (external == nullptr) {
318
+ wgpu::SharedTextureMemoryEndAccessState state{};
319
+ (void)memory.EndAccess(texture, &state);
320
+ throw std::runtime_error(
321
+ "GPUExternalTexture::Create(): CreateExternalTexture returned "
322
+ "null");
323
+ }
324
+
325
+ return std::make_shared<GPUExternalTexture>(
326
+ std::move(external), std::move(memory), std::move(texture),
327
+ std::move(descriptor->source), std::move(label));
328
+ #else
329
+ throw std::runtime_error(
330
+ "GPUExternalTexture::Create(): not yet implemented on this "
331
+ "platform");
332
+ #endif
333
+ }
334
+
335
+ } // namespace rnwgpu