react-native-webgpu 0.5.18 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/android/cpp/AndroidPlatformContext.h +24 -9
  2. package/android/cpp/cpp-adapter.cpp +35 -13
  3. package/android/src/main/java/com/webgpu/WebGPUAHBView.java +2 -2
  4. package/android/src/main/java/com/webgpu/WebGPUAPI.java +7 -2
  5. package/android/src/main/java/com/webgpu/WebGPUSurfaceView.java +3 -1
  6. package/android/src/main/java/com/webgpu/WebGPUTextureView.java +11 -7
  7. package/android/src/main/java/com/webgpu/WebGPUView.java +10 -7
  8. package/android/src/main/java/com/webgpu/WebGPUViewManager.java +6 -0
  9. package/apple/MetalView.mm +33 -8
  10. package/cpp/rnwgpu/RNWebGPUManager.cpp +14 -1
  11. package/cpp/rnwgpu/RNWebGPUManager.h +9 -0
  12. package/cpp/rnwgpu/SurfaceRegistry.h +469 -131
  13. package/cpp/rnwgpu/api/GPUAdapter.cpp +43 -7
  14. package/cpp/rnwgpu/api/GPUAdapter.h +5 -4
  15. package/cpp/rnwgpu/api/GPUBuffer.cpp +12 -2
  16. package/cpp/rnwgpu/api/GPUBuffer.h +3 -2
  17. package/cpp/rnwgpu/api/GPUCanvasContext.cpp +33 -19
  18. package/cpp/rnwgpu/api/GPUCanvasContext.h +3 -1
  19. package/cpp/rnwgpu/api/GPUDevice.cpp +32 -17
  20. package/cpp/rnwgpu/api/GPUDevice.h +9 -7
  21. package/cpp/rnwgpu/api/GPUExternalTexture.cpp +105 -74
  22. package/cpp/rnwgpu/api/GPUExternalTexture.h +21 -2
  23. package/cpp/rnwgpu/api/GPUQueue.cpp +6 -2
  24. package/cpp/rnwgpu/api/GPUQueue.h +3 -3
  25. package/cpp/rnwgpu/api/GPUShaderModule.cpp +7 -2
  26. package/cpp/rnwgpu/api/GPUShaderModule.h +3 -3
  27. package/cpp/rnwgpu/api/RNWebGPU.h +22 -1
  28. package/cpp/rnwgpu/async/RuntimeContext.cpp +8 -1
  29. package/cpp/rnwgpu/async/RuntimeContext.h +9 -2
  30. package/lib/commonjs/Canvas.js +10 -0
  31. package/lib/commonjs/Canvas.js.map +1 -1
  32. package/lib/commonjs/WebPolyfillGPUModule.js +2 -0
  33. package/lib/commonjs/WebPolyfillGPUModule.js.map +1 -1
  34. package/lib/module/Canvas.js +11 -1
  35. package/lib/module/Canvas.js.map +1 -1
  36. package/lib/module/WebPolyfillGPUModule.js +2 -0
  37. package/lib/module/WebPolyfillGPUModule.js.map +1 -1
  38. package/lib/typescript/lib/commonjs/Canvas.d.ts.map +1 -1
  39. package/lib/typescript/lib/module/Canvas.d.ts.map +1 -1
  40. package/lib/typescript/src/Canvas.d.ts.map +1 -1
  41. package/lib/typescript/src/index.d.ts +2 -0
  42. package/lib/typescript/src/index.d.ts.map +1 -1
  43. package/package.json +4 -6
  44. package/src/Canvas.tsx +11 -1
  45. package/src/WebPolyfillGPUModule.ts +2 -0
  46. package/src/index.tsx +12 -0
  47. package/android/src/main/java/com/webgpu/WebGPUBaseView.java +0 -61
@@ -2,12 +2,17 @@
2
2
 
3
3
  #include <algorithm>
4
4
  #include <cstdio>
5
+ #include <cstring>
5
6
  #include <memory>
6
7
  #include <string>
7
8
  #include <unordered_set>
8
9
  #include <utility>
9
10
  #include <vector>
10
11
 
12
+ #ifdef __APPLE__
13
+ #include <TargetConditionals.h>
14
+ #endif
15
+
11
16
  #include "Convertors.h"
12
17
 
13
18
  #include "GPUFeatures.h"
