batching과 draw call 줄이기
WebGL에서 도형마다 draw call을 하나씩 호출하면 구조는 단순합니다.
draw rect A
draw rect B
draw rect C
하지만 도형이 많아지면 draw call 자체가 비용이 됩니다. 그래서 같은 방식으로 그릴 수 있는 도형을 묶습니다. 이것이 batching입니다.
adjacent batch를 만드는 코드
편집기에서는 layer order를 깨면 안 됩니다. 그래서 처음에는 “인접한 node 중 같은 renderer state인 것만 묶기”가 안전합니다.
function batchKey(node) {
if (node.type === "rect") return "solid";
if (node.type === "image") return `image:${node.textureId}`;
if (node.type === "outline") return "overlay-outline";
return "unknown";
}
function buildBatches(nodes) {
const batches = [];
for (const node of nodes) {
const key = batchKey(node);
const last = batches[batches.length - 1];
if (last && last.key === key) {
last.nodes.push(node);
} else {
batches.push({ key, nodes: [node] });
}
}
return batches;
}
렌더링은 batch 단위로 state를 설정하고, 그 batch의 vertex만 buffer에 올려 그립니다.
for (const batch of buildBatches(scene.nodes)) {
usePipelineFor(batch.key);
uploadBatchVertices(batch.nodes);
gl.drawArrays(gl.TRIANGLES, 0, batchVertexCount);
}
이 방식은 모든 rect를 무조건 한곳으로 모으지 않습니다. 화면 순서를 지키면서 연속된 compatible node만 묶습니다.
같은 pipeline이면 묶을 수 있다
같은 shader, 같은 blending 정책, 같은 texture를 쓰는 도형들은 보통 한 batch로 묶기 좋습니다.
solid color rectangles -> one batch
image rectangles using same atlas -> one batch
selection outlines -> another batch
batch가 바뀌는 순간 renderer state도 바뀌는 경우가 많습니다.
draw call보다 state change가 더 중요할 때도 있다
shader program을 바꾸거나 texture를 바꾸는 일은 비용이 큽니다. 그래서 단순히 draw call 수만 보는 것이 아니라 renderer state 전환을 줄여야 합니다.
bad:
rect, image, rect, image
better:
all rects, then all images
하지만 editor에서는 layer order도 중요합니다. 무조건 종류별로 묶으면 시각적 앞뒤가 깨질 수 있습니다.
편집기에서는 batching과 layer order가 충돌한다
2D design editor는 사용자가 정한 레이어 순서를 지켜야 합니다. 투명도까지 있으면 순서가 더 중요합니다.
그래서 batching 정책은 렌더링 정확도와 성능 사이의 균형입니다.
preserve layer order first
batch adjacent compatible nodes
optimize further only when needed
초반 renderer는 정확도를 우선합니다. 성능 최적화는 측정한 뒤에 들어갑니다.
오늘의 핵심
batching은 많은 도형을 적은 GPU 명령으로 그리기 위한 전략입니다. 하지만 편집기에서는 layer order가 먼저입니다.
correct pixels first
then fewer draw calls
이 기준을 지키면 성능 최적화가 렌더링 버그를 만들 가능성이 줄어듭니다.