82 lines
4.4 KiB
JavaScript
82 lines
4.4 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { createRequire } from "node:module";
|
|
import { execFileSync } from "node:child_process";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
import factory from "../../web/app/src/vendor/blender/web_engine.js";
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
const golden = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M11-07/compositor-node-golden.json"), "utf8"));
|
|
const fixturePath = path.join(root, golden.fixture.path);
|
|
const generatorPath = path.join(root, golden.fixture.generator);
|
|
const blender = process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender");
|
|
const sha256 = (value) => crypto.createHash("sha256").update(value).digest("hex");
|
|
assert.equal(sha256(fs.readFileSync(fixturePath)), golden.fixture.sha256);
|
|
assert.equal(sha256(fs.readFileSync(generatorPath)), golden.fixture.generatorSha256);
|
|
assert.match(execFileSync(blender, ["--version"], { encoding: "utf8" }), new RegExp(`^Blender ${golden.fixture.blenderVersion.replaceAll(".", "\\.")}\\b`, "m"));
|
|
|
|
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "m11-compositor-golden-"));
|
|
const requireFromWeb = createRequire(path.join(root, "web/package.json"));
|
|
const ts = requireFromWeb("typescript");
|
|
for (const [sourceName, outputName] of [["capability-gates.ts", "capability-gates.mjs"], ["compositor.ts", "compositor.mjs"]]) {
|
|
const sourcePath = path.join(root, "web/protocol", sourceName);
|
|
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
|
|
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
|
|
fileName: sourcePath,
|
|
reportDiagnostics: true,
|
|
});
|
|
assert.deepEqual(transpiled.diagnostics, []);
|
|
const output = sourceName === "compositor.ts" ? transpiled.outputText.replaceAll('from "./capability-gates"', 'from "./capability-gates.mjs"') : transpiled.outputText;
|
|
fs.writeFileSync(path.join(temporary, outputName), output);
|
|
}
|
|
const compositor = await import(pathToFileURL(path.join(temporary, "compositor.mjs")));
|
|
const wasmBinary = fs.readFileSync(path.join(root, "web/app/src/vendor/blender/web_engine.wasm"));
|
|
const fixture = fs.readFileSync(fixturePath);
|
|
|
|
function open(engine, handle, bytes) {
|
|
const pointer = engine._malloc(bytes.byteLength);
|
|
try {
|
|
engine.HEAPU8.set(bytes, pointer);
|
|
assert.equal(engine._web_engine_open_blend(handle, pointer, bytes.byteLength), 0, engine.UTF8ToString(engine._web_engine_last_error_message()));
|
|
}
|
|
finally { engine._free(pointer); }
|
|
}
|
|
|
|
function snapshot(engine, handle) {
|
|
const dataOut = engine._malloc(4);
|
|
const lengthOut = engine._malloc(4);
|
|
try {
|
|
assert.equal(engine._web_engine_get_scene_snapshot(handle, dataOut, lengthOut), 0, engine.UTF8ToString(engine._web_engine_last_error_message()));
|
|
const pointer = engine.HEAPU32[dataOut >>> 2];
|
|
const length = engine.HEAPU32[lengthOut >>> 2];
|
|
return JSON.parse(new TextDecoder().decode(engine.HEAPU8.slice(pointer, pointer + length)));
|
|
}
|
|
finally { engine._free(dataOut); engine._free(lengthOut); }
|
|
}
|
|
|
|
try {
|
|
const engine = await factory({ wasmBinary: wasmBinary.slice() });
|
|
const handle = engine._web_engine_create();
|
|
try {
|
|
open(engine, handle, fixture);
|
|
const value = snapshot(engine, handle);
|
|
for (const candidate of golden.cases) {
|
|
const scene = value.scenes.find((item) => item.name === candidate.scene);
|
|
assert.equal(scene?.compositorStatus, "AVAILABLE", `${candidate.scene} has no Main compositor graph`);
|
|
assert.deepEqual(scene.compositorGraph.nodes.map((node) => node.type), candidate.nodeTypes);
|
|
assert.deepEqual(compositor.compileCompositorWebGPUPlan(scene.compositorGraph).instructions.map((instruction) => instruction.type), candidate.nodeTypes);
|
|
const cpu = compositor.executeCompositorGraph(scene.compositorGraph, new Map(), { width: golden.width, height: golden.height });
|
|
assert.deepEqual(Array.from(cpu.composite.data.slice(0, 4)), candidate.pixel);
|
|
assert.equal(sha256(new Uint8Array(cpu.composite.data.buffer)), candidate.float32Sha256);
|
|
}
|
|
}
|
|
finally { engine._web_engine_destroy(handle); }
|
|
process.stdout.write(`compositor-node-golden-ok fixture=${golden.fixture.sha256} scenes=${golden.cases.length} allowlist=${golden.allowlist.join(",")} cpu-hashes=4\n`);
|
|
}
|
|
finally {
|
|
fs.rmSync(temporary, { recursive: true, force: true });
|
|
}
|