webgpu improvements

This commit is contained in:
Jorijn van der Graaf 2026-05-24 13:32:08 +02:00
commit 8347467e1e
18 changed files with 1932 additions and 153 deletions

View file

@ -168,15 +168,25 @@ function setValue(cookie, valPtr, valLen) {
// so removeEventListener can re-find it. C++-side handler id counters
// are per-kind, so a per-kind suffix is what makes the keys unique.
// devicePixelRatio scaling factor. dom-webgpu.js sets window.crafter_dpr
// during its canvas sync so this side and the GPU side agree on a single
// physical-pixel coordinate space. Fallback to the live DPR if no GPU
// bridge ran (pure-CppDOM apps); ultimately fallback to 1 so non-HiDPI
// browsers behave as before.
function __dpr() {
return window.crafter_dpr || window.devicePixelRatio || 1;
}
function __makeMouseListenerPair(kind, eventName, exportName) {
return {
add(cookie, id) {
const el = __jsmemory.get(cookie);
if (!el) return;
const handler = (event) => {
const s = __dpr();
__wasm()[exportName](id,
event.clientX, event.clientY,
event.screenX, event.screenY,
event.clientX * s, event.clientY * s,
event.screenX * s, event.screenY * s,
event.button, event.buttons,
event.altKey, event.ctrlKey, event.shiftKey, event.metaKey);
};
@ -317,7 +327,10 @@ const __resizePair = {
// Resize is window-global in CppDOM. Mirror that: attach to `window`
// regardless of which element the C++ caller passed.
add(cookie, id) {
const handler = () => __wasm().ExecuteResizeHandler(id, window.innerWidth, window.innerHeight);
const handler = () => {
const s = __dpr();
__wasm().ExecuteResizeHandler(id, window.innerWidth * s, window.innerHeight * s);
};
__listenerHandlers.set(`${cookie}-${id}-resize`, handler);
window.addEventListener("resize", handler);
},
@ -345,9 +358,10 @@ const __wheelPair = {
add(cookie, id) {
const el = __jsmemory.get(cookie); if (!el) return;
const handler = (event) => {
const s = __dpr();
__wasm().ExecuteWheelHandler(id,
event.deltaX, event.deltaY, event.deltaZ, event.deltaMode,
event.clientX, event.clientY, event.screenX, event.screenY,
event.clientX * s, event.clientY * s, event.screenX * s, event.screenY * s,
event.button, event.buttons,
event.altKey, event.ctrlKey, event.shiftKey, event.metaKey);
};
@ -378,11 +392,97 @@ function domAttachWindow(windowHandle) {
if (fn) fn(__windowAttachedHandle, ...args);
};
__windowListeners.mousemove = (e) => fire("__crafterDom_mouseMove", [e.clientX, e.clientY]);
__windowListeners.mousedown = (e) => fire("__crafterDom_mouseDown", [e.button]);
__windowListeners.mouseup = (e) => fire("__crafterDom_mouseUp", [e.button]);
// Synthetic absolute position for pointer-lock mode. While the
// pointer is locked, browsers fire mousemove events with movementX/Y
// deltas instead of meaningful clientX/Y, and the cursor is hidden +
// captured by the canvas (no window-edge clamp). We accumulate the
// deltas into a synthetic position and feed *that* to the C++ side,
// so the existing `currentMousePos - lastMousePos` delta computation
// keeps working unchanged. Initialised to the cursor position the
// moment lock is acquired.
let __ptrLockSyntheticX = 0;
let __ptrLockSyntheticY = 0;
const __isPointerLocked = () =>
document.pointerLockElement !== null &&
document.pointerLockElement !== undefined;
// pointermove (not mousemove) so we can pull sub-frame events out of
// `getCoalescedEvents()`. Browsers normally collapse multiple raw
// mouse events between paint frames into a single event you'd see
// via `mousemove`; PointerEvent.getCoalescedEvents() returns the raw
// pre-coalesced list. Summing those gives a higher-resolution delta
// per frame than the single coalesced movementX/Y. PointerEvent also
// delivers fractional movementX from high-precision mice on Chromium.
__windowListeners.mousemove = (e) => {
const s = __dpr();
const locked = __isPointerLocked();
if (locked) {
// Accumulate over every sub-frame event the browser had
// queued up. `getCoalescedEvents` is the spec-correct way
// to access raw input between rAF ticks. Some browsers
// return an empty list — fall back to the top-level event.
let dx = 0, dy = 0;
const sub = (typeof e.getCoalescedEvents === "function")
? e.getCoalescedEvents() : null;
if (sub && sub.length > 0) {
for (let i = 0; i < sub.length; i++) {
dx += sub[i].movementX;
dy += sub[i].movementY;
}
} else {
dx = e.movementX;
dy = e.movementY;
}
// No DPR scaling in pointer-lock: position is synthetic and
// there's no UI hit-test using it. DPR-scaling here only
// rounds finer movements up to multiples of `dpr`, which is
// pure quantization loss for aim controls.
__ptrLockSyntheticX += dx;
__ptrLockSyntheticY += dy;
fire("__crafterDom_mouseMove",
[__ptrLockSyntheticX, __ptrLockSyntheticY]);
} else {
fire("__crafterDom_mouseMove", [e.clientX * s, e.clientY * s]);
}
};
__windowListeners.mousedown = (e) => {
// Right-click holds engage pointer lock — typical FPS-camera
// convention. Acquiring on any click (the previous policy) made
// menus annoying: clicking a button hid the cursor mid-flow. Now
// the cursor stays free for clicks/menus until the user holds
// RMB to actively look around. Browsers require lock requests
// from user gestures, which mousedown satisfies.
if (e.button === 2 && !__isPointerLocked()) {
const target = document.body;
if (target && target.requestPointerLock) {
target.requestPointerLock();
// Seed the synthetic position from the click point so
// there's no jump when the lock starts producing deltas.
__ptrLockSyntheticX = e.clientX;
__ptrLockSyntheticY = e.clientY;
}
}
fire("__crafterDom_mouseDown", [e.button]);
};
__windowListeners.mouseup = (e) => {
// Release lock on RMB up — cursor reappears at the seed point
// for clicks/menus until the next RMB hold.
if (e.button === 2 && __isPointerLocked()) {
document.exitPointerLock();
}
fire("__crafterDom_mouseUp", [e.button]);
};
__windowListeners.wheel = (e) => fire("__crafterDom_wheel", [e.deltaY]);
__windowListeners.contextmenu = (e) => { e.preventDefault(); };
__windowListeners.pointerlockchange = () => {
// Reset the synthetic accumulator when lock is released so the
// next acquisition starts cleanly. The C++ side will see one
// small jump back to the real cursor position on release.
if (!__isPointerLocked()) {
__ptrLockSyntheticX = 0;
__ptrLockSyntheticY = 0;
}
};
// Keyboard events go through the document so they fire even when no
// input element is focused. event.code is the layout-independent
@ -400,16 +500,24 @@ function domAttachWindow(windowHandle) {
__wasm().WasmFree(codePtr);
};
__windowListeners.resize = () => fire("__crafterDom_resize", [window.innerWidth, window.innerHeight]);
__windowListeners.resize = () => {
const s = __dpr();
fire("__crafterDom_resize", [window.innerWidth * s, window.innerHeight * s]);
};
__windowListeners.beforeunload = () => fire("__crafterDom_close", []);
document.addEventListener("mousemove", __windowListeners.mousemove);
// pointermove (not mousemove) so the handler receives PointerEvents
// and can use getCoalescedEvents() to recover sub-frame motion. The
// handler's variable name stays "mousemove" — it's the same JS object,
// just bound to a different event type.
document.addEventListener("pointermove", __windowListeners.mousemove);
document.addEventListener("mousedown", __windowListeners.mousedown);
document.addEventListener("mouseup", __windowListeners.mouseup);
document.addEventListener("wheel", __windowListeners.wheel);
document.addEventListener("contextmenu", __windowListeners.contextmenu);
document.addEventListener("keydown", __windowListeners.keydown);
document.addEventListener("keyup", __windowListeners.keyup);
document.addEventListener("pointerlockchange", __windowListeners.pointerlockchange);
window .addEventListener("resize", __windowListeners.resize);
window .addEventListener("beforeunload",__windowListeners.beforeunload);
}
@ -418,8 +526,8 @@ function domSetTitle(titlePtr, titleLen) {
document.title = __readUtf8(titlePtr, titleLen);
}
function domGetInnerWidth() { return window.innerWidth; }
function domGetInnerHeight() { return window.innerHeight; }
function domGetInnerWidth() { return Math.round(window.innerWidth * __dpr()); }
function domGetInnerHeight() { return Math.round(window.innerHeight * __dpr()); }
// ─── requestAnimationFrame loop ───────────────────────────────────────

File diff suppressed because it is too large Load diff