Files
workinf_Blender_Wasm/web/tests/e2e/compositor-node-golden.spec.ts
mes123456 0fe8d2bb56
Some checks are pending
M6 deployable RC / quick (push) Waiting to run
M6 deployable RC / chromium (push) Blocked by required conditions
M6 deployable RC / release (push) Blocked by required conditions
Advance M8-M11 parity workflows
2026-08-17 04:37:07 -04:00

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);
}
});