62 lines
2.4 KiB
JavaScript
62 lines
2.4 KiB
JavaScript
|
|
import '../kernels.js';
|
||
|
|
|
||
|
|
const K = globalThis.MANDEL_KERNELS;
|
||
|
|
const decode = b64 => Uint8Array.from(atob(b64), c => c.charCodeAt(0));
|
||
|
|
|
||
|
|
function instantiate() {
|
||
|
|
for (const [name, payload] of [['simd', K.WASM_SIMD_B64], ['scalar', K.WASM_SCALAR_B64]]) {
|
||
|
|
try {
|
||
|
|
const instance = new WebAssembly.Instance(new WebAssembly.Module(decode(payload)), {});
|
||
|
|
return { name, ex: instance.exports };
|
||
|
|
} catch {}
|
||
|
|
}
|
||
|
|
throw new Error('Neither shallow WASM backend could be instantiated.');
|
||
|
|
}
|
||
|
|
|
||
|
|
function jsPixel(cre, cim, span, width, height, x, y, iter) {
|
||
|
|
const scale = span / width;
|
||
|
|
// Match the public ABI operation order: JavaScript shifts the center once,
|
||
|
|
// then the kernel applies the historical integer-grid formula. The
|
||
|
|
// algebraically equivalent single expression can round differently at a
|
||
|
|
// chaotic boundary and is not a useful backend-equivalence oracle.
|
||
|
|
const shiftedRe = cre + scale * 0.5;
|
||
|
|
const shiftedIm = cim - scale * 0.5;
|
||
|
|
const cr = shiftedRe + scale * (x - width * 0.5);
|
||
|
|
const ci = shiftedIm + scale * (height * 0.5 - y);
|
||
|
|
let zr = 0, zi = 0, zr2 = 0, zi2 = 0, n = 0;
|
||
|
|
while (n < iter && zr2 + zi2 <= 4) {
|
||
|
|
zi = 2 * zr * zi + ci;
|
||
|
|
zr = zr2 - zi2 + cr;
|
||
|
|
zr2 = zr * zr;
|
||
|
|
zi2 = zi * zi;
|
||
|
|
n++;
|
||
|
|
}
|
||
|
|
return n;
|
||
|
|
}
|
||
|
|
|
||
|
|
const backend = instantiate();
|
||
|
|
const scenes = [
|
||
|
|
{ cre: -0.5, cim: 0, span: 3.4, width: 31, height: 19, iter: 350 },
|
||
|
|
{ cre: -0.743643887037151, cim: 0.13182590420533, span: 4e-4, width: 37, height: 23, iter: 700 }
|
||
|
|
];
|
||
|
|
|
||
|
|
let samples = 0;
|
||
|
|
let mismatches = 0;
|
||
|
|
const mismatchDetails = [];
|
||
|
|
for (const s of scenes) {
|
||
|
|
const scale = s.span / s.width;
|
||
|
|
const npx = backend.ex.render_rows(s.cre + scale * 0.5, s.cim - scale * 0.5, s.span, s.width, s.height, 0, s.height, s.iter);
|
||
|
|
const counts = new Uint32Array(backend.ex.memory.buffer, backend.ex.counts_ptr(), npx);
|
||
|
|
for (let y = 0; y < s.height; y++) for (let x = 0; x < s.width; x++) {
|
||
|
|
samples++;
|
||
|
|
const wasmCount = counts[y * s.width + x];
|
||
|
|
const jsCount = jsPixel(s.cre, s.cim, s.span, s.width, s.height, x, y, s.iter);
|
||
|
|
if (wasmCount !== jsCount) {
|
||
|
|
mismatches++;
|
||
|
|
if (mismatchDetails.length < 12) mismatchDetails.push({ scene: scenes.indexOf(s), x, y, wasmCount, jsCount });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (mismatches) throw new Error(`Pixel contract mismatch: ${mismatches}/${samples} ${JSON.stringify(mismatchDetails)}`);
|
||
|
|
console.log(JSON.stringify({ status: 'pass', backend: backend.name, samples, mismatches, contract: '(x+0.5,y+0.5)' }));
|