Advance M8-M11 parity workflows
This commit is contained in:
116
web/tests/e2e/paint-depth-visibility.spec.ts
Normal file
116
web/tests/e2e/paint-depth-visibility.spec.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("M9-09 derives the same occlusion set from real main-thread and Offscreen GPU depth passes", async ({ page }) => {
|
||||
test.setTimeout(60_000);
|
||||
await page.goto("/");
|
||||
const result = await page.evaluate(async () => {
|
||||
const { ViewportRenderer } = await import("/src/three-adapter/viewport.ts");
|
||||
const { OffscreenViewportRenderer } = await import("/src/three-adapter/offscreen-viewport.ts");
|
||||
|
||||
const normalize = (value: number[]) => {
|
||||
const length = Math.hypot(...value);
|
||||
return value.map((component) => component / length);
|
||||
};
|
||||
const cross = (left: number[], right: number[]) => [
|
||||
left[1] * right[2] - left[2] * right[1],
|
||||
left[2] * right[0] - left[0] * right[2],
|
||||
left[0] * right[1] - left[1] * right[0],
|
||||
];
|
||||
const add = (...values: number[][]) => values[0].map((_, axis) => values.reduce((sum, value) => sum + value[axis], 0));
|
||||
const scale = (value: number[], factor: number) => value.map((component) => component * factor);
|
||||
const camera = [
|
||||
7 * Math.cos(0.55) * Math.cos(-Math.PI / 4),
|
||||
7 * Math.cos(0.55) * Math.sin(-Math.PI / 4),
|
||||
7 * Math.sin(0.55),
|
||||
];
|
||||
const direction = normalize(camera.map((component) => -component));
|
||||
const horizontal = normalize(cross(direction, [0, 0, 1]));
|
||||
const vertical = normalize(cross(direction, horizontal));
|
||||
const front = [
|
||||
add(scale(horizontal, -2), scale(vertical, -2)),
|
||||
add(scale(horizontal, -2), scale(vertical, 2)),
|
||||
add(scale(horizontal, 2), scale(vertical, 2)),
|
||||
add(scale(horizontal, 2), scale(vertical, -2)),
|
||||
];
|
||||
const behind = scale(direction, 1.5);
|
||||
const back = [
|
||||
add(behind, scale(horizontal, -0.12), scale(vertical, -0.08)),
|
||||
add(behind, scale(horizontal, 0.12), scale(vertical, -0.08)),
|
||||
add(behind, scale(vertical, 0.12)),
|
||||
];
|
||||
const toBlender = (value: number[]) => [value[0], -value[2], value[1]];
|
||||
const positions = new Float32Array([...front, ...back].flatMap(toBlender));
|
||||
const indices = new Uint32Array([0, 1, 2, 0, 2, 3, 4, 6, 5]);
|
||||
const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
||||
const snapshot = {
|
||||
schemaVersion: 1 as const,
|
||||
revision: 9,
|
||||
sceneId: "scene:paint-depth",
|
||||
source: { kind: "mock" as const },
|
||||
coordinateSystem: { upAxis: "Z" as const, forwardAxis: "-Y" as const, handedness: "RIGHT" as const, unitSystem: 0, unitScale: 1 },
|
||||
nodes: [{
|
||||
id: "object:PaintDepth",
|
||||
name: "PaintDepth",
|
||||
type: "MESH" as const,
|
||||
parentId: null,
|
||||
dataId: "mesh:PaintDepth",
|
||||
visible: true,
|
||||
selectable: true,
|
||||
localMatrix: identity,
|
||||
worldMatrix: identity,
|
||||
transform: { translation: [0, 0, 0] as [number, number, number], rotationEuler: [0, 0, 0] as [number, number, number], scale: [1, 1, 1] as [number, number, number], rotationMode: 1 },
|
||||
}],
|
||||
meshes: [{ id: "mesh:PaintDepth", name: "PaintDepth", vertexCount: 7, edgeCount: 0, faceCount: 3, cornerCount: 9, triangleCount: 3, geometryStatus: "binary" as const }],
|
||||
materials: [], cameras: [], lights: [], worlds: [], images: [], animations: [], collections: [], scenes: [],
|
||||
activeObjectId: "object:PaintDepth",
|
||||
frame: { current: 1, start: 1, end: 1 },
|
||||
};
|
||||
const geometry = {
|
||||
schemaVersion: 1 as const,
|
||||
meshId: "mesh:PaintDepth",
|
||||
byteLength: positions.byteLength + indices.byteLength,
|
||||
positions: positions.buffer,
|
||||
indices: indices.buffer,
|
||||
};
|
||||
const request = { schemaVersion: 1 as const, objectId: "object:PaintDepth", meshId: "mesh:PaintDepth", revision: 9, vertexIndices: [0, 1, 2, 3, 4, 5, 6] };
|
||||
|
||||
const createCanvas = () => {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.style.width = "320px";
|
||||
canvas.style.height = "240px";
|
||||
document.body.append(canvas);
|
||||
return canvas;
|
||||
};
|
||||
|
||||
const mainCanvas = createCanvas();
|
||||
const mainRenderer = new ViewportRenderer(mainCanvas);
|
||||
mainRenderer.setSnapshot(snapshot, [geometry]);
|
||||
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
||||
const main = await mainRenderer.samplePaintVisibility(request);
|
||||
mainRenderer.dispose();
|
||||
mainCanvas.remove();
|
||||
|
||||
const offscreenCanvas = createCanvas();
|
||||
const offscreenRenderer = new OffscreenViewportRenderer(offscreenCanvas);
|
||||
offscreenRenderer.setSnapshot(snapshot, [geometry]);
|
||||
let stale = "";
|
||||
try { await offscreenRenderer.samplePaintVisibility({ ...request, revision: 8 }); }
|
||||
catch (error) { stale = error instanceof Error ? error.message : String(error); }
|
||||
const offscreen = await offscreenRenderer.samplePaintVisibility(request);
|
||||
offscreenRenderer.dispose();
|
||||
offscreenCanvas.remove();
|
||||
|
||||
return { main, offscreen, stale };
|
||||
});
|
||||
|
||||
expect(result.main.backend).toBe("MAIN_THREAD_WEBGL2");
|
||||
expect(result.offscreen.backend).toBe("OFFSCREEN_WEBGL2");
|
||||
expect(result.main.source).toBe("GPU_RGBA_DEPTH_READBACK");
|
||||
expect(result.main.occluderPixelCount).toBeGreaterThan(0);
|
||||
expect(result.offscreen.occluderPixelCount).toBeGreaterThan(0);
|
||||
expect(result.main.depthReadbackBytes).toBe(result.main.width * result.main.height * 4);
|
||||
expect(result.offscreen.depthReadbackBytes).toBe(result.offscreen.width * result.offscreen.height * 4);
|
||||
expect(result.main.visibleVertexIndices).toEqual([0, 1, 2, 3]);
|
||||
expect(result.offscreen.visibleVertexIndices).toEqual(result.main.visibleVertexIndices);
|
||||
expect(result.stale).toContain("REVISION_CONFLICT");
|
||||
});
|
||||
Reference in New Issue
Block a user