Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions demos/interactive_demo.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,23 @@ let renderUniformBuffer = null;
let dpr = 1;

function resizeCanvas() {
dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = Math.round(window.innerWidth * dpr);
canvas.height = Math.round(window.innerHeight * dpr);
/* Neither dimension of the backing store may exceed the device's
maxTextureDimension2D (8192 by default, and this demo does not raise
it). Past that the swap-chain texture is invalid and nothing renders
at all - a black canvas with the controls still drawn on top. Since
the DPR scaling below multiplies the CSS size, a window wider than
maxTextureDimension2D / 2 would otherwise cross the limit on any
Retina display. */
const maxDim = device?.limits?.maxTextureDimension2D ?? 8192;
const cssWidth = Math.max(1, window.innerWidth);
const cssHeight = Math.max(1, window.innerHeight);
const fit = Math.min(maxDim / cssWidth, maxDim / cssHeight);

/* dpr stays the true CSS-to-backing-store scale after clamping, because
pointer coordinates and the influence radius are converted with it. */
dpr = Math.min(window.devicePixelRatio || 1, 2, fit);
canvas.width = Math.round(cssWidth * dpr);
canvas.height = Math.round(cssHeight * dpr);
if (device && renderUniformBuffer) {
device.queue.writeBuffer(renderUniformBuffer, 0, new Float32Array([canvas.width, canvas.height, 0, 0]));
}
Expand Down
37 changes: 37 additions & 0 deletions misc/run_headless_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,43 @@ async function runDemoSmokeTest(browser) {
failures.push(`canvas has no backing store (${canvas.w}x${canvas.h})`);
}

/*
* Regression guard: the canvas backing store must never exceed the
* device's maxTextureDimension2D. Past that the swap-chain texture is
* invalid and the demo renders nothing - a black canvas with the
* controls still drawn on top, and no thrown error to notice.
*
* This is easy to reintroduce because resizeCanvas() multiplies the CSS
* size by devicePixelRatio, so on a Retina display any window wider
* than half the limit crosses it. A wide viewport is used here because
* the default test window is nowhere near large enough to catch it.
*/
const wide = await browser.newPage();
try {
await wide.setViewport({ width: 5000, height: 1000, deviceScaleFactor: 2 });
await wide.goto('http://127.0.0.1:8000/demos/interactive_demo.html', {
waitUntil: 'domcontentloaded',
timeout: 30000
});
await new Promise(r => setTimeout(r, 5000));
const big = await wide.evaluate(async () => {
const c = document.getElementById('canvas');
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
return { w: c.width, h: c.height, max: device.limits.maxTextureDimension2D };
});
if (big.w > big.max || big.h > big.max) {
failures.push(
`canvas ${big.w}x${big.h} exceeds maxTextureDimension2D ${big.max} ` +
`at a 5000x1000 viewport; the demo will render black`
);
} else {
console.log(` wide viewport: canvas ${big.w}x${big.h} within limit ${big.max}`);
}
} finally {
await wide.close();
}

// Every control the demo wires up must exist, or an addEventListener
// call threw and the rest of the module never ran.
const missing = await page.evaluate(() => {
Expand Down
Loading