react-native-webgpu 0.16.1 → 0.19.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 (78) hide show
  1. package/android/cpp/AndroidPlatformContext.h +8 -0
  2. package/android/src/main/java/com/webgpu/BackingViewKind.java +38 -0
  3. package/android/src/main/java/com/webgpu/WebGPUSurfaceView.java +19 -2
  4. package/android/src/main/java/com/webgpu/WebGPUView.java +78 -76
  5. package/android/src/main/java/com/webgpu/WebGPUViewManager.java +13 -6
  6. package/apple/ApplePlatformContext.h +2 -0
  7. package/apple/ApplePlatformContext.mm +12 -0
  8. package/apple/MetalLayerColorSpace.mm +20 -8
  9. package/cpp/jsi/NativeObject.h +42 -32
  10. package/cpp/rnwgpu/CanvasColorSpace.h +9 -0
  11. package/cpp/rnwgpu/PlatformContext.h +6 -0
  12. package/cpp/rnwgpu/RNWebGPUManager.cpp +5 -8
  13. package/cpp/rnwgpu/SurfaceRegistry.h +101 -30
  14. package/cpp/rnwgpu/WebGPURuntime.cpp +30 -11
  15. package/cpp/rnwgpu/WebGPURuntime.h +7 -2
  16. package/cpp/rnwgpu/api/GPUCanvasContext.cpp +2 -1
  17. package/cpp/rnwgpu/api/GPUCanvasContext.h +10 -0
  18. package/cpp/rnwgpu/api/GPUFeatures.h +1 -0
  19. package/cpp/rnwgpu/api/RNWebGPU.h +63 -5
  20. package/cpp/rnwgpu/api/VideoFrame.h +2 -2
  21. package/cpp/rnwgpu/api/descriptors/GPUCanvasConfiguration.h +18 -0
  22. package/cpp/rnwgpu/async/RuntimeContext.cpp +43 -27
  23. package/cpp/rnwgpu/async/RuntimeContext.h +19 -11
  24. package/lib/commonjs/Canvas.js +88 -43
  25. package/lib/commonjs/Canvas.js.map +1 -1
  26. package/lib/commonjs/WebGPUViewNativeComponent.js.map +1 -1
  27. package/lib/commonjs/WebGPUViewNativeComponent.web.js +3 -5
  28. package/lib/commonjs/WebGPUViewNativeComponent.web.js.map +1 -1
  29. package/lib/commonjs/WebPolyfillGPUModule.js +2 -0
  30. package/lib/commonjs/WebPolyfillGPUModule.js.map +1 -1
  31. package/lib/commonjs/importDevice.js +3 -2
  32. package/lib/commonjs/importDevice.js.map +1 -1
  33. package/lib/commonjs/mock.js +0 -1
  34. package/lib/commonjs/mock.js.map +1 -1
  35. package/lib/module/Canvas.js +88 -43
  36. package/lib/module/Canvas.js.map +1 -1
  37. package/lib/module/WebGPUViewNativeComponent.js.map +1 -1
  38. package/lib/module/WebGPUViewNativeComponent.web.js +3 -5
  39. package/lib/module/WebGPUViewNativeComponent.web.js.map +1 -1
  40. package/lib/module/WebPolyfillGPUModule.js +2 -0
  41. package/lib/module/WebPolyfillGPUModule.js.map +1 -1
  42. package/lib/module/importDevice.js +3 -2
  43. package/lib/module/importDevice.js.map +1 -1
  44. package/lib/module/mock.js +0 -1
  45. package/lib/module/mock.js.map +1 -1
  46. package/lib/typescript/lib/commonjs/Canvas.d.ts +1 -2
  47. package/lib/typescript/lib/commonjs/Canvas.d.ts.map +1 -1
  48. package/lib/typescript/lib/commonjs/WebGPUViewNativeComponent.web.d.ts.map +1 -1
  49. package/lib/typescript/lib/commonjs/importDevice.d.ts +3 -2
  50. package/lib/typescript/lib/commonjs/importDevice.d.ts.map +1 -1
  51. package/lib/typescript/lib/commonjs/mock.d.ts +1 -2
  52. package/lib/typescript/lib/commonjs/mock.d.ts.map +1 -1
  53. package/lib/typescript/lib/module/Canvas.d.ts +1 -2
  54. package/lib/typescript/lib/module/Canvas.d.ts.map +1 -1
  55. package/lib/typescript/lib/module/WebGPUViewNativeComponent.web.d.ts.map +1 -1
  56. package/lib/typescript/lib/module/importDevice.d.ts.map +1 -1
  57. package/lib/typescript/lib/module/mock.d.ts +1 -2
  58. package/lib/typescript/lib/module/mock.d.ts.map +1 -1
  59. package/lib/typescript/src/Canvas.d.ts +61 -29
  60. package/lib/typescript/src/Canvas.d.ts.map +1 -1
  61. package/lib/typescript/src/WebGPUViewNativeComponent.d.ts +3 -2
  62. package/lib/typescript/src/WebGPUViewNativeComponent.d.ts.map +1 -1
  63. package/lib/typescript/src/WebGPUViewNativeComponent.web.d.ts +3 -2
  64. package/lib/typescript/src/WebGPUViewNativeComponent.web.d.ts.map +1 -1
  65. package/lib/typescript/src/importDevice.d.ts +3 -2
  66. package/lib/typescript/src/importDevice.d.ts.map +1 -1
  67. package/lib/typescript/src/index.d.ts +2 -0
  68. package/lib/typescript/src/index.d.ts.map +1 -1
  69. package/lib/typescript/src/mock.d.ts +2 -2
  70. package/lib/typescript/src/mock.d.ts.map +1 -1
  71. package/package.json +3 -3
  72. package/src/Canvas.tsx +156 -81
  73. package/src/WebGPUViewNativeComponent.ts +6 -4
  74. package/src/WebGPUViewNativeComponent.web.ts +7 -5
  75. package/src/WebPolyfillGPUModule.ts +3 -0
  76. package/src/importDevice.ts +3 -2
  77. package/src/index.tsx +15 -4
  78. package/src/mock.ts +5 -7
