react-native-webgpu 0.11.0 → 0.11.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 (53) hide show
  1. package/android/cpp/AndroidPlatformContext.h +42 -8
  2. package/android/cpp/AndroidVideoPlayer.h +307 -0
  3. package/android/src/main/java/com/webgpu/WebGPUModule.java +10 -0
  4. package/android/src/main/java/com/webgpu/WebGPUVideoPlayer.java +420 -0
  5. package/apple/AppleVideoPlayer.mm +157 -47
  6. package/cpp/rnwgpu/PlatformContext.h +25 -5
  7. package/cpp/rnwgpu/api/GPUDevice.cpp +2 -1
  8. package/cpp/rnwgpu/api/GPUDevice.h +6 -0
  9. package/cpp/rnwgpu/api/GPUQueue.cpp +80 -10
  10. package/cpp/rnwgpu/api/GPUQueue.h +15 -4
  11. package/cpp/rnwgpu/api/VideoFrameBlit.h +355 -0
  12. package/cpp/rnwgpu/api/VideoPlayer.h +50 -3
  13. package/cpp/rnwgpu/api/descriptors/GPUImageCopyExternalImage.h +30 -3
  14. package/lib/commonjs/importDevice.js +2 -2
  15. package/lib/commonjs/index.js.map +1 -1
  16. package/lib/commonjs/main/index.js +12 -0
  17. package/lib/commonjs/main/index.js.map +1 -1
  18. package/lib/commonjs/main/index.web.js +11 -0
  19. package/lib/commonjs/main/index.web.js.map +1 -1
  20. package/lib/commonjs/video.js +58 -0
  21. package/lib/commonjs/video.js.map +1 -0
  22. package/lib/module/importDevice.js +2 -2
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/main/index.js +1 -0
  25. package/lib/module/main/index.js.map +1 -1
  26. package/lib/module/main/index.web.js +1 -0
  27. package/lib/module/main/index.web.js.map +1 -1
  28. package/lib/module/video.js +49 -0
  29. package/lib/module/video.js.map +1 -0
  30. package/lib/typescript/lib/commonjs/video.d.ts +30 -0
  31. package/lib/typescript/lib/commonjs/video.d.ts.map +1 -0
  32. package/lib/typescript/lib/module/main/index.d.ts +1 -0
  33. package/lib/typescript/lib/module/main/index.web.d.ts +1 -0
  34. package/lib/typescript/lib/module/video.d.ts +4 -0
  35. package/lib/typescript/lib/module/video.d.ts.map +1 -0
  36. package/lib/typescript/src/importDevice.d.ts +2 -2
  37. package/lib/typescript/src/index.d.ts +5 -3
  38. package/lib/typescript/src/index.d.ts.map +1 -1
  39. package/lib/typescript/src/main/index.d.ts +1 -0
  40. package/lib/typescript/src/main/index.d.ts.map +1 -1
  41. package/lib/typescript/src/main/index.web.d.ts +1 -0
  42. package/lib/typescript/src/main/index.web.d.ts.map +1 -1
  43. package/lib/typescript/src/types.d.ts +19 -0
  44. package/lib/typescript/src/types.d.ts.map +1 -1
  45. package/lib/typescript/src/video.d.ts +41 -0
  46. package/lib/typescript/src/video.d.ts.map +1 -0
  47. package/package.json +1 -1
  48. package/src/importDevice.ts +2 -2
  49. package/src/index.tsx +28 -3
  50. package/src/main/index.tsx +1 -0
  51. package/src/main/index.web.tsx +1 -0
  52. package/src/types.ts +60 -7
  53. package/src/video.ts +69 -0
@@ -59,7 +59,8 @@ struct VideoFrameHandle {
59
59
  };
60
60
 
61
61
  // Platform-implemented video source that hands out fresh IOSurface /
