diff options
Diffstat (limited to 'doc')
| -rw-r--r-- | doc/ARCHITECTURE.md | 60 | ||||
| -rw-r--r-- | doc/ASSET_SYSTEM.md | 15 | ||||
| -rw-r--r-- | doc/BACKLOG.md | 197 | ||||
| -rw-r--r-- | doc/BUILD.md | 27 | ||||
| -rw-r--r-- | doc/CODING_STYLE.md | 109 | ||||
| -rw-r--r-- | doc/COMPLETED.md | 41 | ||||
| -rw-r--r-- | doc/CONTRIBUTING.md | 63 | ||||
| -rw-r--r-- | doc/GPU_PROCEDURAL_PHASE4.md | 70 | ||||
| -rw-r--r-- | doc/HOT_RELOAD.md | 162 | ||||
| -rw-r--r-- | doc/HOWTO.md | 210 | ||||
| -rw-r--r-- | doc/RECIPE.md | 202 | ||||
| -rw-r--r-- | doc/SIZE_MEASUREMENT.md | 206 | ||||
| -rw-r--r-- | doc/TOOLS_REFERENCE.md | 89 | ||||
| -rw-r--r-- | doc/TRACKER.md | 39 | ||||
| -rw-r--r-- | doc/UNIFORM_BUFFER_GUIDELINES.md | 106 | ||||
| -rw-r--r-- | doc/WORKSPACE_SYSTEM.md | 546 |
16 files changed, 1934 insertions, 208 deletions
diff --git a/doc/ARCHITECTURE.md b/doc/ARCHITECTURE.md new file mode 100644 index 0000000..1a32300 --- /dev/null +++ b/doc/ARCHITECTURE.md @@ -0,0 +1,60 @@ +# Architectural Overview + +Detailed system architecture for the 64k demo project. + +--- + +## Hybrid 3D Renderer + +**Core Idea**: Uses standard rasterization to draw proxy hulls (boxes), then raymarches inside the fragment shader to find the exact SDF surface. + +**Transforms**: Uses `inv_model` matrices to perform all raymarching in local object space, handling rotation and non-uniform scaling correctly. + +**Shadows**: Instance-based shadow casting with self-shadowing prevention (`skip_idx`). + +--- + +## Sequence & Effect System + +**Effect**: Abstract base for visual elements. Supports `compute` and `render` phases. + +**Sequence**: Timeline of effects with start/end times. + +**MainSequence**: Top-level coordinator and framebuffer manager. + +**seq_compiler**: Transpiles `assets/demo.seq` into C++ `timeline.cc`. + +--- + +## Asset & Build System + +**asset_packer**: Embeds binary assets (like `.spec` files) into C++ arrays. + +**Runtime Manager**: O(1) retrieval with lazy procedural generation support. + +**Automation**: `gen_assets.sh`, `build_win.sh`, and `check_all.sh` for multi-platform validation. + +--- + +## Audio Engine + +### Synthesis +Real-time additive synthesis from spectrograms via FFT-based IDCT (O(N log N)). Stereo output (32kHz, 16-bit, interleaved L/R). Uses orthonormal DCT-II/DCT-III transforms with Numerical Recipes reordering method. + +### Variable Tempo +Music time abstraction with configurable tempo_scale. Tempo changes don't affect pitch. + +### Event-Based Tracker +Individual TrackerEvents trigger as separate voices with dynamic beat calculation. Notes within patterns respect tempo scaling. + +### Backend Abstraction +`AudioBackend` interface with `MiniaudioBackend` (production), `MockAudioBackend` (testing), and `WavDumpBackend` (offline rendering). + +### Dynamic Updates +Double-buffered spectrograms for live thread-safe updates. + +### Procedural Library +Melodies and spectral filters (noise, comb) generated at runtime. + +### Pattern System +TrackerPatterns contain lists of TrackerEvents (beat, sample_id, volume, pan). Events trigger individually based on elapsed music time. diff --git a/doc/ASSET_SYSTEM.md b/doc/ASSET_SYSTEM.md index 4e34378..8f31a6a 100644 --- a/doc/ASSET_SYSTEM.md +++ b/doc/ASSET_SYSTEM.md @@ -89,6 +89,21 @@ Runtime: First `GetAsset()` call invokes generator, caches result. - **Hardcoded procedural dimensions**: Assumes 256×256 RGBA8 - **No integrity checks**: No CRC/hash validation +## Developer Workflow + +**Add new asset:** +1. Place file in `assets/final/` +2. Edit `assets/final/demo_assets.txt`: + ``` + MY_ASSET, myfile.ext, NONE, "Description" + ``` +3. Regenerate: `./scripts/gen_assets.sh` or rebuild (auto-triggered) +4. Use in code: `GetAsset(AssetId::MY_ASSET, &size)` + +**Modify existing asset:** +- Edit source file in `assets/final/` +- CMake auto-detects change and rebuilds + ## Planned Improvements See **TODO.md** for detailed task breakdown: diff --git a/doc/BACKLOG.md b/doc/BACKLOG.md new file mode 100644 index 0000000..403ecc9 --- /dev/null +++ b/doc/BACKLOG.md @@ -0,0 +1,197 @@ +# Future Goals & Ideas (Untriaged) + +This file contains low-priority tasks and ideas that have not yet been triaged for active development. + +--- + +## Audio Tools + +### Task #64: specplay Enhancements +Extend audio analysis tool with new features: +- **Priority 1**: Spectral visualization (ASCII art), waveform display, frequency analysis, dynamic range +- **Priority 2**: Diff mode (compare .wav vs .spec), batch mode (CSV report, find clipping) +- **Priority 3**: WAV export (.spec → .wav), normalization +- **Priority 4**: Spectral envelope, harmonic analysis, onset detection +- **Priority 5**: Interactive mode (seek, loop, volume control) + +See `tools/specplay_README.md` for detailed feature list. + +### Task #65: Data-Driven Tempo Control +Move tempo variation from code to data files. + +**Current**: `g_tempo_scale` is hardcoded in `main.cc` with manual animation curves + +**Goal**: Define tempo curves in `.seq` or `.track` files + +**Approach A**: Add TEMPO directive to `.seq` format +- Example: `TEMPO 0.0 1.0`, `TEMPO 10.0 2.0`, `TEMPO 20.0 1.0` +- seq_compiler generates tempo curve array in timeline.cc + +**Approach B**: Add tempo column to music.track +- Each pattern trigger can specify tempo_scale override +- tracker_compiler generates tempo events in music_data.cc + +**Benefits**: Non-programmers can edit tempo, easier iteration, version control friendly + +**Priority**: Low (current approach works) + +### Task #67: DCT/FFT Performance Benchmarking +Add timing measurements to audio tests. + +**Goal**: Compare performance of different DCT/IDCT implementations + +**Location**: Add timing code to `test_dct.cc` or `test_fft.cc` + +**Measurements**: +- Reference IDCT/FDCT (naive O(N²)) +- FFT-based DCT/IDCT (current O(N log N)) +- Future x86_64 SIMD-optimized versions + +**Output Format**: +- Average time per transform (microseconds) +- Throughput (transforms per second) +- Speedup factor vs reference + +**Test Sizes**: DCT_SIZE=512 (production), plus 128, 256, 1024 for scaling + +**Implementation**: +- Use `std::chrono::high_resolution_clock` +- Run 1000+ iterations to reduce noise +- Report min/avg/max times +- Guard with `#if !defined(STRIP_ALL)` + +**Priority**: Very Low (nice-to-have) + +### Task #69: Convert Audio Pipeline to Clipped Int16 +Use clipped int16 for all audio processing. + +**Current**: Float32 throughout (generation, mixing, synthesis, output) + +**Goal**: Convert to int16 for faster processing and reduced memory + +**Rationale**: +- Simpler arithmetic (no float operations) +- Smaller memory footprint (2 bytes vs 4 bytes) +- Hardware-native format (most audio devices use int16) +- Eliminates float→int16 conversion at output +- Natural clipping behavior + +**Scope**: +- Output path: Definitely convert (backends, WAV dump) +- Synthesis: Consider keeping float32 for quality +- Mixing: Could use int16 with overflow handling +- Asset storage: Already int16 in .spec files + +**Implementation Phases**: +1. **Phase 1: Output Only** (~50 lines) - Convert `synth_render()` output to int16 +2. **Phase 2: Mixing Stage** (~200 lines) - Convert voice mixing to int16 arithmetic +3. **Phase 3: Full Pipeline** (~500+ lines) - Convert spectrograms to int16 storage + +**Trade-offs**: +- Quality loss: 16-bit vs 32-bit float precision +- Dynamic range: Limited to [-32768, 32767] +- Clipping: Must handle overflow carefully +- Code complexity: Saturation arithmetic + +**Testing Requirements**: +- Verify no audible quality degradation +- Ensure clipping behavior matches float version +- Check mixing overflow doesn't cause artifacts +- Validate WAV dumps bit-identical + +**Size Impact**: +- Phase 1: Negligible (~50 bytes) +- Phase 2: ~100-200 bytes +- Phase 3: 50% memory, ~1-2KB code savings + +**Priority**: Low (final optimization only if 64k budget requires it) + +**Notes**: Quality must be validated - may not be worth trade-off + +--- + +## Developer Tools + +### Task #66: External Asset Loading for Debugging +mmap() asset files instead of embedded data. + +**Current**: All assets embedded in `assets_data.cc` (regenerate on every change) + +**Goal**: Load assets from external files in debug builds for faster iteration + +**Scope**: macOS only, non-STRIP_ALL builds only + +**Implementation**: +- Add `DEMO_ENABLE_EXTERNAL_ASSETS` CMake option +- Modify `GetAsset()` to check for external file first (e.g., `assets/final/<name>`) +- Use `mmap()` to map file into memory +- Fallback to embedded data if file not found + +**Benefits**: Edit shaders/assets without regenerating assets_data.cc (~10s rebuild) + +**Trade-offs**: Adds runtime file I/O, only useful during development + +**Priority**: Low (current workflow acceptable) + +--- + +## Visual Effects + +### Task #73: Extend Shader Parametrization [IN PROGRESS - 2/4 complete] +Extend uniform parameter system to remaining effects. + +**Goal**: Add parametrization to DistortEffect, SolarizeEffect + +**Pattern**: Follow FlashEffect implementation (UniformHelper, params struct, .seq syntax) + +**Completed**: ChromaAberrationEffect (offset_scale, angle), GaussianBlurEffect (strength) + +**Priority**: Medium (quality-of-life for artists) + +**Estimated Impact**: ~200-300 bytes per effect + +### Task #52: Procedural SDF Font +Minimal bezier/spline set for [A-Z, 0-9] and SDF rendering. + +### Task #55: SDF Random Planes Intersection +Implement `sdPolyhedron` (crystal/gem shapes) via plane intersection. + +### Task #54: Tracy Integration +Integrate Tracy debugger for performance profiling. + +### Task #58: Advanced Shader Factorization +Further factorize WGSL code into smaller, reusable snippets. + +### Task #59: Comprehensive RNG Library +Add WGSL snippets for float/vec2/vec3 noise (Perlin, Gyroid, etc.) and random number generators. + +### Task #60: OOP Refactoring +Investigate if more C++ code can be made object-oriented without size penalty (vs functional style). + +### Task #61: GPU Procedural Generation +Implement system to generate procedural data (textures, geometry) on GPU and read back to CPU. + +### Task #62: Physics Engine Enhancements (PBD & Rotation) +- **Task #62.1**: Quaternion rotation for `Object3D` with angular momentum +- **Task #62.2**: Position Based Dynamics (PBD) - Re-evaluate velocity after resolving collisions/constraints + +### Task #63: Refactor Large Files +Split `src/gpu/gpu.cc`, `src/3d/visual_debug.cc` and `src/gpu/effect.cc` into sub-functionalities. + +--- + +## Performance Optimization + +### Task #70: SIMD x86_64 Implementation +Implement critical functions using intrinsics for x86_64 platforms. + +**Goal**: Optimize hot paths for audio and procedural generation + +**Scope**: +- IDCT/FDCT transforms +- Audio mixing and voice synthesis +- CPU-side procedural texture/geometry generation + +**Constraint**: Non-critical; fallback to generic C++ must be maintained + +**Priority**: Very Low diff --git a/doc/BUILD.md b/doc/BUILD.md index 1f65122..7cb3473 100644 --- a/doc/BUILD.md +++ b/doc/BUILD.md @@ -21,8 +21,17 @@ cmake --build build_final -j4 | Mode | Flags | Use Case | |------|-------|----------| | Debug | `-DCMAKE_BUILD_TYPE=Debug` | Development, full error checking + debug features | +| SIZE_OPT | `-DDEMO_SIZE_OPT=ON` | Size-optimized, with checks | | STRIP_ALL | `-DDEMO_STRIP_ALL=ON` | Release candidate, full error checking, no debug features (~64k target) | | FINAL_STRIP | `-DDEMO_FINAL_STRIP=ON` | Final release, no error checking, absolute minimum size | +| STRIP_EXTERNAL_LIBS | `-DDEMO_STRIP_EXTERNAL_LIBS=ON` | Size measurement only (binary won't run) | + +**Build Hierarchy:** +- **Debug:** Full checks + debug features (hot-reload, seek, etc.) +- **STRIP_ALL:** Full checks, no debug (~64k target, always fullscreen) +- **FINAL_STRIP:** No checks, no debug (absolute minimum, ⚠️ dangerous) + +**Note:** `DEMO_ALL_OPTIONS=ON` enables tests, tools, AND `STRIP_ALL`. ## Dependencies @@ -67,3 +76,21 @@ open build/demo.xcodeproj ``` Use Xcode Metal debugger for shader performance analysis. + +## Build System Internals + +**Asset Dependency Tracking:** +- CMake tracks 42 demo + 17 test assets +- Editing shaders/audio/sequences auto-triggers rebuild +- Asset lists parsed to extract individual file dependencies + +**Header Organization:** +- `asset_manager_dcl.h`: Forward declarations +- `asset_manager.h`: Core API (GetAsset/DropAsset) +- `asset_manager_utils.h`: Typed helpers + +**Code Generation:** +- Timeline: `seq_compiler` → `generated/timeline.cc` +- Music: `tracker_compiler` → `generated/music.cc` +- Assets: `asset_packer` → `generated/assets.h` + `assets_data.cc` +- Cross-compilation uses host-native tools for generation diff --git a/doc/CODING_STYLE.md b/doc/CODING_STYLE.md new file mode 100644 index 0000000..533cffb --- /dev/null +++ b/doc/CODING_STYLE.md @@ -0,0 +1,109 @@ +# Coding Style Examples + +Detailed examples for the project's C++ coding style. + +--- + +## Core Rules Examples + +### Const Placement +```cpp +const T* name // Correct +const T *name // Wrong +``` + +### Pre-Increment +```cpp +++x // Correct +x++ // Wrong (except when postfix needed) +``` + +### Operator Spacing +```cpp +x = (a + b) * c; // Correct - spaces around all operators +x=(a+b)*c; // Wrong - no spaces +``` + +### No Auto (except complex iterators) +```cpp +int count = get_count(); // Correct +auto count = get_count(); // Wrong + +for (auto it = map.begin(); ...) // OK - complex iterator type +``` + +### No C++ Casts +```cpp +(int)value // Correct +static_cast<int>(value) // Wrong +``` + +--- + +## Preprocessor Style + +```cpp +#if defined(MY_TAG) + // code here +#endif /* defined(MY_TAG) */ +``` + +Always use `defined()` and closing comment. + +--- + +## Struct Initialization + +### Good +```cpp +const WGPUDescriptor desc = { + .format = g_format, + .dimension = WGPUTextureViewDimension_2D, +}; +``` + +### Bad +```cpp +WGPUDescriptor desc = {}; +desc.format = g_format; +desc.dimension = WGPUTextureViewDimension_2D; +``` + +Use designated initializers, not field-by-field assignment. + +--- + +## Class Keywords Indentation + +```cpp +class MyClass { + public: // 1 space indent + void foo(); + + private: // 1 space indent + int field_; +}; +``` + +--- + +## Comments + +### Function Comments +```cpp +// Initializes the audio engine with default settings. +void audio_init() { + ... +} +``` + +One-line comment for non-obvious functions. + +### File Headers +```cpp +// demo64k - 64 kilobyte demo +// src/audio/synth.cc +// Audio synthesis engine +``` + +Three-line header for all source files. diff --git a/doc/COMPLETED.md b/doc/COMPLETED.md index a3c173d..d1c89af 100644 --- a/doc/COMPLETED.md +++ b/doc/COMPLETED.md @@ -29,7 +29,46 @@ Detailed historical documents have been moved to `doc/archive/` for reference: Use `read @doc/archive/FILENAME.md` to access archived documents. -## Recently Completed (February 8, 2026) +## Recently Completed (February 9, 2026) + +- [x] **External Library Size Measurement (Task #76)** + - **Goal**: Measure true demo code size vs external library overhead + - **Implementation**: + - Audio: Use miniaudio's `ma_backend_null` (excludes platform drivers, saves 100-200KB) + - GPU/Platform: Stub our abstractions (~30 functions) instead of external APIs (~300 functions) + - Created `src/platform/stub_types.h` with minimal WebGPU opaque types + - Created `src/platform/stub_platform.cc` and `src/gpu/stub_gpu.cc` + - Added `DEMO_STRIP_EXTERNAL_LIBS` build mode + - Created `scripts/measure_size.sh` for automated measurement + - **Result**: Demo=4.4MB (69%), External=2.0MB (31%). Binary compiles but doesn't run. + - **Documentation**: `doc/SIZE_MEASUREMENT.md` + +- [x] **WGSL Uniform Buffer Validation & Consolidation (Task #75)** + - **Goal**: Standardize uniform buffer usage across all post-process effects and add validation tooling + - **Implementation**: + - Refactored `DistortEffect` and others to use `CommonPostProcessUniforms` (binding 2) + `EffectParams` (binding 3) + - Created `tools/validate_uniforms.py` to parse C++ and WGSL (including embedded strings) and verify size/alignment + - Added validation step to CMake build system + - Renamed generic `EffectParams` to specific names (`FadeParams`, `CircleMaskParams`, etc.) in WGSL and C++ + - Added `doc/UNIFORM_BUFFER_GUIDELINES.md` and updated `CONTRIBUTING.md` + - **Result**: Consistent binding layout across all effects, automatic validation on build + +- [x] **Uniform Buffer Alignment (Task #74)** + - **Goal**: Fix WGSL struct alignment issues causing validation errors and crashes + - **Implementation**: + - `circle_mask_compute.wgsl`: Changed `_pad: vec3<f32>` to three `f32` fields for correct 16-byte alignment + - `fade_effect.cc`: Changed EffectParams padding from `vec3<f32>` to `_pad0/1/2: f32` + - `theme_modulation_effect.cc`: Same padding fix for EffectParams + - Fixed ODR violation in `demo_effects.h` (incomplete FadeEffect forward declaration) + - Renamed shadowing `uniforms_` members to `common_uniforms_`/`flash_uniforms_` + - **Result**: demo64k runs without crashes, 32/33 tests passing (97%), 0 WebGPU validation errors + +- [x] **Fix test_demo Black Screen** + - **Issue**: `test_demo` showed black screen because it failed to load its timeline sequence (`assets/test_demo.seq`) + - **Fix**: Added missing `LoadTimeline` call in `src/test_demo.cc` + - **Result**: `FlashEffect` and `PeakMeterEffect` now render correctly + +## Previously Completed (February 8, 2026) - [x] **Shader Parametrization System (Task #73 Phase 0)** (February 8, 2026) - **Goal**: Enable per-frame dynamic parameters for shaders and effects via uniform buffers and .seq syntax diff --git a/doc/CONTRIBUTING.md b/doc/CONTRIBUTING.md index 3a09dbc..de6378a 100644 --- a/doc/CONTRIBUTING.md +++ b/doc/CONTRIBUTING.md @@ -1,5 +1,7 @@ # Contributing Guidelines +--- + ## Commit Policy ### Verify Before Committing @@ -8,7 +10,6 @@ ```bash ./scripts/check_all.sh ``` -Runs tests, builds tools, cross-compiles Windows. **Manual:** ```bash @@ -26,18 +27,9 @@ cd build && ctest --output-on-failure cmake -S . -B build_debug_check -DDEMO_ENABLE_DEBUG_LOGS=ON cmake --build build_debug_check -j4 ``` -Must compile without errors. **Debug macros** (`src/util/debug.h`): -- `DEBUG_LOG_AUDIO`, `DEBUG_LOG_RING_BUFFER`, `DEBUG_LOG_TRACKER` -- `DEBUG_LOG_SYNTH`, `DEBUG_LOG_3D`, `DEBUG_LOG_ASSETS`, `DEBUG_LOG_GPU` - -Example: -```cpp -#if defined(DEBUG_LOG_AUDIO) - DEBUG_AUDIO("[CALLBACK #%d] frames=%d\n", ++count, frames); -#endif -``` +- `DEBUG_LOG_AUDIO`, `DEBUG_LOG_RING_BUFFER`, `DEBUG_LOG_TRACKER`, `DEBUG_LOG_SYNTH`, `DEBUG_LOG_3D`, `DEBUG_LOG_ASSETS`, `DEBUG_LOG_GPU` ### Code Formatting ```bash @@ -50,6 +42,8 @@ Never format `third_party/`. - 3-line header comment - Max 500 lines (split if larger) +--- + ## Coding Style ### Core Rules @@ -61,36 +55,9 @@ Never format `third_party/`. - No `auto` (except complex iterators) - No C++ casts (`static_cast`, `reinterpret_cast`) -### Preprocessor -```cpp -#if defined(MY_TAG) - ... -#endif /* defined(MY_TAG) */ -``` +See `doc/CODING_STYLE.md` for detailed examples. -### Struct Initialization -```cpp -// Good -const WGPUDescriptor desc = { - .format = g_format, - .dimension = WGPUTextureViewDimension_2D, -}; - -// Bad -WGPUDescriptor desc = {}; -desc.format = g_format; -desc.dimension = WGPUTextureViewDimension_2D; -``` - -### Class Keywords -```cpp - private: // 1 space indent - int field_; -``` - -### Comments -- 1-line comment for non-obvious functions -- 3-line header for all source files +--- ## Development Protocols @@ -170,4 +137,18 @@ After hierarchy changes (moving files, renaming), verify: ./scripts/gen_coverage_report.sh ``` -Update scripts with hardcoded paths. +--- + +## Uniform Buffer Checklist + +To ensure consistency and prevent alignment-related issues: + +1. **Define WGSL Structs:** Pay attention to type alignment (`f32`, `vec2`, `vec3`, `vec4`) and use explicit padding where necessary. +2. **Mirror in C++:** Create corresponding C++ structs that mirror WGSL definitions. +3. **`static_assert` for Size:** Every C++ struct must have a `static_assert` verifying size matches WGSL. +4. **Standard Bindings:** + - **Binding 2:** Always use `CommonPostProcessUniforms` for per-frame data (resolution, time, beat). + - **Binding 3:** Use effect-specific parameter structs for unique data. +5. **Shader Consistency:** Ensure WGSL shaders correctly declare uniforms at specified bindings. +6. **Validation Script:** Run `tools/validate_uniforms.py` to catch discrepancies. +7. **Documentation:** Refer to `doc/UNIFORM_BUFFER_GUIDELINES.md` for detailed alignment rules. diff --git a/doc/GPU_PROCEDURAL_PHASE4.md b/doc/GPU_PROCEDURAL_PHASE4.md new file mode 100644 index 0000000..4cfc271 --- /dev/null +++ b/doc/GPU_PROCEDURAL_PHASE4.md @@ -0,0 +1,70 @@ +# GPU Procedural Phase 4: Texture Composition + +**Status:** ✅ Complete + +## Implementation + +Multi-input composite shaders with configurable sampler support. + +### API + +```cpp +enum class SamplerType { + LinearClamp, LinearRepeat, NearestClamp, NearestRepeat +}; + +void create_gpu_composite_texture( + const std::string& name, + const std::string& shader_func, + const char* shader_code, + const void* uniform_data, + size_t uniform_size, + int width, int height, + const std::vector<std::string>& input_names, + SamplerType sampler = SamplerType::LinearClamp); +``` + +### Shaders + +**gen_blend.wgsl** - Blend two textures with lerp factor: +- Bindings: output (0), uniform (1), input_a (2), input_b (3), sampler (4) +- Uniform: `{u32 width, height; f32 blend_factor, _pad0}` + +**gen_mask.wgsl** - Multiply textures (masking): +- Bindings: output (0), uniform (1), input_a (2), input_b (3), sampler (4) +- Uniform: `{u32 width, height}` + +### Usage + +```cpp +extern const char* gen_blend_compute_wgsl; + +struct { uint32_t width, height; float blend_factor, _pad0; } uni = {256, 256, 0.5f, 0.0f}; + +tex_mgr.create_gpu_composite_texture( + "blended", "gen_blend", gen_blend_compute_wgsl, + &uni, sizeof(uni), 256, 256, + {"noise_a", "noise_b"}, + SamplerType::LinearClamp); +``` + +### Features + +- **Dynamic bind groups:** N input textures + 1 sampler +- **Lazy sampler creation:** Map-based cache, 4 preset types +- **Multi-stage composition:** Composite of composites supported +- **Guarded with `#if !defined(STRIP_GPU_COMPOSITE)`** + +### Size Impact + +- Code: ~460 lines added +- Compressed: ~830 bytes (2 shaders + dispatch logic) + +### Tests + +`test_gpu_composite.cc`: +- Blend two noise textures +- Mask noise with grid +- Multi-stage composite (composite of composites) + +All 35 tests passing. diff --git a/doc/HOT_RELOAD.md b/doc/HOT_RELOAD.md new file mode 100644 index 0000000..681d0aa --- /dev/null +++ b/doc/HOT_RELOAD.md @@ -0,0 +1,162 @@ +# Hot-Reload System + +## Overview + +The hot-reload system enables rapid iteration during development by detecting changes to configuration files at runtime. This feature is **debug-only** and completely stripped from release builds (adds 0 bytes to final binary). + +## Status + +**Implemented:** +- File change detection (`FileWatcher` class) +- Command-line flag `--hot-reload` +- Notification when config files change + +**Not Implemented (Requires Rebuild):** +- Asset reloading (`assets/final/demo_assets.txt`) +- Sequence reloading (`assets/demo.seq`) +- Music reloading (`assets/music.track`) + +Currently, the system **detects** file changes and informs the user to rebuild. Full runtime reload would require significant refactoring of the compile-time asset system. + +## Usage + +```bash +# Launch with hot-reload enabled +./build/demo64k --hot-reload + +# Edit config files while running +vim assets/demo.seq + +# Demo will print: +# [Hot-Reload] Config files changed - rebuild required +# [Hot-Reload] Run: cmake --build build -j4 && ./build/demo64k +``` + +## Architecture + +### File Watcher (`src/util/file_watcher.h`) + +Simple polling-based file change detection using `stat()` mtime: + +```cpp +#if !defined(STRIP_ALL) +FileWatcher watcher; +watcher.add_file("assets/demo.seq"); + +if (watcher.check_changes()) { + // File changed + watcher.reset(); +} +#endif +``` + +**Design:** +- Polls file mtimes in main loop (~60Hz) +- Cross-platform (POSIX stat) +- 1-second mtime granularity (filesystem dependent) + +**Watched Files:** +- `assets/final/demo_assets.txt` - Asset definitions +- `assets/demo.seq` - Visual effect timeline +- `assets/music.track` - Audio patterns + +### Integration (`src/main.cc`) + +```cpp +#if !defined(STRIP_ALL) +bool hot_reload_enabled = false; + +// Command-line parsing +if (strcmp(argv[i], "--hot-reload") == 0) { + hot_reload_enabled = true; +} + +// Setup +FileWatcher file_watcher; +if (hot_reload_enabled) { + file_watcher.add_file("assets/final/demo_assets.txt"); + file_watcher.add_file("assets/demo.seq"); + file_watcher.add_file("assets/music.track"); +} + +// Main loop +if (hot_reload_enabled && file_watcher.check_changes()) { + printf("[Hot-Reload] Config files changed - rebuild required\n"); + file_watcher.reset(); +} +#endif +``` + +## Why Not Full Reload? + +The current architecture compiles all assets at build time: + +1. **Assets** (`demo_assets.txt`): + - Parsed by `asset_packer` tool + - Generates C++ arrays in `generated/assets.h` + - Linked into binary as static data + - Runtime reload would need file I/O + dynamic memory + +2. **Sequences** (`demo.seq`): + - Parsed by `seq_compiler` tool + - Generates `LoadTimeline()` function in C++ + - Effect objects created with compile-time parameters + - Runtime reload would need C++ code generation or scripting + +3. **Music** (`music.track`): + - Parsed by `tracker_compiler` tool + - Generates static pattern/score data + - Referenced by pointers in audio engine + - Runtime reload needs atomic pointer swap + memory management + +Implementing full reload would require: +- Runtime parsers (duplicate build-time compilers) +- Dynamic memory allocation (conflicts with size optimization) +- Effect state preservation (complex) +- Thread-safe audio data swap +- ~20-25 hours of work (per the plan) + +## Size Impact + +**Debug build:** +800 bytes (FileWatcher + main loop logic) +**STRIP_ALL build:** 0 bytes (all code removed by `#if !defined(STRIP_ALL)`) + +## Testing + +Unit test: `src/tests/test_file_watcher.cc` + +```bash +# Run test +cd build && ctest -R FileWatcherTest + +# Note: Test sleeps 1 second to ensure mtime changes +# (some filesystems have 1s mtime granularity) +``` + +## Future Work + +If hot-reload becomes critical for workflow, consider: + +1. **Incremental approach:** + - Phase 1: Asset cache clearing (easy, limited value) + - Phase 2: Sequence state preservation (medium, high value) + - Phase 3: Tracker atomic swap (hard, high value) + +2. **External scripting:** + - Watch files externally (fswatch/inotifywait) + - Auto-rebuild + restart demo + - Preserves current architecture + +3. **Hybrid approach:** + - Keep compile-time for release + - Add optional runtime parsers for debug + - Conditional on `--hot-reload` flag + +## Related Files + +- `src/util/file_watcher.h` - File change detection API +- `src/util/file_watcher.cc` - Implementation +- `src/util/asset_manager.cc` - Stub `ReloadAssetsFromFile()` (clears cache) +- `src/main.cc` - Main loop integration +- `src/tests/test_file_watcher.cc` - Unit tests +- `CMakeLists.txt` - Build system integration diff --git a/doc/HOWTO.md b/doc/HOWTO.md index 967b554..9e32a86 100644 --- a/doc/HOWTO.md +++ b/doc/HOWTO.md @@ -1,6 +1,8 @@ # How To -Common commands for building and testing. +Quick reference for common tasks. + +--- ## Building @@ -10,209 +12,85 @@ cmake -S . -B build cmake --build build -j4 ./build/demo64k ``` +Options: `--fullscreen`, `--resolution WxH`, `--seek TIME`, `--hot-reload` -Options: -- `--fullscreen`: Run in fullscreen -- `--resolution WxH`: Set window size (e.g., 1024x768) -- `--seek TIME`: Jump to timestamp (debug builds only) +### Production Builds +```bash +# Size-optimized (development) +cmake -B build -DDEMO_SIZE_OPT=ON && cmake --build build -j4 -Keyboard: `Esc` (exit), `F` (toggle fullscreen) +# 64k target (full checks, no debug) +cmake -B build -DDEMO_STRIP_ALL=ON && cmake --build build -j4 -### Size-Optimized Build -```bash -cmake -S . -B build -DDEMO_SIZE_OPT=ON -cmake --build build -j4 +# Final release (no checks, absolute minimum) +./scripts/build_final.sh ``` -### Strip Build (64k Target) +### Developer Build ```bash -cmake -S . -B build -DDEMO_STRIP_ALL=ON +cmake -B build -DDEMO_BUILD_TESTS=ON -DDEMO_BUILD_TOOLS=ON cmake --build build -j4 ``` -Always starts in fullscreen. Full error checking enabled. -### Final Build (Maximum Stripping) +### Size Measurement ```bash -./scripts/build_final.sh -# or -cmake -S . -B build_final -DDEMO_FINAL_STRIP=ON -cmake --build build_final -j4 +./scripts/measure_size.sh ``` -⚠️ Removes ALL error checking. Use only for final release. +Measures demo vs external library size. See `doc/SIZE_MEASUREMENT.md`. -**Build Hierarchy:** -- Debug: Full checks + debug features -- STRIP_ALL: Full checks, no debug (~64k target) -- FINAL_STRIP: No checks, no debug (absolute minimum) +--- + +## Testing -### Developer Build ```bash -cmake -S . -B build -DDEMO_ALL_OPTIONS=ON +cmake -B build -DDEMO_BUILD_TESTS=ON cmake --build build -j4 +cd build && ctest ``` -Enables tests, tools, size optimizations. - -## Build System -**Dependency Tracking**: CMake tracks 42 demo + 17 test assets. Editing shaders/audio auto-triggers rebuild. +--- -**Header Organization**: -- `asset_manager_dcl.h`: Forward declarations -- `asset_manager.h`: Core API (GetAsset/DropAsset) -- `asset_manager_utils.h`: Typed helpers +## Timeline -## Git Clone -```bash -git clone ssh://git@51.38.51.127/~/demo.git +Edit `assets/demo.seq`: +```text +SEQUENCE 0.0 0 + EFFECT HeptagonEffect 0.0 60.0 0 ``` +Rebuild to apply. See `doc/SEQUENCE.md`. + +--- -## Audio System +## Audio -### AudioEngine API ```cpp #include "audio/audio_engine.h" audio_init(); static AudioEngine g_audio_engine; g_audio_engine.init(); - -// Main loop g_audio_engine.update(music_time); - g_audio_engine.shutdown(); audio_shutdown(); ``` +See `doc/TRACKER.md` for music system. -**Methods:** -- `init()`: Initialize synth + tracker -- `update(music_time)`: Update music state -- `shutdown()`: Cleanup -- `seek(time)`: Jump to timestamp (debug only) - -**Direct Synth APIs** (performance-critical): -- `synth_register_spectrogram()`: Register samples -- `synth_trigger_voice()`: Trigger playback -- `synth_get_output_peak()`: Get audio level -- `synth_render()`: Low-level rendering - -**Testing:** -```cpp -AudioEngine engine; -engine.init(); -engine.update(1.0f); -engine.shutdown(); -``` - -## Auxiliary Texture Masking - -Share textures between effects: -```cpp -// Generator effect -demo->register_auxiliary_texture("mask_name", width, height); -WGPUTextureView view = demo_->get_auxiliary_view("mask_name"); - -// Consumer effect -WGPUTextureView view = demo_->get_auxiliary_view("mask_name"); -``` -See `doc/MASKING_SYSTEM.md` for details. - -## Demo Timeline +--- -Edit `assets/demo.seq`: -```text -SEQUENCE 0.0 0 - EFFECT HeptagonEffect 0.0 60.0 0 -``` -Rebuild to update timeline. +## Assets -## Testing - -**Run all tests:** -```bash -cmake -S . -B build -DDEMO_BUILD_TESTS=ON -cmake --build build -j4 -cd build && ctest -``` - -**Key tests:** -- `HammingWindowTest`: Window function properties -- `MathUtilsTest`: Math utilities -- `SynthEngineTest`: Audio synthesis -- `SequenceSystemTest`: Timeline logic - -## Code Coverage (macOS) -```bash -brew install lcov -./scripts/gen_coverage_report.sh [target_dir] -``` - -## Tools - -### Windows Cross-Compilation -```bash -./scripts/fetch_win_deps.sh -./scripts/build_win.sh -./scripts/run_win.sh -``` - -### spectool (Audio Analysis) -```bash -cmake -S . -B build -DDEMO_BUILD_TOOLS=ON -cmake --build build -j4 - -# Analyze -./build/spectool analyze input.wav output.spec - -# Play -./build/spectool play input.spec -``` - -### specview (Visualization) -```bash -./build/specview input.spec -``` - -### specplay (Diagnostic) -```bash -./build/specplay input.spec -# or -./build/specplay input.wav -``` -Output: Peak, RMS, clipping detection. - -### Submodule Updates -```bash -cd third_party/wgpu-native -git fetch -git checkout trunk -git reset --hard origin/trunk -cd ../.. -git add third_party/wgpu-native -git commit -m "chore: Update wgpu-native" -``` - -## Asset Management - -### Define Assets -Edit `assets/final/demo_assets.txt`: -``` -KICK_1, kick1.spec, NONE, "Drum kick" -``` - -### Regenerate +Edit `assets/final/demo_assets.txt`, then: ```bash ./scripts/gen_assets.sh ``` -Converts WAV → .spec, packs into C++ arrays. +See `doc/ASSET_SYSTEM.md`. -### Use Assets -```cpp -#include "assets.h" +--- -size_t size; -const uint8_t* data = GetAsset(AssetId::KICK_1, &size); -// Use data... -// DropAsset(AssetId::KICK_1, data); // For compressed assets only -``` +## Additional Documentation -Build system auto-runs `asset_packer` when asset lists change. +- **Build System:** `doc/BUILD.md` - Multi-platform, size optimization +- **Tools:** `doc/TOOLS_REFERENCE.md` - spectool, coverage, Windows cross-compile +- **Shaders:** `doc/SEQUENCE.md` - Timeline format, shader parameters +- **3D:** `doc/3D.md` - Hybrid rendering, scene format +- **Hot Reload:** `doc/HOT_RELOAD.md` - Debug file watching 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` diff --git a/doc/SIZE_MEASUREMENT.md b/doc/SIZE_MEASUREMENT.md new file mode 100644 index 0000000..96c8e6c --- /dev/null +++ b/doc/SIZE_MEASUREMENT.md @@ -0,0 +1,206 @@ +# External Library Size Measurement (Task #76) + +## Goal + +Measure true demo code size by stubbing external library dependencies. + +**Motivation:** STRIP_ALL builds (~5MB) include wgpu_native (~3.5MB), GLFW (~500KB), miniaudio. Need to isolate demo code size for 64KB target tracking. + +## Strategy + +Two-part approach: + +### 1. Audio: Use miniaudio's Null Backend + +miniaudio has built-in `ma_backend_null` that excludes platform audio drivers: + +```cmake +target_compile_definitions(demo64k PRIVATE + MA_ENABLE_ONLY_SPECIFIC_BACKENDS + MA_ENABLE_NULL +) +``` + +**Savings:** ~100-200KB (excludes CoreAudio/WASAPI/ALSA/etc.) +**Benefit:** Audio synthesis still runs, but no driver overhead + +### 2. GPU/Platform: Stub Our Own Abstractions + +Instead of stubbing ~300 external functions, stub our ~10 platform/gpu wrappers: + +**`src/platform/stub_types.h` (STRIP_EXTERNAL_LIBS only):** +```cpp +// Minimal WebGPU opaque types +typedef void* WGPUInstance; +typedef void* WGPUAdapter; +typedef void* WGPUDevice; +typedef void* WGPUBuffer; +typedef void* WGPUTexture; +// ... all types as void* + +struct WGPUBufferDescriptor {}; +struct WGPUTextureDescriptor {}; +// ... all descriptor structs empty +``` + +**`src/platform/stub_platform.cc`:** +```cpp +#if defined(STRIP_EXTERNAL_LIBS) +#include "stub_types.h" + +PlatformState platform_init(bool, int w, int h) { + return {w, h, nullptr, nullptr}; +} + +void platform_poll_events() {} +bool platform_should_close() { return false; } +void platform_shutdown() {} +void platform_present() {} +double platform_get_time() { return 0.0; } +#endif +``` + +**`src/gpu/stub_gpu.cc`:** +```cpp +#if defined(STRIP_EXTERNAL_LIBS) +WGPUDevice gpu_create_device() { return nullptr; } +WGPUBuffer gpu_create_buffer(const WGPUBufferDescriptor*) { return nullptr; } +// ... stub ~20 gpu wrapper functions +#endif +``` + +**Benefits:** +- Stub our API (~30 functions) vs external APIs (~300 functions) +- Opaque pointers only (no real struct definitions needed) +- Don't link wgpu_native or GLFW at all +- Actually measures demo code, not stub code + +## Build System Integration + +**CMakeLists.txt:** +```cmake +if(DEMO_STRIP_EXTERNAL_LIBS) + # Audio: Use null backend + target_compile_definitions(demo64k PRIVATE + MA_ENABLE_ONLY_SPECIFIC_BACKENDS + MA_ENABLE_NULL + ) + + # GPU/Platform: Use stubs + target_compile_definitions(demo64k PRIVATE STRIP_EXTERNAL_LIBS) + target_sources(demo64k PRIVATE + src/platform/stub_platform.cc + src/gpu/stub_gpu.cc + ) + + # Don't link external libs + # (only math/pthread from system) + + # Size measurement flags + set(CMAKE_C_FLAGS "-Os") + set(CMAKE_CXX_FLAGS "-Os") +endif() +``` + +**Build:** +```bash +cmake -S . -B build_size -DDEMO_STRIP_EXTERNAL_LIBS=ON +cmake --build build_size -j4 +strip build_size/demo64k +ls -lh build_size/demo64k # True demo size +``` + +## Expected Results + +``` +STRIP_ALL build: 5.1 MB +├── wgpu_native: 3.5 MB +├── GLFW: 0.5 MB +├── miniaudio (full): 0.3 MB +├── System libs: 0.3 MB +└── Demo code: 0.5 MB + +STRIP_EXTERNAL_LIBS: 0.5 MB +└── Demo code only: 0.5 MB (100%) +``` + +Binary compiles but does NOT run (all I/O stubbed). + +## Implementation Plan + +### Phase 1: Stub Type Definitions (1 hour) + +Create `src/platform/stub_types.h`: +- Define all WebGPU types as `typedef void*` +- Define all descriptor structs as empty `struct {}` +- Include guards for `STRIP_EXTERNAL_LIBS` + +### Phase 2: Platform Stubs (1 hour) + +Create `src/platform/stub_platform.cc`: +- Implement ~10 platform functions as no-ops +- Return dummy PlatformState with reasonable dimensions +- Compile only when `STRIP_EXTERNAL_LIBS` defined + +### Phase 3: GPU Stubs (1 hour) + +Create `src/gpu/stub_gpu.cc`: +- Implement ~20 gpu wrapper functions as no-ops +- All pointer returns = nullptr +- All void functions = empty body + +### Phase 4: Build Integration (1 hour) + +Update `CMakeLists.txt`: +- Add `DEMO_STRIP_EXTERNAL_LIBS` option +- Enable ma_backend_null defines +- Add stub source files conditionally +- Remove external library linking + +### Phase 5: Validation (1 hour) + +```bash +cmake -S . -B build_size -DDEMO_STRIP_EXTERNAL_LIBS=ON +cmake --build build_size -j4 +strip build_size/demo64k +size build_size/demo64k +``` + +**Estimated time: 5 hours** + +## Use Cases + +**Weekly size tracking:** +```bash +./scripts/measure_size.sh # Demo=512KB, External=4.5MB +``` + +**Subsystem attribution:** +- GPU effects: 150KB +- 3D rendering: 120KB +- Audio synthesis: 100KB +- Asset system: 80KB + +**CI size monitoring:** +```yaml +- run: cmake -B build_size -DDEMO_STRIP_EXTERNAL_LIBS=ON +- run: size build_size/demo64k | tee size_report.txt +``` + +## Success Criteria + +1. Binary compiles with `STRIP_EXTERNAL_LIBS=ON` +2. Size < 1MB (confirms external lib isolation) +3. Repeatable builds +4. Tracks size changes over time + +## Related Files + +**New:** +- `src/platform/stub_types.h` - WebGPU opaque types +- `src/platform/stub_platform.cc` - Platform stubs (~10 functions) +- `src/gpu/stub_gpu.cc` - GPU stubs (~20 functions) +- `scripts/measure_size.sh` - Size measurement script + +**Modified:** +- `CMakeLists.txt` - Add STRIP_EXTERNAL_LIBS mode diff --git a/doc/TOOLS_REFERENCE.md b/doc/TOOLS_REFERENCE.md new file mode 100644 index 0000000..61412a9 --- /dev/null +++ b/doc/TOOLS_REFERENCE.md @@ -0,0 +1,89 @@ +# Developer Tools Reference + +Comprehensive reference for all developer tools in the project. + +--- + +## Windows Cross-Compilation + +```bash +# Fetch dependencies +./scripts/fetch_win_deps.sh + +# Build Windows binary +./scripts/build_win.sh + +# Run with Wine +./scripts/run_win.sh +``` + +--- + +## spectool (Audio Analysis) + +```bash +# Build +cmake -S . -B build -DDEMO_BUILD_TOOLS=ON +cmake --build build -j4 + +# Analyze WAV → .spec +./build/spectool analyze input.wav output.spec + +# Play .spec file +./build/spectool play input.spec +``` + +--- + +## specview (Visualization) + +```bash +# View spectrogram +./build/specview input.spec +``` + +Displays spectrogram visualization. + +--- + +## specplay (Diagnostic) + +```bash +# Analyze .spec file +./build/specplay input.spec + +# Or analyze .wav file +./build/specplay input.wav +``` + +Output: Peak, RMS, clipping detection. + +--- + +## Code Coverage (macOS) + +```bash +# Install lcov +brew install lcov + +# Generate coverage report +./scripts/gen_coverage_report.sh [target_dir] +``` + +Creates HTML coverage report. + +--- + +## Submodule Updates + +```bash +cd third_party/wgpu-native +git fetch +git checkout trunk +git reset --hard origin/trunk +cd ../.. +git add third_party/wgpu-native +git commit -m "chore: Update wgpu-native" +``` + +Updates wgpu-native to latest trunk. diff --git a/doc/TRACKER.md b/doc/TRACKER.md index 6e71951..48829c0 100644 --- a/doc/TRACKER.md +++ b/doc/TRACKER.md @@ -74,3 +74,42 @@ PATTERN short_fill LENGTH 0.5 # 2 beats = 1 second at 120 BPM Potential runtime modifiers (not yet implemented): - Randomization, accents, volume modulation, distortion, noise, effects + +--- + +## AudioEngine API + +**Production usage:** +```cpp +#include "audio/audio_engine.h" + +audio_init(); +static AudioEngine g_audio_engine; +g_audio_engine.init(); + +// Main loop +g_audio_engine.update(music_time); + +g_audio_engine.shutdown(); +audio_shutdown(); +``` + +**Methods:** +- `init()`: Initialize synth + tracker +- `update(music_time)`: Update music state, trigger patterns +- `shutdown()`: Cleanup +- `seek(time)`: Jump to timestamp (debug builds only) + +**Testing:** +```cpp +AudioEngine engine; +engine.init(); +engine.update(1.0f); +engine.shutdown(); +``` + +**Direct synth APIs** (performance-critical code only): +- `synth_register_spectrogram()` +- `synth_trigger_voice()` +- `synth_get_output_peak()` +- `synth_render()` diff --git a/doc/UNIFORM_BUFFER_GUIDELINES.md b/doc/UNIFORM_BUFFER_GUIDELINES.md new file mode 100644 index 0000000..ac02223 --- /dev/null +++ b/doc/UNIFORM_BUFFER_GUIDELINES.md @@ -0,0 +1,106 @@ +# WGSL Uniform Buffer Guidelines + +This document outlines the rules and best practices for defining and using uniform buffers in WGSL shaders within this project, focusing on alignment, size, and consistency. + +## WGSL Alignment Rules + +Understanding WGSL's memory layout rules is crucial for correct uniform buffer implementation. The following are the general alignment requirements for common WGSL types: + +- `f32`: 4-byte alignment. +- `vec2<f32>`: 8-byte alignment (4 bytes per component * 2 components = 8 bytes). +- `vec3<f32>`: 16-byte alignment (4 bytes per component * 3 components = 12 bytes, padded to 16). +- `vec4<f32>`: 16-byte alignment (4 bytes per component * 4 components = 16 bytes). +- `array<T, N>`: The alignment of an array is typically the alignment of its base type `T`. + +Structs are padded to the alignment of their largest member. Any trailing space in a struct is also padded to match the maximum alignment of any member within the struct. + +## Standard Uniform Buffer Pattern + +To maintain consistency and facilitate efficient rendering, a standard pattern for uniform buffer usage is established: + +- **Binding 0 & 1:** Reserved for Sampler and Texture access (handled by `pp_update_bind_group`). +- **Binding 2:** **Common Uniforms** (`CommonPostProcessUniforms` or similar). This buffer should contain frequently used data like resolution, aspect ratio, time, beat, and audio intensity. +- **Binding 3:** **Effect-Specific Parameters**. This buffer holds parameters unique to a particular effect (e.g., `strength`, `speed`, `fade_amount`). + +This pattern ensures that common data is shared efficiently across effects, while effect-specific data remains isolated. + +## Defining Uniform Structs + +### WGSL Definitions + +When defining uniform structs in WGSL, adhere to the following: + +- **Explicit Padding:** Use padding fields (`_pad0`, `_pad1`, etc.) where necessary to ensure correct alignment, especially when mixing types of different alignment requirements (e.g., `vec2<f32>` followed by `f32`s). +- **Use `vec2<f32>` for 8-byte padding:** If you need 8 bytes of padding, use `_pad0: vec2<f32>` instead of `_pad0: f32, _pad1: f32` for potentially better clarity and to leverage WGSL's type system. +- **Minimize Padding:** Only add padding where required by alignment rules to reduce memory usage. + +**Example (CommonPostProcessUniforms / HeptagonUniforms):** + +```wgsl +struct CommonUniforms { + resolution: vec2<f32>, + _pad0: vec2<f32>, // 8 bytes padding to align subsequent members + aspect_ratio: f32, + time: f32, + beat: f32, + audio_intensity: f32, +}; +// Expected size: 32 bytes +``` + +**Example (EffectParams with f32 members):** + +```wgsl +struct EffectParams { + parameter1: f32, + parameter2: f32, + // ... more parameters ... +}; +// Expected size: 8 bytes (if only two f32s) +``` + +### C++ Definitions and Validation + +For every WGSL uniform struct, a corresponding C++ struct must exist. This C++ struct must include a `static_assert` to verify its size and alignment matches the WGSL definition. + +- **Mirror WGSL Structure:** The C++ struct should mirror the WGSL struct's member order and types as closely as possible to ensure accurate size calculation. +- **`static_assert`:** Always include `static_assert(sizeof(MyStruct) == EXPECTED_SIZE, "MyStruct must be EXPECTED_SIZE bytes for WGSL alignment");`. +- **Use `float` for `f32`:** Use `float` for `f32` in C++. +- **Use `vec2<f32>` mapping:** If WGSL uses `vec2<f32>`, map it to an equivalent C++ type that occupies 8 bytes, typically `float[2]` or a `struct Vec2 { float x, y; }` if more complex type handling is needed. +- **Padding:** C++ padding rules can differ from WGSL. Pay close attention to `static_assert` for validation. + +**Example (C++ CommonPostProcessUniforms):** + +```cpp +struct CommonPostProcessUniforms { + vec2 resolution; // 8 bytes + float _pad[2]; // 8 bytes padding (matches vec2<f32> in WGSL) + float aspect_ratio; // 4 bytes + float time; // 4 bytes + float beat; // 4 bytes + float audio_intensity; // 4 bytes +}; +static_assert(sizeof(CommonPostProcessUniforms) == 32, + "CommonPostProcessUniforms must be 32 bytes for WGSL alignment"); +``` + +**Example (C++ GaussianBlurParams):** + +```cpp +struct GaussianBlurParams { + float strength = 2.0f; + float _pad = 0.0f; +}; +static_assert(sizeof(GaussianBlurParams) == 8, + "GaussianBlurParams must be 8 bytes for WGSL alignment"); +``` + +## Handling Common Pitfalls + +- **`vec3<f32>` Padding:** Avoid using `vec3<f32>` for padding in WGSL, as it has a 16-byte alignment. If padding is needed, use `vec2<f32>` for 8 bytes or individual `f32`s for 4-byte alignment. +- **C++ vs. WGSL Alignment:** Always rely on `static_assert` in C++ and verify against WGSL alignment rules. C++ padding rules might differ, and the `static_assert` is the ultimate arbiter. +- **Unmatched Structs:** Ensure every WGSL uniform struct has a corresponding C++ struct with a matching `static_assert`. + +## Validation Tool + +The `tools/validate_uniforms.py` script is integrated into the build system. It automatically checks for inconsistencies between WGSL and C++ uniform struct definitions and reports any size mismatches. Ensure this script passes for all new or modified uniform definitions. diff --git a/doc/WORKSPACE_SYSTEM.md b/doc/WORKSPACE_SYSTEM.md new file mode 100644 index 0000000..45fb064 --- /dev/null +++ b/doc/WORKSPACE_SYSTEM.md @@ -0,0 +1,546 @@ +# Workspace System (Task #77) + +## Goal + +Reorganize project to support multiple self-contained demo workspaces, enabling parallel development and clean separation of content. + +**Motivation:** Current structure scatters demo content across multiple locations: +- `assets/demo.seq` - Timeline +- `assets/music.track` - Audio +- `assets/final/demo_assets.txt` - Asset list +- `assets/final/music/*.spec` - Audio samples +- `assets/shaders/*.wgsl` - Shaders (some demo-specific, some shared) + +This makes it hard to: +- Work on multiple demos simultaneously +- Understand what files belong to which demo +- Share common resources cleanly +- Onboard new developers + +## Problem Statement + +**Current structure (flat):** +``` +/assets/ + demo.seq # Main demo timeline + music.track # Main demo music + test_demo.seq # Test demo timeline + test_demo.track # Test demo music + /final/ + demo_assets.txt # Main demo assets + /music/*.spec # Audio samples (mixed usage) + /shaders/ + *.wgsl # Mixed: common + demo-specific +``` + +**Issues:** +1. No clear ownership of files +2. Can't easily switch between demos +3. Assets intermingled (demo vs test) +4. Shaders hard to categorize (shared vs demo-specific) +5. Adding new demos requires scattered changes + +## Proposed Structure + +### Workspace Directory Layout + +``` +/workspaces/ + /main/ # Main production demo + workspace.cfg # Workspace metadata + timeline.seq # Visual effects + music.track # Audio patterns + assets.txt # Asset list + /assets/ + /music/*.spec # Demo-specific audio + /meshes/*.obj # Demo-specific meshes + /shaders/ # Demo-specific shaders + custom_effect.wgsl + + /test/ # Test/validation demo + workspace.cfg + timeline.seq + music.track + assets.txt + /assets/ + /shaders/ + + /experiments/ # Experimental demos + /demo2024_revision/ + workspace.cfg + ... + +/assets/common/ # Shared resources + /shaders/ + /math/ # Shared math utilities + common_utils.wgsl + sdf.wgsl + /common_uniforms/ # Shared uniforms + common.wgsl + /audio/ + standard_drums.spec # Shared samples +``` + +### Workspace Configuration + +**`workspace.cfg` format:** +```ini +[workspace] +name = "Main Demo" +description = "Production 64k demo" +version = "1.0" + +[build] +# Output binary name +target = "demo64k" + +# Include paths (relative to workspace root) +timeline = "timeline.seq" +music = "music.track" +assets = "assets.txt" + +# Asset directories +asset_dirs = ["assets/", "../common/audio/"] + +# Shader directories (order matters: workspace-specific first) +shader_dirs = ["shaders/", "../common/shaders/"] + +[options] +# Default resolution +width = 1280 +height = 720 + +# Duration (seconds, -1 = auto-detect) +duration = -1 + +# BPM for timeline +bpm = 120 +``` + +## Architecture + +### Build System Integration + +**CMakeLists.txt changes:** +```cmake +# Select active workspace (default: main) +set(DEMO_WORKSPACE "main" CACHE STRING "Active workspace") + +# Parse workspace config +set(WORKSPACE_DIR "${CMAKE_SOURCE_DIR}/workspaces/${DEMO_WORKSPACE}") +set(WORKSPACE_CFG "${WORKSPACE_DIR}/workspace.cfg") + +# Read config and set variables +parse_workspace_config(${WORKSPACE_CFG} + TIMELINE_PATH + MUSIC_PATH + ASSETS_PATH + ASSET_DIRS + SHADER_DIRS + TARGET_NAME +) + +# Generate assets from workspace +add_custom_command( + OUTPUT ${GEN_ASSETS_H} + COMMAND asset_packer ${WORKSPACE_DIR}/${ASSETS_PATH} ... + DEPENDS ${WORKSPACE_DIR}/${ASSETS_PATH} +) + +# Generate timeline from workspace +add_custom_command( + OUTPUT ${GEN_TIMELINE_CC} + COMMAND seq_compiler ${WORKSPACE_DIR}/${TIMELINE_PATH} ... + DEPENDS ${WORKSPACE_DIR}/${TIMELINE_PATH} +) + +# Add shader include paths from workspace +foreach(SHADER_DIR ${SHADER_DIRS}) + list(APPEND SHADER_INCLUDE_DIRS "${WORKSPACE_DIR}/${SHADER_DIR}") +endforeach() +``` + +**Building specific workspace:** +```bash +# Default (main workspace) +cmake -S . -B build +cmake --build build -j4 + +# Specific workspace +cmake -S . -B build_test -DDEMO_WORKSPACE=test +cmake --build build_test -j4 + +# Experimental workspace +cmake -S . -B build_exp -DDEMO_WORKSPACE=experiments/demo2024_revision +cmake --build build_exp -j4 +``` + +### Asset Packer Changes + +**Support workspace-relative paths:** +```cpp +// asset_packer.cc +void PackerContext::resolve_asset_path(const char* rel_path) { + // Try workspace-local first + std::string workspace_path = workspace_dir + "/" + rel_path; + if (file_exists(workspace_path)) { + return workspace_path; + } + + // Try common assets + std::string common_path = "assets/common/" + rel_path; + if (file_exists(common_path)) { + return common_path; + } + + error("Asset not found: %s", rel_path); +} +``` + +### Shader Composer Changes + +**Support multiple include paths:** +```cpp +// shader_composer.cc +class ShaderComposer { + std::vector<std::string> include_paths_; + + void add_include_path(const char* path) { + include_paths_.push_back(path); + } + + std::string resolve_include(const char* filename) { + for (const auto& base : include_paths_) { + std::string full = base + "/" + filename; + if (file_exists(full)) { + return full; + } + } + error("Shader include not found: %s", filename); + } +}; +``` + +**Shader includes with search paths:** +```wgsl +// workspace-specific shader +#include "custom_uniforms.wgsl" // From workspaces/main/shaders/ +#include "math/common_utils.wgsl" // From assets/common/shaders/ +``` + +### CLI Tool + +**`scripts/workspace.sh` - Workspace management:** +```bash +#!/bin/bash +# workspace.sh - Manage demo workspaces + +case "$1" in + list) + # List all workspaces + ls -1 workspaces/ + ;; + + create) + # Create new workspace from template + NAME=$2 + mkdir -p workspaces/$NAME/{assets,shaders} + cp templates/workspace.cfg workspaces/$NAME/ + echo "Created workspace: $NAME" + ;; + + build) + # Build specific workspace + NAME=$2 + cmake -S . -B build_$NAME -DDEMO_WORKSPACE=$NAME + cmake --build build_$NAME -j4 + ;; + + info) + # Show workspace details + NAME=$2 + cat workspaces/$NAME/workspace.cfg + ;; +esac +``` + +**Usage:** +```bash +./scripts/workspace.sh list +./scripts/workspace.sh create my_demo +./scripts/workspace.sh build main +./scripts/workspace.sh info test +``` + +## Implementation Plan + +### Phase 1: Create Workspace Structure + +**1.1 Create directory tree:** +```bash +mkdir -p workspaces/{main,test}/assets +mkdir -p workspaces/{main,test}/shaders +mkdir -p assets/common/{shaders,audio} +``` + +**1.2 Move existing files:** +```bash +# Main demo +mv assets/demo.seq workspaces/main/timeline.seq +mv assets/music.track workspaces/main/music.track +mv assets/final/demo_assets.txt workspaces/main/assets.txt +mv assets/final/music workspaces/main/assets/ + +# Test demo +mv assets/test_demo.seq workspaces/test/timeline.seq +mv assets/test_demo.track workspaces/test/music.track +cp assets/final/demo_assets.txt workspaces/test/assets.txt # Or create minimal version + +# Common shaders +mv assets/shaders/math assets/common/shaders/ +mv assets/shaders/common_uniforms assets/common/shaders/ +``` + +**1.3 Create workspace configs:** +```bash +# Generate workspace.cfg for main and test +# See format above +``` + +### Phase 2: Update Build System + +**2.1 Add workspace parsing:** +- Create `cmake/ParseWorkspace.cmake` +- Parse INI-style config +- Set CMake variables + +**2.2 Update CMakeLists.txt:** +- Add `DEMO_WORKSPACE` option +- Use workspace paths for assets/timeline/music +- Pass shader include paths to ShaderComposer + +**2.3 Update compiler tools:** +- `asset_packer`: Accept workspace root dir +- `seq_compiler`: Accept workspace root dir +- `tracker_compiler`: Accept workspace root dir + +### Phase 3: Update Code + +**3.1 ShaderComposer:** +- Add multi-path include resolution +- Search workspace shaders first, then common + +**3.2 Asset loading:** +- Update paths to match new structure +- Ensure backward compatibility during transition + +**3.3 Generated code:** +- Verify generated assets.h/timeline.cc still work +- Update include paths if needed + +### Phase 4: Documentation & Tools + +**4.1 Update docs:** +- README.md: Explain workspace structure +- HOWTO.md: Document workspace build commands +- CONTRIBUTING.md: Workspace best practices + +**4.2 Create helper scripts:** +- `scripts/workspace.sh`: Workspace management CLI +- `templates/workspace.cfg`: Template for new workspaces + +**4.3 Update CI:** +- Build both main and test workspaces +- Validate workspace isolation + +### Phase 5: Migration & Validation + +**5.1 Test builds:** +```bash +# Build both workspaces +cmake -S . -B build_main -DDEMO_WORKSPACE=main +cmake --build build_main -j4 + +cmake -S . -B build_test -DDEMO_WORKSPACE=test +cmake --build build_test -j4 + +# Run tests +cd build_main && ctest +cd build_test && ctest +``` + +**5.2 Verify isolation:** +- Modify workspace-specific shader → only that workspace affected +- Modify common shader → both workspaces affected + +**5.3 Update .gitignore:** +``` +/build_*/ +/workspaces/*/generated/ +``` + +## Benefits + +### 1. Clear Ownership +Each workspace is self-contained: +```bash +workspaces/main/ +├── workspace.cfg # Configuration +├── timeline.seq # Timeline +├── music.track # Music +├── assets.txt # Assets +├── assets/ # Local assets +└── shaders/ # Local shaders +``` + +### 2. Parallel Development +Multiple developers can work on different demos without conflicts: +```bash +# Developer A: Main demo +cd workspaces/main +vim timeline.seq + +# Developer B: Experimental demo +cd workspaces/experiments/christmas_demo +vim timeline.seq +``` + +### 3. Easy Switching +```bash +# Build main demo +cmake -B build -DDEMO_WORKSPACE=main +./build/demo64k + +# Build test demo +cmake -B build -DDEMO_WORKSPACE=test +./build/test_demo +``` + +### 4. Clean Sharing +Common resources in one place: +``` +assets/common/ +├── shaders/ +│ ├── math/ # Shared by all +│ └── common_uniforms/ +└── audio/ + └── standard_drums.spec +``` + +### 5. Scalability +Easy to add new demos: +```bash +./scripts/workspace.sh create party2025 +cd workspaces/party2025 +# Start developing... +``` + +## Migration Path + +### Backward Compatibility + +During transition, support both structures: +```cmake +if(EXISTS "${CMAKE_SOURCE_DIR}/workspaces/${DEMO_WORKSPACE}") + # New workspace structure + set(USING_WORKSPACES TRUE) +else() + # Legacy flat structure + set(USING_WORKSPACES FALSE) +endif() +``` + +### Gradual Migration + +1. **Phase 1:** Create workspace structure alongside existing +2. **Phase 2:** Update build system to support both +3. **Phase 3:** Move files to workspaces +4. **Phase 4:** Remove legacy paths +5. **Phase 5:** Update all documentation + +**Timeline:** 1-2 weeks for full migration + +## Trade-offs + +### Pros +- Clear project organization +- Parallel demo development +- Clean resource sharing +- Better onboarding for new devs +- Scales to many demos + +### Cons +- Initial migration effort +- Build system complexity increases +- More directories to navigate +- Learning curve for contributors + +### Alternative Approaches + +**1. Monorepo with subprojects:** +- Separate CMake projects per demo +- More isolated but harder to share code + +**2. Configuration files only:** +- Keep flat structure, use config to select files +- Less clear, harder to understand ownership + +**3. Git branches per demo:** +- Each demo is a branch +- Conflicts when merging shared code + +**Chosen:** Workspace system (best balance) + +## Implementation Effort + +**Estimated time: 12-16 hours** + +- Phase 1: Create structure (2-3 hours) +- Phase 2: Build system (4-5 hours) +- Phase 3: Code updates (3-4 hours) +- Phase 4: Documentation (2-3 hours) +- Phase 5: Validation (1-2 hours) + +**Priority:** Medium (improves workflow, not blocking) + +## Success Criteria + +1. Both `main` and `test` workspaces build successfully +2. Switching workspaces requires only CMake flag change +3. Workspace-specific changes don't affect other workspaces +4. Common shader changes affect all workspaces +5. New workspace creation takes < 5 minutes +6. All tests pass for both workspaces +7. Documentation clear for new contributors + +## Related Files + +**New files:** +- `workspaces/main/workspace.cfg` - Main demo config +- `workspaces/test/workspace.cfg` - Test demo config +- `cmake/ParseWorkspace.cmake` - Config parser +- `scripts/workspace.sh` - Workspace CLI tool +- `templates/workspace.cfg` - New workspace template + +**Modified files:** +- `CMakeLists.txt` - Workspace support +- `tools/asset_packer.cc` - Multi-path resolution +- `src/gpu/effects/shader_composer.cc` - Multi-path includes +- `README.md` - Workspace documentation +- `doc/HOWTO.md` - Build commands +- `doc/CONTRIBUTING.md` - Workspace guidelines + +**Moved files:** +- `assets/demo.seq` → `workspaces/main/timeline.seq` +- `assets/music.track` → `workspaces/main/music.track` +- `assets/final/demo_assets.txt` → `workspaces/main/assets.txt` +- `assets/test_demo.*` → `workspaces/test/` +- `assets/shaders/math/` → `assets/common/shaders/math/` + +## Notes + +- This is a **structural refactor**, not a feature +- No runtime behavior changes +- Enables future scalability +- Makes project more professional +- Good foundation for demoscene releases (multiple demos per repo) |
