50 lines
4.0 KiB
TypeScript
50 lines
4.0 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
test("decodes, uploads and renders a validated 4K texture in Chromium", async ({ page }) => {
|
|
test.setTimeout(45_000);
|
|
await page.goto("/");
|
|
const result = await page.evaluate(async () => {
|
|
const { createGPUTextureAsset } = await import("/src/render/RenderAssets.ts");
|
|
const { GPUTextureStore } = await import("/src/three-adapter/texture-assets.ts");
|
|
const { Mesh, MeshBasicMaterial, OrthographicCamera, PlaneGeometry, Scene, WebGLRenderer } = await import("/src/vendor/three/three.module.js");
|
|
const source = document.createElement("canvas");
|
|
source.width = 4096; source.height = 4096;
|
|
const context = source.getContext("2d", { alpha: false });
|
|
if (!context) throw new Error("2D texture fixture context is unavailable");
|
|
context.fillStyle = "#dd3322"; context.fillRect(0, 0, 2048, 4096);
|
|
context.fillStyle = "#22bb66"; context.fillRect(2048, 0, 2048, 4096);
|
|
const blob = await new Promise<Blob>((resolve, reject) => source.toBlob((value) => value ? resolve(value) : reject(new Error("4K PNG encoding failed")), "image/png"));
|
|
const data = await blob.arrayBuffer();
|
|
const asset = await createGPUTextureAsset({ assetId: "asset:4k", imageId: "image:4k", mimeType: "image/png", width: 4096, height: 4096, usage: "BASE_COLOR", colorSpace: "SRGB" }, data);
|
|
const started = performance.now();
|
|
const store = new GPUTextureStore();
|
|
const status = await store.upload([asset]);
|
|
const canvas = document.createElement("canvas"); canvas.width = 64; canvas.height = 64; document.body.append(canvas);
|
|
const renderer = new WebGLRenderer({ canvas, preserveDrawingBuffer: true }); renderer.setSize(64, 64, false);
|
|
const scene = new Scene(); const camera = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10); camera.position.z = 1;
|
|
const material = new MeshBasicMaterial({ map: store.get("image:4k", "BASE_COLOR") });
|
|
const geometry = new PlaneGeometry(2, 2);
|
|
scene.add(new Mesh(geometry, material)); renderer.render(scene, camera);
|
|
const gl = renderer.getContext(); const pixels = new Uint8Array(64 * 64 * 4); gl.readPixels(0, 0, 64, 64, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
|
let colored = 0; for (let index = 0; index < pixels.length; index += 4) if (pixels[index] > 60 || pixels[index + 1] > 60) colored += 1;
|
|
const elapsedMs = Math.round(performance.now() - started);
|
|
material.dispose(); geometry.dispose(); renderer.dispose(); store.dispose(); canvas.remove();
|
|
const released = store.get("image:4k", "BASE_COLOR") === undefined;
|
|
const recoveryCanvas = document.createElement("canvas"); recoveryCanvas.width = 16; recoveryCanvas.height = 16; document.body.append(recoveryCanvas);
|
|
const recoveryRenderer = new WebGLRenderer({ canvas: recoveryCanvas, preserveDrawingBuffer: true }); recoveryRenderer.setSize(16, 16, false);
|
|
const recoveryScene = new Scene(); const recoveryMaterial = new MeshBasicMaterial({ color: 0x33cc66 }); const recoveryGeometry = new PlaneGeometry(2, 2);
|
|
recoveryScene.add(new Mesh(recoveryGeometry, recoveryMaterial)); recoveryRenderer.render(recoveryScene, camera);
|
|
const recoveryPixels = new Uint8Array(16 * 16 * 4); recoveryRenderer.getContext().readPixels(0, 0, 16, 16, recoveryRenderer.getContext().RGBA, recoveryRenderer.getContext().UNSIGNED_BYTE, recoveryPixels);
|
|
const recoveryVisible = recoveryPixels.some((value, index) => index % 4 !== 3 && value > 40);
|
|
recoveryMaterial.dispose(); recoveryGeometry.dispose(); recoveryRenderer.dispose(); recoveryCanvas.remove();
|
|
return { width: 4096, height: 4096, status, byteLength: data.byteLength, estimatedPeakGpuBytes: 4096 * 4096 * 4, released, recoveryVisible, colored, elapsedMs };
|
|
});
|
|
console.log("texture-4k-performance", JSON.stringify(result));
|
|
expect(result.status).toMatchObject({ loaded: 1, rejected: 0, bytes: result.byteLength });
|
|
expect(result.byteLength).toBeGreaterThan(0);
|
|
expect(result.colored).toBeGreaterThan(3_000);
|
|
expect(result.elapsedMs).toBeLessThan(30_000);
|
|
expect(result.released).toBe(true);
|
|
expect(result.recoveryVisible).toBe(true);
|
|
});
|