Advance M8-M11 parity workflows
This commit is contained in:
157
web/tests/e2e/shader-compile.spec.ts
Normal file
157
web/tests/e2e/shader-compile.spec.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { createHash } from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const expected = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M10-07/shader-compile.json"), "utf8"));
|
||||
const blendBytes = fs.readFileSync(path.join(root, expected.fixture));
|
||||
|
||||
function sockets() {
|
||||
return {
|
||||
rgb: [{ id: "color", name: "Color", direction: "OUTPUT", dataType: "COLOR", defaultValue: [0.15, 0.25, 0.35, 1] }],
|
||||
value: [{ id: "value", name: "Value", direction: "OUTPUT", dataType: "VALUE", defaultValue: 0.2 }],
|
||||
math: [
|
||||
{ id: "a", name: "Value", direction: "INPUT", dataType: "VALUE", defaultValue: 0 },
|
||||
{ id: "b", name: "Value_001", direction: "INPUT", dataType: "VALUE", defaultValue: 0 },
|
||||
{ id: "value", name: "Value", direction: "OUTPUT", dataType: "VALUE" },
|
||||
],
|
||||
principled: [
|
||||
{ id: "base", name: "Base Color", direction: "INPUT", dataType: "COLOR", defaultValue: [0.2, 0.3, 0.4, 1] },
|
||||
{ id: "roughness", name: "Roughness", direction: "INPUT", dataType: "VALUE", defaultValue: 0.5 },
|
||||
{ id: "bsdf", name: "BSDF", direction: "OUTPUT", dataType: "SHADER" },
|
||||
],
|
||||
output: [{ id: "surface", name: "Surface", direction: "INPUT", dataType: "SHADER" }],
|
||||
};
|
||||
}
|
||||
|
||||
test("M10-07 produces a bounded compile report from the real Main Shader graph", async ({ page }) => {
|
||||
test.setTimeout(120_000);
|
||||
expect(createHash("sha256").update(blendBytes).digest("hex")).toBe(expected.fixtureSha256);
|
||||
await page.goto("/");
|
||||
const result = await page.evaluate(async ({ bytes, socketSet }) => {
|
||||
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
|
||||
const client = new WebEngineClient({ timeoutMs: 60_000 });
|
||||
await client.init();
|
||||
const source = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;
|
||||
const opened = await client.openBlend(source);
|
||||
const material = opened.snapshot.materials[0];
|
||||
if (!material) throw new Error("basic fixture has no editable material");
|
||||
const graph = {
|
||||
schemaVersion: 1 as const,
|
||||
id: "shader:m10-07",
|
||||
materialId: material.id,
|
||||
outputNodeId: "output",
|
||||
nodes: [
|
||||
{ id: "rgb", type: "RGB" as const, name: "RGB", sockets: socketSet.rgb },
|
||||
{ id: "a", type: "VALUE" as const, name: "A", sockets: socketSet.value },
|
||||
{ id: "b", type: "VALUE" as const, name: "B", sockets: [{ ...socketSet.value[0], defaultValue: 0.22 }] },
|
||||
{ id: "math", type: "MATH" as const, name: "Add", properties: { operation: "ADD" as const }, sockets: socketSet.math },
|
||||
{ id: "principled", type: "PRINCIPLED" as const, name: "Principled", sockets: socketSet.principled },
|
||||
{ id: "output", type: "MATERIAL_OUTPUT" as const, name: "Output", sockets: socketSet.output },
|
||||
],
|
||||
links: [
|
||||
{ fromNodeId: "rgb", fromSocketId: "color", toNodeId: "principled", toSocketId: "base" },
|
||||
{ fromNodeId: "a", fromSocketId: "value", toNodeId: "math", toSocketId: "a" },
|
||||
{ fromNodeId: "b", fromSocketId: "value", toNodeId: "math", toSocketId: "b" },
|
||||
{ fromNodeId: "math", fromSocketId: "value", toNodeId: "principled", toSocketId: "roughness" },
|
||||
{ fromNodeId: "principled", fromSocketId: "bsdf", toNodeId: "output", toSocketId: "surface" },
|
||||
],
|
||||
};
|
||||
const applied = await client.applyCommand({ type: "setShaderGraph", materialId: material.id, graph });
|
||||
const report = applied.shaderCompile;
|
||||
const after = await client.snapshot();
|
||||
client.terminate();
|
||||
return {
|
||||
openedHash: material.shaderGraphHash,
|
||||
status: report?.status,
|
||||
taskId: report?.taskId,
|
||||
backend: report?.backend,
|
||||
graphHash: report?.graphHash,
|
||||
roughness: report?.material?.roughness,
|
||||
nodeTypes: report?.compiledNodeTypes,
|
||||
revisionDelta: after.snapshot.revision - opened.snapshot.revision,
|
||||
};
|
||||
}, { bytes: new Uint8Array(blendBytes), socketSet: sockets() });
|
||||
|
||||
expect(result.status).toBe("COMPILED");
|
||||
expect(result.openedHash).toMatch(/^[0-9a-f]{64}$/);
|
||||
expect(result.taskId).toBe("M10-07");
|
||||
expect(result.backend).toBe(expected.backend);
|
||||
expect(result.graphHash).toMatch(/^[0-9a-f]{64}$/);
|
||||
expect(result.roughness).toBeCloseTo(expected.expectedMathResult, 5);
|
||||
expect(result.nodeTypes).toEqual(expect.arrayContaining(expected.allowlist.filter((type: string) => !["IMAGE_TEXTURE", "NORMAL_MAP"].includes(type))));
|
||||
expect(result.revisionDelta).toBe(1);
|
||||
});
|
||||
|
||||
test("M10-07 shared viewport material path compiles Image/Normal/Math bindings", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
const result = await page.evaluate(async () => {
|
||||
const { createPBRMaterial } = await import("/src/three-adapter/pbr.ts");
|
||||
const material = createPBRMaterial({
|
||||
id: "material:viewport",
|
||||
name: "Viewport",
|
||||
baseColor: [0.8, 0.8, 0.8, 1],
|
||||
roughness: 0.5,
|
||||
metallic: 0,
|
||||
emissionColor: [0, 0, 0, 1],
|
||||
alpha: 1,
|
||||
ior: 1.45,
|
||||
nodes: [
|
||||
{ id: "a", type: "VALUE", name: "A", defaultValue: [0.2] },
|
||||
{ id: "b", type: "VALUE", name: "B", defaultValue: [0.22] },
|
||||
{ id: "math", type: "MATH", name: "Add", properties: { operation: "ADD" } },
|
||||
{ id: "image", type: "IMAGE_TEXTURE", name: "Image", imageId: "image:normal" },
|
||||
{ id: "normal", type: "NORMAL_MAP", name: "Normal" },
|
||||
{ id: "principled", type: "PRINCIPLED", name: "Principled" },
|
||||
{ id: "output", type: "OUTPUT", name: "Output" },
|
||||
],
|
||||
links: [
|
||||
{ fromNodeId: "a", fromSocket: "Value", toNodeId: "math", toSocket: "Value" },
|
||||
{ fromNodeId: "b", fromSocket: "Value", toNodeId: "math", toSocket: "Value_001" },
|
||||
{ fromNodeId: "math", fromSocket: "Value", toNodeId: "principled", toSocket: "Roughness" },
|
||||
{ fromNodeId: "image", fromSocket: "Color", toNodeId: "normal", toSocket: "Color" },
|
||||
{ fromNodeId: "normal", fromSocket: "Normal", toNodeId: "principled", toSocket: "Normal" },
|
||||
{ fromNodeId: "principled", fromSocket: "BSDF", toNodeId: "output", toSocket: "Surface" },
|
||||
],
|
||||
});
|
||||
const compile = material.userData.shaderCompile;
|
||||
const report = {
|
||||
status: compile.status,
|
||||
graphHash: compile.graphHash,
|
||||
roughness: material.roughness,
|
||||
textures: compile.textureBindings,
|
||||
};
|
||||
material.dispose();
|
||||
return report;
|
||||
});
|
||||
expect(result.status).toBe("COMPILED");
|
||||
expect(result.graphHash).toMatch(/^[0-9a-f]{64}$/);
|
||||
expect(result.roughness).toBeCloseTo(expected.expectedMathResult, 5);
|
||||
expect(result.textures).toEqual([{ imageId: "image:normal", usage: "NORMAL" }]);
|
||||
});
|
||||
|
||||
test("M10-07 compiler blocks a non-declared node without mutating the input graph", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
const result = await page.evaluate(async () => {
|
||||
const { createPBRMaterial } = await import("/src/three-adapter/pbr.ts");
|
||||
const material = {
|
||||
id: "material:blocked",
|
||||
name: "Blocked",
|
||||
baseColor: [1, 1, 1, 1],
|
||||
roughness: 0.5,
|
||||
metallic: 0,
|
||||
emissionColor: [0, 0, 0, 1],
|
||||
alpha: 1,
|
||||
ior: 1.45,
|
||||
nodes: [{ id: "mix", type: "UNSUPPORTED", name: "Mix" }, { id: "output", type: "OUTPUT", name: "Output" }],
|
||||
links: [],
|
||||
} as const;
|
||||
const before = JSON.stringify(material);
|
||||
const threeMaterial = createPBRMaterial(material);
|
||||
const report = threeMaterial.userData.shaderCompile;
|
||||
threeMaterial.dispose();
|
||||
return { status: report.status, code: report.issues[0]?.code, preserved: JSON.stringify(material) === before };
|
||||
});
|
||||
expect(result).toEqual({ status: "BLOCKED", code: expected.unsupportedNodeCode, preserved: true });
|
||||
});
|
||||
Reference in New Issue
Block a user