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.
- package/android/cpp/AndroidPlatformContext.h +42 -8
- package/android/cpp/AndroidVideoPlayer.h +307 -0
- package/android/src/main/java/com/webgpu/WebGPUModule.java +10 -0
- package/android/src/main/java/com/webgpu/WebGPUVideoPlayer.java +420 -0
- package/apple/AppleVideoPlayer.mm +157 -47
- package/cpp/rnwgpu/PlatformContext.h +25 -5
- package/cpp/rnwgpu/api/GPUDevice.cpp +2 -1
- package/cpp/rnwgpu/api/GPUDevice.h +6 -0
- package/cpp/rnwgpu/api/GPUQueue.cpp +80 -10
- package/cpp/rnwgpu/api/GPUQueue.h +15 -4
- package/cpp/rnwgpu/api/VideoFrameBlit.h +355 -0
- package/cpp/rnwgpu/api/VideoPlayer.h +50 -3
- package/cpp/rnwgpu/api/descriptors/GPUImageCopyExternalImage.h +30 -3
- package/lib/commonjs/importDevice.js +2 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/main/index.js +12 -0
- package/lib/commonjs/main/index.js.map +1 -1
- package/lib/commonjs/main/index.web.js +11 -0
- package/lib/commonjs/main/index.web.js.map +1 -1
- package/lib/commonjs/video.js +58 -0
- package/lib/commonjs/video.js.map +1 -0
- package/lib/module/importDevice.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/main/index.js +1 -0
- package/lib/module/main/index.js.map +1 -1
- package/lib/module/main/index.web.js +1 -0
- package/lib/module/main/index.web.js.map +1 -1
- package/lib/module/video.js +49 -0
- package/lib/module/video.js.map +1 -0
- package/lib/typescript/lib/commonjs/video.d.ts +30 -0
- package/lib/typescript/lib/commonjs/video.d.ts.map +1 -0
- package/lib/typescript/lib/module/main/index.d.ts +1 -0
- package/lib/typescript/lib/module/main/index.web.d.ts +1 -0
- package/lib/typescript/lib/module/video.d.ts +4 -0
- package/lib/typescript/lib/module/video.d.ts.map +1 -0
- package/lib/typescript/src/importDevice.d.ts +2 -2
- package/lib/typescript/src/index.d.ts +5 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/main/index.d.ts +1 -0
- package/lib/typescript/src/main/index.d.ts.map +1 -1
- package/lib/typescript/src/main/index.web.d.ts +1 -0
- package/lib/typescript/src/main/index.web.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +19 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/video.d.ts +41 -0
- package/lib/typescript/src/video.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/importDevice.ts +2 -2
- package/src/index.tsx +28 -3
- package/src/main/index.tsx +1 -0
- package/src/main/index.web.tsx +1 -0
- package/src/types.ts +60 -7
- 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
|
-
|
|
122
|
-
|
|
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,
|
|
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
|
-
|
|
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 >
|
|
339
|
-
|
|
340
|
-
image.height >
|
|
341
|
-
|
|
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),
|
|
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 = ⌖
|
|
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
|