Files
workinf_Blender_Wasm/web/tests/e2e/nanovdb-render-golden.spec.ts
mes123456 0fe8d2bb56
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Advance M8-M11 parity workflows
2026-08-17 04:37:07 -04:00

89 lines
4.9 KiB
TypeScript

import { expect, test } from "@playwright/test";
import fs from "node:fs";
import path from "node:path";
import { compareNanoVDBRenderGolden, type NanoVDBRenderGoldenThresholdsIR } from "../../protocol/nanovdb-render-golden";
const goldenRoot = path.resolve(import.meta.dirname, "../../../tests/golden/M8-19");
const goldenManifest = JSON.parse(fs.readFileSync(path.join(goldenRoot, "manifest.json"), "utf8")) as {
schemaVersion: number;
source: { sha256: string };
renderContract: {
shaderSemanticVersion: string;
width: number;
height: number;
axes: Array<"X" | "Y" | "Z">;
thresholds: NanoVDBRenderGoldenThresholdsIR;
};
images: Array<{ axis: "X" | "Y" | "Z"; file: string; sha256: string }>;
};
const referenceImages = Object.fromEntries(goldenManifest.images.map((image) => [image.axis, new Uint8Array(fs.readFileSync(path.join(goldenRoot, image.file))) ])) as Record<"X" | "Y" | "Z", Uint8Array>;
test("M8-19 compares desktop OpenVDB with main-thread and Offscreen WebGPU goldens", async ({ page }) => {
test.setTimeout(180_000);
await page.goto("/");
const main = await page.evaluate(async () => {
const [viewport, renderer] = await Promise.all([
import("/src/volume/nanovdb-viewport.ts"),
import("/src/render/nanovdb-volume-renderer.ts"),
]);
const asset = await viewport.loadNanoVDBViewportAsset("m8-19-main", "/__vdb_fixture__/manifest", "/__vdb_fixture__/bundle", new AbortController().signal);
const density = asset.manifest.grids.find((grid) => grid.name === asset.manifest.material.densityGrid);
const payload = asset.grids.find((grid) => grid.name === density?.name)?.data;
if (!density || !payload) throw new Error("NANOVDB_STREAM_INCOMPLETE: M8-19 density payload is missing");
const session = new renderer.NanoVDBWebGPUDeviceSession();
const device = await session.open(payload.byteLength, 4);
const uploaded = renderer.uploadNanoVDBFloat32Grid(device, payload);
const material = { ...asset.manifest.material, temperatureGrid: undefined, colorGrid: undefined, emissionGrid: undefined, interpolation: "LINEAR" as const };
const images: Record<string, number[]> = {};
const hashes: Record<string, string> = {};
for (const axis of ["X", "Y", "Z"] as const) {
const pixels = await renderer.renderNanoVDBFloat32WebGPU(device, uploaded, density, material, 64, 64, {}, axis);
images[axis] = Array.from(pixels);
hashes[axis] = Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", pixels))).map((byte) => byte.toString(16).padStart(2, "0")).join("");
}
uploaded.dispose();
session.dispose();
return { sourceSha256: asset.manifest.sourceSha256, shaderSemanticVersion: asset.manifest.gpu.shaderSemanticVersion, hashes, images };
});
const offscreen = await page.evaluate(() => new Promise<any>((resolve, reject) => {
const worker = new Worker("/src/workers/nanovdb-render-golden-test.worker.ts", { type: "module" });
worker.onmessage = (event) => {
worker.terminate();
if (event.data.error) { reject(new Error(event.data.error)); return; }
resolve({
...event.data,
images: Object.fromEntries(Object.entries(event.data.images).map(([axis, buffer]) => [axis, Array.from(new Uint8Array(buffer as ArrayBuffer))])),
});
};
worker.onerror = (event) => { worker.terminate(); reject(new Error(event.message)); };
worker.postMessage({});
}));
expect(goldenManifest.schemaVersion).toBe(1);
expect(main.sourceSha256).toBe(goldenManifest.source.sha256);
expect(offscreen.sourceSha256).toBe(goldenManifest.source.sha256);
expect(main.shaderSemanticVersion).toBe(goldenManifest.renderContract.shaderSemanticVersion);
expect(offscreen.shaderSemanticVersion).toBe(goldenManifest.renderContract.shaderSemanticVersion);
const report: Record<string, unknown> = {};
for (const image of goldenManifest.images) {
const reference = referenceImages[image.axis];
const mainPixels = Uint8Array.from(main.images[image.axis]);
const offscreenPixels = Uint8Array.from(offscreen.images[image.axis]);
const mainComparison = compareNanoVDBRenderGolden(reference, mainPixels, goldenManifest.renderContract.thresholds);
const offscreenComparison = compareNanoVDBRenderGolden(reference, offscreenPixels, goldenManifest.renderContract.thresholds);
const backendComparison = compareNanoVDBRenderGolden(mainPixels, offscreenPixels, {
maxChannelError: 0,
meanAbsoluteError: 0,
rmsError: 0,
alphaCoverageDeltaRatio: 0,
});
expect(main.hashes[image.axis]).toBe(image.sha256);
expect(offscreen.hashes[image.axis]).toBe(image.sha256);
expect(mainComparison.status).toBe("READY");
expect(offscreenComparison.status).toBe("READY");
expect(backendComparison.status).toBe("READY");
report[image.axis] = { main: mainComparison, offscreen: offscreenComparison, backend: backendComparison, sha256: image.sha256 };
}
console.log("nanovdb-render-golden", JSON.stringify(report));
});