62
- // AHardwareBuffer-backed frames as a video plays.
62
+ // AHardwareBuffer-backed frames as a video plays, with the playback controls
63
+ // of an HTMLMediaElement (times in seconds).
63
64
  class IVideoPlayer {
64
65
  public:
65
66
  virtual ~IVideoPlayer() = default;
@@ -71,6 +72,26 @@ public:
71
72
 
72
73
  virtual void play() = 0;
73
74
  virtual void pause() = 0;
75
+ virtual bool paused() = 0;
76
+
77
+ // Playback position and length, in seconds. The duration is 0 until the
78
+ // metadata of the media has loaded.
79
+ virtual double currentTime() = 0;
80
+ virtual void seek(double seconds) = 0;
81
+ virtual double duration() = 0;
82
+
83
+ virtual double volume() = 0;
84
+ virtual void setVolume(double volume) = 0;
85
+ virtual bool loop() = 0;
86
+ virtual void setLoop(bool loop) = 0;
87
+
88
+ // The geometry of the frames, 0 until the metadata has loaded: their coded
89
+ // size, the clockwise rotation (0, 90, 180 or 270) to apply when displaying
90
+ // them, and the nominal frame rate of the video track.
91
+ virtual uint32_t videoWidth() = 0;
92
+ virtual uint32_t videoHeight() = 0;
93
+ virtual int rotation() = 0;
94
+ virtual double frameRate() = 0;
74
95
  };
75
96
 
76
97
  // A request to rasterize a native React Native view (looked up by its React
@@ -117,10 +138,9 @@ public:
117
138
  // pixel, `format` is rgba8unorm or bgra8unorm depending on what the platform
118
139
  // rasterizer produces natively, and `premultiplied` reports its alpha
119
140
  // representation.
120
- virtual void
121
- snapshotView(const ViewSnapshotRequest &request,
122
- std::function<void(ImageData)> onSuccess,
123
- std::function<void(std::string)> onError) = 0;
141
+ virtual void snapshotView(const ViewSnapshotRequest &request,
142
+ std::function<void(ImageData)> onSuccess,
143
+ std::function<void(std::string)> onError) = 0;
124
144
 
125
145
  virtual wgpu::Surface makeSurface(wgpu::Instance instance, void *surface,
126
146
  int width, int height) = 0;
