82 lines
4.2 KiB
TypeScript
82 lines
4.2 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
|
|
const root = path.resolve(import.meta.dirname, "../../..");
|
|
const golden = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M11-07/compositor-node-golden.json"), "utf8")) as {
|
|
width: number;
|
|
height: number;
|
|
maxAbsoluteError: number;
|
|
allowlist: string[];
|
|
fixture: { path: string };
|
|
cases: Array<{ scene: string; newNode: string; nodeTypes: string[]; pixel: number[]; float32Sha256: string }>;
|
|
};
|
|
const fixture = fs.readFileSync(path.join(root, golden.fixture.path));
|
|
|
|
test("M11-07 gives every allowlisted compositor node an independent CPU/WebGPU golden", async ({ page }) => {
|
|
test.setTimeout(60_000);
|
|
await page.goto("/");
|
|
const result = await page.evaluate(async (input) => {
|
|
const [{ WebEngineClient }, cpu, gpu] = await Promise.all([
|
|
import("/src/engine-client/WebEngineClient.ts"),
|
|
import("/src/compositor/CompositorExecutor.ts"),
|
|
import("/src/compositor/CompositorWebGPU.ts"),
|
|
]);
|
|
const hash = async (data: Float32Array): Promise<string> => Array.from(
|
|
new Uint8Array(await crypto.subtle.digest("SHA-256", data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength))),
|
|
(byte) => byte.toString(16).padStart(2, "0"),
|
|
).join("");
|
|
const client = new WebEngineClient({ timeoutMs: 30_000 });
|
|
const device = await gpu.requestCompositorWebGPUDevice();
|
|
try {
|
|
const opened = await client.openBlend(Uint8Array.from(input.bytes).buffer);
|
|
const cases = [];
|
|
for (const expected of input.cases) {
|
|
const scene = opened.snapshot.scenes.find((candidate) => candidate.name === expected.scene);
|
|
if (!scene?.compositorGraph) throw new Error(`Missing compositor graph ${expected.scene}`);
|
|
const plan = cpu.compileCompositorWebGPUPlan(scene.compositorGraph);
|
|
const cpuResult = cpu.executeCompositorGraph(scene.compositorGraph, new Map(), { width: input.width, height: input.height }).composite;
|
|
const gpuResult = await gpu.executeCompositorGraphWebGPU(device, scene.compositorGraph, input.width, input.height);
|
|
let maxAbsoluteError = 0;
|
|
for (let index = 0; index < cpuResult.data.length; index++) maxAbsoluteError = Math.max(maxAbsoluteError, Math.abs(cpuResult.data[index] - gpuResult.data[index]));
|
|
cases.push({
|
|
scene: expected.scene,
|
|
newNode: expected.newNode,
|
|
instructionTypes: plan.instructions.map((instruction) => instruction.type),
|
|
cpuPixel: Array.from(cpuResult.data.slice(0, 4)),
|
|
gpuPixel: Array.from(gpuResult.data.slice(0, 4)),
|
|
cpuSha256: await hash(cpuResult.data),
|
|
gpuSha256: await hash(gpuResult.data),
|
|
maxAbsoluteError,
|
|
});
|
|
}
|
|
const base = opened.snapshot.scenes.find((candidate) => candidate.name === input.cases[0].scene)?.compositorGraph;
|
|
if (!base) throw new Error("Missing base compositor graph");
|
|
let blockedCode = "";
|
|
try {
|
|
cpu.compileCompositorWebGPUPlan({ ...base, nodes: [...base.nodes, { id: "blocked:viewer", type: "VIEWER", name: "Blocked Viewer", properties: {} }] });
|
|
}
|
|
catch (error) { blockedCode = error instanceof cpu.CompositorValidationError ? error.code : String(error); }
|
|
return { allowlist: cpu.COMPOSITOR_WEBGPU_NODE_ALLOWLIST, cases, blockedCode };
|
|
}
|
|
finally {
|
|
device.destroy();
|
|
client.terminate();
|
|
}
|
|
}, { bytes: Array.from(fixture), cases: golden.cases, width: golden.width, height: golden.height });
|
|
|
|
expect(result.allowlist).toEqual(golden.allowlist);
|
|
expect(result.blockedCode).toBe("COMPOSITOR_NODE_UNSUPPORTED");
|
|
for (const [index, candidate] of result.cases.entries()) {
|
|
const expected = golden.cases[index];
|
|
expect(candidate.scene).toBe(expected.scene);
|
|
expect(candidate.newNode).toBe(expected.newNode);
|
|
expect(candidate.instructionTypes).toEqual(expected.nodeTypes);
|
|
expect(candidate.cpuPixel).toEqual(expected.pixel);
|
|
expect(candidate.gpuPixel).toEqual(expected.pixel);
|
|
expect(candidate.cpuSha256).toBe(expected.float32Sha256);
|
|
expect(candidate.gpuSha256).toBe(expected.float32Sha256);
|
|
expect(candidate.maxAbsoluteError).toBeLessThanOrEqual(golden.maxAbsoluteError);
|
|
}
|
|
});
|