import assert from "node:assert/strict"; import fs from "node:fs"; import { performance } from "node:perf_hooks"; import factory from "../../web/app/src/vendor/blender/web_engine.js"; const wasmBinary = fs.readFileSync(new URL("../../web/app/src/vendor/blender/web_engine.wasm", import.meta.url)); const limits = { 100000: Number(process.env.WEB_PERF_100K_MS ?? 120_000), 1000000: Number(process.env.WEB_PERF_1M_MS ?? 180_000), heapBytes: Number(process.env.WEB_PERF_MAX_HEAP_BYTES ?? 1_610_612_736), }; function grid(targetTriangles) { const columns = Math.ceil(Math.sqrt(targetTriangles / 2)); const rows = Math.ceil(targetTriangles / (columns * 2)); const positions = new Float32Array((columns + 1) * (rows + 1) * 3); for (let y = 0; y <= rows; y++) { for (let x = 0; x <= columns; x++) { const offset = (y * (columns + 1) + x) * 3; positions[offset] = x / columns; positions[offset + 1] = y / rows; positions[offset + 2] = Math.sin(x * 0.03) * Math.cos(y * 0.03) * 0.01; } } const triangleCount = Math.min(targetTriangles, columns * rows * 2); const indices = new Uint32Array(triangleCount * 3); for (let triangle = 0; triangle < triangleCount; triangle++) { const cell = Math.floor(triangle / 2); const x = cell % columns; const y = Math.floor(cell / columns); const a = y * (columns + 1) + x; const offset = triangle * 3; if (triangle % 2 === 0) indices.set([a, a + 1, a + columns + 2], offset); else indices.set([a, a + columns + 2, a + columns + 1], offset); } return { positions, indices, triangleCount }; } function alloc(engine, typed) { const pointer = engine._malloc(typed.byteLength); assert.ok(pointer > 0, `WASM allocation failed for ${typed.byteLength} bytes`); engine.HEAPU8.set(new Uint8Array(typed.buffer, typed.byteOffset, typed.byteLength), pointer); return pointer; } const results = []; for (const target of [100_000, 1_000_000]) { const engine = await factory({ wasmBinary }); const source = grid(target); const pointers = []; let report; try { const positions = alloc(engine, source.positions); pointers.push(positions); const indices = alloc(engine, source.indices); pointers.push(indices); const positionsOut = engine._malloc(source.positions.byteLength); pointers.push(positionsOut); const indicesOut = engine._malloc(source.indices.byteLength); pointers.push(indicesOut); const counts = Array.from({ length: 6 }, () => engine._malloc(4)); pointers.push(...counts); const started = performance.now(); const ratio = target === 100_000 ? 0.9 : 1; const result = engine._web_engine_decimate_apply( positions, source.positions.length / 3, indices, source.triangleCount, 0, ratio, 1, 0, 0, 0, 0, -1, 1e-4, 0, 1, 0, 0, 0, 0, positionsOut, source.positions.length, counts[0], indicesOut, source.indices.length, counts[1], 0, 0, counts[2], 0, 0, counts[3], 0, 0, counts[4], counts[5], ); const elapsedMs = performance.now() - started; assert.equal(result, 0, `native decimate failed for ${target} triangles with code ${result}`); const outputTriangles = engine.HEAPU32[counts[1] >>> 2] / 3; assert.ok(outputTriangles > 0 && outputTriangles <= source.triangleCount, "invalid performance-gate output topology"); const heapBytes = engine.HEAPU8.byteLength; assert.ok(elapsedMs <= limits[target], `${target} triangle gate exceeded ${limits[target]}ms: ${elapsedMs.toFixed(1)}ms`); assert.ok(heapBytes <= limits.heapBytes, `${target} triangle gate exceeded WASM heap limit: ${heapBytes}`); report = { target, ratio, inputTriangles: source.triangleCount, outputTriangles, elapsedMs: Math.round(elapsedMs), peakHeapBytes: heapBytes }; } finally { for (const pointer of pointers.reverse()) if (pointer) engine._free(pointer); } const recoveryPointer = engine._malloc(16); assert.ok(recoveryPointer > 0, `${target} triangle gate did not recover a small allocation`); engine._free(recoveryPointer); results.push({ ...report, releasedPointerCount: pointers.length, recoveryAllocation: true }); } process.stdout.write(`release-performance-ok ${JSON.stringify(results)}\n`);