|
| 1 | +const { assert, skip, test, module: describe } = require('qunit'); |
| 2 | +const { GPU } = require('../../src'); |
| 3 | + |
| 4 | +describe('issue #857'); |
| 5 | + |
| 6 | +// A plain Array and a Float32Array holding the same numbers flatten into the |
| 7 | +// same upload buffer, so one kernel should take either. The GL kernel values |
| 8 | +// rejected on constructor identity instead, and because both spell their type |
| 9 | +// 'Array' the switched-to kernel had the same signature and rejected it again |
| 10 | +// -- so the call never settled and threw after four rebuilds. cpu never had |
| 11 | +// the check and always worked. |
| 12 | + |
| 13 | +// What is under test is that the container does not matter, not how exactly a |
| 14 | +// precision round-trips: the unsigned encoder is bit-exact on some drivers and |
| 15 | +// off by ~1e-6 on others (SwiftShader), which would fail an equality assertion |
| 16 | +// for reasons that have nothing to do with this bug. |
| 17 | +function rounded(values) { |
| 18 | + return Array.from(values).map(value => Math.round(value * 1e4) / 1e4); |
| 19 | +} |
| 20 | + |
| 21 | +function testInterchangeable(mode, precision, assert) { |
| 22 | + const gpu = new GPU({ mode }); |
| 23 | + const kernel = gpu.createKernel(function (a) { |
| 24 | + return a[this.thread.x] * 2; |
| 25 | + }, { output: [4], precision }); |
| 26 | + assert.deepEqual(rounded(kernel([1, 2, 3, 4])), [2, 4, 6, 8], 'plain array'); |
| 27 | + assert.deepEqual( |
| 28 | + rounded(kernel(Float32Array.from([5, 6, 7, 8]))), |
| 29 | + [10, 12, 14, 16], |
| 30 | + 'Float32Array on the same kernel'); |
| 31 | + assert.deepEqual(rounded(kernel([9, 10, 11, 12])), [18, 20, 22, 24], 'and back to a plain array'); |
| 32 | + gpu.destroy(); |
| 33 | +} |
| 34 | + |
| 35 | +const MODES = [ |
| 36 | + ['cpu', true], |
| 37 | + ['webgl', GPU.isWebGLSupported], |
| 38 | + ['webgl2', GPU.isWebGL2Supported], |
| 39 | + ['headlessgl', GPU.isHeadlessGLSupported], |
| 40 | +]; |
| 41 | + |
| 42 | +for (const [mode, supported] of MODES) { |
| 43 | + (supported ? test : skip)(`Issue #857 - Array then Float32Array single precision ${ mode }`, assert => { |
| 44 | + testInterchangeable(mode, 'single', assert); |
| 45 | + }); |
| 46 | + (supported ? test : skip)(`Issue #857 - Array then Float32Array unsigned precision ${ mode }`, assert => { |
| 47 | + testInterchangeable(mode, 'unsigned', assert); |
| 48 | + }); |
| 49 | + (supported ? test : skip)(`Issue #857 - Float32Array first, then Array ${ mode }`, assert => { |
| 50 | + const gpu = new GPU({ mode }); |
| 51 | + const kernel = gpu.createKernel(function (a) { |
| 52 | + return a[this.thread.x] * 2; |
| 53 | + }, { output: [4] }); |
| 54 | + assert.deepEqual(Array.from(kernel(Float32Array.from([1, 2, 3, 4]))), [2, 4, 6, 8]); |
| 55 | + assert.deepEqual(Array.from(kernel([5, 6, 7, 8])), [10, 12, 14, 16]); |
| 56 | + gpu.destroy(); |
| 57 | + }); |
| 58 | + (supported ? test : skip)(`Issue #857 - a changed shape still switches kernels ${ mode }`, assert => { |
| 59 | + // relaxing the container check must not let a differently-shaped value |
| 60 | + // upload into the texture built for the old shape |
| 61 | + const gpu = new GPU({ mode }); |
| 62 | + const build = g => g.createKernel(function (a) { |
| 63 | + return a[this.thread.x][0]; |
| 64 | + }, { output: [2] }); |
| 65 | + const kernel = build(gpu); |
| 66 | + assert.deepEqual(Array.from(kernel([[1, 2], [3, 4]])), [1, 3], 'first shape'); |
| 67 | + const reference = build(new GPU({ mode })); |
| 68 | + assert.deepEqual( |
| 69 | + Array.from(kernel([[9, 8], [7, 6]])), |
| 70 | + Array.from(reference([[9, 8], [7, 6]])), |
| 71 | + 'same answer as a kernel that only saw this value'); |
| 72 | + gpu.destroy(); |
| 73 | + }); |
| 74 | +} |
0 commit comments