@plasius/gpu-worker 0.1.8 → 0.1.9
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 +35 -1
- package/README.md +110 -0
- package/dist/index.cjs +170 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +174 -7
- package/dist/index.js.map +1 -1
- package/dist/worker.wgsl +1 -0
- package/package.json +5 -2
- package/src/index.js +182 -5
- package/src/worker.wgsl +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -20,6 +20,39 @@ The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/
|
|
|
20
20
|
- **Security**
|
|
21
21
|
- (placeholder)
|
|
22
22
|
|
|
23
|
+
## [0.1.9] - 2026-03-13
|
|
24
|
+
|
|
25
|
+
- **Added**
|
|
26
|
+
- ADR and TDR guidance describing `@plasius/gpu-worker` as the preferred
|
|
27
|
+
discrete execution plane for current and future `@plasius/gpu-*` compute
|
|
28
|
+
packages.
|
|
29
|
+
- Design documentation for multi-package worker coordination and future effect
|
|
30
|
+
package expansion.
|
|
31
|
+
- Optional `createWorkerLoop` telemetry hooks for dispatch samples and
|
|
32
|
+
per-tick summaries with shared `frameId` correlation support.
|
|
33
|
+
- `queueMode: "dag"` support for loading and assembling against
|
|
34
|
+
multi-root DAG-ready queue helpers from `@plasius/gpu-lock-free-queue`.
|
|
35
|
+
- ADR, TDR, and design documentation for flat versus DAG queue assembly
|
|
36
|
+
contracts.
|
|
37
|
+
|
|
38
|
+
- **Changed**
|
|
39
|
+
- README guidance now points package authors toward worker-job manifests,
|
|
40
|
+
`@plasius/gpu-performance` budget coordination, and optional
|
|
41
|
+
`@plasius/gpu-debug` instrumentation.
|
|
42
|
+
- Worker assembly now guarantees a `complete_job(job_index)` hook is present
|
|
43
|
+
so flat and DAG queue assets share the same worker completion path.
|
|
44
|
+
- Queue package resolution now uses the published npm release
|
|
45
|
+
`@plasius/gpu-lock-free-queue@^0.2.13` instead of the temporary GitHub
|
|
46
|
+
fallback.
|
|
47
|
+
- Updated patch-level eslint tooling to the latest versions allowed by the
|
|
48
|
+
current semver ranges.
|
|
49
|
+
|
|
50
|
+
- **Fixed**
|
|
51
|
+
- (placeholder)
|
|
52
|
+
|
|
53
|
+
- **Security**
|
|
54
|
+
- (placeholder)
|
|
55
|
+
|
|
23
56
|
## [0.1.8] - 2026-03-04
|
|
24
57
|
|
|
25
58
|
- **Added**
|
|
@@ -156,7 +189,7 @@ The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/
|
|
|
156
189
|
|
|
157
190
|
---
|
|
158
191
|
|
|
159
|
-
[Unreleased]: https://github.com/Plasius-LTD/gpu-worker/compare/v0.1.
|
|
192
|
+
[Unreleased]: https://github.com/Plasius-LTD/gpu-worker/compare/v0.1.9...HEAD
|
|
160
193
|
[0.1.0-beta.1]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.0-beta.1
|
|
161
194
|
[0.1.0]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.0
|
|
162
195
|
[0.1.2]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.2
|
|
@@ -177,3 +210,4 @@ The format is based on **[Keep a Changelog](https://keepachangelog.com/en/1.1.0/
|
|
|
177
210
|
[0.1.3]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.3
|
|
178
211
|
[0.1.4]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.4
|
|
179
212
|
[0.1.8]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.8
|
|
213
|
+
[0.1.9]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.9
|
package/README.md
CHANGED
|
@@ -54,6 +54,22 @@ By default it applies queue compatibility renames (for example `JobMeta` -> `Job
|
|
|
54
54
|
set `queueCompat: false` to disable that behavior.
|
|
55
55
|
If you are concatenating WGSL manually, `loadQueueWgsl` provides the same
|
|
56
56
|
compatibility renames by default: `loadQueueWgsl({ url, fetcher, queueCompat: false })`.
|
|
57
|
+
Set `queueMode: "dag"` on `loadQueueWgsl(...)` or `assembleWorkerWgsl(...)`
|
|
58
|
+
to assemble against the multi-root DAG-ready queue helpers from
|
|
59
|
+
`@plasius/gpu-lock-free-queue`.
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
const shaderCode = await assembleWorkerWgsl(workerWgsl, {
|
|
63
|
+
preludeWgsl,
|
|
64
|
+
jobs,
|
|
65
|
+
queueMode: "dag",
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Worker WGSL now calls `complete_job(job_index)` after `process_job(...)`.
|
|
70
|
+
Queue assets from `@plasius/gpu-lock-free-queue` already provide that hook. If
|
|
71
|
+
you pass a custom queue source without it, the assembler appends a no-op shim so
|
|
72
|
+
existing flat queue integrations keep working.
|
|
57
73
|
|
|
58
74
|
To bypass the registry, pass jobs directly:
|
|
59
75
|
```js
|
|
@@ -85,11 +101,102 @@ const loop = createWorkerLoop({
|
|
|
85
101
|
loop.start();
|
|
86
102
|
```
|
|
87
103
|
|
|
104
|
+
For opt-in local instrumentation, `createWorkerLoop` also accepts:
|
|
105
|
+
|
|
106
|
+
- `frameId`: a string or function returning the current frame correlation id.
|
|
107
|
+
- `worker.owner`, `worker.queueClass`, `worker.jobType`, `worker.label`,
|
|
108
|
+
`worker.workgroupSize`: optional metadata for the dequeue pass.
|
|
109
|
+
- the same metadata fields on each job descriptor.
|
|
110
|
+
- `telemetry.onDispatch(sample)`: called after submit for each worker/job
|
|
111
|
+
dispatch.
|
|
112
|
+
- `telemetry.onTick(summary)`: called after submit with the per-tick dispatch
|
|
113
|
+
summary.
|
|
114
|
+
|
|
115
|
+
That allows direct integration with `@plasius/gpu-debug` without coupling the
|
|
116
|
+
runtime to the package:
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
import { createGpuDebugSession } from "@plasius/gpu-debug";
|
|
120
|
+
|
|
121
|
+
const debug = createGpuDebugSession({ enabled: true });
|
|
122
|
+
|
|
123
|
+
const loop = createWorkerLoop({
|
|
124
|
+
device,
|
|
125
|
+
frameId: () => `frame-${frameNumber}`,
|
|
126
|
+
worker: {
|
|
127
|
+
pipeline: workerPipeline,
|
|
128
|
+
bindGroups: [queueBindGroup],
|
|
129
|
+
workgroups: [2, 1, 1],
|
|
130
|
+
workgroupSize: 64,
|
|
131
|
+
owner: "lighting",
|
|
132
|
+
queueClass: "lighting",
|
|
133
|
+
jobType: "worker.dequeue",
|
|
134
|
+
},
|
|
135
|
+
jobs: [
|
|
136
|
+
{
|
|
137
|
+
pipeline: directLightingPipeline,
|
|
138
|
+
bindGroups: [queueBindGroup, lightingBindGroup],
|
|
139
|
+
workgroupCount: [32, 18, 1],
|
|
140
|
+
workgroupSize: [8, 8, 1],
|
|
141
|
+
owner: "lighting",
|
|
142
|
+
queueClass: "lighting",
|
|
143
|
+
jobType: "lighting.direct",
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
telemetry: {
|
|
147
|
+
onDispatch(sample) {
|
|
148
|
+
debug.recordDispatch({
|
|
149
|
+
owner: sample.owner,
|
|
150
|
+
queueClass: sample.queueClass,
|
|
151
|
+
jobType: sample.jobType,
|
|
152
|
+
frameId: sample.frameId,
|
|
153
|
+
workgroups: sample.workgroups,
|
|
154
|
+
workgroupSize: sample.workgroupSize,
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
88
161
|
## What this is
|
|
89
162
|
- A minimal GPU worker layer that combines a lock-free queue with user WGSL jobs.
|
|
90
163
|
- A helper to assemble WGSL modules with queue helpers included.
|
|
91
164
|
- A reference job format for fixed-size job dispatch (u32 indices).
|
|
92
165
|
|
|
166
|
+
## DAG Queue Modes
|
|
167
|
+
|
|
168
|
+
`@plasius/gpu-worker` now supports two scheduler assembly modes:
|
|
169
|
+
|
|
170
|
+
- `flat`: the original lock-free FIFO/worklist execution flow.
|
|
171
|
+
- `dag`: a multi-root, priority-aware ready-queue flow where jobs can declare
|
|
172
|
+
dependencies and join points through their package-owned manifests.
|
|
173
|
+
|
|
174
|
+
The worker runtime still stays lock-free and policy-light. It does not resolve
|
|
175
|
+
budgets or decide priorities itself. Instead it exposes the queue mode and the
|
|
176
|
+
completion hook needed by package manifests and `@plasius/gpu-performance` to
|
|
177
|
+
coordinate DAG-shaped workloads.
|
|
178
|
+
|
|
179
|
+
## Package Integration Model
|
|
180
|
+
|
|
181
|
+
`@plasius/gpu-worker` is the preferred execution plane for discrete GPU work
|
|
182
|
+
across current and future `@plasius/gpu-*` compute packages.
|
|
183
|
+
|
|
184
|
+
Package authors should:
|
|
185
|
+
|
|
186
|
+
- register stable worker job types for each effect family,
|
|
187
|
+
- keep scheduling in terms of compact worklists and bounded dispatches,
|
|
188
|
+
- expose `priority`, `dependencies`, and `schedulerMode` metadata in manifests
|
|
189
|
+
when jobs form ordered DAG stages instead of a flat queue,
|
|
190
|
+
- let `@plasius/gpu-performance` adjust worker budgets instead of building
|
|
191
|
+
separate package-local governors,
|
|
192
|
+
- expose optional local instrumentation through `createWorkerLoop(..., {
|
|
193
|
+
telemetry })` and route that into `@plasius/gpu-debug` when clients enable
|
|
194
|
+
it.
|
|
195
|
+
|
|
196
|
+
This pattern is intended to scale across post-processing, cloth, fluids,
|
|
197
|
+
lighting refresh, voxel generation, and additional GPU job families without
|
|
198
|
+
splitting scheduling policy across packages.
|
|
199
|
+
|
|
93
200
|
## Demo
|
|
94
201
|
The demo enqueues physics and render jobs on the GPU, builds per-type worklists, runs the
|
|
95
202
|
physics kernel, and uses an indirect draw for the particle pass. Install dependencies first
|
|
@@ -139,6 +246,9 @@ npm run pack:check
|
|
|
139
246
|
- `demo/jobs/render.job.wgsl`: Render job kernel (worklist + indirect args).
|
|
140
247
|
- `src/worker.wgsl`: Minimal worker entry point template (dequeue + `process_job` hook).
|
|
141
248
|
- `src/index.js`: Helper functions to load/assemble WGSL.
|
|
249
|
+
- `docs/adrs/*`: architectural decisions for worker runtime and scheduling.
|
|
250
|
+
- `docs/tdrs/*`: technical design records for multi-package worker integration.
|
|
251
|
+
- `docs/design/*`: design notes for package integration, DAG queue modes, and future expansion.
|
|
142
252
|
|
|
143
253
|
## Job shape
|
|
144
254
|
Jobs are variable-length payloads stored in a caller-managed buffer. Each job supplies `job_type`, `payload_offset`, and `payload_words` metadata plus a payload stored in the input payload buffer. For simple cases, use a single-word payload containing an index into your workload array.
|
package/dist/index.cjs
CHANGED
|
@@ -202,6 +202,15 @@ function getQueueCompatMap(source) {
|
|
|
202
202
|
}
|
|
203
203
|
return [{ from: /\bJobMeta\b/g, to: "JobDesc" }];
|
|
204
204
|
}
|
|
205
|
+
function normalizeQueueMode(mode) {
|
|
206
|
+
const resolved = mode ?? "flat";
|
|
207
|
+
if (!import_gpu_lock_free_queue.schedulerModes.includes(resolved)) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`queueMode must be one of: ${import_gpu_lock_free_queue.schedulerModes.join(", ")}.`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
return resolved;
|
|
213
|
+
}
|
|
205
214
|
function applyCompatMap(source, map) {
|
|
206
215
|
if (!map || map.length === 0) {
|
|
207
216
|
return source;
|
|
@@ -276,8 +285,11 @@ async function loadWorkerWgsl(options = {}) {
|
|
|
276
285
|
return source;
|
|
277
286
|
}
|
|
278
287
|
async function loadQueueWgsl(options = {}) {
|
|
279
|
-
const { queueCompat = true, ...rest } = options ?? {};
|
|
280
|
-
const source = await (0, import_gpu_lock_free_queue.
|
|
288
|
+
const { queueCompat = true, queueMode = "flat", ...rest } = options ?? {};
|
|
289
|
+
const source = await (0, import_gpu_lock_free_queue.loadSchedulerWgsl)({
|
|
290
|
+
mode: normalizeQueueMode(queueMode),
|
|
291
|
+
...rest
|
|
292
|
+
});
|
|
281
293
|
if (typeof source !== "string") {
|
|
282
294
|
throw new Error("Failed to load queue WGSL source.");
|
|
283
295
|
}
|
|
@@ -288,6 +300,16 @@ async function loadQueueWgsl(options = {}) {
|
|
|
288
300
|
const compatMap = getQueueCompatMap(source);
|
|
289
301
|
return applyCompatMap(source, compatMap);
|
|
290
302
|
}
|
|
303
|
+
function ensureQueueLifecycleHooks(source) {
|
|
304
|
+
if (/\bfn\s+complete_job\b/.test(source)) {
|
|
305
|
+
return source;
|
|
306
|
+
}
|
|
307
|
+
return `${source}
|
|
308
|
+
|
|
309
|
+
fn complete_job(job_index: u32) {
|
|
310
|
+
_ = job_index;
|
|
311
|
+
}`;
|
|
312
|
+
}
|
|
291
313
|
async function loadJobWgsl(options = {}) {
|
|
292
314
|
const { wgsl, url, fetcher, label } = options ?? {};
|
|
293
315
|
const source = await loadWgslSource({
|
|
@@ -318,12 +340,20 @@ async function assembleWorkerWgsl(workerWgsl, options = {}) {
|
|
|
318
340
|
fetcher,
|
|
319
341
|
jobs,
|
|
320
342
|
debug,
|
|
321
|
-
queueCompat = true
|
|
343
|
+
queueCompat = true,
|
|
344
|
+
queueMode = "flat"
|
|
322
345
|
} = options ?? {};
|
|
323
|
-
const
|
|
346
|
+
const resolvedQueueMode = normalizeQueueMode(queueMode);
|
|
347
|
+
const rawQueueSource = queueWgsl ?? await (0, import_gpu_lock_free_queue.loadSchedulerWgsl)({
|
|
348
|
+
mode: resolvedQueueMode,
|
|
349
|
+
url: queueUrl,
|
|
350
|
+
fetcher
|
|
351
|
+
});
|
|
324
352
|
const bodyRaw = workerWgsl ?? await loadWorkerWgsl({ fetcher });
|
|
325
353
|
const compatMap = queueCompat ? getQueueCompatMap(rawQueueSource) : null;
|
|
326
|
-
const queueSource =
|
|
354
|
+
const queueSource = ensureQueueLifecycleHooks(
|
|
355
|
+
applyCompatMap(rawQueueSource, compatMap)
|
|
356
|
+
);
|
|
327
357
|
const preludeRaw = preludeWgsl ?? (preludeUrl ? await loadWgslSource({ url: preludeUrl, fetcher, baseUrl: workerWgslUrl }) : "");
|
|
328
358
|
if ((preludeWgsl || preludeUrl) && typeof preludeRaw !== "string") {
|
|
329
359
|
throw new Error("Failed to load prelude WGSL source.");
|
|
@@ -396,6 +426,88 @@ function resolveWorkgroups(value, label) {
|
|
|
396
426
|
}
|
|
397
427
|
return normalizeWorkgroups(value, label);
|
|
398
428
|
}
|
|
429
|
+
function normalizeTelemetryText(value, fallback) {
|
|
430
|
+
if (typeof value !== "string") {
|
|
431
|
+
return fallback;
|
|
432
|
+
}
|
|
433
|
+
const trimmed = value.trim();
|
|
434
|
+
return trimmed ? trimmed.slice(0, 120) : fallback;
|
|
435
|
+
}
|
|
436
|
+
function resolveFrameId(value) {
|
|
437
|
+
if (typeof value === "function") {
|
|
438
|
+
return resolveFrameId(value());
|
|
439
|
+
}
|
|
440
|
+
if (typeof value !== "string") {
|
|
441
|
+
return void 0;
|
|
442
|
+
}
|
|
443
|
+
const trimmed = value.trim();
|
|
444
|
+
return trimmed ? trimmed.slice(0, 120) : void 0;
|
|
445
|
+
}
|
|
446
|
+
function toVector(workgroups) {
|
|
447
|
+
return { x: workgroups[0], y: workgroups[1], z: workgroups[2] };
|
|
448
|
+
}
|
|
449
|
+
function resolveTelemetryWorkgroupSize(descriptor, fallback, label) {
|
|
450
|
+
const size = descriptor?.workgroupSize == null ? fallback : descriptor.workgroupSize;
|
|
451
|
+
if (size == null) {
|
|
452
|
+
return void 0;
|
|
453
|
+
}
|
|
454
|
+
const normalized = typeof size === "number" ? normalizeWorkgroups(size, label) : resolveWorkgroups(size, label);
|
|
455
|
+
if (!normalized) {
|
|
456
|
+
return void 0;
|
|
457
|
+
}
|
|
458
|
+
return toVector(normalized);
|
|
459
|
+
}
|
|
460
|
+
function getNow() {
|
|
461
|
+
if (globalThis.performance && typeof globalThis.performance.now === "function") {
|
|
462
|
+
return globalThis.performance.now();
|
|
463
|
+
}
|
|
464
|
+
return Date.now();
|
|
465
|
+
}
|
|
466
|
+
function reportOptionalError(error, onError) {
|
|
467
|
+
if (!onError) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (error instanceof Error) {
|
|
471
|
+
onError(error);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
onError(new Error(String(error)));
|
|
475
|
+
}
|
|
476
|
+
function emitOptionalHook(callback, payload, onError) {
|
|
477
|
+
if (typeof callback !== "function") {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
callback(payload);
|
|
482
|
+
} catch (error) {
|
|
483
|
+
reportOptionalError(error, onError);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function buildDispatchTelemetrySample({
|
|
487
|
+
kind,
|
|
488
|
+
descriptor,
|
|
489
|
+
index,
|
|
490
|
+
frameId,
|
|
491
|
+
workgroups,
|
|
492
|
+
workgroupSize
|
|
493
|
+
}) {
|
|
494
|
+
const labelFallback = kind === "worker" ? "worker" : `job_${index}`;
|
|
495
|
+
const label = normalizeTelemetryText(descriptor?.label, labelFallback);
|
|
496
|
+
return {
|
|
497
|
+
kind,
|
|
498
|
+
index,
|
|
499
|
+
label,
|
|
500
|
+
owner: normalizeTelemetryText(descriptor?.owner, label),
|
|
501
|
+
queueClass: normalizeTelemetryText(descriptor?.queueClass, "custom"),
|
|
502
|
+
jobType: normalizeTelemetryText(
|
|
503
|
+
descriptor?.jobType,
|
|
504
|
+
kind === "worker" ? "worker.dispatch" : label
|
|
505
|
+
),
|
|
506
|
+
frameId,
|
|
507
|
+
workgroups: toVector(workgroups),
|
|
508
|
+
workgroupSize
|
|
509
|
+
};
|
|
510
|
+
}
|
|
399
511
|
function setBindGroups(pass, bindGroups) {
|
|
400
512
|
if (!bindGroups) {
|
|
401
513
|
return;
|
|
@@ -427,7 +539,9 @@ function createWorkerLoop(options = {}) {
|
|
|
427
539
|
rateHz,
|
|
428
540
|
label,
|
|
429
541
|
onTick,
|
|
430
|
-
onError
|
|
542
|
+
onError,
|
|
543
|
+
frameId,
|
|
544
|
+
telemetry
|
|
431
545
|
} = options ?? {};
|
|
432
546
|
if (!device) {
|
|
433
547
|
throw new Error("createWorkerLoop requires a GPUDevice.");
|
|
@@ -441,6 +555,9 @@ function createWorkerLoop(options = {}) {
|
|
|
441
555
|
const intervalMs = Number.isFinite(rateHz) && rateHz > 0 ? 1e3 / rateHz : null;
|
|
442
556
|
const tick = () => {
|
|
443
557
|
try {
|
|
558
|
+
const tickStartMs = getNow();
|
|
559
|
+
const currentFrameId = resolveFrameId(frameId);
|
|
560
|
+
const telemetryDispatches = [];
|
|
444
561
|
const encoder = device.createCommandEncoder();
|
|
445
562
|
const pass = encoder.beginComputePass(
|
|
446
563
|
label ? { label } : void 0
|
|
@@ -451,6 +568,20 @@ function createWorkerLoop(options = {}) {
|
|
|
451
568
|
const workerGroups = explicitWorkerGroups ? explicitWorkerGroups : [computeWorkerWorkgroups(maxJobsPerDispatch, workgroupSize), 1, 1];
|
|
452
569
|
if (workerGroups[0] > 0) {
|
|
453
570
|
pass.dispatchWorkgroups(...workerGroups);
|
|
571
|
+
telemetryDispatches.push(
|
|
572
|
+
buildDispatchTelemetrySample({
|
|
573
|
+
kind: "worker",
|
|
574
|
+
descriptor: worker,
|
|
575
|
+
index: 0,
|
|
576
|
+
frameId: currentFrameId,
|
|
577
|
+
workgroups: workerGroups,
|
|
578
|
+
workgroupSize: resolveTelemetryWorkgroupSize(
|
|
579
|
+
worker,
|
|
580
|
+
workgroupSize,
|
|
581
|
+
"worker workgroupSize"
|
|
582
|
+
)
|
|
583
|
+
})
|
|
584
|
+
);
|
|
454
585
|
}
|
|
455
586
|
jobs.forEach((job, index) => {
|
|
456
587
|
if (!job || !job.pipeline) {
|
|
@@ -467,10 +598,43 @@ function createWorkerLoop(options = {}) {
|
|
|
467
598
|
}
|
|
468
599
|
if (groups[0] > 0) {
|
|
469
600
|
pass.dispatchWorkgroups(...groups);
|
|
601
|
+
telemetryDispatches.push(
|
|
602
|
+
buildDispatchTelemetrySample({
|
|
603
|
+
kind: "job",
|
|
604
|
+
descriptor: job,
|
|
605
|
+
index,
|
|
606
|
+
frameId: currentFrameId,
|
|
607
|
+
workgroups: groups,
|
|
608
|
+
workgroupSize: resolveTelemetryWorkgroupSize(
|
|
609
|
+
job,
|
|
610
|
+
void 0,
|
|
611
|
+
`job ${index} workgroupSize`
|
|
612
|
+
)
|
|
613
|
+
})
|
|
614
|
+
);
|
|
470
615
|
}
|
|
471
616
|
});
|
|
472
617
|
pass.end();
|
|
473
618
|
device.queue.submit([encoder.finish()]);
|
|
619
|
+
telemetryDispatches.forEach((sample) => {
|
|
620
|
+
emitOptionalHook(telemetry?.onDispatch, sample, onError);
|
|
621
|
+
});
|
|
622
|
+
emitOptionalHook(
|
|
623
|
+
telemetry?.onTick,
|
|
624
|
+
{
|
|
625
|
+
frameId: currentFrameId,
|
|
626
|
+
tickDurationMs: getNow() - tickStartMs,
|
|
627
|
+
dispatchCount: telemetryDispatches.length,
|
|
628
|
+
workerDispatchCount: telemetryDispatches.filter(
|
|
629
|
+
(sample) => sample.kind === "worker"
|
|
630
|
+
).length,
|
|
631
|
+
jobDispatchCount: telemetryDispatches.filter(
|
|
632
|
+
(sample) => sample.kind === "job"
|
|
633
|
+
).length,
|
|
634
|
+
dispatches: telemetryDispatches
|
|
635
|
+
},
|
|
636
|
+
onError
|
|
637
|
+
);
|
|
474
638
|
if (onTick) {
|
|
475
639
|
onTick();
|
|
476
640
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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","module","loadQueueWgslRaw"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.js"],"sourcesContent":["import {\n loadSchedulerWgsl as loadSchedulerWgslRaw,\n schedulerModes as workerSchedulerModes,\n} 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 normalizeQueueMode(mode) {\n const resolved = mode ?? \"flat\";\n if (!workerSchedulerModes.includes(resolved)) {\n throw new Error(\n `queueMode must be one of: ${workerSchedulerModes.join(\", \")}.`\n );\n }\n return resolved;\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, queueMode = \"flat\", ...rest } = options ?? {};\n const source = await loadSchedulerWgslRaw({\n mode: normalizeQueueMode(queueMode),\n ...rest,\n });\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\nfunction ensureQueueLifecycleHooks(source) {\n if (/\\bfn\\s+complete_job\\b/.test(source)) {\n return source;\n }\n return `${source}\\n\\nfn complete_job(job_index: u32) {\\n _ = job_index;\\n}`;\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 queueMode = \"flat\",\n } = options ?? {};\n const resolvedQueueMode = normalizeQueueMode(queueMode);\n const rawQueueSource =\n queueWgsl ??\n (await loadSchedulerWgslRaw({\n mode: resolvedQueueMode,\n url: queueUrl,\n fetcher,\n }));\n const bodyRaw = workerWgsl ?? (await loadWorkerWgsl({ fetcher }));\n const compatMap = queueCompat ? getQueueCompatMap(rawQueueSource) : null;\n const queueSource = ensureQueueLifecycleHooks(\n applyCompatMap(rawQueueSource, compatMap)\n );\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 normalizeTelemetryText(value, fallback) {\n if (typeof value !== \"string\") {\n return fallback;\n }\n const trimmed = value.trim();\n return trimmed ? trimmed.slice(0, 120) : fallback;\n}\n\nfunction resolveFrameId(value) {\n if (typeof value === \"function\") {\n return resolveFrameId(value());\n }\n if (typeof value !== \"string\") {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed ? trimmed.slice(0, 120) : undefined;\n}\n\nfunction toVector(workgroups) {\n return { x: workgroups[0], y: workgroups[1], z: workgroups[2] };\n}\n\nfunction resolveTelemetryWorkgroupSize(descriptor, fallback, label) {\n const size =\n descriptor?.workgroupSize == null ? fallback : descriptor.workgroupSize;\n if (size == null) {\n return undefined;\n }\n const normalized =\n typeof size === \"number\" ? normalizeWorkgroups(size, label) : resolveWorkgroups(size, label);\n if (!normalized) {\n return undefined;\n }\n return toVector(normalized);\n}\n\nfunction getNow() {\n if (globalThis.performance && typeof globalThis.performance.now === \"function\") {\n return globalThis.performance.now();\n }\n return Date.now();\n}\n\nfunction reportOptionalError(error, onError) {\n if (!onError) {\n return;\n }\n if (error instanceof Error) {\n onError(error);\n return;\n }\n onError(new Error(String(error)));\n}\n\nfunction emitOptionalHook(callback, payload, onError) {\n if (typeof callback !== \"function\") {\n return;\n }\n try {\n callback(payload);\n } catch (error) {\n reportOptionalError(error, onError);\n }\n}\n\nfunction buildDispatchTelemetrySample({\n kind,\n descriptor,\n index,\n frameId,\n workgroups,\n workgroupSize,\n}) {\n const labelFallback = kind === \"worker\" ? \"worker\" : `job_${index}`;\n const label = normalizeTelemetryText(descriptor?.label, labelFallback);\n return {\n kind,\n index,\n label,\n owner: normalizeTelemetryText(descriptor?.owner, label),\n queueClass: normalizeTelemetryText(descriptor?.queueClass, \"custom\"),\n jobType: normalizeTelemetryText(\n descriptor?.jobType,\n kind === \"worker\" ? \"worker.dispatch\" : label\n ),\n frameId,\n workgroups: toVector(workgroups),\n workgroupSize,\n };\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 frameId,\n telemetry,\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 tickStartMs = getNow();\n const currentFrameId = resolveFrameId(frameId);\n const telemetryDispatches = [];\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 telemetryDispatches.push(\n buildDispatchTelemetrySample({\n kind: \"worker\",\n descriptor: worker,\n index: 0,\n frameId: currentFrameId,\n workgroups: workerGroups,\n workgroupSize: resolveTelemetryWorkgroupSize(\n worker,\n workgroupSize,\n \"worker workgroupSize\"\n ),\n })\n );\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 telemetryDispatches.push(\n buildDispatchTelemetrySample({\n kind: \"job\",\n descriptor: job,\n index,\n frameId: currentFrameId,\n workgroups: groups,\n workgroupSize: resolveTelemetryWorkgroupSize(\n job,\n undefined,\n `job ${index} workgroupSize`\n ),\n })\n );\n }\n });\n\n pass.end();\n device.queue.submit([encoder.finish()]);\n\n telemetryDispatches.forEach((sample) => {\n emitOptionalHook(telemetry?.onDispatch, sample, onError);\n });\n emitOptionalHook(\n telemetry?.onTick,\n {\n frameId: currentFrameId,\n tickDurationMs: getNow() - tickStartMs,\n dispatchCount: telemetryDispatches.length,\n workerDispatchCount: telemetryDispatches.filter(\n (sample) => sample.kind === \"worker\"\n ).length,\n jobDispatchCount: telemetryDispatches.filter(\n (sample) => sample.kind === \"job\"\n ).length,\n dispatches: telemetryDispatches,\n },\n onError\n );\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","module","workerSchedulerModes","loadSchedulerWgslRaw"]}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,10 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
// src/index.js
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
loadSchedulerWgsl as loadSchedulerWgslRaw,
|
|
11
|
+
schedulerModes as workerSchedulerModes
|
|
12
|
+
} from "@plasius/gpu-lock-free-queue";
|
|
10
13
|
var workerWgslUrl = (() => {
|
|
11
14
|
if (typeof import.meta.url !== "undefined") {
|
|
12
15
|
return new URL("./worker.wgsl", import.meta.url);
|
|
@@ -171,6 +174,15 @@ function getQueueCompatMap(source) {
|
|
|
171
174
|
}
|
|
172
175
|
return [{ from: /\bJobMeta\b/g, to: "JobDesc" }];
|
|
173
176
|
}
|
|
177
|
+
function normalizeQueueMode(mode) {
|
|
178
|
+
const resolved = mode ?? "flat";
|
|
179
|
+
if (!workerSchedulerModes.includes(resolved)) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`queueMode must be one of: ${workerSchedulerModes.join(", ")}.`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return resolved;
|
|
185
|
+
}
|
|
174
186
|
function applyCompatMap(source, map) {
|
|
175
187
|
if (!map || map.length === 0) {
|
|
176
188
|
return source;
|
|
@@ -245,8 +257,11 @@ async function loadWorkerWgsl(options = {}) {
|
|
|
245
257
|
return source;
|
|
246
258
|
}
|
|
247
259
|
async function loadQueueWgsl(options = {}) {
|
|
248
|
-
const { queueCompat = true, ...rest } = options ?? {};
|
|
249
|
-
const source = await
|
|
260
|
+
const { queueCompat = true, queueMode = "flat", ...rest } = options ?? {};
|
|
261
|
+
const source = await loadSchedulerWgslRaw({
|
|
262
|
+
mode: normalizeQueueMode(queueMode),
|
|
263
|
+
...rest
|
|
264
|
+
});
|
|
250
265
|
if (typeof source !== "string") {
|
|
251
266
|
throw new Error("Failed to load queue WGSL source.");
|
|
252
267
|
}
|
|
@@ -257,6 +272,16 @@ async function loadQueueWgsl(options = {}) {
|
|
|
257
272
|
const compatMap = getQueueCompatMap(source);
|
|
258
273
|
return applyCompatMap(source, compatMap);
|
|
259
274
|
}
|
|
275
|
+
function ensureQueueLifecycleHooks(source) {
|
|
276
|
+
if (/\bfn\s+complete_job\b/.test(source)) {
|
|
277
|
+
return source;
|
|
278
|
+
}
|
|
279
|
+
return `${source}
|
|
280
|
+
|
|
281
|
+
fn complete_job(job_index: u32) {
|
|
282
|
+
_ = job_index;
|
|
283
|
+
}`;
|
|
284
|
+
}
|
|
260
285
|
async function loadJobWgsl(options = {}) {
|
|
261
286
|
const { wgsl, url, fetcher, label } = options ?? {};
|
|
262
287
|
const source = await loadWgslSource({
|
|
@@ -287,12 +312,20 @@ async function assembleWorkerWgsl(workerWgsl, options = {}) {
|
|
|
287
312
|
fetcher,
|
|
288
313
|
jobs,
|
|
289
314
|
debug,
|
|
290
|
-
queueCompat = true
|
|
315
|
+
queueCompat = true,
|
|
316
|
+
queueMode = "flat"
|
|
291
317
|
} = options ?? {};
|
|
292
|
-
const
|
|
318
|
+
const resolvedQueueMode = normalizeQueueMode(queueMode);
|
|
319
|
+
const rawQueueSource = queueWgsl ?? await loadSchedulerWgslRaw({
|
|
320
|
+
mode: resolvedQueueMode,
|
|
321
|
+
url: queueUrl,
|
|
322
|
+
fetcher
|
|
323
|
+
});
|
|
293
324
|
const bodyRaw = workerWgsl ?? await loadWorkerWgsl({ fetcher });
|
|
294
325
|
const compatMap = queueCompat ? getQueueCompatMap(rawQueueSource) : null;
|
|
295
|
-
const queueSource =
|
|
326
|
+
const queueSource = ensureQueueLifecycleHooks(
|
|
327
|
+
applyCompatMap(rawQueueSource, compatMap)
|
|
328
|
+
);
|
|
296
329
|
const preludeRaw = preludeWgsl ?? (preludeUrl ? await loadWgslSource({ url: preludeUrl, fetcher, baseUrl: workerWgslUrl }) : "");
|
|
297
330
|
if ((preludeWgsl || preludeUrl) && typeof preludeRaw !== "string") {
|
|
298
331
|
throw new Error("Failed to load prelude WGSL source.");
|
|
@@ -365,6 +398,88 @@ function resolveWorkgroups(value, label) {
|
|
|
365
398
|
}
|
|
366
399
|
return normalizeWorkgroups(value, label);
|
|
367
400
|
}
|
|
401
|
+
function normalizeTelemetryText(value, fallback) {
|
|
402
|
+
if (typeof value !== "string") {
|
|
403
|
+
return fallback;
|
|
404
|
+
}
|
|
405
|
+
const trimmed = value.trim();
|
|
406
|
+
return trimmed ? trimmed.slice(0, 120) : fallback;
|
|
407
|
+
}
|
|
408
|
+
function resolveFrameId(value) {
|
|
409
|
+
if (typeof value === "function") {
|
|
410
|
+
return resolveFrameId(value());
|
|
411
|
+
}
|
|
412
|
+
if (typeof value !== "string") {
|
|
413
|
+
return void 0;
|
|
414
|
+
}
|
|
415
|
+
const trimmed = value.trim();
|
|
416
|
+
return trimmed ? trimmed.slice(0, 120) : void 0;
|
|
417
|
+
}
|
|
418
|
+
function toVector(workgroups) {
|
|
419
|
+
return { x: workgroups[0], y: workgroups[1], z: workgroups[2] };
|
|
420
|
+
}
|
|
421
|
+
function resolveTelemetryWorkgroupSize(descriptor, fallback, label) {
|
|
422
|
+
const size = descriptor?.workgroupSize == null ? fallback : descriptor.workgroupSize;
|
|
423
|
+
if (size == null) {
|
|
424
|
+
return void 0;
|
|
425
|
+
}
|
|
426
|
+
const normalized = typeof size === "number" ? normalizeWorkgroups(size, label) : resolveWorkgroups(size, label);
|
|
427
|
+
if (!normalized) {
|
|
428
|
+
return void 0;
|
|
429
|
+
}
|
|
430
|
+
return toVector(normalized);
|
|
431
|
+
}
|
|
432
|
+
function getNow() {
|
|
433
|
+
if (globalThis.performance && typeof globalThis.performance.now === "function") {
|
|
434
|
+
return globalThis.performance.now();
|
|
435
|
+
}
|
|
436
|
+
return Date.now();
|
|
437
|
+
}
|
|
438
|
+
function reportOptionalError(error, onError) {
|
|
439
|
+
if (!onError) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
if (error instanceof Error) {
|
|
443
|
+
onError(error);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
onError(new Error(String(error)));
|
|
447
|
+
}
|
|
448
|
+
function emitOptionalHook(callback, payload, onError) {
|
|
449
|
+
if (typeof callback !== "function") {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
try {
|
|
453
|
+
callback(payload);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
reportOptionalError(error, onError);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
function buildDispatchTelemetrySample({
|
|
459
|
+
kind,
|
|
460
|
+
descriptor,
|
|
461
|
+
index,
|
|
462
|
+
frameId,
|
|
463
|
+
workgroups,
|
|
464
|
+
workgroupSize
|
|
465
|
+
}) {
|
|
466
|
+
const labelFallback = kind === "worker" ? "worker" : `job_${index}`;
|
|
467
|
+
const label = normalizeTelemetryText(descriptor?.label, labelFallback);
|
|
468
|
+
return {
|
|
469
|
+
kind,
|
|
470
|
+
index,
|
|
471
|
+
label,
|
|
472
|
+
owner: normalizeTelemetryText(descriptor?.owner, label),
|
|
473
|
+
queueClass: normalizeTelemetryText(descriptor?.queueClass, "custom"),
|
|
474
|
+
jobType: normalizeTelemetryText(
|
|
475
|
+
descriptor?.jobType,
|
|
476
|
+
kind === "worker" ? "worker.dispatch" : label
|
|
477
|
+
),
|
|
478
|
+
frameId,
|
|
479
|
+
workgroups: toVector(workgroups),
|
|
480
|
+
workgroupSize
|
|
481
|
+
};
|
|
482
|
+
}
|
|
368
483
|
function setBindGroups(pass, bindGroups) {
|
|
369
484
|
if (!bindGroups) {
|
|
370
485
|
return;
|
|
@@ -396,7 +511,9 @@ function createWorkerLoop(options = {}) {
|
|
|
396
511
|
rateHz,
|
|
397
512
|
label,
|
|
398
513
|
onTick,
|
|
399
|
-
onError
|
|
514
|
+
onError,
|
|
515
|
+
frameId,
|
|
516
|
+
telemetry
|
|
400
517
|
} = options ?? {};
|
|
401
518
|
if (!device) {
|
|
402
519
|
throw new Error("createWorkerLoop requires a GPUDevice.");
|
|
@@ -410,6 +527,9 @@ function createWorkerLoop(options = {}) {
|
|
|
410
527
|
const intervalMs = Number.isFinite(rateHz) && rateHz > 0 ? 1e3 / rateHz : null;
|
|
411
528
|
const tick = () => {
|
|
412
529
|
try {
|
|
530
|
+
const tickStartMs = getNow();
|
|
531
|
+
const currentFrameId = resolveFrameId(frameId);
|
|
532
|
+
const telemetryDispatches = [];
|
|
413
533
|
const encoder = device.createCommandEncoder();
|
|
414
534
|
const pass = encoder.beginComputePass(
|
|
415
535
|
label ? { label } : void 0
|
|
@@ -420,6 +540,20 @@ function createWorkerLoop(options = {}) {
|
|
|
420
540
|
const workerGroups = explicitWorkerGroups ? explicitWorkerGroups : [computeWorkerWorkgroups(maxJobsPerDispatch, workgroupSize), 1, 1];
|
|
421
541
|
if (workerGroups[0] > 0) {
|
|
422
542
|
pass.dispatchWorkgroups(...workerGroups);
|
|
543
|
+
telemetryDispatches.push(
|
|
544
|
+
buildDispatchTelemetrySample({
|
|
545
|
+
kind: "worker",
|
|
546
|
+
descriptor: worker,
|
|
547
|
+
index: 0,
|
|
548
|
+
frameId: currentFrameId,
|
|
549
|
+
workgroups: workerGroups,
|
|
550
|
+
workgroupSize: resolveTelemetryWorkgroupSize(
|
|
551
|
+
worker,
|
|
552
|
+
workgroupSize,
|
|
553
|
+
"worker workgroupSize"
|
|
554
|
+
)
|
|
555
|
+
})
|
|
556
|
+
);
|
|
423
557
|
}
|
|
424
558
|
jobs.forEach((job, index) => {
|
|
425
559
|
if (!job || !job.pipeline) {
|
|
@@ -436,10 +570,43 @@ function createWorkerLoop(options = {}) {
|
|
|
436
570
|
}
|
|
437
571
|
if (groups[0] > 0) {
|
|
438
572
|
pass.dispatchWorkgroups(...groups);
|
|
573
|
+
telemetryDispatches.push(
|
|
574
|
+
buildDispatchTelemetrySample({
|
|
575
|
+
kind: "job",
|
|
576
|
+
descriptor: job,
|
|
577
|
+
index,
|
|
578
|
+
frameId: currentFrameId,
|
|
579
|
+
workgroups: groups,
|
|
580
|
+
workgroupSize: resolveTelemetryWorkgroupSize(
|
|
581
|
+
job,
|
|
582
|
+
void 0,
|
|
583
|
+
`job ${index} workgroupSize`
|
|
584
|
+
)
|
|
585
|
+
})
|
|
586
|
+
);
|
|
439
587
|
}
|
|
440
588
|
});
|
|
441
589
|
pass.end();
|
|
442
590
|
device.queue.submit([encoder.finish()]);
|
|
591
|
+
telemetryDispatches.forEach((sample) => {
|
|
592
|
+
emitOptionalHook(telemetry?.onDispatch, sample, onError);
|
|
593
|
+
});
|
|
594
|
+
emitOptionalHook(
|
|
595
|
+
telemetry?.onTick,
|
|
596
|
+
{
|
|
597
|
+
frameId: currentFrameId,
|
|
598
|
+
tickDurationMs: getNow() - tickStartMs,
|
|
599
|
+
dispatchCount: telemetryDispatches.length,
|
|
600
|
+
workerDispatchCount: telemetryDispatches.filter(
|
|
601
|
+
(sample) => sample.kind === "worker"
|
|
602
|
+
).length,
|
|
603
|
+
jobDispatchCount: telemetryDispatches.filter(
|
|
604
|
+
(sample) => sample.kind === "job"
|
|
605
|
+
).length,
|
|
606
|
+
dispatches: telemetryDispatches
|
|
607
|
+
},
|
|
608
|
+
onError
|
|
609
|
+
);
|
|
443
610
|
if (onTick) {
|
|
444
611
|
onTick();
|
|
445
612
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.js"],"sourcesContent":["import {\n loadSchedulerWgsl as loadSchedulerWgslRaw,\n schedulerModes as workerSchedulerModes,\n} 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 normalizeQueueMode(mode) {\n const resolved = mode ?? \"flat\";\n if (!workerSchedulerModes.includes(resolved)) {\n throw new Error(\n `queueMode must be one of: ${workerSchedulerModes.join(\", \")}.`\n );\n }\n return resolved;\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, queueMode = \"flat\", ...rest } = options ?? {};\n const source = await loadSchedulerWgslRaw({\n mode: normalizeQueueMode(queueMode),\n ...rest,\n });\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\nfunction ensureQueueLifecycleHooks(source) {\n if (/\\bfn\\s+complete_job\\b/.test(source)) {\n return source;\n }\n return `${source}\\n\\nfn complete_job(job_index: u32) {\\n _ = job_index;\\n}`;\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 queueMode = \"flat\",\n } = options ?? {};\n const resolvedQueueMode = normalizeQueueMode(queueMode);\n const rawQueueSource =\n queueWgsl ??\n (await loadSchedulerWgslRaw({\n mode: resolvedQueueMode,\n url: queueUrl,\n fetcher,\n }));\n const bodyRaw = workerWgsl ?? (await loadWorkerWgsl({ fetcher }));\n const compatMap = queueCompat ? getQueueCompatMap(rawQueueSource) : null;\n const queueSource = ensureQueueLifecycleHooks(\n applyCompatMap(rawQueueSource, compatMap)\n );\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 normalizeTelemetryText(value, fallback) {\n if (typeof value !== \"string\") {\n return fallback;\n }\n const trimmed = value.trim();\n return trimmed ? trimmed.slice(0, 120) : fallback;\n}\n\nfunction resolveFrameId(value) {\n if (typeof value === \"function\") {\n return resolveFrameId(value());\n }\n if (typeof value !== \"string\") {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed ? trimmed.slice(0, 120) : undefined;\n}\n\nfunction toVector(workgroups) {\n return { x: workgroups[0], y: workgroups[1], z: workgroups[2] };\n}\n\nfunction resolveTelemetryWorkgroupSize(descriptor, fallback, label) {\n const size =\n descriptor?.workgroupSize == null ? fallback : descriptor.workgroupSize;\n if (size == null) {\n return undefined;\n }\n const normalized =\n typeof size === \"number\" ? normalizeWorkgroups(size, label) : resolveWorkgroups(size, label);\n if (!normalized) {\n return undefined;\n }\n return toVector(normalized);\n}\n\nfunction getNow() {\n if (globalThis.performance && typeof globalThis.performance.now === \"function\") {\n return globalThis.performance.now();\n }\n return Date.now();\n}\n\nfunction reportOptionalError(error, onError) {\n if (!onError) {\n return;\n }\n if (error instanceof Error) {\n onError(error);\n return;\n }\n onError(new Error(String(error)));\n}\n\nfunction emitOptionalHook(callback, payload, onError) {\n if (typeof callback !== \"function\") {\n return;\n }\n try {\n callback(payload);\n } catch (error) {\n reportOptionalError(error, onError);\n }\n}\n\nfunction buildDispatchTelemetrySample({\n kind,\n descriptor,\n index,\n frameId,\n workgroups,\n workgroupSize,\n}) {\n const labelFallback = kind === \"worker\" ? \"worker\" : `job_${index}`;\n const label = normalizeTelemetryText(descriptor?.label, labelFallback);\n return {\n kind,\n index,\n label,\n owner: normalizeTelemetryText(descriptor?.owner, label),\n queueClass: normalizeTelemetryText(descriptor?.queueClass, \"custom\"),\n jobType: normalizeTelemetryText(\n descriptor?.jobType,\n kind === \"worker\" ? \"worker.dispatch\" : label\n ),\n frameId,\n workgroups: toVector(workgroups),\n workgroupSize,\n };\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 frameId,\n telemetry,\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 tickStartMs = getNow();\n const currentFrameId = resolveFrameId(frameId);\n const telemetryDispatches = [];\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 telemetryDispatches.push(\n buildDispatchTelemetrySample({\n kind: \"worker\",\n descriptor: worker,\n index: 0,\n frameId: currentFrameId,\n workgroups: workerGroups,\n workgroupSize: resolveTelemetryWorkgroupSize(\n worker,\n workgroupSize,\n \"worker workgroupSize\"\n ),\n })\n );\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 telemetryDispatches.push(\n buildDispatchTelemetrySample({\n kind: \"job\",\n descriptor: job,\n index,\n frameId: currentFrameId,\n workgroups: groups,\n workgroupSize: resolveTelemetryWorkgroupSize(\n job,\n undefined,\n `job ${index} workgroupSize`\n ),\n })\n );\n }\n });\n\n pass.end();\n device.queue.submit([encoder.finish()]);\n\n telemetryDispatches.forEach((sample) => {\n emitOptionalHook(telemetry?.onDispatch, sample, onError);\n });\n emitOptionalHook(\n telemetry?.onTick,\n {\n frameId: currentFrameId,\n tickDurationMs: getNow() - tickStartMs,\n dispatchCount: telemetryDispatches.length,\n workerDispatchCount: telemetryDispatches.filter(\n (sample) => sample.kind === \"worker\"\n ).length,\n jobDispatchCount: telemetryDispatches.filter(\n (sample) => sample.kind === \"job\"\n ).length,\n dispatches: telemetryDispatches,\n },\n onError\n );\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"]}
|
package/dist/worker.wgsl
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plasius/gpu-worker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "WebGPU worker runtime with a lock-free job queue for WGSL workloads.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"author": "Plasius LTD <development@plasius.co.uk>",
|
|
56
56
|
"license": "Apache-2.0",
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@plasius/gpu-lock-free-queue": "^0.2.
|
|
58
|
+
"@plasius/gpu-lock-free-queue": "^0.2.13"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@eslint/js": "^9.39.1",
|
|
@@ -89,5 +89,8 @@
|
|
|
89
89
|
],
|
|
90
90
|
"overrides": {
|
|
91
91
|
"minimatch": "^10.2.1"
|
|
92
|
+
},
|
|
93
|
+
"engines": {
|
|
94
|
+
"node": ">=24"
|
|
92
95
|
}
|
|
93
96
|
}
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
loadSchedulerWgsl as loadSchedulerWgslRaw,
|
|
3
|
+
schedulerModes as workerSchedulerModes,
|
|
4
|
+
} from "@plasius/gpu-lock-free-queue";
|
|
2
5
|
|
|
3
6
|
export const workerWgslUrl = (() => {
|
|
4
7
|
if (typeof __IMPORT_META_URL__ !== "undefined") {
|
|
@@ -188,6 +191,16 @@ function getQueueCompatMap(source) {
|
|
|
188
191
|
return [{ from: /\bJobMeta\b/g, to: "JobDesc" }];
|
|
189
192
|
}
|
|
190
193
|
|
|
194
|
+
function normalizeQueueMode(mode) {
|
|
195
|
+
const resolved = mode ?? "flat";
|
|
196
|
+
if (!workerSchedulerModes.includes(resolved)) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`queueMode must be one of: ${workerSchedulerModes.join(", ")}.`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
return resolved;
|
|
202
|
+
}
|
|
203
|
+
|
|
191
204
|
function applyCompatMap(source, map) {
|
|
192
205
|
if (!map || map.length === 0) {
|
|
193
206
|
return source;
|
|
@@ -266,8 +279,11 @@ export async function loadWorkerWgsl(options = {}) {
|
|
|
266
279
|
}
|
|
267
280
|
|
|
268
281
|
export async function loadQueueWgsl(options = {}) {
|
|
269
|
-
const { queueCompat = true, ...rest } = options ?? {};
|
|
270
|
-
const source = await
|
|
282
|
+
const { queueCompat = true, queueMode = "flat", ...rest } = options ?? {};
|
|
283
|
+
const source = await loadSchedulerWgslRaw({
|
|
284
|
+
mode: normalizeQueueMode(queueMode),
|
|
285
|
+
...rest,
|
|
286
|
+
});
|
|
271
287
|
if (typeof source !== "string") {
|
|
272
288
|
throw new Error("Failed to load queue WGSL source.");
|
|
273
289
|
}
|
|
@@ -279,6 +295,13 @@ export async function loadQueueWgsl(options = {}) {
|
|
|
279
295
|
return applyCompatMap(source, compatMap);
|
|
280
296
|
}
|
|
281
297
|
|
|
298
|
+
function ensureQueueLifecycleHooks(source) {
|
|
299
|
+
if (/\bfn\s+complete_job\b/.test(source)) {
|
|
300
|
+
return source;
|
|
301
|
+
}
|
|
302
|
+
return `${source}\n\nfn complete_job(job_index: u32) {\n _ = job_index;\n}`;
|
|
303
|
+
}
|
|
304
|
+
|
|
282
305
|
export async function loadJobWgsl(options = {}) {
|
|
283
306
|
const { wgsl, url, fetcher, label } = options ?? {};
|
|
284
307
|
const source = await loadWgslSource({
|
|
@@ -311,12 +334,21 @@ export async function assembleWorkerWgsl(workerWgsl, options = {}) {
|
|
|
311
334
|
jobs,
|
|
312
335
|
debug,
|
|
313
336
|
queueCompat = true,
|
|
337
|
+
queueMode = "flat",
|
|
314
338
|
} = options ?? {};
|
|
339
|
+
const resolvedQueueMode = normalizeQueueMode(queueMode);
|
|
315
340
|
const rawQueueSource =
|
|
316
|
-
queueWgsl ??
|
|
341
|
+
queueWgsl ??
|
|
342
|
+
(await loadSchedulerWgslRaw({
|
|
343
|
+
mode: resolvedQueueMode,
|
|
344
|
+
url: queueUrl,
|
|
345
|
+
fetcher,
|
|
346
|
+
}));
|
|
317
347
|
const bodyRaw = workerWgsl ?? (await loadWorkerWgsl({ fetcher }));
|
|
318
348
|
const compatMap = queueCompat ? getQueueCompatMap(rawQueueSource) : null;
|
|
319
|
-
const queueSource =
|
|
349
|
+
const queueSource = ensureQueueLifecycleHooks(
|
|
350
|
+
applyCompatMap(rawQueueSource, compatMap)
|
|
351
|
+
);
|
|
320
352
|
const preludeRaw =
|
|
321
353
|
preludeWgsl ??
|
|
322
354
|
(preludeUrl
|
|
@@ -394,6 +426,98 @@ function resolveWorkgroups(value, label) {
|
|
|
394
426
|
return normalizeWorkgroups(value, label);
|
|
395
427
|
}
|
|
396
428
|
|
|
429
|
+
function normalizeTelemetryText(value, fallback) {
|
|
430
|
+
if (typeof value !== "string") {
|
|
431
|
+
return fallback;
|
|
432
|
+
}
|
|
433
|
+
const trimmed = value.trim();
|
|
434
|
+
return trimmed ? trimmed.slice(0, 120) : fallback;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function resolveFrameId(value) {
|
|
438
|
+
if (typeof value === "function") {
|
|
439
|
+
return resolveFrameId(value());
|
|
440
|
+
}
|
|
441
|
+
if (typeof value !== "string") {
|
|
442
|
+
return undefined;
|
|
443
|
+
}
|
|
444
|
+
const trimmed = value.trim();
|
|
445
|
+
return trimmed ? trimmed.slice(0, 120) : undefined;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function toVector(workgroups) {
|
|
449
|
+
return { x: workgroups[0], y: workgroups[1], z: workgroups[2] };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function resolveTelemetryWorkgroupSize(descriptor, fallback, label) {
|
|
453
|
+
const size =
|
|
454
|
+
descriptor?.workgroupSize == null ? fallback : descriptor.workgroupSize;
|
|
455
|
+
if (size == null) {
|
|
456
|
+
return undefined;
|
|
457
|
+
}
|
|
458
|
+
const normalized =
|
|
459
|
+
typeof size === "number" ? normalizeWorkgroups(size, label) : resolveWorkgroups(size, label);
|
|
460
|
+
if (!normalized) {
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
return toVector(normalized);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function getNow() {
|
|
467
|
+
if (globalThis.performance && typeof globalThis.performance.now === "function") {
|
|
468
|
+
return globalThis.performance.now();
|
|
469
|
+
}
|
|
470
|
+
return Date.now();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function reportOptionalError(error, onError) {
|
|
474
|
+
if (!onError) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (error instanceof Error) {
|
|
478
|
+
onError(error);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
onError(new Error(String(error)));
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function emitOptionalHook(callback, payload, onError) {
|
|
485
|
+
if (typeof callback !== "function") {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
callback(payload);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
reportOptionalError(error, onError);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function buildDispatchTelemetrySample({
|
|
496
|
+
kind,
|
|
497
|
+
descriptor,
|
|
498
|
+
index,
|
|
499
|
+
frameId,
|
|
500
|
+
workgroups,
|
|
501
|
+
workgroupSize,
|
|
502
|
+
}) {
|
|
503
|
+
const labelFallback = kind === "worker" ? "worker" : `job_${index}`;
|
|
504
|
+
const label = normalizeTelemetryText(descriptor?.label, labelFallback);
|
|
505
|
+
return {
|
|
506
|
+
kind,
|
|
507
|
+
index,
|
|
508
|
+
label,
|
|
509
|
+
owner: normalizeTelemetryText(descriptor?.owner, label),
|
|
510
|
+
queueClass: normalizeTelemetryText(descriptor?.queueClass, "custom"),
|
|
511
|
+
jobType: normalizeTelemetryText(
|
|
512
|
+
descriptor?.jobType,
|
|
513
|
+
kind === "worker" ? "worker.dispatch" : label
|
|
514
|
+
),
|
|
515
|
+
frameId,
|
|
516
|
+
workgroups: toVector(workgroups),
|
|
517
|
+
workgroupSize,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
397
521
|
function setBindGroups(pass, bindGroups) {
|
|
398
522
|
if (!bindGroups) {
|
|
399
523
|
return;
|
|
@@ -429,6 +553,8 @@ export function createWorkerLoop(options = {}) {
|
|
|
429
553
|
label,
|
|
430
554
|
onTick,
|
|
431
555
|
onError,
|
|
556
|
+
frameId,
|
|
557
|
+
telemetry,
|
|
432
558
|
} = options ?? {};
|
|
433
559
|
|
|
434
560
|
if (!device) {
|
|
@@ -446,6 +572,9 @@ export function createWorkerLoop(options = {}) {
|
|
|
446
572
|
|
|
447
573
|
const tick = () => {
|
|
448
574
|
try {
|
|
575
|
+
const tickStartMs = getNow();
|
|
576
|
+
const currentFrameId = resolveFrameId(frameId);
|
|
577
|
+
const telemetryDispatches = [];
|
|
449
578
|
const encoder = device.createCommandEncoder();
|
|
450
579
|
const pass = encoder.beginComputePass(
|
|
451
580
|
label ? { label } : undefined
|
|
@@ -465,6 +594,20 @@ export function createWorkerLoop(options = {}) {
|
|
|
465
594
|
|
|
466
595
|
if (workerGroups[0] > 0) {
|
|
467
596
|
pass.dispatchWorkgroups(...workerGroups);
|
|
597
|
+
telemetryDispatches.push(
|
|
598
|
+
buildDispatchTelemetrySample({
|
|
599
|
+
kind: "worker",
|
|
600
|
+
descriptor: worker,
|
|
601
|
+
index: 0,
|
|
602
|
+
frameId: currentFrameId,
|
|
603
|
+
workgroups: workerGroups,
|
|
604
|
+
workgroupSize: resolveTelemetryWorkgroupSize(
|
|
605
|
+
worker,
|
|
606
|
+
workgroupSize,
|
|
607
|
+
"worker workgroupSize"
|
|
608
|
+
),
|
|
609
|
+
})
|
|
610
|
+
);
|
|
468
611
|
}
|
|
469
612
|
|
|
470
613
|
jobs.forEach((job, index) => {
|
|
@@ -482,12 +625,46 @@ export function createWorkerLoop(options = {}) {
|
|
|
482
625
|
}
|
|
483
626
|
if (groups[0] > 0) {
|
|
484
627
|
pass.dispatchWorkgroups(...groups);
|
|
628
|
+
telemetryDispatches.push(
|
|
629
|
+
buildDispatchTelemetrySample({
|
|
630
|
+
kind: "job",
|
|
631
|
+
descriptor: job,
|
|
632
|
+
index,
|
|
633
|
+
frameId: currentFrameId,
|
|
634
|
+
workgroups: groups,
|
|
635
|
+
workgroupSize: resolveTelemetryWorkgroupSize(
|
|
636
|
+
job,
|
|
637
|
+
undefined,
|
|
638
|
+
`job ${index} workgroupSize`
|
|
639
|
+
),
|
|
640
|
+
})
|
|
641
|
+
);
|
|
485
642
|
}
|
|
486
643
|
});
|
|
487
644
|
|
|
488
645
|
pass.end();
|
|
489
646
|
device.queue.submit([encoder.finish()]);
|
|
490
647
|
|
|
648
|
+
telemetryDispatches.forEach((sample) => {
|
|
649
|
+
emitOptionalHook(telemetry?.onDispatch, sample, onError);
|
|
650
|
+
});
|
|
651
|
+
emitOptionalHook(
|
|
652
|
+
telemetry?.onTick,
|
|
653
|
+
{
|
|
654
|
+
frameId: currentFrameId,
|
|
655
|
+
tickDurationMs: getNow() - tickStartMs,
|
|
656
|
+
dispatchCount: telemetryDispatches.length,
|
|
657
|
+
workerDispatchCount: telemetryDispatches.filter(
|
|
658
|
+
(sample) => sample.kind === "worker"
|
|
659
|
+
).length,
|
|
660
|
+
jobDispatchCount: telemetryDispatches.filter(
|
|
661
|
+
(sample) => sample.kind === "job"
|
|
662
|
+
).length,
|
|
663
|
+
dispatches: telemetryDispatches,
|
|
664
|
+
},
|
|
665
|
+
onError
|
|
666
|
+
);
|
|
667
|
+
|
|
491
668
|
if (onTick) {
|
|
492
669
|
onTick();
|
|
493
670
|
}
|