Skip to main content

forge-first-triangle

Draw colored geometry with vertex buffers, shaders, and a graphics pipeline using SDL3 GPU API. Use when someone needs to render vertices with per-vertex attributes.

설치로 이동

소스 정보

저장소
Nebulavenus/forge-gpu
최근 소스 활동
2026년 3월 17일 04:52
감지된 SKILL.md 언어
영어
스타
38
포크
7

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
forge-first-triangle
description
Draw colored geometry with vertex buffers, shaders, and a graphics pipeline using SDL3 GPU API. Use when someone needs to render vertices with per-vertex attributes.
# First Triangle — Vertex Buffers, Shaders & Pipeline This skill teaches how to draw colored geometry with SDL3's GPU API. It builds on the `forge-sdl-gpu-setup` skill (device, window, swapchain, render loop). ## When to use - Drawing any geometry (triangles, quads, meshes) - Setting up vertex buffers with per-vertex attributes - Creating a graphics pipeline with shaders - Uploading CPU data to the GPU ## Vertex data pattern Define a vertex struct matching your shader inputs: ```c typedef struct Vertex { float x, y; /* position in NDC (float2) → TEXCOORD0 in HLSL */ float r, g, b; /* color (float3) → TEXCOORD1 in HLSL */ } Vertex; ``` **Rule:** The C struct layout must exactly match the vertex attributes you declare in the pipeline's vertex input state. **Coordinate spaces:** For the first triangle lesson, positions are provided directly in **NDC** (Normalized Device Coordinates), which is clip space with w = 1. The range is [-1, 1] for x and y. Later lessons introduce model-view-projection matrices to transform from model space. See `lessons/math/02-coordinate-spaces` for the complete transformation pipeline. ## Shader conventions SDL GPU uses HLSL semantics for vertex inputs: | Vertex attribute location | HLSL semantic | |--------------------------|---------------| | 0 | TEXCOORD0 | | 1 | TEXCOORD1 | | 2 | TEXCOORD2 | | N | TEXCOORD{N} | Output position uses `SV_Position`. Fragment shader output uses `SV_Target`. **Shader resource counts** must be declared when creating a shader object: `num_samplers`, `num_storage_textures`, `num_storage_buffers`, `num_uniform_buffers`. ## Shader compilation Write HLSL source, compile to SPIRV and DXIL with `dxc`: ```bash # SPIRV (Vulkan) — requires Vulkan SDK dxc -spirv -T vs_6_0 -E main shader.vert.hlsl -Fo shader.vert.spv dxc -spirv -T ps_6_0 -E main shader.frag.hlsl -Fo shader.frag.spv # DXIL (D3D12) dxc -T vs_6_0 -E main shader.vert.hlsl -Fo shader.vert.dxil dxc -T ps_6_0 -E main shader.frag.hlsl -Fo shader.frag.dxil ``` Embed compiled bytecodes as C byte arrays in header files, then pick the right format at runtime based on `SDL_GetGPUShaderFormats(device)`. ## Creating shaders — correct order ```c SDL_GPUShaderCreateInfo info = { 0 }; info.code = bytecode; /* SPIRV or DXIL bytes */ info.code_size = bytecode_size; info.entrypoint = "main"; info.format = SDL_GPU_SHADERFORMAT_SPIRV; /* or DXIL */ info.stage = SDL_GPU_SHADERSTAGE_VERTEX; /* or FRAGMENT */ info.num_samplers = 0; info.num_storage_textures = 0; info.num_storage_buffers = 0; info.num_uniform_buffers = 0; SDL_GPUShader *shader = SDL_CreateGPUShader(device, &info); ``` ## Graphics pipeline — correct order ```c /* 1. Vertex buffer description (stride, input rate) */ SDL_GPUVertexBufferDescription vbuf_desc = { 0 }; vbuf_desc.slot = 0; vbuf_desc.pitch = sizeof(Vertex); vbuf_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX; /* 2. Vertex attributes (location, format, offset) */ SDL_GPUVertexAttribute attrs[2] = { 0 }; attrs[0].location = 0; attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2; attrs[0].offset = offsetof(Vertex, x); attrs[1].location = 1; attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3; attrs[1].offset = offsetof(Vertex, r); /* 3. Pipeline create info */ SDL_GPUGraphicsPipelineCreateInfo pipe_info = { 0 }; pipe_info.vertex_shader = vert_shader; pipe_info.fragment_shader = frag_shader; pipe_info.vertex_input_state.vertex_buffer_descriptions = &vbuf_desc; pipe_info.vertex_input_state.num_vertex_buffers = 1; pipe_info.vertex_input_state.vertex_attributes = attrs; pipe_info.vertex_input_state.num_vertex_attributes = 2; pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; pipe_info.rasterizer_state.fill_mode = SDL_GPU_FILLMODE_FILL; pipe_info.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE; pipe_info.rasterizer_state.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE; /* 4. Color target must match swapchain format */ SDL_GPUColorTargetDescription ct = { 0 }; ct.format = SDL_GetGPUSwapchainTextureFormat(device, window); pipe_info.target_info.color_target_descriptions = &ct; pipe_info.target_info.num_color_targets = 1; SDL_GPUGraphicsPipeline *pipeline = SDL_CreateGPUGraphicsPipeline(device, &pipe_info); /* 5. Release shader objects — pipeline keeps its own copy */ SDL_ReleaseGPUShader(device, frag_shader); SDL_ReleaseGPUShader(device, vert_shader); ``` ## GPU buffer upload pattern ```c /* 1. Create GPU buffer */ SDL_GPUBufferCreateInfo buf = { 0 }; buf.usage = SDL_GPU_BUFFERUSAGE_VERTEX; buf.size = data_size; SDL_GPUBuffer *gpu_buf = SDL_CreateGPUBuffer(device, &buf); /* 2. Create transfer buffer (staging) */ SDL_GPUTransferBufferCreateInfo tbuf = { 0 }; tbuf.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; tbuf.size = data_size; SDL_GPUTransferBuffer *transfer = SDL_CreateGPUTransferBuffer(device, &tbuf); /* 3. Map, copy, unmap */ void *mapped = SDL_MapGPUTransferBuffer(device, transfer, false); SDL_memcpy(mapped, source_data, data_size); SDL_UnmapGPUTransferBuffer(device, transfer); /* 4. Copy pass: transfer → GPU buffer */ SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(device); SDL_GPUCopyPass *copy = SDL_BeginGPUCopyPass(cmd); SDL_GPUTransferBufferLocation src = { .transfer_buffer = transfer }; SDL_GPUBufferRegion dst = { .buffer = gpu_buf, .size = data_size }; SDL_UploadToGPUBuffer(copy, &src, &dst, false); SDL_EndGPUCopyPass(copy); SDL_SubmitGPUCommandBuffer(cmd); /* 5. Release staging buffer */ SDL_ReleaseGPUTransferBuffer(device, transfer); ``` ## Drawing — inside a render pass ```c SDL_BindGPUGraphicsPipeline(pass, pipeline); SDL_GPUBufferBinding binding = { .buffer = vertex_buffer }; SDL_BindGPUVertexBuffers(pass, 0, &binding, 1); SDL_DrawGPUPrimitives(pass, vertex_count, 1, 0, 0); ``` ## Common mistakes | Mistake | Fix | |---------|-----| | Vertex struct doesn't match shader inputs | Ensure `offsetof` values match attribute offsets exactly | | Wrong `num_uniform_buffers` in shader create info | Must match the number your shader actually uses (0 if none) | | Forgetting to release shaders after pipeline creation | Call `SDL_ReleaseGPUShader` — pipeline keeps its own copy | | Using `malloc` instead of `SDL_calloc` | SDL functions expect SDL allocators | | Color target format doesn't match swapchain | Use `SDL_GetGPUSwapchainTextureFormat(device, window)` | | Forgetting `SDL_zero()` on create info structs | Uninitialized fields cause undefined behavior | | Not releasing the transfer buffer after upload | Leaks CPU-visible memory every init | ## Cleanup order (reverse of creation) ```c SDL_ReleaseGPUBuffer(device, vertex_buffer); SDL_ReleaseGPUGraphicsPipeline(device, pipeline); SDL_ReleaseWindowFromGPUDevice(device, window); SDL_DestroyWindow(window); SDL_DestroyGPUDevice(device); ```
GitHub에서 보기