1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
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`
|