wasm improvements

This commit is contained in:
Jorijn van der Graaf 2026-08-05 04:08:13 +02:00
commit 6aa0338c0f
9 changed files with 603 additions and 70 deletions

View file

@ -94,6 +94,16 @@ document.body.appendChild(canvas);
// when set, the canvas is reparented into that element and sized to it.
let mountEl = null;
// Upper bound on the canvas backing store, in pixels. Infinity until an RT
// dispatch derives the real budget from the device's buffer limits (see
// wgpuDispatchRT): the wavefront ray buffers scale with W·H·raysPerPixel,
// and a 256 MiB maxBufferSize (Firefox's baseline) overflows at ~0.5 Mpx
// for 4 rays/pixel — far below a HiDPI 16:9 mount — failing not at the
// createBuffer call but as an uncapturable device OOM at submit. The
// mounted canvas is CSS-pinned to width:100%/height:100%, so a clamped
// backing store upscales on screen instead of shrinking.
let maxCanvasPixels = Infinity;
function syncCanvasSize() {
// Canvas pixel size = CSS size × devicePixelRatio so the GPU draws
// at physical pixel resolution on HiDPI displays — otherwise the
@ -117,8 +127,13 @@ function syncCanvasSize() {
cssW = window.innerWidth;
cssH = window.innerHeight;
}
const w = Math.max(1, Math.round(cssW * dpr));
const h = Math.max(1, Math.round(cssH * dpr));
let w = Math.max(1, Math.round(cssW * dpr));
let h = Math.max(1, Math.round(cssH * dpr));
if (w * h > maxCanvasPixels) {
const s = Math.sqrt(maxCanvasPixels / (w * h));
w = Math.max(1, Math.floor(w * s));
h = Math.max(1, Math.floor(h * s));
}
if (canvas.width !== w) canvas.width = w;
if (canvas.height !== h) canvas.height = h;
return { w, h };
@ -3511,6 +3526,28 @@ env.wgpuDispatchRT = (pipelineHandle, pushPtr, pushBytes,
}
const W = state.width, H = state.height;
const depth = Math.max(1, maxDepth | 0);
// Clamp the render resolution to what the wavefront buffers can hold.
// The payload store is the largest per-ray cost (2 regions ×
// WF_PAYLOAD_BYTES) and must fit both maxBufferSize and one storage
// binding; the indirect TRACE/SHADE dispatch additionally bounds rays
// at maxComputeWorkgroupsPerDimension×64. When the canvas is over
// budget, tighten maxCanvasPixels and sit this frame out — the next
// wgpuFrameBegin's ensureSized() shrinks the backing store (CSS keeps
// the on-screen size) and rendering resumes within budget.
const rpp = Math.max(1, raysPerPixel | 0);
const rayBudget = Math.min(
Math.floor(Math.min(device.limits.maxBufferSize,
device.limits.maxStorageBufferBindingSize)
/ (2 * WF_PAYLOAD_BYTES)),
device.limits.maxComputeWorkgroupsPerDimension * 64);
const pixelBudget = Math.max(1, Math.floor(rayBudget / rpp));
if (pixelBudget < maxCanvasPixels) maxCanvasPixels = pixelBudget;
if (W * H > maxCanvasPixels) {
console.warn(`[crafter-wgpu] ${W}x${H} at ${rpp} rays/px overflows the `
+ `wavefront budget (maxBufferSize ${device.limits.maxBufferSize}); `
+ `reducing render resolution to ~${maxCanvasPixels}px`);
return;
}
const wf = ensureWavefrontBuffers(W, H, raysPerPixel);
const cap = wf.cap; // per-bounce ray capacity = raysPerPixel·W·H