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.

الانتقال إلى التثبيت

معلومات المصدر

المستودع
Nebulavenus/forge-gpu
آخر نشاط في المصدر
١ مارس ٢٠٢٦ في ٢١:٣١
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٣٨
التفرعات
٧

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
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).
عرض على GitHub