react-native-webgpu 0.10.5 → 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 +100 -9
- package/android/cpp/AndroidVideoPlayer.h +307 -0
- package/android/cpp/cpp-adapter.cpp +66 -6
- package/android/src/main/java/com/webgpu/ViewSnapshot.java +162 -0
- package/android/src/main/java/com/webgpu/WebGPUModule.java +78 -0
- package/android/src/main/java/com/webgpu/WebGPUVideoPlayer.java +420 -0
- package/apple/ApplePlatformContext.h +14 -1
- package/apple/ApplePlatformContext.mm +176 -1
- package/apple/AppleVideoPlayer.mm +157 -47
- package/apple/WebGPUModule.mm +20 -1
- package/cpp/rnwgpu/PlatformContext.h +76 -1
- package/cpp/rnwgpu/RNWebGPUManager.cpp +7 -0
- package/cpp/rnwgpu/api/GPUDevice.cpp +2 -1
- package/cpp/rnwgpu/api/GPUDevice.h +6 -0
- package/cpp/rnwgpu/api/GPUQueue.cpp +261 -4
- package/cpp/rnwgpu/api/GPUQueue.h +29 -2
- package/cpp/rnwgpu/api/VideoFrameBlit.h +355 -0
- package/cpp/rnwgpu/api/VideoPlayer.h +50 -3
- package/cpp/rnwgpu/api/descriptors/GPUDrawElementImageDestination.h +60 -0
- package/cpp/rnwgpu/api/descriptors/GPUDrawElementImageSource.h +106 -0
- 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 +45 -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 +34 -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 +8 -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 +32 -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 +57 -3
- package/src/main/index.tsx +50 -0
- package/src/main/index.web.tsx +1 -0
- package/src/types.ts +89 -7
- package/src/video.ts +69 -0
|
@@ -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
|
|
@@ -14,7 +14,9 @@ namespace rnwgpu {
|
|
|
14
14
|
namespace jsi = facebook::jsi;
|
|
15
15
|
|
|
16
16
|
// JSI wrapper around a platform-specific IVideoPlayer. Hands out fresh
|
|
17
|
-
// VideoFrame handles each time the underlying decoder produces a new frame
|
|
17
|
+
// VideoFrame handles each time the underlying decoder produces a new frame,
|
|
18
|
+
// and exposes playback controls modeled on HTMLMediaElement (see
|
|
19
|
+
// VideoPlayer in src/types.ts).
|
|
18
20
|
class VideoPlayer : public NativeObject<VideoPlayer> {
|
|
19
21
|
public:
|
|
20
22
|
static constexpr const char *CLASS_NAME = "VideoPlayer";
|
|
@@ -27,8 +29,7 @@ public:
|
|
|
27
29
|
// Returns the latest decoded frame, or null if no new frame is ready yet.
|
|
28
30
|
// Callers should poll this from their render loop and skip rendering (or
|
|
29
31
|
// reuse the last frame's texture) when null.
|
|
30
|
-
std::variant<std::nullptr_t, std::shared_ptr<VideoFrame>>
|
|
31
|
-
copyLatestFrame() {
|
|
32
|
+
std::variant<std::nullptr_t, std::shared_ptr<VideoFrame>> copyLatestFrame() {
|
|
32
33
|
if (!_impl) {
|
|
33
34
|
return nullptr;
|
|
34
35
|
}
|
|
@@ -51,6 +52,37 @@ public:
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
// A released player reads as paused, at the start, with no metadata.
|
|
56
|
+
bool getPaused() { return _impl ? _impl->paused() : true; }
|
|
57
|
+
|
|
58
|
+
double getCurrentTime() { return _impl ? _impl->currentTime() : 0; }
|
|
59
|
+
void setCurrentTime(double seconds) {
|
|
60
|
+
if (_impl) {
|
|
61
|
+
_impl->seek(seconds);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
double getDuration() { return _impl ? _impl->duration() : 0; }
|
|
66
|
+
|
|
67
|
+
double getVolume() { return _impl ? _impl->volume() : 0; }
|
|
68
|
+
void setVolume(double volume) {
|
|
69
|
+
if (_impl) {
|
|
70
|
+
_impl->setVolume(volume);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
bool getLoop() { return _impl ? _impl->loop() : false; }
|
|
75
|
+
void setLoop(bool loop) {
|
|
76
|
+
if (_impl) {
|
|
77
|
+
_impl->setLoop(loop);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
double getVideoWidth() { return _impl ? _impl->videoWidth() : 0; }
|
|
82
|
+
double getVideoHeight() { return _impl ? _impl->videoHeight() : 0; }
|
|
83
|
+
double getRotation() { return _impl ? _impl->rotation() : 0; }
|
|
84
|
+
double getFrameRate() { return _impl ? _impl->frameRate() : 0; }
|
|
85
|
+
|
|
54
86
|
void release() { _impl.reset(); }
|
|
55
87
|
|
|
56
88
|
static void definePrototype(jsi::Runtime &runtime, jsi::Object &prototype) {
|
|
@@ -60,6 +92,21 @@ public:
|
|
|
60
92
|
installMethod(runtime, prototype, "play", &VideoPlayer::play);
|
|
61
93
|
installMethod(runtime, prototype, "pause", &VideoPlayer::pause);
|
|
62
94
|
installMethod(runtime, prototype, "release", &VideoPlayer::release);
|
|
95
|
+
installGetter(runtime, prototype, "paused", &VideoPlayer::getPaused);
|
|
96
|
+
installGetterSetter(runtime, prototype, "currentTime",
|
|
97
|
+
&VideoPlayer::getCurrentTime,
|
|
98
|
+
&VideoPlayer::setCurrentTime);
|
|
99
|
+
installGetter(runtime, prototype, "duration", &VideoPlayer::getDuration);
|
|
100
|
+
installGetterSetter(runtime, prototype, "volume", &VideoPlayer::getVolume,
|
|
101
|
+
&VideoPlayer::setVolume);
|
|
102
|
+
installGetterSetter(runtime, prototype, "loop", &VideoPlayer::getLoop,
|
|
103
|
+
&VideoPlayer::setLoop);
|
|
104
|
+
installGetter(runtime, prototype, "videoWidth",
|
|
105
|
+
&VideoPlayer::getVideoWidth);
|
|
106
|
+
installGetter(runtime, prototype, "videoHeight",
|
|
107
|
+
&VideoPlayer::getVideoHeight);
|
|
108
|
+
installGetter(runtime, prototype, "rotation", &VideoPlayer::getRotation);
|
|
109
|
+
installGetter(runtime, prototype, "frameRate", &VideoPlayer::getFrameRate);
|
|
63
110
|
}
|
|
64
111
|
|
|
65
112
|
private:
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <memory>
|
|
4
|
+
#include <optional>
|
|
5
|
+
|
|
6
|
+
#include "JSIConverter.h"
|
|
7
|
+
|
|
8
|
+
#include "GPUExtent3D.h"
|
|
9
|
+
#include "GPUImageCopyTextureTagged.h"
|
|
10
|
+
|
|
11
|
+
namespace jsi = facebook::jsi;
|
|
12
|
+
|
|
13
|
+
namespace rnwgpu {
|
|
14
|
+
|
|
15
|
+
// The `destination` argument of GPUQueue.drawElementImageToTexture:
|
|
16
|
+
// dictionary GPUDrawElementImageDestination : GPUCopyExternalImageDestInfo {
|
|
17
|
+
// GPUExtent3D size;
|
|
18
|
+
// };
|
|
19
|
+
// `size` is the pixel size of the region the view is drawn into. When omitted
|
|
20
|
+
// it is the source rectangle's natural pixel size (points times the device
|
|
21
|
+
// pixel ratio).
|
|
22
|
+
struct GPUDrawElementImageDestination {
|
|
23
|
+
std::shared_ptr<GPUImageCopyTextureTagged> info;
|
|
24
|
+
std::optional<std::shared_ptr<GPUExtent3D>> size;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
} // namespace rnwgpu
|
|
28
|
+
|
|
29
|
+
namespace rnwgpu {
|
|
30
|
+
|
|
31
|
+
template <>
|
|
32
|
+
struct JSIConverter<std::shared_ptr<rnwgpu::GPUDrawElementImageDestination>> {
|
|
33
|
+
static std::shared_ptr<rnwgpu::GPUDrawElementImageDestination>
|
|
34
|
+
fromJSI(jsi::Runtime &runtime, const jsi::Value &arg, bool outOfBounds) {
|
|
35
|
+
auto result = std::make_unique<rnwgpu::GPUDrawElementImageDestination>();
|
|
36
|
+
if (outOfBounds || !arg.isObject()) {
|
|
37
|
+
throw std::runtime_error(
|
|
38
|
+
"drawElementImageToTexture: expected a destination dictionary");
|
|
39
|
+
}
|
|
40
|
+
result->info =
|
|
41
|
+
JSIConverter<std::shared_ptr<GPUImageCopyTextureTagged>>::fromJSI(
|
|
42
|
+
runtime, arg, false);
|
|
43
|
+
auto obj = arg.getObject(runtime);
|
|
44
|
+
if (obj.hasProperty(runtime, "size")) {
|
|
45
|
+
result->size =
|
|
46
|
+
JSIConverter<std::optional<std::shared_ptr<GPUExtent3D>>>::fromJSI(
|
|
47
|
+
runtime, obj.getProperty(runtime, "size"), false);
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static jsi::Value
|
|
53
|
+
toJSI(jsi::Runtime &runtime,
|
|
54
|
+
std::shared_ptr<rnwgpu::GPUDrawElementImageDestination> arg) {
|
|
55
|
+
throw std::runtime_error(
|
|
56
|
+
"Invalid GPUDrawElementImageDestination::toJSI()");
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
} // namespace rnwgpu
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <memory>
|
|
4
|
+
#include <optional>
|
|
5
|
+
#include <string>
|
|
6
|
+
|
|
7
|
+
#include "JSIConverter.h"
|
|
8
|
+
|
|
9
|
+
namespace jsi = facebook::jsi;
|
|
10
|
+
|
|
11
|
+
namespace rnwgpu {
|
|
12
|
+
|
|
13
|
+
// The `source` argument of GPUQueue.drawElementImageToTexture, modeled on the
|
|
14
|
+
// html-in-canvas proposal's GPUDrawElementImageSource. On the web `source` is
|
|
15
|
+
// an Element; here it is a native React Native view, identified by its React
|
|
16
|
+
// tag. The optional rectangle selects the part of the view to draw, in the
|
|
17
|
+
// view's own coordinate space and in density-independent points (React Native
|
|
18
|
+
// layout units); it defaults to the whole view.
|
|
19
|
+
struct GPUDrawElementImageSource {
|
|
20
|
+
int viewTag = 0; // resolved from `source`
|
|
21
|
+
std::optional<double> sourceX; // float
|
|
22
|
+
std::optional<double> sourceY; // float
|
|
23
|
+
std::optional<double> sourceWidth; // float
|
|
24
|
+
std::optional<double> sourceHeight; // float
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
} // namespace rnwgpu
|
|
28
|
+
|
|
29
|
+
namespace rnwgpu {
|
|
30
|
+
|
|
31
|
+
template <>
|
|
32
|
+
struct JSIConverter<std::shared_ptr<rnwgpu::GPUDrawElementImageSource>> {
|
|
33
|
+
// Resolve `source` to a React tag. Accepted shapes:
|
|
34
|
+
// - a number: the tag itself (what findNodeHandle returns)
|
|
35
|
+
// - a React Native host component instance: on the New Architecture these
|
|
36
|
+
// carry the tag as `__nativeTag` (ReactFabricHostComponent /
|
|
37
|
+
// ReactNativeElement), which is also what findNodeHandle reads
|
|
38
|
+
// - a ref object (`{ current }`) wrapping either of the above
|
|
39
|
+
// The JS wrapper on the main runtime additionally runs findNodeHandle so
|
|
40
|
+
// class components and legacy instances resolve too; this native fallback is
|
|
41
|
+
// what worklet runtimes (no findNodeHandle) get.
|
|
42
|
+
static int resolveViewTag(jsi::Runtime &runtime, const jsi::Value &value,
|
|
43
|
+
int depth = 0) {
|
|
44
|
+
if (value.isNumber()) {
|
|
45
|
+
return static_cast<int>(value.getNumber());
|
|
46
|
+
}
|
|
47
|
+
if (value.isObject() && depth < 2) {
|
|
48
|
+
auto obj = value.getObject(runtime);
|
|
49
|
+
if (obj.hasProperty(runtime, "__nativeTag")) {
|
|
50
|
+
auto tag = obj.getProperty(runtime, "__nativeTag");
|
|
51
|
+
if (tag.isNumber()) {
|
|
52
|
+
return static_cast<int>(tag.getNumber());
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (obj.hasProperty(runtime, "current")) {
|
|
56
|
+
return resolveViewTag(runtime, obj.getProperty(runtime, "current"),
|
|
57
|
+
depth + 1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
throw std::runtime_error(
|
|
61
|
+
"drawElementImageToTexture: `source` must be a React Native view "
|
|
62
|
+
"(a host component ref, its instance, or the tag returned by "
|
|
63
|
+
"findNodeHandle)");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static std::shared_ptr<rnwgpu::GPUDrawElementImageSource>
|
|
67
|
+
fromJSI(jsi::Runtime &runtime, const jsi::Value &arg, bool outOfBounds) {
|
|
68
|
+
auto result = std::make_unique<rnwgpu::GPUDrawElementImageSource>();
|
|
69
|
+
if (outOfBounds || !arg.isObject()) {
|
|
70
|
+
throw std::runtime_error(
|
|
71
|
+
"drawElementImageToTexture: expected a source dictionary");
|
|
72
|
+
}
|
|
73
|
+
auto obj = arg.getObject(runtime);
|
|
74
|
+
if (!obj.hasProperty(runtime, "source")) {
|
|
75
|
+
throw std::runtime_error(
|
|
76
|
+
"drawElementImageToTexture: `source.source` is required");
|
|
77
|
+
}
|
|
78
|
+
result->viewTag =
|
|
79
|
+
resolveViewTag(runtime, obj.getProperty(runtime, "source"));
|
|
80
|
+
if (obj.hasProperty(runtime, "sourceX")) {
|
|
81
|
+
result->sourceX = JSIConverter<std::optional<double>>::fromJSI(
|
|
82
|
+
runtime, obj.getProperty(runtime, "sourceX"), false);
|
|
83
|
+
}
|
|
84
|
+
if (obj.hasProperty(runtime, "sourceY")) {
|
|
85
|
+
result->sourceY = JSIConverter<std::optional<double>>::fromJSI(
|
|
86
|
+
runtime, obj.getProperty(runtime, "sourceY"), false);
|
|
87
|
+
}
|
|
88
|
+
if (obj.hasProperty(runtime, "sourceWidth")) {
|
|
89
|
+
result->sourceWidth = JSIConverter<std::optional<double>>::fromJSI(
|
|
90
|
+
runtime, obj.getProperty(runtime, "sourceWidth"), false);
|
|
91
|
+
}
|
|
92
|
+
if (obj.hasProperty(runtime, "sourceHeight")) {
|
|
93
|
+
result->sourceHeight = JSIConverter<std::optional<double>>::fromJSI(
|
|
94
|
+
runtime, obj.getProperty(runtime, "sourceHeight"), false);
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static jsi::Value
|
|
100
|
+
toJSI(jsi::Runtime &runtime,
|
|
101
|
+
std::shared_ptr<rnwgpu::GPUDrawElementImageSource> arg) {
|
|
102
|
+
throw std::runtime_error("Invalid GPUDrawElementImageSource::toJSI()");
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
} // namespace rnwgpu
|
|
@@ -12,15 +12,24 @@
|
|
|
12
12
|
|
|
13
13
|
#include "GPUOrigin2D.h"
|
|
14
14
|
#include "ImageBitmap.h"
|
|
15
|
+
#include "VideoFrame.h"
|
|
15
16
|
|
|
16
17
|
namespace jsi = facebook::jsi;
|
|
17
18
|
|
|
18
19
|
namespace rnwgpu {
|
|
19
20
|
|
|
21
|
+
// GPUCopyExternalImageSourceInfo. The source is either an ImageBitmap (the
|
|
22
|
+
// spec's CPU upload) or, as a React Native extension, a NativeVideoFrame that
|
|
23
|
+
// is rendered into the destination through an external texture (see
|
|
24
|
+
// VideoFrameBlit). `rotation` and `mirrored` only apply to a native frame and
|
|
25
|
+
// mirror the extension of GPUExternalTextureDescriptor.
|
|
20
26
|
struct GPUImageCopyExternalImage {
|
|
21
|
-
std::shared_ptr<ImageBitmap> source;
|
|
27
|
+
std::shared_ptr<ImageBitmap> source;
|
|
28
|
+
std::shared_ptr<VideoFrame> videoFrame;
|
|
22
29
|
std::optional<std::shared_ptr<GPUOrigin2D>> origin; // GPUOrigin2DStrict
|
|
23
30
|
std::optional<bool> flipY; // boolean
|
|
31
|
+
std::optional<double> rotation;
|
|
32
|
+
std::optional<bool> mirrored;
|
|
24
33
|
};
|
|
25
34
|
|
|
26
35
|
} // namespace rnwgpu
|
|
@@ -36,8 +45,14 @@ struct JSIConverter<std::shared_ptr<rnwgpu::GPUImageCopyExternalImage>> {
|
|
|
36
45
|
auto obj = arg.getObject(runtime);
|
|
37
46
|
if (obj.hasProperty(runtime, "source")) {
|
|
38
47
|
auto prop = obj.getProperty(runtime, "source");
|
|
39
|
-
|
|
40
|
-
runtime
|
|
48
|
+
if (prop.isObject() &&
|
|
49
|
+
prop.getObject(runtime).hasNativeState<VideoFrame>(runtime)) {
|
|
50
|
+
result->videoFrame =
|
|
51
|
+
prop.getObject(runtime).getNativeState<VideoFrame>(runtime);
|
|
52
|
+
} else {
|
|
53
|
+
result->source = JSIConverter<std::shared_ptr<ImageBitmap>>::fromJSI(
|
|
54
|
+
runtime, prop, false);
|
|
55
|
+
}
|
|
41
56
|
}
|
|
42
57
|
if (obj.hasProperty(runtime, "origin")) {
|
|
43
58
|
auto prop = obj.getProperty(runtime, "origin");
|
|
@@ -49,6 +64,18 @@ struct JSIConverter<std::shared_ptr<rnwgpu::GPUImageCopyExternalImage>> {
|
|
|
49
64
|
auto prop = obj.getProperty(runtime, "flipY");
|
|
50
65
|
result->flipY = JSIConverter<bool>::fromJSI(runtime, prop, false);
|
|
51
66
|
}
|
|
67
|
+
if (obj.hasProperty(runtime, "rotation")) {
|
|
68
|
+
auto prop = obj.getProperty(runtime, "rotation");
|
|
69
|
+
if (prop.isNumber()) {
|
|
70
|
+
result->rotation = prop.asNumber();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (obj.hasProperty(runtime, "mirrored")) {
|
|
74
|
+
auto prop = obj.getProperty(runtime, "mirrored");
|
|
75
|
+
if (prop.isBool()) {
|
|
76
|
+
result->mirrored = prop.getBool();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
52
79
|
}
|
|
53
80
|
|
|
54
81
|
return result;
|
|
@@ -30,10 +30,10 @@ const importDevice = pointer => {
|
|
|
30
30
|
* ownership of one reference: the returned texture releases it when destroyed.
|
|
31
31
|
*
|
|
32
32
|
* Pair with producers that hand out a +1 reference, such as
|
|
33
|
-
* `Skia.Image.
|
|
33
|
+
* `Skia.Image.MakeGPUTextureFromImage()`:
|
|
34
34
|
*
|
|
35
35
|
* ```ts
|
|
36
|
-
* const texture = adoptTexture(Skia.Image.
|
|
36
|
+
* const texture = adoptTexture(Skia.Image.MakeGPUTextureFromImage(image));
|
|
37
37
|
* ```
|
|
38
38
|
*/
|
|
39
39
|
exports.importDevice = importDevice;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_main","require","Object","keys","forEach","key","prototype","hasOwnProperty","call","_exportNames","exports","defineProperty","enumerable","get","_constants","_install"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"names":["_main","require","Object","keys","forEach","key","prototype","hasOwnProperty","call","_exportNames","exports","defineProperty","enumerable","get","_constants","_install"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;;;;;;;;;;AAkBA,IAAAA,KAAA,GAAAC,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAH,KAAA,EAAAI,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAH,MAAA,CAAAI,SAAA,CAAAC,cAAA,CAAAC,IAAA,CAAAC,YAAA,EAAAJ,GAAA;EAAA,IAAAA,GAAA,IAAAK,OAAA,IAAAA,OAAA,CAAAL,GAAA,MAAAL,KAAA,CAAAK,GAAA;EAAAH,MAAA,CAAAS,cAAA,CAAAD,OAAA,EAAAL,GAAA;IAAAO,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAb,KAAA,CAAAK,GAAA;IAAA;EAAA;AAAA;AACA,IAAAS,UAAA,GAAAb,OAAA;AAAAC,MAAA,CAAAC,IAAA,CAAAW,UAAA,EAAAV,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAH,MAAA,CAAAI,SAAA,CAAAC,cAAA,CAAAC,IAAA,CAAAC,YAAA,EAAAJ,GAAA;EAAA,IAAAA,GAAA,IAAAK,OAAA,IAAAA,OAAA,CAAAL,GAAA,MAAAS,UAAA,CAAAT,GAAA;EAAAH,MAAA,CAAAS,cAAA,CAAAD,OAAA,EAAAL,GAAA;IAAAO,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAC,UAAA,CAAAT,GAAA;IAAA;EAAA;AAAA;AACA,IAAAU,QAAA,GAAAd,OAAA","ignoreList":[]}
|