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
|
# Auxiliary Texture Initialization
**Technical decision: Effect initialization order (resize → init)**
---
## Problem
Auxiliary textures created at wrong resolution (1280×720 defaults instead of actual window size).
**Root Cause:** `init()` called before `resize()` in MainSequence. Effects registered textures using default dimensions.
```cpp
// BEFORE (effect.cc:180-181)
entry.seq->init(this); // Uses width_/height_ = 1280×720
entry.seq->resize(width, height); // Too late - textures already created
```
**Affected:**
- CircleMaskEffect (circle_mask texture)
- CNNEffect (captured_frame texture)
- RotatingCubeEffect (consumer, hardcoded resolution in uniforms)
---
## Solution: Reorder init/resize ✅
Call `resize()` before `init()` to set dimensions first.
```cpp
// AFTER (effect.cc:179-180)
entry.seq->resize(width, height); // Set dimensions FIRST
entry.seq->init(this); // Then init with correct dimensions
```
**Rejected Alternative:** Lazy initialization (ensure_texture() guards) - complexity cascade to consumers.
---
## Implementation
### 1. MainSequence Order Swap
**File:** `src/gpu/effect.cc`
Lines 179-180 and 189-190: Call `resize()` before `init()`.
### 2. Auxiliary Texture Effects
Register textures in `init()` using width_/height_ (now valid):
```cpp
void MyEffect::init(MainSequence* demo) {
demo_ = demo;
demo_->register_auxiliary_texture("my_texture", width_, height_);
// Create pipelines/bind groups...
}
```
Handle resize with early returns:
```cpp
void MyEffect::resize(int width, int height) {
if (width == width_ && height == height_)
return;
Effect::resize(width, height);
if (!demo_)
return;
demo_->resize_auxiliary_texture("my_texture", width, height);
// Recreate bind groups...
}
```
### 3. Effects with Renderer3D
Add `initialized_` flag to guard resize():
```cpp
// Header
bool initialized_ = false;
// init()
renderer_.init(ctx_.device, ctx_.queue, ctx_.format);
renderer_.resize(width_, height_);
initialized_ = true;
// resize()
if (!initialized_)
return; // Don't resize uninitialized renderer
renderer_.resize(width_, height_);
```
**Why flag?** `ctx_.device` exists before init() but Renderer3D not initialized yet.
**Applied to:** FlashCubeEffect, Hybrid3DEffect
### 4. Hardcoded Resolutions
**RotatingCubeEffect:** Fixed hardcoded `vec2(1280.0f, 720.0f)` → `u.resolution`
**Audit Results:** No other hardcoded resolutions in effects. Defaults in effect.cc:161-162 are test-only.
---
## Guidelines
### Creating Auxiliary Textures
```cpp
void MyEffect::init(MainSequence* demo) {
demo_ = demo;
demo_->register_auxiliary_texture("my_texture", width_, height_);
// width_/height_ valid (resize() called before init())
}
```
### Consuming Auxiliary Textures
```cpp
void MyEffect::init(MainSequence* demo) {
demo_ = demo;
WGPUTextureView view = demo_->get_auxiliary_view("circle_mask");
// Producer effects initialized before consumers (timeline order)
}
```
### Using Resolutions
**Always use `u.resolution` from CommonPostProcessUniforms, never hardcode dimensions.**
---
## Testing
All 36 tests pass. Verified:
- Auxiliary textures at correct resolution
- Renderer3D effects don't crash on resize-before-init
- No hardcoded resolution artifacts
- Window resize works correctly
---
## Related
- `doc/MASKING_SYSTEM.md` - Auxiliary texture patterns
- `doc/EFFECT_WORKFLOW.md` - Effect creation workflow
- `src/gpu/effect.h` - MainSequence API
---
**Decision:** 2026-02-11 | **Status:** Active | **By:** Claude Sonnet 4.5
|