Install
$ agentstack add skill-nebulavenus-forge-gpu-forge-compute-shaders ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Compute Shaders — GPU Compute with SDL3
This skill teaches how to create and dispatch compute shaders using SDL3 GPU. It covers the compute pipeline, storage textures, dispatch groups, and the compute-then-render pattern where a compute pass writes to a texture and a render pass displays it.
When to use
- Running general-purpose computation on the GPU (not tied to rendering)
- Image processing or post-processing (blur, tone mapping, histograms)
- Procedural texture or noise generation
- Particle simulation or physics computation
- Any scenario where you need random-access write to a texture or buffer
Key API calls (ordered)
SDL_CreateGPUTexture— withCOMPUTE_STORAGE_WRITE | SAMPLERusageSDL_CreateGPUComputePipeline— embeds shader code directly (no separate shader object)SDL_PushGPUComputeUniformData— push per-frame data (time, resolution)SDL_BeginGPUComputePass— binds RW storage textures and buffersSDL_BindGPUComputePipeline— bind the pipelineSDL_DispatchGPUCompute— launch workgroupsSDL_EndGPUComputePassSDL_BeginGPURenderPass— render pass samples the same textureSDL_BindGPUFragmentSamplers— bind compute output as sampled texture
Compute pipeline creation
The compute pipeline embeds the shader code directly — no separate SDL_CreateGPUShader step. The threadcount values must match [numthreads()] in the HLSL.
SDL_GPUComputePipelineCreateInfo info;
SDL_zero(info);
info.entrypoint = "main";
info.code = shader_code;
info.code_size = shader_size;
info.format = shader_format;
info.num_readwrite_storage_textures = 1; /* RWTexture2D outputs */
info.num_uniform_buffers = 1; /* cbuffer with time etc. */
info.threadcount_x = 8; /* must match HLSL */
info.threadcount_y = 8;
info.threadcount_z = 1;
SDL_GPUComputePipeline *pipeline =
SDL_CreateGPUComputePipeline(device, &info);
Register layout (DXIL — compute shaders)
Compute shaders use different register spaces than vertex/fragment shaders:
| Resource | Register | Space | |----------|----------|-------| | Sampled textures, read-only storage textures/buffers | t[n] | space0 | | Read-write storage textures, read-write storage buffers | u[n] | space1 | | Uniform buffers | b[n] | space2 |
For SPIR-V, use descriptor sets 0, 1, 2 in the same order.
Storage texture creation
The texture must have both COMPUTE_STORAGE_WRITE (for the compute shader) and SAMPLER (for the render pass) usage flags:
SDL_GPUTextureCreateInfo tex_info;
SDL_zero(tex_info);
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
tex_info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
tex_info.usage = SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE |
SDL_GPU_TEXTUREUSAGE_SAMPLER;
tex_info.width = width;
tex_info.height = height;
tex_info.layer_count_or_depth = 1;
tex_info.num_levels = 1;
Use R8G8B8A8_UNORM (not _SRGB) when compute writes raw linear values. Let the sRGB swapchain handle gamma conversion.
Compute-then-render pattern
Each frame: compute pass writes to texture, render pass samples it. SDL3 synchronizes automatically between passes on the same command buffer.
/* Push compute uniforms BEFORE the pass */
SDL_PushGPUComputeUniformData(cmd, 0, &uniforms, sizeof(uniforms));
/* ── Compute pass ────────────────────────────── */
SDL_GPUStorageTextureReadWriteBinding storage_binding;
SDL_zero(storage_binding);
storage_binding.texture = texture;
storage_binding.cycle = true; /* frame pipelining */
SDL_GPUComputePass *compute_pass = SDL_BeginGPUComputePass(
cmd,
&storage_binding, 1, /* RW storage textures */
NULL, 0 /* RW storage buffers */
);
SDL_BindGPUComputePipeline(compute_pass, compute_pipeline);
/* Ceiling division for dispatch groups */
Uint32 groups_x = (width + 7) / 8;
Uint32 groups_y = (height + 7) / 8;
SDL_DispatchGPUCompute(compute_pass, groups_x, groups_y, 1);
SDL_EndGPUComputePass(compute_pass);
/* ── Render pass ─────────────────────────────── */
/* Same texture, now bound as a sampled texture */
SDL_GPUTextureSamplerBinding tex_binding;
tex_binding.texture = texture; /* same texture! */
tex_binding.sampler = sampler;
SDL_BindGPUFragmentSamplers(render_pass, 0, &tex_binding, 1);
SDL_DrawGPUPrimitives(render_pass, 3, 1, 0, 0); /* fullscreen tri */
Dispatch group calculation
Dispatch groups must cover every pixel. Use ceiling division:
Uint32 groups_x = (tex_width + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
Uint32 groups_y = (tex_height + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
The HLSL shader must bounds-check SV_DispatchThreadID to handle overshoot:
[numthreads(8, 8, 1)]
void main(uint3 id : SV_DispatchThreadID)
{
if (id.x >= (uint)width || id.y >= (uint)height)
return;
// ... write pixel
}
Fullscreen triangle (no vertex buffer)
Draw 3 vertices from SV_VertexID — no vertex buffer needed. The triangle overshoots the screen and is clipped to fill it exactly:
struct VSOutput {
float4 position : SV_Position;
float2 uv : TEXCOORD0;
};
VSOutput main(uint id : SV_VertexID)
{
VSOutput output;
float2 pos;
pos.x = (float)((id & 1u) << 1) - 1.0;
pos.y = (float)((id & 2u)) - 1.0;
output.position = float4(pos.x * 2.0 + 1.0,
pos.y * 2.0 + 1.0,
0.0, 1.0);
output.uv = float2((output.position.x + 1.0) * 0.5,
1.0 - (output.position.y + 1.0) * 0.5);
return output;
}
Graphics pipeline needs zero vertex attributes:
gfx_info.vertex_input_state.num_vertex_buffers = 0;
gfx_info.vertex_input_state.num_vertex_attributes = 0;
Common mistakes
| Mistake | Fix | |---------|-----| | threadcount in create info doesn't match HLSL [numthreads()] | Must be identical — 8,8,1 in both | | Forgot bounds check in compute shader | Dispatch may overshoot texture size | | Using _SRGB texture format with compute | Use _UNORM; let the sRGB swapchain convert | | Forgot COMPUTE_STORAGE_WRITE usage flag | Texture won't bind as RW in compute pass | | Using vertex shader register spaces for compute | Compute uses space0/1/2, not the graphics layout | | Not pushing uniforms before BeginGPUComputePass | Uniforms must be pushed before the pass |
Related lessons
- [Lesson 11 — Compute Shaders](../../../lessons/gpu/11-compute-shaders/) — full tutorial
- [Lesson 03 — Uniforms & Motion](../../../lessons/gpu/03-uniforms-and-motion/) — push uniforms
- [Lesson 04 — Textures & Samplers](../../../lessons/gpu/04-textures-and-samplers/) — texture binding
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Nebulavenus
- Source: Nebulavenus/forge-gpu
- License: Zlib
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.