Skip to main content

forge-uniforms-and-motion

Pass per-frame data to shaders with push uniforms. Use when animating geometry, passing time/matrices/colors to shaders, or setting up uniform buffers in SDL3 GPU.

Ir a la instalación

Datos de origen

Repositorio
Nebulavenus/forge-gpu
Última actividad en el origen
1 de marzo de 2026 a las 21:31
Idioma detectado de SKILL.md
inglés
Estrellas
38
Forks
7

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
forge-uniforms-and-motion
description
Pass per-frame data to shaders with push uniforms. Use when animating geometry, passing time/matrices/colors to shaders, or setting up uniform buffers in SDL3 GPU.
# Uniforms & Motion — Push Uniforms for Per-Frame Data This skill teaches how to pass data from the CPU to GPU shaders every frame using SDL3's push uniform API. It builds on the `first-triangle` skill (vertex buffers, shaders, pipeline). ## When to use - Passing per-frame data to shaders (time, matrices, colors, parameters) - Animating geometry on the GPU - Setting up uniform buffers in a graphics pipeline - Any draw call that needs data beyond vertex attributes ## Push uniforms vs. GPU uniform buffers | Method | When to use | |--------|-------------| | **Push uniforms** (`SDL_PushGPUVertexUniformData`) | Small data that changes frequently — time, MVP matrices, colors | | **GPU uniform buffer** (create + upload) | Large or rarely-changing data | Push uniforms are the simple path: push a pointer to a C struct, SDL copies it internally. No buffer creation, no transfer buffers, no copy passes. ## Uniform data struct Define a C struct matching your shader's cbuffer layout: ```c typedef struct Uniforms { float time; /* elapsed time in seconds */ float aspect; /* window width / height — for correcting NDC */ } Uniforms; ``` **std140 layout rules:** vec3 and vec4 fields must be 16-byte aligned. A single float or a float4 is naturally aligned. If you mix types, add padding. ## HLSL shader convention SDL GPU maps uniform buffer slots to specific HLSL registers: | Stage | Slot 0 register | Slot 1 register | |----------|-----------------------|-----------------------| | Vertex | `register(b0, space1)` | `register(b1, space1)` | | Fragment | `register(b0, space3)` | `register(b1, space3)` | ```hlsl /* Vertex shader — uniform slot 0 */ cbuffer Uniforms : register(b0, space1) { float time; float aspect; }; VSOutput main(VSInput input) { /* Correct for non-square windows BEFORE rotation so the triangle * keeps its shape at every angle. If done after, the squish * would distort the already-rotated coordinates. */ float2 corrected = float2(input.position.x / aspect, input.position.y); float c = cos(time); float s = sin(time); float2 rotated; rotated.x = corrected.x * c - corrected.y * s; rotated.y = corrected.x * s + corrected.y * c; VSOutput output; output.position = float4(rotated, 0.0, 1.0); output.color = float4(input.color, 1.0); return output; } ``` ## Shader creation — declaring uniform count When creating a shader, `num_uniform_buffers` must match the number of cbuffers your shader code declares: ```c SDL_GPUShaderCreateInfo info = { 0 }; info.stage = SDL_GPU_SHADERSTAGE_VERTEX; info.entrypoint = "main"; info.num_uniform_buffers = 1; /* ← must match shader's cbuffer count */ info.num_samplers = 0; info.num_storage_textures = 0; info.num_storage_buffers = 0; /* ... code, code_size, format ... */ SDL_GPUShader *shader = SDL_CreateGPUShader(device, &info); ``` ## Pushing uniform data — each frame ```c /* 1. Compute your per-frame data */ float elapsed = (float)(SDL_GetTicks() - start_ticks) / 1000.0f; int w = 0, h = 0; SDL_GetWindowSizeInPixels(window, &w, &h); Uniforms uniforms; uniforms.time = elapsed; uniforms.aspect = (h > 0) ? (float)w / (float)h : 1.0f; /* 2. Acquire command buffer */ SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(device); /* 3. Push BEFORE the render pass */ SDL_PushGPUVertexUniformData(cmd, 0, &uniforms, sizeof(uniforms)); /* 4. Begin render pass and draw as usual */ SDL_GPURenderPass *pass = SDL_BeginGPURenderPass(cmd, &color_target, 1, NULL); SDL_BindGPUGraphicsPipeline(pass, pipeline); SDL_BindGPUVertexBuffers(pass, 0, &binding, 1); SDL_DrawGPUPrimitives(pass, vertex_count, 1, 0, 0); SDL_EndGPURenderPass(pass); SDL_SubmitGPUCommandBuffer(cmd); ``` **Key details:** - Push happens on the **command buffer**, not the render pass - Push **before** `SDL_BeginGPURenderPass` — SDL latches the data at pass start - Data is copied internally — your struct can live on the stack - Slot 0 in the push call matches `b0` in the HLSL register - Each stage (vertex/fragment) has 4 independent slots (0–3) - Data persists in a slot until you push new data to it ## Fragment shader uniforms Same pattern, different function and register space: ```c SDL_PushGPUFragmentUniformData(cmd, 0, &frag_uniforms, sizeof(frag_uniforms)); ``` ```hlsl cbuffer FragUniforms : register(b0, space3) /* space3 for fragment */ { float brightness; }; ``` ## Tracking time ```c /* In app_state */ Uint64 start_ticks; /* In SDL_AppInit */ state->start_ticks = SDL_GetTicks(); /* In SDL_AppIterate */ float elapsed = (float)(SDL_GetTicks() - state->start_ticks) / 1000.0f; ``` `SDL_GetTicks()` returns milliseconds since `SDL_Init`. Dividing by 1000 gives seconds as a float — ideal for shader math like `sin(time)` and `cos(time)`. ## Common mistakes | Mistake | Fix | |---------|-----| | `num_uniform_buffers = 0` but shader has a cbuffer | Must match — set to 1 (or however many cbuffers your shader declares) | | Pushing uniform data *after* `SDL_BeginGPURenderPass` | Push *before* the render pass — SDL latches uniforms at pass start | | Wrong register space in HLSL | Vertex = `space1`, Fragment = `space3` | | Forgetting std140 alignment for vec3/vec4 | Pad structs so vec3/vec4 start at 16-byte boundaries | | Using `SDL_PushGPUVertexUniformData` for fragment data | Use `SDL_PushGPUFragmentUniformData` for fragment stage | | Not declaring `num_uniform_buffers` on the fragment shader | Each stage declares its own count independently | | Rotation looks skewed on non-square windows | Pass aspect ratio as uniform, divide x by it **before** rotation | | Aspect correction applied after rotation | Triangle skews at certain angles — correct *before* rotating so you rotate in uniform space | | Triangle wobbles instead of spinning in place | Center vertices so centroid is at origin (average of all positions = 0,0) | ## Cleanup No extra cleanup needed for push uniforms — there are no GPU buffer objects to release. The only cleanup is the same as Lesson 02 (pipeline, vertex buffer, window, device).
Ver en GitHub