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.
Files changed (50) hide show
  1. package/CANVAS_PROXY.md +602 -0
  2. package/README.md +854 -0
  3. package/favicon.ico +0 -0
  4. package/index.html +14 -0
  5. package/index.js +21 -0
  6. package/original/paths.canvas2d/bars.js +252 -0
  7. package/original/paths.canvas2d/catmullRomCentrip.js +125 -0
  8. package/original/paths.canvas2d/linear.js +170 -0
  9. package/original/paths.canvas2d/monotoneCubic.js +68 -0
  10. package/original/paths.canvas2d/points.js +66 -0
  11. package/original/paths.canvas2d/spline.js +103 -0
  12. package/original/paths.canvas2d/stepped.js +124 -0
  13. package/original/paths.canvas2d/utils.js +301 -0
  14. package/original/uPlot.canvas2d.js +3548 -0
  15. package/package.json +110 -0
  16. package/paths/bars.js +253 -0
  17. package/paths/catmullRomCentrip.js +126 -0
  18. package/paths/linear.js +171 -0
  19. package/paths/monotoneCubic.js +69 -0
  20. package/paths/points.js +67 -0
  21. package/paths/spline.js +104 -0
  22. package/paths/stepped.js +125 -0
  23. package/paths/utils.js +301 -0
  24. package/scripts/uPlot.css +168 -0
  25. package/scripts/uPlot.d.ts +26 -0
  26. package/scripts/uPlot.js +3687 -0
  27. package/scripts/utils/dom.js +124 -0
  28. package/scripts/utils/domClasses.js +22 -0
  29. package/scripts/utils/feats.js +13 -0
  30. package/scripts/utils/fmtDate.js +398 -0
  31. package/scripts/utils/opts.js +844 -0
  32. package/scripts/utils/strings.js +22 -0
  33. package/scripts/utils/sync.js +27 -0
  34. package/scripts/utils/utils.js +692 -0
  35. package/scripts/webgpu/GPUPath.d.ts +46 -0
  36. package/scripts/webgpu/GPUPath.js +633 -0
  37. package/scripts/webgpu/GPUPath.ts +634 -0
  38. package/scripts/webgpu/WebGPURenderer.d.ts +176 -0
  39. package/scripts/webgpu/WebGPURenderer.js +4256 -0
  40. package/scripts/webgpu/WebGPURenderer.ts +4257 -0
  41. package/scripts/webgpu/browserSmokeHarness.js +105 -0
  42. package/scripts/webgpu/exporters.d.ts +8 -0
  43. package/scripts/webgpu/exporters.js +212 -0
  44. package/scripts/webgpu/shaders.d.ts +2 -0
  45. package/scripts/webgpu/shaders.js +76 -0
  46. package/scripts/webgpu/shaders.ts +77 -0
  47. package/scripts/webgpu/smokeTest.d.ts +2 -0
  48. package/scripts/webgpu/smokeTest.js +144 -0
  49. package/scripts/webgpu/webgpu-ambient.d.ts +41 -0
  50. package/tinybuild.config.js +109 -0
@@ -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.