@@ -18,6 +23,7 @@
18
23
  namespace rnwgpu {
19
24
 
20
25
  async::AsyncTaskHandle GPUAdapter::requestDevice(
26
+ jsi::Runtime &runtime,
21
27
  std::optional<std::shared_ptr<GPUDeviceDescriptor>> descriptor) {
22
28
  // Enable the react-native-wgpu "native-texture" umbrella by default, mirroring
23
29
  // the web where importExternalTexture is core and needs no feature request.
@@ -130,9 +136,15 @@ async::AsyncTaskHandle GPUAdapter::requestDevice(
130
136
  descriptor.has_value() ? descriptor.value()->label.value_or("") : "";
131
137
 
132
138
  auto creationRuntime = getCreationRuntime();
133
- return _async->postTask(
139
+ // Post to the CALLING runtime's context so the promise settles on the
140
+ // thread that requested it (see GPUBuffer::mapAsync). The GPUDevice is also
141
+ // bound to this context, honoring the contract that a device belongs to the
142
+ // runtime that requested it.
143
+ auto context =
144
+ async::RuntimeContext::getOrCreate(runtime, _async->instance());
145
+ return context->postTask(
134
146
  [this, aDescriptor, descriptor, label = std::move(label),
135
- deviceLostBinding,
147
+ deviceLostBinding, context,
136
148
  creationRuntime](const async::AsyncTaskHandle::ResolveFunction &resolve,
137
149
  const async::AsyncTaskHandle::RejectFunction &reject) {
138
150
  // Build a local mutable copy so we can chain Dawn's device toggles.
@@ -150,21 +162,45 @@ async::AsyncTaskHandle GPUAdapter::requestDevice(
150
162
  for (const auto &t : dawnToggles->enabledToggles.value()) {
151
163
  enabledToggles.push_back(t.c_str());
152
164
  }
153
- toggles.enabledToggleCount = enabledToggles.size();
154
- toggles.enabledToggles = enabledToggles.data();
155
165
  }
156
166
  if (dawnToggles->disabledToggles) {
157
167
  for (const auto &t : dawnToggles->disabledToggles.value()) {
158
168
  disabledToggles.push_back(t.c_str());
159
169
  }
160
- toggles.disabledToggleCount = disabledToggles.size();
161
- toggles.disabledToggles = disabledToggles.data();
162
170
  }
171
+ }
172
+ // TODO: in the latest version of Dawn, this won't be needed (https://issues.chromium.org/issues/42241591)
173
+ #if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
174
+ // The iOS Simulator only advertises MTLFeatureSet_iOS_GPUFamily2, so
175
+ // Dawn defaults disable_base_instance/disable_base_vertex on and then
176
+ // rejects every draw with a non-zero firstInstance or baseVertex,
177
+ // both core WebGPU. The simulator forwards Metal calls to the host
178
+ // GPU, which does support base vertex/instance drawing, so force the
179
+ // toggles off. Device builds are unaffected: WebGPU-capable iPhones
180
+ // and iPads are all GPUFamily3+. These are device-stage toggles, so
181
+ // they must be chained here rather than on the instance descriptor
182
+ // (instance-stage toggle parsing silently drops them).
183
+ static const char *const kSimulatorDisabledToggles[] = {
184
+ "disable_base_instance", "disable_base_vertex"};
185
+ for (const char *name : kSimulatorDisabledToggles) {
186
+ const bool explicitlyEnabled = std::any_of(
187
+ enabledToggles.begin(), enabledToggles.end(),
188
+ [name](const char *t) { return std::strcmp(t, name) == 0; });
189
+ if (!explicitlyEnabled) {
190
+ disabledToggles.push_back(name);
191
+ }
192
+ }
193
+ #endif
194
+ if (!enabledToggles.empty() || !disabledToggles.empty()) {
195
+ toggles.enabledToggleCount = enabledToggles.size();
196
+ toggles.enabledToggles = enabledToggles.data();
197
+ toggles.disabledToggleCount = disabledToggles.size();
198
+ toggles.disabledToggles = disabledToggles.data();
163
199
  deviceDesc.nextInChain = &toggles;
164
200
  }
165
201
  _instance.RequestDevice(
166
202
  &deviceDesc, wgpu::CallbackMode::AllowProcessEvents,
167
- [context = _async, resolve, reject, label, creationRuntime,
203
+ [context, resolve, reject, label, creationRuntime,
168
204
  deviceLostBinding](wgpu::RequestDeviceStatus status,
169
205
  wgpu::Device device, wgpu::StringView message) {
170
206
  if (message.length) {
@@ -33,8 +33,9 @@ public:
33
33
  public:
34
34
  std::string getBrand() { return CLASS_NAME; }
35
35
 
36
- async::AsyncTaskHandle
37
- requestDevice(std::optional<std::shared_ptr<GPUDeviceDescriptor>> descriptor);
36
+ async::AsyncTaskHandle requestDevice(
37
+ jsi::Runtime &runtime,
38
+ std::optional<std::shared_ptr<GPUDeviceDescriptor>> descriptor);
38
39
 
39
40
  std::unordered_set<std::string> getFeatures();
40
41
  std::shared_ptr<GPUSupportedLimits> getLimits();
@@ -42,8 +43,8 @@ public:
42
43
 
43
44
  static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
44
45
  installGetter(runtime, prototype, "__brand", &GPUAdapter::getBrand);
45
- installMethod(runtime, prototype, "requestDevice",
46
- &GPUAdapter::requestDevice);
46
+ installMethodWithRuntime(runtime, prototype, "requestDevice",
47
+ &GPUAdapter::requestDevice);
47
48
  installGetter(runtime, prototype, "features", &GPUAdapter::getFeatures);
48
49
  installGetter(runtime, prototype, "limits", &GPUAdapter::getLimits);
49
50
  installGetter(runtime, prototype, "info", &GPUAdapter::getInfo);
@@ -37,7 +37,8 @@ GPUBuffer::getMappedRange(std::optional<size_t> o, std::optional<size_t> size) {
37
37
 
38
38
  void GPUBuffer::destroy() { _instance.Destroy(); }
39
39
 
40
- async::AsyncTaskHandle GPUBuffer::mapAsync(uint64_t modeIn,
40
+ async::AsyncTaskHandle GPUBuffer::mapAsync(jsi::Runtime &runtime,
41
+ uint64_t modeIn,
41
42
  std::optional<uint64_t> offset,
42
43
  std::optional<uint64_t> size) {
43
44
  Convertor conv;
@@ -51,7 +52,16 @@ async::AsyncTaskHandle GPUBuffer::mapAsync(uint64_t modeIn,
51
52
  auto bufferHandle = _instance;
52
53
  uint64_t resolvedOffset = offset.value_or(0);
53
54
 
54
- return _async->postTask(
55
+ // Post to the CALLING runtime's context, not the one captured at buffer
56
+ // creation (_async): the buffer may have been created on another runtime and
57
+ // boxed across (e.g. device created on the main JS runtime, mapAsync called
58
+ // from a worklet). The returned Promise lives on the calling runtime, so it
59
+ // must be settled from that runtime's own thread — and postTask itself
60
+ // schedules the pump through its context's runtime (setTimeout), which is
61
+ // only safe for the runtime we are currently executing on.
62
+ auto context =
63
+ async::RuntimeContext::getOrCreate(runtime, _async->instance());
64
+ return context->postTask(
55
65
  [bufferHandle, mode, resolvedOffset,
56
66
  rangeSize](const async::AsyncTaskHandle::ResolveFunction &resolve,
57
67
  const async::AsyncTaskHandle::RejectFunction &reject) {
@@ -33,7 +33,7 @@ public:
33
33
  public:
34
34
  std::string getBrand() { return CLASS_NAME; }
35
35
 
36
- async::AsyncTaskHandle mapAsync(uint64_t modeIn,
36
+ async::AsyncTaskHandle mapAsync(jsi::Runtime &runtime, uint64_t modeIn,
37
37
  std::optional<uint64_t> offset,
38
38
  std::optional<uint64_t> size);
39
39
  std::shared_ptr<ArrayBuffer> getMappedRange(std::optional<size_t> offset,
@@ -53,7 +53,8 @@ public:
53
53
 
54
54
  static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
55
55
  installGetter(runtime, prototype, "__brand", &GPUBuffer::getBrand);
56
- installMethod(runtime, prototype, "mapAsync", &GPUBuffer::mapAsync);
56
+ installMethodWithRuntime(runtime, prototype, "mapAsync",
57
+ &GPUBuffer::mapAsync);
57
58
  installMethod(runtime, prototype, "getMappedRange",
58
59
  &GPUBuffer::getMappedRange);
59
60
  installMethod(runtime, prototype, "unmap", &GPUBuffer::unmap);
@@ -1,7 +1,10 @@
1
1
  #include "GPUCanvasContext.h"
2
2
  #include "Convertors.h"
3
3
  #include "RNWebGPUManager.h"
4
+ #include <algorithm>
4
5
  #include <memory>
6
+ #include <utility>
7
+ #include <vector>
5
8
 
6
9
  namespace rnwgpu {
7
10
 
@@ -10,37 +13,50 @@ void GPUCanvasContext::configure(
10
13
  Convertor conv;
11
14
  wgpu::SurfaceConfiguration surfaceConfiguration;
12
15
  surfaceConfiguration.device = configuration->device->get();
13
- if (configuration->viewFormats.has_value()) {
14
- if (!conv(surfaceConfiguration.viewFormats,
15
- surfaceConfiguration.viewFormatCount,
16
- configuration->viewFormats.value())) {
17
- throw std::runtime_error("Error with SurfaceConfiguration");
18
- }
19
- }
20
16
  if (!conv(surfaceConfiguration.usage, configuration->usage) ||
21
17
  !conv(surfaceConfiguration.format, configuration->format)) {
22
18
  throw std::runtime_error("Error with SurfaceConfiguration");
23
19
  }
20
+ // viewFormats are deep-copied into SurfaceInfo (which outlives this call);
21
+ // Convertor-allocated arrays would dangle.
22
+ std::vector<wgpu::TextureFormat> viewFormats;
23
+ if (configuration->viewFormats.has_value()) {
24
+ viewFormats = configuration->viewFormats.value();
25
+ }
24
26
 
25
27
  #ifdef __APPLE__
26
28
  surfaceConfiguration.alphaMode = configuration->alphaMode;
27
29
  #endif
28
30
  surfaceConfiguration.presentMode = wgpu::PresentMode::Fifo;
29
- _surfaceInfo->configure(surfaceConfiguration);
31
+ _surfaceInfo->configure(surfaceConfiguration, std::move(viewFormats));
30
32
  }
31
33
 
32
- void GPUCanvasContext::unconfigure() {}
34
+ void GPUCanvasContext::unconfigure() { _surfaceInfo->unconfigure(); }
33
35
 
34
36
  std::shared_ptr<GPUTexture> GPUCanvasContext::getCurrentTexture() {
37
+ if (!_surfaceInfo->isConfigured()) {
38
+ // Web parity: on the web this is an InvalidStateError, not a crash.
39
+ throw std::runtime_error(
40
+ "[WebGPU] getCurrentTexture() called on a canvas context that is not "
41
+ "configured; call context.configure() first");
42
+ }
43
+ // The drawing buffer tracks canvas.width/height (like on the web); resize it
44
+ // lazily when they changed. Sizes are clamped to 1 so a canvas that has not
45
+ // been laid out yet (0x0) keeps working.
35
46
  auto prevSize = _surfaceInfo->getConfig();
36
- auto width = _canvas->getWidth();
37
- auto height = _canvas->getHeight();
38
- auto sizeHasChanged = prevSize.width != width || prevSize.height != height;
47
+ auto width = std::max(1, _canvas->getWidth());
48
+ auto height = std::max(1, _canvas->getHeight());
49
+ auto sizeHasChanged = prevSize.width != static_cast<uint32_t>(width) ||
50
+ prevSize.height != static_cast<uint32_t>(height);
39
51
  if (sizeHasChanged) {
40
52
  _surfaceInfo->reconfigure(width, height);
41
53
  }
42
54
 
43
55
  auto texture = _surfaceInfo->getCurrentTexture();
56
+ if (texture == nullptr) {
57
+ throw std::runtime_error(
58
+ "[WebGPU] getCurrentTexture() failed to acquire a texture");
59
+ }
44
60
 
45
61
  auto size = _surfaceInfo->getSize();
46
62
  _canvas->setClientWidth(size.width);
@@ -52,13 +68,11 @@ std::shared_ptr<GPUTexture> GPUCanvasContext::getCurrentTexture() {
52
68
  }
53
69
 
54
70
  void GPUCanvasContext::present() {
55
- // Present runs synchronously on the calling thread (the one that did
56
- // getCurrentTexture / submit), preserving Dawn surface thread-affinity.
57
- // Required on every runtime (main JS, Reanimated UI, dedicated worklet);
58
- // offscreen surfaces have no wgpu::Surface so they no-op.
59
- if (_surfaceInfo->hasSurface()) {
60
- _surfaceInfo->presentFrame();
61
- }
71
+ // presentFrame() is the end-of-frame boundary: it presents when this frame's
72
+ // texture was acquired from the on-screen surface (offscreen and dropped
73
+ // frames are skipped), clears the frame state, and adopts any surface that
74
+ // attached while the frame was in flight.
75
+ _surfaceInfo->presentFrame();
62
76
  }
63
77
 
64
78
  } // namespace rnwgpu
@@ -57,7 +57,9 @@ public:
57
57
  std::shared_ptr<GPUTexture> getCurrentTexture();
58
58
  // Present is explicit on every runtime (main JS, Reanimated UI, and dedicated
59
59
  // worklet runtimes). It runs synchronously on the calling thread, preserving
60
- // Dawn surface thread-affinity; offscreen surfaces no-op.
60
+ // Dawn surface thread-affinity; frames rendered offscreen (no surface
61
+ // attached) are skipped. It is also the end-of-frame boundary at which a
62
+ // newly attached native surface is adopted (see SurfaceInfo).
61
63
  void present();
62
64
 
63
65
  private:
@@ -348,6 +348,7 @@ std::shared_ptr<GPUSharedFence> GPUDevice::importSharedFence(
348
348
  }
349
349
 
350
350
  async::AsyncTaskHandle GPUDevice::createComputePipelineAsync(
351
+ jsi::Runtime &runtime,
351
352
  std::shared_ptr<GPUComputePipelineDescriptor> descriptor) {
352
353
  wgpu::ComputePipelineDescriptor desc{};
353
354
  Convertor conv;
@@ -360,12 +361,16 @@ async::AsyncTaskHandle GPUDevice::createComputePipelineAsync(
360
361
  descriptor->label.has_value() ? descriptor->label.value() : "");
361
362
  auto pipelineHolder = std::make_shared<GPUComputePipeline>(nullptr, label);
362
363
 
363
- return _async->postTask([device = _instance, desc, descriptor,
364
- pipelineHolder](
365
- const async::AsyncTaskHandle::ResolveFunction
366
- &resolve,
367
- const async::AsyncTaskHandle::RejectFunction
368
- &reject) {
364
+ // Post to the CALLING runtime's context so the promise settles on the
365
+ // thread that requested it (see GPUBuffer::mapAsync).
366
+ auto context =
367
+ async::RuntimeContext::getOrCreate(runtime, _async->instance());
368
+ return context->postTask([device = _instance, desc, descriptor,
369
+ pipelineHolder](
370
+ const async::AsyncTaskHandle::ResolveFunction
371
+ &resolve,
372
+ const async::AsyncTaskHandle::RejectFunction
373
+ &reject) {
369
374
  (void)descriptor;
370
375
  device.CreateComputePipelineAsync(
371
376
  &desc, wgpu::CallbackMode::AllowProcessEvents,
@@ -389,6 +394,7 @@ async::AsyncTaskHandle GPUDevice::createComputePipelineAsync(
389
394
  }
390
395
 
391
396
  async::AsyncTaskHandle GPUDevice::createRenderPipelineAsync(
397
+ jsi::Runtime &runtime,
392
398
  std::shared_ptr<GPURenderPipelineDescriptor> descriptor) {
393
399
  wgpu::RenderPipelineDescriptor desc{};
394
400
  Convertor conv;
@@ -402,12 +408,16 @@ async::AsyncTaskHandle GPUDevice::createRenderPipelineAsync(
402
408
  descriptor->label.has_value() ? descriptor->label.value() : "");
403
409
  auto pipelineHolder = std::make_shared<GPURenderPipeline>(nullptr, label);
404
410
 
405
- return _async->postTask([device = _instance, desc, descriptor,
406
- pipelineHolder](
407
- const async::AsyncTaskHandle::ResolveFunction
408
- &resolve,
409
- const async::AsyncTaskHandle::RejectFunction
410
- &reject) {
411
+ // Post to the CALLING runtime's context so the promise settles on the
412
+ // thread that requested it (see GPUBuffer::mapAsync).
413
+ auto context =
414
+ async::RuntimeContext::getOrCreate(runtime, _async->instance());
415
+ return context->postTask([device = _instance, desc, descriptor,
416
+ pipelineHolder](
417
+ const async::AsyncTaskHandle::ResolveFunction
418
+ &resolve,
419
+ const async::AsyncTaskHandle::RejectFunction
420
+ &reject) {
411
421
  (void)descriptor;
412
422
  device.CreateRenderPipelineAsync(
413
423
  &desc, wgpu::CallbackMode::AllowProcessEvents,
@@ -433,13 +443,18 @@ void GPUDevice::pushErrorScope(wgpu::ErrorFilter filter) {
433
443
  _instance.PushErrorScope(filter);
434
444
  }
435
445
 
436
- async::AsyncTaskHandle GPUDevice::popErrorScope() {
446
+ async::AsyncTaskHandle GPUDevice::popErrorScope(jsi::Runtime &runtime) {
437
447
  auto device = _instance;
438
448
 
439
- return _async->postTask([device](const async::AsyncTaskHandle::ResolveFunction
440
- &resolve,
441
- const async::AsyncTaskHandle::RejectFunction
442
- &reject) {
449
+ // Post to the CALLING runtime's context so the promise settles on the
450
+ // thread that requested it (see GPUBuffer::mapAsync).
451
+ auto context =
452
+ async::RuntimeContext::getOrCreate(runtime, _async->instance());
453
+ return context->postTask([device](
454
+ const async::AsyncTaskHandle::ResolveFunction
455
+ &resolve,
456
+ const async::AsyncTaskHandle::RejectFunction
457
+ &reject) {
443
458
  device.PopErrorScope(
444
459
  wgpu::CallbackMode::AllowProcessEvents,
445
460
  [resolve, reject](wgpu::PopErrorScopeStatus status,
@@ -136,8 +136,10 @@ public:
136
136
  std::shared_ptr<GPURenderPipeline>
137
137
  createRenderPipeline(std::shared_ptr<GPURenderPipelineDescriptor> descriptor);
138
138
  async::AsyncTaskHandle createComputePipelineAsync(
139
+ jsi::Runtime &runtime,
139
140
  std::shared_ptr<GPUComputePipelineDescriptor> descriptor);
140
141
  async::AsyncTaskHandle createRenderPipelineAsync(
142
+ jsi::Runtime &runtime,
141
143
  std::shared_ptr<GPURenderPipelineDescriptor> descriptor);
142
144
  std::shared_ptr<GPUCommandEncoder> createCommandEncoder(
143
145
  std::optional<std::shared_ptr<GPUCommandEncoderDescriptor>> descriptor);
@@ -146,7 +148,7 @@ public:
146
148
  std::shared_ptr<GPUQuerySet>
147
149
  createQuerySet(std::shared_ptr<GPUQuerySetDescriptor> descriptor);
148
150
  void pushErrorScope(wgpu::ErrorFilter filter);
149
- async::AsyncTaskHandle popErrorScope();
151
+ async::AsyncTaskHandle popErrorScope(jsi::Runtime &runtime);
150
152
 
151
153
  std::unordered_set<std::string> getFeatures();
152
154
  std::shared_ptr<GPUSupportedLimits> getLimits();
@@ -192,10 +194,10 @@ public:
192
194
  &GPUDevice::createComputePipeline);
193
195
  installMethod(runtime, prototype, "createRenderPipeline",
194
196
  &GPUDevice::createRenderPipeline);
195
- installMethod(runtime, prototype, "createComputePipelineAsync",
196
- &GPUDevice::createComputePipelineAsync);
197
- installMethod(runtime, prototype, "createRenderPipelineAsync",
198
- &GPUDevice::createRenderPipelineAsync);
197
+ installMethodWithRuntime(runtime, prototype, "createComputePipelineAsync",
198
+ &GPUDevice::createComputePipelineAsync);
199
+ installMethodWithRuntime(runtime, prototype, "createRenderPipelineAsync",
200
+ &GPUDevice::createRenderPipelineAsync);
199
201
  installMethod(runtime, prototype, "createCommandEncoder",
200
202
  &GPUDevice::createCommandEncoder);
201
203
  installMethod(runtime, prototype, "createRenderBundleEncoder",
@@ -204,8 +206,8 @@ public:
204
206
  &GPUDevice::createQuerySet);
205
207
  installMethod(runtime, prototype, "pushErrorScope",
206
208
  &GPUDevice::pushErrorScope);
207
- installMethod(runtime, prototype, "popErrorScope",
208
- &GPUDevice::popErrorScope);
209
+ installMethodWithRuntime(runtime, prototype, "popErrorScope",
210
+ &GPUDevice::popErrorScope);
209
211
  installGetter(runtime, prototype, "features", &GPUDevice::getFeatures);
210
212
  installGetter(runtime, prototype, "limits", &GPUDevice::getLimits);
211
213
  installGetter(runtime, prototype, "queue", &GPUDevice::getQueue);
@@ -1,5 +1,6 @@
1
1
  #include "GPUExternalTexture.h"
2
2
 
3
+ #include <array>
3
4
  #include <cmath>
4
5
  #include <memory>
5
6
  #include <string>
@@ -56,44 +57,76 @@ static const float kIdentityTransferParams[7] = {
56
57
  0.0f, // F
57
58
  };
58
59
 
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, //
60
+ // Identity passthrough for GPUExternalTexture.yuvToRgbMatrix: the sampled
61
+ // texel is already RGB (Apple's biplanar path where Dawn converts in the
62
+ // sampler transform, RGBA surfaces, or an Android AHB whose driver reports
63
+ // RGB_IDENTITY). A 3x4 row-major matrix mapping [c.r, c.g, c.b, 1] to itself.
64
+ static constexpr std::array<float, 12> kYuvPassthroughMatrix = {
65
+ 1.0f, 0.0f, 0.0f, 0.0f, //
66
+ 0.0f, 1.0f, 0.0f, 0.0f, //
67
+ 0.0f, 0.0f, 1.0f, 0.0f, //
72
68
  };
73
69
 
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;
70
+ // Build a 3x4 row-major matrix mapping the *sampled* gamma-encoded
71
+ // [Y, Cb, Cr, 1] (each normalized to [0,1]) to gamma-encoded R'G'B', from the
72
+ // Vulkan-suggested YCbCr model + range of an Android external-format buffer.
73
+ // Needed because Dawn's opaque-YCbCr sampling path hard-codes an RGB_IDENTITY
74
+ // conversion (SamplerVk.cpp::GetYCbCrForTextureView, crbug.com/497675620), so
75
+ // the shader receives raw Y/Cb/Cr and must convert itself.
76
+ // The VkSamplerYcbcrModelConversion / VkSamplerYcbcrRange values are inlined
77
+ // to avoid a Vulkan header dependency in this cross-platform file.
78
+ [[maybe_unused]] static std::array<float, 12>
79
+ makeYuvToRgbMatrix(uint32_t vkModel, uint32_t vkRange) {
80
+ constexpr uint32_t kModelRgbIdentity = 0; // VK_..._RGB_IDENTITY
81
+ constexpr uint32_t kModel709 = 2; // VK_..._YCBCR_709
82
+ constexpr uint32_t kModel601 = 3; // VK_..._YCBCR_601
83
+ constexpr uint32_t kModel2020 = 4; // VK_..._YCBCR_2020
84
+ constexpr uint32_t kRangeItuNarrow = 1; // VK_SAMPLER_YCBCR_RANGE_ITU_NARROW
85
+
86
+ if (vkModel == kModelRgbIdentity) {
87
+ // The buffer content is RGB; nothing to convert.
88
+ return kYuvPassthroughMatrix;
89
+ }
90
+ float kr;
91
+ float kb;
92
+ switch (vkModel) {
93
+ case kModel709:
94
+ kr = 0.2126f;
95
+ kb = 0.0722f;
96
+ break;
97
+ case kModel2020:
98
+ kr = 0.2627f;
99
+ kb = 0.0593f;
100
+ break;
101
+ case kModel601:
87
102
  default:
88
- return false;
103
+ // YCBCR_IDENTITY and unknown models: Android camera streams are BT.601 in
104
+ // practice, so that is the sane default.
105
+ kr = 0.299f;
106
+ kb = 0.114f;
107
+ break;
89
108
  }
109
+ const float kg = 1.0f - kr - kb;
110
+ const bool narrow = vkRange == kRangeItuNarrow;
111
+ const float yScale = narrow ? 255.0f / 219.0f : 1.0f;
112
+ const float cScale = narrow ? 255.0f / 224.0f : 1.0f;
113
+ const float yOffset = narrow ? 16.0f / 255.0f : 0.0f;
114
+ const float cOffset = 128.0f / 255.0f;
115
+ const float crR = 2.0f * (1.0f - kr) * cScale;
116
+ const float cbG = -2.0f * kb * (1.0f - kb) / kg * cScale;
117
+ const float crG = -2.0f * kr * (1.0f - kr) / kg * cScale;
118
+ const float cbB = 2.0f * (1.0f - kb) * cScale;
119
+ return {
120
+ yScale, 0.0f, crR, -yScale * yOffset - crR * cOffset, //
121
+ yScale, cbG, crG, -yScale * yOffset - (cbG + crG) * cOffset, //
122
+ yScale, cbB, 0.0f, -yScale * yOffset - cbB * cOffset, //
123
+ };
90
124
  }
91
125
 
92
126
  // Map a rotation in degrees (0 / 90 / 180 / 270) to Dawn's enum. Anything that
93
127
  // isn't a clean multiple of 90 snaps to the nearest quadrant; Dawn only
94
128
  // supports those four steps for external textures.
95
- static wgpu::ExternalTextureRotation
96
- toExternalTextureRotation(double degrees) {
129
+ static wgpu::ExternalTextureRotation toExternalTextureRotation(double degrees) {
97
130
  int quadrant = static_cast<int>(std::lround(degrees / 90.0)) & 3;
98
131
  switch (quadrant) {
99
132
  case 1:
@@ -216,7 +249,11 @@ std::shared_ptr<GPUExternalTexture> GPUExternalTexture::Create(
216
249
 
217
250
  return std::make_shared<GPUExternalTexture>(
218
251
  std::move(external), std::move(memory), std::move(texture),
219
- std::move(descriptor->source), std::move(label));
252
+ std::move(descriptor->source), std::move(label),
253
+ // Dawn's Metal path applies the YUV->RGB conversion (from
254
+ // frame.yuvToRgbMatrix above) inside the sampling transform, so the
255
+ // sampled texel is already RGB.
256
+ kYuvPassthroughMatrix);
220
257
  #elif defined(__ANDROID__)
221
258
  // 1. Import the AHardwareBuffer as SharedTextureMemory. For YUV AHBs this
222
259
  // yields a Dawn texture in the implementation-defined OpaqueYCbCrAndroid
@@ -257,32 +294,39 @@ std::shared_ptr<GPUExternalTexture> GPUExternalTexture::Create(
257
294
  "GPUExternalTexture::Create(): BeginAccess failed");
258
295
  }
259
296
 
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());
297
+ // 4. Resolve the YUV->RGB conversion the *shader* must apply. Dawn imports
298
+ // every YUV / implementation-defined AHB format as OpaqueYCbCrAndroid
299
+ // (AHBFunctions.cpp::FormatFromAHardwareBufferFormat) and samples it
300
+ // through a Vulkan SamplerYcbcrConversion that is hard-coded to
301
+ // RGB_IDENTITY (SamplerVk.cpp::GetYCbCrForTextureView,
302
+ // crbug.com/497675620), so the sample always comes back as raw
303
+ // [Y, Cb, Cr]. The driver's *suggested* model + range are captured at
304
+ // import time though; read them back from the shared memory's properties
305
+ // and derive the correct conversion matrix, exposed to JS as
306
+ // GPUExternalTexture.yuvToRgbMatrix. Defined color formats (e.g. an RGBA
307
+ // AHB -> RGBA8Unorm) sample as RGB directly and get the passthrough.
308
+ std::array<float, 12> yuvToRgbMatrix = kYuvPassthroughMatrix;
309
+ if (texture.GetFormat() == wgpu::TextureFormat::OpaqueYCbCrAndroid) {
310
+ wgpu::SharedTextureMemoryAHardwareBufferProperties ahbProps{};
311
+ wgpu::SharedTextureMemoryProperties props{};
312
+ props.nextInChain = &ahbProps;
313
+ if (memory.GetProperties(&props)) {
314
+ yuvToRgbMatrix = makeYuvToRgbMatrix(ahbProps.yCbCrInfo.vkYCbCrModel,
315
+ ahbProps.yCbCrInfo.vkYCbCrRange);
316
+ } else {
317
+ // Fall back to the Android camera norm rather than passthrough.
318
+ yuvToRgbMatrix = makeYuvToRgbMatrix(/* YCBCR_601 */ 3,
319
+ /* ITU_NARROW */ 1);
320
+ }
321
+ }
283
322
 
284
- wgpu::TextureView plane0;
285
- wgpu::TextureView plane1;
323
+ // 5. Build the ExternalTextureDescriptor: a single (opaque or RGB) plane
324
+ // with identity transfer. The YUV->RGB conversion cannot run inside
325
+ // Dawn's external-texture transform here: the single-plane branch of the
326
+ // Tint transform ignores yuvToRgbConversionMatrix, which is why the
327
+ // matrix is surfaced to the shader instead. The gamut/transfer arrays
328
+ // must still be non-null: ComputeExternalTextureParams dereferences them
329
+ // unconditionally.
286
330
  wgpu::ExternalTextureDescriptor extDesc{};
287
331
  if (!label.empty()) {
288
332
  extDesc.label = wgpu::StringView(label.c_str(), label.size());
@@ -291,24 +335,10 @@ std::shared_ptr<GPUExternalTexture> GPUExternalTexture::Create(
291
335
  extDesc.cropSize = {frame.width, frame.height};
292
336
  extDesc.apparentSize = {frame.width, frame.height};
293
337
  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
- }
338
+ wgpu::TextureView plane0 = texture.CreateView();
339
+ extDesc.plane0 = plane0;
340
+ extDesc.srcTransferFunctionParameters = kIdentityTransferParams;
341
+ extDesc.dstTransferFunctionParameters = kIdentityTransferParams;
312
342
  extDesc.mirrored = descriptor->mirrored.value_or(false);
313
343
  extDesc.rotation =
314
344
  toExternalTextureRotation(descriptor->rotation.value_or(0));
@@ -324,7 +354,8 @@ std::shared_ptr<GPUExternalTexture> GPUExternalTexture::Create(
324
354
 
325
355
  return std::make_shared<GPUExternalTexture>(
326
356
  std::move(external), std::move(memory), std::move(texture),
327
- std::move(descriptor->source), std::move(label));
357
+ std::move(descriptor->source), std::move(label),
358
+ std::move(yuvToRgbMatrix));
328
359
  #else
329
360
  throw std::runtime_error(
330
361
  "GPUExternalTexture::Create(): not yet implemented on this "