@shopify/react-native-skia 2.6.4 → 2.6.5
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/CMakeLists.txt +7 -4
- package/android/build.gradle +22 -3
- package/android/cpp/rnskia-android/RNSkAndroidPlatformContext.h +62 -0
- package/apple/RNSkApplePlatformContext.h +2 -0
- package/apple/RNSkApplePlatformContext.mm +71 -0
- package/apple/RNWebGPUAppleNativeBuffer.mm +33 -0
- package/cpp/api/JsiNativeBuffer.h +9 -1
- package/cpp/api/JsiSkAnimatedImageFactory.h +1 -1
- package/cpp/api/JsiSkApi.h +2 -2
- package/cpp/api/JsiSkCanvas.h +1 -1
- package/cpp/api/JsiSkDataFactory.h +1 -1
- package/cpp/api/JsiSkFont.h +1 -1
- package/cpp/api/JsiSkFontMgr.h +1 -1
- package/cpp/api/JsiSkHostObjects.h +3 -3
- package/cpp/api/JsiSkImage.h +2 -2
- package/cpp/api/JsiSkImageFactory.h +2 -2
- package/cpp/api/JsiSkPath.h +1 -1
- package/cpp/api/JsiSkPathFactory.h +1 -1
- package/cpp/api/JsiSkSurface.h +13 -5
- package/cpp/api/JsiSkTypeface.h +1 -1
- package/cpp/api/JsiSkTypefaceFontProvider.h +1 -1
- package/cpp/api/JsiSkiaContext.h +2 -2
- package/cpp/api/JsiTextureInfo.h +1 -1
- package/cpp/api/JsiVideo.h +2 -2
- package/cpp/api/recorder/Drawings.h +1 -1
- package/cpp/api/recorder/JsiRecorder.h +4 -4
- package/cpp/api/recorder/RNRecorder.h +1 -1
- package/cpp/jsi/ViewProperty.h +1 -1
- package/cpp/rnskia/RNDawnContext.h +13 -0
- package/cpp/rnskia/RNDawnUtils.h +11 -1
- package/cpp/rnskia/RNSkJsiViewApi.h +2 -2
- package/cpp/rnskia/RNSkManager.cpp +88 -2
- package/cpp/rnskia/RNSkPictureView.h +4 -4
- package/cpp/rnskia/RNSkPlatformContext.h +7 -0
- package/cpp/rnskia/RNSkView.h +9 -6
- package/cpp/rnwgpu/ArrayBuffer.h +51 -7
- package/cpp/rnwgpu/api/AppleNativeBuffer.h +22 -0
- package/cpp/rnwgpu/api/Convertors.h +33 -11
- package/cpp/rnwgpu/api/GPUAdapter.cpp +28 -2
- package/cpp/rnwgpu/api/GPUBuffer.h +1 -1
- package/cpp/rnwgpu/api/GPUDevice.cpp +29 -2
- package/cpp/rnwgpu/api/GPUDevice.h +6 -0
- package/cpp/rnwgpu/api/GPUExternalTexture.cpp +139 -0
- package/cpp/rnwgpu/api/GPUExternalTexture.h +52 -2
- package/cpp/rnwgpu/api/GPUQueue.cpp +50 -45
- package/cpp/rnwgpu/api/GPUSharedTextureMemory.cpp +82 -0
- package/cpp/rnwgpu/api/GPUSharedTextureMemory.h +70 -0
- package/cpp/rnwgpu/api/ImageBitmap.h +62 -0
- package/cpp/rnwgpu/api/NativeBufferUtils.h +87 -0
- package/cpp/rnwgpu/api/descriptors/GPUBindGroupEntry.h +4 -1
- package/cpp/rnwgpu/api/descriptors/GPUCanvasConfiguration.h +1 -1
- package/cpp/rnwgpu/api/descriptors/GPUDawnTogglesDescriptor.h +56 -0
- package/cpp/rnwgpu/api/descriptors/GPUDeviceDescriptor.h +10 -0
- package/cpp/rnwgpu/api/descriptors/GPUExternalTextureDescriptor.h +43 -24
- package/cpp/rnwgpu/api/descriptors/GPUImageCopyExternalImage.h +9 -9
- package/cpp/rnwgpu/api/descriptors/GPUImageCopyTexture.h +1 -1
- package/cpp/rnwgpu/api/descriptors/GPUImageCopyTextureTagged.h +2 -2
- package/cpp/rnwgpu/api/descriptors/GPUSharedTextureMemoryDescriptor.h +73 -0
- package/cpp/rnwgpu/api/descriptors/GPUTextureDescriptor.h +1 -1
- package/cpp/rnwgpu/api/descriptors/GPUUncapturedErrorEventInit.h +1 -1
- package/cpp/skia/include/core/SkRegion.h +10 -5
- package/cpp/skia/include/effects/SkRuntimeEffect.h +2 -2
- package/cpp/skia/include/gpu/graphite/Context.h +1 -1
- package/lib/commonjs/skia/types/NativeBuffer/NativeBufferFactory.d.ts +10 -1
- package/lib/commonjs/skia/types/NativeBuffer/NativeBufferFactory.js.map +1 -1
- package/lib/commonjs/skia/types/WebGPU.d.ts +88 -0
- package/lib/commonjs/skia/types/WebGPU.js +6 -0
- package/lib/commonjs/skia/types/WebGPU.js.map +1 -0
- package/lib/commonjs/skia/types/index.d.ts +1 -0
- package/lib/commonjs/skia/types/index.js +11 -0
- package/lib/commonjs/skia/types/index.js.map +1 -1
- package/lib/commonjs/skia/web/JsiSkNativeBufferFactory.d.ts +1 -0
- package/lib/commonjs/skia/web/JsiSkNativeBufferFactory.js +19 -0
- package/lib/commonjs/skia/web/JsiSkNativeBufferFactory.js.map +1 -1
- package/lib/commonjs/skia/web/JsiSkPath.js.map +1 -1
- package/lib/module/skia/types/NativeBuffer/NativeBufferFactory.d.ts +10 -1
- package/lib/module/skia/types/NativeBuffer/NativeBufferFactory.js.map +1 -1
- package/lib/module/skia/types/WebGPU.d.ts +88 -0
- package/lib/module/skia/types/WebGPU.js +2 -0
- package/lib/module/skia/types/WebGPU.js.map +1 -0
- package/lib/module/skia/types/index.d.ts +1 -0
- package/lib/module/skia/types/index.js +1 -0
- package/lib/module/skia/types/index.js.map +1 -1
- package/lib/module/skia/web/JsiSkNativeBufferFactory.d.ts +1 -0
- package/lib/module/skia/web/JsiSkNativeBufferFactory.js +19 -0
- package/lib/module/skia/web/JsiSkNativeBufferFactory.js.map +1 -1
- package/lib/module/skia/web/JsiSkPath.js.map +1 -1
- package/lib/module/web/LoadSkiaWeb.js +1 -2
- package/lib/module/web/LoadSkiaWeb.js.map +1 -1
- package/lib/typescript/lib/commonjs/skia/types/WebGPU.d.ts +1 -0
- package/lib/typescript/lib/commonjs/skia/web/JsiSkNativeBufferFactory.d.ts +1 -0
- package/lib/typescript/lib/module/skia/types/WebGPU.d.ts +1 -0
- package/lib/typescript/lib/module/skia/types/index.d.ts +1 -0
- package/lib/typescript/lib/module/skia/web/JsiSkNativeBufferFactory.d.ts +1 -0
- package/lib/typescript/src/skia/types/NativeBuffer/NativeBufferFactory.d.ts +10 -1
- package/lib/typescript/src/skia/types/WebGPU.d.ts +88 -0
- package/lib/typescript/src/skia/types/index.d.ts +1 -0
- package/lib/typescript/src/skia/web/JsiSkNativeBufferFactory.d.ts +1 -0
- package/package.json +10 -8
- package/react-native-skia.podspec +59 -7
- package/src/skia/types/NativeBuffer/NativeBufferFactory.ts +10 -1
- package/src/skia/types/WebGPU.ts +108 -0
- package/src/skia/types/index.ts +1 -0
- package/src/skia/web/JsiSkNativeBufferFactory.ts +20 -0
- package/src/skia/web/JsiSkPath.ts +8 -2
- package/scripts/install-libs.js +0 -133
package/cpp/rnskia/RNDawnUtils.h
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
#include "dawn/dawn_proc.h"
|
|
6
6
|
#include "dawn/native/DawnNative.h"
|
|
7
7
|
|
|
8
|
-
#include "RNSkLog.h"
|
|
8
|
+
#include "utils/RNSkLog.h"
|
|
9
9
|
#include "include/core/SkColorType.h"
|
|
10
10
|
#include "include/gpu/graphite/dawn/DawnBackendContext.h"
|
|
11
11
|
|
|
@@ -206,12 +206,22 @@ createDawnBackendContext(dawn::native::Instance *instance) {
|
|
|
206
206
|
wgpu::FeatureName::ImplicitDeviceSynchronization,
|
|
207
207
|
#ifdef __APPLE__
|
|
208
208
|
wgpu::FeatureName::SharedTextureMemoryIOSurface,
|
|
209
|
+
// Required to call SharedTextureMemory::EndAccess on Metal (it exports a
|
|
210
|
+
// MTLSharedEvent fence). importExternalTexture / importSharedTextureMemory
|
|
211
|
+
// end the access window after submit; without this EndAccess errors with
|
|
212
|
+
// "Required feature (SharedFenceMTLSharedEvent) is missing". Safe here
|
|
213
|
+
// because we always queue.submit() before EndAccess (the secondary device
|
|
214
|
+
// omits it on purpose — its camera path doesn't commit first).
|
|
215
|
+
wgpu::FeatureName::SharedFenceMTLSharedEvent,
|
|
209
216
|
wgpu::FeatureName::DawnMultiPlanarFormats,
|
|
210
217
|
wgpu::FeatureName::MultiPlanarFormatP010,
|
|
211
218
|
wgpu::FeatureName::MultiPlanarFormatP210,
|
|
212
219
|
wgpu::FeatureName::MultiPlanarFormatExtendedUsages,
|
|
213
220
|
#else
|
|
214
221
|
wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer,
|
|
222
|
+
// Vulkan equivalent of the above: EndAccess exports a sync-fd fence.
|
|
223
|
+
wgpu::FeatureName::SharedFenceSyncFD,
|
|
224
|
+
wgpu::FeatureName::SharedFenceVkSemaphoreOpaqueFD,
|
|
215
225
|
#endif
|
|
216
226
|
};
|
|
217
227
|
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
#include <utility>
|
|
10
10
|
#include <vector>
|
|
11
11
|
|
|
12
|
-
#include "JsiHostObject.h"
|
|
12
|
+
#include "jsi/JsiHostObject.h"
|
|
13
13
|
#include "RNSkPictureView.h"
|
|
14
14
|
#include "RNSkPlatformContext.h"
|
|
15
15
|
#include "RNSkView.h"
|
|
16
|
-
#include "ViewProperty.h"
|
|
16
|
+
#include "jsi/ViewProperty.h"
|
|
17
17
|
#include <jsi/jsi.h>
|
|
18
18
|
|
|
19
19
|
namespace RNSkia {
|
|
@@ -5,22 +5,29 @@
|
|
|
5
5
|
|
|
6
6
|
#include <jsi/jsi.h>
|
|
7
7
|
|
|
8
|
-
#include "JsiSkApi.h"
|
|
8
|
+
#include "api/JsiSkApi.h"
|
|
9
9
|
#include "RNSkJsiViewApi.h"
|
|
10
10
|
#include "RNSkView.h"
|
|
11
11
|
|
|
12
|
-
#include "RuntimeAwareCache.h"
|
|
12
|
+
#include "jsi/RuntimeAwareCache.h"
|
|
13
13
|
|
|
14
14
|
#ifdef SK_GRAPHITE
|
|
15
15
|
#include "RNDawnContext.h"
|
|
16
|
+
#include "rnwgpu/ArrayBuffer.h"
|
|
16
17
|
#include "rnwgpu/api/GPU.h"
|
|
17
18
|
#include "rnwgpu/api/GPUUncapturedErrorEvent.h"
|
|
19
|
+
#include "rnwgpu/api/ImageBitmap.h"
|
|
18
20
|
#include "rnwgpu/api/RNWebGPU.h"
|
|
19
21
|
#include "rnwgpu/api/descriptors/GPUBufferUsage.h"
|
|
20
22
|
#include "rnwgpu/api/descriptors/GPUColorWrite.h"
|
|
21
23
|
#include "rnwgpu/api/descriptors/GPUMapMode.h"
|
|
22
24
|
#include "rnwgpu/api/descriptors/GPUShaderStage.h"
|
|
23
25
|
#include "rnwgpu/api/descriptors/GPUTextureUsage.h"
|
|
26
|
+
#include "jsi2/Promise.h"
|
|
27
|
+
|
|
28
|
+
#include "include/core/SkData.h"
|
|
29
|
+
#include "include/core/SkImage.h"
|
|
30
|
+
#include "include/core/SkImageInfo.h"
|
|
24
31
|
#endif
|
|
25
32
|
|
|
26
33
|
namespace RNSkia {
|
|
@@ -114,6 +121,85 @@ void RNSkManager::installBindings() {
|
|
|
114
121
|
auto rnWebGPU = std::make_shared<rnwgpu::RNWebGPU>(gpu, nullptr);
|
|
115
122
|
_jsRuntime->global().setProperty(
|
|
116
123
|
*_jsRuntime, "RNWebGPU", rnwgpu::RNWebGPU::create(*_jsRuntime, rnWebGPU));
|
|
124
|
+
|
|
125
|
+
// DRAFT — compile-unverified. Install the ImageBitmap constructor (so
|
|
126
|
+
// `instanceof ImageBitmap` works) and a global createImageBitmap() that
|
|
127
|
+
// accepts the non-standard encoded-BufferSource overload.
|
|
128
|
+
//
|
|
129
|
+
// The BufferSource is run through the shared rnwgpu::ArrayBuffer converter,
|
|
130
|
+
// which validates byteOffset/byteLength against the backing buffer and throws
|
|
131
|
+
// synchronously on a spoofed / out-of-bounds view — so createImageBitmap()
|
|
132
|
+
// rejects rather than reading out of bounds (see ArrayBufferBounds /
|
|
133
|
+
// ImageBitmapBounds specs). Decoding uses Skia's own codec; no platform image
|
|
134
|
+
// decoder is needed.
|
|
135
|
+
rnwgpu::ImageBitmap::installConstructor(*_jsRuntime);
|
|
136
|
+
_jsRuntime->global().setProperty(
|
|
137
|
+
*_jsRuntime, "createImageBitmap",
|
|
138
|
+
jsi::Function::createFromHostFunction(
|
|
139
|
+
*_jsRuntime,
|
|
140
|
+
jsi::PropNameID::forAscii(*_jsRuntime, "createImageBitmap"), 1,
|
|
141
|
+
[](jsi::Runtime &rt, const jsi::Value & /*thisVal*/,
|
|
142
|
+
const jsi::Value *args, size_t count) -> jsi::Value {
|
|
143
|
+
if (count < 1 || !args[0].isObject()) {
|
|
144
|
+
throw jsi::JSError(
|
|
145
|
+
rt, "createImageBitmap requires a BufferSource argument");
|
|
146
|
+
}
|
|
147
|
+
// Only the encoded ArrayBuffer / ArrayBufferView overload is
|
|
148
|
+
// supported here. Anything else (Blob, ImageData, …) is rejected.
|
|
149
|
+
auto obj = args[0].getObject(rt);
|
|
150
|
+
bool isBufferSource = obj.isArrayBuffer(rt);
|
|
151
|
+
if (!isBufferSource && obj.hasProperty(rt, "buffer")) {
|
|
152
|
+
auto bufferProp = obj.getProperty(rt, "buffer");
|
|
153
|
+
isBufferSource =
|
|
154
|
+
bufferProp.isObject() &&
|
|
155
|
+
bufferProp.getObject(rt).isArrayBuffer(rt);
|
|
156
|
+
}
|
|
157
|
+
if (!isBufferSource) {
|
|
158
|
+
throw jsi::JSError(rt, "createImageBitmap: unsupported source "
|
|
159
|
+
"(expected an ArrayBuffer or TypedArray "
|
|
160
|
+
"of encoded image bytes)");
|
|
161
|
+
}
|
|
162
|
+
// Validates bounds and THROWS synchronously on a spoofed view, so
|
|
163
|
+
// the bad pointer never reaches the copy below.
|
|
164
|
+
auto buffer =
|
|
165
|
+
rnwgpu::JSIConverter<std::shared_ptr<rnwgpu::ArrayBuffer>>::
|
|
166
|
+
fromJSI(rt, args[0], false);
|
|
167
|
+
// Copy the encoded bytes off the JS-owned ArrayBuffer.
|
|
168
|
+
const uint8_t *bytes = buffer->data();
|
|
169
|
+
std::vector<uint8_t> encoded(bytes, bytes + buffer->size());
|
|
170
|
+
|
|
171
|
+
return rnwgpu::Promise::createPromise(
|
|
172
|
+
rt, [encoded = std::move(encoded)](
|
|
173
|
+
jsi::Runtime &runtime,
|
|
174
|
+
std::shared_ptr<rnwgpu::Promise> promise) mutable {
|
|
175
|
+
auto skData =
|
|
176
|
+
SkData::MakeWithCopy(encoded.data(), encoded.size());
|
|
177
|
+
auto image = SkImages::DeferredFromEncodedData(skData);
|
|
178
|
+
if (image == nullptr) {
|
|
179
|
+
promise->reject(
|
|
180
|
+
"createImageBitmap: failed to decode image data");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const int w = image->width();
|
|
184
|
+
const int h = image->height();
|
|
185
|
+
auto info =
|
|
186
|
+
SkImageInfo::Make(w, h, kRGBA_8888_SkColorType,
|
|
187
|
+
kUnpremul_SkAlphaType);
|
|
188
|
+
std::vector<uint8_t> pixels(info.computeMinByteSize());
|
|
189
|
+
// nullptr context: decode/read on the CPU (raster).
|
|
190
|
+
if (!image->readPixels(nullptr, info, pixels.data(),
|
|
191
|
+
info.minRowBytes(), 0, 0)) {
|
|
192
|
+
promise->reject(
|
|
193
|
+
"createImageBitmap: failed to read decoded pixels");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
auto bitmap = std::make_shared<rnwgpu::ImageBitmap>(
|
|
197
|
+
std::move(pixels), static_cast<size_t>(w),
|
|
198
|
+
static_cast<size_t>(h));
|
|
199
|
+
promise->resolve(
|
|
200
|
+
rnwgpu::ImageBitmap::create(runtime, bitmap));
|
|
201
|
+
});
|
|
202
|
+
}));
|
|
117
203
|
#endif
|
|
118
204
|
}
|
|
119
205
|
} // namespace RNSkia
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
#include <jsi/jsi.h>
|
|
12
12
|
|
|
13
13
|
#include "RNSkView.h"
|
|
14
|
-
#include "ViewProperty.h"
|
|
14
|
+
#include "jsi/ViewProperty.h"
|
|
15
15
|
|
|
16
|
-
#include "JsiSkPicture.h"
|
|
17
|
-
#include "RNSkLog.h"
|
|
16
|
+
#include "api/JsiSkPicture.h"
|
|
17
|
+
#include "utils/RNSkLog.h"
|
|
18
18
|
#include "RNSkPlatformContext.h"
|
|
19
|
-
#include "RNSkTimingInfo.h"
|
|
19
|
+
#include "utils/RNSkTimingInfo.h"
|
|
20
20
|
|
|
21
21
|
#pragma clang diagnostic push
|
|
22
22
|
#pragma clang diagnostic ignored "-Wdocumentation"
|
|
@@ -141,6 +141,13 @@ public:
|
|
|
141
141
|
|
|
142
142
|
virtual uint64_t makeNativeBuffer(sk_sp<SkImage> image) = 0;
|
|
143
143
|
|
|
144
|
+
// Allocate a platform native buffer (IOSurface on Apple, AHardwareBuffer on
|
|
145
|
+
// Android) of the given size filled with a procedural test pattern (RGB
|
|
146
|
+
// gradient + diagonal stripes), entirely on the CPU. Intended for examples
|
|
147
|
+
// and tests that need a buffer to feed into importExternalTexture without a
|
|
148
|
+
// camera/video source. Release it with releaseNativeBuffer().
|
|
149
|
+
virtual uint64_t makeTestNativeBuffer(int width, int height) = 0;
|
|
150
|
+
|
|
144
151
|
virtual std::shared_ptr<RNSkVideo> createVideo(const std::string &url) = 0;
|
|
145
152
|
|
|
146
153
|
/**
|
package/cpp/rnskia/RNSkView.h
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
#include <vector>
|
|
8
8
|
|
|
9
9
|
#include "RNSkPlatformContext.h"
|
|
10
|
-
#include "ViewProperty.h"
|
|
10
|
+
#include "jsi/ViewProperty.h"
|
|
11
11
|
|
|
12
|
-
#include "JsiSkImage.h"
|
|
13
|
-
#include "JsiSkPoint.h"
|
|
14
|
-
#include "JsiSkRect.h"
|
|
12
|
+
#include "api/JsiSkImage.h"
|
|
13
|
+
#include "api/JsiSkPoint.h"
|
|
14
|
+
#include "api/JsiSkRect.h"
|
|
15
15
|
|
|
16
16
|
#pragma clang diagnostic push
|
|
17
17
|
#pragma clang diagnostic ignored "-Wdocumentation"
|
|
@@ -94,8 +94,11 @@ public:
|
|
|
94
94
|
image = _surface->makeImageSnapshot();
|
|
95
95
|
}
|
|
96
96
|
#if defined(SK_GRAPHITE)
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
// Only Graphite-backed surfaces have a recorder to snap/submit; a raster
|
|
98
|
+
// surface's snapshot is already a valid CPU image.
|
|
99
|
+
if (auto *recorder = _surface->recorder()) {
|
|
100
|
+
DawnContext::getInstance().submitRecording(recorder->snap().get());
|
|
101
|
+
}
|
|
99
102
|
return DawnContext::getInstance().MakeRasterImage(image);
|
|
100
103
|
#else
|
|
101
104
|
auto grContext = _context->getDirectContext();
|
package/cpp/rnwgpu/ArrayBuffer.h
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
#include <jsi/jsi.h>
|
|
3
3
|
|
|
4
|
+
#include <cmath>
|
|
5
|
+
#include <cstdint>
|
|
4
6
|
#include <memory>
|
|
5
7
|
|
|
6
8
|
#include "jsi2/JSIConverter.h"
|
|
@@ -47,16 +49,58 @@ template <> struct JSIConverter<std::shared_ptr<ArrayBuffer>> {
|
|
|
47
49
|
if (bufferProp.isObject() &&
|
|
48
50
|
bufferProp.getObject(runtime).isArrayBuffer(runtime)) {
|
|
49
51
|
auto buff = bufferProp.getObject(runtime);
|
|
50
|
-
auto bytesPerElements =
|
|
51
|
-
obj.getProperty(runtime, "BYTES_PER_ELEMENT").asNumber();
|
|
52
52
|
auto arrayBuffer = buff.getArrayBuffer(runtime);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
const size_t bufferSize = arrayBuffer.size(runtime);
|
|
54
|
+
|
|
55
|
+
// byteOffset / byteLength are user-readable JS properties, not values
|
|
56
|
+
// the engine guarantees (unlike Dawn's node binding, which reads them
|
|
57
|
+
// off the engine's typed-array view). Read them as doubles so we can
|
|
58
|
+
// reject negative, non-integral, NaN/Inf, or oversized values before
|
|
59
|
+
// they wrap around when cast to size_t.
|
|
60
|
+
const double byteOffsetValue =
|
|
61
|
+
obj.getProperty(runtime, "byteOffset").asNumber();
|
|
62
|
+
const double byteLengthValue =
|
|
63
|
+
obj.getProperty(runtime, "byteLength").asNumber();
|
|
64
|
+
|
|
65
|
+
auto isValidByteIndex = [](double value) {
|
|
66
|
+
return std::isfinite(value) && value >= 0.0 &&
|
|
67
|
+
value <= static_cast<double>(SIZE_MAX) &&
|
|
68
|
+
std::floor(value) == value;
|
|
69
|
+
};
|
|
70
|
+
if (!isValidByteIndex(byteOffsetValue) ||
|
|
71
|
+
!isValidByteIndex(byteLengthValue)) {
|
|
72
|
+
throw std::runtime_error(
|
|
73
|
+
"ArrayBuffer::fromJSI: invalid byteOffset/byteLength");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const size_t byteOffset = static_cast<size_t>(byteOffsetValue);
|
|
77
|
+
const size_t byteLength = static_cast<size_t>(byteLengthValue);
|
|
78
|
+
|
|
79
|
+
// Overflow-safe bounds check: byteOffset + byteLength <= bufferSize.
|
|
80
|
+
if (byteOffset > bufferSize ||
|
|
81
|
+
byteLength > bufferSize - byteOffset) {
|
|
82
|
+
throw std::runtime_error(
|
|
83
|
+
"ArrayBuffer::fromJSI: view bounds [byteOffset, byteOffset + "
|
|
84
|
+
"byteLength) exceed the backing ArrayBuffer size");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// BYTES_PER_ELEMENT is absent on a DataView; default to 1. A spoofed
|
|
88
|
+
// object could report 0 (or a negative/NaN value), so clamp to a
|
|
89
|
+
// minimum of 1 to avoid a later division by zero in writeBuffer.
|
|
90
|
+
size_t bytesPerElements = 1;
|
|
91
|
+
if (obj.hasProperty(runtime, "BYTES_PER_ELEMENT")) {
|
|
92
|
+
auto bpe = obj.getProperty(runtime, "BYTES_PER_ELEMENT");
|
|
93
|
+
if (bpe.isNumber()) {
|
|
94
|
+
const double value = bpe.asNumber();
|
|
95
|
+
if (std::isfinite(value) && value >= 1.0) {
|
|
96
|
+
bytesPerElements = static_cast<size_t>(value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
57
101
|
return std::make_shared<ArrayBuffer>(
|
|
58
102
|
arrayBuffer.data(runtime) + byteOffset, byteLength,
|
|
59
|
-
|
|
103
|
+
bytesPerElements);
|
|
60
104
|
}
|
|
61
105
|
}
|
|
62
106
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#if defined(__APPLE__)
|
|
4
|
+
|
|
5
|
+
#include <cstdint>
|
|
6
|
+
|
|
7
|
+
namespace rnwgpu {
|
|
8
|
+
|
|
9
|
+
// Extract the backing IOSurface and dimensions from a CVPixelBufferRef pointer
|
|
10
|
+
// (the value Skia's NativeBuffer.MakeFromImage returns on Apple). The returned
|
|
11
|
+
// IOSurfaceRef is owned by the CVPixelBuffer; the caller must keep the
|
|
12
|
+
// CVPixelBuffer alive while the IOSurface is in use. Returns nullptr (and
|
|
13
|
+
// leaves the out-params at 0) if the buffer has no IOSurface.
|
|
14
|
+
//
|
|
15
|
+
// Defined in apple/RNWebGPUAppleNativeBuffer.mm (Objective-C++ so it can use
|
|
16
|
+
// CoreVideo, which isn't available from the cpp/ translation units).
|
|
17
|
+
void *GetIOSurfaceFromNativeBuffer(void *cvPixelBuffer, uint32_t *outWidth,
|
|
18
|
+
uint32_t *outHeight);
|
|
19
|
+
|
|
20
|
+
} // namespace rnwgpu
|
|
21
|
+
|
|
22
|
+
#endif // __APPLE__
|
|
@@ -252,13 +252,24 @@ public:
|
|
|
252
252
|
|
|
253
253
|
[[nodiscard]] bool Convert(wgpu::BindGroupLayoutEntry &out,
|
|
254
254
|
const GPUBindGroupLayoutEntry &in) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
255
|
+
out = {};
|
|
256
|
+
if (!Convert(out.binding, in.binding) ||
|
|
257
|
+
!Convert(out.visibility, in.visibility) ||
|
|
258
|
+
!Convert(out.buffer, in.buffer) || !Convert(out.sampler, in.sampler) ||
|
|
259
|
+
!Convert(out.texture, in.texture) ||
|
|
260
|
+
!Convert(out.storageTexture, in.storageTexture)) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
if (in.externalTexture.has_value() &&
|
|
264
|
+
in.externalTexture.value() != nullptr) {
|
|
265
|
+
// External texture layouts bind via a chained struct rather than a
|
|
266
|
+
// direct field on BindGroupLayoutEntry. The chained struct must outlive
|
|
267
|
+
// the BindGroupLayoutEntry until Device::CreateBindGroupLayout returns,
|
|
268
|
+
// so we allocate it on the Convertor's arena.
|
|
269
|
+
auto *chain = Allocate<wgpu::ExternalTextureBindingLayout>();
|
|
270
|
+
out.nextInChain = chain;
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
262
273
|
}
|
|
263
274
|
|
|
264
275
|
[[nodiscard]] bool Convert(wgpu::BlendComponent &out,
|
|
@@ -422,9 +433,11 @@ public:
|
|
|
422
433
|
}
|
|
423
434
|
|
|
424
435
|
[[nodiscard]] bool Convert(wgpu::ExternalTextureBindingLayout &out,
|
|
425
|
-
const GPUExternalTextureBindingLayout &in) {
|
|
426
|
-
// no
|
|
427
|
-
|
|
436
|
+
const GPUExternalTextureBindingLayout & /*in*/) {
|
|
437
|
+
// ExternalTextureBindingLayout carries no fields of its own; its presence
|
|
438
|
+
// (as a chained struct) is what marks the entry as an external texture.
|
|
439
|
+
out = {};
|
|
440
|
+
return true;
|
|
428
441
|
}
|
|
429
442
|
|
|
430
443
|
[[nodiscard]] bool Convert(wgpu::ConstantEntry &out, const std::string &key,
|
|
@@ -729,7 +742,16 @@ public:
|
|
|
729
742
|
out.buffer = buffer->get();
|
|
730
743
|
return true;
|
|
731
744
|
}
|
|
732
|
-
|
|
745
|
+
if (in.externalTexture != nullptr) {
|
|
746
|
+
// External textures bind via a chained struct rather than a direct field
|
|
747
|
+
// on BindGroupEntry. The chained struct must outlive the BindGroupEntry
|
|
748
|
+
// until Device::CreateBindGroup returns, so we allocate it on the
|
|
749
|
+
// Convertor's arena.
|
|
750
|
+
auto *chain = Allocate<wgpu::ExternalTextureBindingEntry>();
|
|
751
|
+
chain->externalTexture = in.externalTexture->get();
|
|
752
|
+
out.nextInChain = chain;
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
733
755
|
return false;
|
|
734
756
|
}
|
|
735
757
|
|
|
@@ -109,9 +109,35 @@ async::AsyncTaskHandle GPUAdapter::requestDevice(
|
|
|
109
109
|
deviceLostBinding,
|
|
110
110
|
creationRuntime](const async::AsyncTaskHandle::ResolveFunction &resolve,
|
|
111
111
|
const async::AsyncTaskHandle::RejectFunction &reject) {
|
|
112
|
-
|
|
112
|
+
// Build a local mutable copy so we can chain Dawn's device toggles.
|
|
113
|
+
// The toggle name strings are owned by `descriptor` (captured above),
|
|
114
|
+
// and the const char* / DawnTogglesDescriptor locals live for the
|
|
115
|
+
// whole synchronous RequestDevice call below, which is when Dawn reads
|
|
116
|
+
// the chained struct.
|
|
117
|
+
wgpu::DeviceDescriptor deviceDesc = aDescriptor;
|
|
118
|
+
wgpu::DawnTogglesDescriptor toggles{};
|
|
119
|
+
std::vector<const char *> enabledToggles;
|
|
120
|
+
std::vector<const char *> disabledToggles;
|
|
121
|
+
if (descriptor.has_value() && descriptor.value()->dawnToggles) {
|
|
122
|
+
const auto &dawnToggles = descriptor.value()->dawnToggles.value();
|
|
123
|
+
if (dawnToggles->enabledToggles) {
|
|
124
|
+
for (const auto &t : dawnToggles->enabledToggles.value()) {
|
|
125
|
+
enabledToggles.push_back(t.c_str());
|
|
126
|
+
}
|
|
127
|
+
toggles.enabledToggleCount = enabledToggles.size();
|
|
128
|
+
toggles.enabledToggles = enabledToggles.data();
|
|
129
|
+
}
|
|
130
|
+
if (dawnToggles->disabledToggles) {
|
|
131
|
+
for (const auto &t : dawnToggles->disabledToggles.value()) {
|
|
132
|
+
disabledToggles.push_back(t.c_str());
|
|
133
|
+
}
|
|
134
|
+
toggles.disabledToggleCount = disabledToggles.size();
|
|
135
|
+
toggles.disabledToggles = disabledToggles.data();
|
|
136
|
+
}
|
|
137
|
+
deviceDesc.nextInChain = &toggles;
|
|
138
|
+
}
|
|
113
139
|
_instance.RequestDevice(
|
|
114
|
-
&
|
|
140
|
+
&deviceDesc, wgpu::CallbackMode::AllowProcessEvents,
|
|
115
141
|
[asyncRunner = _async, resolve, reject, label, creationRuntime,
|
|
116
142
|
deviceLostBinding](wgpu::RequestDeviceStatus status,
|
|
117
143
|
wgpu::Device device,
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
#include <vector>
|
|
8
8
|
|
|
9
9
|
#include "Convertors.h"
|
|
10
|
+
#include "NativeBufferUtils.h"
|
|
10
11
|
#include "jsi2/JSIConverter.h"
|
|
11
12
|
|
|
12
13
|
#include "GPUFeatures.h"
|
|
@@ -234,8 +235,34 @@ std::shared_ptr<GPUPipelineLayout> GPUDevice::createPipelineLayout(
|
|
|
234
235
|
|
|
235
236
|
std::shared_ptr<GPUExternalTexture> GPUDevice::importExternalTexture(
|
|
236
237
|
std::shared_ptr<GPUExternalTextureDescriptor> descriptor) {
|
|
237
|
-
|
|
238
|
-
|
|
238
|
+
// The import / begin-access / descriptor-build logic, plus the matching
|
|
239
|
+
// EndAccess, all live on GPUExternalTexture so the begin/end lifecycle stays
|
|
240
|
+
// in one translation unit (see GPUExternalTexture.cpp).
|
|
241
|
+
return GPUExternalTexture::Create(_instance, std::move(descriptor));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
std::shared_ptr<GPUSharedTextureMemory> GPUDevice::importSharedTextureMemory(
|
|
245
|
+
std::shared_ptr<GPUSharedTextureMemoryDescriptor> descriptor) {
|
|
246
|
+
if (!descriptor || descriptor->handle == 0) {
|
|
247
|
+
throw std::runtime_error(
|
|
248
|
+
"GPUDevice::importSharedTextureMemory(): handle must be a non-null "
|
|
249
|
+
"native buffer pointer (from Skia.NativeBuffer.MakeFromImage)");
|
|
250
|
+
}
|
|
251
|
+
void *bufferPtr =
|
|
252
|
+
reinterpret_cast<void *>(static_cast<uintptr_t>(descriptor->handle));
|
|
253
|
+
std::string label = descriptor->label.value_or("");
|
|
254
|
+
|
|
255
|
+
auto memory = importNativeBufferAsSharedTextureMemory(
|
|
256
|
+
_instance, bufferPtr, label, /*outWidth=*/nullptr, /*outHeight=*/nullptr);
|
|
257
|
+
if (memory == nullptr) {
|
|
258
|
+
throw std::runtime_error(
|
|
259
|
+
"GPUDevice::importSharedTextureMemory(): ImportSharedTextureMemory "
|
|
260
|
+
"returned null - is the 'shared-texture-memory-iosurface' (Apple) or "
|
|
261
|
+
"'shared-texture-memory-ahardware-buffer' (Android) feature enabled on "
|
|
262
|
+
"the device?");
|
|
263
|
+
}
|
|
264
|
+
return std::make_shared<GPUSharedTextureMemory>(std::move(memory),
|
|
265
|
+
std::move(label));
|
|
239
266
|
}
|
|
240
267
|
|
|
241
268
|
async::AsyncTaskHandle GPUDevice::createComputePipelineAsync(
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
#include "GPURenderPipeline.h"
|
|
38
38
|
#include "GPUSampler.h"
|
|
39
39
|
#include "GPUShaderModule.h"
|
|
40
|
+
#include "GPUSharedTextureMemory.h"
|
|
40
41
|
#include "GPUSupportedLimits.h"
|
|
41
42
|
#include "GPUTexture.h"
|
|
42
43
|
#include "descriptors/GPUBindGroupDescriptor.h"
|
|
@@ -51,6 +52,7 @@
|
|
|
51
52
|
#include "descriptors/GPURenderPipelineDescriptor.h"
|
|
52
53
|
#include "descriptors/GPUSamplerDescriptor.h"
|
|
53
54
|
#include "descriptors/GPUShaderModuleDescriptor.h"
|
|
55
|
+
#include "descriptors/GPUSharedTextureMemoryDescriptor.h"
|
|
54
56
|
#include "descriptors/GPUTextureDescriptor.h"
|
|
55
57
|
|
|
56
58
|
namespace rnwgpu {
|
|
@@ -120,6 +122,8 @@ public:
|
|
|
120
122
|
std::optional<std::shared_ptr<GPUSamplerDescriptor>> descriptor);
|
|
121
123
|
std::shared_ptr<GPUExternalTexture> importExternalTexture(
|
|
122
124
|
std::shared_ptr<GPUExternalTextureDescriptor> descriptor);
|
|
125
|
+
std::shared_ptr<GPUSharedTextureMemory> importSharedTextureMemory(
|
|
126
|
+
std::shared_ptr<GPUSharedTextureMemoryDescriptor> descriptor);
|
|
123
127
|
std::shared_ptr<GPUBindGroupLayout> createBindGroupLayout(
|
|
124
128
|
std::shared_ptr<GPUBindGroupLayoutDescriptor> descriptor);
|
|
125
129
|
std::shared_ptr<GPUPipelineLayout>
|
|
@@ -173,6 +177,8 @@ public:
|
|
|
173
177
|
&GPUDevice::createSampler);
|
|
174
178
|
installMethod(runtime, prototype, "importExternalTexture",
|
|
175
179
|
&GPUDevice::importExternalTexture);
|
|
180
|
+
installMethod(runtime, prototype, "importSharedTextureMemory",
|
|
181
|
+
&GPUDevice::importSharedTextureMemory);
|
|
176
182
|
installMethod(runtime, prototype, "createBindGroupLayout",
|
|
177
183
|
&GPUDevice::createBindGroupLayout);
|
|
178
184
|
installMethod(runtime, prototype, "createPipelineLayout",
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#include "GPUExternalTexture.h"
|
|
2
|
+
|
|
3
|
+
#include <cmath>
|
|
4
|
+
#include <memory>
|
|
5
|
+
#include <string>
|
|
6
|
+
#include <utility>
|
|
7
|
+
|
|
8
|
+
#include "NativeBufferUtils.h"
|
|
9
|
+
#include "descriptors/GPUExternalTextureDescriptor.h"
|
|
10
|
+
|
|
11
|
+
namespace rnwgpu {
|
|
12
|
+
|
|
13
|
+
// Identity gamut (same primaries) as a 3x3 column-major matrix.
|
|
14
|
+
static const float kIdentityGamutMatrix[9] = {
|
|
15
|
+
1.0f, 0.0f, 0.0f, //
|
|
16
|
+
0.0f, 1.0f, 0.0f, //
|
|
17
|
+
0.0f, 0.0f, 1.0f, //
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Identity transfer (y = x). The native buffers produced by Skia's NativeBuffer
|
|
21
|
+
// API are single-plane BGRA/RGBA already in the render target's color space, so
|
|
22
|
+
// no conversion is wanted. Dawn dereferences the transfer-function arrays
|
|
23
|
+
// unconditionally (ComputeExternalTextureParams), so these must be non-null.
|
|
24
|
+
static const float kIdentityTransferParams[7] = {
|
|
25
|
+
1.0f, // G
|
|
26
|
+
1.0f, // A
|
|
27
|
+
0.0f, // B
|
|
28
|
+
0.0f, // C
|
|
29
|
+
0.0f, // D
|
|
30
|
+
0.0f, // E
|
|
31
|
+
0.0f, // F
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Map a rotation in degrees (0 / 90 / 180 / 270) to Dawn's enum. Anything that
|
|
35
|
+
// isn't a clean multiple of 90 snaps to the nearest quadrant; Dawn only
|
|
36
|
+
// supports those four steps for external textures.
|
|
37
|
+
static wgpu::ExternalTextureRotation toExternalTextureRotation(double degrees) {
|
|
38
|
+
int quadrant = static_cast<int>(std::lround(degrees / 90.0));
|
|
39
|
+
quadrant = ((quadrant % 4) + 4) % 4;
|
|
40
|
+
switch (quadrant) {
|
|
41
|
+
case 1:
|
|
42
|
+
return wgpu::ExternalTextureRotation::Rotate90Degrees;
|
|
43
|
+
case 2:
|
|
44
|
+
return wgpu::ExternalTextureRotation::Rotate180Degrees;
|
|
45
|
+
case 3:
|
|
46
|
+
return wgpu::ExternalTextureRotation::Rotate270Degrees;
|
|
47
|
+
default:
|
|
48
|
+
return wgpu::ExternalTextureRotation::Rotate0Degrees;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
std::shared_ptr<GPUExternalTexture> GPUExternalTexture::Create(
|
|
53
|
+
wgpu::Device device,
|
|
54
|
+
std::shared_ptr<GPUExternalTextureDescriptor> descriptor) {
|
|
55
|
+
if (!descriptor || descriptor->source == 0) {
|
|
56
|
+
throw std::runtime_error(
|
|
57
|
+
"GPUExternalTexture::Create(): descriptor.source (a native buffer "
|
|
58
|
+
"pointer from Skia.NativeBuffer.MakeFromImage) is required");
|
|
59
|
+
}
|
|
60
|
+
void *bufferPtr =
|
|
61
|
+
reinterpret_cast<void *>(static_cast<uintptr_t>(descriptor->source));
|
|
62
|
+
std::string label = descriptor->label.value_or("external-texture");
|
|
63
|
+
|
|
64
|
+
// 1. Import the native buffer as SharedTextureMemory and read its dimensions.
|
|
65
|
+
uint32_t width = 0;
|
|
66
|
+
uint32_t height = 0;
|
|
67
|
+
wgpu::SharedTextureMemory memory = importNativeBufferAsSharedTextureMemory(
|
|
68
|
+
device, bufferPtr, label, &width, &height);
|
|
69
|
+
if (memory == nullptr) {
|
|
70
|
+
throw std::runtime_error(
|
|
71
|
+
"GPUExternalTexture::Create(): ImportSharedTextureMemory returned "
|
|
72
|
+
"null");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 2. Create the texture from the surface (Dawn picks the single-plane
|
|
76
|
+
// BGRA/RGBA format).
|
|
77
|
+
auto texture = memory.CreateTexture();
|
|
78
|
+
if (texture == nullptr) {
|
|
79
|
+
throw std::runtime_error(
|
|
80
|
+
"GPUExternalTexture::Create(): CreateTexture returned null");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 3. Begin access. The matching EndAccess runs when the GPUExternalTexture is
|
|
84
|
+
// destroyed (explicitly via destroy() or at GC).
|
|
85
|
+
wgpu::SharedTextureMemoryBeginAccessDescriptor begin{};
|
|
86
|
+
begin.initialized = true;
|
|
87
|
+
begin.concurrentRead = false;
|
|
88
|
+
#if defined(__ANDROID__)
|
|
89
|
+
// Dawn's Vulkan backend requires the acquired VkImageLayout to be chained.
|
|
90
|
+
// UNDEFINED (= 0) on both ends is the canonical "no prior GPU producer"
|
|
91
|
+
// pattern (matches GPUSharedTextureMemory::beginAccess).
|
|
92
|
+
wgpu::SharedTextureMemoryVkImageLayoutBeginState vkBegin{};
|
|
93
|
+
vkBegin.oldLayout = 0;
|
|
94
|
+
vkBegin.newLayout = 0;
|
|
95
|
+
begin.nextInChain = &vkBegin;
|
|
96
|
+
#endif
|
|
97
|
+
if (!memory.BeginAccess(texture, &begin)) {
|
|
98
|
+
throw std::runtime_error(
|
|
99
|
+
"GPUExternalTexture::Create(): BeginAccess failed");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 4. Single-plane view (the whole BGRA/RGBA surface).
|
|
103
|
+
auto plane0 = texture.CreateView();
|
|
104
|
+
|
|
105
|
+
// 5. Build the ExternalTextureDescriptor. The surface is already RGB in the
|
|
106
|
+
// target color space, so pass it through with identity transfer/gamut.
|
|
107
|
+
wgpu::ExternalTextureDescriptor extDesc{};
|
|
108
|
+
if (!label.empty()) {
|
|
109
|
+
extDesc.label = wgpu::StringView(label.c_str(), label.size());
|
|
110
|
+
}
|
|
111
|
+
extDesc.plane0 = plane0;
|
|
112
|
+
extDesc.gamutConversionMatrix = kIdentityGamutMatrix;
|
|
113
|
+
extDesc.srcTransferFunctionParameters = kIdentityTransferParams;
|
|
114
|
+
extDesc.dstTransferFunctionParameters = kIdentityTransferParams;
|
|
115
|
+
extDesc.cropOrigin = {0, 0};
|
|
116
|
+
extDesc.cropSize = {width, height};
|
|
117
|
+
extDesc.apparentSize = {width, height};
|
|
118
|
+
extDesc.mirrored = descriptor->mirrored.value_or(false);
|
|
119
|
+
extDesc.rotation =
|
|
120
|
+
toExternalTextureRotation(descriptor->rotation.value_or(0));
|
|
121
|
+
|
|
122
|
+
auto external = device.CreateExternalTexture(&extDesc);
|
|
123
|
+
if (external == nullptr) {
|
|
124
|
+
wgpu::SharedTextureMemoryEndAccessState state{};
|
|
125
|
+
#if defined(__ANDROID__)
|
|
126
|
+
wgpu::SharedTextureMemoryVkImageLayoutEndState vkEnd{};
|
|
127
|
+
state.nextInChain = &vkEnd;
|
|
128
|
+
#endif
|
|
129
|
+
(void)memory.EndAccess(texture, &state);
|
|
130
|
+
throw std::runtime_error(
|
|
131
|
+
"GPUExternalTexture::Create(): CreateExternalTexture returned null");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return std::make_shared<GPUExternalTexture>(
|
|
135
|
+
std::move(external), std::move(memory), std::move(texture),
|
|
136
|
+
std::move(label));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
} // namespace rnwgpu
|