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,105 @@
1
+ import { GPUPath } from './GPUPath.js';
2
+ import WebGPURenderer from './WebGPURenderer.js';
3
+ import { runWebGPURendererSmokeTests } from './smokeTest.js';
4
+
5
+ export async function runWebGPUBrowserSmokeHarness(options = {}) {
6
+ runWebGPURendererSmokeTests();
7
+
8
+ if (typeof document == 'undefined')
9
+ throw new Error('The WebGPU browser smoke harness requires a DOM.');
10
+
11
+ let width = options.width || 480;
12
+ let height = options.height || 260;
13
+ let canvas = options.canvas || document.createElement('canvas');
14
+ let ratio = globalThis.devicePixelRatio || 1;
15
+ canvas.width = Math.max(1, Math.round(width * ratio));
16
+ canvas.height = Math.max(1, Math.round(height * ratio));
17
+ canvas.style.width = width + 'px';
18
+ canvas.style.height = height + 'px';
19
+
20
+ let host = options.appendTo || document.body;
21
+ let wrap = document.createElement('div');
22
+ wrap.style.position = 'relative';
23
+ wrap.style.width = width + 'px';
24
+ wrap.style.height = height + 'px';
25
+
26
+ if (canvas.parentNode != null) {
27
+ canvas.parentNode.insertBefore(wrap, canvas);
28
+ wrap.appendChild(canvas);
29
+ }
30
+ else {
31
+ host.appendChild(wrap);
32
+ wrap.appendChild(canvas);
33
+ }
34
+
35
+ let ctx = new WebGPURenderer(canvas);
36
+ ctx.mount(wrap, canvas);
37
+ await ctx.initPromise;
38
+
39
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
40
+
41
+ let swatch = ctx.createImageData(18, 18);
42
+ for (let y = 0; y < swatch.height; y++) {
43
+ for (let x = 0; x < swatch.width; x++) {
44
+ let i = (y * swatch.width + x) * 4;
45
+ swatch.data[i + 0] = x * 14;
46
+ swatch.data[i + 1] = y * 14;
47
+ swatch.data[i + 2] = 255;
48
+ swatch.data[i + 3] = 255;
49
+ }
50
+ }
51
+
52
+ ctx.scale(ratio, ratio);
53
+
54
+ let bg = ctx.createLinearGradient(0, 0, width, height);
55
+ bg.addColorStop(0, '#172033');
56
+ bg.addColorStop(1, '#283c5f');
57
+ ctx.fillStyle = bg;
58
+ ctx.fillRect(0, 0, width, height);
59
+ ctx.putImageData(swatch, Math.round((width - 34) * ratio), Math.round(18 * ratio));
60
+
61
+ let circle = new GPUPath();
62
+ circle.arc(90, 95, 54, 0, Math.PI * 2);
63
+ ctx.fillStyle = 'rgba(255,255,255,0.14)';
64
+ ctx.shadowColor = 'rgba(0,0,0,0.35)';
65
+ ctx.shadowBlur = 10;
66
+ ctx.shadowOffsetX = 3;
67
+ ctx.shadowOffsetY = 4;
68
+ ctx.fill(circle);
69
+
70
+ ctx.shadowColor = 'rgba(0,0,0,0)';
71
+ ctx.lineWidth = 5;
72
+ ctx.lineJoin = 'round';
73
+ ctx.lineCap = 'round';
74
+ ctx.setLineDash([14, 8, 3]);
75
+ ctx.strokeStyle = '#d8e8ff';
76
+ let wave = new GPUPath();
77
+ wave.moveTo(165, 140);
78
+ wave.bezierCurveTo(215, 35, 285, 230, 340, 90);
79
+ wave.bezierCurveTo(370, 25, 420, 115, 450, 60);
80
+ ctx.stroke(wave);
81
+
82
+ ctx.setLineDash([]);
83
+ ctx.globalCompositeOperation = 'lighter';
84
+ ctx.fillStyle = 'rgba(130,200,255,0.25)';
85
+ ctx.fillRect(230, 92, 140, 66);
86
+ ctx.globalCompositeOperation = 'source-over';
87
+
88
+ ctx.font = '600 24px system-ui, sans-serif';
89
+ ctx.fillStyle = '#ffffff';
90
+ ctx.fillText('WebGPU uPlot smoke', 24, 220, 310);
91
+ ctx.font = '13px system-ui, sans-serif';
92
+ ctx.letterSpacing = '0.04em';
93
+ ctx.fillStyle = 'rgba(255,255,255,0.72)';
94
+ ctx.fillText('paths, text, clips, dashes, shadows, gradients', 24, 240, 420);
95
+
96
+ ctx.present();
97
+
98
+ let readback = null;
99
+ if (typeof ctx.getImageDataAsync == 'function')
100
+ readback = await ctx.getImageDataAsync(0, 0, Math.min(8, canvas.width), Math.min(8, canvas.height));
101
+
102
+ return {ok: true, canvas, renderer: ctx, wrap, readback};
103
+ }
104
+
105
+ export default runWebGPUBrowserSmokeHarness;
@@ -0,0 +1,8 @@
1
+ export function normalizeExportType(type?: string): string;
2
+ export function rgbaImageDataToBmpBytes(imageData: ImageData | {width: number; height: number; data: Uint8ClampedArray | Uint8Array}): Uint8Array;
3
+ export function rgbaImageDataToPngBytes(imageData: ImageData | {width: number; height: number; data: Uint8ClampedArray | Uint8Array}): Uint8Array;
4
+ export function bytesToBase64(bytes: Uint8Array): string;
5
+ export function bytesToBlob(bytes: Uint8Array, type: string): Blob | {type: string; size: number; arrayBuffer(): Promise<ArrayBuffer>};
6
+ export function textToBlob(text: string, type: string): Blob | {type: string; size: number; arrayBuffer(): Promise<ArrayBuffer>};
7
+ export function escapeXmlAttr(value: unknown): string;
8
+ export function encodeTextBase64(text: string): string;
@@ -0,0 +1,212 @@
1
+ export function rgbaImageDataToBmpBytes(imageData) {
2
+ let width = Math.max(0, Math.floor(Number(imageData?.width) || 0));
3
+ let height = Math.max(0, Math.floor(Number(imageData?.height) || 0));
4
+ let data = imageData?.data || new Uint8ClampedArray(0);
5
+ let rowStride = Math.ceil((width * 4) / 4) * 4;
6
+ let pixelBytes = rowStride * height;
7
+ let bytes = new Uint8Array(14 + 40 + pixelBytes);
8
+ let view = new DataView(bytes.buffer);
9
+
10
+ bytes[0] = 0x42;
11
+ bytes[1] = 0x4d;
12
+ view.setUint32(2, bytes.length, true);
13
+ view.setUint32(10, 54, true);
14
+ view.setUint32(14, 40, true);
15
+ view.setInt32(18, width, true);
16
+ view.setInt32(22, -height, true);
17
+ view.setUint16(26, 1, true);
18
+ view.setUint16(28, 32, true);
19
+ view.setUint32(34, pixelBytes, true);
20
+
21
+ let out = 54;
22
+ for (let y = 0; y < height; y++) {
23
+ let src = y * width * 4;
24
+ for (let x = 0; x < width; x++) {
25
+ let si = src + x * 4;
26
+ let di = out + x * 4;
27
+ bytes[di + 0] = data[si + 2] || 0;
28
+ bytes[di + 1] = data[si + 1] || 0;
29
+ bytes[di + 2] = data[si + 0] || 0;
30
+ bytes[di + 3] = data[si + 3] == null ? 255 : data[si + 3];
31
+ }
32
+ out += rowStride;
33
+ }
34
+
35
+ return bytes;
36
+ }
37
+
38
+
39
+ function makeCrc32Table() {
40
+ let table = new Uint32Array(256);
41
+ for (let i = 0; i < 256; i++) {
42
+ let c = i;
43
+ for (let j = 0; j < 8; j++)
44
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
45
+ table[i] = c >>> 0;
46
+ }
47
+ return table;
48
+ }
49
+
50
+ const CRC32_TABLE = makeCrc32Table();
51
+
52
+ function crc32(bytes) {
53
+ let c = 0xffffffff;
54
+ for (let i = 0; i < bytes.length; i++)
55
+ c = CRC32_TABLE[(c ^ bytes[i]) & 255] ^ (c >>> 8);
56
+ return (c ^ 0xffffffff) >>> 0;
57
+ }
58
+
59
+ function adler32(bytes) {
60
+ let a = 1;
61
+ let b = 0;
62
+ for (let i = 0; i < bytes.length; i++) {
63
+ a = (a + bytes[i]) % 65521;
64
+ b = (b + a) % 65521;
65
+ }
66
+ return ((b << 16) | a) >>> 0;
67
+ }
68
+
69
+ function writeU32(bytes, offset, value) {
70
+ bytes[offset + 0] = (value >>> 24) & 255;
71
+ bytes[offset + 1] = (value >>> 16) & 255;
72
+ bytes[offset + 2] = (value >>> 8) & 255;
73
+ bytes[offset + 3] = value & 255;
74
+ }
75
+
76
+ function asciiBytes(text) {
77
+ let out = new Uint8Array(text.length);
78
+ for (let i = 0; i < text.length; i++)
79
+ out[i] = text.charCodeAt(i) & 255;
80
+ return out;
81
+ }
82
+
83
+ function pngChunk(type, data) {
84
+ let name = asciiBytes(type);
85
+ let out = new Uint8Array(12 + data.length);
86
+ writeU32(out, 0, data.length);
87
+ out.set(name, 4);
88
+ out.set(data, 8);
89
+ let crcInput = new Uint8Array(name.length + data.length);
90
+ crcInput.set(name, 0);
91
+ crcInput.set(data, name.length);
92
+ writeU32(out, out.length - 4, crc32(crcInput));
93
+ return out;
94
+ }
95
+
96
+ function zlibStore(bytes) {
97
+ let blocks = [];
98
+ let total = 2 + 4;
99
+ for (let pos = 0; pos < bytes.length; pos += 65535) {
100
+ let len = Math.min(65535, bytes.length - pos);
101
+ let block = new Uint8Array(5 + len);
102
+ block[0] = pos + len >= bytes.length ? 1 : 0;
103
+ block[1] = len & 255;
104
+ block[2] = (len >>> 8) & 255;
105
+ let nlen = (~len) & 65535;
106
+ block[3] = nlen & 255;
107
+ block[4] = (nlen >>> 8) & 255;
108
+ block.set(bytes.subarray(pos, pos + len), 5);
109
+ blocks.push(block);
110
+ total += block.length;
111
+ }
112
+
113
+ let out = new Uint8Array(total);
114
+ out[0] = 0x78;
115
+ out[1] = 0x01;
116
+ let o = 2;
117
+ for (let block of blocks) {
118
+ out.set(block, o);
119
+ o += block.length;
120
+ }
121
+ writeU32(out, o, adler32(bytes));
122
+ return out;
123
+ }
124
+
125
+ export function rgbaImageDataToPngBytes(imageData) {
126
+ let width = Math.max(0, Math.floor(Number(imageData?.width) || 0));
127
+ let height = Math.max(0, Math.floor(Number(imageData?.height) || 0));
128
+ let data = imageData?.data || new Uint8ClampedArray(0);
129
+ let raw = new Uint8Array((width * 4 + 1) * height);
130
+ let dst = 0;
131
+ let src = 0;
132
+ for (let y = 0; y < height; y++) {
133
+ raw[dst++] = 0;
134
+ for (let x = 0; x < width; x++) {
135
+ raw[dst++] = data[src++] || 0;
136
+ raw[dst++] = data[src++] || 0;
137
+ raw[dst++] = data[src++] || 0;
138
+ let a = data[src++];
139
+ raw[dst++] = a == null ? 255 : a;
140
+ }
141
+ }
142
+
143
+ let ihdr = new Uint8Array(13);
144
+ writeU32(ihdr, 0, width);
145
+ writeU32(ihdr, 4, height);
146
+ ihdr[8] = 8;
147
+ ihdr[9] = 6;
148
+ ihdr[10] = 0;
149
+ ihdr[11] = 0;
150
+ ihdr[12] = 0;
151
+
152
+ let signature = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
153
+ let chunks = [signature, pngChunk('IHDR', ihdr), pngChunk('IDAT', zlibStore(raw)), pngChunk('IEND', new Uint8Array(0))];
154
+ let total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
155
+ let out = new Uint8Array(total);
156
+ let offset = 0;
157
+ for (let chunk of chunks) {
158
+ out.set(chunk, offset);
159
+ offset += chunk.length;
160
+ }
161
+ return out;
162
+ }
163
+
164
+ export function normalizeExportType(type) {
165
+ type = String(type || 'image/png').toLowerCase();
166
+ return type == 'image/bmp' || type == 'image/x-ms-bmp' ? 'image/bmp' : 'image/png';
167
+ }
168
+
169
+ export function bytesToBase64(bytes) {
170
+ if (typeof Buffer != 'undefined')
171
+ return Buffer.from(bytes).toString('base64');
172
+
173
+ let s = '';
174
+ let chunk = 0x8000;
175
+ for (let i = 0; i < bytes.length; i += chunk)
176
+ s += String.fromCharCode(...bytes.subarray(i, i + chunk));
177
+ return btoa(s);
178
+ }
179
+
180
+ export function bytesToBlob(bytes, type) {
181
+ if (typeof Blob != 'undefined')
182
+ return new Blob([bytes], {type});
183
+
184
+ return {
185
+ type,
186
+ size: bytes.byteLength,
187
+ async arrayBuffer() {
188
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
189
+ },
190
+ };
191
+ }
192
+
193
+ export function textToBlob(text, type) {
194
+ if (typeof Blob != 'undefined')
195
+ return new Blob([text], {type});
196
+
197
+ let bytes = new TextEncoder().encode(text);
198
+ return bytesToBlob(bytes, type);
199
+ }
200
+
201
+ export function escapeXmlAttr(value) {
202
+ return String(value).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
203
+ }
204
+
205
+ export function encodeTextBase64(text) {
206
+ if (typeof Buffer != 'undefined')
207
+ return Buffer.from(text, 'utf8').toString('base64');
208
+
209
+ let bytes = new TextEncoder().encode(text);
210
+ return bytesToBase64(bytes);
211
+ }
212
+
@@ -0,0 +1,2 @@
1
+ export const CHART_WGSL: string;
2
+ export const IMAGE_WGSL: string;
@@ -0,0 +1,76 @@
1
+ export const CHART_WGSL = `
2
+ struct Uniforms {
3
+ resolution: vec2<f32>,
4
+ };
5
+
6
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
7
+
8
+ struct VertexIn {
9
+ @location(0) pos: vec2<f32>,
10
+ @location(1) color: vec4<f32>,
11
+ };
12
+
13
+ struct VertexOut {
14
+ @builtin(position) position: vec4<f32>,
15
+ @location(0) color: vec4<f32>,
16
+ };
17
+
18
+ @vertex
19
+ fn vsMain(input: VertexIn) -> VertexOut {
20
+ var out: VertexOut;
21
+ let clip = vec2<f32>(
22
+ (input.pos.x / uniforms.resolution.x) * 2.0 - 1.0,
23
+ 1.0 - (input.pos.y / uniforms.resolution.y) * 2.0,
24
+ );
25
+ out.position = vec4<f32>(clip, 0.0, 1.0);
26
+ out.color = input.color;
27
+ return out;
28
+ }
29
+
30
+ @fragment
31
+ fn fsMain(input: VertexOut) -> @location(0) vec4<f32> {
32
+ return input.color;
33
+ }
34
+ `;
35
+
36
+ export const IMAGE_WGSL = `
37
+ struct Uniforms {
38
+ resolution: vec2<f32>,
39
+ };
40
+
41
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
42
+ @group(1) @binding(0) var imageSampler: sampler;
43
+ @group(1) @binding(1) var imageTexture: texture_2d<f32>;
44
+
45
+ struct VertexIn {
46
+ @location(0) pos: vec2<f32>,
47
+ @location(1) uv: vec2<f32>,
48
+ @location(2) alpha: f32,
49
+ };
50
+
51
+ struct VertexOut {
52
+ @builtin(position) position: vec4<f32>,
53
+ @location(0) uv: vec2<f32>,
54
+ @location(1) alpha: f32,
55
+ };
56
+
57
+ @vertex
58
+ fn vsImage(input: VertexIn) -> VertexOut {
59
+ var out: VertexOut;
60
+ let clip = vec2<f32>(
61
+ (input.pos.x / uniforms.resolution.x) * 2.0 - 1.0,
62
+ 1.0 - (input.pos.y / uniforms.resolution.y) * 2.0,
63
+ );
64
+ out.position = vec4<f32>(clip, 0.0, 1.0);
65
+ out.uv = input.uv;
66
+ out.alpha = input.alpha;
67
+ return out;
68
+ }
69
+
70
+ @fragment
71
+ fn fsImage(input: VertexOut) -> @location(0) vec4<f32> {
72
+ let tex = textureSample(imageTexture, imageSampler, input.uv);
73
+ let a = tex.a * input.alpha;
74
+ return vec4<f32>(tex.rgb * a, a);
75
+ }
76
+ `;
@@ -0,0 +1,77 @@
1
+ // @ts-nocheck
2
+ export const CHART_WGSL = `
3
+ struct Uniforms {
4
+ resolution: vec2<f32>,
5
+ };
6
+
7
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
8
+
9
+ struct VertexIn {
10
+ @location(0) pos: vec2<f32>,
11
+ @location(1) color: vec4<f32>,
12
+ };
13
+
14
+ struct VertexOut {
15
+ @builtin(position) position: vec4<f32>,
16
+ @location(0) color: vec4<f32>,
17
+ };
18
+
19
+ @vertex
20
+ fn vsMain(input: VertexIn) -> VertexOut {
21
+ var out: VertexOut;
22
+ let clip = vec2<f32>(
23
+ (input.pos.x / uniforms.resolution.x) * 2.0 - 1.0,
24
+ 1.0 - (input.pos.y / uniforms.resolution.y) * 2.0,
25
+ );
26
+ out.position = vec4<f32>(clip, 0.0, 1.0);
27
+ out.color = input.color;
28
+ return out;
29
+ }
30
+
31
+ @fragment
32
+ fn fsMain(input: VertexOut) -> @location(0) vec4<f32> {
33
+ return input.color;
34
+ }
35
+ `;
36
+
37
+ export const IMAGE_WGSL = `
38
+ struct Uniforms {
39
+ resolution: vec2<f32>,
40
+ };
41
+
42
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
43
+ @group(1) @binding(0) var imageSampler: sampler;
44
+ @group(1) @binding(1) var imageTexture: texture_2d<f32>;
45
+
46
+ struct VertexIn {
47
+ @location(0) pos: vec2<f32>,
48
+ @location(1) uv: vec2<f32>,
49
+ @location(2) alpha: f32,
50
+ };
51
+
52
+ struct VertexOut {
53
+ @builtin(position) position: vec4<f32>,
54
+ @location(0) uv: vec2<f32>,
55
+ @location(1) alpha: f32,
56
+ };
57
+
58
+ @vertex
59
+ fn vsImage(input: VertexIn) -> VertexOut {
60
+ var out: VertexOut;
61
+ let clip = vec2<f32>(
62
+ (input.pos.x / uniforms.resolution.x) * 2.0 - 1.0,
63
+ 1.0 - (input.pos.y / uniforms.resolution.y) * 2.0,
64
+ );
65
+ out.position = vec4<f32>(clip, 0.0, 1.0);
66
+ out.uv = input.uv;
67
+ out.alpha = input.alpha;
68
+ return out;
69
+ }
70
+
71
+ @fragment
72
+ fn fsImage(input: VertexOut) -> @location(0) vec4<f32> {
73
+ let tex = textureSample(imageTexture, imageSampler, input.uv);
74
+ let a = tex.a * input.alpha;
75
+ return vec4<f32>(tex.rgb * a, a);
76
+ }
77
+ `;
@@ -0,0 +1,2 @@
1
+ export function runWebGPURendererSmokeTests(): boolean;
2
+ export default runWebGPURendererSmokeTests;
@@ -0,0 +1,144 @@
1
+ import { GPUPath } from './GPUPath.js';
2
+ import { WebGPURendererInternals } from './WebGPURenderer.js';
3
+ import { normalizeExportType, rgbaImageDataToBmpBytes, rgbaImageDataToPngBytes } from './exporters.js';
4
+
5
+ function assert(name, value) {
6
+ if (!value)
7
+ throw new Error(`WebGPU renderer smoke test failed: ${name}`);
8
+ }
9
+
10
+ function approx(a, b, eps = 1e-6) {
11
+ return Math.abs(a - b) <= eps;
12
+ }
13
+
14
+ export function runWebGPURendererSmokeTests() {
15
+ let outer = [[0, 0], [10, 0], [10, 10], [0, 10]];
16
+ let inner = [[3, 3], [3, 7], [7, 7], [7, 3]];
17
+
18
+ assert('nonzero outer hit', WebGPURendererInternals.fillContainsPoint([1, 1], [outer, inner], 'nonzero'));
19
+ assert('nonzero opposite winding hole', !WebGPURendererInternals.fillContainsPoint([5, 5], [outer, inner], 'nonzero'));
20
+ assert('evenodd hole', !WebGPURendererInternals.fillContainsPoint([5, 5], [outer, inner], 'evenodd'));
21
+ assert('source-over composite', WebGPURendererInternals.clampCompositeMode('source-over') == 'source-over');
22
+ assert('unknown composite fallback', WebGPURendererInternals.clampCompositeMode('not-real') == 'source-over');
23
+
24
+ let clipped = WebGPURendererInternals.normalizeImageRect(
25
+ {width: 10, height: 10},
26
+ -5, -5, 10, 10,
27
+ 0, 0, 100, 100,
28
+ );
29
+ assert('image source clip exists', clipped != null);
30
+ assert('image source clipped x', clipped.sx == 0 && clipped.sy == 0);
31
+ assert('image destination scaled', clipped.dx == 50 && clipped.dy == 50 && clipped.dw == 50 && clipped.dh == 50);
32
+
33
+ let neg = WebGPURendererInternals.normalizeImageRect(
34
+ {width: 10, height: 10},
35
+ 8, 8, -4, -4,
36
+ 20, 30, -40, -50,
37
+ );
38
+ assert('negative image rect normalized', neg != null && neg.sx == 4 && neg.sy == 4 && neg.dx == -20 && neg.dy == -20 && neg.dw == 40 && neg.dh == 50);
39
+
40
+ let emptyClose = new GPUPath();
41
+ emptyClose.closePath();
42
+ assert('empty closePath does not create geometry', emptyClose.toSubpaths().length == 0);
43
+
44
+ let path = new GPUPath();
45
+ path.rect(0, 0, 10, 10);
46
+ path.closePath();
47
+ assert('GPUPath emits subpaths', path.toSubpaths().length > 0);
48
+
49
+ let circle = new GPUPath();
50
+ circle.arc(0, 0, 10, 0, Math.PI * 2);
51
+ let circleSub = circle.toSubpaths()[0];
52
+ assert('full circle arc keeps geometry', circleSub.length > 10);
53
+ assert('full circle arc returns near start', approx(circleSub[0][0], circleSub[circleSub.length - 1][0]) && approx(circleSub[0][1], circleSub[circleSub.length - 1][1]));
54
+
55
+ let ellipse = new GPUPath();
56
+ ellipse.ellipse(0, 0, 10, 5, Math.PI / 6, 0, Math.PI * 2);
57
+ assert('full ellipse keeps geometry', ellipse.toSubpaths()[0].length > 10);
58
+
59
+ let rounded = new GPUPath();
60
+ rounded.roundRect(0, 0, 20, 12, [4, 2, 5, 3]);
61
+ let roundedSub = rounded.toSubpaths()[0];
62
+ assert('roundRect emits closed geometry', roundedSub.length > 8 && approx(roundedSub[0][0], roundedSub[roundedSub.length - 1][0]) && approx(roundedSub[0][1], roundedSub[roundedSub.length - 1][1]));
63
+
64
+ let arcToPath = new GPUPath();
65
+ arcToPath.moveTo(0, 10);
66
+ arcToPath.arcTo(10, 0, 20, 10, 6);
67
+ let arcToSub = arcToPath.toSubpaths()[0];
68
+ let arcToEnd = arcToSub[arcToSub.length - 1];
69
+ assert('arcTo emits rounded connector geometry', arcToSub.length > 3 && arcToEnd[0] > 10 && arcToEnd[0] < 20 && arcToEnd[1] > 0 && arcToEnd[1] < 10);
70
+
71
+ let copy = new GPUPath(path);
72
+ copy.lineTo(12, 12);
73
+ let copiedSubpaths = copy.toSubpaths();
74
+ assert('copy constructor preserves current point', copiedSubpaths[copiedSubpaths.length - 1].some(p => p[0] == 12 && p[1] == 12));
75
+
76
+ let add = new GPUPath();
77
+ add.addPath(path);
78
+ add.lineTo(14, 14);
79
+ let addedSubpaths = add.toSubpaths();
80
+ assert('addPath preserves current point', addedSubpaths[addedSubpaths.length - 1].some(p => p[0] == 14 && p[1] == 14));
81
+
82
+ let invalid = new GPUPath();
83
+ invalid.moveTo(0, 0);
84
+ invalid.lineTo(NaN, 3);
85
+ assert('invalid path coordinates are ignored', invalid.toSubpaths()[0].length == 1);
86
+
87
+
88
+ let modernRgb = WebGPURendererInternals.parsePlainCssColor('rgb(255 0 0 / 50%)');
89
+ assert('modern rgb space slash alpha parses', approx(modernRgb[0], 1) && approx(modernRgb[1], 0) && approx(modernRgb[2], 0) && approx(modernRgb[3], 0.5));
90
+
91
+ let multiRect = new GPUPath();
92
+ multiRect.rect(0, 0, 10, 10);
93
+ multiRect.rect(3, 3, 4, 4);
94
+ let evenOddRegions = WebGPURendererInternals.pathToClipRegions(multiRect, [1, 0, 0, 1, 0, 0], 'evenodd');
95
+ let centerCovered = evenOddRegions.some(region => WebGPURendererInternals.pointInPolygon([5, 5], region));
96
+ let cornerCovered = evenOddRegions.some(region => WebGPURendererInternals.pointInPolygon([1, 1], region));
97
+ assert('evenodd clip keeps outer region', cornerCovered);
98
+ assert('evenodd clip removes inner rect hole', !centerCovered);
99
+
100
+
101
+ let nonzeroRects = new GPUPath();
102
+ nonzeroRects.rect(0, 0, 10, 10);
103
+ nonzeroRects.rect(7, 3, -4, 4);
104
+ let nonzeroRegions = WebGPURendererInternals.pathToClipRegions(nonzeroRects, [1, 0, 0, 1, 0, 0], 'nonzero');
105
+ let nonzeroCenterCovered = nonzeroRegions.some(region => WebGPURendererInternals.pointInPolygon([5, 5], region));
106
+ let nonzeroCornerCovered = nonzeroRegions.some(region => WebGPURendererInternals.pointInPolygon([1, 1], region));
107
+ assert('nonzero rect clip keeps outer region', nonzeroCornerCovered);
108
+ assert('nonzero rect clip removes opposite-winding hole', !nonzeroCenterCovered);
109
+
110
+ let clippedTri = WebGPURendererInternals.clipVertices([
111
+ -10, -10, 1, 0, 0, 1,
112
+ 10, -10, 1, 0, 0, 1,
113
+ 0, 10, 1, 0, 0, 1,
114
+ ], [[[0, 0], [1, 0], [1, 1], [0, 1]]]);
115
+ assert('clipped quadrilateral fans to two triangles', clippedTri.length == 36);
116
+
117
+ assert('readback row alignment', WebGPURendererInternals.alignBytesPerRow(4) == 256 && WebGPURendererInternals.alignBytesPerRow(260) == 512);
118
+ let readRect = WebGPURendererInternals.normalizeReadRect(8, 8, -4, -5, 20, 20);
119
+ assert('negative readback rect normalized', readRect.x == 4 && readRect.y == 3 && readRect.w == 4 && readRect.h == 5);
120
+ let clippedRead = WebGPURendererInternals.normalizeReadRect(-3, -2, 8, 7, 10, 10);
121
+ assert('readback rect clips to frame', clippedRead.x == 0 && clippedRead.y == 0 && clippedRead.w == 5 && clippedRead.h == 5);
122
+
123
+ let commaRgb = WebGPURendererInternals.parsePlainCssColor('rgba(0, 128, 255, 0.25)');
124
+ assert('comma rgba still parses', approx(commaRgb[1], 128 / 255) && approx(commaRgb[3], 0.25));
125
+
126
+ let dirty = WebGPURendererInternals.normalizeDirtyRect({width: 10, height: 8}, 8, 7, -5, -4);
127
+ assert('negative putImageData dirty rect normalized', dirty.x == 3 && dirty.y == 3 && dirty.w == 5 && dirty.h == 4);
128
+ let dirtyClip = WebGPURendererInternals.normalizeDirtyRect({width: 10, height: 8}, -2, -1, 5, 5);
129
+ assert('putImageData dirty rect clips to image', dirtyClip.x == 0 && dirtyClip.y == 0 && dirtyClip.w == 3 && dirtyClip.h == 4);
130
+
131
+ let bmpBytes = rgbaImageDataToBmpBytes({width: 1, height: 1, data: new Uint8ClampedArray([255, 0, 128, 64])});
132
+ assert('bmp encoder emits BMP signature', bmpBytes[0] == 0x42 && bmpBytes[1] == 0x4d);
133
+ assert('bmp encoder emits BGRA pixel', bmpBytes[54] == 128 && bmpBytes[55] == 0 && bmpBytes[56] == 255 && bmpBytes[57] == 64);
134
+
135
+ let pngBytes = rgbaImageDataToPngBytes({width: 1, height: 1, data: new Uint8ClampedArray([255, 0, 128, 64])});
136
+ assert('png encoder emits PNG signature', pngBytes[0] == 137 && pngBytes[1] == 80 && pngBytes[2] == 78 && pngBytes[3] == 71);
137
+ assert('png encoder has IHDR chunk', String.fromCharCode(pngBytes[12], pngBytes[13], pngBytes[14], pngBytes[15]) == 'IHDR');
138
+ assert('export type defaults to png', normalizeExportType('image/jpeg') == 'image/png');
139
+ assert('export type preserves bmp', normalizeExportType('image/bmp') == 'image/bmp');
140
+
141
+ return true;
142
+ }
143
+
144
+ export default runWebGPURendererSmokeTests;
@@ -0,0 +1,41 @@
1
+ // Minimal WebGPU ambient declarations for TypeScript installs whose DOM lib does not include WebGPU yet.
2
+ type GPUDevice = any;
3
+ type GPUAdapter = any;
4
+ type GPUCanvasContext = any;
5
+ type GPUTextureFormat = string;
6
+ type GPUBuffer = any;
7
+ type GPURenderPipeline = any;
8
+ type GPUComputePipeline = any;
9
+ type GPUBindGroup = any;
10
+ type GPUBindGroupLayout = any;
11
+ type GPUPipelineLayout = any;
12
+ type GPUSampler = any;
13
+ type GPUTexture = any;
14
+ type GPUTextureView = any;
15
+ type GPUQueue = any;
16
+ type GPUCommandEncoder = any;
17
+ type GPURenderPassEncoder = any;
18
+ type GPUShaderModule = any;
19
+ type GPUBufferUsageFlags = number;
20
+ type GPUTextureUsageFlags = number;
21
+ type GPUShaderStageFlags = number;
22
+ type GPUMapModeFlags = number;
23
+ type GPUPowerPreference = 'low-power' | 'high-performance';
24
+ type GPUColor = any;
25
+ type GPUColorDict = any;
26
+ type GPUBlendState = any;
27
+ type GPUPrimitiveState = any;
28
+ type GPUVertexBufferLayout = any;
29
+ type GPUCanvasConfiguration = any;
30
+ type GPURequestAdapterOptions = any;
31
+ type GPUDeviceDescriptor = any;
32
+ type GPUAdapterInfo = Record<string, unknown>;
33
+
34
+ declare const GPUBufferUsage: any;
35
+ declare const GPUTextureUsage: any;
36
+ declare const GPUShaderStage: any;
37
+ declare const GPUMapMode: any;
38
+
39
+ interface Navigator {
40
+ gpu?: any;
41
+ }