DPR, resize, context lost 처리
GPU canvas는 브라우저 창 크기, sidebar, devicePixelRatio 변화, context lost의 영향을 받습니다.
resize와 context lost를 lifecycle로 묶는 코드
canvas 크기 변경과 WebGL context 복구는 renderer가 직접 소유해야 합니다.
function attachCanvasLifecycle(canvas, renderer) {
const resizeObserver = new ResizeObserver(() => {
const rect = canvas.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
const width = Math.max(1, Math.round(rect.width * dpr));
const height = Math.max(1, Math.round(rect.height * dpr));
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
renderer.resize(width, height, dpr);
}
});
canvas.addEventListener("webglcontextlost", (event) => {
event.preventDefault();
renderer.markContextLost();
});
canvas.addEventListener("webglcontextrestored", () => {
renderer.recreateGpuResources();
});
resizeObserver.observe(canvas);
return () => resizeObserver.disconnect();
}
resize는 renderer lifecycle이다
const rect = canvas.getBoundingClientRect();
canvas.width = Math.round(rect.width * devicePixelRatio);
canvas.height = Math.round(rect.height * devicePixelRatio);
WebGL에서는 webglcontextlost와 webglcontextrestored 이벤트도 처리해야 합니다.
context 복구 시 resource를 다시 만든다
class WebGlRenderer {
markContextLost() {
this.contextLost = true;
this.programs.clear();
this.buffers.clear();
this.textures.clear();
}
recreateGpuResources() {
this.gl = this.canvas.getContext("webgl2");
this.contextLost = false;
this.createPrograms();
this.reuploadStaticBuffers();
this.markAllTexturesDirty();
this.requestRender();
}
render() {
if (this.contextLost) return;
this.drawFrame();
}
}
context lost 이후에는 기존 WebGL object를 믿으면 안 됩니다. shader program, buffer, texture를 다시 만들 수 있도록 CPU 쪽 source data를 따로 가지고 있어야 합니다.
오늘의 핵심
크기와 context 복구는 부가 기능이 아닙니다. 배포 가능한 renderer의 기본 lifecycle입니다.