diff --git a/docs/superpowers/specs/2026-03-26-instancing-design.md b/docs/superpowers/specs/2026-03-26-instancing-design.md new file mode 100644 index 0000000..56edfa1 --- /dev/null +++ b/docs/superpowers/specs/2026-03-26-instancing-design.md @@ -0,0 +1,98 @@ +# GPU Instancing Design + +## Overview + +동일 메시를 수천 개 렌더링할 때 단일 draw call로 처리하는 인스턴싱 시스템. per-entity UBO 대신 인스턴스 버퍼에 모델 행렬을 패킹. + +## Scope + +- InstanceData 구조체 (모델 행렬 + 추가 per-instance 데이터) +- InstanceBuffer 관리 (동적 크기, 매 프레임 업로드) +- 인스턴스드 렌더 파이프라인 (기존 mesh_shader 확장) +- 기존 파이프라인과 병행 (인스턴싱은 옵션) + +## InstanceData + +```rust +#[repr(C)] +#[derive(Copy, Clone, Pod, Zeroable)] +pub struct InstanceData { + pub model: [[f32; 4]; 4], // 모델 행렬 (64 bytes) + pub color: [f32; 4], // per-instance 색상/틴트 (16 bytes) +} +// Total: 80 bytes per instance +``` + +셰이더에서 vertex attribute로 전달 (location 4~8). + +## InstanceBuffer + +```rust +pub struct InstanceBuffer { + buffer: wgpu::Buffer, + capacity: usize, // 현재 버퍼 용량 (인스턴스 수) + pub count: usize, // 실제 인스턴스 수 +} +``` + +- `new(device, initial_capacity)` — VERTEX | COPY_DST 버퍼 +- `update(device, queue, instances: &[InstanceData])` — 용량 초과 시 재생성, write_buffer +- 인스턴스 데이터는 CPU에서 매 프레임 빌드 (Transform 순회) + +## Instanced Shader (instanced_shader.wgsl) + +기존 mesh_shader.wgsl 기반 + 인스턴스 입력: + +```wgsl +struct InstanceInput { + @location(4) model_0: vec4, + @location(5) model_1: vec4, + @location(6) model_2: vec4, + @location(7) model_3: vec4, + @location(8) color: vec4, +}; + +@vertex +fn vs_main(vertex: VertexInput, instance: InstanceInput) -> VertexOutput { + let model = mat4x4(instance.model_0, instance.model_1, instance.model_2, instance.model_3); + let world_pos = model * vec4(vertex.position, 1.0); + // ... +} +``` + +## Pipeline + +기존 create_mesh_pipeline에 instance attributes 추가: +- slot 0: MeshVertex (48 bytes, per-vertex) +- slot 1: InstanceData (80 bytes, per-instance, step_mode: Instance) + +```rust +pub fn create_instanced_pipeline( + device: &wgpu::Device, + format: wgpu::TextureFormat, + light_layout: &wgpu::BindGroupLayout, + texture_layout: &wgpu::BindGroupLayout, +) -> wgpu::RenderPipeline; +``` + +## Render + +```rust +rpass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..)); +rpass.set_vertex_buffer(1, instance_buffer.buffer.slice(..)); +rpass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32); +rpass.draw_indexed(0..mesh.num_indices, 0, 0..instance_buffer.count as u32); +``` + +## File Structure + +- `crates/voltex_renderer/src/instancing.rs` — InstanceData, InstanceBuffer, pipeline, InstanceData::LAYOUT +- `crates/voltex_renderer/src/instanced_shader.wgsl` — 인스턴스드 셰이더 +- `crates/voltex_renderer/src/lib.rs` — 모듈 추가 + +## Testing + +- InstanceData 크기: size_of == 80 +- InstanceData::LAYOUT: attribute 수, stride 검증 +- InstanceBuffer: capacity 확장 로직 (GPU 없이 로직만) +- sort by mesh for batching (같은 메시끼리 모아서 단일 draw call)