react-native-webgpu 0.10.4 → 0.11.0
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/Package.swift +24 -10
- package/android/cpp/AndroidPlatformContext.h +60 -3
- 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 +68 -0
- package/apple/ApplePlatformContext.h +14 -1
- package/apple/ApplePlatformContext.mm +176 -1
- package/apple/WebGPUModule.mm +20 -1
- package/cpp/rnwgpu/PlatformContext.h +55 -0
- package/cpp/rnwgpu/RNWebGPUManager.cpp +7 -0
- package/cpp/rnwgpu/SurfaceRegistry.h +21 -2
- package/cpp/rnwgpu/api/GPU.cpp +1 -1
- package/cpp/rnwgpu/api/GPUCanvasContext.cpp +14 -2
- package/cpp/rnwgpu/api/GPUCanvasContext.h +8 -0
- package/cpp/rnwgpu/api/GPUQueue.cpp +187 -0
- package/cpp/rnwgpu/api/GPUQueue.h +16 -0
- package/cpp/rnwgpu/api/descriptors/GPUDrawElementImageDestination.h +60 -0
- package/cpp/rnwgpu/api/descriptors/GPUDrawElementImageSource.h +106 -0
- package/lib/commonjs/WebPolyfillGPUModule.js +7 -0
- package/lib/commonjs/WebPolyfillGPUModule.js.map +1 -1
- package/lib/commonjs/importDevice.js +3 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/main/index.js +33 -0
- package/lib/commonjs/main/index.js.map +1 -1
- package/lib/module/WebPolyfillGPUModule.js +7 -0
- package/lib/module/WebPolyfillGPUModule.js.map +1 -1
- package/lib/module/importDevice.js +3 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/main/index.js +33 -0
- package/lib/module/main/index.js.map +1 -1
- package/lib/typescript/lib/commonjs/importDevice.d.ts +3 -3
- package/lib/typescript/src/importDevice.d.ts +3 -3
- package/lib/typescript/src/index.d.ts +5 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/main/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +13 -0
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/WebPolyfillGPUModule.ts +10 -2
- package/src/importDevice.ts +3 -3
- package/src/index.tsx +29 -0
- package/src/main/index.tsx +49 -0
- package/src/types.ts +29 -0
|
@@ -10,10 +10,15 @@
|
|
|
10
10
|
#import <ReactCommon/RCTTurboModule.h>
|
|
11
11
|
|
|
12
12
|
#include "AppleVideoPlayer.h"
|
|
13
|
+
#import "RNWGUIKit.h"
|
|
13
14
|
|
|
14
15
|
#include "RNWebGPUManager.h"
|
|
15
16
|
#include "WebGPUModule.h"
|
|
16
17
|
|
|
18
|
+
#include <cmath>
|
|
19
|
+
#include <string>
|
|
20
|
+
#include <utility>
|
|
21
|
+
|
|
17
22
|
namespace rnwgpu {
|
|
18
23
|
|
|
19
24
|
void checkIfUsingSimulatorWithAPIValidation() {
|
|
@@ -30,10 +35,180 @@ void checkIfUsingSimulatorWithAPIValidation() {
|
|
|
30
35
|
#endif
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
ApplePlatformContext::ApplePlatformContext()
|
|
38
|
+
ApplePlatformContext::ApplePlatformContext(ViewLookup viewLookup)
|
|
39
|
+
: _viewLookup(std::move(viewLookup)) {
|
|
34
40
|
checkIfUsingSimulatorWithAPIValidation();
|
|
35
41
|
}
|
|
36
42
|
|
|
43
|
+
namespace {
|
|
44
|
+
|
|
45
|
+
// Rasterize `request` on the main thread into tightly packed, premultiplied
|
|
46
|
+
// BGRA8 pixels. Throws std::runtime_error with a user-facing message.
|
|
47
|
+
ImageData snapshotViewOnMainThread(const ApplePlatformContext::ViewLookup &lookup,
|
|
48
|
+
const ViewSnapshotRequest &request) {
|
|
49
|
+
if (!lookup) {
|
|
50
|
+
throw std::runtime_error(
|
|
51
|
+
"drawElementImageToTexture: no view registry is available");
|
|
52
|
+
}
|
|
53
|
+
RNWGPlatformView *view = (__bridge RNWGPlatformView *)lookup(request.viewTag);
|
|
54
|
+
if (view == nil) {
|
|
55
|
+
throw std::runtime_error(
|
|
56
|
+
"drawElementImageToTexture: no native view found for tag " +
|
|
57
|
+
std::to_string(request.viewTag) +
|
|
58
|
+
" (is the view mounted, and rendered with collapsable={false}?)");
|
|
59
|
+
}
|
|
60
|
+
const CGRect bounds = view.bounds;
|
|
61
|
+
if (bounds.size.width <= 0 || bounds.size.height <= 0) {
|
|
62
|
+
throw std::runtime_error(
|
|
63
|
+
"drawElementImageToTexture: the view has no size yet");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Source rectangle in points (view space); the whole view by default.
|
|
67
|
+
const double sourceX = request.sourceX;
|
|
68
|
+
const double sourceY = request.sourceY;
|
|
69
|
+
const double sourceWidth =
|
|
70
|
+
request.sourceWidth > 0 ? request.sourceWidth : bounds.size.width;
|
|
71
|
+
const double sourceHeight =
|
|
72
|
+
request.sourceHeight > 0 ? request.sourceHeight : bounds.size.height;
|
|
73
|
+
|
|
74
|
+
// Natural pixel size: points times the screen scale the view is shown at.
|
|
75
|
+
#if !TARGET_OS_OSX
|
|
76
|
+
CGFloat scale = view.window.screen.scale;
|
|
77
|
+
if (scale <= 0) {
|
|
78
|
+
scale = UIScreen.mainScreen.scale;
|
|
79
|
+
}
|
|
80
|
+
#else
|
|
81
|
+
CGFloat scale = view.window.backingScaleFactor;
|
|
82
|
+
if (scale <= 0) {
|
|
83
|
+
scale = NSScreen.mainScreen.backingScaleFactor;
|
|
84
|
+
}
|
|
85
|
+
#endif
|
|
86
|
+
const uint32_t width =
|
|
87
|
+
request.width > 0
|
|
88
|
+
? request.width
|
|
89
|
+
: static_cast<uint32_t>(std::llround(sourceWidth * scale));
|
|
90
|
+
const uint32_t height =
|
|
91
|
+
request.height > 0
|
|
92
|
+
? request.height
|
|
93
|
+
: static_cast<uint32_t>(std::llround(sourceHeight * scale));
|
|
94
|
+
if (width == 0 || height == 0) {
|
|
95
|
+
throw std::runtime_error(
|
|
96
|
+
"drawElementImageToTexture: the source rectangle is empty");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// The view's bounds are drawn scaled so that the source rectangle covers
|
|
100
|
+
// the whole output.
|
|
101
|
+
const CGFloat scaleX = static_cast<CGFloat>(width) / sourceWidth;
|
|
102
|
+
const CGFloat scaleY = static_cast<CGFloat>(height) / sourceHeight;
|
|
103
|
+
const CGRect drawRect =
|
|
104
|
+
CGRectMake(-sourceX * scaleX, -sourceY * scaleY,
|
|
105
|
+
bounds.size.width * scaleX, bounds.size.height * scaleY);
|
|
106
|
+
|
|
107
|
+
CGImageRef cgImage = NULL;
|
|
108
|
+
#if !TARGET_OS_OSX
|
|
109
|
+
// drawViewHierarchyInRect renders what is on screen (including Metal /
|
|
110
|
+
// CAMetalLayer content), which is what "the element as displayed" means.
|
|
111
|
+
// afterScreenUpdates:YES flushes pending layout and layer changes first, so
|
|
112
|
+
// a snapshot taken right after a state change reflects that change.
|
|
113
|
+
UIGraphicsImageRendererFormat *format =
|
|
114
|
+
[UIGraphicsImageRendererFormat defaultFormat];
|
|
115
|
+
format.scale = 1.0;
|
|
116
|
+
format.opaque = NO;
|
|
117
|
+
format.preferredRange = UIGraphicsImageRendererFormatRangeStandard;
|
|
118
|
+
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc]
|
|
119
|
+
initWithSize:CGSizeMake(width, height)
|
|
120
|
+
format:format];
|
|
121
|
+
// drawViewHierarchyInRect returns NO and draws nothing when the view is not
|
|
122
|
+
// in a window (e.g. a screen pushed under another one). The image would
|
|
123
|
+
// then be fully transparent, so treat it as a failure instead.
|
|
124
|
+
__block BOOL drawn = NO;
|
|
125
|
+
UIImage *image = [renderer
|
|
126
|
+
imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull context) {
|
|
127
|
+
drawn = [view drawViewHierarchyInRect:drawRect afterScreenUpdates:YES];
|
|
128
|
+
}];
|
|
129
|
+
if (!drawn) {
|
|
130
|
+
throw std::runtime_error(
|
|
131
|
+
"drawElementImageToTexture: the view could not be rendered (is it "
|
|
132
|
+
"attached to a window?)");
|
|
133
|
+
}
|
|
134
|
+
cgImage = image.CGImage;
|
|
135
|
+
if (cgImage != NULL) {
|
|
136
|
+
CGImageRetain(cgImage);
|
|
137
|
+
}
|
|
138
|
+
#else
|
|
139
|
+
NSBitmapImageRep *rep = [view bitmapImageRepForCachingDisplayInRect:bounds];
|
|
140
|
+
if (rep != nil) {
|
|
141
|
+
[view cacheDisplayInRect:bounds toBitmapImageRep:rep];
|
|
142
|
+
cgImage = rep.CGImage;
|
|
143
|
+
if (cgImage != NULL) {
|
|
144
|
+
CGImageRetain(cgImage);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
#endif
|
|
148
|
+
if (cgImage == NULL) {
|
|
149
|
+
throw std::runtime_error(
|
|
150
|
+
"drawElementImageToTexture: the view could not be rendered");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Normalize into premultiplied BGRA8 with no row padding, whatever the
|
|
154
|
+
// renderer produced.
|
|
155
|
+
ImageData result;
|
|
156
|
+
result.width = width;
|
|
157
|
+
result.height = height;
|
|
158
|
+
result.format = wgpu::TextureFormat::BGRA8Unorm;
|
|
159
|
+
result.premultiplied = true;
|
|
160
|
+
result.data.assign(static_cast<size_t>(width) * height * 4, 0);
|
|
161
|
+
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
162
|
+
CGContextRef context = CGBitmapContextCreate(
|
|
163
|
+
result.data.data(), width, height, 8, static_cast<size_t>(width) * 4,
|
|
164
|
+
colorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
|
|
165
|
+
CGColorSpaceRelease(colorSpace);
|
|
166
|
+
if (context == NULL) {
|
|
167
|
+
CGImageRelease(cgImage);
|
|
168
|
+
throw std::runtime_error(
|
|
169
|
+
"drawElementImageToTexture: could not allocate the pixel buffer");
|
|
170
|
+
}
|
|
171
|
+
#if !TARGET_OS_OSX
|
|
172
|
+
// The renderer already produced exactly width x height pixels.
|
|
173
|
+
const CGRect imageRect = CGRectMake(0, 0, width, height);
|
|
174
|
+
#else
|
|
175
|
+
// The cached display covers the whole view at the backing scale; draw it so
|
|
176
|
+
// that the source rectangle fills the output. CoreGraphics' origin is the
|
|
177
|
+
// bottom-left corner, hence the flipped vertical offset.
|
|
178
|
+
const CGRect imageRect = CGRectMake(
|
|
179
|
+
-sourceX * scaleX,
|
|
180
|
+
-(bounds.size.height - sourceY - sourceHeight) * scaleY,
|
|
181
|
+
bounds.size.width * scaleX, bounds.size.height * scaleY);
|
|
182
|
+
#endif
|
|
183
|
+
CGContextSetBlendMode(context, kCGBlendModeCopy);
|
|
184
|
+
CGContextDrawImage(context, imageRect, cgImage);
|
|
185
|
+
CGContextRelease(context);
|
|
186
|
+
CGImageRelease(cgImage);
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
} // namespace
|
|
191
|
+
|
|
192
|
+
void ApplePlatformContext::snapshotView(
|
|
193
|
+
const ViewSnapshotRequest &request,
|
|
194
|
+
std::function<void(ImageData)> onSuccess,
|
|
195
|
+
std::function<void(std::string)> onError) {
|
|
196
|
+
// Copies: the block outlives this call.
|
|
197
|
+
ViewLookup lookup = _viewLookup;
|
|
198
|
+
ViewSnapshotRequest snapshotRequest = request;
|
|
199
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
200
|
+
@autoreleasepool {
|
|
201
|
+
try {
|
|
202
|
+
onSuccess(snapshotViewOnMainThread(lookup, snapshotRequest));
|
|
203
|
+
} catch (const std::exception &error) {
|
|
204
|
+
onError(error.what());
|
|
205
|
+
} catch (...) {
|
|
206
|
+
onError("drawElementImageToTexture: unknown native error");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
37
212
|
wgpu::Surface ApplePlatformContext::makeSurface(wgpu::Instance instance,
|
|
38
213
|
void *surface, int width,
|
|
39
214
|
int height) {
|
package/apple/WebGPUModule.mm
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#import "WebGPUModule.h"
|
|
2
2
|
#include "ApplePlatformContext.h"
|
|
3
3
|
#import "GPUCanvasContext.h"
|
|
4
|
+
#import "RNWGUIKit.h"
|
|
4
5
|
|
|
5
6
|
#import <React/RCTBridge+Private.h>
|
|
6
7
|
#import <React/RCTCallInvoker.h>
|
|
@@ -31,6 +32,11 @@ static std::shared_ptr<rnwgpu::RNWebGPUManager> webgpuManager;
|
|
|
31
32
|
// calls setCallInvoker: during module initialization.
|
|
32
33
|
@synthesize callInvoker = _callInvoker;
|
|
33
34
|
|
|
35
|
+
// Synthesize the view registry so the TurboModule infra injects it too. It
|
|
36
|
+
// resolves React tags to native views in both Bridge and Bridgeless mode
|
|
37
|
+
// (Fabric component views included), which drawElementImageToTexture needs.
|
|
38
|
+
@synthesize viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED;
|
|
39
|
+
|
|
34
40
|
+ (std::shared_ptr<rnwgpu::RNWebGPUManager>)getManager {
|
|
35
41
|
return webgpuManager;
|
|
36
42
|
}
|
|
@@ -74,8 +80,21 @@ RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install) {
|
|
|
74
80
|
return [NSNumber numberWithBool:NO];
|
|
75
81
|
}
|
|
76
82
|
|
|
83
|
+
// Main-thread only (see ApplePlatformContext::ViewLookup). Weak: the module
|
|
84
|
+
// is invalidated on reload, after which lookups simply fail.
|
|
85
|
+
__weak WebGPUModule *weakSelf = self;
|
|
86
|
+
rnwgpu::ApplePlatformContext::ViewLookup viewLookup =
|
|
87
|
+
[weakSelf](int tag) -> void * {
|
|
88
|
+
WebGPUModule *strongSelf = weakSelf;
|
|
89
|
+
if (strongSelf == nil) {
|
|
90
|
+
return nullptr;
|
|
91
|
+
}
|
|
92
|
+
RNWGPlatformView *view =
|
|
93
|
+
[strongSelf.viewRegistry_DEPRECATED viewForReactTag:@(tag)];
|
|
94
|
+
return (__bridge void *)view;
|
|
95
|
+
};
|
|
77
96
|
std::shared_ptr<rnwgpu::PlatformContext> platformContext =
|
|
78
|
-
std::make_shared<rnwgpu::ApplePlatformContext>();
|
|
97
|
+
std::make_shared<rnwgpu::ApplePlatformContext>(std::move(viewLookup));
|
|
79
98
|
webgpuManager = std::make_shared<rnwgpu::RNWebGPUManager>(runtime, jsInvoker,
|
|
80
99
|
platformContext);
|
|
81
100
|
return @true;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
#include <functional>
|
|
4
4
|
#include <memory>
|
|
5
|
+
#include <mutex>
|
|
5
6
|
#include <span>
|
|
6
7
|
#include <string>
|
|
7
8
|
#include <utility>
|
|
@@ -72,11 +73,55 @@ public:
|
|
|
72
73
|
virtual void pause() = 0;
|
|
73
74
|
};
|
|
74
75
|
|
|
76
|
+
// A request to rasterize a native React Native view (looked up by its React
|
|
77
|
+
// tag) for GPUQueue.drawElementImageToTexture.
|
|
78
|
+
//
|
|
79
|
+
// The source rectangle is expressed in the view's own coordinate space, in
|
|
80
|
+
// density-independent points (the units of React Native layout). A
|
|
81
|
+
// non-positive sourceWidth/sourceHeight means "the whole view". The
|
|
82
|
+
// destination size is in pixels: the platform rasterizes the source rectangle
|
|
83
|
+
// scaled to exactly width x height.
|
|
84
|
+
struct ViewSnapshotRequest {
|
|
85
|
+
int viewTag = 0;
|
|
86
|
+
double sourceX = 0;
|
|
87
|
+
double sourceY = 0;
|
|
88
|
+
double sourceWidth = 0;
|
|
89
|
+
double sourceHeight = 0;
|
|
90
|
+
uint32_t width = 0;
|
|
91
|
+
uint32_t height = 0;
|
|
92
|
+
};
|
|
93
|
+
|
|
75
94
|
class PlatformContext {
|
|
76
95
|
public:
|
|
77
96
|
PlatformContext() = default;
|
|
78
97
|
virtual ~PlatformContext() = default;
|
|
79
98
|
|
|
99
|
+
// The platform context of the currently installed RNWebGPUManager, for API
|
|
100
|
+
// objects (e.g. GPUQueue) that are created without a reference to it. Set on
|
|
101
|
+
// install, cleared on teardown; a dev reload replaces it.
|
|
102
|
+
static void setCurrent(std::shared_ptr<PlatformContext> context) {
|
|
103
|
+
std::lock_guard<std::mutex> lock(currentMutex());
|
|
104
|
+
currentSlot() = std::move(context);
|
|
105
|
+
}
|
|
106
|
+
static std::shared_ptr<PlatformContext> current() {
|
|
107
|
+
std::lock_guard<std::mutex> lock(currentMutex());
|
|
108
|
+
return currentSlot();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Rasterize a native view into CPU pixels (see ViewSnapshotRequest). The
|
|
112
|
+
// implementation hops to the platform UI thread, so this is asynchronous:
|
|
113
|
+
// exactly one of onSuccess / onError is invoked, from an arbitrary thread
|
|
114
|
+
// (typically the UI thread). Callers must not touch JSI in the callbacks.
|
|
115
|
+
//
|
|
116
|
+
// The returned ImageData is tightly packed (no row padding), 4 bytes per
|
|
117
|
+
// pixel, `format` is rgba8unorm or bgra8unorm depending on what the platform
|
|
118
|
+
// rasterizer produces natively, and `premultiplied` reports its alpha
|
|
119
|
+
// representation.
|
|
120
|
+
virtual void
|
|
121
|
+
snapshotView(const ViewSnapshotRequest &request,
|
|
122
|
+
std::function<void(ImageData)> onSuccess,
|
|
123
|
+
std::function<void(std::string)> onError) = 0;
|
|
124
|
+
|
|
80
125
|
virtual wgpu::Surface makeSurface(wgpu::Instance instance, void *surface,
|
|
81
126
|
int width, int height) = 0;
|
|
82
127
|
virtual ImageData createImageBitmap(std::string blobId, double offset,
|
|
@@ -129,6 +174,16 @@ public:
|
|
|
129
174
|
// and return its absolute path. Lets the SharedTextureMemory example play
|
|
130
175
|
// a real decoded video without bundling an asset.
|
|
131
176
|
virtual std::string writeTestVideoFile() = 0;
|
|
177
|
+
|
|
178
|
+
private:
|
|
179
|
+
static std::mutex ¤tMutex() {
|
|
180
|
+
static std::mutex mutex;
|
|
181
|
+
return mutex;
|
|
182
|
+
}
|
|
183
|
+
static std::shared_ptr<PlatformContext> ¤tSlot() {
|
|
184
|
+
static std::shared_ptr<PlatformContext> slot;
|
|
185
|
+
return slot;
|
|
186
|
+
}
|
|
132
187
|
};
|
|
133
188
|
|
|
134
189
|
} // namespace rnwgpu
|
|
@@ -67,6 +67,10 @@ RNWebGPUManager::RNWebGPUManager(
|
|
|
67
67
|
// no invoker (best-effort; see README "Threading model").
|
|
68
68
|
async::RuntimeContext::registerMainRuntime(_jsRuntime, _jsCallInvoker);
|
|
69
69
|
|
|
70
|
+
// Expose the platform context to API objects created later without a
|
|
71
|
+
// reference to it (GPUQueue::drawElementImageToTexture).
|
|
72
|
+
PlatformContext::setCurrent(_platformContext);
|
|
73
|
+
|
|
70
74
|
auto gpu = std::make_shared<GPU>(*_jsRuntime);
|
|
71
75
|
auto rnWebGPU =
|
|
72
76
|
std::make_shared<RNWebGPU>(gpu, _platformContext, _jsCallInvoker);
|
|
@@ -248,6 +252,9 @@ RNWebGPUManager::~RNWebGPUManager() {
|
|
|
248
252
|
// contextId counter, and stale entries would alias new canvases onto dead
|
|
249
253
|
// surfaces.
|
|
250
254
|
SurfaceRegistry::getInstance().clear();
|
|
255
|
+
if (PlatformContext::current() == _platformContext) {
|
|
256
|
+
PlatformContext::setCurrent(nullptr);
|
|
257
|
+
}
|
|
251
258
|
_jsRuntime = nullptr;
|
|
252
259
|
_jsCallInvoker = nullptr;
|
|
253
260
|
}
|
|
@@ -446,8 +446,10 @@ public:
|
|
|
446
446
|
|
|
447
447
|
// Returns the texture for the current frame: the surface's swapchain texture
|
|
448
448
|
// when a surface is attached and healthy, an offscreen texture otherwise.
|
|
449
|
-
// Never returns null; throws when called before configure().
|
|
450
|
-
|
|
449
|
+
// Never returns null; throws when called before configure(). When frameEpoch
|
|
450
|
+
// is given, it receives the epoch identifying this frame (see
|
|
451
|
+
// isCurrentFrame).
|
|
452
|
+
wgpu::Texture getCurrentTexture(uint64_t *frameEpoch = nullptr) {
|
|
451
453
|
// Start-of-frame boundary; a new acquire supersedes any previous frame
|
|
452
454
|
// that never presented.
|
|
453
455
|
applyPendingAttach(/* supersedeInFlightFrame = */ true);
|
|
@@ -471,6 +473,9 @@ public:
|
|
|
471
473
|
_frameInFlight = true;
|
|
472
474
|
_acquiredFromSurface = false;
|
|
473
475
|
_frameEpoch++;
|
|
476
|
+
if (frameEpoch) {
|
|
477
|
+
*frameEpoch = _frameEpoch;
|
|
478
|
+
}
|
|
474
479
|
return texture;
|
|
475
480
|
}
|
|
476
481
|
// No slot available (pool not allocated yet, unsupported device, view
|
|
@@ -488,6 +493,9 @@ public:
|
|
|
488
493
|
_frameInFlight = true;
|
|
489
494
|
_acquiredFromSurface = false;
|
|
490
495
|
_frameEpoch++;
|
|
496
|
+
if (frameEpoch) {
|
|
497
|
+
*frameEpoch = _frameEpoch;
|
|
498
|
+
}
|
|
491
499
|
if (_surface) {
|
|
492
500
|
auto texture = acquireSurfaceTextureLocked();
|
|
493
501
|
if (texture) {
|
|
@@ -504,6 +512,17 @@ public:
|
|
|
504
512
|
return _texture;
|
|
505
513
|
}
|
|
506
514
|
|
|
515
|
+
// True while the frame started by the getCurrentTexture() call that
|
|
516
|
+
// returned frameEpoch is still the current one: nothing presented,
|
|
517
|
+
// configured, resized, unconfigured, detached, or adopted a surface since.
|
|
518
|
+
// A pending attach also ends it, so a frame that never presents cannot block
|
|
519
|
+
// adoption: the next getCurrentTexture() supersedes it, as an uncached call
|
|
520
|
+
// would.
|
|
521
|
+
bool isCurrentFrame(uint64_t frameEpoch) {
|
|
522
|
+
std::shared_lock<std::shared_mutex> lock(_mutex);
|
|
523
|
+
return _frameInFlight && !_hasPendingAttach && _frameEpoch == frameEpoch;
|
|
524
|
+
}
|
|
525
|
+
|
|
507
526
|
// Present the current frame. Runs synchronously on the thread that did
|
|
508
527
|
// getCurrentTexture/submit (main JS, Reanimated UI, or a worklet runtime),
|
|
509
528
|
// preserving Dawn surface thread-affinity. Frames whose texture was not
|
package/cpp/rnwgpu/api/GPU.cpp
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
namespace rnwgpu {
|
|
17
17
|
|
|
18
18
|
GPU::GPU(jsi::Runtime & /*runtime*/) : NativeObject(CLASS_NAME) {
|
|
19
|
-
// If a Graphite build of
|
|
19
|
+
// If a Graphite build of react-native-skia is loaded in this
|
|
20
20
|
// process, adopt its wgpu::Instance instead of creating our own, so
|
|
21
21
|
// Graphite's device and every JS-created device live on one instance. That
|
|
22
22
|
// makes pointer-based handoff (RNWebGPU.importDevice of Skia's device)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include "RNWebGPUManager.h"
|
|
4
4
|
#include <algorithm>
|
|
5
5
|
#include <memory>
|
|
6
|
+
#include <mutex>
|
|
6
7
|
#include <utility>
|
|
7
8
|
#include <vector>
|
|
8
9
|
|
|
@@ -52,7 +53,17 @@ std::shared_ptr<GPUTexture> GPUCanvasContext::getCurrentTexture() {
|
|
|
52
53
|
_surfaceInfo->reconfigure(width, height);
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
// Keep both the native texture and its JS wrapper stable until the frame
|
|
57
|
+
// ends. NativeObject::create already caches JS wrappers by native identity.
|
|
58
|
+
// The frame ends on any SurfaceInfo transition (present, configure, resize,
|
|
59
|
+
// unconfigure, surface detach or adoption), tracked by its frame epoch.
|
|
60
|
+
std::lock_guard<std::mutex> lock(_currentTextureMutex);
|
|
61
|
+
if (_currentTexture && _surfaceInfo->isCurrentFrame(_currentTextureEpoch)) {
|
|
62
|
+
return _currentTexture;
|
|
63
|
+
}
|
|
64
|
+
_currentTexture.reset();
|
|
65
|
+
|
|
66
|
+
auto texture = _surfaceInfo->getCurrentTexture(&_currentTextureEpoch);
|
|
56
67
|
if (texture == nullptr) {
|
|
57
68
|
throw std::runtime_error(
|
|
58
69
|
"[WebGPU] getCurrentTexture() failed to acquire a texture");
|
|
@@ -64,7 +75,8 @@ std::shared_ptr<GPUTexture> GPUCanvasContext::getCurrentTexture() {
|
|
|
64
75
|
|
|
65
76
|
// Pass reportsMemoryPressure=false to avoid triggering spurious Hermes GC
|
|
66
77
|
// cycles every frame since the canvas texture doesn't own the buffer.
|
|
67
|
-
|
|
78
|
+
_currentTexture = std::make_shared<GPUTexture>(texture, "", false);
|
|
79
|
+
return _currentTexture;
|
|
68
80
|
}
|
|
69
81
|
|
|
70
82
|
void GPUCanvasContext::present() {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
+
#include <cstdint>
|
|
3
4
|
#include <memory>
|
|
5
|
+
#include <mutex>
|
|
4
6
|
#include <string>
|
|
5
7
|
#include <utility>
|
|
6
8
|
|
|
@@ -66,6 +68,12 @@ private:
|
|
|
66
68
|
std::shared_ptr<Canvas> _canvas;
|
|
67
69
|
std::shared_ptr<SurfaceInfo> _surfaceInfo;
|
|
68
70
|
std::shared_ptr<GPU> _gpu;
|
|
71
|
+
// The current frame's texture, valid while
|
|
72
|
+
// _surfaceInfo->isCurrentFrame(_currentTextureEpoch). Guarded by
|
|
73
|
+
// _currentTextureMutex: contexts can be driven from worklet runtimes.
|
|
74
|
+
std::mutex _currentTextureMutex;
|
|
75
|
+
std::shared_ptr<GPUTexture> _currentTexture;
|
|
76
|
+
uint64_t _currentTextureEpoch = 0;
|
|
69
77
|
};
|
|
70
78
|
|
|
71
79
|
} // namespace rnwgpu
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
#include "GPUQueue.h"
|
|
2
2
|
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cmath>
|
|
3
5
|
#include <limits>
|
|
4
6
|
#include <memory>
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <utility>
|
|
5
9
|
#include <vector>
|
|
6
10
|
|
|
7
11
|
#include "Convertors.h"
|
|
12
|
+
#include "ImageBitmap.h"
|
|
13
|
+
#include "PlatformContext.h"
|
|
8
14
|
|
|
9
15
|
namespace rnwgpu {
|
|
10
16
|
|
|
@@ -193,6 +199,187 @@ void GPUQueue::copyExternalImageToTexture(
|
|
|
193
199
|
}
|
|
194
200
|
}
|
|
195
201
|
|
|
202
|
+
async::AsyncTaskHandle GPUQueue::drawElementImageToTexture(
|
|
203
|
+
jsi::Runtime &runtime, std::shared_ptr<GPUDrawElementImageSource> source,
|
|
204
|
+
std::shared_ptr<GPUDrawElementImageDestination> destination) {
|
|
205
|
+
auto platform = PlatformContext::current();
|
|
206
|
+
if (!platform) {
|
|
207
|
+
throw std::runtime_error(
|
|
208
|
+
"drawElementImageToTexture: react-native-webgpu is not installed");
|
|
209
|
+
}
|
|
210
|
+
if (!destination || !destination->info || !destination->info->texture) {
|
|
211
|
+
throw std::runtime_error(
|
|
212
|
+
"drawElementImageToTexture: `destination.texture` is required");
|
|
213
|
+
}
|
|
214
|
+
const auto &info = destination->info;
|
|
215
|
+
|
|
216
|
+
// Destination texture: only the 8-bit RGBA/BGRA formats the platform
|
|
217
|
+
// rasterizers produce natively are accepted (with a byte swizzle between
|
|
218
|
+
// the two); anything else would need a GPU blit.
|
|
219
|
+
wgpu::TexelCopyTextureInfo dst{};
|
|
220
|
+
Convertor conv;
|
|
221
|
+
if (!conv(dst.aspect, info->aspect) || !conv(dst.mipLevel, info->mipLevel) ||
|
|
222
|
+
!conv(dst.origin, info->origin) || !conv(dst.texture, info->texture)) {
|
|
223
|
+
throw std::runtime_error(
|
|
224
|
+
"drawElementImageToTexture: invalid destination");
|
|
225
|
+
}
|
|
226
|
+
const wgpu::Texture texture = info->texture->get();
|
|
227
|
+
bool destinationIsBgra = false;
|
|
228
|
+
switch (texture.GetFormat()) {
|
|
229
|
+
case wgpu::TextureFormat::RGBA8Unorm:
|
|
230
|
+
case wgpu::TextureFormat::RGBA8UnormSrgb:
|
|
231
|
+
destinationIsBgra = false;
|
|
232
|
+
break;
|
|
233
|
+
case wgpu::TextureFormat::BGRA8Unorm:
|
|
234
|
+
case wgpu::TextureFormat::BGRA8UnormSrgb:
|
|
235
|
+
destinationIsBgra = true;
|
|
236
|
+
break;
|
|
237
|
+
default:
|
|
238
|
+
throw std::runtime_error(
|
|
239
|
+
"drawElementImageToTexture: the destination texture must be "
|
|
240
|
+
"rgba8unorm, rgba8unorm-srgb, bgra8unorm or bgra8unorm-srgb");
|
|
241
|
+
}
|
|
242
|
+
if (!(texture.GetUsage() & wgpu::TextureUsage::CopyDst)) {
|
|
243
|
+
throw std::runtime_error(
|
|
244
|
+
"drawElementImageToTexture: the destination texture must have "
|
|
245
|
+
"GPUTextureUsage.COPY_DST");
|
|
246
|
+
}
|
|
247
|
+
if (texture.GetDimension() != wgpu::TextureDimension::e2D ||
|
|
248
|
+
texture.GetSampleCount() != 1) {
|
|
249
|
+
throw std::runtime_error(
|
|
250
|
+
"drawElementImageToTexture: the destination must be a single-sampled "
|
|
251
|
+
"2D texture");
|
|
252
|
+
}
|
|
253
|
+
if (dst.mipLevel >= texture.GetMipLevelCount()) {
|
|
254
|
+
throw std::runtime_error(
|
|
255
|
+
"drawElementImageToTexture: destination.mipLevel is out of range");
|
|
256
|
+
}
|
|
257
|
+
const uint32_t mipWidth =
|
|
258
|
+
std::max<uint32_t>(1, texture.GetWidth() >> dst.mipLevel);
|
|
259
|
+
const uint32_t mipHeight =
|
|
260
|
+
std::max<uint32_t>(1, texture.GetHeight() >> dst.mipLevel);
|
|
261
|
+
if (dst.origin.z >= texture.GetDepthOrArrayLayers()) {
|
|
262
|
+
throw std::runtime_error(
|
|
263
|
+
"drawElementImageToTexture: destination.origin.z is out of range");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Source rectangle (points, view space). Omitted width/height mean "the
|
|
267
|
+
// whole view", which only the platform knows.
|
|
268
|
+
ViewSnapshotRequest request{};
|
|
269
|
+
request.viewTag = source->viewTag;
|
|
270
|
+
request.sourceX = source->sourceX.value_or(0.0);
|
|
271
|
+
request.sourceY = source->sourceY.value_or(0.0);
|
|
272
|
+
request.sourceWidth = source->sourceWidth.value_or(0.0);
|
|
273
|
+
request.sourceHeight = source->sourceHeight.value_or(0.0);
|
|
274
|
+
if (!std::isfinite(request.sourceX) || !std::isfinite(request.sourceY) ||
|
|
275
|
+
!std::isfinite(request.sourceWidth) ||
|
|
276
|
+
!std::isfinite(request.sourceHeight) ||
|
|
277
|
+
(source->sourceWidth.has_value() && request.sourceWidth <= 0) ||
|
|
278
|
+
(source->sourceHeight.has_value() && request.sourceHeight <= 0)) {
|
|
279
|
+
throw std::runtime_error(
|
|
280
|
+
"drawElementImageToTexture: the source rectangle must be finite with "
|
|
281
|
+
"a positive width and height");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Destination size (pixels). Omitted means the source rectangle's natural
|
|
285
|
+
// pixel size (points times the device pixel ratio), which the platform
|
|
286
|
+
// computes; the bounds check then happens once the pixels are back.
|
|
287
|
+
if (destination->size.has_value()) {
|
|
288
|
+
wgpu::Extent3D size{};
|
|
289
|
+
if (!conv(size, destination->size.value())) {
|
|
290
|
+
throw std::runtime_error(
|
|
291
|
+
"drawElementImageToTexture: invalid destination.size");
|
|
292
|
+
}
|
|
293
|
+
if (size.width == 0 || size.height == 0 || size.depthOrArrayLayers != 1) {
|
|
294
|
+
throw std::runtime_error(
|
|
295
|
+
"drawElementImageToTexture: destination.size must have a positive "
|
|
296
|
+
"width and height and a depthOrArrayLayers of 1");
|
|
297
|
+
}
|
|
298
|
+
if (size.width > mipWidth - std::min(dst.origin.x, mipWidth) ||
|
|
299
|
+
size.height > mipHeight - std::min(dst.origin.y, mipHeight)) {
|
|
300
|
+
throw std::runtime_error(
|
|
301
|
+
"drawElementImageToTexture: destination.origin + destination.size "
|
|
302
|
+
"exceeds the texture's extent at destination.mipLevel");
|
|
303
|
+
}
|
|
304
|
+
request.width = size.width;
|
|
305
|
+
request.height = size.height;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// premultipliedAlpha defaults to false, like copyExternalImageToTexture.
|
|
309
|
+
const bool destinationPremultiplied =
|
|
310
|
+
info->premultipliedAlpha.value_or(false);
|
|
311
|
+
|
|
312
|
+
// Settle on the CALLING runtime's context (see GPUBuffer::mapAsync): the
|
|
313
|
+
// snapshot completes on the UI thread and is deposited into that context's
|
|
314
|
+
// mailbox; the texture write and the resolve then run on the runtime's own
|
|
315
|
+
// thread during its next tick.
|
|
316
|
+
auto context =
|
|
317
|
+
async::RuntimeContext::getOrCreate(runtime, _async->instance());
|
|
318
|
+
auto queue = _instance;
|
|
319
|
+
// Keeps the destination texture alive until the write is issued.
|
|
320
|
+
auto gpuTexture = info->texture;
|
|
321
|
+
return context->postTask(
|
|
322
|
+
[platform, request, dst, mipWidth, mipHeight, destinationIsBgra,
|
|
323
|
+
destinationPremultiplied, queue,
|
|
324
|
+
gpuTexture](const async::AsyncTaskHandle::ResolveFunction &resolve,
|
|
325
|
+
const async::AsyncTaskHandle::RejectFunction &reject) {
|
|
326
|
+
platform->snapshotView(
|
|
327
|
+
request,
|
|
328
|
+
[resolve, reject, dst, mipWidth, mipHeight, destinationIsBgra,
|
|
329
|
+
destinationPremultiplied, queue,
|
|
330
|
+
gpuTexture](ImageData image) mutable {
|
|
331
|
+
// Arbitrary (UI) thread: validate without touching JSI.
|
|
332
|
+
if (image.width == 0 || image.height == 0 ||
|
|
333
|
+
image.data.size() < image.width * image.height * 4) {
|
|
334
|
+
reject("drawElementImageToTexture: the view produced no "
|
|
335
|
+
"pixels (is it mounted with a non-zero size?)");
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (image.width > mipWidth - std::min<uint32_t>(dst.origin.x,
|
|
339
|
+
mipWidth) ||
|
|
340
|
+
image.height > mipHeight - std::min<uint32_t>(dst.origin.y,
|
|
341
|
+
mipHeight)) {
|
|
342
|
+
reject("drawElementImageToTexture: the view's natural pixel "
|
|
343
|
+
"size (" +
|
|
344
|
+
std::to_string(image.width) + "x" +
|
|
345
|
+
std::to_string(image.height) +
|
|
346
|
+
") does not fit in the destination texture at "
|
|
347
|
+
"destination.origin; pass destination.size to scale "
|
|
348
|
+
"it, or create a larger texture");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
resolve([image = std::move(image), dst, destinationIsBgra,
|
|
352
|
+
destinationPremultiplied, queue,
|
|
353
|
+
gpuTexture](jsi::Runtime &) mutable -> jsi::Value {
|
|
354
|
+
// Owning runtime's thread. Bring the pixels into the
|
|
355
|
+
// destination's channel order and alpha representation, then
|
|
356
|
+
// issue the write.
|
|
357
|
+
const bool sourceIsBgra =
|
|
358
|
+
image.format == wgpu::TextureFormat::BGRA8Unorm ||
|
|
359
|
+
image.format == wgpu::TextureFormat::BGRA8UnormSrgb;
|
|
360
|
+
uint8_t *pixels = image.data.data();
|
|
361
|
+
const size_t byteLength = image.width * image.height * 4;
|
|
362
|
+
if (sourceIsBgra != destinationIsBgra) {
|
|
363
|
+
for (size_t i = 0; i + 3 < byteLength; i += 4) {
|
|
364
|
+
std::swap(pixels[i], pixels[i + 2]);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
convertAlpha(pixels, byteLength, image.premultiplied,
|
|
368
|
+
destinationPremultiplied);
|
|
369
|
+
wgpu::TexelCopyBufferLayout layout{};
|
|
370
|
+
layout.offset = 0;
|
|
371
|
+
layout.bytesPerRow = static_cast<uint32_t>(image.width * 4);
|
|
372
|
+
layout.rowsPerImage = static_cast<uint32_t>(image.height);
|
|
373
|
+
wgpu::Extent3D extent{static_cast<uint32_t>(image.width),
|
|
374
|
+
static_cast<uint32_t>(image.height), 1};
|
|
375
|
+
queue.WriteTexture(&dst, pixels, byteLength, &layout, &extent);
|
|
376
|
+
return jsi::Value::undefined();
|
|
377
|
+
});
|
|
378
|
+
},
|
|
379
|
+
[reject](std::string error) { reject(std::move(error)); });
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
196
383
|
void GPUQueue::writeTexture(std::shared_ptr<GPUImageCopyTexture> destination,
|
|
197
384
|
std::shared_ptr<ArrayBuffer> data,
|
|
198
385
|
std::shared_ptr<GPUImageDataLayout> dataLayout,
|