github gpujs/gpu.js 2.21.0

4 hours ago

The WebGPU backend finishes what 2.20.0 started — graphical mode and Math.random land, the deferred list shrinks to three — and kernels survive their bundler. One breaking change, and it is the correction of a 2.20.0 mistake, explained below.

Breaking: getPixels() returns a Promise under the async contract

We made a mistake in 2.20.0. The async contract's rule is that every result-bearing call returns a Promise uniformly, so the same code runs on every backend — that is the entire point of mode: 'async'. getPixels() should have followed that rule from the day the contract shipped, and it did not: it kept returning raw bytes on the GL and CPU backends even on an asyncMode kernel. The moment WebGPU graphical mode existed, the inconsistency became a portability bug: WebGPU readback is genuinely asynchronous, so its getPixels() must return a Promise, and the same kernel code behaved differently by backend.

2.21.0 corrects it: under the async contract (asyncMode: true or mode: 'async'), getPixels() returns a Promise on every backend — resolved immediately on GL and CPU, genuinely asynchronous on WebGPU. await kernel.getPixels() is the portable form, and the v3 migration guide now says so (step 5).

Who is affected: only code that calls getPixels() on an asyncMode kernel — a combination introduced in 2.20.0. Add await. Kernels outside the async contract are byte-for-byte unchanged.

WebGPU: graphical mode

graphical: true works on mode: 'webgpu'. The kernel stays a compute pass writing RGBA through this.color(...); a fixed fullscreen-triangle render pass presents it to the kernel's canvas. Orientation matches the GL backends (thread.y = 0 is the bottom row). Presentation needs no readback, so an un-awaited kernel() per animation frame works; getPixels() returns a Promise, as above.

Under mode: 'async', a graphical kernel binds to its backend at creation — a canvas is permanently committed to its first context type, so the backend must be decided before kernel.canvas is ever exposed. await GPU.isWebGPUAvailable() before createKernel to guarantee the probe has settled; either way the canvas never changes identity.

WebGPU: Math.random()

Implemented as PCG in integer WGSL — and therefore better than the GL implementation, not just equal to it: with randomSeed, the stream is bit-exact across runs and across drivers, a promise the GL backends' float-hash generator cannot make. Unseeded kernels draw differently every run, as on GL. Verified on real hardware: 4096 draws, mean 0.4997, full per-thread decorrelation, seeded streams byte-identical across repeat runs.

Kernels survive their bundler

Three fixes for code that works in development and breaks minified:

  • esbuild/terser statement folding compiles everywherec && (x = 1), comma chains hanging off a return, ternaries of assignments, comma for-headers: a de-minification pass unfolds them back into statements before translation, on every backend. Ten fixtures pinned exactly as esbuild emits them.
  • addFunction survives name-stripping (#863): settings.name now works for function sources, Function.prototype.name covers inferred names, and object-form argumentTypes whose keys match no parameter (the minifier renamed them) throw with a pointer to the position-based array form instead of silently computing wrong values.
  • WGSL reserved words can be helper names (#861): every user function is name-mangled unconditionally — filter, get, set, type, self and sixty more now work as addFunction names on WebGPU.

Correctness fixes

  • One kernel's arguments no longer clobber another's array constants (#862) — texture units are context state, but each kernel counted its own from zero; constants were bound once and silently read the other kernel's data from the second call on. Constants now rebind per run. This one was invisible without cross-checking results, and warmed-up harnesses only ever saw the corrupted state.
  • A kernel accepts an Array after a Float32Array and vice versa (#857) — same shape, same upload; constructor identity was never load-bearing.
  • A kernel local may shadow a constant's name (#858) — const n = this.constants.n no longer dies on the CPU backend.
  • One banner in the minified bundles, not two — the minify step prepended a banner terser had already preserved.

Installing

npm install gpu.js@2.21.0
<script src="https://unpkg.com/gpu.js@2.21.0/dist/gpu-browser.min.js"></script>

Don't miss a new gpu.js release

NewReleases is sending notifications on new releases.