@@ -133,7 +133,8 @@ std::shared_ptr<GPUSupportedLimits> GPUDevice::getLimits() {
133
133
 
134
134
  std::shared_ptr<GPUQueue> GPUDevice::getQueue() {
135
135
  auto result = _instance.GetQueue();
136
- return std::make_shared<GPUQueue>(result, _async, _label);
136
+ return std::make_shared<GPUQueue>(_instance, result, _async, _videoFrameBlit,
137
+ _label);
137
138
  }
138
139
 
139
140
  std::shared_ptr<GPUCommandEncoder> GPUDevice::createCommandEncoder(
@@ -54,6 +54,7 @@
54
54
  #include "GPUTexture.h"
55
55
  #include "GPUTextureDescriptor.h"
56
56
  #include "GPUUncapturedErrorEvent.h"
57
+ #include "VideoFrameBlit.h"
57
58
 
58
59
  namespace rnwgpu {
59
60
 
@@ -285,6 +286,11 @@ private:
285
286
  wgpu::Device _instance;
286
287
  std::shared_ptr<async::RuntimeContext> _async;
287
288
  std::string _label;
289
+ // Renders native video frames into textures for
290
+ // GPUQueue.copyExternalImageToTexture; shared by every GPUQueue wrapper of
291
+ // this device so the pipelines are created once.
292
+ std::shared_ptr<VideoFrameBlit> _videoFrameBlit =
293
+ std::make_shared<VideoFrameBlit>();
288
294
  // Guards the device-lost state below. getLost() runs on a JS thread, but
289
295
  // Dawn's AllowSpontaneous device-lost callback (and device destruction) can
290
296
  // fire notifyDeviceLost() from other threads, so the mutex keeps these
@@ -109,6 +109,68 @@ async::AsyncTaskHandle GPUQueue::onSubmittedWorkDone(jsi::Runtime &runtime) {
109
109
  });
110
110
  }
111
111
 
112
+ void GPUQueue::copyVideoFrameToTexture(
113
+ const GPUImageCopyExternalImage &source,
114
+ const GPUImageCopyTextureTagged &destination, const wgpu::Extent3D &size) {
115
+ if (!destination.texture) {
116
+ throw std::runtime_error(
117
+ "copyExternalImageToTexture: destination.texture is required");
118
+ }
119
+ if (destination.aspect.value_or(wgpu::TextureAspect::All) !=
120
+ wgpu::TextureAspect::All) {
121
+ throw std::runtime_error(
122
+ "copyExternalImageToTexture: a native frame can only be copied into "
123
+ "the color aspect");
124
+ }
125
+ if (size.depthOrArrayLayers > 1) {
126
+ throw std::runtime_error(
127
+ "copyExternalImageToTexture: a native frame is a single 2D image");
128
+ }
129
+ constexpr double kMaxCoordinate =
130
+ static_cast<double>(std::numeric_limits<uint32_t>::max());
131
+ auto coordinate = [&](double value, const char *name) {
132
+ if (!(value >= 0) || value > kMaxCoordinate) {
133
+ throw std::runtime_error(std::string("copyExternalImageToTexture: ") +
134
+ name +
135
+ " must be a non-negative integer coordinate");
136
+ }
137
+ return static_cast<uint32_t>(value);
138
+ };
139
+ const double rotation = source.rotation.value_or(0);
140
+ if (rotation != 0 && rotation != 90 && rotation != 180 && rotation != 270) {
141
+ throw std::runtime_error(
142
+ "copyExternalImageToTexture: rotation must be 0, 90, 180 or 270");
143
+ }
144
+
145
+ VideoFrameBlit::Request request;
146
+ request.frame = source.videoFrame;
147
+ if (const auto origin = source.origin.value_or(nullptr)) {
148
+ request.originX = coordinate(origin->x, "origin.x");
149
+ request.originY = coordinate(origin->y, "origin.y");
150
+ }
151
+ request.width = size.width;
152
+ request.height = size.height;
153
+ request.flipY = source.flipY.value_or(false);
154
+ request.rotation = rotation;
155
+ request.mirrored = source.mirrored.value_or(false);
156
+ request.texture = destination.texture->get();
157
+ request.mipLevel = coordinate(destination.mipLevel.value_or(0), "mipLevel");
158
+ wgpu::Origin3D dstOrigin{};
159
+ Convertor conv;
160
+ if (!conv(dstOrigin, destination.origin)) {
161
+ throw std::runtime_error(
162
+ "copyExternalImageToTexture: invalid destination origin");
163
+ }
164
+ if (dstOrigin.z != 0) {
165
+ throw std::runtime_error(
166
+ "copyExternalImageToTexture: a native frame can only be copied into "
167
+ "the first layer of the texture");
168
+ }
169
+ request.dstX = dstOrigin.x;
170
+ request.dstY = dstOrigin.y;
171
+ _videoFrameBlit->copy(_device, _instance, request);
172
+ }
173
+
112
174
  void GPUQueue::copyExternalImageToTexture(
113
175
  std::shared_ptr<GPUImageCopyExternalImage> source,
114
176
  std::shared_ptr<GPUImageCopyTextureTagged> destination,
@@ -117,6 +179,17 @@ void GPUQueue::copyExternalImageToTexture(
117
179
  wgpu::TexelCopyBufferLayout layout{};
118
180
  wgpu::Extent3D sz{};
119
181
  Convertor conv;
182
+ if (source->videoFrame) {
183
+ if (!conv(sz, size)) {
184
+ throw std::runtime_error("copyExternalImageToTexture: invalid copy size");
185
+ }
186
+ copyVideoFrameToTexture(*source, *destination, sz);
187
+ return;
188
+ }
189
+ if (!source->source) {
190
+ throw std::runtime_error("copyExternalImageToTexture: the source must be "
191
+ "an ImageBitmap or a NativeVideoFrame");
192
+ }
120
193
  uint32_t bytesPerPixel =
121
194
  source->source->getSize() /
122
195
  (source->source->getWidth() * source->source->getHeight());
@@ -145,10 +218,8 @@ void GPUQueue::copyExternalImageToTexture(
145
218
  throw std::runtime_error(
146
219
  "The source origin must be a non-negative integer coordinate.");
147
220
  }
148
- const size_t sourceOriginX =
149
- origin ? static_cast<size_t>(origin->x) : 0;
150
- const size_t sourceOriginY =
151
- origin ? static_cast<size_t>(origin->y) : 0;
221
+ const size_t sourceOriginX = origin ? static_cast<size_t>(origin->x) : 0;
222
+ const size_t sourceOriginY = origin ? static_cast<size_t>(origin->y) : 0;
152
223
  if (sourceOriginX > source->source->getWidth() ||
153
224
  sz.width > source->source->getWidth() - sourceOriginX ||
154
225
  sourceOriginY > source->source->getHeight() ||
@@ -220,8 +291,7 @@ async::AsyncTaskHandle GPUQueue::drawElementImageToTexture(
220
291
  Convertor conv;
221
292
  if (!conv(dst.aspect, info->aspect) || !conv(dst.mipLevel, info->mipLevel) ||
222
293
  !conv(dst.origin, info->origin) || !conv(dst.texture, info->texture)) {
223
- throw std::runtime_error(
224
- "drawElementImageToTexture: invalid destination");
294
+ throw std::runtime_error("drawElementImageToTexture: invalid destination");
225
295
  }
226
296
  const wgpu::Texture texture = info->texture->get();
227
297
  bool destinationIsBgra = false;
@@ -335,10 +405,10 @@ async::AsyncTaskHandle GPUQueue::drawElementImageToTexture(
335
405
  "pixels (is it mounted with a non-zero size?)");
336
406
  return;
337
407
  }
338
- if (image.width > mipWidth - std::min<uint32_t>(dst.origin.x,
339
- mipWidth) ||
340
- image.height > mipHeight - std::min<uint32_t>(dst.origin.y,
341
- mipHeight)) {
408
+ if (image.width >
409
+ mipWidth - std::min<uint32_t>(dst.origin.x, mipWidth) ||
410
+ image.height >
411
+ mipHeight - std::min<uint32_t>(dst.origin.y, mipHeight)) {
342
412
  reject("drawElementImageToTexture: the view's natural pixel "
343
413
  "size (" +
344
414
  std::to_string(image.width) + "x" +
@@ -20,6 +20,7 @@
20
20
  #include "GPUDrawElementImageSource.h"
21
21
  #include "GPUImageCopyExternalImage.h"
22
22
  #include "GPUImageCopyTextureTagged.h"
23
+ #include "VideoFrameBlit.h"
23
24
 
24
25
  namespace rnwgpu {
25
26
 
@@ -29,10 +30,12 @@ class GPUQueue : public NativeObject<GPUQueue> {
29
30
  public:
30
31
  static constexpr const char *CLASS_NAME = "GPUQueue";
31
32
 
32
- explicit GPUQueue(wgpu::Queue instance,
33
+ explicit GPUQueue(wgpu::Device device, wgpu::Queue instance,
33
34
  std::shared_ptr<async::RuntimeContext> async,
35
+ std::shared_ptr<VideoFrameBlit> videoFrameBlit,
34
36
  std::string label)
35
- : NativeObject(CLASS_NAME), _instance(instance), _async(async),
37
+ : NativeObject(CLASS_NAME), _device(device), _instance(instance),
38
+ _async(async), _videoFrameBlit(std::move(videoFrameBlit)),
36
39
  _label(label) {}
37
40
 
38
41
  public:
@@ -48,6 +51,9 @@ public:
48
51
  std::shared_ptr<ArrayBuffer> data,
49
52
  std::shared_ptr<GPUImageDataLayout> dataLayout,
50
53
  std::shared_ptr<GPUExtent3D> size);
54
+ // The source is an ImageBitmap (uploaded from the CPU) or, as a React
55
+ // Native extension, a NativeVideoFrame rendered into the destination through
56
+ // an external texture (see VideoFrameBlit).
51
57
  void copyExternalImageToTexture(
52
58
  std::shared_ptr<GPUImageCopyExternalImage> source,
53
59
  std::shared_ptr<GPUImageCopyTextureTagged> destination,
@@ -61,8 +67,7 @@ public:
61
67
  // runtime's thread right before the promise resolves, so work submitted
62
68
  // after `await` observes the new contents.
63
69
  async::AsyncTaskHandle drawElementImageToTexture(
64
- jsi::Runtime &runtime,
65
- std::shared_ptr<GPUDrawElementImageSource> source,
70
+ jsi::Runtime &runtime, std::shared_ptr<GPUDrawElementImageSource> source,
66
71
  std::shared_ptr<GPUDrawElementImageDestination> destination);
67
72
 
68
73
  std::string getLabel() { return _label; }
@@ -89,8 +94,14 @@ public:
89
94
  inline const wgpu::Queue get() { return _instance; }
90
95
 
91
96
  private:
97
+ void copyVideoFrameToTexture(const GPUImageCopyExternalImage &source,
98
+ const GPUImageCopyTextureTagged &destination,
99
+ const wgpu::Extent3D &size);
100
+
101
+ wgpu::Device _device;
92
102
  wgpu::Queue _instance;
93
103
  std::shared_ptr<async::RuntimeContext> _async;
104
+ std::shared_ptr<VideoFrameBlit> _videoFrameBlit;
94
105
  std::string _label;
95
106
  };
96
107
 
@@ -0,0 +1,355 @@
1
+ #pragma once
2
+
3
+ #include <array>
4
+ #include <cstring>
5
+ #include <memory>
6
+ #include <mutex>
7
+ #include <stdexcept>
8
+ #include <string>
9
+ #include <unordered_map>
10
+ #include <utility>
11
+ #include <vector>
12
+
13
+ #include "webgpu/webgpu_cpp.h"
14
+
15
+ #include "GPUExternalTexture.h"
16
+ #include "GPUExternalTextureDescriptor.h"
17
+ #include "VideoFrame.h"
18
+
19
+ namespace rnwgpu {
20
+
21
+ // Renders a native video frame into a region of a regular texture. The frame
22
+ // is imported as an external texture, so Dawn does the YUV to RGB conversion
23
+ // and applies the rotation and the mirroring; a full-screen triangle then
24
+ // samples it into the destination. This is what backs
25
+ // GPUQueue.copyExternalImageToTexture for NativeVideoFrame sources.
26
+ //
27
+ // One instance per device, shared by its GPUQueue wrappers (a new wrapper is
28
+ // created on every `device.queue` access): it caches the shader, the layouts
29
+ // and one pipeline per destination format.
30
+ class VideoFrameBlit {
31
+ public:
32
+ struct Request {
33
+ std::shared_ptr<VideoFrame> frame;
34
+ // The region of the upright frame (after rotation and mirroring) to copy.
35
+ uint32_t originX = 0;
36
+ uint32_t originY = 0;
37
+ uint32_t width = 0;
38
+ uint32_t height = 0;
39
+ bool flipY = false;
40
+ double rotation = 0;
41
+ bool mirrored = false;
42
+ // The destination region.
43
+ wgpu::Texture texture;
44
+ uint32_t mipLevel = 0;
45
+ uint32_t dstX = 0;
46
+ uint32_t dstY = 0;
47
+ };
48
+
49
+ void copy(const wgpu::Device &device, const wgpu::Queue &queue,
50
+ const Request &request) {
51
+ if (!request.frame || request.frame->handle().handle == nullptr) {
52
+ throw std::runtime_error(
53
+ "copyExternalImageToTexture: the NativeVideoFrame was released");
54
+ }
55
+ if (!request.texture) {
56
+ throw std::runtime_error(
57
+ "copyExternalImageToTexture: destination.texture is required");
58
+ }
59
+ if (!(request.texture.GetUsage() & wgpu::TextureUsage::RenderAttachment)) {
60
+ throw std::runtime_error(
61
+ "copyExternalImageToTexture: a native frame is rendered into the "
62
+ "destination, which needs GPUTextureUsage.RENDER_ATTACHMENT");
63
+ }
64
+ const wgpu::TextureFormat format = request.texture.GetFormat();
65
+ if (!isSupportedDestination(format)) {
66
+ throw std::runtime_error(
67
+ "copyExternalImageToTexture: a native frame can only be copied "
68
+ "into an rgba8unorm, bgra8unorm (or -srgb), rgba16float or "
69
+ "rgb10a2unorm texture");
70
+ }
71
+ if (request.mipLevel >= request.texture.GetMipLevelCount()) {
72
+ throw std::runtime_error(
73
+ "copyExternalImageToTexture: destination.mipLevel is out of range");
74
+ }
75
+ const uint32_t mipWidth =
76
+ std::max(1u, request.texture.GetWidth() >> request.mipLevel);
77
+ const uint32_t mipHeight =
78
+ std::max(1u, request.texture.GetHeight() >> request.mipLevel);
79
+ if (request.width == 0 || request.height == 0) {
80
+ return;
81
+ }
82
+ if (request.dstX > mipWidth || request.width > mipWidth - request.dstX ||
83
+ request.dstY > mipHeight || request.height > mipHeight - request.dstY) {
84
+ throw std::runtime_error(
85
+ "copyExternalImageToTexture: the destination region is outside "
86
+ "the texture");
87
+ }
88
+
89
+ // The frame as the shader sees it: Dawn swaps the apparent size of a
90
+ // frame rotated by 90 or 270 degrees.
91
+ const auto &handle = request.frame->handle();
92
+ const int quadrant = static_cast<int>(request.rotation / 90.0) & 3;
93
+ const bool swapped = quadrant == 1 || quadrant == 3;
94
+ const uint32_t frameWidth = swapped ? handle.height : handle.width;
95
+ const uint32_t frameHeight = swapped ? handle.width : handle.height;
96
+ if (request.originX > frameWidth ||
97
+ request.width > frameWidth - request.originX ||
98
+ request.originY > frameHeight ||
99
+ request.height > frameHeight - request.originY) {
100
+ throw std::runtime_error(
101
+ "copyExternalImageToTexture: the source region is outside the "
102
+ "frame");
103
+ }
104
+
105
+ // Importing the frame begins the access to its surface; destroy() ends
106
+ // it once the commands that sample it are submitted.
107
+ auto descriptor = std::make_shared<GPUExternalTextureDescriptor>();
108
+ descriptor->source = request.frame;
109
+ descriptor->label = "copyExternalImageToTexture";
110
+ descriptor->rotation = request.rotation;
111
+ descriptor->mirrored = request.mirrored;
112
+ auto external = GPUExternalTexture::Create(device, descriptor);
113
+
114
+ const Resources &resources = resourcesFor(device, format);
115
+
116
+ // uv = position01 * uvScale + uvOffset, with position01 running from the
117
+ // top-left corner of the copy region; flipY reads the rows bottom-up.
118
+ const float scaleX =
119
+ static_cast<float>(request.width) / static_cast<float>(frameWidth);
120
+ const float scaleY =
121
+ static_cast<float>(request.height) / static_cast<float>(frameHeight);
122
+ const float offsetX =
123
+ static_cast<float>(request.originX) / static_cast<float>(frameWidth);
124
+ const float offsetY =
125
+ static_cast<float>(request.originY) / static_cast<float>(frameHeight);
126
+ std::array<float, 20> uniforms = {};
127
+ const std::vector<double> matrix = external->getYuvToRgbMatrix();
128
+ for (size_t i = 0; i < 12 && i < matrix.size(); i++) {
129
+ uniforms[i] = static_cast<float>(matrix[i]);
130
+ }
131
+ uniforms[12] = scaleX;
132
+ uniforms[13] = request.flipY ? -scaleY : scaleY;
133
+ uniforms[14] = offsetX;
134
+ uniforms[15] = request.flipY ? offsetY + scaleY : offsetY;
135
+ const uint32_t decodeSrgb = isSrgb(format) ? 1u : 0u;
136
+ std::memcpy(&uniforms[16], &decodeSrgb, sizeof(decodeSrgb));
137
+
138
+ wgpu::BufferDescriptor bufferDesc = {};
139
+ bufferDesc.label = "copyExternalImageToTexture uniforms";
140
+ bufferDesc.size = sizeof(uniforms);
141
+ bufferDesc.usage = wgpu::BufferUsage::Uniform;
142
+ bufferDesc.mappedAtCreation = true;
143
+ wgpu::Buffer uniformBuffer = device.CreateBuffer(&bufferDesc);
144
+ std::memcpy(uniformBuffer.GetMappedRange(), uniforms.data(),
145
+ sizeof(uniforms));
146
+ uniformBuffer.Unmap();
147
+
148
+ wgpu::ExternalTextureBindingEntry externalEntry = {};
149
+ externalEntry.externalTexture = external->get();
150
+ std::array<wgpu::BindGroupEntry, 3> entries = {};
151
+ entries[0].binding = 0;
152
+ entries[0].nextInChain = &externalEntry;
153
+ entries[1].binding = 1;
154
+ entries[1].sampler = resources.sampler;
155
+ entries[2].binding = 2;
156
+ entries[2].buffer = uniformBuffer;
157
+ entries[2].size = sizeof(uniforms);
158
+ wgpu::BindGroupDescriptor bindGroupDesc = {};
159
+ bindGroupDesc.layout = resources.bindGroupLayout;
160
+ bindGroupDesc.entryCount = entries.size();
161
+ bindGroupDesc.entries = entries.data();
162
+ wgpu::BindGroup bindGroup = device.CreateBindGroup(&bindGroupDesc);
163
+
164
+ wgpu::TextureViewDescriptor viewDesc = {};
165
+ viewDesc.baseMipLevel = request.mipLevel;
166
+ viewDesc.mipLevelCount = 1;
167
+ viewDesc.baseArrayLayer = 0;
168
+ viewDesc.arrayLayerCount = 1;
169
+ viewDesc.dimension = wgpu::TextureViewDimension::e2D;
170
+ wgpu::TextureView target = request.texture.CreateView(&viewDesc);
171
+
172
+ // The rest of the texture is left untouched: the pass loads the existing
173
+ // contents and the viewport and scissor confine the draw to the region.
174
+ wgpu::RenderPassColorAttachment attachment = {};
175
+ attachment.view = target;
176
+ attachment.loadOp = wgpu::LoadOp::Load;
177
+ attachment.storeOp = wgpu::StoreOp::Store;
178
+ wgpu::RenderPassDescriptor passDesc = {};
179
+ passDesc.label = "copyExternalImageToTexture";
180
+ passDesc.colorAttachmentCount = 1;
181
+ passDesc.colorAttachments = &attachment;
182
+
183
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
184
+ wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&passDesc);
185
+ pass.SetPipeline(resources.pipeline);
186
+ pass.SetBindGroup(0, bindGroup);
187
+ pass.SetViewport(static_cast<float>(request.dstX),
188
+ static_cast<float>(request.dstY),
189
+ static_cast<float>(request.width),
190
+ static_cast<float>(request.height), 0.0f, 1.0f);
191
+ pass.SetScissorRect(request.dstX, request.dstY, request.width,
192
+ request.height);
193
+ pass.Draw(3);
194
+ pass.End();
195
+ wgpu::CommandBuffer commands = encoder.Finish();
196
+ queue.Submit(1, &commands);
197
+ external->destroy();
198
+ }
199
+
200
+ private:
201
+ struct Resources {
202
+ wgpu::BindGroupLayout bindGroupLayout;
203
+ wgpu::Sampler sampler;
204
+ wgpu::RenderPipeline pipeline;
205
+ };
206
+
207
+ static bool isSrgb(wgpu::TextureFormat format) {
208
+ return format == wgpu::TextureFormat::RGBA8UnormSrgb ||
209
+ format == wgpu::TextureFormat::BGRA8UnormSrgb;
210
+ }
211
+
212
+ static bool isSupportedDestination(wgpu::TextureFormat format) {
213
+ switch (format) {
214
+ case wgpu::TextureFormat::RGBA8Unorm:
215
+ case wgpu::TextureFormat::RGBA8UnormSrgb:
216
+ case wgpu::TextureFormat::BGRA8Unorm:
217
+ case wgpu::TextureFormat::BGRA8UnormSrgb:
218
+ case wgpu::TextureFormat::RGBA16Float:
219
+ case wgpu::TextureFormat::RGB10A2Unorm:
220
+ return true;
221
+ default:
222
+ return false;
223
+ }
224
+ }
225
+
226
+ static constexpr const char *kShader = R"(
227
+ struct Uniforms {
228
+ // The rows of the 3x4 matrix mapping the sampled texel to R'G'B'
229
+ // (GPUExternalTexture.yuvToRgbMatrix; the identity outside Android YUV).
230
+ yuvToRgb0: vec4f,
231
+ yuvToRgb1: vec4f,
232
+ yuvToRgb2: vec4f,
233
+ // uv = position01 * uvScale + uvOffset
234
+ uvScale: vec2f,
235
+ uvOffset: vec2f,
236
+ // x: decode sRGB to linear, for an sRGB destination that re-encodes on write
237
+ flags: vec4u,
238
+ };
239
+
240
+ @group(0) @binding(0) var frame: texture_external;
241
+ @group(0) @binding(1) var frameSampler: sampler;
242
+ @group(0) @binding(2) var<uniform> u: Uniforms;
243
+
244
+ struct VsOut {
245
+ @builtin(position) position: vec4f,
246
+ @location(0) uv: vec2f,
247
+ };
248
+
249
+ @vertex fn vs(@builtin(vertex_index) index: u32) -> VsOut {
250
+ // A triangle covering the viewport; position01 runs from its top-left
251
+ // corner, like image coordinates.
252
+ var corners = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));
253
+ let corner = corners[index];
254
+ var out: VsOut;
255
+ out.position = vec4f(corner, 0.0, 1.0);
256
+ let position01 = vec2f(corner.x * 0.5 + 0.5, 0.5 - corner.y * 0.5);
257
+ out.uv = position01 * u.uvScale + u.uvOffset;
258
+ return out;
259
+ }
260
+
261
+ fn srgbToLinear(c: vec3f) -> vec3f {
262
+ let low = c / 12.92;
263
+ let high = pow((c + 0.055) / 1.055, vec3f(2.4));
264
+ return select(high, low, c <= vec3f(0.04045));
265
+ }
266
+
267
+ @fragment fn fs(in: VsOut) -> @location(0) vec4f {
268
+ let sampled = textureSampleBaseClampToEdge(frame, frameSampler, in.uv);
269
+ let texel = vec4f(sampled.rgb, 1.0);
270
+ var rgb = vec3f(dot(u.yuvToRgb0, texel), dot(u.yuvToRgb1, texel), dot(u.yuvToRgb2, texel));
271
+ rgb = clamp(rgb, vec3f(0.0), vec3f(1.0));
272
+ if (u.flags.x == 1u) {
273
+ rgb = srgbToLinear(rgb);
274
+ }
275
+ return vec4f(rgb, 1.0);
276
+ }
277
+ )";
278
+
279
+ const Resources &resourcesFor(const wgpu::Device &device,
280
+ wgpu::TextureFormat format) {
281
+ std::lock_guard<std::mutex> lock(_mutex);
282
+ auto found = _resources.find(static_cast<WGPUTextureFormat>(format));
283
+ if (found != _resources.end()) {
284
+ return found->second;
285
+ }
286
+ if (!_module) {
287
+ wgpu::ShaderSourceWGSL wgsl = {};
288
+ wgsl.code = kShader;
289
+ wgpu::ShaderModuleDescriptor moduleDesc = {};
290
+ moduleDesc.label = "copyExternalImageToTexture";
291
+ moduleDesc.nextInChain = &wgsl;
292
+ _module = device.CreateShaderModule(&moduleDesc);
293
+
294
+ wgpu::ExternalTextureBindingLayout externalLayout = {};
295
+ std::array<wgpu::BindGroupLayoutEntry, 3> entries = {};
296
+ entries[0].binding = 0;
297
+ entries[0].visibility = wgpu::ShaderStage::Fragment;
298
+ entries[0].nextInChain = &externalLayout;
299
+ entries[1].binding = 1;
300
+ entries[1].visibility = wgpu::ShaderStage::Fragment;
301
+ entries[1].sampler.type = wgpu::SamplerBindingType::Filtering;
302
+ entries[2].binding = 2;
303
+ entries[2].visibility =
304
+ wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment;
305
+ entries[2].buffer.type = wgpu::BufferBindingType::Uniform;
306
+ wgpu::BindGroupLayoutDescriptor layoutDesc = {};
307
+ layoutDesc.label = "copyExternalImageToTexture";
308
+ layoutDesc.entryCount = entries.size();
309
+ layoutDesc.entries = entries.data();
310
+ _bindGroupLayout = device.CreateBindGroupLayout(&layoutDesc);
311
+
312
+ wgpu::PipelineLayoutDescriptor pipelineLayoutDesc = {};
313
+ pipelineLayoutDesc.bindGroupLayoutCount = 1;
314
+ pipelineLayoutDesc.bindGroupLayouts = &_bindGroupLayout;
315
+ _pipelineLayout = device.CreatePipelineLayout(&pipelineLayoutDesc);
316
+
317
+ wgpu::SamplerDescriptor samplerDesc = {};
318
+ samplerDesc.magFilter = wgpu::FilterMode::Linear;
319
+ samplerDesc.minFilter = wgpu::FilterMode::Linear;
320
+ _sampler = device.CreateSampler(&samplerDesc);
321
+ }
322
+
323
+ wgpu::ColorTargetState target = {};
324
+ target.format = format;
325
+ wgpu::FragmentState fragment = {};
326
+ fragment.module = _module;
327
+ fragment.entryPoint = "fs";
328
+ fragment.targetCount = 1;
329
+ fragment.targets = &target;
330
+ wgpu::RenderPipelineDescriptor pipelineDesc = {};
331
+ pipelineDesc.label = "copyExternalImageToTexture";
332
+ pipelineDesc.layout = _pipelineLayout;
333
+ pipelineDesc.vertex.module = _module;
334
+ pipelineDesc.vertex.entryPoint = "vs";
335
+ pipelineDesc.fragment = &fragment;
336
+ pipelineDesc.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
337
+
338
+ Resources resources;
339
+ resources.bindGroupLayout = _bindGroupLayout;
340
+ resources.sampler = _sampler;
341
+ resources.pipeline = device.CreateRenderPipeline(&pipelineDesc);
342
+ auto inserted = _resources.emplace(static_cast<WGPUTextureFormat>(format),
343
+ std::move(resources));
344
+ return inserted.first->second;
345
+ }
346
+
347
+ std::mutex _mutex;
348
+ wgpu::ShaderModule _module;
349
+ wgpu::BindGroupLayout _bindGroupLayout;
350
+ wgpu::PipelineLayout _pipelineLayout;
351
+ wgpu::Sampler _sampler;
352
+ std::unordered_map<WGPUTextureFormat, Resources> _resources;
353
+ };
354
+
355
+ } // namespace rnwgpu