three-slicer 0.3.2 → 0.3.4
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/CHANGELOG.md +134 -0
- package/README.md +6 -0
- package/data/config-schema-lean.json +1 -1
- package/data/config-schema.json +6 -34
- package/data/filaments.js +1 -1
- package/data/printers.json +1 -1
- package/data/processes.js +1 -1
- package/engine/PARAMS.md +86 -8
- package/engine/src/contour_front_gpu.js +291 -0
- package/engine/src/contour_front_shaders.js +449 -0
- package/engine/src/contour_gpu.js +367 -0
- package/engine/src/contour_gpu_shaders.js +450 -0
- package/engine/src/contour_slice.js +131 -0
- package/engine/src/settings.js +25 -13
- package/engine/src/slicer.worker.js +50 -4
- package/engine/src/slicer_core.js +0 -0
- package/engine/src/slicer_core.mt.js +0 -0
- package/engine/src/slicer_core.mt.wasm +0 -0
- package/package.json +2 -2
- package/types/data/filaments.js.d.ts +6 -1
- package/types/data/printers.json.d.ts +13 -3
- package/types/data/processes.js.d.ts +6 -1
- package/types/settings.d.ts +16 -1
- package/types/worker.d.ts +4 -1
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
// The WGSL of the contour union pipeline (contour_gpu.js). This is the shader text of the measured experiment
|
|
2
|
+
// (.omc/plans/gpu-parallel-topology-design.md, appendices D to F17), kept as it ran: every kernel here was checked
|
|
3
|
+
// against Clipper on real slices, so it is edited only with a measurement beside it. The host that binds and
|
|
4
|
+
// dispatches them, and the meaning of every buffer, is contour_gpu.js.
|
|
5
|
+
//
|
|
6
|
+
// Conventions shared by every kernel: a dispatch is two-dimensional (x up to 65535 workgroups of 256, y the overflow),
|
|
7
|
+
// so an invocation's index is g.x + g.y * 16776960; a segment's index is also the id of the vertex it starts at; a
|
|
8
|
+
// crossing's vertex id is segmentCount + its pair index.
|
|
9
|
+
|
|
10
|
+
const PRELUDE = /* wgsl */`// a range from a corrupt scan must not spin the GPU: a dispatch keeps running after its process is killed (measured). Observed max 107 per cell, 13 per segment.
|
|
11
|
+
const RANGE_CAP = 4096u;
|
|
12
|
+
fn badRange(lo: u32, hi: u32) -> bool { return hi < lo || hi - lo > RANGE_CAP; }
|
|
13
|
+
|
|
14
|
+
fn mulu(a: u32, b: u32) -> vec2<u32> { // unsigned 32x32 -> 64 (lo, hi)
|
|
15
|
+
let al = a & 0xffffu; let ah = a >> 16u; let bl = b & 0xffffu; let bh = b >> 16u;
|
|
16
|
+
let ll = al * bl; let lh = al * bh; let hl = ah * bl; let hh = ah * bh;
|
|
17
|
+
var lo = ll; var hi = hh + (lh >> 16u) + (hl >> 16u);
|
|
18
|
+
let t1 = lh << 16u; lo = lo + t1; if (lo < t1) { hi = hi + 1u; }
|
|
19
|
+
let t2 = hl << 16u; lo = lo + t2; if (lo < t2) { hi = hi + 1u; }
|
|
20
|
+
return vec2<u32>(lo, hi);
|
|
21
|
+
}
|
|
22
|
+
fn muls(a: i32, b: i32) -> vec2<u32> { // signed 32x32 -> 64, two's complement
|
|
23
|
+
let neg = (a < 0) != (b < 0);
|
|
24
|
+
var r = mulu(u32(abs(a)), u32(abs(b)));
|
|
25
|
+
if (neg) { r = neg64(r); }
|
|
26
|
+
return r;
|
|
27
|
+
}
|
|
28
|
+
fn neg64(a: vec2<u32>) -> vec2<u32> { var lo = ~a.x; var hi = ~a.y; lo = lo + 1u; if (lo == 0u) { hi = hi + 1u; } return vec2<u32>(lo, hi); }
|
|
29
|
+
fn sub64(a: vec2<u32>, b: vec2<u32>) -> vec2<u32> { var hi = a.y - b.y; if (a.x < b.x) { hi = hi - 1u; } return vec2<u32>(a.x - b.x, hi); }
|
|
30
|
+
fn sign64(a: vec2<u32>) -> i32 { if ((a.y & 0x80000000u) != 0u) { return -1; } if (a.x == 0u && a.y == 0u) { return 0; } return 1; }
|
|
31
|
+
fn cross(ux: i32, uy: i32, vx: i32, vy: i32) -> vec2<u32> { return sub64(muls(ux, vy), muls(uy, vx)); }
|
|
32
|
+
fn orient(ax: i32, ay: i32, bx: i32, by: i32, cx: i32, cy: i32) -> i32 { return sign64(cross(bx - ax, by - ay, cx - ax, cy - ay)); }
|
|
33
|
+
fn u64f(a: vec2<u32>) -> f32 { return f32(a.y) * 4294967296.0 + f32(a.x); } // non-negative values only
|
|
34
|
+
// exact rounded quotient round(num * |d| / den) for 0 <= num < den (u64), |d| < 2^31: restoring division in 128-bit arithmetic
|
|
35
|
+
fn add128u(a: vec4<u32>, b: vec4<u32>) -> vec4<u32> { var r: vec4<u32>; r.x = a.x + b.x; var c = select(0u, 1u, r.x < a.x);
|
|
36
|
+
let y = a.y + b.y; var c1 = select(0u, 1u, y < a.y); r.y = y + c; if (r.y < y) { c1 = 1u; } c = c1;
|
|
37
|
+
let z = a.z + b.z; var c2 = select(0u, 1u, z < a.z); r.z = z + c; if (r.z < z) { c2 = 1u; } c = c2; r.w = a.w + b.w + c; return r; }
|
|
38
|
+
fn sub128u(a: vec4<u32>, b: vec4<u32>) -> vec4<u32> { return add128u(a, add128u(vec4<u32>(~b.x, ~b.y, ~b.z, ~b.w), vec4<u32>(1u, 0u, 0u, 0u))); }
|
|
39
|
+
fn ge128(a: vec4<u32>, b: vec4<u32>) -> bool { if (a.w != b.w) { return a.w > b.w; } if (a.z != b.z) { return a.z > b.z; } if (a.y != b.y) { return a.y > b.y; } return a.x >= b.x; }
|
|
40
|
+
fn shl128(a: vec4<u32>, s: u32) -> vec4<u32> { if (s == 0u) { return a; } let t = 32u - s; return vec4<u32>(a.x << s, (a.y << s) | (a.x >> t), (a.z << s) | (a.y >> t), (a.w << s) | (a.z >> t)); }
|
|
41
|
+
fn roundedShift(num: vec2<u32>, den: vec2<u32>, d: i32) -> i32 {
|
|
42
|
+
if (d == 0) { return 0; }
|
|
43
|
+
let ad = u32(abs(d)); let lo = mulu(num.x, ad); let hi = mulu(num.y, ad);
|
|
44
|
+
var n = vec4<u32>(lo.x, lo.y, 0u, 0u); n = add128u(n, vec4<u32>(0u, hi.x, hi.y, 0u));
|
|
45
|
+
n = shl128(n, 1u); n = add128u(n, vec4<u32>(den.x, den.y, 0u, 0u));
|
|
46
|
+
let dd = shl128(vec4<u32>(den.x, den.y, 0u, 0u), 1u);
|
|
47
|
+
var q = 0u; var sh = shl128(dd, 31u);
|
|
48
|
+
for (var b = 31; b >= 0; b--) { if (ge128(n, sh)) { n = sub128u(n, sh); q = q | (1u << u32(b)); }
|
|
49
|
+
sh = vec4<u32>((sh.x >> 1u) | (sh.y << 31u), (sh.y >> 1u) | (sh.z << 31u), (sh.z >> 1u) | (sh.w << 31u), sh.w >> 1u); }
|
|
50
|
+
if (d < 0) { return -i32(q); } return i32(q);
|
|
51
|
+
}
|
|
52
|
+
struct Params { n: u32, cells: u32, cs: i32, mode: u32 }
|
|
53
|
+
@group(0) @binding(0) var<uniform> params: Params;
|
|
54
|
+
@group(0) @binding(1) var<storage, read> segs: array<vec4<i32>>;
|
|
55
|
+
@group(0) @binding(2) var<storage, read> segInfo: array<vec4<u32>>;
|
|
56
|
+
@group(0) @binding(3) var<storage, read> layerInfo: array<vec4<i32>>; // gw, gh, cellBase, 0
|
|
57
|
+
fn vtx(v: u32) -> vec2<i32> { return segs[v].xy; }
|
|
58
|
+
fn orientSoS(p: u32, q: u32, r: u32) -> i32 { // Simulation of Simplicity: higher id dominates, x displacement before y
|
|
59
|
+
let P = vtx(p); let Q = vtx(q); let R = vtx(r);
|
|
60
|
+
let d = orient(P.x, P.y, Q.x, Q.y, R.x, R.y); if (d != 0) { return d; }
|
|
61
|
+
var ids = array<u32, 3>(p, q, r);
|
|
62
|
+
var cv = array<vec2<i32>, 3>(R - Q, P - R, Q - P);
|
|
63
|
+
for (var round = 0; round < 3; round++) {
|
|
64
|
+
var top = 0u; var found = false;
|
|
65
|
+
for (var k = 0; k < 3; k++) { if (ids[k] != 0xffffffffu && (!found || ids[k] > top)) { top = ids[k]; found = true; } }
|
|
66
|
+
if (!found) { break; }
|
|
67
|
+
var c = vec2<i32>(0, 0);
|
|
68
|
+
for (var k = 0; k < 3; k++) { if (ids[k] == top) { c = c + cv[k]; ids[k] = 0xffffffffu; } }
|
|
69
|
+
if (c.y != 0) { return -sign(c.y); }
|
|
70
|
+
if (c.x != 0) { return sign(c.x); }
|
|
71
|
+
}
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
fn cellRange(i: u32) -> vec4<i32> { let s = segs[i]; return vec4<i32>(min(s.x, s.z) / params.cs, min(s.y, s.w) / params.cs, max(s.x, s.z) / params.cs, max(s.y, s.w) / params.cs); }
|
|
75
|
+
`
|
|
76
|
+
|
|
77
|
+
export const BIN_SHADER = PRELUDE + /* wgsl */`
|
|
78
|
+
@group(0) @binding(4) var<storage, read_write> cellCount: array<atomic<u32>>;
|
|
79
|
+
@group(0) @binding(5) var<storage, read> cellStart: array<u32>;
|
|
80
|
+
@group(0) @binding(6) var<storage, read_write> cellList: array<vec2<u32>>;
|
|
81
|
+
@compute @workgroup_size(256) fn count(@builtin(global_invocation_id) gid_raw: vec3<u32>) { let gid = vec3<u32>(gid_raw.x + gid_raw.y * 16776960u, 0u, 0u);
|
|
82
|
+
let i = gid.x; if (i >= params.n) { return; }
|
|
83
|
+
let r = cellRange(i); let L = layerInfo[segInfo[i].x];
|
|
84
|
+
for (var cy = r.y; cy <= r.w; cy++) { for (var cx = r.x; cx <= r.z; cx++) { atomicAdd(&cellCount[u32(L.z + cy * L.x + cx)], 1u); } }
|
|
85
|
+
}
|
|
86
|
+
@compute @workgroup_size(256) fn scatter(@builtin(global_invocation_id) gid_raw: vec3<u32>) { let gid = vec3<u32>(gid_raw.x + gid_raw.y * 16776960u, 0u, 0u);
|
|
87
|
+
let i = gid.x; if (i >= params.n) { return; }
|
|
88
|
+
let r = cellRange(i); let L = layerInfo[segInfo[i].x];
|
|
89
|
+
for (var cy = r.y; cy <= r.w; cy++) { for (var cx = r.x; cx <= r.z; cx++) { let c = u32(L.z + cy * L.x + cx); let k = atomicAdd(&cellCount[c], 1u); if (cellStart[c] + k < arrayLength(&cellList)) { cellList[cellStart[c] + k] = vec2<u32>(i, c); } } }
|
|
90
|
+
}`
|
|
91
|
+
|
|
92
|
+
export const INTERSECT_SHADER = PRELUDE + /* wgsl */`
|
|
93
|
+
@group(0) @binding(4) var<storage, read> cellStart: array<u32>;
|
|
94
|
+
@group(0) @binding(5) var<storage, read> cellList: array<vec2<u32>>;
|
|
95
|
+
@group(0) @binding(6) var<storage, read_write> evCount: array<atomic<u32>>;
|
|
96
|
+
@group(0) @binding(7) var<storage, read_write> events: array<vec4<u32>>;
|
|
97
|
+
@group(0) @binding(8) var<storage, read> evStart: array<u32>;
|
|
98
|
+
@group(0) @binding(9) var<storage, read_write> flags: array<atomic<u32>>; // [0]: pair counter, [1]: collinear pivots
|
|
99
|
+
@group(0) @binding(10) var<storage, read_write> pairEdges: array<vec2<u32>>;
|
|
100
|
+
fn emit(seg: u32, num: vec2<u32>, den: vec2<u32>, delta: i32, px: i32, py: i32, vid: u32) {
|
|
101
|
+
if (params.mode == 0u) { atomicAdd(&evCount[seg], 1u); return; }
|
|
102
|
+
let k = atomicAdd(&evCount[seg], 1u); let at = (evStart[seg] + k) * 2u; if (at + 1u >= arrayLength(&events)) { return; }
|
|
103
|
+
events[at] = vec4<u32>(num.x, num.y, den.x, den.y); events[at + 1u] = vec4<u32>(bitcast<u32>(delta), bitcast<u32>(px), bitcast<u32>(py), vid);
|
|
104
|
+
}
|
|
105
|
+
fn dotw(ux: i32, uy: i32, vx: i32, vy: i32) -> vec2<u32> { let a = muls(ux, vx); let b = muls(uy, vy); var lo = a.x + b.x; var hi = a.y + b.y; if (lo < a.x) { hi = hi + 1u; } return vec2<u32>(lo, hi); }
|
|
106
|
+
@compute @workgroup_size(256) fn intersect(@builtin(global_invocation_id) gid_raw: vec3<u32>) { let gid = vec3<u32>(gid_raw.x + gid_raw.y * 16776960u, 0u, 0u);
|
|
107
|
+
let a0 = gid.x; if (a0 >= arrayLength(&cellList) || params.cells == 0u) { return; }
|
|
108
|
+
let c = cellList[a0].y; if (c >= params.cells) { return; }
|
|
109
|
+
let s0 = cellStart[c]; let s1 = cellStart[c + 1u]; if (a0 < s0 || a0 >= s1 || badRange(s0, s1)) { return; }
|
|
110
|
+
let L = layerInfo[segInfo[cellList[s0].x].x];
|
|
111
|
+
let cl = i32(c) - L.z; let cellX = cl % L.x; let cellY = cl / L.x;
|
|
112
|
+
for (var a = a0; a < a0 + 1u; a++) {
|
|
113
|
+
let i = cellList[a].x; let A = segs[i]; let a1 = segInfo[i].z;
|
|
114
|
+
for (var b = a + 1u; b < s1; b++) {
|
|
115
|
+
let j = cellList[b].x; let Bs = segs[j]; let b1 = segInfo[j].z;
|
|
116
|
+
let lox = max(min(A.x, A.z), min(Bs.x, Bs.z)); let hix = min(max(A.x, A.z), max(Bs.x, Bs.z));
|
|
117
|
+
let loy = max(min(A.y, A.w), min(Bs.y, Bs.w)); let hiy = min(max(A.y, A.w), max(Bs.y, Bs.w));
|
|
118
|
+
if (lox > hix || loy > hiy) { continue; }
|
|
119
|
+
if (lox / params.cs != cellX || loy / params.cs != cellY) { continue; }
|
|
120
|
+
if (i == j || i == b1 || a1 == j || a1 == b1) { continue; } // adjacent edges meet only at their shared vertex
|
|
121
|
+
let o1 = orientSoS(i, a1, j); let o2 = orientSoS(i, a1, b1); let o3 = orientSoS(j, b1, i); let o4 = orientSoS(j, b1, a1);
|
|
122
|
+
if (!(o1 * o2 < 0 && o3 * o4 < 0)) { continue; }
|
|
123
|
+
var den = cross(A.z - A.x, A.w - A.y, Bs.z - Bs.x, Bs.w - Bs.y);
|
|
124
|
+
var numI = vec2<u32>(0u, 0u); var denI = vec2<u32>(1u, 0u); var numJ = vec2<u32>(0u, 0u); var denJ = vec2<u32>(1u, 0u);
|
|
125
|
+
var px = 0; var py = 0;
|
|
126
|
+
if (sign64(den) != 0) {
|
|
127
|
+
numI = cross(Bs.x - A.x, Bs.y - A.y, Bs.z - Bs.x, Bs.w - Bs.y); denI = den;
|
|
128
|
+
if (sign64(denI) < 0) { numI = neg64(numI); denI = neg64(denI); }
|
|
129
|
+
numJ = cross(A.x - Bs.x, A.y - Bs.y, A.z - A.x, A.w - A.y); denJ = neg64(den);
|
|
130
|
+
if (sign64(denJ) < 0) { numJ = neg64(numJ); denJ = neg64(denJ); }
|
|
131
|
+
// the point is computed along the lower-index segment: which of the two a cell lists first depends on atomic order, and the
|
|
132
|
+
// two parametrisations round differently in f32 (measured: 1-unit differences between identical runs)
|
|
133
|
+
// exact integer rounding (f32 rounded 0.84 % of points differently on SwiftShader than on Metal), along the lower-index segment
|
|
134
|
+
if (params.mode == 0u) { }
|
|
135
|
+
else if (i < j) { px = A.x + roundedShift(numI, denI, A.z - A.x); py = A.y + roundedShift(numI, denI, A.w - A.y); }
|
|
136
|
+
else { px = Bs.x + roundedShift(numJ, denJ, Bs.z - Bs.x); py = Bs.y + roundedShift(numJ, denJ, Bs.w - Bs.y); }
|
|
137
|
+
} else { // collinear pair made to cross by SoS: next to the pivot endpoint
|
|
138
|
+
if (params.mode == 1u) { atomicAdd(&flags[1], 1u); }
|
|
139
|
+
let top = max(max(i, a1), max(j, b1)); var pv = a1;
|
|
140
|
+
if (top == b1) { pv = j; } else if (top == j) { pv = b1; } else if (top == a1) { pv = i; }
|
|
141
|
+
let Pv = vtx(pv); px = Pv.x; py = Pv.y;
|
|
142
|
+
if (pv == i) { numI = vec2<u32>(0u, 0u); } else if (pv == a1) { numI = vec2<u32>(1u, 0u); } else { numI = dotw(px - A.x, py - A.y, A.z - A.x, A.w - A.y); denI = dotw(A.z - A.x, A.w - A.y, A.z - A.x, A.w - A.y); }
|
|
143
|
+
if (pv == j) { numJ = vec2<u32>(0u, 0u); } else if (pv == b1) { numJ = vec2<u32>(1u, 0u); } else { numJ = dotw(px - Bs.x, py - Bs.y, Bs.z - Bs.x, Bs.w - Bs.y); denJ = dotw(Bs.z - Bs.x, Bs.w - Bs.y, Bs.z - Bs.x, Bs.w - Bs.y); }
|
|
144
|
+
}
|
|
145
|
+
var vid = 0u;
|
|
146
|
+
if (params.mode == 1u) { let pid = atomicAdd(&flags[0], 1u); vid = params.n + pid; if (pid < arrayLength(&pairEdges)) { pairEdges[pid] = vec2<u32>(i, j); } }
|
|
147
|
+
var dI = -1; if (o3 < 0) { dI = 1; }
|
|
148
|
+
var dJ = -1; if (o1 < 0) { dJ = 1; }
|
|
149
|
+
emit(i, numI, denI, dI, px, py, vid);
|
|
150
|
+
emit(j, numJ, denJ, dJ, px, py, vid);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}`
|
|
154
|
+
|
|
155
|
+
export const SORT_SHADER = PRELUDE + /* wgsl */`
|
|
156
|
+
@group(0) @binding(4) var<storage, read> evStart: array<u32>;
|
|
157
|
+
@group(0) @binding(5) var<storage, read_write> events: array<vec4<u32>>;
|
|
158
|
+
@group(0) @binding(6) var<storage, read> pairEdges: array<vec2<u32>>;
|
|
159
|
+
fn add64(a: vec2<u32>, b: vec2<u32>) -> vec3<u32> { var lo = a.x + b.x; var c = 0u; if (lo < a.x) { c = 1u; } var hi = a.y + b.y + c; var c2 = 0u; if (hi < a.y || (c == 1u && hi == a.y)) { c2 = 1u; } return vec3<u32>(lo, hi, c2); }
|
|
160
|
+
fn mul128(a: vec2<u32>, b: vec2<u32>) -> vec4<u32> {
|
|
161
|
+
let p00 = mulu(a.x, b.x); let p01 = mulu(a.x, b.y); let p10 = mulu(a.y, b.x); let p11 = mulu(a.y, b.y);
|
|
162
|
+
var r0 = p00.x;
|
|
163
|
+
let s1 = add64(vec2<u32>(p00.y, 0u), vec2<u32>(p01.x, p01.y)); // limbs 1,2 (+carry into 3)
|
|
164
|
+
let s2 = add64(vec2<u32>(s1.x, s1.y), vec2<u32>(p10.x, p10.y));
|
|
165
|
+
let s3 = add64(vec2<u32>(s2.y, s1.z + s2.z), vec2<u32>(p11.x, p11.y));
|
|
166
|
+
return vec4<u32>(r0, s2.x, s3.x, s3.y);
|
|
167
|
+
}
|
|
168
|
+
fn lt128(x: vec4<u32>, y: vec4<u32>) -> bool {
|
|
169
|
+
if (x.w != y.w) { return x.w < y.w; } if (x.z != y.z) { return x.z < y.z; } if (x.y != y.y) { return x.y < y.y; } return x.x < y.x;
|
|
170
|
+
}
|
|
171
|
+
fn before(p: vec4<u32>, q: vec4<u32>) -> bool { return lt128(mul128(p.xy, q.zw), mul128(q.xy, p.zw)); }
|
|
172
|
+
// ---- two events at the same place on a segment: ordered as if every vertex were moved by an infinitesimal amount,
|
|
173
|
+
// higher vertex ids first, x before y (the same perturbation the crossing test uses). The order is the sign of the
|
|
174
|
+
// difference of the two events' parameters' derivatives, each a fraction of signed integers up to 128 bits.
|
|
175
|
+
struct SM { m: vec4<u32>, neg: bool }
|
|
176
|
+
fn isZero128(a: vec4<u32>) -> bool { return a.x == 0u && a.y == 0u && a.z == 0u && a.w == 0u; }
|
|
177
|
+
fn add128(a: vec4<u32>, b: vec4<u32>) -> vec4<u32> {
|
|
178
|
+
var r = vec4<u32>(0u); var carry = 0u;
|
|
179
|
+
for (var k = 0; k < 4; k++) { let s1 = a[k] + b[k]; var c = 0u; if (s1 < a[k]) { c = 1u; } let s2 = s1 + carry; if (s2 < s1) { c = 1u; } r[k] = s2; carry = c; }
|
|
180
|
+
return r; }
|
|
181
|
+
fn sub128(a: vec4<u32>, b: vec4<u32>) -> vec4<u32> { // a >= b
|
|
182
|
+
var r = vec4<u32>(0u); var borrow = 0u;
|
|
183
|
+
for (var k = 0; k < 4; k++) { let d1 = a[k] - b[k]; var c = 0u; if (a[k] < b[k]) { c = 1u; } let d2 = d1 - borrow; if (d1 < borrow) { c = 1u; } r[k] = d2; borrow = c; }
|
|
184
|
+
return r; }
|
|
185
|
+
fn smAdd(a: SM, b: SM) -> SM {
|
|
186
|
+
if (a.neg == b.neg) { return SM(add128(a.m, b.m), a.neg); }
|
|
187
|
+
if (lt128(a.m, b.m)) { return SM(sub128(b.m, a.m), b.neg); }
|
|
188
|
+
return SM(sub128(a.m, b.m), a.neg); }
|
|
189
|
+
fn smNeg(a: SM) -> SM { return SM(a.m, !a.neg); }
|
|
190
|
+
fn smProd(u: vec2<u32>, x: i32) -> SM { return SM(mul128(u, vec2<u32>(u32(abs(x)), 0u)), x < 0); } // unsigned 64 x signed 32
|
|
191
|
+
fn mul256(a: vec4<u32>, b: vec4<u32>) -> array<u32, 8> {
|
|
192
|
+
var r: array<u32, 8>;
|
|
193
|
+
for (var i = 0; i < 4; i++) { var carry = 0u;
|
|
194
|
+
for (var j = 0; j < 4; j++) { let p = mulu(a[i], b[j]); let s1 = r[i + j] + p.x; var c = 0u; if (s1 < p.x) { c = 1u; } let s2 = s1 + carry; if (s2 < s1) { c = c + 1u; } r[i + j] = s2; carry = p.y + c; }
|
|
195
|
+
r[i + 4] = carry; }
|
|
196
|
+
return r; }
|
|
197
|
+
// sign of num1/den1 - num2/den2, by cross-multiplying (a zero denominator gives equal, as on the CPU)
|
|
198
|
+
fn cmpFrac(n1in: SM, d1: SM, n2in: SM, d2: SM) -> i32 {
|
|
199
|
+
var n1 = n1in; var n2 = n2in; if (d1.neg) { n1.neg = !n1.neg; } if (d2.neg) { n2.neg = !n2.neg; }
|
|
200
|
+
let l = mul256(n1.m, d2.m); let r = mul256(n2.m, d1.m);
|
|
201
|
+
var lz = true; var rz = true; var mag = 0;
|
|
202
|
+
for (var k = 7; k >= 0; k--) { if (l[k] != 0u) { lz = false; } if (r[k] != 0u) { rz = false; } if (mag == 0 && l[k] != r[k]) { if (l[k] < r[k]) { mag = -1; } else { mag = 1; } } }
|
|
203
|
+
let ln = n1.neg && !lz; let rn = n2.neg && !rz;
|
|
204
|
+
if (ln != rn) { if (ln) { return -1; } return 1; }
|
|
205
|
+
if (ln) { return -mag; }
|
|
206
|
+
return mag; }
|
|
207
|
+
struct Shift { num: SM, den: SM }
|
|
208
|
+
// the derivative of the event's parameter along segment A when vertex v moves in x (axis 0) or y (axis 1)
|
|
209
|
+
fn shiftOf(A: u32, n: vec2<u32>, d: vec2<u32>, C: u32, v: u32, axis: u32) -> Shift {
|
|
210
|
+
let a0 = A; let a1 = segInfo[A].z; let c0 = C; let c1 = segInfo[C].z;
|
|
211
|
+
let Pa0 = vtx(a0); let Pa1 = vtx(a1); let Pc0 = vtx(c0); let Pc1 = vtx(c1);
|
|
212
|
+
let dAx = Pa1.x - Pa0.x; let dAy = Pa1.y - Pa0.y; let dCx = Pc1.x - Pc0.x; let dCy = Pc1.y - Pc0.y;
|
|
213
|
+
var D = cross(dAx, dAy, dCx, dCy); let Dneg = sign64(D) < 0; if (Dneg) { D = neg64(D); }
|
|
214
|
+
let den = SM(mul128(D, d), Dneg);
|
|
215
|
+
// cg(u) = gx * u.y - gy * u.x, for u = (w - a0) * d - n * dA, and for u = dC
|
|
216
|
+
var cgC = dCy; if (axis == 1u) { cgC = -dCx; }
|
|
217
|
+
var num = SM(vec4<u32>(0u), false); var touched = false;
|
|
218
|
+
if (v == c0 || v == c1) {
|
|
219
|
+
var w = Pc1; if (v == c1) { w = Pc0; }
|
|
220
|
+
var part: SM;
|
|
221
|
+
if (axis == 0u) { part = smAdd(smProd(d, w.y - Pa0.y), smNeg(smProd(n, dAy))); }
|
|
222
|
+
else { part = smNeg(smAdd(smProd(d, w.x - Pa0.x), smNeg(smProd(n, dAx)))); }
|
|
223
|
+
if (v == c0) { num = smAdd(num, part); }
|
|
224
|
+
if (v == c1) { num = smAdd(num, smNeg(part)); }
|
|
225
|
+
touched = true; }
|
|
226
|
+
if (v == c0 && v == c1) { touched = true; }
|
|
227
|
+
if (v == a0) { let lo = d.x - n.x; var hi = d.y - n.y; if (d.x < n.x) { hi = hi - 1u; } num = smAdd(num, smNeg(smProd(vec2<u32>(lo, hi), cgC))); touched = true; }
|
|
228
|
+
if (v == a1) { num = smAdd(num, smNeg(smProd(n, cgC))); touched = true; }
|
|
229
|
+
if (!touched) { return Shift(SM(vec4<u32>(0u), false), SM(vec4<u32>(1u, 0u, 0u, 0u), false)); }
|
|
230
|
+
return Shift(num, den); }
|
|
231
|
+
fn otherEdge(A: u32, vid: u32) -> u32 { let pid = vid - params.n; if (pid >= arrayLength(&pairEdges)) { return A; } let e = pairEdges[pid]; if (e.x == A) { return e.y; } return e.x; }
|
|
232
|
+
// -1: p first, 1: q first, 0: the perturbation does not separate them (two segments on one line)
|
|
233
|
+
fn sosOrder(A: u32, p0: vec4<u32>, p1: vec4<u32>, q0: vec4<u32>, q1: vec4<u32>) -> i32 {
|
|
234
|
+
let Cp = otherEdge(A, p1.w); let Cq = otherEdge(A, q1.w);
|
|
235
|
+
var ids = array<u32, 6>(A, segInfo[A].z, Cp, segInfo[Cp].z, Cq, segInfo[Cq].z);
|
|
236
|
+
for (var round = 0; round < 6; round++) {
|
|
237
|
+
var top = 0u; var found = false;
|
|
238
|
+
for (var k = 0; k < 6; k++) { if (ids[k] != 0xffffffffu && (!found || ids[k] > top)) { top = ids[k]; found = true; } }
|
|
239
|
+
if (!found) { break; }
|
|
240
|
+
for (var k = 0; k < 6; k++) { if (ids[k] == top) { ids[k] = 0xffffffffu; } }
|
|
241
|
+
for (var axis = 0u; axis < 2u; axis++) {
|
|
242
|
+
let sp = shiftOf(A, p0.xy, p0.zw, Cp, top, axis); let sq = shiftOf(A, q0.xy, q0.zw, Cq, top, axis);
|
|
243
|
+
let c = cmpFrac(sp.num, sp.den, sq.num, sq.den);
|
|
244
|
+
if (c != 0) { return c; } } }
|
|
245
|
+
return 0; }
|
|
246
|
+
fn beforeExact(A: u32, k0: vec4<u32>, k1: vec4<u32>, p0: vec4<u32>, p1: vec4<u32>) -> bool {
|
|
247
|
+
if (before(k0, p0)) { return true; }
|
|
248
|
+
if (before(p0, k0)) { return false; }
|
|
249
|
+
if (params.mode == 0u) { return false; }
|
|
250
|
+
return sosOrder(A, k0, k1, p0, p1) < 0; }
|
|
251
|
+
@compute @workgroup_size(256) fn sortEvents(@builtin(global_invocation_id) gid_raw: vec3<u32>) { let gid = vec3<u32>(gid_raw.x + gid_raw.y * 16776960u, 0u, 0u);
|
|
252
|
+
let i = gid.x; if (i >= params.n) { return; }
|
|
253
|
+
let r0 = evStart[i]; let r1 = evStart[i + 1u]; if (badRange(r0, r1)) { return; }
|
|
254
|
+
for (var a = r0 + 1u; a < r1; a++) {
|
|
255
|
+
let k0 = events[a * 2u]; let k1 = events[a * 2u + 1u]; var b = a;
|
|
256
|
+
loop { if (b == r0) { break; } let p = events[(b - 1u) * 2u]; let p1 = events[(b - 1u) * 2u + 1u]; if (!beforeExact(i, k0, k1, p, p1)) { break; } events[b * 2u] = p; events[b * 2u + 1u] = p1; b = b - 1u; }
|
|
257
|
+
events[b * 2u] = k0; events[b * 2u + 1u] = k1;
|
|
258
|
+
}
|
|
259
|
+
}`
|
|
260
|
+
|
|
261
|
+
export const LINK_SHADER = /* wgsl */`
|
|
262
|
+
struct LP { e: u32, v: u32, pad0: u32, pad1: u32 }
|
|
263
|
+
@group(0) @binding(0) var<uniform> lp: LP;
|
|
264
|
+
@group(0) @binding(1) var<storage, read> pv: array<vec4<u32>>; // start vid, end vid, layer
|
|
265
|
+
@group(0) @binding(2) var<storage, read> pc: array<vec4<i32>>; // sx, sy, ex, ey
|
|
266
|
+
@group(0) @binding(3) var<storage, read_write> vcount: array<atomic<u32>>;
|
|
267
|
+
@group(0) @binding(4) var<storage, read> vstart: array<u32>;
|
|
268
|
+
@group(0) @binding(5) var<storage, read_write> vlist: array<u32>;
|
|
269
|
+
@group(0) @binding(6) var<storage, read_write> succ: array<u32>;
|
|
270
|
+
@group(0) @binding(7) var<storage, read_write> labA: array<vec2<u32>>; // label, next
|
|
271
|
+
@group(0) @binding(8) var<storage, read_write> labB: array<vec2<u32>>;
|
|
272
|
+
@compute @workgroup_size(256) fn vcountK(@builtin(global_invocation_id) g_raw: vec3<u32>) { let g = vec3<u32>(g_raw.x + g_raw.y * 16776960u, 0u, 0u); if (g.x >= lp.e) { return; } atomicAdd(&vcount[pv[g.x].x], 1u); }
|
|
273
|
+
@compute @workgroup_size(256) fn vscatter(@builtin(global_invocation_id) g_raw: vec3<u32>) { let g = vec3<u32>(g_raw.x + g_raw.y * 16776960u, 0u, 0u); if (g.x >= lp.e) { return; } let v = pv[g.x].x; let k = atomicAdd(&vcount[v], 1u); vlist[vstart[v] + k] = g.x; }
|
|
274
|
+
@compute @workgroup_size(256) fn successor(@builtin(global_invocation_id) g_raw: vec3<u32>) { let g = vec3<u32>(g_raw.x + g_raw.y * 16776960u, 0u, 0u);
|
|
275
|
+
let e = g.x; if (e >= lp.e) { return; }
|
|
276
|
+
let v = pv[e].y; let a = vstart[v]; let b = vstart[v + 1u];
|
|
277
|
+
if (b == a) { succ[e] = e; labA[e] = vec2<u32>(e, e); return; }
|
|
278
|
+
var best = vlist[a];
|
|
279
|
+
if (b - a > 1024u) { atomicAdd(&vcount[lp.v], 1u); succ[e] = e; labA[e] = vec2<u32>(e, e); return; } // out-degree cap: a corrupt vstart must not spin the GPU
|
|
280
|
+
if (b - a > 1u) { // first outgoing edge clockwise from the reverse of the incoming one
|
|
281
|
+
let q = pc[e]; let rin = atan2(f32(q.y - q.w), f32(q.x - q.z)); var bestAng = 10.0;
|
|
282
|
+
for (var k = a; k < b; k++) { let o = vlist[k]; let r = pc[o]; var ang = rin - atan2(f32(r.w - r.y), f32(r.z - r.x));
|
|
283
|
+
if (!(ang > 0.0)) { ang = ang + 6.2831853; } if (ang > 6.2831853) { ang = ang - 6.2831853; } // a difference of two atan2 lies in [-2pi, 2pi]: one step each, no loop
|
|
284
|
+
if (ang < bestAng) { bestAng = ang; best = o; } }
|
|
285
|
+
}
|
|
286
|
+
succ[e] = best; labA[e] = vec2<u32>(min(e, best), best);
|
|
287
|
+
}
|
|
288
|
+
@compute @workgroup_size(256) fn jumpAB(@builtin(global_invocation_id) g_raw: vec3<u32>) { let g = vec3<u32>(g_raw.x + g_raw.y * 16776960u, 0u, 0u); let e = g.x; if (e >= lp.e) { return; } let s = labA[e]; let t = labA[s.y]; labB[e] = vec2<u32>(min(s.x, t.x), t.y); }
|
|
289
|
+
@compute @workgroup_size(256) fn jumpBA(@builtin(global_invocation_id) g_raw: vec3<u32>) { let g = vec3<u32>(g_raw.x + g_raw.y * 16776960u, 0u, 0u); let e = g.x; if (e >= lp.e) { return; } let s = labB[e]; let t = labB[s.y]; labA[e] = vec2<u32>(min(s.x, t.x), t.y); }
|
|
290
|
+
`
|
|
291
|
+
|
|
292
|
+
export const SCAN_SHADER = /* wgsl */`
|
|
293
|
+
@group(0) @binding(0) var<uniform> sp: vec4<u32>;
|
|
294
|
+
@group(0) @binding(1) var<storage, read_write> data: array<u32>;
|
|
295
|
+
@group(0) @binding(2) var<storage, read_write> sums: array<u32>;
|
|
296
|
+
var<workgroup> tmp: array<u32, 256>;
|
|
297
|
+
@compute @workgroup_size(256) fn scanBlock(@builtin(global_invocation_id) g_raw: vec3<u32>, @builtin(local_invocation_id) l: vec3<u32>, @builtin(workgroup_id) wg_raw: vec3<u32>) { let g = vec3<u32>(g_raw.x + g_raw.y * 16776960u, 0u, 0u); let wg = vec3<u32>(wg_raw.x + wg_raw.y * 65535u, 0u, 0u);
|
|
298
|
+
if (wg.x * 256u >= sp.x + 256u) { return; } // a workgroup past the last block (the second dispatch row): nothing to scan, and sums[wg] would be out of range
|
|
299
|
+
let i = g.x; var v = 0u; if (i < sp.x) { v = data[i]; } tmp[l.x] = v; workgroupBarrier();
|
|
300
|
+
for (var off = 1u; off < 256u; off = off * 2u) { var t = 0u; if (l.x >= off) { t = tmp[l.x - off]; } workgroupBarrier(); tmp[l.x] = tmp[l.x] + t; workgroupBarrier(); }
|
|
301
|
+
if (i < sp.x) { data[i] = tmp[l.x] - v; }
|
|
302
|
+
if (l.x == 255u) { sums[wg.x] = tmp[255]; }
|
|
303
|
+
}
|
|
304
|
+
@compute @workgroup_size(256) fn addOff(@builtin(global_invocation_id) g_raw: vec3<u32>, @builtin(workgroup_id) wg_raw: vec3<u32>) { let g = vec3<u32>(g_raw.x + g_raw.y * 16776960u, 0u, 0u); let wg = vec3<u32>(wg_raw.x + wg_raw.y * 65535u, 0u, 0u); if (g.x < sp.x) { data[g.x] = data[g.x] + sums[wg.x]; } }`
|
|
305
|
+
|
|
306
|
+
export const CHECK_SHADER = /* wgsl */`
|
|
307
|
+
@group(0) @binding(0) var<uniform> cp: vec4<u32>; // n, cap
|
|
308
|
+
@group(0) @binding(1) var<storage, read> starts: array<u32>;
|
|
309
|
+
@group(0) @binding(2) var<storage, read_write> bad: array<atomic<u32>>;
|
|
310
|
+
@compute @workgroup_size(256) fn check(@builtin(global_invocation_id) g_raw: vec3<u32>) { let g = vec3<u32>(g_raw.x + g_raw.y * 16776960u, 0u, 0u); let i = g.x; if (i >= cp.x) { return; }
|
|
311
|
+
let lo = starts[i]; let hi = starts[i + 1u]; if (hi < lo || hi - lo > cp.y) { atomicAdd(&bad[0], 1u); } }`
|
|
312
|
+
|
|
313
|
+
export const GLUE_SHADER = PRELUDE + /* wgsl */`
|
|
314
|
+
struct GP { n: u32, polys: u32, fill: u32, mode: u32 }
|
|
315
|
+
@group(0) @binding(4) var<uniform> gp: GP;
|
|
316
|
+
@group(0) @binding(5) var<storage, read> evStart: array<u32>;
|
|
317
|
+
@group(0) @binding(6) var<storage, read> events: array<vec4<u32>>;
|
|
318
|
+
@group(0) @binding(7) var<storage, read> polyInfo: array<vec4<u32>>; // first, count, layer segStart, layer segEnd
|
|
319
|
+
@group(0) @binding(8) var<storage, read_write> work: array<u32>;
|
|
320
|
+
@group(0) @binding(9) var<storage, read_write> polyW: array<i32>;
|
|
321
|
+
fn leQ(v: u32, a0: u32, ay: i32, dA: vec2<i32>) -> bool {
|
|
322
|
+
if (v == a0) { var s = sign(dA.y); if (s == 0) { s = sign(dA.x); } return s >= 0; }
|
|
323
|
+
let d = vtx(v).y - ay; if (d != 0) { return d < 0; } return v < a0; }
|
|
324
|
+
@compute @workgroup_size(256) fn rays(@builtin(global_invocation_id) g_raw: vec3<u32>) { let g = vec3<u32>(g_raw.x + g_raw.y * 16776960u, 0u, 0u);
|
|
325
|
+
let p = g.x; if (p >= gp.polys) { return; }
|
|
326
|
+
let pi = polyInfo[p]; let a0 = pi.x; let a1 = segInfo[a0].z; let A0 = vtx(a0); let dA = vtx(a1) - A0;
|
|
327
|
+
var wn = 0;
|
|
328
|
+
for (var c = pi.z; c < pi.w; c++) { let c1 = segInfo[c].z;
|
|
329
|
+
if (leQ(c, a0, A0.y, dA) == leQ(c1, a0, A0.y, dA)) { continue; }
|
|
330
|
+
var side = 1;
|
|
331
|
+
if (c != a0) {
|
|
332
|
+
if (c1 == a0) { side = orientSoS(c, c1, a1); if (side == 0) { let dc = vtx(c1) - vtx(c); side = sign(dc.x * dA.x + dc.y * dA.y); } }
|
|
333
|
+
else { side = orientSoS(c, c1, a0); } }
|
|
334
|
+
let C0 = vtx(c); let C1 = vtx(c1);
|
|
335
|
+
if (C1.y > C0.y || (C1.y == C0.y && c1 > c)) { if (side > 0) { wn = wn + 1; } } else { if (side < 0) { wn = wn - 1; } } }
|
|
336
|
+
polyW[p] = wn;
|
|
337
|
+
}
|
|
338
|
+
@compute @workgroup_size(256) fn deltaSums(@builtin(global_invocation_id) g_raw: vec3<u32>) { let g = vec3<u32>(g_raw.x + g_raw.y * 16776960u, 0u, 0u);
|
|
339
|
+
let a = g.x; if (a > gp.n) { return; } if (a == gp.n) { work[a] = 0u; return; }
|
|
340
|
+
if (badRange(evStart[a], evStart[a + 1u])) { return; }
|
|
341
|
+
var s = 0; for (var r = evStart[a]; r < evStart[a + 1u]; r++) { s = s + bitcast<i32>(events[r * 2u + 1u].x); } work[a] = bitcast<u32>(s);
|
|
342
|
+
}`
|
|
343
|
+
|
|
344
|
+
export const PIECES_SHADER = PRELUDE + /* wgsl */`
|
|
345
|
+
struct GP { n: u32, polys: u32, fill: u32, mode: u32 }
|
|
346
|
+
@group(0) @binding(4) var<uniform> gp: GP;
|
|
347
|
+
@group(0) @binding(5) var<storage, read> evStart: array<u32>;
|
|
348
|
+
@group(0) @binding(6) var<storage, read> events: array<vec4<u32>>;
|
|
349
|
+
@group(0) @binding(7) var<storage, read> leftW: array<vec2<u32>>; // per segment: (scanned delta sum, polygon index)
|
|
350
|
+
@group(0) @binding(8) var<storage, read> polyInfoW: array<vec4<i32>>; // first, count, start winding, 0
|
|
351
|
+
@group(0) @binding(9) var<storage, read_write> pieceCount: array<u32>;
|
|
352
|
+
@group(0) @binding(10) var<storage, read_write> pv: array<vec4<u32>>; // start vid, end vid, layer
|
|
353
|
+
@group(0) @binding(11) var<storage, read_write> pcd: array<vec4<i32>>; // two entries per piece: coordinates, parent direction
|
|
354
|
+
fn insideOf(w: i32) -> bool { if (gp.fill == 3u) { return (w & 1) != 0; } if (gp.fill == 2u) { return w < 0; } if (gp.fill == 1u) { return w > 0; } return w != 0; }
|
|
355
|
+
@compute @workgroup_size(256) fn pieces(@builtin(global_invocation_id) g_raw: vec3<u32>) { let g = vec3<u32>(g_raw.x + g_raw.y * 16776960u, 0u, 0u);
|
|
356
|
+
let a = g.x; if (a >= gp.n) { return; }
|
|
357
|
+
let lw = leftW[a]; let pw = polyInfoW[lw.y]; let firstScan = leftW[u32(pw.x)].x;
|
|
358
|
+
var wl = pw.z + bitcast<i32>(lw.x - firstScan);
|
|
359
|
+
let S = segs[a]; var x = S.x; var y = S.y; var vid = a; let nxt = segInfo[a].z; let dir = vec2<i32>(S.z - S.x, S.w - S.y); let layer = segInfo[a].x;
|
|
360
|
+
var k = 0u; let base = pieceCount[a]; let r0 = evStart[a]; let r1 = evStart[a + 1u]; if (badRange(r0, r1)) { return; }
|
|
361
|
+
for (var r = r0; r <= r1; r++) {
|
|
362
|
+
var nx = S.z; var ny = S.w; var nv = nxt; var d = 0;
|
|
363
|
+
if (r < r1) { let e = events[r * 2u + 1u]; d = bitcast<i32>(e.x); nx = bitcast<i32>(e.y); ny = bitcast<i32>(e.z); nv = e.w; }
|
|
364
|
+
let il = insideOf(wl); let ir = insideOf(wl - 1);
|
|
365
|
+
if (il != ir) {
|
|
366
|
+
if (gp.mode == 1u) { let o = base + k;
|
|
367
|
+
if (il) { pv[o] = vec4<u32>(vid, nv, layer, 0u); pcd[o * 2u] = vec4<i32>(x, y, nx, ny); pcd[o * 2u + 1u] = vec4<i32>(0, 0, dir.x, dir.y); }
|
|
368
|
+
else { pv[o] = vec4<u32>(nv, vid, layer, 0u); pcd[o * 2u] = vec4<i32>(nx, ny, x, y); pcd[o * 2u + 1u] = vec4<i32>(0, 0, -dir.x, -dir.y); } }
|
|
369
|
+
k = k + 1u; }
|
|
370
|
+
x = nx; y = ny; vid = nv; wl = wl + d; }
|
|
371
|
+
if (gp.mode == 0u) { pieceCount[a] = k; }
|
|
372
|
+
}`
|
|
373
|
+
|
|
374
|
+
export const TIES_SHADER = /* wgsl */`
|
|
375
|
+
@group(0) @binding(0) var<uniform> tp: vec4<u32>;
|
|
376
|
+
@group(0) @binding(1) var<storage, read> evStart: array<u32>;
|
|
377
|
+
@group(0) @binding(2) var<storage, read> events: array<vec4<u32>>;
|
|
378
|
+
@group(0) @binding(3) var<storage, read_write> tieList: array<atomic<u32>>;
|
|
379
|
+
fn mulu(a: u32, b: u32) -> vec2<u32> { let al = a & 0xffffu; let ah = a >> 16u; let bl = b & 0xffffu; let bh = b >> 16u; let ll = al * bl; let lh = al * bh; let hl = ah * bl; let hh = ah * bh;
|
|
380
|
+
var lo = ll; var hi = hh + (lh >> 16u) + (hl >> 16u); let t1 = lh << 16u; lo = lo + t1; if (lo < t1) { hi = hi + 1u; } let t2 = hl << 16u; lo = lo + t2; if (lo < t2) { hi = hi + 1u; } return vec2<u32>(lo, hi); }
|
|
381
|
+
fn add128(a: vec4<u32>, b: vec4<u32>) -> vec4<u32> { var r: vec4<u32>; r.x = a.x + b.x; var c = select(0u, 1u, r.x < a.x);
|
|
382
|
+
let y = a.y + b.y; var c1 = select(0u, 1u, y < a.y); r.y = y + c; if (r.y < y) { c1 = 1u; } c = c1;
|
|
383
|
+
let z = a.z + b.z; var c2 = select(0u, 1u, z < a.z); r.z = z + c; if (r.z < z) { c2 = 1u; } c = c2; r.w = a.w + b.w + c; return r; }
|
|
384
|
+
fn mul128(a: vec2<u32>, b: vec2<u32>) -> vec4<u32> { let p00 = mulu(a.x, b.x); let p01 = mulu(a.x, b.y); let p10 = mulu(a.y, b.x); let p11 = mulu(a.y, b.y);
|
|
385
|
+
var r = vec4<u32>(p00.x, p00.y, p11.x, p11.y); r = add128(r, vec4<u32>(0u, p01.x, p01.y, 0u)); r = add128(r, vec4<u32>(0u, p10.x, p10.y, 0u)); return r; }
|
|
386
|
+
@compute @workgroup_size(256) fn ties(@builtin(global_invocation_id) g_raw: vec3<u32>) { let g = vec3<u32>(g_raw.x + g_raw.y * 16776960u, 0u, 0u);
|
|
387
|
+
let a = g.x; if (a >= tp.x) { return; } if (evStart[a + 1u] < evStart[a] || evStart[a + 1u] - evStart[a] > 4096u) { return; } // RANGE_CAP (this shader has no prelude)
|
|
388
|
+
for (var r = evStart[a] + 1u; r < evStart[a + 1u]; r++) { let p = events[(r - 1u) * 2u]; let q = events[r * 2u];
|
|
389
|
+
if (all(mul128(p.xy, q.zw) == mul128(q.xy, p.zw))) { let k = atomicAdd(&tieList[0], 1u); atomicStore(&tieList[1u + k], a); return; } }
|
|
390
|
+
}`
|
|
391
|
+
|
|
392
|
+
export const ZIP_SHADER = /* wgsl */`
|
|
393
|
+
@group(0) @binding(0) var<uniform> zp: vec4<u32>;
|
|
394
|
+
@group(0) @binding(1) var<storage, read> a: array<u32>;
|
|
395
|
+
@group(0) @binding(2) var<storage, read> b: array<u32>;
|
|
396
|
+
@group(0) @binding(3) var<storage, read_write> o: array<vec2<u32>>;
|
|
397
|
+
@compute @workgroup_size(256) fn zip(@builtin(global_invocation_id) g_raw: vec3<u32>) { let g = vec3<u32>(g_raw.x + g_raw.y * 16776960u, 0u, 0u); if (g.x < zp.x) { o[g.x] = vec2<u32>(a[g.x], b[g.x]); } }`
|
|
398
|
+
|
|
399
|
+
export const DIRECTIONS_SHADER = /* wgsl */`
|
|
400
|
+
@group(0) @binding(0) var<uniform> dp: vec4<u32>;
|
|
401
|
+
@group(0) @binding(1) var<storage, read> pcd: array<vec4<i32>>;
|
|
402
|
+
@group(0) @binding(2) var<storage, read_write> dirOut: array<vec4<i32>>;
|
|
403
|
+
@compute @workgroup_size(256) fn dirs(@builtin(global_invocation_id) g_raw: vec3<u32>) { let g = vec3<u32>(g_raw.x + g_raw.y * 16776960u, 0u, 0u); if (g.x < dp.x) { dirOut[g.x] = pcd[g.x * 2u + 1u]; } }`
|
|
404
|
+
|
|
405
|
+
export const EXPAND_SHADER = /* wgsl */`
|
|
406
|
+
@group(0) @binding(0) var<uniform> ep: vec4<u32>; // segments, polygons
|
|
407
|
+
@group(0) @binding(1) var<storage, read> pts: array<vec2<i32>>;
|
|
408
|
+
@group(0) @binding(2) var<storage, read> polyIn: array<vec4<u32>>; // first, count, layer start, layer end
|
|
409
|
+
@group(0) @binding(3) var<storage, read> polyLayerIn: array<u32>;
|
|
410
|
+
@group(0) @binding(4) var<storage, read_write> segOut: array<vec4<i32>>;
|
|
411
|
+
@group(0) @binding(5) var<storage, read_write> infoOut: array<vec4<u32>>;
|
|
412
|
+
@group(0) @binding(6) var<storage, read_write> segPolyOut: array<u32>;
|
|
413
|
+
@compute @workgroup_size(256) fn expand(@builtin(global_invocation_id) g_raw: vec3<u32>) { let i = g_raw.x + g_raw.y * 16776960u; if (i >= ep.x || ep.y == 0u) { return; }
|
|
414
|
+
var lo = 0u; var hi = ep.y;
|
|
415
|
+
for (var step = 0u; step < 32u; step++) { if (hi - lo <= 1u) { break; } let mid = (lo + hi) / 2u; if (polyIn[mid].x <= i) { lo = mid; } else { hi = mid; } }
|
|
416
|
+
let first = polyIn[lo].x; let count = polyIn[lo].y; let next = first + (i - first + 1u) % count; let prev = first + (i - first + count - 1u) % count;
|
|
417
|
+
let a = pts[i]; let b = pts[next];
|
|
418
|
+
segOut[i] = vec4<i32>(a.x, a.y, b.x, b.y);
|
|
419
|
+
var flag = 0u; if (i == first) { flag = 1u; }
|
|
420
|
+
infoOut[i] = vec4<u32>(polyLayerIn[lo], prev, next, flag);
|
|
421
|
+
segPolyOut[i] = lo; }`
|
|
422
|
+
|
|
423
|
+
export const COMPACT_SHADER = /* wgsl */`
|
|
424
|
+
@group(0) @binding(0) var<uniform> dp: vec4<u32>;
|
|
425
|
+
@group(0) @binding(1) var<storage, read> pcd: array<vec4<i32>>;
|
|
426
|
+
@group(0) @binding(2) var<storage, read> succIn: array<u32>;
|
|
427
|
+
@group(0) @binding(3) var<storage, read> pvIn: array<vec4<u32>>;
|
|
428
|
+
@group(0) @binding(4) var<storage, read_write> compactOut: array<vec4<i32>>;
|
|
429
|
+
@compute @workgroup_size(256) fn compact(@builtin(global_invocation_id) g_raw: vec3<u32>) { let g = g_raw.x + g_raw.y * 16776960u; if (g < dp.x) { let c = pcd[g * 2u]; compactOut[g] = vec4<i32>(c.x, c.y, bitcast<i32>(succIn[g]), bitcast<i32>(pvIn[g].z)); } }`
|
|
430
|
+
|
|
431
|
+
export const GATE_SHADER = /* wgsl */`
|
|
432
|
+
@group(0) @binding(0) var<uniform> gu: vec4<u32>; // x: index into src, y: use src (1) or the constant z
|
|
433
|
+
@group(0) @binding(1) var<storage, read> bad: array<u32>;
|
|
434
|
+
@group(0) @binding(2) var<storage, read> src: array<u32>;
|
|
435
|
+
@group(0) @binding(3) var<storage, read_write> dst: array<u32>;
|
|
436
|
+
// a count for a later kernel's uniform, zero once any range check has failed: every later loop is bounded by that count
|
|
437
|
+
@compute @workgroup_size(1) fn gate() { var v = gu.z; if (gu.y == 1u) { v = src[gu.x]; } if (bad[0] != 0u) { v = 0u; } dst[0] = v; }`
|
|
438
|
+
|
|
439
|
+
export const ARGS_SHADER = /* wgsl */`
|
|
440
|
+
@group(0) @binding(0) var<uniform> au: vec4<u32>; // x: workgroup size
|
|
441
|
+
@group(0) @binding(1) var<storage, read> cnt: array<u32>;
|
|
442
|
+
@group(0) @binding(2) var<storage, read_write> args: array<u32>;
|
|
443
|
+
@compute @workgroup_size(1) fn argsK() { let groups = max(1u, (cnt[0] + au.x - 1u) / au.x); args[0] = min(groups, 65535u); args[1] = (groups + 65534u) / 65535u; args[2] = 1u; }`
|
|
444
|
+
|
|
445
|
+
export const POLYGON_WINDING_SHADER = /* wgsl */`
|
|
446
|
+
@group(0) @binding(0) var<uniform> pu: vec4<u32>;
|
|
447
|
+
@group(0) @binding(1) var<storage, read> polyInfo: array<vec4<u32>>;
|
|
448
|
+
@group(0) @binding(2) var<storage, read> polyW: array<i32>;
|
|
449
|
+
@group(0) @binding(3) var<storage, read_write> polyInfoW: array<vec4<i32>>;
|
|
450
|
+
@compute @workgroup_size(256) fn polyWK(@builtin(global_invocation_id) g_raw: vec3<u32>) { let g = vec3<u32>(g_raw.x + g_raw.y * 16776960u, 0u, 0u); let p = g.x; if (p >= pu.x) { return; } let q = polyInfo[p]; polyInfoW[p] = vec4<i32>(i32(q.x), i32(q.y), polyW[p], 0); }`
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// One slice with PASS1's front on the GPU: the kernel's calls (wasm-core/contour_phase.h) around contour_gpu.js'.
|
|
2
|
+
// Used by the slicer worker, and by the node test with Dawn's device. Two routes:
|
|
3
|
+
// mesh the kernel parses, seats and plans the layers and hands the mesh over (MESH); the GPU cuts, chains and
|
|
4
|
+
// unions every layer (contour_front_gpu.js -> contour_gpu.js) without the contours leaving it; the kernel
|
|
5
|
+
// walks the union's pieces and finishes the slice (INJECT). Taken when the kernel and the GPU module both
|
|
6
|
+
// have it.
|
|
7
|
+
// capture the kernel cuts and chains every layer itself (CAPTURE), the GPU unions them, the kernel finishes (INJECT).
|
|
8
|
+
//
|
|
9
|
+
// The slice never depends on the GPU: a kernel without the contour phase, a slice that does not reach PASS1 (a
|
|
10
|
+
// multi-material or cached one), a device that refuses the input, a pipeline that stops itself — each ends in the
|
|
11
|
+
// kernel's own slice, and the reason is reported. A layer the GPU left open (a chain that does not close, a union
|
|
12
|
+
// whose pieces do not close) gets the kernel's own union inside the GPU slice (contour_phase::assemble) and is counted;
|
|
13
|
+
// on the mesh route the kernel first cuts and chains that layer from the mesh it kept.
|
|
14
|
+
const PHASE_OFF = 0, PHASE_CAPTURE = 1, PHASE_MESH = 3
|
|
15
|
+
|
|
16
|
+
/** Whether this kernel build can take contours from outside. */
|
|
17
|
+
export const kernelHasContourPhase = (kernel) => typeof kernel?.contour_phase_mode === 'function'
|
|
18
|
+
/** Whether this kernel build can hand its mesh to the GPU's cut and chain (the mesh route). */
|
|
19
|
+
export const kernelHasMeshPhase = (kernel) => typeof kernel?.contour_mesh === 'function'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {object} job
|
|
23
|
+
* @param {object} job.kernel the loaded kernel module
|
|
24
|
+
* @param {object} job.contourGpu makeContourGpu()'s result (`front` present: the mesh route)
|
|
25
|
+
* @param {Uint8Array} job.stl
|
|
26
|
+
* @param {string} job.paramsText
|
|
27
|
+
* @param {(done: number, total: number) => void} job.onProgress
|
|
28
|
+
* @param {() => boolean} [job.canceled] read between the kernel calls (the last call clears the cancel flag)
|
|
29
|
+
* @param {boolean} [job.meshRoute] false keeps the capture route even where the mesh route is available
|
|
30
|
+
* @returns {Promise<object>} the kernel's result; `stats.contour_engine` is 'gpu' or 'cpu', with
|
|
31
|
+
* `contour_engine_reason` when the GPU was asked for and not used, or 'reused' when the layers came from the stage
|
|
32
|
+
* cache; `stats.contour_front` says which route cut and chained the layers ('gpu' on the mesh route, 'cpu' on capture)
|
|
33
|
+
*/
|
|
34
|
+
export async function sliceWithContourGpu(job) {
|
|
35
|
+
if (job.meshRoute !== false && job.contourGpu.front && kernelHasMeshPhase(job.kernel)) return sliceThroughMesh(job)
|
|
36
|
+
return sliceThroughCapture(job)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// the kernel's own slice, with the reason the GPU was not used
|
|
40
|
+
const kernelSlice = ({ kernel, stl, paramsText, onProgress }, reason) => {
|
|
41
|
+
kernel.contour_phase_mode(PHASE_OFF)
|
|
42
|
+
const result = kernel.slice(stl, paramsText, onProgress)
|
|
43
|
+
if (result?.stats) { result.stats.contour_engine = 'cpu'; result.stats.contour_engine_reason = reason }
|
|
44
|
+
return result
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// a first call that did not stop at PASS1: the slice took a route without its union, or failed or was canceled, and
|
|
48
|
+
// what it returned is the answer
|
|
49
|
+
const notStopped = ({ kernel, paramsText }, result) => {
|
|
50
|
+
kernel.contour_phase_mode(PHASE_OFF)
|
|
51
|
+
if (result?.stats) {
|
|
52
|
+
// reuse_stages 2 is the kernel's stage cache: the layers, contours included, are the previous slice's
|
|
53
|
+
result.stats.contour_engine = 'cpu'
|
|
54
|
+
result.stats.contour_engine_reason = 'this slice has its own contour path'
|
|
55
|
+
if (Number(JSON.parse(paramsText).reuse_stages) >= 2) { result.stats.contour_engine = 'reused'; delete result.stats.contour_engine_reason }
|
|
56
|
+
}
|
|
57
|
+
return result
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// the first kernel call, in `phase`; it stops at PASS1 when the slice reaches it
|
|
61
|
+
const firstCall = ({ kernel, stl, paramsText, onProgress }, phase) => {
|
|
62
|
+
kernel.contour_phase_mode(phase)
|
|
63
|
+
try { return kernel.slice(stl, paramsText, onProgress) }
|
|
64
|
+
catch (error) { kernel.contour_phase_mode(PHASE_OFF); throw error }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// the last kernel call, on the contours assemble() left; the stats say how they were made
|
|
68
|
+
const finish = ({ kernel, stl, paramsText, onProgress }, stats) => {
|
|
69
|
+
let result
|
|
70
|
+
try { result = kernel.slice(stl, paramsText, onProgress) }
|
|
71
|
+
finally { kernel.contour_phase_mode(PHASE_OFF) }
|
|
72
|
+
if (result?.stats) Object.assign(result.stats, { contour_engine: 'gpu' }, stats)
|
|
73
|
+
return result
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function sliceThroughMesh(job) {
|
|
77
|
+
const { kernel, contourGpu, canceled = () => false } = job
|
|
78
|
+
const meshStarted = performance.now()
|
|
79
|
+
const meshed = firstCall(job, PHASE_MESH)
|
|
80
|
+
if (!meshed?.captured) return notStopped(job, meshed)
|
|
81
|
+
const meshMs = performance.now() - meshStarted
|
|
82
|
+
|
|
83
|
+
const gpuStarted = performance.now()
|
|
84
|
+
let front
|
|
85
|
+
try {
|
|
86
|
+
const mesh = kernel.contour_mesh() // views into the kernel's heap: read before the kernel allocates again
|
|
87
|
+
front = await contourGpu.front.cut(mesh.tris, mesh.planes)
|
|
88
|
+
} catch (error) { front = { failure: String(error?.message ?? error) } }
|
|
89
|
+
if (canceled()) { kernel.contour_phase_mode(PHASE_OFF); return { error: 'canceled' } }
|
|
90
|
+
if (front.failure) return kernelSlice(job, front.failure)
|
|
91
|
+
kernel.contour_origin_buffer(front.input.layerCount).set(front.origins)
|
|
92
|
+
kernel.contour_open_layers().set(front.openLayers)
|
|
93
|
+
let outcome
|
|
94
|
+
try { outcome = await contourGpu.union(front.input, (pieceCount) => kernel.contour_result_buffer(pieceCount)) }
|
|
95
|
+
catch (error) { outcome = { ok: false, reason: String(error?.message ?? error) } }
|
|
96
|
+
if (canceled()) { kernel.contour_phase_mode(PHASE_OFF); return { error: 'canceled' } }
|
|
97
|
+
if (!outcome.ok) return kernelSlice(job, outcome.reason)
|
|
98
|
+
const assembled = kernel.contour_assemble() // from here the kernel reads the contours on its next slice
|
|
99
|
+
if (assembled.unavailable) return kernelSlice(job, 'a layer the GPU left open has no mesh to be rebuilt from')
|
|
100
|
+
const gpuMs = performance.now() - gpuStarted
|
|
101
|
+
|
|
102
|
+
let openLayers = 0
|
|
103
|
+
for (const flag of front.openLayers) openLayers += flag
|
|
104
|
+
return finish(job, {
|
|
105
|
+
contour_front: 'gpu', contour_capture_ms: meshMs, contour_front_ms: front.ms, contour_gpu_ms: gpuMs,
|
|
106
|
+
contour_fallback_layers: assembled.fallbackLayers, contour_open_layers: openLayers,
|
|
107
|
+
contour_pieces: outcome.pieceCount, contour_crossings: outcome.eventCount,
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function sliceThroughCapture(job) {
|
|
112
|
+
const { kernel, contourGpu, canceled = () => false } = job
|
|
113
|
+
const captureStarted = performance.now()
|
|
114
|
+
const captured = firstCall(job, PHASE_CAPTURE)
|
|
115
|
+
if (!captured?.captured) return notStopped(job, captured)
|
|
116
|
+
const captureMs = performance.now() - captureStarted
|
|
117
|
+
|
|
118
|
+
const gpuStarted = performance.now()
|
|
119
|
+
let outcome
|
|
120
|
+
try { outcome = await contourGpu.union(kernel.contour_gpu_input(), (pieceCount) => kernel.contour_result_buffer(pieceCount)) }
|
|
121
|
+
catch (error) { outcome = { ok: false, reason: String(error?.message ?? error) } }
|
|
122
|
+
if (canceled()) { kernel.contour_phase_mode(PHASE_OFF); return { error: 'canceled' } }
|
|
123
|
+
if (!outcome.ok) return kernelSlice(job, outcome.reason)
|
|
124
|
+
const assembled = kernel.contour_assemble() // from here the kernel reads the contours on its next slice
|
|
125
|
+
const gpuMs = performance.now() - gpuStarted
|
|
126
|
+
|
|
127
|
+
return finish(job, {
|
|
128
|
+
contour_front: 'cpu', contour_capture_ms: captureMs, contour_gpu_ms: gpuMs,
|
|
129
|
+
contour_fallback_layers: assembled.fallbackLayers, contour_pieces: outcome.pieceCount, contour_crossings: outcome.eventCount,
|
|
130
|
+
})
|
|
131
|
+
}
|