@plasius/gpu-worker 0.1.1 → 0.1.3
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 +57 -4
- package/README.md +74 -10
- package/dist/index.cjs +490 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +478 -7
- package/dist/index.js.map +1 -1
- package/dist/worker.wgsl +24 -252
- package/package.json +9 -4
- package/src/index.js +532 -9
- package/src/worker.wgsl +24 -252
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
// src/index.js
|
|
9
|
-
import { loadQueueWgsl } from "@plasius/gpu-lock-free-queue";
|
|
9
|
+
import { loadQueueWgsl as loadQueueWgslRaw } from "@plasius/gpu-lock-free-queue";
|
|
10
10
|
var workerWgslUrl = (() => {
|
|
11
11
|
if (typeof import.meta.url !== "undefined") {
|
|
12
12
|
return new URL("./worker.wgsl", import.meta.url);
|
|
@@ -18,20 +18,491 @@ var workerWgslUrl = (() => {
|
|
|
18
18
|
const base = typeof process !== "undefined" && process.cwd ? `file://${process.cwd()}/` : "file:///";
|
|
19
19
|
return new URL("./worker.wgsl", base);
|
|
20
20
|
})();
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
var jobRegistry = [];
|
|
22
|
+
var nextJobType = 0;
|
|
23
|
+
async function loadWgslSource(options = {}) {
|
|
24
|
+
const { wgsl, url, fetcher = globalThis.fetch, baseUrl } = options ?? {};
|
|
25
|
+
if (typeof wgsl === "string") {
|
|
26
|
+
assertNotHtmlWgsl(wgsl, "inline WGSL");
|
|
27
|
+
return wgsl;
|
|
28
|
+
}
|
|
29
|
+
if (!url) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const resolved = url instanceof URL ? url : new URL(url, baseUrl);
|
|
33
|
+
if (!fetcher) {
|
|
34
|
+
if (resolved.protocol !== "file:") {
|
|
35
|
+
throw new Error("No fetcher available for non-file WGSL URL.");
|
|
36
|
+
}
|
|
37
|
+
const { readFile } = await import("fs/promises");
|
|
38
|
+
const { fileURLToPath } = await import("url");
|
|
39
|
+
const source2 = await readFile(fileURLToPath(resolved), "utf8");
|
|
40
|
+
assertNotHtmlWgsl(source2, resolved.href);
|
|
41
|
+
return source2;
|
|
42
|
+
}
|
|
43
|
+
const response = await fetcher(resolved);
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const status = "status" in response ? response.status : "unknown";
|
|
46
|
+
const statusText = "statusText" in response ? response.statusText : "";
|
|
47
|
+
const detail = statusText ? `${status} ${statusText}` : `${status}`;
|
|
48
|
+
throw new Error(`Failed to load WGSL (${detail})`);
|
|
49
|
+
}
|
|
50
|
+
const source = await response.text();
|
|
51
|
+
assertNotHtmlWgsl(source, resolved.href);
|
|
52
|
+
return source;
|
|
53
|
+
}
|
|
54
|
+
function stripComments(source) {
|
|
55
|
+
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
|
56
|
+
}
|
|
57
|
+
function tokenize(source) {
|
|
58
|
+
return source.match(/[A-Za-z_][A-Za-z0-9_]*|[{}();<>,:=]/g) ?? [];
|
|
59
|
+
}
|
|
60
|
+
function isIdentifier(token) {
|
|
61
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(token);
|
|
62
|
+
}
|
|
63
|
+
function readNameAfterType(tokens, startIndex) {
|
|
64
|
+
let i = startIndex;
|
|
65
|
+
if (tokens[i] === "<") {
|
|
66
|
+
let depth = 1;
|
|
67
|
+
i += 1;
|
|
68
|
+
while (i < tokens.length && depth > 0) {
|
|
69
|
+
if (tokens[i] === "<") {
|
|
70
|
+
depth += 1;
|
|
71
|
+
} else if (tokens[i] === ">") {
|
|
72
|
+
depth -= 1;
|
|
73
|
+
}
|
|
74
|
+
i += 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return tokens[i];
|
|
78
|
+
}
|
|
79
|
+
function scanModuleNames(source) {
|
|
80
|
+
const cleaned = stripComments(source);
|
|
81
|
+
const tokens = tokenize(cleaned);
|
|
82
|
+
const names = [];
|
|
83
|
+
let depth = 0;
|
|
84
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
85
|
+
const token = tokens[i];
|
|
86
|
+
if (token === "{") {
|
|
87
|
+
depth += 1;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (token === "}") {
|
|
91
|
+
depth = Math.max(0, depth - 1);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (depth !== 0) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (token === "fn") {
|
|
98
|
+
const name = tokens[i + 1];
|
|
99
|
+
if (isIdentifier(name)) {
|
|
100
|
+
names.push({ kind: "fn", name });
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (token === "struct") {
|
|
105
|
+
const name = tokens[i + 1];
|
|
106
|
+
if (isIdentifier(name)) {
|
|
107
|
+
names.push({ kind: "struct", name });
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (token === "alias") {
|
|
112
|
+
const name = tokens[i + 1];
|
|
113
|
+
if (isIdentifier(name)) {
|
|
114
|
+
names.push({ kind: "alias", name });
|
|
115
|
+
}
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (token === "var" || token === "let" || token === "const" || token === "override") {
|
|
119
|
+
const name = readNameAfterType(tokens, i + 1);
|
|
120
|
+
if (isIdentifier(name)) {
|
|
121
|
+
names.push({ kind: token, name });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return names;
|
|
126
|
+
}
|
|
127
|
+
function buildNameIndex(modules) {
|
|
128
|
+
const index = /* @__PURE__ */ new Map();
|
|
129
|
+
for (const module of modules) {
|
|
130
|
+
for (const item of scanModuleNames(module.source)) {
|
|
131
|
+
const bucket = index.get(item.name) ?? [];
|
|
132
|
+
bucket.push({ kind: item.kind, module: module.name });
|
|
133
|
+
index.set(item.name, bucket);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return index;
|
|
137
|
+
}
|
|
138
|
+
function assertNoNameClashes(modules) {
|
|
139
|
+
const index = buildNameIndex(modules);
|
|
140
|
+
const clashes = [];
|
|
141
|
+
for (const [name, entries] of index.entries()) {
|
|
142
|
+
if (entries.length > 1) {
|
|
143
|
+
clashes.push({ name, entries });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (clashes.length === 0) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const lines = ["WGSL debug: identifier clashes detected:"];
|
|
150
|
+
for (const clash of clashes) {
|
|
151
|
+
const locations = clash.entries.map((entry) => `${entry.module} (${entry.kind})`).join(", ");
|
|
152
|
+
lines.push(`- ${clash.name}: ${locations}`);
|
|
153
|
+
}
|
|
154
|
+
throw new Error(lines.join("\n"));
|
|
155
|
+
}
|
|
156
|
+
function assertNotHtmlWgsl(source, context) {
|
|
157
|
+
const sample = source.slice(0, 200).toLowerCase();
|
|
158
|
+
if (sample.includes("<!doctype") || sample.includes("<html") || sample.includes("<meta")) {
|
|
159
|
+
const label = context ? ` for ${context}` : "";
|
|
160
|
+
throw new Error(
|
|
161
|
+
`Expected WGSL${label} but received HTML. Check the URL or server root.`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function renameProcessJob(source, name) {
|
|
166
|
+
return source.replace(/\bprocess_job\b/g, name);
|
|
167
|
+
}
|
|
168
|
+
function getQueueCompatMap(source) {
|
|
169
|
+
if (!/\bJobMeta\b/.test(source)) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
return [{ from: /\bJobMeta\b/g, to: "JobDesc" }];
|
|
173
|
+
}
|
|
174
|
+
function applyCompatMap(source, map) {
|
|
175
|
+
if (!map || map.length === 0) {
|
|
176
|
+
return source;
|
|
177
|
+
}
|
|
178
|
+
let next = source;
|
|
179
|
+
for (const entry of map) {
|
|
180
|
+
next = next.replace(entry.from, entry.to);
|
|
181
|
+
}
|
|
182
|
+
return next;
|
|
183
|
+
}
|
|
184
|
+
function normalizeJobs(jobs) {
|
|
185
|
+
const normalized = jobs.map((job, index) => {
|
|
186
|
+
if (typeof job === "string") {
|
|
187
|
+
return {
|
|
188
|
+
jobType: index,
|
|
189
|
+
wgsl: job,
|
|
190
|
+
label: `job_${index}`,
|
|
191
|
+
sourceName: `job-${index}`
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
if (!job || typeof job.wgsl !== "string") {
|
|
195
|
+
throw new Error("Job entries must provide WGSL source strings.");
|
|
196
|
+
}
|
|
197
|
+
const jobType = job.jobType ?? index;
|
|
198
|
+
const label = job.label ?? `job_${jobType}`;
|
|
199
|
+
return {
|
|
200
|
+
jobType,
|
|
201
|
+
wgsl: job.wgsl,
|
|
202
|
+
label,
|
|
203
|
+
sourceName: job.sourceName ?? job.label ?? `job-${jobType}`
|
|
204
|
+
};
|
|
205
|
+
});
|
|
206
|
+
const seen = /* @__PURE__ */ new Set();
|
|
207
|
+
for (const job of normalized) {
|
|
208
|
+
if (seen.has(job.jobType)) {
|
|
209
|
+
throw new Error(`Duplicate job_type detected: ${job.jobType}`);
|
|
210
|
+
}
|
|
211
|
+
seen.add(job.jobType);
|
|
212
|
+
}
|
|
213
|
+
return normalized;
|
|
214
|
+
}
|
|
215
|
+
function buildProcessJobDispatch(jobs) {
|
|
216
|
+
const lines = [
|
|
217
|
+
"fn process_job(job_index: u32, job_type: u32, payload_words: u32) {"
|
|
218
|
+
];
|
|
219
|
+
if (jobs.length === 0) {
|
|
220
|
+
lines.push(" return;");
|
|
221
|
+
lines.push("}");
|
|
222
|
+
return lines.join("\n");
|
|
223
|
+
}
|
|
224
|
+
jobs.forEach((job, idx) => {
|
|
225
|
+
const clause = idx === 0 ? "if" : "else if";
|
|
226
|
+
lines.push(` ${clause} (job_type == ${job.jobType}u) {`);
|
|
227
|
+
lines.push(
|
|
228
|
+
` ${job.entryName}(job_index, job_type, payload_words);`
|
|
229
|
+
);
|
|
230
|
+
lines.push(" }");
|
|
231
|
+
});
|
|
232
|
+
lines.push("}");
|
|
233
|
+
return lines.join("\n");
|
|
234
|
+
}
|
|
235
|
+
async function loadWorkerWgsl(options = {}) {
|
|
236
|
+
const { url = workerWgslUrl, fetcher } = options ?? {};
|
|
237
|
+
const source = await loadWgslSource({
|
|
238
|
+
url,
|
|
239
|
+
fetcher,
|
|
240
|
+
baseUrl: workerWgslUrl
|
|
241
|
+
});
|
|
242
|
+
if (typeof source !== "string") {
|
|
243
|
+
throw new Error("Failed to load worker WGSL source.");
|
|
244
|
+
}
|
|
245
|
+
return source;
|
|
246
|
+
}
|
|
247
|
+
async function loadQueueWgsl(options = {}) {
|
|
248
|
+
const { queueCompat = true, ...rest } = options ?? {};
|
|
249
|
+
const source = await loadQueueWgslRaw(rest);
|
|
250
|
+
if (typeof source !== "string") {
|
|
251
|
+
throw new Error("Failed to load queue WGSL source.");
|
|
252
|
+
}
|
|
253
|
+
assertNotHtmlWgsl(source, rest?.url ? String(rest.url) : "queue WGSL");
|
|
254
|
+
if (!queueCompat) {
|
|
255
|
+
return source;
|
|
256
|
+
}
|
|
257
|
+
const compatMap = getQueueCompatMap(source);
|
|
258
|
+
return applyCompatMap(source, compatMap);
|
|
259
|
+
}
|
|
260
|
+
async function loadJobWgsl(options = {}) {
|
|
261
|
+
const { wgsl, url, fetcher, label } = options ?? {};
|
|
262
|
+
const source = await loadWgslSource({
|
|
263
|
+
wgsl,
|
|
264
|
+
url,
|
|
265
|
+
fetcher,
|
|
266
|
+
baseUrl: workerWgslUrl
|
|
267
|
+
});
|
|
268
|
+
if (typeof source !== "string") {
|
|
269
|
+
throw new Error("loadJobWgsl requires a WGSL string or URL.");
|
|
270
|
+
}
|
|
271
|
+
const jobType = nextJobType;
|
|
272
|
+
nextJobType += 1;
|
|
273
|
+
jobRegistry.push({
|
|
274
|
+
jobType,
|
|
275
|
+
wgsl: source,
|
|
276
|
+
label: label ?? `job_${jobType}`,
|
|
277
|
+
sourceName: label ?? `job-${jobType}`
|
|
278
|
+
});
|
|
279
|
+
return jobType;
|
|
24
280
|
}
|
|
25
281
|
async function assembleWorkerWgsl(workerWgsl, options = {}) {
|
|
26
|
-
const {
|
|
27
|
-
|
|
28
|
-
|
|
282
|
+
const {
|
|
283
|
+
queueWgsl,
|
|
284
|
+
queueUrl,
|
|
285
|
+
preludeWgsl,
|
|
286
|
+
preludeUrl,
|
|
287
|
+
fetcher,
|
|
288
|
+
jobs,
|
|
289
|
+
debug,
|
|
290
|
+
queueCompat = true
|
|
291
|
+
} = options ?? {};
|
|
292
|
+
const rawQueueSource = queueWgsl ?? await loadQueueWgslRaw({ url: queueUrl, fetcher });
|
|
293
|
+
const bodyRaw = workerWgsl ?? await loadWorkerWgsl({ fetcher });
|
|
294
|
+
const compatMap = queueCompat ? getQueueCompatMap(rawQueueSource) : null;
|
|
295
|
+
const queueSource = applyCompatMap(rawQueueSource, compatMap);
|
|
296
|
+
const preludeRaw = preludeWgsl ?? (preludeUrl ? await loadWgslSource({ url: preludeUrl, fetcher, baseUrl: workerWgslUrl }) : "");
|
|
297
|
+
if ((preludeWgsl || preludeUrl) && typeof preludeRaw !== "string") {
|
|
298
|
+
throw new Error("Failed to load prelude WGSL source.");
|
|
299
|
+
}
|
|
300
|
+
const preludeSource = typeof preludeRaw === "string" && preludeRaw.length > 0 ? applyCompatMap(preludeRaw, compatMap) : "";
|
|
301
|
+
const body = applyCompatMap(bodyRaw, compatMap);
|
|
302
|
+
const jobList = normalizeJobs(
|
|
303
|
+
typeof jobs === "undefined" ? jobRegistry : jobs
|
|
304
|
+
);
|
|
305
|
+
if (!jobList || jobList.length === 0) {
|
|
306
|
+
return `${queueSource}
|
|
307
|
+
|
|
308
|
+
${body}`;
|
|
309
|
+
}
|
|
310
|
+
const rewrittenJobs = jobList.map((job) => {
|
|
311
|
+
const source = applyCompatMap(job.wgsl, compatMap);
|
|
312
|
+
const hasProcessJob = /\bfn\s+process_job\b/.test(source);
|
|
313
|
+
if (!hasProcessJob) {
|
|
314
|
+
throw new Error(
|
|
315
|
+
`Job ${job.sourceName} is missing a process_job() entry function.`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
const entryName = `process_job__${job.jobType}`;
|
|
319
|
+
const renamed = renameProcessJob(source, entryName);
|
|
320
|
+
return { ...job, entryName, wgsl: renamed };
|
|
321
|
+
});
|
|
322
|
+
const dispatch = buildProcessJobDispatch(rewrittenJobs);
|
|
323
|
+
const modulesForDebug = debug ? [
|
|
324
|
+
{ name: "queue.wgsl", source: queueSource },
|
|
325
|
+
...preludeSource ? [{ name: "jobs.prelude.wgsl", source: preludeSource }] : [],
|
|
326
|
+
...rewrittenJobs.map((job) => ({
|
|
327
|
+
name: job.sourceName,
|
|
328
|
+
source: job.wgsl
|
|
329
|
+
})),
|
|
330
|
+
{ name: "jobs.dispatch.wgsl", source: dispatch },
|
|
331
|
+
{ name: "worker.wgsl", source: body }
|
|
332
|
+
] : null;
|
|
333
|
+
if (modulesForDebug) {
|
|
334
|
+
assertNoNameClashes(modulesForDebug);
|
|
335
|
+
}
|
|
336
|
+
const jobBlocks = rewrittenJobs.map((job) => `// Job ${job.jobType}: ${job.label}
|
|
337
|
+
${job.wgsl}`).join("\n\n");
|
|
338
|
+
const preludeBlock = preludeSource ? `${preludeSource}
|
|
339
|
+
|
|
340
|
+
` : "";
|
|
29
341
|
return `${queueSource}
|
|
30
342
|
|
|
343
|
+
${preludeBlock}${jobBlocks}
|
|
344
|
+
|
|
345
|
+
${dispatch}
|
|
346
|
+
|
|
31
347
|
${body}`;
|
|
32
348
|
}
|
|
349
|
+
function normalizeWorkgroups(value, label) {
|
|
350
|
+
if (typeof value === "number") {
|
|
351
|
+
return [value, 1, 1];
|
|
352
|
+
}
|
|
353
|
+
if (Array.isArray(value)) {
|
|
354
|
+
const [x = 0, y = 1, z = 1] = value;
|
|
355
|
+
return [x, y, z];
|
|
356
|
+
}
|
|
357
|
+
throw new Error(`Invalid workgroup count for ${label}.`);
|
|
358
|
+
}
|
|
359
|
+
function resolveWorkgroups(value, label) {
|
|
360
|
+
if (typeof value === "function") {
|
|
361
|
+
return normalizeWorkgroups(value(), label);
|
|
362
|
+
}
|
|
363
|
+
if (value == null) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
return normalizeWorkgroups(value, label);
|
|
367
|
+
}
|
|
368
|
+
function setBindGroups(pass, bindGroups) {
|
|
369
|
+
if (!bindGroups) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
bindGroups.forEach((group, index) => {
|
|
373
|
+
if (group) {
|
|
374
|
+
pass.setBindGroup(index, group);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
function computeWorkerWorkgroups(maxJobs, workgroupSize) {
|
|
379
|
+
const jobs = typeof maxJobs === "function" ? Number(maxJobs()) : Number(maxJobs);
|
|
380
|
+
if (!Number.isFinite(jobs) || jobs <= 0) {
|
|
381
|
+
throw new Error("maxJobsPerDispatch must be a positive number.");
|
|
382
|
+
}
|
|
383
|
+
const size = Number(workgroupSize);
|
|
384
|
+
if (!Number.isFinite(size) || size <= 0) {
|
|
385
|
+
throw new Error("workgroupSize must be a positive number.");
|
|
386
|
+
}
|
|
387
|
+
return Math.max(1, Math.ceil(jobs / size));
|
|
388
|
+
}
|
|
389
|
+
function createWorkerLoop(options = {}) {
|
|
390
|
+
const {
|
|
391
|
+
device,
|
|
392
|
+
worker,
|
|
393
|
+
jobs = [],
|
|
394
|
+
workgroupSize = 64,
|
|
395
|
+
maxJobsPerDispatch,
|
|
396
|
+
rateHz,
|
|
397
|
+
label,
|
|
398
|
+
onTick,
|
|
399
|
+
onError
|
|
400
|
+
} = options ?? {};
|
|
401
|
+
if (!device) {
|
|
402
|
+
throw new Error("createWorkerLoop requires a GPUDevice.");
|
|
403
|
+
}
|
|
404
|
+
if (!worker || !worker.pipeline) {
|
|
405
|
+
throw new Error("createWorkerLoop requires a worker pipeline.");
|
|
406
|
+
}
|
|
407
|
+
let running = false;
|
|
408
|
+
let handle = null;
|
|
409
|
+
let usingRaf = false;
|
|
410
|
+
const intervalMs = Number.isFinite(rateHz) && rateHz > 0 ? 1e3 / rateHz : null;
|
|
411
|
+
const tick = () => {
|
|
412
|
+
try {
|
|
413
|
+
const encoder = device.createCommandEncoder();
|
|
414
|
+
const pass = encoder.beginComputePass(
|
|
415
|
+
label ? { label } : void 0
|
|
416
|
+
);
|
|
417
|
+
pass.setPipeline(worker.pipeline);
|
|
418
|
+
setBindGroups(pass, worker.bindGroups);
|
|
419
|
+
const explicitWorkerGroups = resolveWorkgroups(worker.workgroups, "worker") ?? resolveWorkgroups(worker.workgroupCount, "worker") ?? resolveWorkgroups(worker.dispatch, "worker");
|
|
420
|
+
const workerGroups = explicitWorkerGroups ? explicitWorkerGroups : [computeWorkerWorkgroups(maxJobsPerDispatch, workgroupSize), 1, 1];
|
|
421
|
+
if (workerGroups[0] > 0) {
|
|
422
|
+
pass.dispatchWorkgroups(...workerGroups);
|
|
423
|
+
}
|
|
424
|
+
jobs.forEach((job, index) => {
|
|
425
|
+
if (!job || !job.pipeline) {
|
|
426
|
+
throw new Error(`Job pipeline missing at index ${index}.`);
|
|
427
|
+
}
|
|
428
|
+
pass.setPipeline(job.pipeline);
|
|
429
|
+
setBindGroups(pass, job.bindGroups);
|
|
430
|
+
const groups = resolveWorkgroups(
|
|
431
|
+
job.workgroups ?? job.workgroupCount ?? job.dispatch,
|
|
432
|
+
`job ${index}`
|
|
433
|
+
);
|
|
434
|
+
if (!groups) {
|
|
435
|
+
throw new Error(`Job ${index} requires a workgroup count.`);
|
|
436
|
+
}
|
|
437
|
+
if (groups[0] > 0) {
|
|
438
|
+
pass.dispatchWorkgroups(...groups);
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
pass.end();
|
|
442
|
+
device.queue.submit([encoder.finish()]);
|
|
443
|
+
if (onTick) {
|
|
444
|
+
onTick();
|
|
445
|
+
}
|
|
446
|
+
} catch (err) {
|
|
447
|
+
if (onError) {
|
|
448
|
+
onError(err);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
throw err;
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
const scheduleNext = () => {
|
|
455
|
+
if (!running) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (intervalMs != null) {
|
|
459
|
+
tick();
|
|
460
|
+
usingRaf = false;
|
|
461
|
+
handle = setTimeout(scheduleNext, intervalMs);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
tick();
|
|
465
|
+
if (typeof requestAnimationFrame === "function") {
|
|
466
|
+
usingRaf = true;
|
|
467
|
+
handle = requestAnimationFrame(scheduleNext);
|
|
468
|
+
} else {
|
|
469
|
+
usingRaf = false;
|
|
470
|
+
handle = setTimeout(scheduleNext, 0);
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
const start = () => {
|
|
474
|
+
if (running) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
running = true;
|
|
478
|
+
scheduleNext();
|
|
479
|
+
};
|
|
480
|
+
const stop = () => {
|
|
481
|
+
running = false;
|
|
482
|
+
if (handle == null) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (usingRaf && typeof cancelAnimationFrame === "function") {
|
|
486
|
+
cancelAnimationFrame(handle);
|
|
487
|
+
} else {
|
|
488
|
+
clearTimeout(handle);
|
|
489
|
+
}
|
|
490
|
+
handle = null;
|
|
491
|
+
};
|
|
492
|
+
return {
|
|
493
|
+
start,
|
|
494
|
+
stop,
|
|
495
|
+
tick,
|
|
496
|
+
get running() {
|
|
497
|
+
return running;
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
}
|
|
33
501
|
export {
|
|
34
502
|
assembleWorkerWgsl,
|
|
503
|
+
createWorkerLoop,
|
|
504
|
+
loadJobWgsl,
|
|
505
|
+
loadQueueWgsl,
|
|
35
506
|
loadWorkerWgsl,
|
|
36
507
|
workerWgslUrl
|
|
37
508
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.js"],"sourcesContent":["import { loadQueueWgsl } from \"@plasius/gpu-lock-free-queue\";\n\nexport const workerWgslUrl = (() => {\n if (typeof __IMPORT_META_URL__ !== \"undefined\") {\n return new URL(\"./worker.wgsl\", __IMPORT_META_URL__);\n }\n if (typeof __filename !== \"undefined\" && typeof require !== \"undefined\") {\n const { pathToFileURL } = require(\"node:url\");\n return new URL(\"./worker.wgsl\", pathToFileURL(__filename));\n }\n const base =\n typeof process !== \"undefined\" && process.cwd\n ? `file://${process.cwd()}/`\n : \"file:///\";\n return new URL(\"./worker.wgsl\", base);\n})();\n\nexport async function loadWorkerWgsl() {\n const response = await fetch(workerWgslUrl);\n return response.text();\n}\n\nexport async function assembleWorkerWgsl(workerWgsl, options = {}) {\n const { queueWgsl, queueUrl, fetcher } = options ?? {};\n const queueSource =\n queueWgsl ?? (await loadQueueWgsl({ url: queueUrl, fetcher }));\n const body = workerWgsl ?? (await loadWorkerWgsl());\n return `${queueSource}\\n\\n${body}`;\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,qBAAqB;AAEvB,IAAM,iBAAiB,MAAM;AAClC,MAAI,OAAO,oBAAwB,aAAa;AAC9C,WAAO,IAAI,IAAI,iBAAiB,eAAmB;AAAA,EACrD;AACA,MAAI,OAAO,eAAe,eAAe,OAAO,cAAY,aAAa;AACvE,UAAM,EAAE,cAAc,IAAI,UAAQ,KAAU;AAC5C,WAAO,IAAI,IAAI,iBAAiB,cAAc,UAAU,CAAC;AAAA,EAC3D;AACA,QAAM,OACJ,OAAO,YAAY,eAAe,QAAQ,MACtC,UAAU,QAAQ,IAAI,CAAC,MACvB;AACN,SAAO,IAAI,IAAI,iBAAiB,IAAI;AACtC,GAAG;AAEH,eAAsB,iBAAiB;AACrC,QAAM,WAAW,MAAM,MAAM,aAAa;AAC1C,SAAO,SAAS,KAAK;AACvB;AAEA,eAAsB,mBAAmB,YAAY,UAAU,CAAC,GAAG;AACjE,QAAM,EAAE,WAAW,UAAU,QAAQ,IAAI,WAAW,CAAC;AACrD,QAAM,cACJ,aAAc,MAAM,cAAc,EAAE,KAAK,UAAU,QAAQ,CAAC;AAC9D,QAAM,OAAO,cAAe,MAAM,eAAe;AACjD,SAAO,GAAG,WAAW;AAAA;AAAA,EAAO,IAAI;AAClC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.js"],"sourcesContent":["import { loadQueueWgsl as loadQueueWgslRaw } from \"@plasius/gpu-lock-free-queue\";\n\nexport const workerWgslUrl = (() => {\n if (typeof __IMPORT_META_URL__ !== \"undefined\") {\n return new URL(\"./worker.wgsl\", __IMPORT_META_URL__);\n }\n if (typeof __filename !== \"undefined\" && typeof require !== \"undefined\") {\n const { pathToFileURL } = require(\"node:url\");\n return new URL(\"./worker.wgsl\", pathToFileURL(__filename));\n }\n const base =\n typeof process !== \"undefined\" && process.cwd\n ? `file://${process.cwd()}/`\n : \"file:///\";\n return new URL(\"./worker.wgsl\", base);\n})();\n\nconst jobRegistry = [];\nlet nextJobType = 0;\n\nasync function loadWgslSource(options = {}) {\n const { wgsl, url, fetcher = globalThis.fetch, baseUrl } = options ?? {};\n if (typeof wgsl === \"string\") {\n assertNotHtmlWgsl(wgsl, \"inline WGSL\");\n return wgsl;\n }\n if (!url) {\n return null;\n }\n const resolved = url instanceof URL ? url : new URL(url, baseUrl);\n if (!fetcher) {\n if (resolved.protocol !== \"file:\") {\n throw new Error(\"No fetcher available for non-file WGSL URL.\");\n }\n const { readFile } = await import(\"fs/promises\");\n const { fileURLToPath } = await import(\"url\");\n const source = await readFile(fileURLToPath(resolved), \"utf8\");\n assertNotHtmlWgsl(source, resolved.href);\n return source;\n }\n const response = await fetcher(resolved);\n if (!response.ok) {\n const status = \"status\" in response ? response.status : \"unknown\";\n const statusText = \"statusText\" in response ? response.statusText : \"\";\n const detail = statusText ? `${status} ${statusText}` : `${status}`;\n throw new Error(`Failed to load WGSL (${detail})`);\n }\n const source = await response.text();\n assertNotHtmlWgsl(source, resolved.href);\n return source;\n}\n\nfunction stripComments(source) {\n return source\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\")\n .replace(/\\/\\/.*$/gm, \"\");\n}\n\nfunction tokenize(source) {\n return source.match(/[A-Za-z_][A-Za-z0-9_]*|[{}();<>,:=]/g) ?? [];\n}\n\nfunction isIdentifier(token) {\n return /^[A-Za-z_][A-Za-z0-9_]*$/.test(token);\n}\n\nfunction readNameAfterType(tokens, startIndex) {\n let i = startIndex;\n if (tokens[i] === \"<\") {\n let depth = 1;\n i += 1;\n while (i < tokens.length && depth > 0) {\n if (tokens[i] === \"<\") {\n depth += 1;\n } else if (tokens[i] === \">\") {\n depth -= 1;\n }\n i += 1;\n }\n }\n return tokens[i];\n}\n\nfunction scanModuleNames(source) {\n const cleaned = stripComments(source);\n const tokens = tokenize(cleaned);\n const names = [];\n let depth = 0;\n for (let i = 0; i < tokens.length; i += 1) {\n const token = tokens[i];\n if (token === \"{\") {\n depth += 1;\n continue;\n }\n if (token === \"}\") {\n depth = Math.max(0, depth - 1);\n continue;\n }\n if (depth !== 0) {\n continue;\n }\n if (token === \"fn\") {\n const name = tokens[i + 1];\n if (isIdentifier(name)) {\n names.push({ kind: \"fn\", name });\n }\n continue;\n }\n if (token === \"struct\") {\n const name = tokens[i + 1];\n if (isIdentifier(name)) {\n names.push({ kind: \"struct\", name });\n }\n continue;\n }\n if (token === \"alias\") {\n const name = tokens[i + 1];\n if (isIdentifier(name)) {\n names.push({ kind: \"alias\", name });\n }\n continue;\n }\n if (token === \"var\" || token === \"let\" || token === \"const\" || token === \"override\") {\n const name = readNameAfterType(tokens, i + 1);\n if (isIdentifier(name)) {\n names.push({ kind: token, name });\n }\n }\n }\n return names;\n}\n\nfunction buildNameIndex(modules) {\n const index = new Map();\n for (const module of modules) {\n for (const item of scanModuleNames(module.source)) {\n const bucket = index.get(item.name) ?? [];\n bucket.push({ kind: item.kind, module: module.name });\n index.set(item.name, bucket);\n }\n }\n return index;\n}\n\nfunction assertNoNameClashes(modules) {\n const index = buildNameIndex(modules);\n const clashes = [];\n for (const [name, entries] of index.entries()) {\n if (entries.length > 1) {\n clashes.push({ name, entries });\n }\n }\n if (clashes.length === 0) {\n return;\n }\n const lines = [\"WGSL debug: identifier clashes detected:\"];\n for (const clash of clashes) {\n const locations = clash.entries\n .map((entry) => `${entry.module} (${entry.kind})`)\n .join(\", \");\n lines.push(`- ${clash.name}: ${locations}`);\n }\n throw new Error(lines.join(\"\\n\"));\n}\n\nfunction assertNotHtmlWgsl(source, context) {\n const sample = source.slice(0, 200).toLowerCase();\n if (\n sample.includes(\"<!doctype\") ||\n sample.includes(\"<html\") ||\n sample.includes(\"<meta\")\n ) {\n const label = context ? ` for ${context}` : \"\";\n throw new Error(\n `Expected WGSL${label} but received HTML. Check the URL or server root.`\n );\n }\n}\n\nfunction renameProcessJob(source, name) {\n return source.replace(/\\bprocess_job\\b/g, name);\n}\n\nfunction getQueueCompatMap(source) {\n if (!/\\bJobMeta\\b/.test(source)) {\n return null;\n }\n return [{ from: /\\bJobMeta\\b/g, to: \"JobDesc\" }];\n}\n\nfunction applyCompatMap(source, map) {\n if (!map || map.length === 0) {\n return source;\n }\n let next = source;\n for (const entry of map) {\n next = next.replace(entry.from, entry.to);\n }\n return next;\n}\n\nfunction normalizeJobs(jobs) {\n const normalized = jobs.map((job, index) => {\n if (typeof job === \"string\") {\n return {\n jobType: index,\n wgsl: job,\n label: `job_${index}`,\n sourceName: `job-${index}`,\n };\n }\n if (!job || typeof job.wgsl !== \"string\") {\n throw new Error(\"Job entries must provide WGSL source strings.\");\n }\n const jobType = job.jobType ?? index;\n const label = job.label ?? `job_${jobType}`;\n return {\n jobType,\n wgsl: job.wgsl,\n label,\n sourceName: job.sourceName ?? job.label ?? `job-${jobType}`,\n };\n });\n const seen = new Set();\n for (const job of normalized) {\n if (seen.has(job.jobType)) {\n throw new Error(`Duplicate job_type detected: ${job.jobType}`);\n }\n seen.add(job.jobType);\n }\n return normalized;\n}\n\nfunction buildProcessJobDispatch(jobs) {\n const lines = [\n \"fn process_job(job_index: u32, job_type: u32, payload_words: u32) {\",\n ];\n if (jobs.length === 0) {\n lines.push(\" return;\");\n lines.push(\"}\");\n return lines.join(\"\\n\");\n }\n jobs.forEach((job, idx) => {\n const clause = idx === 0 ? \"if\" : \"else if\";\n lines.push(` ${clause} (job_type == ${job.jobType}u) {`);\n lines.push(\n ` ${job.entryName}(job_index, job_type, payload_words);`\n );\n lines.push(\" }\");\n });\n lines.push(\"}\");\n return lines.join(\"\\n\");\n}\n\nexport async function loadWorkerWgsl(options = {}) {\n const { url = workerWgslUrl, fetcher } = options ?? {};\n const source = await loadWgslSource({\n url,\n fetcher,\n baseUrl: workerWgslUrl,\n });\n if (typeof source !== \"string\") {\n throw new Error(\"Failed to load worker WGSL source.\");\n }\n return source;\n}\n\nexport async function loadQueueWgsl(options = {}) {\n const { queueCompat = true, ...rest } = options ?? {};\n const source = await loadQueueWgslRaw(rest);\n if (typeof source !== \"string\") {\n throw new Error(\"Failed to load queue WGSL source.\");\n }\n assertNotHtmlWgsl(source, rest?.url ? String(rest.url) : \"queue WGSL\");\n if (!queueCompat) {\n return source;\n }\n const compatMap = getQueueCompatMap(source);\n return applyCompatMap(source, compatMap);\n}\n\nexport async function loadJobWgsl(options = {}) {\n const { wgsl, url, fetcher, label } = options ?? {};\n const source = await loadWgslSource({\n wgsl,\n url,\n fetcher,\n baseUrl: workerWgslUrl,\n });\n if (typeof source !== \"string\") {\n throw new Error(\"loadJobWgsl requires a WGSL string or URL.\");\n }\n const jobType = nextJobType;\n nextJobType += 1;\n jobRegistry.push({\n jobType,\n wgsl: source,\n label: label ?? `job_${jobType}`,\n sourceName: label ?? `job-${jobType}`,\n });\n return jobType;\n}\n\nexport async function assembleWorkerWgsl(workerWgsl, options = {}) {\n const {\n queueWgsl,\n queueUrl,\n preludeWgsl,\n preludeUrl,\n fetcher,\n jobs,\n debug,\n queueCompat = true,\n } = options ?? {};\n const rawQueueSource =\n queueWgsl ?? (await loadQueueWgslRaw({ url: queueUrl, fetcher }));\n const bodyRaw = workerWgsl ?? (await loadWorkerWgsl({ fetcher }));\n const compatMap = queueCompat ? getQueueCompatMap(rawQueueSource) : null;\n const queueSource = applyCompatMap(rawQueueSource, compatMap);\n const preludeRaw =\n preludeWgsl ??\n (preludeUrl\n ? await loadWgslSource({ url: preludeUrl, fetcher, baseUrl: workerWgslUrl })\n : \"\");\n if ((preludeWgsl || preludeUrl) && typeof preludeRaw !== \"string\") {\n throw new Error(\"Failed to load prelude WGSL source.\");\n }\n const preludeSource =\n typeof preludeRaw === \"string\" && preludeRaw.length > 0\n ? applyCompatMap(preludeRaw, compatMap)\n : \"\";\n const body = applyCompatMap(bodyRaw, compatMap);\n const jobList = normalizeJobs(\n typeof jobs === \"undefined\" ? jobRegistry : jobs\n );\n if (!jobList || jobList.length === 0) {\n return `${queueSource}\\n\\n${body}`;\n }\n const rewrittenJobs = jobList.map((job) => {\n const source = applyCompatMap(job.wgsl, compatMap);\n const hasProcessJob = /\\bfn\\s+process_job\\b/.test(source);\n if (!hasProcessJob) {\n throw new Error(\n `Job ${job.sourceName} is missing a process_job() entry function.`\n );\n }\n const entryName = `process_job__${job.jobType}`;\n const renamed = renameProcessJob(source, entryName);\n return { ...job, entryName, wgsl: renamed };\n });\n const dispatch = buildProcessJobDispatch(rewrittenJobs);\n const modulesForDebug = debug\n ? [\n { name: \"queue.wgsl\", source: queueSource },\n ...(preludeSource\n ? [{ name: \"jobs.prelude.wgsl\", source: preludeSource }]\n : []),\n ...rewrittenJobs.map((job) => ({\n name: job.sourceName,\n source: job.wgsl,\n })),\n { name: \"jobs.dispatch.wgsl\", source: dispatch },\n { name: \"worker.wgsl\", source: body },\n ]\n : null;\n if (modulesForDebug) {\n assertNoNameClashes(modulesForDebug);\n }\n const jobBlocks = rewrittenJobs\n .map((job) => `// Job ${job.jobType}: ${job.label}\\n${job.wgsl}`)\n .join(\"\\n\\n\");\n const preludeBlock = preludeSource ? `${preludeSource}\\n\\n` : \"\";\n return `${queueSource}\\n\\n${preludeBlock}${jobBlocks}\\n\\n${dispatch}\\n\\n${body}`;\n}\n\nfunction normalizeWorkgroups(value, label) {\n if (typeof value === \"number\") {\n return [value, 1, 1];\n }\n if (Array.isArray(value)) {\n const [x = 0, y = 1, z = 1] = value;\n return [x, y, z];\n }\n throw new Error(`Invalid workgroup count for ${label}.`);\n}\n\nfunction resolveWorkgroups(value, label) {\n if (typeof value === \"function\") {\n return normalizeWorkgroups(value(), label);\n }\n if (value == null) {\n return null;\n }\n return normalizeWorkgroups(value, label);\n}\n\nfunction setBindGroups(pass, bindGroups) {\n if (!bindGroups) {\n return;\n }\n bindGroups.forEach((group, index) => {\n if (group) {\n pass.setBindGroup(index, group);\n }\n });\n}\n\nfunction computeWorkerWorkgroups(maxJobs, workgroupSize) {\n const jobs =\n typeof maxJobs === \"function\" ? Number(maxJobs()) : Number(maxJobs);\n if (!Number.isFinite(jobs) || jobs <= 0) {\n throw new Error(\"maxJobsPerDispatch must be a positive number.\");\n }\n const size = Number(workgroupSize);\n if (!Number.isFinite(size) || size <= 0) {\n throw new Error(\"workgroupSize must be a positive number.\");\n }\n return Math.max(1, Math.ceil(jobs / size));\n}\n\nexport function createWorkerLoop(options = {}) {\n const {\n device,\n worker,\n jobs = [],\n workgroupSize = 64,\n maxJobsPerDispatch,\n rateHz,\n label,\n onTick,\n onError,\n } = options ?? {};\n\n if (!device) {\n throw new Error(\"createWorkerLoop requires a GPUDevice.\");\n }\n if (!worker || !worker.pipeline) {\n throw new Error(\"createWorkerLoop requires a worker pipeline.\");\n }\n\n let running = false;\n let handle = null;\n let usingRaf = false;\n const intervalMs =\n Number.isFinite(rateHz) && rateHz > 0 ? 1000 / rateHz : null;\n\n const tick = () => {\n try {\n const encoder = device.createCommandEncoder();\n const pass = encoder.beginComputePass(\n label ? { label } : undefined\n );\n\n pass.setPipeline(worker.pipeline);\n setBindGroups(pass, worker.bindGroups);\n\n const explicitWorkerGroups =\n resolveWorkgroups(worker.workgroups, \"worker\") ??\n resolveWorkgroups(worker.workgroupCount, \"worker\") ??\n resolveWorkgroups(worker.dispatch, \"worker\");\n\n const workerGroups = explicitWorkerGroups\n ? explicitWorkerGroups\n : [computeWorkerWorkgroups(maxJobsPerDispatch, workgroupSize), 1, 1];\n\n if (workerGroups[0] > 0) {\n pass.dispatchWorkgroups(...workerGroups);\n }\n\n jobs.forEach((job, index) => {\n if (!job || !job.pipeline) {\n throw new Error(`Job pipeline missing at index ${index}.`);\n }\n pass.setPipeline(job.pipeline);\n setBindGroups(pass, job.bindGroups);\n const groups = resolveWorkgroups(\n job.workgroups ?? job.workgroupCount ?? job.dispatch,\n `job ${index}`\n );\n if (!groups) {\n throw new Error(`Job ${index} requires a workgroup count.`);\n }\n if (groups[0] > 0) {\n pass.dispatchWorkgroups(...groups);\n }\n });\n\n pass.end();\n device.queue.submit([encoder.finish()]);\n\n if (onTick) {\n onTick();\n }\n } catch (err) {\n if (onError) {\n onError(err);\n return;\n }\n throw err;\n }\n };\n\n const scheduleNext = () => {\n if (!running) {\n return;\n }\n if (intervalMs != null) {\n tick();\n usingRaf = false;\n handle = setTimeout(scheduleNext, intervalMs);\n return;\n }\n tick();\n if (typeof requestAnimationFrame === \"function\") {\n usingRaf = true;\n handle = requestAnimationFrame(scheduleNext);\n } else {\n usingRaf = false;\n handle = setTimeout(scheduleNext, 0);\n }\n };\n\n const start = () => {\n if (running) {\n return;\n }\n running = true;\n scheduleNext();\n };\n\n const stop = () => {\n running = false;\n if (handle == null) {\n return;\n }\n if (usingRaf && typeof cancelAnimationFrame === \"function\") {\n cancelAnimationFrame(handle);\n } else {\n clearTimeout(handle);\n }\n handle = null;\n };\n\n return {\n start,\n stop,\n tick,\n get running() {\n return running;\n },\n };\n}\n"],"mappings":"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","names":["source"]}
|