uplot-webgpu 0.1.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/CANVAS_PROXY.md +602 -0
- package/README.md +854 -0
- package/favicon.ico +0 -0
- package/index.html +14 -0
- package/index.js +21 -0
- package/original/paths.canvas2d/bars.js +252 -0
- package/original/paths.canvas2d/catmullRomCentrip.js +125 -0
- package/original/paths.canvas2d/linear.js +170 -0
- package/original/paths.canvas2d/monotoneCubic.js +68 -0
- package/original/paths.canvas2d/points.js +66 -0
- package/original/paths.canvas2d/spline.js +103 -0
- package/original/paths.canvas2d/stepped.js +124 -0
- package/original/paths.canvas2d/utils.js +301 -0
- package/original/uPlot.canvas2d.js +3548 -0
- package/package.json +110 -0
- package/paths/bars.js +253 -0
- package/paths/catmullRomCentrip.js +126 -0
- package/paths/linear.js +171 -0
- package/paths/monotoneCubic.js +69 -0
- package/paths/points.js +67 -0
- package/paths/spline.js +104 -0
- package/paths/stepped.js +125 -0
- package/paths/utils.js +301 -0
- package/scripts/uPlot.css +168 -0
- package/scripts/uPlot.d.ts +26 -0
- package/scripts/uPlot.js +3687 -0
- package/scripts/utils/dom.js +124 -0
- package/scripts/utils/domClasses.js +22 -0
- package/scripts/utils/feats.js +13 -0
- package/scripts/utils/fmtDate.js +398 -0
- package/scripts/utils/opts.js +844 -0
- package/scripts/utils/strings.js +22 -0
- package/scripts/utils/sync.js +27 -0
- package/scripts/utils/utils.js +692 -0
- package/scripts/webgpu/GPUPath.d.ts +46 -0
- package/scripts/webgpu/GPUPath.js +633 -0
- package/scripts/webgpu/GPUPath.ts +634 -0
- package/scripts/webgpu/WebGPURenderer.d.ts +176 -0
- package/scripts/webgpu/WebGPURenderer.js +4256 -0
- package/scripts/webgpu/WebGPURenderer.ts +4257 -0
- package/scripts/webgpu/browserSmokeHarness.js +105 -0
- package/scripts/webgpu/exporters.d.ts +8 -0
- package/scripts/webgpu/exporters.js +212 -0
- package/scripts/webgpu/shaders.d.ts +2 -0
- package/scripts/webgpu/shaders.js +76 -0
- package/scripts/webgpu/shaders.ts +77 -0
- package/scripts/webgpu/smokeTest.d.ts +2 -0
- package/scripts/webgpu/smokeTest.js +144 -0
- package/scripts/webgpu/webgpu-ambient.d.ts +41 -0
- package/tinybuild.config.js +109 -0
package/CANVAS_PROXY.md
ADDED
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
# WebGPU Canvas proxy
|
|
2
|
+
|
|
3
|
+
`uplot-webgpu` includes a Canvas2D-like renderer backed by WebGPU.
|
|
4
|
+
|
|
5
|
+
This renderer exists primarily so the WebGPU uPlot port can support normal uPlot drawing paths, plugin drawing hooks, path rendering, gradients, images, text, clipping, and compositing.
|
|
6
|
+
|
|
7
|
+
It can also be used directly without uPlot.
|
|
8
|
+
|
|
9
|
+
## When to use the standalone renderer
|
|
10
|
+
|
|
11
|
+
Use `WebGPURenderer` directly when:
|
|
12
|
+
|
|
13
|
+
1. You want Canvas2D-style drawing commands that render through WebGPU.
|
|
14
|
+
2. You want to build custom high-throughput visualizations outside uPlot.
|
|
15
|
+
3. You want to mix chart rendering with custom GPU drawing.
|
|
16
|
+
4. You want to reuse WebGPU resources across charts, compute passes, or signal buffers.
|
|
17
|
+
5. You want a Canvas-like API for dashboards, telemetry, or visualization tools.
|
|
18
|
+
|
|
19
|
+
Normal uPlot users do not need this. The default `uPlot` constructor already creates and manages the renderer internally.
|
|
20
|
+
|
|
21
|
+
## Basic usage
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
import {
|
|
25
|
+
WebGPURenderer,
|
|
26
|
+
GPUPath,
|
|
27
|
+
} from "uplot-webgpu";
|
|
28
|
+
|
|
29
|
+
const canvas = document.querySelector("canvas");
|
|
30
|
+
|
|
31
|
+
const ctx = new WebGPURenderer(canvas, {
|
|
32
|
+
memoryMode: "balanced",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await ctx.init();
|
|
36
|
+
|
|
37
|
+
ctx.resize(canvas.clientWidth, canvas.clientHeight);
|
|
38
|
+
|
|
39
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
40
|
+
|
|
41
|
+
ctx.fillStyle = "#f8fafc";
|
|
42
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
43
|
+
|
|
44
|
+
ctx.strokeStyle = "#276ef1";
|
|
45
|
+
ctx.lineWidth = 3;
|
|
46
|
+
|
|
47
|
+
ctx.beginPath();
|
|
48
|
+
ctx.moveTo(40, 140);
|
|
49
|
+
ctx.bezierCurveTo(120, 20, 220, 260, 340, 80);
|
|
50
|
+
ctx.stroke();
|
|
51
|
+
|
|
52
|
+
ctx.fillStyle = "rgba(39, 110, 241, 0.18)";
|
|
53
|
+
ctx.beginPath();
|
|
54
|
+
ctx.roundRect(60, 180, 240, 90, 18);
|
|
55
|
+
ctx.fill();
|
|
56
|
+
|
|
57
|
+
await ctx.flush();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Canvas-like drawing API
|
|
61
|
+
|
|
62
|
+
The renderer mirrors the Canvas2D APIs needed by uPlot and common plugin workflows.
|
|
63
|
+
|
|
64
|
+
### State
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
ctx.save();
|
|
68
|
+
ctx.restore();
|
|
69
|
+
|
|
70
|
+
ctx.globalAlpha = 0.75;
|
|
71
|
+
ctx.globalCompositeOperation = "source-over";
|
|
72
|
+
|
|
73
|
+
ctx.lineWidth = 2;
|
|
74
|
+
ctx.lineCap = "round";
|
|
75
|
+
ctx.lineJoin = "round";
|
|
76
|
+
ctx.miterLimit = 10;
|
|
77
|
+
|
|
78
|
+
ctx.strokeStyle = "#276ef1";
|
|
79
|
+
ctx.fillStyle = "rgba(39, 110, 241, 0.15)";
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Transforms
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
ctx.translate(x, y);
|
|
86
|
+
ctx.scale(x, y);
|
|
87
|
+
ctx.rotate(angle);
|
|
88
|
+
|
|
89
|
+
ctx.setTransform(a, b, c, d, e, f);
|
|
90
|
+
ctx.resetTransform();
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Paths
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
ctx.beginPath();
|
|
97
|
+
|
|
98
|
+
ctx.moveTo(x, y);
|
|
99
|
+
ctx.lineTo(x, y);
|
|
100
|
+
|
|
101
|
+
ctx.quadraticCurveTo(cpx, cpy, x, y);
|
|
102
|
+
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
|
|
103
|
+
|
|
104
|
+
ctx.arc(cx, cy, r, startAngle, endAngle);
|
|
105
|
+
ctx.arcTo(x1, y1, x2, y2, radius);
|
|
106
|
+
ctx.ellipse(x, y, rx, ry, rotation, startAngle, endAngle);
|
|
107
|
+
|
|
108
|
+
ctx.rect(x, y, w, h);
|
|
109
|
+
ctx.roundRect(x, y, w, h, radius);
|
|
110
|
+
|
|
111
|
+
ctx.closePath();
|
|
112
|
+
|
|
113
|
+
ctx.fill();
|
|
114
|
+
ctx.stroke();
|
|
115
|
+
ctx.clip();
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Rectangles
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
ctx.fillRect(x, y, w, h);
|
|
122
|
+
ctx.strokeRect(x, y, w, h);
|
|
123
|
+
ctx.clearRect(x, y, w, h);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Text
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
ctx.font = "14px system-ui";
|
|
130
|
+
ctx.textAlign = "center";
|
|
131
|
+
ctx.textBaseline = "middle";
|
|
132
|
+
|
|
133
|
+
ctx.fillText("hello", x, y);
|
|
134
|
+
ctx.strokeText("hello", x, y);
|
|
135
|
+
|
|
136
|
+
const metrics = ctx.measureText("hello");
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Text is drawn through a DOM-backed text layer where appropriate. This keeps labels and plugin text crisp, selectable by browser layout rules where supported, and close to Canvas2D behavior for uPlot usage.
|
|
140
|
+
|
|
141
|
+
Browser font metrics can still differ slightly from native Canvas2D.
|
|
142
|
+
|
|
143
|
+
### Gradients
|
|
144
|
+
|
|
145
|
+
```js
|
|
146
|
+
const grad = ctx.createLinearGradient(0, 0, 400, 0);
|
|
147
|
+
|
|
148
|
+
grad.addColorStop(0, "#276ef1");
|
|
149
|
+
grad.addColorStop(0.5, "#23a455");
|
|
150
|
+
grad.addColorStop(1, "#e4572e");
|
|
151
|
+
|
|
152
|
+
ctx.fillStyle = grad;
|
|
153
|
+
ctx.fillRect(40, 40, 360, 90);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Radial gradient:
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
const grad = ctx.createRadialGradient(200, 120, 4, 200, 120, 100);
|
|
160
|
+
|
|
161
|
+
grad.addColorStop(0, "white");
|
|
162
|
+
grad.addColorStop(0.45, "rgba(228, 87, 46, 0.35)");
|
|
163
|
+
grad.addColorStop(1, "rgba(124, 58, 237, 0.08)");
|
|
164
|
+
|
|
165
|
+
ctx.fillStyle = grad;
|
|
166
|
+
|
|
167
|
+
ctx.beginPath();
|
|
168
|
+
ctx.ellipse(200, 120, 120, 70, -0.25, 0, Math.PI * 2);
|
|
169
|
+
ctx.fill();
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Conic gradient:
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
const grad = ctx.createConicGradient(-Math.PI / 2, 200, 160);
|
|
176
|
+
|
|
177
|
+
grad.addColorStop(0, "#276ef1");
|
|
178
|
+
grad.addColorStop(0.33, "#23a455");
|
|
179
|
+
grad.addColorStop(0.66, "#e4572e");
|
|
180
|
+
grad.addColorStop(1, "#276ef1");
|
|
181
|
+
|
|
182
|
+
ctx.fillStyle = grad;
|
|
183
|
+
|
|
184
|
+
ctx.beginPath();
|
|
185
|
+
ctx.arc(200, 160, 90, 0, Math.PI * 2);
|
|
186
|
+
ctx.arc(200, 160, 40, 0, Math.PI * 2);
|
|
187
|
+
ctx.fill("evenodd");
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Patterns
|
|
191
|
+
|
|
192
|
+
```js
|
|
193
|
+
const patternCanvas = document.createElement("canvas");
|
|
194
|
+
patternCanvas.width = 16;
|
|
195
|
+
patternCanvas.height = 16;
|
|
196
|
+
|
|
197
|
+
const pctx = patternCanvas.getContext("2d");
|
|
198
|
+
|
|
199
|
+
pctx.fillStyle = "#fff4d7";
|
|
200
|
+
pctx.fillRect(0, 0, 16, 16);
|
|
201
|
+
|
|
202
|
+
pctx.fillStyle = "#d39200";
|
|
203
|
+
pctx.fillRect(0, 0, 8, 8);
|
|
204
|
+
pctx.fillRect(8, 8, 8, 8);
|
|
205
|
+
|
|
206
|
+
const pattern = ctx.createPattern(patternCanvas, "repeat");
|
|
207
|
+
|
|
208
|
+
ctx.fillStyle = pattern;
|
|
209
|
+
ctx.fillRect(40, 40, 320, 120);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Images
|
|
213
|
+
|
|
214
|
+
```js
|
|
215
|
+
const img = new Image();
|
|
216
|
+
|
|
217
|
+
img.src = "/example.png";
|
|
218
|
+
await img.decode();
|
|
219
|
+
|
|
220
|
+
ctx.drawImage(img, 40, 40, 256, 160);
|
|
221
|
+
|
|
222
|
+
await ctx.flush();
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
For high-throughput image paths, prefer `ImageBitmap` where possible:
|
|
226
|
+
|
|
227
|
+
```js
|
|
228
|
+
const bitmap = await createImageBitmap(img);
|
|
229
|
+
|
|
230
|
+
ctx.drawImage(bitmap, 40, 40, 256, 160);
|
|
231
|
+
|
|
232
|
+
await ctx.flush();
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Supported forms:
|
|
236
|
+
|
|
237
|
+
```js
|
|
238
|
+
ctx.drawImage(image, dx, dy);
|
|
239
|
+
ctx.drawImage(image, dx, dy, dw, dh);
|
|
240
|
+
ctx.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Clipping
|
|
244
|
+
|
|
245
|
+
```js
|
|
246
|
+
ctx.save();
|
|
247
|
+
|
|
248
|
+
ctx.beginPath();
|
|
249
|
+
ctx.roundRect(70, 160, 280, 140, 24);
|
|
250
|
+
ctx.clip();
|
|
251
|
+
|
|
252
|
+
ctx.fillStyle = "rgba(39, 110, 241, 0.25)";
|
|
253
|
+
ctx.fillRect(0, 0, 500, 500);
|
|
254
|
+
|
|
255
|
+
ctx.restore();
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Even-odd fill:
|
|
259
|
+
|
|
260
|
+
```js
|
|
261
|
+
ctx.beginPath();
|
|
262
|
+
|
|
263
|
+
ctx.arc(200, 200, 90, 0, Math.PI * 2);
|
|
264
|
+
ctx.arc(200, 200, 40, 0, Math.PI * 2);
|
|
265
|
+
|
|
266
|
+
ctx.fill("evenodd");
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Composite modes
|
|
270
|
+
|
|
271
|
+
```js
|
|
272
|
+
ctx.save();
|
|
273
|
+
|
|
274
|
+
ctx.globalAlpha = 0.75;
|
|
275
|
+
ctx.globalCompositeOperation = "lighter";
|
|
276
|
+
|
|
277
|
+
ctx.fillStyle = "rgba(39, 110, 241, 0.55)";
|
|
278
|
+
ctx.beginPath();
|
|
279
|
+
ctx.arc(160, 230, 70, 0, Math.PI * 2);
|
|
280
|
+
ctx.fill();
|
|
281
|
+
|
|
282
|
+
ctx.fillStyle = "rgba(228, 87, 46, 0.55)";
|
|
283
|
+
ctx.beginPath();
|
|
284
|
+
ctx.arc(240, 230, 70, 0, Math.PI * 2);
|
|
285
|
+
ctx.fill();
|
|
286
|
+
|
|
287
|
+
ctx.restore();
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Common modes such as `source-over`, `lighter`, `multiply`, `screen`, and `destination-out` are covered by the compatibility gallery.
|
|
291
|
+
|
|
292
|
+
Some browser-native Canvas2D compositing edge cases may not match pixel-perfectly.
|
|
293
|
+
|
|
294
|
+
## GPUPath
|
|
295
|
+
|
|
296
|
+
`GPUPath` is the WebGPU-side `Path2D`-style helper. It can be passed to `fill`, `stroke`, or `clip`.
|
|
297
|
+
|
|
298
|
+
```js
|
|
299
|
+
import {
|
|
300
|
+
WebGPURenderer,
|
|
301
|
+
GPUPath,
|
|
302
|
+
} from "uplot-webgpu";
|
|
303
|
+
|
|
304
|
+
const ctx = new WebGPURenderer(canvas);
|
|
305
|
+
|
|
306
|
+
await ctx.init();
|
|
307
|
+
|
|
308
|
+
const path = new GPUPath();
|
|
309
|
+
|
|
310
|
+
path.moveTo(80, 80);
|
|
311
|
+
path.lineTo(220, 120);
|
|
312
|
+
path.quadraticCurveTo(300, 40, 380, 180);
|
|
313
|
+
path.lineTo(80, 180);
|
|
314
|
+
path.closePath();
|
|
315
|
+
|
|
316
|
+
ctx.fillStyle = "rgba(39, 110, 241, 0.20)";
|
|
317
|
+
ctx.strokeStyle = "#276ef1";
|
|
318
|
+
ctx.lineWidth = 2;
|
|
319
|
+
|
|
320
|
+
ctx.fill(path);
|
|
321
|
+
ctx.stroke(path);
|
|
322
|
+
|
|
323
|
+
await ctx.flush();
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
`GPUPath` is useful when:
|
|
327
|
+
|
|
328
|
+
1. You want to reuse geometry across frames.
|
|
329
|
+
2. You want to prepare paths outside the immediate draw call.
|
|
330
|
+
3. You want a `Path2D`-style object for custom render code.
|
|
331
|
+
4. You want to share path-building logic with uPlot plugin code.
|
|
332
|
+
|
|
333
|
+
## GPU buffer interop
|
|
334
|
+
|
|
335
|
+
The standalone renderer gives you access to the underlying WebGPU device:
|
|
336
|
+
|
|
337
|
+
```js
|
|
338
|
+
const device = ctx.getDevice();
|
|
339
|
+
|
|
340
|
+
if (!device)
|
|
341
|
+
throw new Error("WebGPU device was not initialized");
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
You can use that device to create buffers that are shared with your own shaders:
|
|
345
|
+
|
|
346
|
+
```js
|
|
347
|
+
const sampleCount = 1_000_000;
|
|
348
|
+
|
|
349
|
+
const signalBuffer = device.createBuffer({
|
|
350
|
+
size: sampleCount * 2 * 4,
|
|
351
|
+
usage:
|
|
352
|
+
GPUBufferUsage.STORAGE |
|
|
353
|
+
GPUBufferUsage.VERTEX |
|
|
354
|
+
GPUBufferUsage.COPY_DST |
|
|
355
|
+
GPUBufferUsage.COPY_SRC,
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Upload x/y points:
|
|
360
|
+
|
|
361
|
+
```js
|
|
362
|
+
const points = new Float32Array(sampleCount * 2);
|
|
363
|
+
|
|
364
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
365
|
+
points[i * 2 + 0] = i;
|
|
366
|
+
points[i * 2 + 1] = Math.sin(i * 0.01);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
device.queue.writeBuffer(signalBuffer, 0, points);
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Use the same buffer in a compute pass:
|
|
373
|
+
|
|
374
|
+
```js
|
|
375
|
+
const encoder = device.createCommandEncoder();
|
|
376
|
+
|
|
377
|
+
const pass = encoder.beginComputePass();
|
|
378
|
+
|
|
379
|
+
pass.setPipeline(computePipeline);
|
|
380
|
+
pass.setBindGroup(0, computeBindGroup);
|
|
381
|
+
pass.dispatchWorkgroups(Math.ceil(sampleCount / 256));
|
|
382
|
+
|
|
383
|
+
pass.end();
|
|
384
|
+
|
|
385
|
+
device.queue.submit([encoder.finish()]);
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Then use the same buffer in a custom render pipeline or another analysis pass.
|
|
389
|
+
|
|
390
|
+
This is the main reason the WebGPU version can fit into larger signal-processing, simulation, or telemetry systems more naturally than a pure Canvas2D renderer.
|
|
391
|
+
|
|
392
|
+
## Readback and export helpers
|
|
393
|
+
|
|
394
|
+
Readback and export helpers are split out of the core renderer and loaded lazily. Normal drawing does not pay for PNG/BMP/SVG export helper code.
|
|
395
|
+
|
|
396
|
+
```js
|
|
397
|
+
const imageData = await ctx.getImageDataAsync(0, 0, canvas.width, canvas.height);
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Export as blob:
|
|
401
|
+
|
|
402
|
+
```js
|
|
403
|
+
const blob = await ctx.convertToBlob({
|
|
404
|
+
type: "image/png",
|
|
405
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Canvas-style callback:
|
|
409
|
+
|
|
410
|
+
```js
|
|
411
|
+
await ctx.toBlob(blob => {
|
|
412
|
+
console.log(blob);
|
|
413
|
+
}, "image/png");
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Data URL:
|
|
417
|
+
|
|
418
|
+
```js
|
|
419
|
+
const dataUrl = await ctx.toDataURLAsync("image/png");
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
SVG wrapper export:
|
|
423
|
+
|
|
424
|
+
```js
|
|
425
|
+
const svgText = await ctx.toSVGStringAsync();
|
|
426
|
+
const svgBlob = await ctx.toSVGBlob();
|
|
427
|
+
const svgDataUrl = await ctx.toSVGDataURLAsync();
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Direct optional export module:
|
|
431
|
+
|
|
432
|
+
```js
|
|
433
|
+
import {
|
|
434
|
+
rgbaImageDataToPngBytes,
|
|
435
|
+
rgbaImageDataToBmpBytes,
|
|
436
|
+
} from "uplot-webgpu/webgpu/exporters";
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## Memory modes
|
|
440
|
+
|
|
441
|
+
Balanced default:
|
|
442
|
+
|
|
443
|
+
```js
|
|
444
|
+
const ctx = new WebGPURenderer(canvas, {
|
|
445
|
+
memoryMode: "balanced",
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Lower retained memory:
|
|
450
|
+
|
|
451
|
+
```js
|
|
452
|
+
const ctx = new WebGPURenderer(canvas, {
|
|
453
|
+
memoryMode: "low",
|
|
454
|
+
});
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Animation-heavy throughput mode:
|
|
458
|
+
|
|
459
|
+
```js
|
|
460
|
+
const ctx = new WebGPURenderer(canvas, {
|
|
461
|
+
memoryMode: "throughput",
|
|
462
|
+
});
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Memory helpers:
|
|
466
|
+
|
|
467
|
+
```js
|
|
468
|
+
ctx.getMemoryStats();
|
|
469
|
+
ctx.trimMemory();
|
|
470
|
+
ctx.destroy();
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
## Shared runtime
|
|
474
|
+
|
|
475
|
+
Multiple renderers can share a WebGPU runtime. This helps dashboards with many charts or canvases.
|
|
476
|
+
|
|
477
|
+
```js
|
|
478
|
+
WebGPURenderer.setSharedRuntimeEnabled(true);
|
|
479
|
+
|
|
480
|
+
const a = new WebGPURenderer(canvasA);
|
|
481
|
+
const b = new WebGPURenderer(canvasB);
|
|
482
|
+
|
|
483
|
+
await Promise.all([
|
|
484
|
+
a.init(),
|
|
485
|
+
b.init(),
|
|
486
|
+
]);
|
|
487
|
+
|
|
488
|
+
console.log(WebGPURenderer.getSharedRuntimeStats());
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Shared runtime is useful when:
|
|
492
|
+
|
|
493
|
+
1. Many charts exist on one page.
|
|
494
|
+
2. You want fewer device/adapter setup paths.
|
|
495
|
+
3. You want consistent GPU resource management.
|
|
496
|
+
4. You are combining uPlot charts with standalone WebGPU visualizations.
|
|
497
|
+
|
|
498
|
+
## Animation loop
|
|
499
|
+
|
|
500
|
+
```js
|
|
501
|
+
import { WebGPURenderer } from "uplot-webgpu";
|
|
502
|
+
|
|
503
|
+
const ctx = new WebGPURenderer(canvas, {
|
|
504
|
+
memoryMode: "throughput",
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
await ctx.init();
|
|
508
|
+
|
|
509
|
+
function frame(t) {
|
|
510
|
+
ctx.resize(canvas.clientWidth, canvas.clientHeight);
|
|
511
|
+
|
|
512
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
513
|
+
|
|
514
|
+
ctx.strokeStyle = "#276ef1";
|
|
515
|
+
ctx.lineWidth = 2;
|
|
516
|
+
|
|
517
|
+
ctx.beginPath();
|
|
518
|
+
|
|
519
|
+
for (let i = 0; i < 1000; i++) {
|
|
520
|
+
const x = i;
|
|
521
|
+
const y = 160 + Math.sin(i * 0.02 + t * 0.004) * 80;
|
|
522
|
+
|
|
523
|
+
if (i === 0)
|
|
524
|
+
ctx.moveTo(x, y);
|
|
525
|
+
else
|
|
526
|
+
ctx.lineTo(x, y);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
ctx.stroke();
|
|
530
|
+
ctx.flush();
|
|
531
|
+
|
|
532
|
+
requestAnimationFrame(frame);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
requestAnimationFrame(frame);
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
## Using the renderer beside uPlot
|
|
539
|
+
|
|
540
|
+
The normal uPlot constructor owns its renderer internally:
|
|
541
|
+
|
|
542
|
+
```js
|
|
543
|
+
import uPlot from "uplot-webgpu";
|
|
544
|
+
|
|
545
|
+
const chart = new uPlot(opts, data, target);
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
For advanced integrations, the renderer is available from the chart context:
|
|
549
|
+
|
|
550
|
+
```js
|
|
551
|
+
const renderer = chart.ctx;
|
|
552
|
+
|
|
553
|
+
renderer.getMemoryStats();
|
|
554
|
+
renderer.trimMemory();
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
You can also use standalone renderers beside uPlot charts:
|
|
558
|
+
|
|
559
|
+
```js
|
|
560
|
+
import uPlot, { WebGPURenderer } from "uplot-webgpu";
|
|
561
|
+
|
|
562
|
+
const chart = new uPlot(opts, data, chartEl);
|
|
563
|
+
|
|
564
|
+
const custom = new WebGPURenderer(customCanvas);
|
|
565
|
+
|
|
566
|
+
await custom.init();
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
## Practical compatibility notes
|
|
570
|
+
|
|
571
|
+
This renderer is focused on the drawing paths needed by uPlot and high-throughput visualization workloads.
|
|
572
|
+
|
|
573
|
+
Covered areas include:
|
|
574
|
+
|
|
575
|
+
- paths
|
|
576
|
+
- strokes
|
|
577
|
+
- fills
|
|
578
|
+
- dashed lines
|
|
579
|
+
- line caps and joins
|
|
580
|
+
- gradients
|
|
581
|
+
- patterns
|
|
582
|
+
- clipping
|
|
583
|
+
- text
|
|
584
|
+
- images
|
|
585
|
+
- compositing
|
|
586
|
+
- readback
|
|
587
|
+
- PNG/BMP/SVG export helpers
|
|
588
|
+
- lifecycle and memory trimming
|
|
589
|
+
- shared WebGPU runtime
|
|
590
|
+
- access to WebGPU device resources for custom buffer interop
|
|
591
|
+
|
|
592
|
+
Areas that can differ from native Canvas2D:
|
|
593
|
+
|
|
594
|
+
- browser font metrics
|
|
595
|
+
- antialiasing
|
|
596
|
+
- exact text rasterization
|
|
597
|
+
- rare compositing edge cases
|
|
598
|
+
- pathological self-intersecting geometry
|
|
599
|
+
- browser-specific image source behavior
|
|
600
|
+
- unsupported or unusual Canvas2D APIs outside the uPlot/plugin workload
|
|
601
|
+
|
|
602
|
+
For best results, treat this as a high-throughput visualization renderer with a Canvas-like API, not as a universal browser Canvas2D replacement for every possible canvas application.
|