@plasius/gpu-worker
Advanced tools
+23
-1
@@ -23,2 +23,21 @@ # Changelog | ||
| ## [0.1.1] - 2026-01-23 | ||
| - **Added** | ||
| - `assembleWorkerWgsl` now accepts optional queue WGSL overrides for local demos. | ||
| - **Changed** | ||
| - Demo now simulates millions of instanced objects with range checks, bounding spheres/AABBs, and face contact stats. | ||
| - **Breaking:** Queue bindings updated to remove the payload arena and use payload offsets into caller-managed buffers. | ||
| - Demo updated to match the new payload-handle layout. | ||
| - **Breaking:** Queue bindings now use job metadata and a variable-size payload arena. | ||
| - Worker job payloads are read from the output payload buffer using `output_stride`. | ||
| - Demo updated to emit job metadata and payload buffers. | ||
| - **Fixed** | ||
| - Demo can load a local queue WGSL to avoid mismatched dependency versions. | ||
| - **Security** | ||
| - (placeholder) | ||
| ## [0.1.0] - 2026-01-22 | ||
@@ -73,4 +92,7 @@ | ||
| [Unreleased]: https://github.com/Plasius-LTD/gpu-worker/compare/v0.1.0...HEAD | ||
| [Unreleased]: https://github.com/Plasius-LTD/gpu-worker/compare/v0.1.1...HEAD | ||
| [0.1.0-beta.1]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.0-beta.1 | ||
| [0.1.0]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.0 | ||
| [0.2.0]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.2.0 | ||
| [0.3.0]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.3.0 | ||
| [0.1.1]: https://github.com/Plasius-LTD/gpu-worker/releases/tag/v0.1.1 |
+4
-3
@@ -43,6 +43,7 @@ var __defProp = Object.defineProperty; | ||
| } | ||
| async function assembleWorkerWgsl(workerWgsl) { | ||
| const queueWgsl = await (0, import_gpu_lock_free_queue.loadQueueWgsl)(); | ||
| async function assembleWorkerWgsl(workerWgsl, options = {}) { | ||
| const { queueWgsl, queueUrl, fetcher } = options ?? {}; | ||
| const queueSource = queueWgsl ?? await (0, import_gpu_lock_free_queue.loadQueueWgsl)({ url: queueUrl, fetcher }); | ||
| const body = workerWgsl ?? await loadWorkerWgsl(); | ||
| return `${queueWgsl} | ||
| return `${queueSource} | ||
@@ -49,0 +50,0 @@ ${body}`; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/index.js"],"sourcesContent":["import { loadQueueWgsl } from \"@plasius/gpu-lock-free-queue\";\n\nexport const workerWgslUrl = (() => {\n if (typeof __IMPORT_META_URL__ !== \"undefined\") {\n return new URL(\"./worker.wgsl\", __IMPORT_META_URL__);\n }\n if (typeof __filename !== \"undefined\" && typeof require !== \"undefined\") {\n const { pathToFileURL } = require(\"node:url\");\n return new URL(\"./worker.wgsl\", pathToFileURL(__filename));\n }\n const base =\n typeof process !== \"undefined\" && process.cwd\n ? `file://${process.cwd()}/`\n : \"file:///\";\n return new URL(\"./worker.wgsl\", base);\n})();\n\nexport async function loadWorkerWgsl() {\n const response = await fetch(workerWgslUrl);\n return response.text();\n}\n\nexport async function assembleWorkerWgsl(workerWgsl) {\n const queueWgsl = await loadQueueWgsl();\n const body = workerWgsl ?? (await loadWorkerWgsl());\n return `${queueWgsl}\\n\\n${body}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iCAA8B;AAEvB,IAAM,iBAAiB,MAAM;AAClC,MAAI,OAA4C;AAC9C,WAAO,IAAI,IAAI,iBAAiB,MAAmB;AAAA,EACrD;AACA,MAAI,OAAO,eAAe,eAAe,OAAO,YAAY,aAAa;AACvE,UAAM,EAAE,cAAc,IAAI,QAAQ,KAAU;AAC5C,WAAO,IAAI,IAAI,iBAAiB,cAAc,UAAU,CAAC;AAAA,EAC3D;AACA,QAAM,OACJ,OAAO,YAAY,eAAe,QAAQ,MACtC,UAAU,QAAQ,IAAI,CAAC,MACvB;AACN,SAAO,IAAI,IAAI,iBAAiB,IAAI;AACtC,GAAG;AAEH,eAAsB,iBAAiB;AACrC,QAAM,WAAW,MAAM,MAAM,aAAa;AAC1C,SAAO,SAAS,KAAK;AACvB;AAEA,eAAsB,mBAAmB,YAAY;AACnD,QAAM,YAAY,UAAM,0CAAc;AACtC,QAAM,OAAO,cAAe,MAAM,eAAe;AACjD,SAAO,GAAG,SAAS;AAAA;AAAA,EAAO,IAAI;AAChC;","names":[]} | ||
| {"version":3,"sources":["../src/index.js"],"sourcesContent":["import { loadQueueWgsl } from \"@plasius/gpu-lock-free-queue\";\n\nexport const workerWgslUrl = (() => {\n if (typeof __IMPORT_META_URL__ !== \"undefined\") {\n return new URL(\"./worker.wgsl\", __IMPORT_META_URL__);\n }\n if (typeof __filename !== \"undefined\" && typeof require !== \"undefined\") {\n const { pathToFileURL } = require(\"node:url\");\n return new URL(\"./worker.wgsl\", pathToFileURL(__filename));\n }\n const base =\n typeof process !== \"undefined\" && process.cwd\n ? `file://${process.cwd()}/`\n : \"file:///\";\n return new URL(\"./worker.wgsl\", base);\n})();\n\nexport async function loadWorkerWgsl() {\n const response = await fetch(workerWgslUrl);\n return response.text();\n}\n\nexport async function assembleWorkerWgsl(workerWgsl, options = {}) {\n const { queueWgsl, queueUrl, fetcher } = options ?? {};\n const queueSource =\n queueWgsl ?? (await loadQueueWgsl({ url: queueUrl, fetcher }));\n const body = workerWgsl ?? (await loadWorkerWgsl());\n return `${queueSource}\\n\\n${body}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iCAA8B;AAEvB,IAAM,iBAAiB,MAAM;AAClC,MAAI,OAA4C;AAC9C,WAAO,IAAI,IAAI,iBAAiB,MAAmB;AAAA,EACrD;AACA,MAAI,OAAO,eAAe,eAAe,OAAO,YAAY,aAAa;AACvE,UAAM,EAAE,cAAc,IAAI,QAAQ,KAAU;AAC5C,WAAO,IAAI,IAAI,iBAAiB,cAAc,UAAU,CAAC;AAAA,EAC3D;AACA,QAAM,OACJ,OAAO,YAAY,eAAe,QAAQ,MACtC,UAAU,QAAQ,IAAI,CAAC,MACvB;AACN,SAAO,IAAI,IAAI,iBAAiB,IAAI;AACtC,GAAG;AAEH,eAAsB,iBAAiB;AACrC,QAAM,WAAW,MAAM,MAAM,aAAa;AAC1C,SAAO,SAAS,KAAK;AACvB;AAEA,eAAsB,mBAAmB,YAAY,UAAU,CAAC,GAAG;AACjE,QAAM,EAAE,WAAW,UAAU,QAAQ,IAAI,WAAW,CAAC;AACrD,QAAM,cACJ,aAAc,UAAM,0CAAc,EAAE,KAAK,UAAU,QAAQ,CAAC;AAC9D,QAAM,OAAO,cAAe,MAAM,eAAe;AACjD,SAAO,GAAG,WAAW;AAAA;AAAA,EAAO,IAAI;AAClC;","names":[]} |
+4
-3
@@ -25,6 +25,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { | ||
| } | ||
| async function assembleWorkerWgsl(workerWgsl) { | ||
| const queueWgsl = await loadQueueWgsl(); | ||
| async function assembleWorkerWgsl(workerWgsl, options = {}) { | ||
| const { queueWgsl, queueUrl, fetcher } = options ?? {}; | ||
| const queueSource = queueWgsl ?? await loadQueueWgsl({ url: queueUrl, fetcher }); | ||
| const body = workerWgsl ?? await loadWorkerWgsl(); | ||
| return `${queueWgsl} | ||
| return `${queueSource} | ||
@@ -31,0 +32,0 @@ ${body}`; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/index.js"],"sourcesContent":["import { loadQueueWgsl } from \"@plasius/gpu-lock-free-queue\";\n\nexport const workerWgslUrl = (() => {\n if (typeof __IMPORT_META_URL__ !== \"undefined\") {\n return new URL(\"./worker.wgsl\", __IMPORT_META_URL__);\n }\n if (typeof __filename !== \"undefined\" && typeof require !== \"undefined\") {\n const { pathToFileURL } = require(\"node:url\");\n return new URL(\"./worker.wgsl\", pathToFileURL(__filename));\n }\n const base =\n typeof process !== \"undefined\" && process.cwd\n ? `file://${process.cwd()}/`\n : \"file:///\";\n return new URL(\"./worker.wgsl\", base);\n})();\n\nexport async function loadWorkerWgsl() {\n const response = await fetch(workerWgslUrl);\n return response.text();\n}\n\nexport async function assembleWorkerWgsl(workerWgsl) {\n const queueWgsl = await loadQueueWgsl();\n const body = workerWgsl ?? (await loadWorkerWgsl());\n return `${queueWgsl}\\n\\n${body}`;\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,qBAAqB;AAEvB,IAAM,iBAAiB,MAAM;AAClC,MAAI,OAAO,oBAAwB,aAAa;AAC9C,WAAO,IAAI,IAAI,iBAAiB,eAAmB;AAAA,EACrD;AACA,MAAI,OAAO,eAAe,eAAe,OAAO,cAAY,aAAa;AACvE,UAAM,EAAE,cAAc,IAAI,UAAQ,KAAU;AAC5C,WAAO,IAAI,IAAI,iBAAiB,cAAc,UAAU,CAAC;AAAA,EAC3D;AACA,QAAM,OACJ,OAAO,YAAY,eAAe,QAAQ,MACtC,UAAU,QAAQ,IAAI,CAAC,MACvB;AACN,SAAO,IAAI,IAAI,iBAAiB,IAAI;AACtC,GAAG;AAEH,eAAsB,iBAAiB;AACrC,QAAM,WAAW,MAAM,MAAM,aAAa;AAC1C,SAAO,SAAS,KAAK;AACvB;AAEA,eAAsB,mBAAmB,YAAY;AACnD,QAAM,YAAY,MAAM,cAAc;AACtC,QAAM,OAAO,cAAe,MAAM,eAAe;AACjD,SAAO,GAAG,SAAS;AAAA;AAAA,EAAO,IAAI;AAChC;","names":[]} | ||
| {"version":3,"sources":["../src/index.js"],"sourcesContent":["import { loadQueueWgsl } from \"@plasius/gpu-lock-free-queue\";\n\nexport const workerWgslUrl = (() => {\n if (typeof __IMPORT_META_URL__ !== \"undefined\") {\n return new URL(\"./worker.wgsl\", __IMPORT_META_URL__);\n }\n if (typeof __filename !== \"undefined\" && typeof require !== \"undefined\") {\n const { pathToFileURL } = require(\"node:url\");\n return new URL(\"./worker.wgsl\", pathToFileURL(__filename));\n }\n const base =\n typeof process !== \"undefined\" && process.cwd\n ? `file://${process.cwd()}/`\n : \"file:///\";\n return new URL(\"./worker.wgsl\", base);\n})();\n\nexport async function loadWorkerWgsl() {\n const response = await fetch(workerWgslUrl);\n return response.text();\n}\n\nexport async function assembleWorkerWgsl(workerWgsl, options = {}) {\n const { queueWgsl, queueUrl, fetcher } = options ?? {};\n const queueSource =\n queueWgsl ?? (await loadQueueWgsl({ url: queueUrl, fetcher }));\n const body = workerWgsl ?? (await loadWorkerWgsl());\n return `${queueSource}\\n\\n${body}`;\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,qBAAqB;AAEvB,IAAM,iBAAiB,MAAM;AAClC,MAAI,OAAO,oBAAwB,aAAa;AAC9C,WAAO,IAAI,IAAI,iBAAiB,eAAmB;AAAA,EACrD;AACA,MAAI,OAAO,eAAe,eAAe,OAAO,cAAY,aAAa;AACvE,UAAM,EAAE,cAAc,IAAI,UAAQ,KAAU;AAC5C,WAAO,IAAI,IAAI,iBAAiB,cAAc,UAAU,CAAC;AAAA,EAC3D;AACA,QAAM,OACJ,OAAO,YAAY,eAAe,QAAQ,MACtC,UAAU,QAAQ,IAAI,CAAC,MACvB;AACN,SAAO,IAAI,IAAI,iBAAiB,IAAI;AACtC,GAAG;AAEH,eAAsB,iBAAiB;AACrC,QAAM,WAAW,MAAM,MAAM,aAAa;AAC1C,SAAO,SAAS,KAAK;AACvB;AAEA,eAAsB,mBAAmB,YAAY,UAAU,CAAC,GAAG;AACjE,QAAM,EAAE,WAAW,UAAU,QAAQ,IAAI,WAAW,CAAC;AACrD,QAAM,cACJ,aAAc,MAAM,cAAc,EAAE,KAAK,UAAU,QAAQ,CAAC;AAC9D,QAAM,OAAO,cAAe,MAAM,eAAe;AACjD,SAAO,GAAG,WAAW;AAAA;AAAA,EAAO,IAAI;AAClC;","names":[]} |
+3
-2
| { | ||
| "name": "@plasius/gpu-worker", | ||
| "version": "0.1.0", | ||
| "version": "0.1.1", | ||
| "description": "WebGPU worker runtime with a lock-free job queue for WGSL workloads.", | ||
@@ -29,2 +29,3 @@ "type": "module", | ||
| "demo": "python3 -m http.server", | ||
| "demo:https": "node demo/serve-https.js", | ||
| "test": "npm run test:unit", | ||
@@ -50,3 +51,3 @@ "test:unit": "node --test", | ||
| "dependencies": { | ||
| "@plasius/gpu-lock-free-queue": "^0.1.1-beta.1" | ||
| "@plasius/gpu-lock-free-queue": "^0.2.1" | ||
| }, | ||
@@ -53,0 +54,0 @@ "devDependencies": { |
+22
-2
@@ -25,2 +25,5 @@ # @plasius/gpu-worker | ||
| `assembleWorkerWgsl` also accepts an optional second argument to override the queue WGSL source: | ||
| `assembleWorkerWgsl(workerWgsl, { queueWgsl, queueUrl, fetcher })`. | ||
| ## What this is | ||
@@ -37,3 +40,3 @@ - A minimal GPU worker layer that combines a lock-free queue with user WGSL jobs. | ||
| npm install | ||
| python3 -m http.server | ||
| npm run demo | ||
| ``` | ||
@@ -43,2 +46,17 @@ | ||
| ### HTTPS demo | ||
| WebGPU requires a secure context. For non-localhost access, run the HTTPS demo server. | ||
| ``` | ||
| mkdir -p demo/certs | ||
| mkcert -key-file demo/certs/localhost-key.pem -cert-file demo/certs/localhost.pem localhost 127.0.0.1 ::1 | ||
| # or | ||
| openssl req -x509 -newkey rsa:2048 -nodes -keyout demo/certs/localhost-key.pem -out demo/certs/localhost.pem -days 365 -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" | ||
| npm run demo:https | ||
| ``` | ||
| Then open `https://localhost:8443/demo/`. If you use a different hostname/IP, generate a | ||
| certificate for that name and set `DEMO_HOST`, `DEMO_PORT`, `DEMO_TLS_CERT`, and | ||
| `DEMO_TLS_KEY` as needed. | ||
| ## Build Outputs | ||
@@ -55,2 +73,4 @@ | ||
| ## Job shape | ||
| Jobs are `u32` indices into a fixed workload array (tiles, particles, etc). Keep job data fixed-size; use indices into a separate payload buffer for variable payloads. | ||
| 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. | ||
| Set `output_stride` in queue params to the maximum payload size you want copied out for a job; `job_type` can be used by schedulers to route work to different kernels. The queue mirrors input metadata into `output_jobs` and optionally copies payloads into `output_payloads`. |
+5
-3
@@ -23,6 +23,8 @@ import { loadQueueWgsl } from "@plasius/gpu-lock-free-queue"; | ||
| export async function assembleWorkerWgsl(workerWgsl) { | ||
| const queueWgsl = await loadQueueWgsl(); | ||
| export async function assembleWorkerWgsl(workerWgsl, options = {}) { | ||
| const { queueWgsl, queueUrl, fetcher } = options ?? {}; | ||
| const queueSource = | ||
| queueWgsl ?? (await loadQueueWgsl({ url: queueUrl, fetcher })); | ||
| const body = workerWgsl ?? (await loadWorkerWgsl()); | ||
| return `${queueWgsl}\n\n${body}`; | ||
| return `${queueSource}\n\n${body}`; | ||
| } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
51546
22.6%115
3.6%73
37.74%+ Added
+ Added
- Removed