webgpu embedding

This commit is contained in:
Jorijn van der Graaf 2026-07-19 01:38:25 +02:00
commit a879c834c7
4 changed files with 86 additions and 4 deletions

View file

@ -54,6 +54,7 @@ function stub(name) {
"wgpuRegisterMeshBLAS", "wgpuRegisterMeshBLASDeviceAabbs", "wgpuRefitMeshBLASDeviceAabbs",
"wgpuLoadRTPipeline", "wgpuDispatchRT", "wgpuBuildTLAS",
"wgpuLoadComputePipeline", "wgpuDispatchCompute",
"wgpuSetCanvasMount",
]) {
// Read-write ints don't need a stub-throw; return 0 for the size queries.
e[n] = n.endsWith("Width") || n.endsWith("Height")
@ -85,6 +86,11 @@ canvas.style.cssText = "position:fixed;inset:0;width:100vw;height:100vh;display:
document.body.style.margin = "0";
document.body.appendChild(canvas);
// Optional mount target set via wgpuSetCanvasMount() (below). When null the
// canvas is a full-viewport fixed layer (the original, unchanged default);
// when set, the canvas is reparented into that element and sized to it.
let mountEl = null;
function syncCanvasSize() {
// Canvas pixel size = CSS size × devicePixelRatio so the GPU draws
// at physical pixel resolution on HiDPI displays — otherwise the
@ -98,8 +104,18 @@ function syncCanvasSize() {
// share the physical-pixel coordinate space with window.width/.height.
const dpr = window.devicePixelRatio || 1;
window.crafter_dpr = dpr;
const w = Math.max(1, Math.round(window.innerWidth * dpr));
const h = Math.max(1, Math.round(window.innerHeight * dpr));
// Mounted → track the host element's box; unmounted → the viewport.
let cssW, cssH;
if (mountEl) {
const r = mountEl.getBoundingClientRect();
cssW = r.width;
cssH = r.height;
} else {
cssW = window.innerWidth;
cssH = window.innerHeight;
}
const w = Math.max(1, Math.round(cssW * dpr));
const h = Math.max(1, Math.round(cssH * dpr));
if (canvas.width !== w) canvas.width = w;
if (canvas.height !== h) canvas.height = h;
return { w, h };
@ -566,6 +582,40 @@ const env = window.crafter_webbuild_env;
env.wgpuGetCanvasWidth = () => canvas.width;
env.wgpuGetCanvasHeight = () => canvas.height;
// Reparent the render canvas into a host DOM element so a Crafter.Graphics
// scene can render inline inside an app's own layout instead of as a
// full-page layer. `idPtr`/`idLen` is a UTF-8 wasm string:
// - non-empty id of an existing element → move the canvas into it,
// position it to fill it (absolute inset:0), and size the render
// surface to that element's box (syncCanvasSize picks it up per frame).
// - empty string → detach back to <body> and hide it (display:none), so
// the host page is a plain DOM document again.
// The element is made position:relative if it is otherwise static, so the
// absolutely-positioned canvas anchors to it. Pointer events pass through
// (the inline use cases are non-interactive display surfaces).
env.wgpuSetCanvasMount = (idPtr, idLen) => {
const id = idLen > 0
? new TextDecoder().decode(memU8().subarray(idPtr, idPtr + idLen))
: "";
if (!id) {
mountEl = null;
if (canvas.parentNode !== document.body) document.body.appendChild(canvas);
canvas.style.cssText = "position:fixed;inset:0;width:100vw;height:100vh;display:none;";
return;
}
const el = document.getElementById(id);
if (!el) {
console.warn(`[crafter-wgpu] wgpuSetCanvasMount: no element #${id}`);
return;
}
mountEl = el;
if (getComputedStyle(el).position === "static") el.style.position = "relative";
if (canvas.parentNode !== el) el.appendChild(canvas);
canvas.style.cssText =
"position:absolute;inset:0;width:100%;height:100%;display:block;pointer-events:none;";
ensureSized();
};
env.wgpuCreateBuffer = (byteSize) => {
const h = newHandle();
const buf = device.createBuffer({