mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:50:43 +00:00
fix: eliminate WebGL camera-sync lag and forced-reflow cost
Two issues with mounting the WebGL renderer alongside canvas2D: 1. One-frame camera lag. WebGL had its own RAF loop independent of canvas2D's. When the user panned, WebGL's RAF could fire before canvas2D's syncCamera ran, drawing with stale camera state. Fix: pass a capturing raf/caf to the renderer so its loop never actually schedules itself; invoke the captured frame callback synchronously from canvas2D's onPreRender hook, after setCameraState. Both renderers now lock-step on a single RAF. 2. Layout thrashing. syncCamera read glCanvas.clientWidth/Height every frame, forcing a synchronous layout flush — ~11% CPU under Layout. Fix: cache canvas dimensions and update via ResizeObserver. Canvas size changes are rare; the cached values are accurate between resizes.
This commit is contained in:
@@ -248,6 +248,21 @@ function mountWebGLDebugRenderer(
|
||||
glCanvas.style.pointerEvents = "none";
|
||||
document.body.insertBefore(glCanvas, document.body.firstChild);
|
||||
|
||||
// Capture the WebGL renderer's animation-frame callback rather than letting
|
||||
// it run its own RAF loop. Two independent RAF loops race: when the user
|
||||
// pans, the WebGL renderer can draw with one-frame-stale camera state
|
||||
// because its RAF fires before canvas2D's RAF (which would have synced the
|
||||
// camera). Driving WebGL's draw synchronously from canvas2D's onPreRender
|
||||
// hook locks them to the same frame.
|
||||
let cachedWebGLFrameCallback: FrameRequestCallback | null = null;
|
||||
const captureRaf = (cb: FrameRequestCallback): number => {
|
||||
cachedWebGLFrameCallback = cb;
|
||||
return 0;
|
||||
};
|
||||
const captureCaf = (_id: number): void => {
|
||||
cachedWebGLFrameCallback = null;
|
||||
};
|
||||
|
||||
const palette = new Float32Array(4096 * 2 * 4);
|
||||
const view = new WebGLGameView(
|
||||
glCanvas,
|
||||
@@ -264,6 +279,8 @@ function mountWebGLDebugRenderer(
|
||||
},
|
||||
terrainBytes,
|
||||
palette,
|
||||
captureRaf,
|
||||
captureCaf,
|
||||
);
|
||||
|
||||
// Names are rendered by the existing HTML NameLayer; disable the renderer's
|
||||
@@ -277,20 +294,40 @@ function mountWebGLDebugRenderer(
|
||||
}
|
||||
});
|
||||
|
||||
// Cache canvas dimensions to avoid forced reflows every frame. Reading
|
||||
// clientWidth/clientHeight flushes pending layout — at 60fps that's a
|
||||
// measurable cost. Only update on resize events from the observer.
|
||||
let cachedCanvasW = glCanvas.clientWidth;
|
||||
let cachedCanvasH = glCanvas.clientHeight;
|
||||
const resizeObs = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const { width, height } = entry.contentRect;
|
||||
if (width > 0 && height > 0) {
|
||||
cachedCanvasW = width;
|
||||
cachedCanvasH = height;
|
||||
}
|
||||
}
|
||||
});
|
||||
resizeObs.observe(glCanvas);
|
||||
|
||||
const syncCamera = (): void => {
|
||||
const scale = transformHandler.scale;
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const canvasW = glCanvas.clientWidth;
|
||||
const canvasH = glCanvas.clientHeight;
|
||||
const centerX =
|
||||
transformHandler.offsetX +
|
||||
mapWidth / 2 +
|
||||
(canvasW - mapWidth) / (2 * scale);
|
||||
(cachedCanvasW - mapWidth) / (2 * scale);
|
||||
const centerY =
|
||||
transformHandler.offsetY +
|
||||
mapHeight / 2 +
|
||||
(canvasH - mapHeight) / (2 * scale);
|
||||
(cachedCanvasH - mapHeight) / (2 * scale);
|
||||
view.setCameraState(centerX, centerY, scale * dpr);
|
||||
// Invoke the WebGL renderer's frame callback synchronously, with the just-
|
||||
// updated camera state. The callback re-arms itself via captureRaf, so
|
||||
// we'll get a fresh callback ready for the next canvas2D frame.
|
||||
const cb = cachedWebGLFrameCallback;
|
||||
cachedWebGLFrameCallback = null;
|
||||
cb?.(performance.now());
|
||||
};
|
||||
|
||||
(window as unknown as { __webglView?: unknown }).__webglView = view;
|
||||
|
||||
Reference in New Issue
Block a user