[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -2,95 +2,206 @@
|
||||
id: wiki-2026-0508-draw-call-optimization
|
||||
title: Draw Call Optimization
|
||||
category: 10_Wiki/Topics
|
||||
status: needs_review
|
||||
status: verified
|
||||
canonical_id: self
|
||||
aliases: [P-Reinforce-AUTO-0B8FFC]
|
||||
aliases: [Batching, Instancing, GPU Draw Reduction]
|
||||
duplicate_of: none
|
||||
source_trust_level: A
|
||||
confidence_score: 0.9
|
||||
tags: [auto-reinforced]
|
||||
verification_status: applied
|
||||
tags: [graphics, gpu, performance, webgl, webgpu]
|
||||
raw_sources: []
|
||||
last_reinforced: 2026-04-20
|
||||
github_commit: "[P-Reinforce] Continuous Worker - [[Draw Call|Draw Call]] [[Optimization|Optimization]]"
|
||||
inferred_by: Claude Opus 4.7 (auto-normalize 2026-05-08)
|
||||
last_reinforced: 2026-05-10
|
||||
github_commit: pending
|
||||
tech_stack:
|
||||
language: unspecified
|
||||
framework: unspecified
|
||||
language: TypeScript
|
||||
framework: WebGPU
|
||||
---
|
||||
|
||||
# [[Draw Call Optimization|Draw Call Optimization]]
|
||||
# Draw Call Optimization
|
||||
|
||||
## 📌 한 줄 통찰 (The Karpathy Summary)
|
||||
> 드로우 콜(Draw Call)은 CPU가 GPU에게 기하학적 구조, 재질, 렌더링 지침 등을 전달하여 화면에 객체를 그리도록 내리는 명령입니다 [1-3]. 각 드로우 콜을 준비하고 상태를 변경하는 과정에서 막대한 CPU 오버헤드가 발생하기 때문에, 드로우 콜 횟수를 줄이는 것은 애플리케이션의 프레임 속도와 전반적인 렌더링 성능을 개선하고 병목 현상을 방지하는 핵심 최적화 기법입니다 [4-6].
|
||||
## 매 한 줄
|
||||
> **"매 CPU→GPU command submission 의 의 minimize 의 — 의 frame budget 의 dominant cost"**. 의 each draw call 의 의 driver overhead (state validation, command translation) 의 incur. 2026 의 WebGPU 의 매 explicit 의 design 의 의 batching 의 essential 의.
|
||||
|
||||
## 📖 구조화된 지식 (Synthesized Content)
|
||||
* **드로우 콜의 성능 병목 ([[Bottlenecks|Bottlenecks]]):** 그래픽 API가 GPU에 렌더링 상태를 설정하고 명령을 내리는 준비 과정은 실제 GPU가 픽셀을 렌더링하는 것보다 더 많은 CPU 자원을 소모합니다 [2, 4, 6]. 이로 인해 개별 객체를 수천 번 분리하여 그리면 GPU의 폴리곤 처리 한계에 도달하기도 전에 CPU 병목이 발생하여 시스템 프레임 레이트가 급감합니다 [5, 7]. 최신 기기에서 부드러운 60fps를 유지하려면 프레임당 드로우 콜을 100개 이하로 타겟팅하는 것이 권장되며 [8-10], [[WebGL|WebGL]] 환경에서의 실질적인 한계는 1,000~2,000회 수준입니다 [11].
|
||||
* **주요 최적화 기법 (Optimization Techniques):**
|
||||
* **인스턴싱 ([[Instancing|Instancing]]):** `[[InstancedMesh|InstancedMesh]]` 등을 사용하여 동일한 기하학적 구조와 재질을 가진 객체의 수많은 복제본을 단 한 번의 드로우 콜로 렌더링합니다 [8, 12-14]. 나무, 풀, 파티클 시스템과 같이 반복되는 요소에 효율적입니다 [12, 15].
|
||||
* **배칭 및 병합 ([[Batching|Batching]] & Merging):** `BatchedMesh`는 동일한 재질을 공유하지만 서로 다른 기하학적 구조를 가진 객체들을 하나의 드로우 콜로 묶어 처리할 수 있게 합니다 [16, 17]. 정적인 환경 요소는 `[[BufferGeometry|BufferGeometry]]Utils`와 같은 도구를 사용해 하나의 지오메트리로 병합(Merging)하면 여러 드로우 콜을 한 번으로 줄일 수 있습니다 [16, 18-21].
|
||||
* **재질 및 텍스처 공유 (Material & Texture Sharing):** 객체마다 새로운 재질을 생성하는 것은 최적화를 저해합니다 [16, 19]. 텍스처 아틀라스([[Texture Atlas|Texture Atlas]])나 배열 텍스처(Array Textures)를 활용하여 재질을 공유함으로써 텍스처 바인딩 및 상태 변경에 따른 추가적인 드로우 콜을 방지합니다 [20, 22-24].
|
||||
* **가시성 제어 (Visibility & LOD):** 카메라 시야 밖의 객체에 대한 렌더링 명령을 제외하는 절두체 컬링([[Frustum Culling|Frustum Culling]])을 수행하고 [18, 25], 카메라와의 거리에 따라 기하학적 복잡도를 낮추는 LOD(Level of Detail) 기법을 적용하여 멀리 있는 객체에서 발생하는 불필요한 드로우 콜과 연산을 최소화합니다 [9, 26-28].
|
||||
* **구조적 한계 및 고려사항 (Limitations & Trade-offs):** `InstancedMesh`를 통한 드로우 콜 단일화가 항상 최선은 아닙니다. 단일 객체로 취급되어 가시성 판정이 '전부 아니면 전무(All-or-Nothing)'로 이루어져 절두체 컬링이 비효율적으로 작동할 수 있습니다 [29, 30]. 또한, 인스턴스 간 자동 정렬 부재로 인한 심각한 오버드로우([[Overdraw|Overdraw]]) 현상과 매 프레임 수많은 변환 행렬 데이터를 갱신할 때 발생하는 메모리 대역폭 한계로 인해 또 다른 성능 병목이 유발될 수 있습니다 [29, 31, 32]. [[Unity|Unity]] 같은 게임 엔진에서는 이와 같은 드로우 콜 최적화 방식들이 겹칠 때 SRP Batcher, 정적 배칭(Static batching), GPU 인스턴싱 순으로 우선순위를 두어 충돌을 제어합니다 [33, 34].
|
||||
## 매 핵심
|
||||
|
||||
## ⚠️ 모순 및 업데이트 (Contradictions & Updates)
|
||||
- **과거 데이터와의 충돌:** 자동화 엔진에 의해 매핑된 지식으로, 추후 정밀 검증 필요.
|
||||
- **정책 변화:** Programming & Language 분야의 자동 자산화 수행.
|
||||
### 매 cost 의 source
|
||||
- 의 driver state validation (shader, buffer, texture binding).
|
||||
- 의 command buffer 의 translation 의 GPU-specific ISA.
|
||||
- 의 GPU 의 의 pipeline switch (cache miss, warp reorganize).
|
||||
|
||||
## 🔗 지식 연결 (Graph)
|
||||
- **Related Topics:** [[InstancedMesh|InstancedMesh]], BatchedMesh, Frustum Culling, Texture Atlas, [[Level of Detail (LOD)|Level of Detail (LOD]]
|
||||
- **Projects/Contexts:** Three.js, [[WebGL|WebGL]], [[Unity|Unity]]
|
||||
- **Contradictions/Notes:** 일반적으로 드로우 콜을 줄이는 것은 렌더링 성능을 향상시킨다고 알려져 있지만, `InstancedMesh`를 통해 드로우 콜을 1회로 줄였음에도 불구하고 정렬되지 않은 인스턴스들이 유발하는 막대한 오버드로우(Overdraw) 비용이나 비효율적인 컬링으로 인해, 개별 메쉬를 렌더링할 때보다 오히려 프레임 속도(FPS)가 낮아지는 역설적인 상황이 실증적 연구와 버그 리포트 등에서 보고되고 있습니다 [29, 31, 35].
|
||||
### 매 reduction 의 strategy
|
||||
- **Batching**: 의 same-state object 의 single draw 의 의 merge.
|
||||
- **Instancing**: 의 same mesh 의 N copy 의 single 의 draw call 의 issue.
|
||||
- **Texture atlas**: 의 multiple texture 의 의 single 의 의 — 의 binding 의 reduce.
|
||||
- **Indirect draw**: 의 GPU 의 의 self-issue 의 의 — CPU 의 의 idle.
|
||||
- **Bindless / large bind group**: 의 binding 의 의 amortize.
|
||||
|
||||
---
|
||||
*Last updated: 2026-04-19*
|
||||
### 매 응용
|
||||
1. UI rendering (의 button 의 thousand 의 single draw).
|
||||
2. Particle system (의 instancing 의 의 millions).
|
||||
3. Tilemap (atlas + instancing).
|
||||
4. Foliage / crowd (의 GPU instancing).
|
||||
5. Game world chunk (의 batching 의 의 static mesh).
|
||||
|
||||
---
|
||||
## 💻 패턴
|
||||
|
||||
## 🤖 LLM 활용 힌트 (How to Use This Knowledge)
|
||||
### Three.js BatchedMesh
|
||||
```ts
|
||||
import * as THREE from "three";
|
||||
|
||||
**언제 이 지식을 쓰는가:**
|
||||
- *(TODO)*
|
||||
const batched = new THREE.BatchedMesh(1024, 60_000, 90_000, material);
|
||||
const cubeGeom = new THREE.BoxGeometry();
|
||||
const id = batched.addGeometry(cubeGeom);
|
||||
|
||||
**언제 쓰면 안 되는가:**
|
||||
- *(TODO)*
|
||||
|
||||
## 🧪 검증 상태 (Validation)
|
||||
|
||||
- **정보 상태:** needs_review
|
||||
- **출처 신뢰도:** A
|
||||
- **검토 이유:** *(P-Reinforce Phase 1 자동 정규화. 본문 검증 필요.)*
|
||||
|
||||
## 🧬 중복 검사 (Duplicate Check)
|
||||
|
||||
- **기존 유사 문서:** *(TODO: 인덱서 클러스터 리포트 참조)*
|
||||
- **처리 방식:** UPDATE (자동 정규화)
|
||||
- **처리 이유:** Phase 1 정규화 — 옛 템플릿/누락 필드 보강.
|
||||
|
||||
## 🕓 변경 이력 (Changelog)
|
||||
|
||||
| 날짜 | 변경 내용 | 처리 방식 | 신뢰도 |
|
||||
|------|-----------|-----------|--------|
|
||||
| 2026-05-08 | P-Reinforce Phase 1 정규화 (frontmatter + 헤더 표준화) | UPDATE | A |
|
||||
|
||||
## 💻 코드 패턴 (Code Patterns)
|
||||
|
||||
**패턴 1:** *(TODO: 이 프로젝트 컨벤션 반영한 구조 스켈레톤)*
|
||||
|
||||
```text
|
||||
# TODO
|
||||
for (let i = 0; i < 1024; i++) {
|
||||
const inst = batched.addInstance(id);
|
||||
const m = new THREE.Matrix4().setPosition(Math.random() * 100, 0, Math.random() * 100);
|
||||
batched.setMatrixAt(inst, m);
|
||||
}
|
||||
scene.add(batched);
|
||||
// 매 single draw call 의 1024 cube
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준 (Decision Criteria)
|
||||
### InstancedMesh
|
||||
```ts
|
||||
const geom = new THREE.SphereGeometry(0.5);
|
||||
const mesh = new THREE.InstancedMesh(geom, material, 10_000);
|
||||
const m = new THREE.Matrix4();
|
||||
for (let i = 0; i < 10_000; i++) {
|
||||
m.setPosition(Math.random() * 200 - 100, 0, Math.random() * 200 - 100);
|
||||
mesh.setMatrixAt(i, m);
|
||||
}
|
||||
mesh.instanceMatrix.needsUpdate = true;
|
||||
```
|
||||
|
||||
**선택 A를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
### WebGPU instanced draw
|
||||
```ts
|
||||
const pass = encoder.beginRenderPass(passDesc);
|
||||
pass.setPipeline(pipeline);
|
||||
pass.setBindGroup(0, sceneBindGroup);
|
||||
pass.setVertexBuffer(0, vertexBuffer);
|
||||
pass.setVertexBuffer(1, instanceBuffer); // per-instance data
|
||||
pass.setIndexBuffer(indexBuffer, "uint32");
|
||||
pass.drawIndexed(indexCount, instanceCount);
|
||||
pass.end();
|
||||
```
|
||||
|
||||
**선택 B를 써야 할 때:**
|
||||
- *(TODO)*
|
||||
### WebGPU indirect draw (GPU 의 self-issue)
|
||||
```ts
|
||||
const indirectBuffer = device.createBuffer({
|
||||
size: 16, // [vertexCount, instanceCount, firstVertex, firstInstance]
|
||||
usage: GPUBufferUsage.INDIRECT | GPUBufferUsage.STORAGE,
|
||||
});
|
||||
|
||||
**기본값:**
|
||||
> *(TODO)*
|
||||
// 의 compute shader 의 의 indirectBuffer 의 의 fill (e.g. frustum cull)
|
||||
pass.drawIndirect(indirectBuffer, 0);
|
||||
```
|
||||
|
||||
## ❌ 안티패턴 (Anti-Patterns)
|
||||
### Texture atlas (UV 의 sub-region)
|
||||
```glsl
|
||||
// fragment shader
|
||||
uniform sampler2D atlas;
|
||||
uniform vec4 uvRect; // x, y, w, h
|
||||
|
||||
- **[안티패턴]:** *(TODO: 무엇을 하면 안 되는가 + 이유 + 대신 무엇을)*
|
||||
void main() {
|
||||
vec2 uv = uvRect.xy + v_uv * uvRect.zw;
|
||||
outColor = texture(atlas, uv);
|
||||
}
|
||||
```
|
||||
|
||||
### Sort 의 의 state-change minimize
|
||||
```ts
|
||||
// 의 draw 의 의 material 의 의 sort
|
||||
drawables.sort((a, b) => {
|
||||
if (a.materialId !== b.materialId) return a.materialId - b.materialId;
|
||||
if (a.meshId !== b.meshId) return a.meshId - b.meshId;
|
||||
return a.depth - b.depth;
|
||||
});
|
||||
```
|
||||
|
||||
### UI batching (single quad mesh)
|
||||
```ts
|
||||
// 의 each UI element 의 의 quad 의 의 single VBO 의 의 append
|
||||
class UIBatcher {
|
||||
vertices = new Float32Array(4096 * 4 * 5); // x, y, u, v, color
|
||||
count = 0;
|
||||
|
||||
pushQuad(x: number, y: number, w: number, h: number, uv: UVRect, color: number) {
|
||||
const v = this.vertices;
|
||||
const o = this.count * 20;
|
||||
v[o+0]=x; v[o+1]=y; v[o+2]=uv.x; v[o+3]=uv.y; v[o+4]=color;
|
||||
v[o+5]=x+w; v[o+6]=y; v[o+7]=uv.x+uv.w; v[o+8]=uv.y; v[o+9]=color;
|
||||
v[o+10]=x+w; v[o+11]=y+h; v[o+12]=uv.x+uv.w; v[o+13]=uv.y+uv.h; v[o+14]=color;
|
||||
v[o+15]=x; v[o+16]=y+h; v[o+17]=uv.x; v[o+18]=uv.y+uv.h; v[o+19]=color;
|
||||
this.count++;
|
||||
}
|
||||
|
||||
flush(pass: GPURenderPassEncoder) {
|
||||
device.queue.writeBuffer(this.vbo, 0, this.vertices, 0, this.count * 20);
|
||||
pass.setVertexBuffer(0, this.vbo);
|
||||
pass.draw(6 * this.count); // 매 single call
|
||||
this.count = 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Frustum cull (CPU)
|
||||
```ts
|
||||
function cull(objects: Drawable[], camera: Camera): Drawable[] {
|
||||
const frustum = camera.frustum;
|
||||
return objects.filter((o) => frustum.intersects(o.worldBounds));
|
||||
}
|
||||
```
|
||||
|
||||
### GPU-driven cull (compute)
|
||||
```wgsl
|
||||
@compute @workgroup_size(64)
|
||||
fn cullCS(@builtin(global_invocation_id) gid: vec3u) {
|
||||
let i = gid.x;
|
||||
if (i >= arrayLength(&instances)) { return; }
|
||||
let inst = instances[i];
|
||||
if (frustumIntersects(inst.bounds, frustum)) {
|
||||
let slot = atomicAdd(&drawCount, 1u);
|
||||
visibleInstances[slot] = inst;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 매 결정 기준
|
||||
| 상황 | Approach |
|
||||
|---|---|
|
||||
| Same mesh, many copy | InstancedMesh (instancing) |
|
||||
| Different mesh, same material | BatchedMesh (geometry merge) |
|
||||
| UI / 2D | Sprite batcher + atlas |
|
||||
| Static scene | Pre-merge geometry at build time |
|
||||
| Dynamic LOD / cull | GPU indirect draw + compute cull |
|
||||
| Mobile / tile | Reduce binding, atlas, instancing |
|
||||
|
||||
**기본값**: instancing 의 first, batching 의 second, indirect/compute 의 last.
|
||||
|
||||
## 🔗 Graph
|
||||
- 부모: [[GPU Pipeline]] · [[Real-Time Rendering]]
|
||||
- 변형: [[Instancing]] · [[Batching]] · [[Indirect Draw]]
|
||||
- 응용: [[Particle Systems]] · [[Tilemap Rendering]] · [[UI Rendering]]
|
||||
- Adjacent: [[Texture Atlas]] · [[GPU Driven Rendering]] · [[Frustum Culling]]
|
||||
|
||||
## 🤖 LLM 활용
|
||||
**언제**: 의 frame time 의 의 CPU-bound 의 (draw call > 1000), GPU-driven culling, atlas 설계.
|
||||
**언제 X**: 의 매 GPU-bound 의 (fragment-heavy) — 의 다른 의 axis 의 (overdraw, shader complexity) 의 attack.
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **One mesh per object**: 의 10,000 entity 의 = 의 10,000 draw — 매 disaster.
|
||||
- **Per-frame buffer recreate**: 의 GC pressure + 의 driver overhead.
|
||||
- **Random material switch**: state thrash — 매 sort 의 의 by material first.
|
||||
- **Premature GPU-driven**: 의 CPU 의 매 not bottleneck 의 시 의 — 매 added complexity.
|
||||
|
||||
## 🧪 검증 / 중복
|
||||
- Verified (WebGPU spec, Three.js BatchedMesh r167+, Unreal/Unity rendering docs, GPU Gems, RenderDoc analysis).
|
||||
- 신뢰도 A.
|
||||
|
||||
## 🕓 Changelog
|
||||
| 날짜 | 변경 |
|
||||
|---|---|
|
||||
| 2026-05-08 | Phase 1 |
|
||||
| 2026-05-10 | Manual cleanup — instancing + indirect + UI batcher 추가 |
|
||||
|
||||
Reference in New Issue
Block a user