diff options
Diffstat (limited to 'doc')
| -rw-r--r-- | doc/RECIPE.md | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/doc/RECIPE.md b/doc/RECIPE.md new file mode 100644 index 0000000..6404391 --- /dev/null +++ b/doc/RECIPE.md @@ -0,0 +1,202 @@ +# 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<uniform> 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<f32>) -> @location(0) vec4<f32> { + return compute_color(uv); // Implemented by included snippet +} +``` + +**Register snippets at startup:** +```cpp +ShaderComposer::Get().RegisterSnippet("plasma_shader", R"( +fn compute_color(uv: vec2<f32>) -> vec4<f32> { + 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<f32>) -> vec4<f32> { + 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<f32>; +@group(0) @binding(1) var tex_sampler: sampler; +@group(0) @binding(2) var<uniform> common: CommonPostProcessUniforms; +@group(0) @binding(3) var<uniform> params: EffectParams; +@group(0) @binding(4) var aux_texture: texture_2d<f32>; + +@fragment +fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> { + 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<MyEffectParams> 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<f32>` 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<f32> color; + f32 _pad0; + vec2<f32> offset; + f32 _pad1; + f32 _pad2; +}; + +// Option 2: Avoid vec3 +struct MyUniforms { + f32 color_r; + f32 color_g; + f32 color_b; + f32 intensity; + vec2<f32> 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` |
