scene model과 renderer 분리하기
WebGL이나 WebGPU를 배우기 시작하면 API 이름이 먼저 눈에 들어옵니다. buffer, shader, pipeline, bind group, render pass. 낯선 단어가 많습니다.
하지만 Figma-like 에디터를 만들 때 더 먼저 정해야 하는 것은 API가 아니라 경계입니다.
무엇이 편집기 모델이고,
무엇이 렌더러 상태인가?
model은 문서의 의미를 담는다
scene model은 사용자의 문서를 표현합니다.
const scene = {
nodes: [
{
id: "rect-1",
type: "rect",
transform: [1, 0, 0, 1, 120, 80],
size: [200, 120],
fill: [0.1, 0.3, 0.9, 1]
}
]
};
여기에는 사용자가 편집하고 저장해야 하는 정보가 들어갑니다. 도형의 위치, 크기, 색상, 부모 관계, 잠금 상태, 이름, 선택 가능 여부 같은 것들입니다.
반대로 GPU buffer handle, compiled shader, texture object 같은 것은 scene model에 들어가면 안 됩니다. 그것들은 문서의 의미가 아니라 특정 renderer의 구현 세부사항입니다.
renderer는 model을 읽어서 그린다
renderer는 scene model을 입력으로 받아 화면을 만듭니다.
renderer.render({
scene,
camera,
selection
});
처음에는 renderer가 매 프레임 scene 전체를 읽고 그려도 괜찮습니다. 나중에 성능이 필요해지면 dirty flag, GPU buffer cache, texture cache를 추가할 수 있습니다.
중요한 것은 캐시를 model과 혼동하지 않는 것입니다.
scene.nodes[0].fill = 문서 상태
gpuBuffer = 그 상태를 빠르게 그리기 위한 캐시
캐시는 언제든 버리고 다시 만들 수 있어야 합니다. 문서 상태는 버리면 안 됩니다.
같은 model에 여러 renderer 붙이기
이 강의에서는 같은 scene model을 여러 방식으로 그릴 수 있게 설계합니다.
scene model
-> debug DOM renderer
-> Canvas 2D renderer
-> WebGL renderer
-> WebGPU renderer
처음부터 모든 renderer를 만들 필요는 없습니다. 하지만 경계를 이렇게 잡아두면 학습이 쉬워집니다. WebGL에서 막혔을 때 DOM debug renderer로 model을 확인할 수 있고, WebGPU로 넘어갈 때 editor tool 코드를 다시 쓰지 않아도 됩니다.
input도 renderer를 직접 고치지 않는다
사용자가 도형을 드래그할 때 pointer handler가 GPU buffer를 직접 수정하면 당장은 빠르게 보일 수 있습니다. 하지만 곧 문제가 생깁니다. undo/redo는 무엇을 기록해야 할까요? JSON export는 어디서 읽어야 할까요? WebGPU renderer도 같은 수정을 받아야 할까요?
그래서 입력은 model을 바꿉니다.
pointer drag
-> tool computes new transform
-> command updates scene model
-> renderer reads updated model
renderer는 결과를 보여주는 쪽입니다. 편집의 진실은 model에 있습니다.
오늘의 핵심
GPU 기반 편집기의 안정성은 shader보다 경계에서 먼저 나옵니다.
editor core owns meaning
renderer owns pixels
이 구분이 있으면 WebGL과 WebGPU는 같은 문제를 푸는 두 backend가 됩니다. 구분이 없으면 렌더링 코드와 편집 코드가 서로의 발목을 잡습니다.
다음 레슨부터는 이 model을 실제 픽셀 표면에 그리기 위해 render loop, camera, WebGL buffer를 차례로 붙입니다.