여러 rectangle을 buffer에 담기
rectangle 하나를 그릴 수 있으면 다음 질문은 분명합니다.
도형이 100개면 어떻게 그릴까?
가장 단순한 방법은 각 rectangle의 vertex를 하나의 큰 buffer에 담고 한 번에 그리는 것입니다.
scene node 배열을 buffer로 펴는 코드
실제 renderer는 scene node 객체를 그대로 GPU에 보내지 않습니다. 먼저 숫자 배열로 번역합니다.
function pushRect(vertices, node) {
const { x, y, width, height, fill } = node;
const x0 = x;
const y0 = y;
const x1 = x + width;
const y1 = y + height;
const [r, g, b, a] = fill;
vertices.push(
x0, y0, r, g, b, a,
x1, y0, r, g, b, a,
x0, y1, r, g, b, a,
x0, y1, r, g, b, a,
x1, y0, r, g, b, a,
x1, y1, r, g, b, a
);
}
const vertices = [];
for (const node of scene.nodes) {
pushRect(vertices, node);
}
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.DYNAMIC_DRAW);
position과 color가 한 buffer에 섞여 있으므로 attribute stride를 맞춰야 합니다.
const stride = 6 * Float32Array.BYTES_PER_ELEMENT;
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, stride, 0);
gl.enableVertexAttribArray(colorLocation);
gl.vertexAttribPointer(
colorLocation,
4,
gl.FLOAT,
false,
stride,
2 * Float32Array.BYTES_PER_ELEMENT
);
gl.drawArrays(gl.TRIANGLES, 0, vertices.length / 6);
buffer는 GPU가 읽을 vertex 배열이다
CPU의 scene model은 객체 배열입니다.
scene.nodes = [
{ x: 20, y: 40, width: 120, height: 80 },
{ x: 180, y: 90, width: 160, height: 100 }
];
GPU는 이런 객체를 직접 읽지 않습니다. renderer가 숫자 배열로 펴서 buffer에 올립니다.
node objects -> flat vertex array -> GPU buffer
interleaved data로 시작할 수 있다
각 vertex에 position과 color를 같이 넣을 수 있습니다.
x, y, r, g, b, a
x, y, r, g, b, a
...
이런 방식을 interleaved buffer라고 부릅니다. 초반에는 한 buffer에 필요한 값을 같이 넣는 편이 이해하기 쉽습니다.
scene이 바뀌면 buffer를 다시 만든다
처음에는 scene 전체를 순회해서 매 프레임 buffer를 다시 만들어도 됩니다. 나중에 성능이 필요하면 변경된 node만 반영하는 cache를 붙입니다.
simple first:
rebuild all vertices
optimize later:
dirty ranges
bufferSubData
instance data
중요한 것은 buffer가 문서 모델이 아니라 renderer cache라는 점입니다.
오늘의 핵심
WebGL renderer는 scene model을 GPU가 읽을 수 있는 숫자 배열로 번역합니다.
scene nodes
-> rectangle vertices
-> GPU buffer
-> drawArrays
이 번역 레이어가 명확하면 editor state와 GPU 최적화를 분리할 수 있습니다.