# Recipe: Common Patterns Quick reference for implementing common patterns in the demo codebase. ## Runtime Shader Composition Use `ShaderComposer` to dynamically assemble shaders from snippets. **Pattern:** ```cpp #include "gpu/effects/shader_composer.h" #include "generated/assets.h" // 1. Load base shader template from asset size_t shader_size; const char* shader_code = (const char*)GetAsset(AssetId::MY_SHADER_TEMPLATE, &shader_size); // 2. Define substitutions for dynamic parts ShaderComposer::CompositionMap composition_map; composition_map["placeholder_name"] = "actual_snippet_name"; composition_map["fragment_main"] = "plasma_shader"; // Example // 3. Compose final shader std::string composed_shader = ShaderComposer::Get().Compose( {}, // Optional: explicit dependencies std::string(shader_code, shader_size), composition_map); // 4. Create shader module WGPUShaderSourceWGSL wgsl_src = {}; wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL; wgsl_src.code = str_view(composed_shader.c_str()); WGPUShaderModuleDescriptor shader_desc = {}; shader_desc.nextInChain = &wgsl_src.chain; WGPUShaderModule shader_module = wgpuDeviceCreateShaderModule(ctx_.device, &shader_desc); ``` **Base shader template (WGSL asset):** ```wgsl // Common bindings @group(0) @binding(0) var uniforms: CommonUniforms; @group(0) @binding(1) var tex_sampler: sampler; // Placeholder for dynamic fragment code #include "fragment_main" @fragment fn fs_main(@location(0) uv: vec2) -> @location(0) vec4 { return compute_color(uv); // Implemented by included snippet } ``` **Register snippets at startup:** ```cpp ShaderComposer::Get().RegisterSnippet("plasma_shader", R"( fn compute_color(uv: vec2) -> vec4 { let t = uniforms.time; return vec4(sin(uv.x * 10.0 + t), cos(uv.y * 10.0 + t), 0.5, 1.0); } )"); ShaderComposer::Get().RegisterSnippet("tunnel_shader", R"( fn compute_color(uv: vec2) -> vec4 { let r = length(uv - vec2(0.5)); return vec4(vec3(1.0 / r), 1.0); } )"); ``` **Example usage:** `src/gpu/effects/rotating_cube_effect.cc:72-75` ## QuadEffect with Auxiliary Textures Full-screen quad effect with access to previous framebuffer + side textures. **Binding layout:** ``` @group(0) @binding(0) - Previous framebuffer texture @group(0) @binding(1) - Sampler @group(0) @binding(2) - CommonPostProcessUniforms @group(0) @binding(3) - Effect-specific params @group(0) @binding(4+) - Auxiliary textures (optional) ``` **Access auxiliary texture:** ```cpp // In effect init() WGPUTextureView aux_view = demo_->get_auxiliary_view("mask_name"); // Bind to binding 4 const WGPUBindGroupEntry entries[] = { {.binding = 0, .textureView = prev_frame_view}, {.binding = 1, .sampler = sampler}, {.binding = 2, .buffer = common_uniforms}, {.binding = 3, .buffer = effect_params}, {.binding = 4, .textureView = aux_view}, // Side texture }; ``` **WGSL shader:** ```wgsl @group(0) @binding(0) var prev_frame: texture_2d; @group(0) @binding(1) var tex_sampler: sampler; @group(0) @binding(2) var common: CommonPostProcessUniforms; @group(0) @binding(3) var params: EffectParams; @group(0) @binding(4) var aux_texture: texture_2d; @fragment fn fs_main(@location(0) uv: vec2) -> @location(0) vec4 { let prev = textureSample(prev_frame, tex_sampler, uv); let mask = textureSample(aux_texture, tex_sampler, uv); return mix(prev, compute_effect(uv), mask.r); } ``` ## Dynamic Effect Parameters Use `UniformHelper` for .seq-controllable parameters. **C++ param struct:** ```cpp struct MyEffectParams { float strength; float speed; float _pad0; float _pad1; }; static_assert(sizeof(MyEffectParams) == 16); class MyEffect : public Effect { private: UniformHelper params_; }; ``` **Effect init:** ```cpp void MyEffect::init(MainSequence* demo) { params_.init(ctx_.device); params_.get().strength = 1.0f; params_.get().speed = 2.0f; } ``` **Update per frame:** ```cpp void MyEffect::render(WGPUTextureView prev, WGPUTextureView target, float beat, const EffectParams* ep) { params_.apply_optional(ep); // Updates from .seq params_.upload(ctx_.queue); // ... render pass } ``` **.seq syntax:** ``` EFFECT MyEffect 0.0 10.0 strength=0.5 speed=3.0 EFFECT MyEffect 10.0 20.0 strength=2.0 # speed keeps previous value ``` **Example:** `src/gpu/effects/flash_effect.cc`, `src/gpu/effects/chroma_aberration_effect.cc` ## Uniform Buffer Alignment **WGSL padding rules:** - `vec3` requires 16-byte alignment (use padding or switch to `vec4`) - Use three `f32` fields instead of single `vec3` when possible **Correct patterns:** ```cpp // Option 1: Explicit padding struct MyUniforms { vec3 color; f32 _pad0; vec2 offset; f32 _pad1; f32 _pad2; }; // Option 2: Avoid vec3 struct MyUniforms { f32 color_r; f32 color_g; f32 color_b; f32 intensity; vec2 offset; f32 _pad0; f32 _pad1; }; ``` **Verification:** ```cpp static_assert(sizeof(MyUniforms) == EXPECTED_SIZE); ``` **Validation:** Run `tools/validate_uniforms.py` before commit. **Reference:** `doc/UNIFORM_BUFFER_GUIDELINES.md`