summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/RECIPE.md202
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`