Summary
Calling one kernel first with a plain Array and then with a Float32Array (or the reverse) throws on webgl/webgl2:
this kernel cannot run the arguments it was given (argumentMismatch); it did not
settle on a kernel for them after 4 attempts. Create a separate kernel for this
call's argument types.
The same code returns the correct answer on cpu, so the two backends disagree about whether this is legal.
This is adjacent to 5d1b70b ("reusing a kernel with a different argument type gives that argument's answer") but is a different failure: that one fixed a silently stale result, this one never converges and errors out. Erroring beats returning the previous call's framebuffer, so 2.20 is an improvement — but a plain array and a typed array holding the same numbers seem like they should be interchangeable.
Reproduction
gpu.js 2.20.0, Chrome 152 on macOS, WebGL2Kernel (ANGLE Metal, Apple M1 Max).
const gpu = new GPU({ mode }); // 'gpu' or 'cpu'
const k = gpu.createKernel(function (a) {
return a[this.thread.x] * 2;
}, { output: [4] });
k([1, 2, 3, 4]); // [2, 4, 6, 8]
k(Float32Array.from([5, 6, 7, 8])); // expected [10, 12, 14, 16]
| mode |
first call |
second call |
cpu |
[2, 4, 6, 8] |
[10, 12, 14, 16] ✅ |
gpu (WebGL2) |
[2, 4, 6, 8] |
throws argumentMismatch ❌ |
Both orders fail (Float32Array first, then Array, fails identically).
Why it can't settle
The type layer and the value layer disagree, so the switch loop has nothing to converge on:
utils.getVariableType returns 'Array' for both, since utils.isArray is !isNaN(array.length) (src/utils.js:115, :165-169). The kernel's argument-type signature is therefore identical for the two calls.
- But the GL kernel value rejects on constructor identity:
if (value.constructor !== this.initialValueConstructor) { this.onUpdateValueMismatch(value.constructor); return; } (src/backend/web-gl/kernel-value/single-array.js:31, same in single-array1d-i.js:36 and siblings).
kernel-run-shortcut.js:22-35 then retries up to MAX_SWITCHES (4). Because the recorded type still says 'Array', each switched-to kernel is built with the same signature, its value again records initialValueConstructor = Array, rejects again, and requests another switch — hence "did not settle after 4 attempts".
cpu has no equivalent constructor check, which is why it just works.
Possible directions
- Tolerate array-likes in the value.
updateValue immediately does utils.flattenTo(value, this.uploadValue), which already handles plain and typed arrays alike (src/utils.js:348), so for the numeric single-array values the constructor identity may not need to be load-bearing at all — the upload path never sees the original container.
- Make the switch produce a genuinely different kernel by including the container constructor in the argument-type signature, so the rebuilt kernel binds to the new constructor instead of re-deriving the old one.
- Fail fast with a clear message if interchangeability isn't intended — the current error costs four kernel rebuilds before surfacing, and doesn't say which argument or which two constructors disagreed.
Happy to send a PR if you have a preference between (1) and (2).
Impact
Found while writing GPGPU course material where a kernel is naturally called with a literal array first (k([1, 2, 3, 4])) and later with a Float32Array returned by a previous kernel — a very common shape when chaining passes. The workaround is to normalise every argument (Float32Array.from(...)) at every call site, which is easy once you know, and quite mysterious until then.
Summary
Calling one kernel first with a plain
Arrayand then with aFloat32Array(or the reverse) throws onwebgl/webgl2:The same code returns the correct answer on
cpu, so the two backends disagree about whether this is legal.This is adjacent to 5d1b70b ("reusing a kernel with a different argument type gives that argument's answer") but is a different failure: that one fixed a silently stale result, this one never converges and errors out. Erroring beats returning the previous call's framebuffer, so 2.20 is an improvement — but a plain array and a typed array holding the same numbers seem like they should be interchangeable.
Reproduction
gpu.js 2.20.0, Chrome 152 on macOS,
WebGL2Kernel(ANGLE Metal, Apple M1 Max).cpu[2, 4, 6, 8][10, 12, 14, 16]✅gpu(WebGL2)[2, 4, 6, 8]argumentMismatch❌Both orders fail (
Float32Arrayfirst, thenArray, fails identically).Why it can't settle
The type layer and the value layer disagree, so the switch loop has nothing to converge on:
utils.getVariableTypereturns'Array'for both, sinceutils.isArrayis!isNaN(array.length)(src/utils.js:115,:165-169). The kernel's argument-type signature is therefore identical for the two calls.if (value.constructor !== this.initialValueConstructor) { this.onUpdateValueMismatch(value.constructor); return; }(src/backend/web-gl/kernel-value/single-array.js:31, same insingle-array1d-i.js:36and siblings).kernel-run-shortcut.js:22-35then retries up toMAX_SWITCHES(4). Because the recorded type still says'Array', each switched-to kernel is built with the same signature, its value again recordsinitialValueConstructor = Array, rejects again, and requests another switch — hence "did not settle after 4 attempts".cpuhas no equivalent constructor check, which is why it just works.Possible directions
updateValueimmediately doesutils.flattenTo(value, this.uploadValue), which already handles plain and typed arrays alike (src/utils.js:348), so for the numeric single-array values the constructor identity may not need to be load-bearing at all — the upload path never sees the original container.Happy to send a PR if you have a preference between (1) and (2).
Impact
Found while writing GPGPU course material where a kernel is naturally called with a literal array first (
k([1, 2, 3, 4])) and later with aFloat32Arrayreturned by a previous kernel — a very common shape when chaining passes. The workaround is to normalise every argument (Float32Array.from(...)) at every call site, which is easy once you know, and quite mysterious until then.