Summary
On the WebGL and WebGL2 backends, an integer-valued number of 1e21 or larger produces a fragment shader that fails to compile:
Error compiling fragment shader: ERROR: 0:518: '.0' : syntax error
The generated GLSL contains 1e+30.0, which is not a valid float literal.
1e30 is a natural thing to write — it is the usual "infinitely far away" sentinel in a ray marcher or path tracer, which is where I hit it.
Reproduction
Verified on gpu.js 2.21.0, Chrome 150, Apple M1 Max (apple metal-3), via dist/gpu-browser.js.
// FAILS — literal in the kernel body
new GPU({ mode: 'webgl2' })
.createKernel(function () { return 1e30; })
.setOutput([1])();
// FAILS — value passed through setConstants
new GPU({ mode: 'webgl2' })
.createKernel(function () { return this.constants.big * 1; })
.setConstants({ big: 1e30 })
.setOutput([1])();
// OK — runtime argument, uploaded as a uniform rather than emitted into source
new GPU({ mode: 'webgl2' })
.createKernel(function (v) { return v * 1; })
.setOutput([1])(1e30);
Affected backends
| backend |
1e30 |
1e21 |
1e20 |
123456 |
webgl2 |
FAIL |
FAIL |
ok |
ok |
webgl |
FAIL |
FAIL |
ok |
ok |
webgpu |
ok |
ok |
ok |
ok |
Cause
The threshold is exactly 1e21, which is where Number.prototype.toString() switches to exponential notation:
(1e20).toString() // "100000000000000000000" -> "100000000000000000000.0" valid
(1e21).toString() // "1e+21" -> "1e+21.0" invalid
src/backend/web-gl/function-node.js (around line 237) appends .0 whenever the value is an integer:
if (Number.isInteger(ast.value)) {
...
retArr.push(`${ast.value}.0`);
Number.isInteger(1e30) is true, so the .0 is appended to a string that is already in exponential form.
Suggested fix
Only append .0 when the rendered string does not already make the literal a float. GLSL accepts 1e30 as a float literal, so the exponential form can pass through unchanged:
const s = `${ast.value}`;
retArr.push(/[.eE]/.test(s) ? s : `${s}.0`);
The same ${value}.0 pattern appears in src/backend/function-node.js and src/backend/web-gpu/function-node.js; I only observed the failure on the two WebGL backends, so I have not checked whether the others are reachable with the same input.
Workaround
Keep sentinels below 1e21 — 1e20 renders as digits and works, and for most scenes something like 1e9 is plenty.
Summary
On the WebGL and WebGL2 backends, an integer-valued number of
1e21or larger produces a fragment shader that fails to compile:The generated GLSL contains
1e+30.0, which is not a valid float literal.1e30is a natural thing to write — it is the usual "infinitely far away" sentinel in a ray marcher or path tracer, which is where I hit it.Reproduction
Verified on gpu.js 2.21.0, Chrome 150, Apple M1 Max (
apple metal-3), viadist/gpu-browser.js.Affected backends
1e301e211e20123456webgl2webglwebgpuCause
The threshold is exactly
1e21, which is whereNumber.prototype.toString()switches to exponential notation:src/backend/web-gl/function-node.js(around line 237) appends.0whenever the value is an integer:Number.isInteger(1e30)istrue, so the.0is appended to a string that is already in exponential form.Suggested fix
Only append
.0when the rendered string does not already make the literal a float. GLSL accepts1e30as a float literal, so the exponential form can pass through unchanged:The same
${value}.0pattern appears insrc/backend/function-node.jsandsrc/backend/web-gpu/function-node.js; I only observed the failure on the two WebGL backends, so I have not checked whether the others are reachable with the same input.Workaround
Keep sentinels below
1e21—1e20renders as digits and works, and for most scenes something like1e9is plenty.