react-native-webgpu 0.6.1 → 0.6.3
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/README.md +1 -15
- package/android/cpp/AndroidPlatformContext.h +3 -0
- package/apple/ApplePlatformContext.mm +23 -24
- package/cpp/jsi/NativeObject.h +77 -8
- package/cpp/jsi/RuntimeAwareCache.cpp +1 -1
- package/cpp/jsi/RuntimeAwareCache.h +25 -6
- package/cpp/rnwgpu/PlatformContext.h +7 -0
- package/cpp/rnwgpu/api/GPUQueue.cpp +29 -15
- package/cpp/rnwgpu/api/ImageBitmap.h +32 -0
- package/cpp/rnwgpu/api/RNWebGPU.h +42 -4
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/react-native-webgpu.podspec +2 -1
- package/src/index.tsx +1 -0
package/README.md
CHANGED
|
@@ -8,20 +8,6 @@ WebGPU for React Native, powered by Dawn.
|
|
|
8
8
|
|
|
9
9
|
[Installation instructions](https://wcandillon.github.io/react-native-webgpu/docs/getting-started/installation)
|
|
10
10
|
|
|
11
|
-
## Expo config plugin
|
|
12
|
-
|
|
13
|
-
The package ships with an Expo config plugin that disables Metal API validation in the iOS Xcode scheme, which is required to run WebGPU on the iOS Simulator (Metal API validation reports false positives for Dawn that do not occur on device). Because the plugin is part of the package, plugin and package versions are always aligned.
|
|
14
|
-
|
|
15
|
-
```json
|
|
16
|
-
{
|
|
17
|
-
"expo": {
|
|
18
|
-
"plugins": ["react-native-webgpu"]
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
See the [Expo guide](https://wcandillon.github.io/react-native-webgpu/docs/getting-started/expo#config-plugin) for details.
|
|
24
|
-
|
|
25
11
|
## Contributing
|
|
26
12
|
|
|
27
|
-
For detailed information on library development, building, testing, and contributing guidelines, please see [CONTRIBUTING.md](packages/webgpu/CONTRIBUTING.md).
|
|
13
|
+
For detailed information on library development, building, testing, and contributing guidelines, please see [CONTRIBUTING.md](packages/webgpu/CONTRIBUTING.md).
|
|
@@ -179,6 +179,9 @@ public:
|
|
|
179
179
|
result.height = static_cast<int>(bitmapInfo.height);
|
|
180
180
|
result.data.resize(bitmapInfo.height * bitmapInfo.stride);
|
|
181
181
|
memcpy(result.data.data(), bitmapPixels, result.data.size());
|
|
182
|
+
// BitmapFactory hands back premultiplied ARGB_8888 pixels; createImageBitmap
|
|
183
|
+
// converts to the representation requested by premultiplyAlpha.
|
|
184
|
+
result.premultiplied = true;
|
|
182
185
|
|
|
183
186
|
AndroidBitmap_unlockPixels(env, bitmap);
|
|
184
187
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
#import <AVFoundation/AVFoundation.h>
|
|
6
6
|
#import <CoreVideo/CoreVideo.h>
|
|
7
|
+
#import <ImageIO/ImageIO.h>
|
|
7
8
|
#import <React/RCTBlobManager.h>
|
|
8
9
|
#import <React/RCTBridge+Private.h>
|
|
9
10
|
#import <ReactCommon/RCTTurboModule.h>
|
|
@@ -88,36 +89,32 @@ void ApplePlatformContext::createImageBitmapAsync(
|
|
|
88
89
|
|
|
89
90
|
ImageData
|
|
90
91
|
ApplePlatformContext::createImageBitmapFromData(std::span<const uint8_t> data) {
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
92
|
+
// All formats are decoded through ImageIO. Apple's imaging stack always
|
|
93
|
+
// premultiplies alpha at decode, so the result is flagged premultiplied and
|
|
94
|
+
// createImageBitmap / copyExternalImageToTexture convert from there. This
|
|
95
|
+
// means premultiplyAlpha "none" is a lossy round trip for low-alpha pixels
|
|
96
|
+
// (as it is on Android); the snapshot suites compare with pixelmatch
|
|
97
|
+
// tolerance to absorb it.
|
|
97
98
|
NSData *nsData =
|
|
98
99
|
[NSData dataWithBytesNoCopy:const_cast<uint8_t *>(data.data())
|
|
99
100
|
length:data.size()
|
|
100
101
|
freeWhenDone:NO];
|
|
101
102
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
CGImageSourceRef imageSource =
|
|
104
|
+
CGImageSourceCreateWithData((__bridge CFDataRef)nsData, NULL);
|
|
105
|
+
if (imageSource == NULL) {
|
|
106
|
+
throw std::runtime_error("Couldn't create image source");
|
|
107
|
+
}
|
|
108
|
+
NSDictionary *decodeOptions = @{(id)kCGImageSourceShouldCache : @NO};
|
|
109
|
+
CGImageRef cgImage = CGImageSourceCreateImageAtIndex(
|
|
110
|
+
imageSource, 0, (__bridge CFDictionaryRef)decodeOptions);
|
|
111
|
+
CFRelease(imageSource);
|
|
112
|
+
if (cgImage == NULL) {
|
|
108
113
|
throw std::runtime_error("Couldn't decode image");
|
|
109
114
|
}
|
|
110
115
|
|
|
111
|
-
#if !TARGET_OS_OSX
|
|
112
|
-
CGImageRef cgImage = image.CGImage;
|
|
113
|
-
#else
|
|
114
|
-
CGImageRef cgImage = [image CGImageForProposedRect:NULL
|
|
115
|
-
context:NULL
|
|
116
|
-
hints:NULL];
|
|
117
|
-
#endif
|
|
118
116
|
size_t width = CGImageGetWidth(cgImage);
|
|
119
117
|
size_t height = CGImageGetHeight(cgImage);
|
|
120
|
-
size_t bitsPerComponent = 8;
|
|
121
118
|
size_t bytesPerRow = width * 4;
|
|
122
119
|
|
|
123
120
|
ImageData result;
|
|
@@ -126,16 +123,18 @@ ApplePlatformContext::createImageBitmapFromData(std::span<const uint8_t> data) {
|
|
|
126
123
|
result.data.resize(height * bytesPerRow);
|
|
127
124
|
result.format = wgpu::TextureFormat::RGBA8Unorm;
|
|
128
125
|
|
|
126
|
+
// The draw premultiplies alpha; flag the result premultiplied so
|
|
127
|
+
// createImageBitmap and copyExternalImageToTexture convert consistently.
|
|
129
128
|
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
130
129
|
CGContextRef context = CGBitmapContextCreate(
|
|
131
|
-
result.data.data(), width, height,
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
result.data.data(), width, height, 8, bytesPerRow, colorSpace,
|
|
131
|
+
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
|
|
134
132
|
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
|
|
135
|
-
|
|
136
133
|
CGContextRelease(context);
|
|
137
134
|
CGColorSpaceRelease(colorSpace);
|
|
135
|
+
CGImageRelease(cgImage);
|
|
138
136
|
|
|
137
|
+
result.premultiplied = true;
|
|
139
138
|
return result;
|
|
140
139
|
}
|
|
141
140
|
|
package/cpp/jsi/NativeObject.h
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
#include <memory>
|
|
10
10
|
#include <mutex>
|
|
11
11
|
#include <optional>
|
|
12
|
+
#include <stdexcept>
|
|
12
13
|
#include <string>
|
|
13
14
|
#include <type_traits>
|
|
14
15
|
#include <unordered_map>
|
|
@@ -48,6 +49,12 @@ public:
|
|
|
48
49
|
|
|
49
50
|
void registerInstaller(const std::string &brand, InstallerFunc installer) {
|
|
50
51
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
52
|
+
if (_installers.count(brand) != 0) {
|
|
53
|
+
// The brand is the key unbox() uses to rebuild objects on worklet
|
|
54
|
+
// runtimes - a duplicate would let one class hijack another's unboxing.
|
|
55
|
+
throw std::runtime_error("Duplicate native object brand registered: " +
|
|
56
|
+
brand);
|
|
57
|
+
}
|
|
51
58
|
_installers[brand] = std::move(installer);
|
|
52
59
|
}
|
|
53
60
|
|
|
@@ -235,6 +242,10 @@ public:
|
|
|
235
242
|
* Each NativeObject<Derived> type has its own static cache.
|
|
236
243
|
* Uses StaticRuntimeAwareCache to properly handle runtime lifecycle
|
|
237
244
|
* and hot reload (where the main runtime is destroyed and recreated).
|
|
245
|
+
*
|
|
246
|
+
* Callers must hold getPrototypeCacheMutex(): the cache is reached
|
|
247
|
+
* concurrently from the main JS thread (create()) and from worklet
|
|
248
|
+
* runtime threads (BoxedWebGPUObject::unbox() -> installPrototype()).
|
|
238
249
|
*/
|
|
239
250
|
static RuntimeAwareCache<PrototypeCacheEntry> &
|
|
240
251
|
getPrototypeCache(jsi::Runtime &runtime) {
|
|
@@ -242,11 +253,22 @@ public:
|
|
|
242
253
|
return cache.get(runtime);
|
|
243
254
|
}
|
|
244
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Per-class mutex guarding getPrototypeCache() and prototype
|
|
258
|
+
* installation. Serializes the StaticRuntimeAwareCache pointer swap
|
|
259
|
+
* (hot reload) and the per-runtime cache lookups.
|
|
260
|
+
*/
|
|
261
|
+
static std::mutex &getPrototypeCacheMutex() {
|
|
262
|
+
static std::mutex mutex;
|
|
263
|
+
return mutex;
|
|
264
|
+
}
|
|
265
|
+
|
|
245
266
|
/**
|
|
246
267
|
* Ensure the prototype is installed for this runtime.
|
|
247
268
|
* Called automatically by create(), but can be called manually.
|
|
248
269
|
*/
|
|
249
270
|
static void installPrototype(jsi::Runtime &runtime) {
|
|
271
|
+
std::lock_guard<std::mutex> lock(getPrototypeCacheMutex());
|
|
250
272
|
auto &entry = getPrototypeCache(runtime).get(runtime);
|
|
251
273
|
if (entry.prototype.has_value()) {
|
|
252
274
|
return; // Already installed
|
|
@@ -278,6 +300,49 @@ public:
|
|
|
278
300
|
defineProperty.call(runtime, prototype, toStringTag, descriptor);
|
|
279
301
|
}
|
|
280
302
|
|
|
303
|
+
// Install a generic toJSON so JSON.stringify works: data properties live
|
|
304
|
+
// as getters on this shared prototype, and JSON.stringify only serializes
|
|
305
|
+
// own enumerable properties (so it would otherwise produce {}).
|
|
306
|
+
auto toJSON = jsi::Function::createFromHostFunction(
|
|
307
|
+
runtime, jsi::PropNameID::forUtf8(runtime, "toJSON"), 0,
|
|
308
|
+
[](jsi::Runtime &rt, const jsi::Value &thisVal,
|
|
309
|
+
const jsi::Value * /*args*/, size_t /*count*/) -> jsi::Value {
|
|
310
|
+
auto thisObj = thisVal.getObject(rt);
|
|
311
|
+
auto objectCtor = rt.global().getPropertyAsObject(rt, "Object");
|
|
312
|
+
auto getPrototypeOf =
|
|
313
|
+
objectCtor.getPropertyAsFunction(rt, "getPrototypeOf");
|
|
314
|
+
auto proto = getPrototypeOf.call(rt, thisObj);
|
|
315
|
+
jsi::Object result(rt);
|
|
316
|
+
if (!proto.isObject()) {
|
|
317
|
+
return std::move(result);
|
|
318
|
+
}
|
|
319
|
+
auto getOwnPropertyNames =
|
|
320
|
+
objectCtor.getPropertyAsFunction(rt, "getOwnPropertyNames");
|
|
321
|
+
auto names =
|
|
322
|
+
getOwnPropertyNames.call(rt, proto).getObject(rt).getArray(rt);
|
|
323
|
+
size_t length = names.size(rt);
|
|
324
|
+
for (size_t i = 0; i < length; i++) {
|
|
325
|
+
auto nameValue = names.getValueAtIndex(rt, i);
|
|
326
|
+
if (!nameValue.isString()) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
auto name = nameValue.getString(rt).utf8(rt);
|
|
330
|
+
if (name == "constructor" || name == "toJSON") {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
// Read off `this` so prototype getters evaluate against the
|
|
334
|
+
// object's native state. Getters that throw keep throwing.
|
|
335
|
+
auto value = thisObj.getProperty(rt, name.c_str());
|
|
336
|
+
if (value.isObject() && value.getObject(rt).isFunction(rt)) {
|
|
337
|
+
// Skip methods - JSON.stringify would drop them anyway
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
result.setProperty(rt, name.c_str(), value);
|
|
341
|
+
}
|
|
342
|
+
return std::move(result);
|
|
343
|
+
});
|
|
344
|
+
prototype.setProperty(runtime, "toJSON", toJSON);
|
|
345
|
+
|
|
281
346
|
// Cache the prototype
|
|
282
347
|
entry.prototype = std::move(prototype);
|
|
283
348
|
}
|
|
@@ -303,6 +368,7 @@ public:
|
|
|
303
368
|
|
|
304
369
|
installPrototype(runtime);
|
|
305
370
|
|
|
371
|
+
std::lock_guard<std::mutex> lock(getPrototypeCacheMutex());
|
|
306
372
|
auto &entry = getPrototypeCache(runtime).get(runtime);
|
|
307
373
|
if (!entry.prototype.has_value()) {
|
|
308
374
|
return;
|
|
@@ -346,14 +412,17 @@ public:
|
|
|
346
412
|
obj.setNativeState(runtime, instance);
|
|
347
413
|
|
|
348
414
|
// Set prototype
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
415
|
+
{
|
|
416
|
+
std::lock_guard<std::mutex> lock(getPrototypeCacheMutex());
|
|
417
|
+
auto &entry = getPrototypeCache(runtime).get(runtime);
|
|
418
|
+
if (entry.prototype.has_value()) {
|
|
419
|
+
// Use Object.setPrototypeOf to set the prototype
|
|
420
|
+
auto objectCtor =
|
|
421
|
+
runtime.global().getPropertyAsObject(runtime, "Object");
|
|
422
|
+
auto setPrototypeOf =
|
|
423
|
+
objectCtor.getPropertyAsFunction(runtime, "setPrototypeOf");
|
|
424
|
+
setPrototypeOf.call(runtime, obj, *entry.prototype);
|
|
425
|
+
}
|
|
357
426
|
}
|
|
358
427
|
|
|
359
428
|
// Set memory pressure hint for GC
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
#include <jsi/jsi.h>
|
|
4
4
|
|
|
5
|
+
#include <atomic>
|
|
5
6
|
#include <cassert>
|
|
6
7
|
#include <memory>
|
|
8
|
+
#include <mutex>
|
|
7
9
|
#include <unordered_map>
|
|
8
10
|
#include <utility>
|
|
9
11
|
|
|
@@ -15,17 +17,22 @@ namespace jsi = facebook::jsi;
|
|
|
15
17
|
|
|
16
18
|
class BaseRuntimeAwareCache {
|
|
17
19
|
public:
|
|
18
|
-
static void setMainJsRuntime(jsi::Runtime *rt) {
|
|
20
|
+
static void setMainJsRuntime(jsi::Runtime *rt) {
|
|
21
|
+
// Atomic: hot reload rewrites this from the JS thread while worklet
|
|
22
|
+
// runtime threads read it concurrently.
|
|
23
|
+
_mainRuntime.store(rt, std::memory_order_release);
|
|
24
|
+
}
|
|
19
25
|
static jsi::Runtime *getMainJsRuntime() {
|
|
20
|
-
|
|
26
|
+
auto *rt = _mainRuntime.load(std::memory_order_acquire);
|
|
27
|
+
assert(rt != nullptr &&
|
|
21
28
|
"Expected main Javascript runtime to be set in the "
|
|
22
29
|
"BaseRuntimeAwareCache class.");
|
|
23
30
|
|
|
24
|
-
return
|
|
31
|
+
return rt;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
private:
|
|
28
|
-
static jsi::Runtime
|
|
35
|
+
static std::atomic<jsi::Runtime *> _mainRuntime;
|
|
29
36
|
};
|
|
30
37
|
|
|
31
38
|
/**
|
|
@@ -56,7 +63,10 @@ class RuntimeAwareCache : public BaseRuntimeAwareCache,
|
|
|
56
63
|
public:
|
|
57
64
|
void onRuntimeDestroyed(jsi::Runtime *rt) override {
|
|
58
65
|
if (getMainJsRuntime() != rt) {
|
|
59
|
-
// We are removing a secondary runtime
|
|
66
|
+
// We are removing a secondary runtime. This is invoked by
|
|
67
|
+
// RuntimeLifecycleMonitor on the destroyed runtime's thread, which may
|
|
68
|
+
// run concurrently with get() on another runtime's thread.
|
|
69
|
+
std::lock_guard<std::mutex> lock(_secondaryCachesMutex);
|
|
60
70
|
_secondaryRuntimeCaches.erase(rt);
|
|
61
71
|
}
|
|
62
72
|
}
|
|
@@ -75,6 +85,14 @@ public:
|
|
|
75
85
|
if (getMainJsRuntime() == &rt) {
|
|
76
86
|
return _primaryCache;
|
|
77
87
|
} else {
|
|
88
|
+
// Guard the secondary map: it can be mutated concurrently by get()
|
|
89
|
+
// on a worklet runtime's thread and by onRuntimeDestroyed() when
|
|
90
|
+
// another secondary runtime is torn down. The main-runtime path above
|
|
91
|
+
// stays lock-free. References into the map remain valid after the
|
|
92
|
+
// lock is released (unordered_map never invalidates references on
|
|
93
|
+
// insert/erase of other keys, and this runtime's entry can only be
|
|
94
|
+
// erased from this runtime's own thread).
|
|
95
|
+
std::lock_guard<std::mutex> lock(_secondaryCachesMutex);
|
|
78
96
|
if (_secondaryRuntimeCaches.count(&rt) == 0) {
|
|
79
97
|
// we only add listener when the secondary runtime is used, this assumes
|
|
80
98
|
// that the secondary runtime is terminated first. This lets us avoid
|
|
@@ -88,11 +106,12 @@ public:
|
|
|
88
106
|
T cache;
|
|
89
107
|
_secondaryRuntimeCaches.emplace(&rt, std::move(cache));
|
|
90
108
|
}
|
|
109
|
+
return _secondaryRuntimeCaches.at(&rt);
|
|
91
110
|
}
|
|
92
|
-
return _secondaryRuntimeCaches.at(&rt);
|
|
93
111
|
}
|
|
94
112
|
|
|
95
113
|
private:
|
|
114
|
+
std::mutex _secondaryCachesMutex;
|
|
96
115
|
std::unordered_map<void *, T> _secondaryRuntimeCaches;
|
|
97
116
|
T _primaryCache;
|
|
98
117
|
};
|
|
@@ -16,6 +16,13 @@ struct ImageData {
|
|
|
16
16
|
size_t width;
|
|
17
17
|
size_t height;
|
|
18
18
|
wgpu::TextureFormat format;
|
|
19
|
+
// Whether the RGB channels are premultiplied by alpha. Each platform decoder
|
|
20
|
+
// records the representation it produced; createImageBitmap then converts to
|
|
21
|
+
// the representation requested through premultiplyAlpha, and
|
|
22
|
+
// copyExternalImageToTexture converts again to match the destination's
|
|
23
|
+
// premultipliedAlpha. Defaults to true because most native decoders
|
|
24
|
+
// (CoreGraphics, Android Bitmap) hand back premultiplied pixels.
|
|
25
|
+
bool premultiplied = true;
|
|
19
26
|
};
|
|
20
27
|
|
|
21
28
|
// Pixel layout of a VideoFrame. Determines whether the underlying surface is
|
|
@@ -128,26 +128,40 @@ void GPUQueue::copyExternalImageToTexture(
|
|
|
128
128
|
throw std::runtime_error("Invalid input for GPUQueue::writeTexture()");
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
const bool flipY = source->flipY.value_or(false);
|
|
132
|
+
// premultipliedAlpha defaults to false per the WebGPU spec: an untagged
|
|
133
|
+
// destination expects straight alpha. Convert only when the ImageBitmap's
|
|
134
|
+
// representation differs, using the same rounding as the reference client.
|
|
135
|
+
const bool sourcePremultiplied = source->source->getPremultiplied();
|
|
136
|
+
const bool destinationPremultiplied =
|
|
137
|
+
destination->premultipliedAlpha.value_or(false);
|
|
138
|
+
const bool needsAlphaConversion =
|
|
139
|
+
bytesPerPixel == 4 && sourcePremultiplied != destinationPremultiplied;
|
|
140
|
+
|
|
141
|
+
if (flipY || needsAlphaConversion) {
|
|
133
142
|
uint32_t rowSize = bytesPerPixel * source->source->getWidth();
|
|
134
143
|
uint32_t totalSize = source->source->getSize();
|
|
135
144
|
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
// Stage a mutable copy so flipping and/or alpha conversion never touch the
|
|
146
|
+
// ImageBitmap's backing store.
|
|
147
|
+
std::vector<uint8_t> staged(totalSize);
|
|
148
|
+
const uint8_t *src =
|
|
149
|
+
static_cast<const uint8_t *>(source->source->getData());
|
|
150
|
+
if (flipY) {
|
|
151
|
+
for (uint32_t row = 0; row < source->source->getHeight(); ++row) {
|
|
152
|
+
std::memcpy(staged.data() +
|
|
153
|
+
(source->source->getHeight() - 1 - row) * rowSize,
|
|
154
|
+
src + row * rowSize, rowSize);
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
std::memcpy(staged.data(), src, totalSize);
|
|
158
|
+
}
|
|
159
|
+
if (needsAlphaConversion) {
|
|
160
|
+
convertAlpha(staged.data(), totalSize, sourcePremultiplied,
|
|
161
|
+
destinationPremultiplied);
|
|
146
162
|
}
|
|
147
|
-
|
|
148
|
-
_instance.WriteTexture(&dst, flippedData.data(), totalSize, &layout, &sz);
|
|
163
|
+
_instance.WriteTexture(&dst, staged.data(), totalSize, &layout, &sz);
|
|
149
164
|
} else {
|
|
150
|
-
|
|
151
165
|
_instance.WriteTexture(&dst, source->source->getData(),
|
|
152
166
|
source->source->getSize(), &layout, &sz);
|
|
153
167
|
}
|
|
@@ -11,6 +11,33 @@ namespace rnwgpu {
|
|
|
11
11
|
|
|
12
12
|
namespace jsi = facebook::jsi;
|
|
13
13
|
|
|
14
|
+
// Convert RGBA8 pixel data in place between straight and premultiplied alpha.
|
|
15
|
+
// Uses the same integer rounding as the web reference polyfill in the test
|
|
16
|
+
// harness (setup.ts, convertAlpha) so the native result is bit-exact with the
|
|
17
|
+
// dawn.node client. A no-op when the source and destination representations
|
|
18
|
+
// already match.
|
|
19
|
+
inline void convertAlpha(uint8_t *data, size_t byteLength,
|
|
20
|
+
bool sourcePremultiplied,
|
|
21
|
+
bool destinationPremultiplied) {
|
|
22
|
+
if (sourcePremultiplied == destinationPremultiplied) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
for (size_t i = 0; i + 3 < byteLength; i += 4) {
|
|
26
|
+
const uint32_t alpha = data[i + 3];
|
|
27
|
+
for (size_t channel = 0; channel < 3; channel++) {
|
|
28
|
+
const uint32_t value = data[i + channel];
|
|
29
|
+
if (destinationPremultiplied) {
|
|
30
|
+
data[i + channel] = static_cast<uint8_t>((value * alpha + 127) / 255);
|
|
31
|
+
} else if (alpha == 0) {
|
|
32
|
+
data[i + channel] = 0;
|
|
33
|
+
} else {
|
|
34
|
+
const uint32_t straight = (value * 255 + (alpha >> 1)) / alpha;
|
|
35
|
+
data[i + channel] = static_cast<uint8_t>(straight > 255 ? 255 : straight);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
14
41
|
class ImageBitmap : public NativeObject<ImageBitmap> {
|
|
15
42
|
public:
|
|
16
43
|
static constexpr const char *CLASS_NAME = "ImageBitmap";
|
|
@@ -26,6 +53,11 @@ public:
|
|
|
26
53
|
|
|
27
54
|
size_t getSize() { return _imageData.data.size(); }
|
|
28
55
|
|
|
56
|
+
// Whether the stored pixels are premultiplied by alpha. Used by
|
|
57
|
+
// copyExternalImageToTexture to decide whether a conversion to the
|
|
58
|
+
// destination's premultipliedAlpha representation is needed.
|
|
59
|
+
bool getPremultiplied() { return _imageData.premultiplied; }
|
|
60
|
+
|
|
29
61
|
void close() {
|
|
30
62
|
_imageData.data.clear();
|
|
31
63
|
_imageData.data.shrink_to_fit();
|
|
@@ -90,6 +90,41 @@ public:
|
|
|
90
90
|
auto platformContext = _platformContext;
|
|
91
91
|
auto callInvoker = _callInvoker;
|
|
92
92
|
|
|
93
|
+
// Resolve the requested alpha representation from the ImageBitmapOptions.
|
|
94
|
+
// The options bag is the second argument for createImageBitmap(source,
|
|
95
|
+
// options) and the sixth for the crop-rect overload createImageBitmap(
|
|
96
|
+
// source, sx, sy, sw, sh, options). premultiplyAlpha defaults to
|
|
97
|
+
// "default", which (like "premultiply") stores premultiplied pixels; only
|
|
98
|
+
// "none" keeps straight alpha. The other options (crop rect, resize,
|
|
99
|
+
// imageOrientation, colorSpaceConversion) are not yet implemented natively.
|
|
100
|
+
bool wantPremultiplied = true;
|
|
101
|
+
const jsi::Value *optionsArg = nullptr;
|
|
102
|
+
if (count >= 2 && args[1].isObject()) {
|
|
103
|
+
optionsArg = &args[1];
|
|
104
|
+
} else if (count >= 6 && args[5].isObject()) {
|
|
105
|
+
optionsArg = &args[5];
|
|
106
|
+
}
|
|
107
|
+
if (optionsArg != nullptr) {
|
|
108
|
+
auto options = optionsArg->getObject(runtime);
|
|
109
|
+
if (options.hasProperty(runtime, "premultiplyAlpha")) {
|
|
110
|
+
auto value = options.getProperty(runtime, "premultiplyAlpha");
|
|
111
|
+
if (value.isString() &&
|
|
112
|
+
value.getString(runtime).utf8(runtime) == "none") {
|
|
113
|
+
wantPremultiplied = false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Bring the decoded pixels into the representation requested via
|
|
119
|
+
// premultiplyAlpha before wrapping them in an ImageBitmap.
|
|
120
|
+
auto toRequestedAlpha = [wantPremultiplied](ImageData &imageData) {
|
|
121
|
+
if (imageData.premultiplied != wantPremultiplied) {
|
|
122
|
+
convertAlpha(imageData.data.data(), imageData.data.size(),
|
|
123
|
+
imageData.premultiplied, wantPremultiplied);
|
|
124
|
+
imageData.premultiplied = wantPremultiplied;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
93
128
|
// Check if the argument is an ArrayBuffer or ArrayBufferView
|
|
94
129
|
// (TypedArray / DataView). Only a real buffer source is run through the
|
|
95
130
|
// ArrayBuffer converter, which validates byteOffset/byteLength against the
|
|
@@ -119,12 +154,14 @@ public:
|
|
|
119
154
|
|
|
120
155
|
return Promise::createPromise(
|
|
121
156
|
runtime,
|
|
122
|
-
[platformContext, callInvoker,
|
|
157
|
+
[platformContext, callInvoker, toRequestedAlpha,
|
|
158
|
+
dataCopy = std::move(dataCopy)](
|
|
123
159
|
jsi::Runtime & /*runtime*/,
|
|
124
160
|
std::shared_ptr<Promise> promise) mutable {
|
|
125
161
|
platformContext->createImageBitmapFromDataAsync(
|
|
126
162
|
dataCopy,
|
|
127
|
-
[callInvoker, promise](ImageData imageData) {
|
|
163
|
+
[callInvoker, promise, toRequestedAlpha](ImageData imageData) {
|
|
164
|
+
toRequestedAlpha(imageData);
|
|
128
165
|
auto imageBitmap = std::make_shared<ImageBitmap>(imageData);
|
|
129
166
|
callInvoker->invokeAsync([promise, imageBitmap]() {
|
|
130
167
|
promise->resolve(
|
|
@@ -149,11 +186,12 @@ public:
|
|
|
149
186
|
|
|
150
187
|
return Promise::createPromise(
|
|
151
188
|
runtime,
|
|
152
|
-
[platformContext, callInvoker, blobId, offset,
|
|
189
|
+
[platformContext, callInvoker, toRequestedAlpha, blobId, offset,
|
|
153
190
|
size](jsi::Runtime & /*runtime*/, std::shared_ptr<Promise> promise) {
|
|
154
191
|
platformContext->createImageBitmapAsync(
|
|
155
192
|
blobId, offset, size,
|
|
156
|
-
[callInvoker, promise](ImageData imageData) {
|
|
193
|
+
[callInvoker, promise, toRequestedAlpha](ImageData imageData) {
|
|
194
|
+
toRequestedAlpha(imageData);
|
|
157
195
|
auto imageBitmap = std::make_shared<ImageBitmap>(imageData);
|
|
158
196
|
callInvoker->invokeAsync([promise, imageBitmap]() {
|
|
159
197
|
promise->resolve(
|
|
@@ -37,6 +37,6 @@ declare global {
|
|
|
37
37
|
destroy(): void;
|
|
38
38
|
readonly yuvToRgbMatrix: number[];
|
|
39
39
|
}
|
|
40
|
-
function createImageBitmap(image: ArrayBuffer | ArrayBufferView): Promise<ImageBitmap>;
|
|
40
|
+
function createImageBitmap(image: ArrayBuffer | ArrayBufferView, options?: ImageBitmapOptions): Promise<ImageBitmap>;
|
|
41
41
|
}
|
|
42
42
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EACd,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,gCAAgC,EAChC,YAAY,EACZ,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,SAAS,CAAC;AAEjB,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,YAAY,EACV,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,wBAAwB,EACxB,wBAAwB,EACxB,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,gCAAgC,EAChC,oCAAoC,EACpC,sBAAsB,EACtB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,SAAS,CAAC;AAEjB,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,SAAS;QACjB,GAAG,EAAE,GAAG,CAAC;KACV;IAED,IAAI,SAAS,EAAE,SAAS,CAAC;IAEzB,IAAI,QAAQ,EAAE;QACZ,GAAG,EAAE,GAAG,CAAC;QACT,MAAM,EAAE,OAAO,CAAC;QAChB,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,YAAY,CAAC;QACtD,uBAAuB,EAAE,CACvB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,KACX,eAAe,CAAC;QAGrB,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;QAC5C,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,eAAe,GAAG,WAAW,KAAK,MAAM,CAAC;QACvE,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;QAC5C,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,gBAAgB,CAAC;QACnD,oBAAoB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,gBAAgB,CAAC;QAI1E,gCAAgC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,gBAAgB,CAAC;QACxE,iBAAiB,EAAE,CACjB,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,sBAAsB,KACjC,WAAW,CAAC;QACjB,kBAAkB,EAAE,MAAM,MAAM,CAAC;KAClC,CAAC;IAEF,UAAU,SAAS;QACjB,yBAAyB,CACvB,UAAU,EAAE,gCAAgC,GAC3C,sBAAsB,CAAC;QAC1B,iBAAiB,CAAC,UAAU,EAAE,wBAAwB,GAAG,cAAc,CAAC;KACzE;IAID,UAAU,mBAAmB;QAC3B,WAAW,CAAC,EAAE,wBAAwB,CAAC;KACxC;IAMD,UAAU,4BAA4B;QACpC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,GAAG,CAAC;QAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB;IASD,UAAU,kBAAkB;QAC1B,OAAO,IAAI,IAAI,CAAC;QAShB,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;KACnC;IAGD,SAAS,iBAAiB,CACxB,KAAK,EAAE,WAAW,GAAG,eAAe,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EACd,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,gCAAgC,EAChC,YAAY,EACZ,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,SAAS,CAAC;AAEjB,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,YAAY,EACV,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,wBAAwB,EACxB,wBAAwB,EACxB,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,gCAAgC,EAChC,oCAAoC,EACpC,sBAAsB,EACtB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,SAAS,CAAC;AAEjB,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,SAAS;QACjB,GAAG,EAAE,GAAG,CAAC;KACV;IAED,IAAI,SAAS,EAAE,SAAS,CAAC;IAEzB,IAAI,QAAQ,EAAE;QACZ,GAAG,EAAE,GAAG,CAAC;QACT,MAAM,EAAE,OAAO,CAAC;QAChB,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,YAAY,CAAC;QACtD,uBAAuB,EAAE,CACvB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,KACX,eAAe,CAAC;QAGrB,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;QAC5C,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,eAAe,GAAG,WAAW,KAAK,MAAM,CAAC;QACvE,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;QAC5C,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,gBAAgB,CAAC;QACnD,oBAAoB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,gBAAgB,CAAC;QAI1E,gCAAgC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,gBAAgB,CAAC;QACxE,iBAAiB,EAAE,CACjB,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,sBAAsB,KACjC,WAAW,CAAC;QACjB,kBAAkB,EAAE,MAAM,MAAM,CAAC;KAClC,CAAC;IAEF,UAAU,SAAS;QACjB,yBAAyB,CACvB,UAAU,EAAE,gCAAgC,GAC3C,sBAAsB,CAAC;QAC1B,iBAAiB,CAAC,UAAU,EAAE,wBAAwB,GAAG,cAAc,CAAC;KACzE;IAID,UAAU,mBAAmB;QAC3B,WAAW,CAAC,EAAE,wBAAwB,CAAC;KACxC;IAMD,UAAU,4BAA4B;QACpC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,GAAG,CAAC;QAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB;IASD,UAAU,kBAAkB;QAC1B,OAAO,IAAI,IAAI,CAAC;QAShB,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;KACnC;IAGD,SAAS,iBAAiB,CACxB,KAAK,EAAE,WAAW,GAAG,eAAe,EACpC,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,WAAW,CAAC,CAAC;CACzB"}
|
package/package.json
CHANGED
|
@@ -23,7 +23,8 @@ Pod::Spec.new do |s|
|
|
|
23
23
|
|
|
24
24
|
# The VideoPlayer API uses AVFoundation / CoreMedia, and shared-texture
|
|
25
25
|
# surfaces use CoreVideo (CVPixelBuffer). Link them so their symbols resolve.
|
|
26
|
-
|
|
26
|
+
# ImageIO provides CGImageSource, the image decoder behind createImageBitmap.
|
|
27
|
+
s.frameworks = "AVFoundation", "CoreMedia", "CoreVideo", "ImageIO"
|
|
27
28
|
|
|
28
29
|
s.pod_target_xcconfig = {
|
|
29
30
|
'HEADER_SEARCH_PATHS' => '$(PODS_TARGET_SRCROOT)/cpp',
|
package/src/index.tsx
CHANGED