Skip to main content

forge-instanced-rendering

Draw many copies of a mesh efficiently with per-instance vertex buffers. Use when rendering forests, particle systems, city blocks, or any scene with repeated geometry in SDL3 GPU.

Zur Installation springen

Quellinformationen

Repository
Nebulavenus/forge-gpu
Letzte Quellaktivität
1. März 2026 um 21:31
Erkannte Sprache von SKILL.md
Englisch
Sterne
38
Forks
7

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
forge-instanced-rendering
description
Draw many copies of a mesh efficiently with per-instance vertex buffers. Use when rendering forests, particle systems, city blocks, or any scene with repeated geometry in SDL3 GPU.
Set up instanced rendering with per-instance transforms passed through vertex attributes. One draw call renders all instances of a mesh. ## When to use - Drawing **many copies** of the same mesh at different positions/rotations - CPU draw-call overhead is a concern (10+ instances of the same mesh) - Particles, vegetation, crowds, buildings, or any repeated geometry ## Core pattern ### 1. Instance data structure Each instance gets a model matrix (64 bytes = 4 × float4 columns): ```c typedef struct InstanceData { mat4 model; /* 4 × vec4 columns, 64 bytes total */ } InstanceData; ``` ### 2. Pipeline with two vertex buffer slots Declare per-vertex AND per-instance buffer slots: ```c SDL_GPUVertexBufferDescription vb_descs[2]; /* Slot 0: per-vertex mesh data */ vb_descs[0].slot = 0; vb_descs[0].pitch = sizeof(YourVertex); vb_descs[0].input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX; /* Slot 1: per-instance transform data */ vb_descs[1].slot = 1; vb_descs[1].pitch = sizeof(InstanceData); /* 64 bytes */ vb_descs[1].input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE; ``` ### 3. Vertex attributes (7 total) ```c SDL_GPUVertexAttribute attrs[7]; /* Per-vertex (slot 0) */ attrs[0] = { .location=0, .buffer_slot=0, .format=FLOAT3, .offset=offsetof(V, position) }; attrs[1] = { .location=1, .buffer_slot=0, .format=FLOAT3, .offset=offsetof(V, normal) }; attrs[2] = { .location=2, .buffer_slot=0, .format=FLOAT2, .offset=offsetof(V, uv) }; /* Per-instance (slot 1) — mat4 split into 4 columns */ attrs[3] = { .location=3, .buffer_slot=1, .format=FLOAT4, .offset=0 }; /* col 0 */ attrs[4] = { .location=4, .buffer_slot=1, .format=FLOAT4, .offset=16 }; /* col 1 */ attrs[5] = { .location=5, .buffer_slot=1, .format=FLOAT4, .offset=32 }; /* col 2 */ attrs[6] = { .location=6, .buffer_slot=1, .format=FLOAT4, .offset=48 }; /* col 3 */ ``` ### 4. Vertex shader — reconstruct matrix from columns ```hlsl cbuffer VertUniforms : register(b0, space1) { column_major float4x4 vp; }; struct VSInput { float3 position : TEXCOORD0; /* per-vertex */ float3 normal : TEXCOORD1; float2 uv : TEXCOORD2; float4 model_c0 : TEXCOORD3; /* per-instance */ float4 model_c1 : TEXCOORD4; float4 model_c2 : TEXCOORD5; float4 model_c3 : TEXCOORD6; }; struct VSOutput { float4 clip_pos : SV_Position; /* clip-space position */ float2 uv : TEXCOORD0; /* texture coordinates */ float3 world_norm : TEXCOORD1; /* world-space normal */ float3 world_pos : TEXCOORD2; /* world-space position */ }; VSOutput main(VSInput input) { VSOutput output; /* Reconstruct column-major model matrix. * float4x4() takes ROWS, so transpose to get columns. */ float4x4 model = transpose(float4x4( input.model_c0, input.model_c1, input.model_c2, input.model_c3)); float4 wp = mul(model, float4(input.position, 1.0)); output.clip_pos = mul(vp, wp); output.world_pos = wp.xyz; /* Normal transform: adjugate transpose (works for all matrices) */ float3x3 m = (float3x3)model; float3x3 adj_t; adj_t[0] = cross(m[1], m[2]); adj_t[1] = cross(m[2], m[0]); adj_t[2] = cross(m[0], m[1]); output.world_norm = mul(adj_t, input.normal); output.uv = input.uv; return output; } ``` ### 5. Generate and upload instance transforms ```c InstanceData instances[MAX_INSTANCES]; for (int i = 0; i < count; i++) { mat4 t = mat4_translate(positions[i]); mat4 r = mat4_rotate_y(angles[i]); mat4 s = mat4_scale(scales[i]); instances[i].model = mat4_multiply(t, mat4_multiply(r, s)); } SDL_GPUBuffer *instance_buffer = upload_gpu_buffer( device, SDL_GPU_BUFFERUSAGE_VERTEX, instances, count * sizeof(InstanceData)); ``` ### 6. Render — bind both buffers, draw instanced ```c SDL_GPUBufferBinding vb[2]; vb[0].buffer = mesh->vertex_buffer; /* per-vertex */ vb[1].buffer = model->instance_buffer; /* per-instance */ SDL_BindGPUVertexBuffers(pass, 0, vb, 2); /* Bind index buffer */ SDL_GPUBufferBinding ib = { .buffer = mesh->index_buffer }; SDL_BindGPUIndexBuffer(pass, &ib, index_type); /* THE instanced draw — instance_count is the key */ SDL_DrawGPUIndexedPrimitives(pass, index_count, instance_count, 0, 0, 0); ``` ## Common mistakes 1. **Setting `instance_step_rate` to non-zero** — SDL3 GPU requires `instance_step_rate = 0` for all vertex buffer descriptions. The `VERTEXINPUTRATE_INSTANCE` input rate alone controls per-instance advancement. 2. **Wrong matrix reconstruction** — HLSL `float4x4(a,b,c,d)` treats arguments as ROWS. If your data is column-major (like forge_math.h), you must `transpose()` the result. 3. **Using the wrong buffer usage** — instance buffers use `SDL_GPU_BUFFERUSAGE_VERTEX`, not a special "instance" flag. The input rate on the pipeline description is what distinguishes per-vertex from per-instance. 4. **Not checking SDL return values** — every `SDL_CreateGPU*` and `SDL_SubmitGPUCommandBuffer` returns a value. Log the function name + `SDL_GetError()` on failure. ## Uniform layout The instanced pipeline only pushes the VP matrix as a vertex uniform (64 bytes). The model matrix comes from the instance buffer. Fragment uniforms (material + lighting) are pushed per-material, same as non-instanced rendering. ## Performance notes - Instanced rendering reduces **CPU overhead** (fewer draw calls, fewer uniform pushes), not GPU work. The GPU still processes the same number of triangles. - One instanced draw call can replace hundreds of individual draw calls. - For dynamic instances (moving objects), re-upload the instance buffer each frame using the transfer buffer pattern. - Multiple different meshes can share the same instanced pipeline — just bind different vertex/instance buffers per mesh. ## Reference - **Lesson**: [GPU Lesson 13 — Instanced Rendering](../../../lessons/gpu/13-instanced-rendering/) - **Math**: mat4_translate, mat4_rotate_y, mat4_scale, mat4_multiply from forge_math.h
Auf GitHub ansehen