@@ -19,6 +19,9 @@
19
19
 
20
20
  #include "webgpu/webgpu_cpp.h"
21
21
 
22
+ #include "CanvasColorSpace.h"
23
+ #include "WebGPURuntime.h"
24
+
22
25
  #if defined(__ANDROID__)
23
26
  #include <android/hardware_buffer.h>
24
27
  #include <android/log.h>
@@ -36,9 +39,11 @@ namespace rnwgpu {
36
39
 
37
40
  #ifdef __APPLE__
38
41
  // Tags the CAMetalLayer with the colorspace matching the configured texture
39
- // format. Implemented in apple/MetalLayerColorSpace.mm.
42
+ // format and the canvas's colorSpace. Implemented in
43
+ // apple/MetalLayerColorSpace.mm.
40
44
  void applyCAMetalLayerColorSpace(void *nativeSurface,
41
- wgpu::TextureFormat format);
45
+ wgpu::TextureFormat format,
46
+ CanvasColorSpace colorSpace);
42
47
  #endif
43
48
 
44
49
  struct NativeInfo {
@@ -123,12 +128,13 @@ struct AHBPool {
123
128
  // Ownership & threading model:
124
129
  // - A registry entry is created on first use (by whichever of JS/native gets
125
130
  // there first) and lives exactly as long as its JS Canvas: contextIds are
126
- // never reused. It is removed by the native view's teardown (MetalView
127
- // dealloc / WebGPUViewManager.onDropViewInstance) when a surface is
128
- // attached, or by RNWebGPU.destroyContext (the Canvas unmount cleanup) when
129
- // none ever was — see RNWebGPU::destroyContext for why the split. Surface
130
- // destruction alone (backgrounding, TextureView teardown) never removes an
131
- // entry; it only detaches the surface.
131
+ // never reused, not even across a reload (see allocateContextId). It is
132
+ // removed by the native view's teardown (MetalView dealloc /
133
+ // WebGPUViewManager.onDropViewInstance) when a surface is attached, or by
134
+ // RNWebGPU.destroyContext (the Canvas unmount cleanup) when none ever was —
135
+ // see RNWebGPU::destroyContext for why the split. Surface destruction alone
136
+ // (backgrounding, TextureView teardown) never removes an entry; it only
137
+ // detaches the surface.
132
138
  // - Attaching a surface is LATCHED: the UI thread stores it as pending
133
139
  // (attachSurface) and it is adopted at the next frame boundary — start of
134
140
  // getCurrentTexture or end of presentFrame — on whichever thread renders
@@ -147,8 +153,20 @@ struct AHBPool {
147
153
  // native surface exists.
148
154
  class SurfaceInfo {
149
155
  public:
150
- SurfaceInfo(wgpu::Instance gpu, int width, int height)
151
- : _gpu(std::move(gpu)), _width(width), _height(height) {}
156
+ // releaseSurfaceOnUnconfigure is for a surface on a window the caller
157
+ // handed in (see SurfaceRegistry::makeUnregisteredSurfaceInfo), which no
158
+ // view attaches or detaches: unconfigure() then destroys the Dawn surface
159
+ // rather than only unconfiguring it, and configure() makes a new one for
160
+ // the same window. On Android, a window keeps the buffers it handed to the
161
+ // swapchain until the VkSurfaceKHR is destroyed, which Surface::Unconfigure
162
+ // does not do (it parks the swapchain for the next Configure): a MediaCodec
163
+ // input surface would otherwise keep them until the context is garbage
164
+ // collected. The window itself is retained until the SurfaceInfo is
165
+ // destroyed.
166
+ SurfaceInfo(wgpu::Instance gpu, int width, int height,
167
+ bool releaseSurfaceOnUnconfigure = false)
168
+ : _gpu(std::move(gpu)), _width(width), _height(height),
169
+ _releaseSurfaceOnUnconfigure(releaseSurfaceOnUnconfigure) {}
152
170
 
153
171
  ~SurfaceInfo() {
154
172
  #if defined(__ANDROID__)
@@ -279,7 +297,7 @@ public:
279
297
  }
280
298
  configureSurfaceLocked(config);
281
299
  #ifdef __APPLE__
282
- applyCAMetalLayerColorSpace(_nativeSurface, _config.format);
300
+ applyCAMetalLayerColorSpace(_nativeSurface, _config.format, _colorSpace);
283
301
  #endif
284
302
  if (blit) {
285
303
  presentBlit = blitOffscreenToSurfaceLocked();
@@ -316,10 +334,12 @@ public:
316
334
  // -------------------------------------------------------
317
335
 
318
336
  void configure(wgpu::SurfaceConfiguration &newConfig,
319
- std::vector<wgpu::TextureFormat> viewFormats) {
337
+ std::vector<wgpu::TextureFormat> viewFormats,
338
+ CanvasColorSpace colorSpace = CanvasColorSpace::SRGB) {
320
339
  applyPendingAttach(/* supersedeInFlightFrame = */ true);
321
340
  std::unique_lock<std::shared_mutex> lock(_mutex);
322
341
  _viewFormats = std::move(viewFormats);
342
+ _colorSpace = colorSpace;
323
343
  _config = newConfig;
324
344
  // The caller's viewFormats storage dies with the call; point the stored
325
345
  // configuration at our own copy.
@@ -346,6 +366,10 @@ public:
346
366
  _poolUnsupported = false;
347
367
  }
348
368
  #endif
369
+ if (_releaseSurfaceOnUnconfigure && !_surface && _nativeSurface) {
370
+ // unconfigure() destroyed the surface; make a new one for the window.
371
+ recreateSurfaceLocked();
372
+ }
349
373
  _configureLocked();
350
374
  }
351
375
 
@@ -365,10 +389,22 @@ public:
365
389
  void unconfigure() {
366
390
  std::unique_lock<std::shared_mutex> lock(_mutex);
367
391
  if (_surface) {
368
- _surface.Unconfigure();
392
+ if (_releaseSurfaceOnUnconfigure) {
393
+ // Destroy the surface, and so its swapchain, while their device is
394
+ // still alive. The window is kept: configure() makes a new surface
395
+ // for it (see the constructor).
396
+ _surface = nullptr;
397
+ #if defined(__ANDROID__)
398
+ // DAWN_WORKAROUND_DEVICE_DESTROY_BEFORE_SURFACE_RELEASE
399
+ _surfaceDevice = nullptr;
400
+ #endif
401
+ } else {
402
+ _surface.Unconfigure();
403
+ }
369
404
  }
370
405
  _texture = nullptr;
371
406
  _config = {};
407
+ _colorSpace = CanvasColorSpace::SRGB;
372
408
  _viewFormats.clear();
373
409
  _acquiredFromSurface = false;
374
410
  _frameEpoch++;
@@ -875,7 +911,7 @@ private:
875
911
  if (_surface) {
876
912
  configureSurfaceLocked(_config);
877
913
  #ifdef __APPLE__
878
- applyCAMetalLayerColorSpace(_nativeSurface, _config.format);
914
+ applyCAMetalLayerColorSpace(_nativeSurface, _config.format, _colorSpace);
879
915
  #endif
880
916
  } else {
881
917
  _texture = createOffscreenTextureLocked();
@@ -906,26 +942,24 @@ private:
906
942
  #endif
907
943
  }
908
944
 
909
- #if defined(__ANDROID__)
910
- // DAWN_WORKAROUND_DEVICE_DESTROY_BEFORE_SURFACE_RELEASE
911
945
  // Replace _surface with a new, unconfigured surface for the same native
912
- // window. Dropping the old one runs ~Surface, which detaches both the
913
- // active and the recycled swapchain against their still-alive device.
946
+ // window: after unconfigure() destroyed it (see the constructor), or on
947
+ // Android for DAWN_WORKAROUND_DEVICE_DESTROY_BEFORE_SURFACE_RELEASE, where
948
+ // dropping the old one runs ~Surface, which detaches both the active and
949
+ // the recycled swapchain against their still-alive device.
914
950
  void recreateSurfaceLocked() {
915
951
  _surface = nullptr;
952
+ #if defined(__ANDROID__)
953
+ // DAWN_WORKAROUND_DEVICE_DESTROY_BEFORE_SURFACE_RELEASE
916
954
  _surfaceDevice = nullptr;
955
+ #endif
917
956
  // A frame acquired from the old surface can no longer be presented.
918
957
  _acquiredFromSurface = false;
919
958
  _frameEpoch++;
920
959
  if (_nativeSurface) {
921
- wgpu::SurfaceSourceAndroidNativeWindow source;
922
- source.window = _nativeSurface;
923
- wgpu::SurfaceDescriptor descriptor;
924
- descriptor.nextInChain = &source;
925
- _surface = _gpu.CreateSurface(&descriptor);
960
+ _surface = WebGPURuntime::makeSurface(_gpu, _nativeSurface);
926
961
  }
927
962
  }
928
- #endif
929
963
 
930
964
  #if defined(__ANDROID__)
931
965
  // Drop every pool generation and the ready / in-flight slot bookkeeping.
@@ -1241,12 +1275,15 @@ private:
1241
1275
  // that _config.viewFormats points at.
1242
1276
  wgpu::SurfaceConfiguration _config;
1243
1277
  std::vector<wgpu::TextureFormat> _viewFormats;
1278
+ CanvasColorSpace _colorSpace = CanvasColorSpace::SRGB;
1244
1279
  // Keeps the Dawn instance alive for as long as any canvas exists.
1245
1280
  wgpu::Instance _gpu;
1246
1281
  // Native view size in dp (surfaced as clientWidth/clientHeight on the JS
1247
1282
  // canvas).
1248
1283
  int _width;
1249
1284
  int _height;
1285
+ // See the constructor.
1286
+ bool _releaseSurfaceOnUnconfigure = false;
1250
1287
 
1251
1288
  #if defined(__ANDROID__)
1252
1289
  // Pool state. Guarded by _poolMutex (never nested under _mutex while
@@ -1296,6 +1333,15 @@ public:
1296
1333
  SurfaceRegistry(const SurfaceRegistry &) = delete;
1297
1334
  SurfaceRegistry &operator=(const SurfaceRegistry &) = delete;
1298
1335
 
1336
+ // The id of a Canvas about to mount. Ids are handed out here, once for the
1337
+ // life of the process, rather than counted by each JS runtime from the
1338
+ // same start: this registry outlives a reload, and the views of the bundle
1339
+ // being replaced are torn down (removeSurfaceInfo) on the UI thread with
1340
+ // no ordering against the new bundle's first canvases. An id counted from
1341
+ // the same start as its predecessor would let the predecessor's teardown
1342
+ // detach and erase the entry of a live canvas.
1343
+ int allocateContextId() { return _nextContextId.fetch_add(1); }
1344
+
1299
1345
  std::shared_ptr<SurfaceInfo> getSurfaceInfo(int id) {
1300
1346
  std::shared_lock<std::shared_mutex> lock(_mutex);
1301
1347
  auto it = _registry.find(id);
@@ -1357,6 +1403,19 @@ public:
1357
1403
  _registry.erase(it);
1358
1404
  }
1359
1405
 
1406
+ // A SurfaceInfo that is not in the registry, for a context presenting to a
1407
+ // native window of the caller's (see
1408
+ // RNWebGPU::MakeWebGPUCanvasContextFromNativeWindow): the context owns it,
1409
+ // no view attaches to it, and it releases its surface on unconfigure (see
1410
+ // the SurfaceInfo constructor). It is still tracked for
1411
+ // releaseSurfacesForDevice.
1412
+ std::shared_ptr<SurfaceInfo>
1413
+ makeUnregisteredSurfaceInfo(wgpu::Instance gpu, int width, int height) {
1414
+ std::unique_lock<std::shared_mutex> lock(_mutex);
1415
+ return makeSurfaceInfoLocked(std::move(gpu), width, height,
1416
+ /* releaseSurfaceOnUnconfigure = */ true);
1417
+ }
1418
+
1360
1419
  // DAWN_WORKAROUND_DEVICE_DESTROY_BEFORE_SURFACE_RELEASE
1361
1420
  // See SurfaceInfo::releaseSurfaceForDevice. Lock order is registry ->
1362
1421
  // SurfaceInfo, matching every other path.
@@ -1382,8 +1441,9 @@ public:
1382
1441
  }
1383
1442
 
1384
1443
  // Drops all entries. Called when the RN instance tears down (dev reload):
1385
- // JS context ids restart from scratch, so surviving entries would alias new
1386
- // canvases onto dead surfaces.
1444
+ // the canvases of the old bundle never run their JS cleanup, and nothing
1445
+ // reaches their ids again (see allocateContextId), so the entries would
1446
+ // otherwise stay for the life of the process.
1387
1447
  void clear() {
1388
1448
  std::unique_lock<std::shared_mutex> lock(_mutex);
1389
1449
  _registry.clear();
@@ -1410,15 +1470,25 @@ private:
1410
1470
  _retired.erase(retired);
1411
1471
  }
1412
1472
  if (!info) {
1413
- info = std::make_shared<SurfaceInfo>(gpu, width, height);
1414
- // DAWN_WORKAROUND_DEVICE_DESTROY_BEFORE_SURFACE_RELEASE
1415
- pruneAllSurfacesLocked();
1416
- _allSurfaces.push_back(info);
1473
+ info = makeSurfaceInfoLocked(std::move(gpu), width, height);
1417
1474
  }
1418
1475
  _registry[id] = info;
1419
1476
  return info;
1420
1477
  }
1421
1478
 
1479
+ // Every SurfaceInfo is made here, so releaseSurfacesForDevice reaches it
1480
+ // whether or not it is in _registry.
1481
+ std::shared_ptr<SurfaceInfo>
1482
+ makeSurfaceInfoLocked(wgpu::Instance gpu, int width, int height,
1483
+ bool releaseSurfaceOnUnconfigure = false) {
1484
+ auto info = std::make_shared<SurfaceInfo>(std::move(gpu), width, height,
1485
+ releaseSurfaceOnUnconfigure);
1486
+ // DAWN_WORKAROUND_DEVICE_DESTROY_BEFORE_SURFACE_RELEASE
1487
+ pruneAllSurfacesLocked();
1488
+ _allSurfaces.push_back(info);
1489
+ return info;
1490
+ }
1491
+
1422
1492
  void pruneRetiredLocked() {
1423
1493
  for (auto it = _retired.begin(); it != _retired.end();) {
1424
1494
  it = it->second.expired() ? _retired.erase(it) : ++it;
@@ -1436,6 +1506,7 @@ private:
1436
1506
  }
1437
1507
 
1438
1508
  mutable std::shared_mutex _mutex;
1509
+ std::atomic<int> _nextContextId{1};
1439
1510
  std::unordered_map<int, std::shared_ptr<SurfaceInfo>> _registry;
1440
1511
  // The entries JS retired while a GPUCanvasContext still held their
1441
1512
  // SurfaceInfo (see removeSurfaceInfoIfDetached). Non-owning; pruned on
@@ -9,18 +9,32 @@ namespace rnwgpu {
9
9
  namespace {
10
10
 
11
11
  wgpu::Instance createInstance() {
12
- // If a Graphite build of react-native-skia is loaded in this
12
+ // If a Graphite build of react-native-skia is already loaded in this
13
13
  // process, adopt its wgpu::Instance instead of creating our own, so
14
- // Graphite's device and every JS-created device live on one instance. That
15
- // makes pointer-based handoff (RNWebGPU.importDevice of Skia's device)
16
- // sound: the async pump serves the shared instance, so callbacks on
17
- // imported devices settle. Discovered at runtime via dlsym so there is no
18
- // build-time coupling and native-module init order does not matter. Both
19
- // packages must link the same single Dawn copy (they vendor the same
20
- // artifact) for the handle to be meaningful.
14
+ // Graphite's device and every JS-created device live on one instance.
15
+ // Discovered at runtime via dlsym so there is no build-time coupling. The
16
+ // adoption is best-effort and decided once, when the instance is first
17
+ // used: on Android it only succeeds when librnskia.so is loaded before
18
+ // react-native-webgpu installs (see the RTLD_NOLOAD lookup below), so the
19
+ // native-module init order decides whether the instance is shared.
20
+ // RNWebGPU.importDevice does not depend on it: an imported device's async
21
+ // work is pumped on the instance that owns it (see async::RuntimeContext).
22
+ // Both packages must link the same single Dawn copy (they vendor the same
23
+ // artifact) for the handles they exchange to be meaningful.
21
24
  using GetInstanceFn = WGPUInstance (*)();
22
- if (auto getSkiaInstance = reinterpret_cast<GetInstanceFn>(
23
- dlsym(RTLD_DEFAULT, "rnskia_getWGPUInstance"))) {
25
+ void *getter = dlsym(RTLD_DEFAULT, "rnskia_getWGPUInstance");
26
+ #ifdef __ANDROID__
27
+ // System.loadLibrary loads librnskia.so with RTLD_LOCAL, so RTLD_DEFAULT
28
+ // does not search it. RTLD_NOLOAD returns its handle only if it is already
29
+ // loaded, so this never loads Skia.
30
+ if (getter == nullptr) {
31
+ if (void *skia = dlopen("librnskia.so", RTLD_NOW | RTLD_NOLOAD)) {
32
+ getter = dlsym(skia, "rnskia_getWGPUInstance");
33
+ dlclose(skia);
34
+ }
35
+ }
36
+ #endif
37
+ if (auto getSkiaInstance = reinterpret_cast<GetInstanceFn>(getter)) {
24
38
  if (WGPUInstance external = getSkiaInstance()) {
25
39
  wgpuInstanceAddRef(external);
26
40
  return wgpu::Instance::Acquire(external);
@@ -76,6 +90,11 @@ wgpu::Instance WebGPURuntime::instance() {
76
90
  }
77
91
 
78
92
  wgpu::Surface WebGPURuntime::makeSurface(void *nativeSurface) {
93
+ return makeSurface(instance(), nativeSurface);
94
+ }
95
+
96
+ wgpu::Surface WebGPURuntime::makeSurface(const wgpu::Instance &gpu,
97
+ void *nativeSurface) {
79
98
  #if defined(__ANDROID__)
80
99
  wgpu::SurfaceSourceAndroidNativeWindow source;
81
100
  source.window = nativeSurface;
@@ -85,7 +104,7 @@ wgpu::Surface WebGPURuntime::makeSurface(void *nativeSurface) {
85
104
  #endif
86
105
  wgpu::SurfaceDescriptor descriptor;
87
106
  descriptor.nextInChain = &source;
88
- return instance().CreateSurface(&descriptor);
107
+ return gpu.CreateSurface(&descriptor);
89
108
  }
90
109
 
91
110
  } // namespace rnwgpu
@@ -15,14 +15,19 @@ class WebGPURuntime {
15
15
  public:
16
16
  static WebGPURuntime &shared();
17
17
 
18
- // The process-wide instance: Skia Graphite's when that is loaded, otherwise
19
- // our own. Created on first use. Safe to call from any thread.
18
+ // The process-wide instance: Skia Graphite's when that is already loaded on
19
+ // first use, otherwise our own. Safe to call from any thread.
20
20
  wgpu::Instance instance();
21
21
 
22
22
  // Creates a surface on the shared instance for a CAMetalLayer (Apple) or an
23
23
  // ANativeWindow (Android). Safe to call from any thread.
24
24
  wgpu::Surface makeSurface(void *nativeSurface);
25
25
 
26
+ // The same on a given instance: a SurfaceInfo recreates its surface on the
27
+ // instance it was made with.
28
+ static wgpu::Surface makeSurface(const wgpu::Instance &gpu,
29
+ void *nativeSurface);
30
+
26
31
  WebGPURuntime(const WebGPURuntime &) = delete;
27
32
  WebGPURuntime &operator=(const WebGPURuntime &) = delete;
28
33
 
@@ -29,7 +29,8 @@ void GPUCanvasContext::configure(
29
29
  surfaceConfiguration.alphaMode = configuration->alphaMode;
30
30
  #endif
31
31
  surfaceConfiguration.presentMode = wgpu::PresentMode::Fifo;
32
- _surfaceInfo->configure(surfaceConfiguration, std::move(viewFormats));
32
+ _surfaceInfo->configure(surfaceConfiguration, std::move(viewFormats),
33
+ configuration->colorSpace);
33
34
  }
34
35
 
35
36
  void GPUCanvasContext::unconfigure() { _surfaceInfo->unconfigure(); }
@@ -35,6 +35,16 @@ public:
35
35
  registry.getSurfaceInfoOrCreate(contextId, _gpu->get(), width, height);
36
36
  }
37
37
 
38
+ // A context presenting to a surface of its own rather than a view's, outside
39
+ // of the registry (see RNWebGPU::MakeWebGPUCanvasContextFromNativeWindow).
40
+ GPUCanvasContext(std::shared_ptr<GPU> gpu,
41
+ std::shared_ptr<SurfaceInfo> surfaceInfo, int width,
42
+ int height)
43
+ : NativeObject(CLASS_NAME), _surfaceInfo(std::move(surfaceInfo)),
44
+ _gpu(std::move(gpu)) {
45
+ _canvas = std::make_shared<Canvas>(nullptr, width, height);
46
+ }
47
+
38
48
  public:
39
49
  std::string getBrand() { return CLASS_NAME; }
40
50
 
@@ -104,6 +104,7 @@ namespace rnwgpu {
104
104
  V(AdapterPropertiesWGPU, "adapter-properties-wgpu") \
105
105
  V(SharedBufferMemoryFromWindowsHandle, \
106
106
  "shared-buffer-memory-from-windows-handle") \
107
+ V(SharedBufferMemoryHostPointer, "shared-buffer-memory-host-pointer") \
107
108
  V(SharedTextureMemoryD3D12Resource, "shared-texture-memory-d3d12-resource") \
108
109
  V(ChromiumExperimentalSamplingResourceTable, \
109
110
  "chromium-experimental-sampling-resource-table") \
@@ -1,5 +1,6 @@
1
1
  #pragma once
2
2
 
3
+ #include <cmath>
3
4
  #include <memory>
4
5
  #include <string>
5
6
  #include <utility>
@@ -18,6 +19,7 @@
18
19
  #include "PlatformContext.h"
19
20
  #include "VideoFrame.h"
20
21
  #include "VideoPlayer.h"
22
+ #include "WebGPURuntime.h"
21
23
 
22
24
  #include <ReactCommon/CallInvoker.h>
23
25
 
@@ -83,13 +85,58 @@ public:
83
85
  return ctx;
84
86
  }
85
87
 
88
+ // A canvas context presenting to a native window that is not a WebGPU
89
+ // view, e.g. the input Surface of a MediaCodec encoder: an ANativeWindow*
90
+ // (Android) or a CAMetalLayer* (Apple), passed as a BigInt. Unlike a view's,
91
+ // the context owns its surface, outside of the SurfaceRegistry, and the
92
+ // caller drives it from one thread at a time. The context retains the
93
+ // window for as long as it lives; unconfigure() releases the surface (and
94
+ // on Android the buffers the window handed to it) and configure() makes a
95
+ // new one for the window, see the SurfaceInfo constructor.
96
+ std::shared_ptr<GPUCanvasContext>
97
+ MakeWebGPUCanvasContextFromNativeWindow(void *window, float width,
98
+ float height) {
99
+ if (window == nullptr) {
100
+ throw std::runtime_error(
101
+ "MakeWebGPUCanvasContextFromNativeWindow: expected a non-null "
102
+ "ANativeWindow (Android) or CAMetalLayer (Apple) pointer (BigInt)");
103
+ }
104
+ if (!(std::isfinite(width) && std::isfinite(height) && width > 0 &&
105
+ height > 0)) {
106
+ throw std::runtime_error(
107
+ "MakeWebGPUCanvasContextFromNativeWindow: expected a positive "
108
+ "width and height");
109
+ }
110
+ auto &runtime = WebGPURuntime::shared();
111
+ // Unlike a view's, this surface has no offscreen fallback worth having:
112
+ // nothing would ever show up, so fail here.
113
+ auto surface = runtime.makeSurface(window);
114
+ if (surface == nullptr) {
115
+ throw std::runtime_error(
116
+ "MakeWebGPUCanvasContextFromNativeWindow: could not create a surface "
117
+ "for the window; is it a valid ANativeWindow (Android) or "
118
+ "CAMetalLayer (Apple)?");
119
+ }
120
+ auto info = SurfaceRegistry::getInstance().makeUnregisteredSurfaceInfo(
121
+ runtime.instance(), static_cast<int>(width), static_cast<int>(height));
122
+ // The window is retained last, once nothing left here can throw: the
123
+ // SurfaceInfo owns the reference from here on. Adopted by the context's
124
+ // configure() (see SurfaceInfo::configure).
125
+ info->attachSurface(window, std::move(surface),
126
+ _platformContext->retainNativeWindow(window));
127
+ return std::make_shared<GPUCanvasContext>(_gpu, std::move(info), width,
128
+ height);
129
+ }
130
+
86
131
  // Wrap an externally created WGPUDevice (passed as a BigInt pointer, e.g.
87
132
  // Skia's Graphite device from Skia.getNativeDevice()) in a GPUDevice.
88
133
  // AddRefs the handle, so the original owner keeps its reference. Only sound
89
- // because the process links a single Dawn copy and the GPU instance is
90
- // shared with the exporter (see the GPU constructor): the calling runtime's
91
- // pump serves the instance the device lives on, so its async callbacks
92
- // settle normally.
134
+ // because the process links a single Dawn copy. The device's async
135
+ // callbacks are pumped on the instance that owns it (RuntimeContext is per
136
+ // runtime and per instance), which is not necessarily the shared one:
137
+ // WebGPURuntime adopts Skia's instance only when Skia's getter is reachable
138
+ // as the instance is first used (on Android, only once librnskia.so is
139
+ // loaded), and other exporters have their own.
93
140
  std::shared_ptr<GPUDevice> importDevice(jsi::Runtime &runtime,
94
141
  void *pointer) {
95
142
  if (pointer == nullptr) {
@@ -99,7 +146,8 @@ public:
99
146
  auto raw = reinterpret_cast<WGPUDevice>(pointer);
100
147
  wgpuDeviceAddRef(raw);
101
148
  wgpu::Device device = wgpu::Device::Acquire(raw);
102
- auto ctx = async::RuntimeContext::getOrCreate(runtime, _gpu->get());
149
+ auto ctx = async::RuntimeContext::getOrCreate(
150
+ runtime, device.GetAdapter().GetInstance());
103
151
  return std::make_shared<GPUDevice>(device, ctx, "Imported Device");
104
152
  }
105
153
 
@@ -282,6 +330,12 @@ public:
282
330
  return _platformContext->writeTestVideoFile();
283
331
  }
284
332
 
333
+ // The id for a Canvas about to mount, unique for the life of the process
334
+ // (see SurfaceRegistry::allocateContextId).
335
+ int allocateContextId() {
336
+ return rnwgpu::SurfaceRegistry::getInstance().allocateContextId();
337
+ }
338
+
285
339
  std::shared_ptr<Canvas> getNativeSurface(int contextId) {
286
340
  auto &registry = rnwgpu::SurfaceRegistry::getInstance();
287
341
  auto info = registry.getSurfaceInfo(contextId);
@@ -319,12 +373,16 @@ public:
319
373
  installGetter(runtime, prototype, "gpu", &RNWebGPU::getGPU);
320
374
  installMethod(runtime, prototype, "createImageBitmap",
321
375
  &RNWebGPU::createImageBitmap);
376
+ installMethod(runtime, prototype, "allocateContextId",
377
+ &RNWebGPU::allocateContextId);
322
378
  installMethod(runtime, prototype, "getNativeSurface",
323
379
  &RNWebGPU::getNativeSurface);
324
380
  installMethod(runtime, prototype, "destroyContext",
325
381
  &RNWebGPU::destroyContext);
326
382
  installMethod(runtime, prototype, "MakeWebGPUCanvasContext",
327
383
  &RNWebGPU::MakeWebGPUCanvasContext);
384
+ installMethod(runtime, prototype, "MakeWebGPUCanvasContextFromNativeWindow",
385
+ &RNWebGPU::MakeWebGPUCanvasContextFromNativeWindow);
328
386
  installMethod(runtime, prototype, "loadVideoFrame",
329
387
  &RNWebGPU::loadVideoFrame);
330
388
  installMethod(runtime, prototype, "createTestVideoFrame",
@@ -21,8 +21,8 @@ namespace jsi = facebook::jsi;
21
21
  class VideoFrame : public NativeObject<VideoFrame> {
22
22
  public:
23
23
  // JS-facing brand. installConstructor() exposes this as
24
- // globalThis[CLASS_NAME] and keys the NativeObjectRegistry / worklet
25
- // serializer off it, so it must NOT be "VideoFrame": that would install our
24
+ // globalThis[CLASS_NAME], and the NativeObjectRegistry / worklet serializer
25
+ // are keyed off it, so it must NOT be "VideoFrame": that would install our
26
26
  // constructor over the WebCodecs `VideoFrame` global and shadow it at
27
27
  // runtime. "NativeVideoFrame" matches the public TypeScript type (see
28
28
  // src/types.ts) and keeps the two namespaces distinct. The C++ class stays
@@ -5,6 +5,7 @@
5
5
 
6
6
  #include "webgpu/webgpu_cpp.h"
7
7
 
8
+ #include "CanvasColorSpace.h"
8
9
  #include "JSIConverter.h"
9
10
 
10
11
  #include "GPUDevice.h"
@@ -20,6 +21,7 @@ struct GPUCanvasConfiguration {
20
21
  std::optional<std::vector<wgpu::TextureFormat>>
21
22
  viewFormats; // Iterable<GPUTextureFormat>
22
23
  wgpu::CompositeAlphaMode alphaMode = wgpu::CompositeAlphaMode::Opaque;
24
+ CanvasColorSpace colorSpace = CanvasColorSpace::SRGB;
23
25
  };
24
26
 
25
27
  } // namespace rnwgpu
@@ -63,6 +65,22 @@ struct JSIConverter<std::shared_ptr<rnwgpu::GPUCanvasConfiguration>> {
63
65
  result->alphaMode = wgpu::CompositeAlphaMode::Premultiplied;
64
66
  }
65
67
  }
68
+ if (value.hasProperty(runtime, "colorSpace")) {
69
+ auto prop = value.getProperty(runtime, "colorSpace");
70
+ if (!prop.isUndefined()) {
71
+ auto name = prop.asString(runtime).utf8(runtime);
72
+ if (name == "display-p3") {
73
+ result->colorSpace = CanvasColorSpace::DisplayP3;
74
+ } else if (name != "srgb") {
75
+ throw jsi::JSError(
76
+ runtime, "Failed to execute 'configure' on 'GPUCanvasContext': "
77
+ "The provided value '" +
78
+ name +
79
+ "' is not a valid enum value of type "
80
+ "PredefinedColorSpace.");
81
+ }
82
+ }
83
+ }
66
84
  }
67
85
 
68
86
  return result;