Eight bug fixes, several of them long-standing, found by finally running the full browser suite on real GPUs and real Windows machines.
Fixed
- Kernels indexing one array by another returned garbage on Windows (#300, open since 2018).
lookup[input[this.thread.x]]compiles to a texture read nested inside another's argument list, and FXC — the HLSL compiler every Windows browser hands ANGLE's output to — miscompiles that shape: the outer read samples the wrong texture. The compiler now hoists the inner read into a temporary, the shape FXC gets right. This also fixes "integer division as index" kernels (array[a[x]/b[x]]), which were the same bug through a different door. Diagnosed down to the DXBC bytecode; workaround also submitted upstream to ANGLE (issue). - Every video passed to a WebGL kernel sampled arbitrary texels. The texture was sized from
video.width/height— the presentational attributes, 0 on a detached element — instead ofvideoWidth/videoHeight, so the shader divided by a 0×0 size. Constant indices often looked right by luck; computed ones returned different garbage per run. kernel.toString()dropped everyelsebranch while flattening helper functions — regenerated kernels parsed, ran, and silently computed wrong answers. Also fixed in the same area: a crash on barelet x;declarations, expression-bodied arrow functions being corrupted, and mapped kernel outputs reading back as all zeros (the generatedtoArray()reused the draw framebuffer, where subkernel textures are still bound to other attachments).- The CPU backend read images and video a shade off — its readback canvas lacked
willReadFrequently, so the browser kept it GPU-backed andgetImageDatawasn't bit-exact with the decoded image (253 came back 252). CPU and WebGL backends now agree exactly. - Internally-defined matrices couldn't be indexed dynamically on WebGL1 —
getMatrix()[y][x]failed shader compilation with "Index expression can only contain const or loop symbols" (GLSL ES 1.00 restriction). The matrix is now walked with loop counters, which are legal index expressions; constant indices keep the direct form. GPU.destroy()leaked WebGL contexts and kernels (from earlier in this cycle).- Bitwise NOT ignored the sign of its operand;
/truncated to an integer divide on most desktop GPUs (from earlier in this cycle).
Build
- gulp is gone:
npm run makenow runs plain Node scripts, bundling with rolldown instead of browserify.
Testing
The browser suite (3,699 tests, previously skipped in CI's Node-only run) now runs in real Chromium via Playwright (npm run test:browser), survives crashing tests instead of hanging, and runs on demand across BrowserStack's desktop matrix (npm run test:browserstack:qunit). Suite status: 0 failures on macOS Node, 0 on real Apple GPU, 0 on Windows Chrome and Edge on BrowserStack.