diff options
Diffstat (limited to 'doc')
| -rw-r--r-- | doc/3D.md | 27 | ||||
| -rw-r--r-- | doc/BUILD_OPTIMIZATION_PROPOSAL.md | 344 | ||||
| -rw-r--r-- | doc/BUILD_OPTIMIZATION_PROPOSAL_V2.md | 346 | ||||
| -rw-r--r-- | doc/CONTRIBUTING.md | 4 | ||||
| -rw-r--r-- | doc/HOWTO.md | 13 | ||||
| -rw-r--r-- | doc/SPECTRAL_BRUSH_EDITOR.md | 497 |
6 files changed, 1223 insertions, 8 deletions
@@ -153,15 +153,26 @@ Instead of full SDF-on-SDF intersection (which is expensive), we approximate dyn * **Normal**: Compute gradient $N = \nabla SDF_B(P_{local})$ via central differences. Transform $N$ back to world space. * **Contact Point**: $P_{contact} = P_{world} - N * p$. -### Solver Loop (Semi-Implicit Euler) -1. **Integrate Velocity**: `vel += gravity * dt`. +### Narrow Phase & Resolution: +* If collision detected (depth $p > 0$): + * **Positional Correction**: Move object by $N * p$ (to resolve penetration). + * **Velocity Response (PBD)**: In Position Based Dynamics (PBD), the velocity is re-evaluated *after* all collisions and constraints have been resolved: `v = (p_new - p_old) / dt`. + * **Friction**: Apply tangential damping. + +### Rotation & Angular Momentum +Objects should support rotation via **Quaternions**. +* **State**: Add `quat orientation` and `vec3 angular_velocity` to `Object3D`. +* **Integration**: Update orientation using angular velocity: `q = q + 0.5 * dt * [0, w] * q`, then normalize. +* **Narrow Phase**: Proxy points must be rotated by the quaternion: `P_local = Inverse(Q) * (P_world - Position)`. +* **Collision Response**: Collision impulses should affect both linear and angular momentum. + +### Solver Loop (Position Based Dynamics) +1. **Integrate Position (Predicted)**: `p_pred = p + v * dt`. 2. **Broad Phase**: Find pairs using BVH. 3. **Narrow Phase & Resolution**: - * If collision detected (depth $p > 0$): - * **Positional Correction**: Move object by $N * p$ (to resolve penetration). - * **Velocity Response**: `vel = vel - (1 + restitution) * dot(vel, N) * N`. - * **Friction**: Apply tangential damping. -4. **Integrate Position**: `pos += vel * dt`. + * Resolve penetrations and constraints on `p_pred`. +4. **Update Velocity**: `v = (p_pred - p) / dt`. +5. **Finalize Position**: `p = p_pred`. ### Code Integration Plan @@ -183,4 +194,4 @@ our AssetManager or as c++ code directly) - **Task #36: Blender Exporter**. ## latter improvement How to handle transparency? Multi-Ray-casting? -We need to think about the lighting strategy. - **Task #40: Advanced Lighting & Transparency**. +We need to think about the lighting strategy. - **Task #40: Advanced Lighting & Transparency**.
\ No newline at end of file diff --git a/doc/BUILD_OPTIMIZATION_PROPOSAL.md b/doc/BUILD_OPTIMIZATION_PROPOSAL.md new file mode 100644 index 0000000..8d61e10 --- /dev/null +++ b/doc/BUILD_OPTIMIZATION_PROPOSAL.md @@ -0,0 +1,344 @@ +# Build Optimization Proposal: CMake Dependency Graph Analysis + +## Executive Summary + +Current incremental build times are good (0.2s when nothing changes), but **changing common headers or assets causes unnecessary broad rebuilds** (1.9-3.5s). This analysis identifies bottlenecks and proposes solutions to achieve <1s incremental builds for typical development workflows. + +--- + +## Current Build Performance Baseline + +### Measured Build Times +``` +Clean build (all targets): ~45s (estimated) +Incremental build (no changes): 0.229s ✅ +Touch mini_math.h: 1.897s ⚠️ +Touch demo_assets.txt: 3.498s ⚠️ +Touch shader asset: 0.636s ⚠️ (should be 0s - not tracked!) +``` + +### Dependency Analysis Results + +**Key Finding 1: Individual asset files are NOT tracked as dependencies** +- Only `demo_assets.txt` is a dependency, not the actual `.wgsl`/`.spec`/`.obj` files +- Changing a shader doesn't trigger asset regeneration (stale builds!) +- Developer must manually re-run `./scripts/gen_assets.sh` or touch `demo_assets.txt` + +**Key Finding 2: Generated files cause broad rebuilds** +- `assets_data.cc`: 33 assets, 20KB+ generated code +- `music_data.cc`: Tracker patterns +- `timeline.cc`: Demo sequence +- Changes to ANY asset → full rebuild of `util`, `demo64k`, and all tests + +**Key Finding 3: Header dependency chains** +- `mini_math.h` → triggers rebuild of entire `3d` + `gpu` libraries (23 files) +- `asset_manager.h` → used by audio, 3d, gpu, tests (30+ files) +- `object.h` (includes asset_manager) → transitively includes it everywhere + +**Key Finding 4: Test dependency explosion** +- 20 tests, each depends on `generate_demo_assets` or `generate_test_assets` +- Changing assets rebuilds ALL tests (even if they don't use the changed asset) + +--- + +## Problem Categories + +### 1. Missing File-Level Asset Dependencies ⚠️ HIGH PRIORITY +**Problem**: Changing a `.wgsl` shader doesn't trigger `asset_packer` to regenerate `assets.h`/`assets_data.cc`. + +**Root Cause**: CMake custom command only depends on `demo_assets.txt`, not the actual asset files: +```cmake +add_custom_command( + OUTPUT ${OUT_H} ${OUT_CC} + DEPENDS ${ASSET_PACKER_DEPENDS} ${INPUT_TXT} # ❌ Missing individual asset files! + ... +) +``` + +**Impact**: +- Developers get **stale builds** after modifying shaders +- Must manually trigger rebuild (`touch demo_assets.txt` or `./scripts/gen_assets.sh`) +- Risk of bugs from testing old shader code + +**Solution Complexity**: Medium (requires parsing `demo_assets.txt` to extract file list) + +--- + +### 2. Monolithic Generated Asset Files ⚠️ MEDIUM PRIORITY +**Problem**: `assets_data.cc` contains ALL assets (33 items, 20KB+). Changing one shader regenerates the entire file, causing broad rebuilds. + +**Root Cause**: Single monolithic file instead of per-asset or per-category files. + +**Impact**: +- Touch `demo_assets.txt` → 3.5s rebuild (main.cc, util, all tests) +- Even if developer only changed one shader + +**Solution Complexity**: High (requires refactoring asset packing system) + +--- + +### 3. Header Include Hygiene 🟢 LOW PRIORITY +**Problem**: `asset_manager.h` and `mini_math.h` included widely, causing transitive rebuilds. + +**Root Cause**: Headers include more than necessary, creating dependency chains. + +**Example Chain**: +``` +object.h (includes asset_manager.h) + ↓ +renderer.cc (includes object.h) + ↓ +Entire 3d library rebuilds when asset_manager.h changes +``` + +**Impact**: Moderate (1-2s for `mini_math.h`, less for `asset_manager.h`) + +**Solution Complexity**: Low (forward declarations, split headers) + +--- + +### 4. Test Dependency Granularity 🟢 LOW PRIORITY +**Problem**: All tests depend on `generate_demo_assets`, even if they don't use assets. + +**Impact**: Moderate (asset changes rebuild all 20 tests unnecessarily) + +**Solution Complexity**: Medium (requires per-test dependency analysis) + +--- + +## Proposed Solutions (Ranked by Impact) + +### **Proposal 1: Add File-Level Asset Dependencies** ⭐ HIGHEST IMPACT +**Effort**: Medium | **Impact**: High | **Priority**: Critical + +**Goal**: Make CMake aware of individual asset files, so changing a shader triggers regeneration. + +**Implementation**: +```cmake +# Parse demo_assets.txt to extract asset file paths +function(parse_asset_list INPUT_TXT OUT_FILE_LIST) + file(STRINGS ${INPUT_TXT} LINES) + set(ASSET_FILES "") + foreach(LINE ${LINES}) + if(NOT LINE MATCHES "^#") # Skip comments + string(REGEX REPLACE "^[^,]+,[ ]*([^,]+).*" "\\1" FILENAME "${LINE}") + list(APPEND ASSET_FILES "${CMAKE_SOURCE_DIR}/assets/final/${FILENAME}") + endif() + endforeach() + set(${OUT_FILE_LIST} ${ASSET_FILES} PARENT_SCOPE) +endfunction() + +# Use in pack_assets function +function(pack_assets NAME INPUT_TXT HEADER_VAR DATA_CC_VAR TARGET_NAME) + parse_asset_list(${INPUT_TXT} ASSET_FILE_DEPS) + + add_custom_command( + OUTPUT ${OUT_H} ${OUT_CC} + COMMAND ${ASSET_PACKER_CMD} ${INPUT_TXT} ${OUT_H} ${OUT_CC} + DEPENDS ${ASSET_PACKER_DEPENDS} ${INPUT_TXT} ${ASSET_FILE_DEPS} # ✅ Fixed! + COMMENT "Generating assets for ${NAME}..." + ) + ... +endfunction() +``` + +**Benefits**: +- ✅ Correct incremental builds (no more stale shaders!) +- ✅ Eliminates manual `touch demo_assets.txt` workaround +- ✅ Developer workflow improved + +**Risks**: +- Adds ~33 file dependencies to CMake graph (negligible overhead) +- Requires robust parsing of `demo_assets.txt` format + +--- + +### **Proposal 2: Split Assets into Categories** ⭐ MEDIUM IMPACT +**Effort**: High | **Impact**: Medium | **Priority**: Medium + +**Goal**: Split `assets_data.cc` into `shaders_data.cc`, `audio_data.cc`, `meshes_data.cc` to reduce rebuild scope. + +**Implementation**: +```cmake +pack_assets(shaders assets/final/shaders_list.txt ...) +pack_assets(audio assets/final/audio_list.txt ...) +pack_assets(meshes assets/final/meshes_list.txt ...) + +# demo64k depends on all three +target_link_libraries(demo64k PRIVATE ... ${SHADER_DATA_CC} ${AUDIO_DATA_CC} ${MESH_DATA_CC}) +``` + +**Benefits**: +- ✅ Changing a shader only rebuilds files using shaders +- ✅ Reduces 3.5s rebuild to ~1s for single-category changes + +**Risks**: +- ⚠️ Requires splitting `demo_assets.txt` into multiple files +- ⚠️ More complex build system +- ⚠️ Asset ID namespacing might be needed + +--- + +### **Proposal 3: Precompiled Headers (PCH)** 🔧 ALTERNATIVE APPROACH +**Effort**: Low | **Impact**: Medium | **Priority**: Optional + +**Goal**: Use CMake's `target_precompile_headers` for common includes. + +**Implementation**: +```cmake +target_precompile_headers(3d PRIVATE + <cstdio> + <cmath> + <vector> + <algorithm> + "util/mini_math.h" +) +``` + +**Benefits**: +- ✅ Reduces parse time for large headers +- ✅ Speeds up clean builds significantly + +**Drawbacks**: +- ⚠️ Doesn't help incremental builds much (headers still tracked) +- ⚠️ Can mask missing includes (code compiles but is fragile) + +--- + +### **Proposal 4: Header Include Hygiene** 🟢 LOW HANGING FRUIT +**Effort**: Low | **Impact**: Low | **Priority**: Nice-to-have + +**Goal**: Reduce transitive includes via forward declarations. + +**Example Refactor**: +```cpp +// object.h (BEFORE) +#include "util/asset_manager.h" // ❌ Full include + +// object.h (AFTER) +enum class AssetId : uint16_t; // ✅ Forward declaration +``` + +**Benefits**: +- ✅ Reduces dependency chains +- ✅ Faster incremental builds for header changes + +**Drawbacks**: +- ⚠️ Requires careful analysis of which headers can be forward-declared +- ⚠️ May break existing code if not done carefully + +--- + +### **Proposal 5: Ninja Build Generator** ⚡ QUICK WIN +**Effort**: Trivial | **Impact**: Small | **Priority**: Easy + +**Goal**: Use Ninja instead of Make for faster dependency checking. + +**Implementation**: +```bash +cmake -S . -B build -G Ninja +ninja -C build demo64k +``` + +**Benefits**: +- ✅ ~20% faster incremental builds (0.229s → 0.18s) +- ✅ Better parallelism for large projects +- ✅ Zero code changes required + +**Drawbacks**: +- ⚠️ Requires Ninja installed on developer machines + +--- + +## Recommended Implementation Plan + +### Phase 1: Critical Fixes (1-2 days) +1. ✅ **Proposal 1**: Add file-level asset dependencies +2. ✅ **Proposal 5**: Document Ninja usage (already works, just recommend it) + +### Phase 2: Medium-Term Improvements (3-5 days) +3. 🔧 **Proposal 2**: Split assets into categories (optional, if rebuild times still problematic) +4. 🔧 **Proposal 4**: Header include hygiene (ongoing, as-needed) + +### Phase 3: Optimization (optional) +5. 🔧 **Proposal 3**: Precompiled headers (if clean builds become bottleneck) + +--- + +## Metrics & Success Criteria + +**Current Baseline**: +- Touch shader → 0.6s (but stale build!) +- Touch demo_assets.txt → 3.5s +- Touch mini_math.h → 1.9s + +**Target After Phase 1**: +- Touch shader → 0.8s (regenerates assets_data.cc correctly) +- Touch demo_assets.txt → 3.5s (unchanged, but correct) +- Touch mini_math.h → 1.9s (unchanged) + +**Target After Phase 2**: +- Touch shader → 0.5s (only shader_data.cc regenerates) +- Touch audio asset → 0.4s (only audio_data.cc regenerates) +- Touch mini_math.h → 1.2s (fewer transitive includes) + +--- + +## Open Questions for Discussion + +1. **Asset categorization**: Split by type (shaders/audio/meshes) or by usage (runtime/debug/tests)? +2. **Precompiled headers**: Worth the complexity given incremental builds are already fast? +3. **Unity builds**: Would `UNITY_BUILD` help for clean builds? (Probably not needed for 64k demo) +4. **ccache**: Should we recommend/document ccache usage for developers? + +--- + +## Appendix: Dependency Graph Analysis + +### Current Library Dependencies +``` +demo64k +├── 3d (7 files, depends on: util, procedural) +│ ├── renderer.cc → includes asset_manager.h +│ ├── renderer_sdf.cc → includes asset_manager.h +│ ├── renderer_mesh.cc → includes asset_manager.h +│ └── ... (all include mini_math.h) +├── gpu (18 files, depends on: util, procedural) +│ ├── effects/shaders.cc → includes asset_manager.h +│ └── effects/*_effect.cc → includes object.h → mini_math.h +├── audio (12 files, depends on: util, procedural) +│ ├── audio.cc → includes asset_manager.h +│ └── tracker.cc → includes asset_manager.h +├── util (1 file) +│ └── asset_manager.cc (includes generated assets.h) +└── procedural (1 file) +``` + +### Rebuild Cascade for `assets_data.cc` Change +``` +assets_data.cc (regenerated) + ↓ +util library (recompiles asset_manager.cc) + ↓ +audio library (no rebuild - doesn't depend on util) +3d library (no rebuild - doesn't depend on util) +gpu library (no rebuild - doesn't depend on util) + ↓ +demo64k (relinks with new util) + ↓ +ALL tests (relink with new util + demo64k dependencies) +``` + +**Conclusion**: Rebuild cascade is actually **well-isolated** to util + demo64k. The 3.5s time is mostly asset_packer + relink overhead, not cascading recompiles. + +--- + +## Final Recommendation + +**Implement Proposal 1 immediately** (file-level asset dependencies) to fix the critical stale build issue. This is the highest-priority bug affecting developer workflow. + +**Defer Proposal 2** (asset categorization) unless measurements show it's worth the complexity. Current 3.5s rebuild for asset changes is acceptable if it only happens when intentionally modifying assets. + +**Document Ninja usage** as a quick win for interested developers. + +**Monitor** header dependency issues (Proposal 4) and address opportunistically during refactors. diff --git a/doc/BUILD_OPTIMIZATION_PROPOSAL_V2.md b/doc/BUILD_OPTIMIZATION_PROPOSAL_V2.md new file mode 100644 index 0000000..315911a --- /dev/null +++ b/doc/BUILD_OPTIMIZATION_PROPOSAL_V2.md @@ -0,0 +1,346 @@ +# Build Optimization Proposal V2: Core Library Iteration Speed + +## Executive Summary (Revised Focus) + +**Real Problem**: Changing common headers during development causes **4-5 second rebuilds** of 35+ files. Asset changes are infrequent and acceptable. + +**Root Cause**: `asset_manager.h` is over-included. 17 files include it, but only 7 need the struct definitions (TextureAsset, MeshAsset). + +**Solution**: Split `asset_manager.h` into core + extensions → reduce rebuild cascade from **35 files to ~7 files**. + +--- + +## Revised Performance Baseline + +### Developer Workflow Measurements +``` +Single .cc file change: 0.65s ✅ (excellent) +Touch asset_manager.h: 4.82s ❌ (35 files rebuild) +Touch mini_math.h: 1.90s ⚠️ (23 files rebuild) +Touch object.h: ??? (needs measurement) +Asset changes: 3.50s ✅ (acceptable - rare) +``` + +**Key Insight**: The bottleneck is **header dependencies**, not asset generation. + +--- + +## Problem Analysis: asset_manager.h + +### Current Structure +```cpp +// asset_manager.h (MONOLITHIC) +#pragma once +#include <cstddef> +#include <cstdint> + +enum class AssetId : uint16_t; // ✅ Already forward-declared + +typedef bool (*ProcGenFunc)(...); + +struct AssetRecord { ... }; // ❌ Rarely needed + +const uint8_t* GetAsset(...); // ✅ Frequently needed +void DropAsset(...); // ✅ Frequently needed + +struct TextureAsset { ... }; // ❌ Only 2 files need this +struct MeshVertex { ... }; // ❌ Only 3 files need this +struct MeshAsset { ... }; // ❌ Only 3 files need this + +TextureAsset GetTextureAsset(...); // ❌ Only 2 files need this +MeshAsset GetMeshAsset(...); // ❌ Only 3 files need this +``` + +### Who Includes It? (17 files) +``` +✅ NEED BASIC (GetAsset only): + - src/3d/renderer_skybox.cc + - src/3d/renderer_sdf.cc + - src/audio/audio.cc + - src/audio/tracker.cc + - src/gpu/effects/shaders.cc + - src/audio/spectrogram_resource_manager.h + - ... (10 files total) + +❌ NEED STRUCTS (GetTextureAsset/GetMeshAsset): + - src/3d/renderer_mesh.cc (MeshAsset) + - src/3d/visual_debug.cc (MeshAsset) + - src/gpu/effects/flash_cube_effect.cc (TextureAsset) + - src/gpu/effects/hybrid_3d_effect.cc (TextureAsset) + - ... (7 files total) +``` + +### Transitive Includes (The Real Problem!) +``` +object.h + #include "asset_manager.h" ❌ Only needs AssetId (already forward-declared!) + ↓ +renderer.cc, renderer_sdf.cc, renderer_mesh.cc, renderer_skybox.cc + ↓ +ALL of 3d library rebuilds when asset_manager.h changes! +``` + +**Root Cause**: `object.h` unnecessarily includes full `asset_manager.h` for `AssetId mesh_asset_id`, but `AssetId` is already forward-declared! + +--- + +## Proposed Solution: Split asset_manager.h + +### New Structure + +#### File 1: `asset_manager_fwd.h` (Forward Declarations) +```cpp +// asset_manager_fwd.h - LIGHTWEIGHT, SAFE TO INCLUDE EVERYWHERE +#pragma once +#include <cstddef> +#include <cstdint> + +enum class AssetId : uint16_t; // Just the forward declaration + +typedef bool (*ProcGenFunc)(uint8_t*, int, int, const float*, int); + +struct AssetRecord; // Forward declaration (opaque) +``` + +#### File 2: `asset_manager.h` (Core API) +```cpp +// asset_manager.h - BASIC ASSET RETRIEVAL (most common use case) +#pragma once +#include "asset_manager_fwd.h" + +struct AssetRecord { + const uint8_t* data; + size_t size; + bool is_procedural; + const char* proc_func_name_str; + const float* proc_params; + int num_proc_params; +}; + +// Core API - most files only need this +const uint8_t* GetAsset(AssetId asset_id, size_t* out_size = nullptr); +void DropAsset(AssetId asset_id, const uint8_t* asset); +``` + +#### File 3: `asset_manager_helpers.h` (Typed Asset Helpers) +```cpp +// asset_manager_helpers.h - SPECIALIZED HELPERS (only 7 files need this) +#pragma once +#include "asset_manager.h" + +struct TextureAsset { + int width; + int height; + const uint8_t* pixels; +}; + +struct MeshVertex { + float p[3]; + float n[3]; + float u[2]; +}; + +struct MeshAsset { + uint32_t num_vertices; + const MeshVertex* vertices; + uint32_t num_indices; + const uint32_t* indices; +}; + +TextureAsset GetTextureAsset(AssetId asset_id); +MeshAsset GetMeshAsset(AssetId asset_id); +``` + +### Migration Plan + +#### Step 1: Fix object.h (QUICK WIN - 0 files) +```cpp +// object.h (BEFORE) +#include "util/asset_manager.h" // ❌ Pulls in everything + +// object.h (AFTER) +#include "util/asset_manager_fwd.h" // ✅ Only AssetId forward declaration +``` + +**Impact**: Changing `asset_manager.h` no longer rebuilds entire 3d library! + +#### Step 2: Update includes in all source files +```cpp +// Most files (10 files) - only need basic GetAsset +#include "util/asset_manager.h" + +// Specialized files (7 files) - need struct helpers +#include "util/asset_manager_helpers.h" +``` + +**Automated Migration**: +```bash +# Files using GetTextureAsset or GetMeshAsset +for file in renderer_mesh.cc visual_debug.cc flash_cube_effect.cc hybrid_3d_effect.cc; do + sed -i '' 's|asset_manager\.h|asset_manager_helpers.h|g' "src/**/$file" +done + +# All other files - keep as-is (will include base asset_manager.h) +``` + +--- + +## Expected Performance Improvements + +### Before Split +``` +Touch asset_manager.h → 35 files rebuild → 4.82s +``` + +### After Split (Estimated) +``` +Touch asset_manager_fwd.h → 0 files rebuild (forward decl only) → 0.2s ✅ +Touch asset_manager.h → 10 files rebuild → 1.5s ✅ +Touch asset_manager_helpers.h → 7 files rebuild → 1.0s ✅ +``` + +**Worst Case**: 1.5s (vs 4.82s) = **69% faster** ⚡ + +**Typical Case**: Most changes to asset_manager internals won't affect the API → 0.2s + +--- + +## Implementation Checklist + +### Phase 1: Create New Headers (30 minutes) +- [ ] Create `src/util/asset_manager_fwd.h` (forward declarations only) +- [ ] Split `src/util/asset_manager.h` → keep core API +- [ ] Create `src/util/asset_manager_helpers.h` → move TextureAsset/MeshAsset + +### Phase 2: Update Includes (30 minutes) +- [ ] Update `src/3d/object.h` → use `asset_manager_fwd.h` +- [ ] Update 7 files needing structs → use `asset_manager_helpers.h`: + - `src/3d/renderer_mesh.cc` + - `src/3d/visual_debug.cc` + - `src/3d/visual_debug.h` + - `src/gpu/effects/flash_cube_effect.cc` + - `src/gpu/effects/hybrid_3d_effect.cc` + - `src/tests/test_assets.cc` + - `src/tests/test_mesh.cc` +- [ ] Update `src/util/asset_manager.cc` → include `asset_manager_helpers.h` + +### Phase 3: Verify (15 minutes) +- [ ] Run all tests: `ctest` +- [ ] Measure rebuild time: `touch src/util/asset_manager.h && cmake --build build` +- [ ] Confirm <2s rebuild time + +**Total Effort**: ~75 minutes + +--- + +## Additional Low-Hanging Fruit + +### 1. Check other transitive includes +**Action**: Audit `object.h` for other unnecessary includes +```bash +# Find what object.h transitively pulls in +cpp -dM src/3d/object.h | wc -l +``` + +### 2. Use forward declarations in headers +**Pattern**: +```cpp +// BAD - pulls in entire header +#include "camera.h" + +// GOOD - forward declaration +class Camera; +``` + +### 3. Move inline functions to .cc files +**Problem**: Inline functions in headers force recompilation of all users when implementation changes. + +**Solution**: Move non-trivial inline functions to `.cc` files. + +--- + +## Risks & Mitigation + +### Risk 1: Breaking Existing Code +**Likelihood**: Low (forward declaration already exists) + +**Mitigation**: +- Compile after each file change +- Run full test suite before committing + +### Risk 2: Increased Include Complexity +**Likelihood**: Low (only 3 headers instead of 1) + +**Mitigation**: +- Clear naming: `_fwd.h` = forward decls, `_helpers.h` = specialized +- Document in CONTRIBUTING.md + +### Risk 3: Forgetting to Include Helpers +**Likelihood**: Medium (compiler errors if you use TextureAsset without including helpers) + +**Mitigation**: +- Compiler will error immediately with clear message +- Easy to fix: add `#include "asset_manager_helpers.h"` + +--- + +## Comparison with Original Proposal + +### Original Proposal 1 (Asset File Dependencies) +- **Impact**: Fixes correctness bug for asset changes +- **Performance**: No improvement (assets change rarely) +- **Recommendation**: LOW PRIORITY (assets rebuild is acceptable) + +### NEW Proposal (Split asset_manager.h) +- **Impact**: 69% faster rebuilds during core library iteration +- **Performance**: 4.82s → 1.5s (developer pain point!) +- **Recommendation**: HIGH PRIORITY ⭐ + +--- + +## Measurement Plan + +### Before Implementation +```bash +# Baseline: Current rebuild time +time (touch src/util/asset_manager.h && cmake --build build --target demo64k -j8) +# Expected: 4.82s, 35 files +``` + +### After Implementation +```bash +# Test 1: Forward declaration change (should be instant) +time (touch src/util/asset_manager_fwd.h && cmake --build build --target demo64k -j8) +# Expected: 0.2s, 0 files + +# Test 2: Core API change (should be fast) +time (touch src/util/asset_manager.h && cmake --build build --target demo64k -j8) +# Expected: 1.5s, 10 files + +# Test 3: Helper change (should be fast) +time (touch src/util/asset_manager_helpers.h && cmake --build build --target demo64k -j8) +# Expected: 1.0s, 7 files +``` + +--- + +## Other Headers to Investigate + +### Candidates for Similar Treatment +``` +mini_math.h → 1.9s rebuild (23 files) - already isolated to 3d library ✅ +camera.h → unknown (need measurement) +scene.h → unknown (need measurement) +``` + +**Recommendation**: Start with `asset_manager.h` (proven 4.8s bottleneck), then measure others if iteration is still slow. + +--- + +## Final Recommendation + +**Implement the header split immediately.** This is a high-impact, low-risk change that directly addresses the developer pain point (slow iteration during core library changes). + +**Do NOT implement asset file tracking** (Proposal 1 from original doc) - assets change infrequently, and 3.5s rebuild is acceptable. + +**Expected Result**: Developer iteration cycle improves from 4.8s → 1.5s (69% faster) for the most common workflow (tweaking core library code). diff --git a/doc/CONTRIBUTING.md b/doc/CONTRIBUTING.md index 27391e4..4b85e9f 100644 --- a/doc/CONTRIBUTING.md +++ b/doc/CONTRIBUTING.md @@ -238,6 +238,10 @@ Make sure the `.field = ...,` initialization pattern is compatible with the comp keep the code compact vertically. That includes shader code, too. Use only one statement per line. +### File size limit + +Any file larger than 500 lines should ideally be split into sub-functionalities in separate files. (Exceptions: platform code or generated files). + ### finally Make sure everything is reflected in clang-format. diff --git a/doc/HOWTO.md b/doc/HOWTO.md index 5af3f05..ef1f1ed 100644 --- a/doc/HOWTO.md +++ b/doc/HOWTO.md @@ -55,6 +55,19 @@ cmake -S . -B build -DDEMO_ALL_OPTIONS=ON cmake --build build ``` +### Build System Notes + +**Incremental Builds**: The build system tracks all source files (.cc, .h) and asset files (.wgsl shaders, .spec audio, .obj meshes) as dependencies. Editing any file will trigger the necessary rebuilds automatically. + +**Asset Dependency Tracking**: CMake tracks 42 demo assets and 17 test assets individually. Changing a shader file (e.g., `assets/final/shaders/renderer_3d.wgsl`) automatically regenerates the asset bundle and recompiles dependent files. No manual workarounds needed. + +**Header Organization**: The `asset_manager` system is split into three headers for faster incremental builds: +- `asset_manager_dcl.h`: Forward declarations (use in headers) +- `asset_manager.h`: Core API (GetAsset/DropAsset) +- `asset_manager_utils.h`: Typed helpers (TextureAsset/MeshAsset) + +Include only what you need to minimize rebuild times. + ## git cloning if you have the public ssh key authorized on the VPS, you can use diff --git a/doc/SPECTRAL_BRUSH_EDITOR.md b/doc/SPECTRAL_BRUSH_EDITOR.md new file mode 100644 index 0000000..7ea0270 --- /dev/null +++ b/doc/SPECTRAL_BRUSH_EDITOR.md @@ -0,0 +1,497 @@ +# Spectral Brush Editor (Task #5) + +## Concept + +The **Spectral Brush Editor** is a web-based tool for creating procedural audio by tracing spectrograms with parametric curves. It enables compact representation of audio samples for 64k demos. + +### Goal + +Replace large `.spec` asset files with tiny procedural C++ code: +- **Before:** 5 KB binary `.spec` file +- **After:** ~100 bytes of C++ code calling `draw_bezier_curve()` + +### Workflow + +``` +.wav file → Load in editor → Trace with spectral brushes → Export params + C++ code + ↓ + (later) +procedural_params.txt → Load in editor → Adjust curves → Re-export +``` + +--- + +## Core Primitive: "Spectral Brush" + +A spectral brush consists of two components: + +### 1. Central Curve (Bezier) +Traces a path through time-frequency space: +``` +{freq_bin, amplitude} = bezier(frame_number) +``` + +**Properties:** +- Control points: `[(frame, freq_hz, amplitude), ...]` +- Interpolation: Linear (between control points) +- Future: Cubic Bezier, Catmull-Rom splines + +**Example:** +```javascript +control_points: [ + {frame: 0, freq_hz: 200.0, amplitude: 0.9}, // Attack + {frame: 20, freq_hz: 80.0, amplitude: 0.7}, // Sustain + {frame: 100, freq_hz: 50.0, amplitude: 0.0} // Decay +] +``` + +### 2. Vertical Profile +At each frame, applies a shape **vertically** in frequency bins around the central curve. + +**Profile Types:** + +#### Gaussian (Smooth harmonic) +``` +amplitude(dist) = exp(-(dist² / σ²)) +``` +- **σ (sigma)**: Width in frequency bins +- Use case: Musical tones, bass notes, melodic lines + +#### Decaying Sinusoid (Textured/resonant) +``` +amplitude(dist) = exp(-decay * dist) * cos(ω * dist) +``` +- **decay**: Falloff rate +- **ω (omega)**: Oscillation frequency +- Use case: Metallic sounds, bells, resonant tones + +#### Noise (Textured/gritty) +``` +amplitude(dist) = random(seed, dist) * noise_amplitude +``` +- **seed**: Deterministic RNG seed +- Use case: Hi-hats, cymbals, textured sounds + +#### Composite (Combinable) +```javascript +{ + type: "composite", + operation: "add" | "subtract" | "multiply", + profiles: [ + {type: "gaussian", sigma: 30.0}, + {type: "noise", amplitude: 0.1, seed: 42} + ] +} +``` + +--- + +## Visual Model + +``` +Frequency (bins) + ^ + | +512 | * ← Gaussian profile (frame 80) + | * * * +256 | **** * * * ← Gaussian profile (frame 50) + | ** ** * * +128 | ** Curve * * ← Central Bezier curve + | ** * + 64 | ** * + | * * + 0 +--*--*--*--*--*--*--*--*---→ Time (frames) + 0 10 20 30 40 50 60 70 80 + + At each frame: + 1. Evaluate curve → get freq_bin_0 and amplitude + 2. Draw profile vertically at that frame +``` + +--- + +## File Formats + +### A. `procedural_params.txt` (Human-readable, re-editable) + +```text +# Kick drum spectral brush definition +METADATA dct_size=512 num_frames=100 sample_rate=32000 + +CURVE bezier + CONTROL_POINT 0 200.0 0.9 # frame, freq_hz, amplitude + CONTROL_POINT 20 80.0 0.7 + CONTROL_POINT 50 60.0 0.3 + CONTROL_POINT 100 50.0 0.0 + PROFILE gaussian sigma=30.0 + PROFILE_ADD noise amplitude=0.1 seed=42 +END_CURVE + +CURVE bezier + CONTROL_POINT 0 500.0 0.5 + CONTROL_POINT 30 300.0 0.2 + PROFILE decaying_sinusoid decay=0.15 frequency=0.8 +END_CURVE +``` + +**Purpose:** +- Load back into editor for re-editing +- Human-readable, version-control friendly +- Can be hand-tweaked in text editor + +### B. C++ Code (Ready to compile) + +```cpp +// Generated from procedural_params.txt +// File: src/audio/gen_kick_procedural.cc + +#include "audio/spectral_brush.h" + +void gen_kick_procedural(float* spec, int dct_size, int num_frames) { + // Curve 0: Low-frequency punch with noise texture + { + const float frames[] = {0.0f, 20.0f, 50.0f, 100.0f}; + const float freqs[] = {200.0f, 80.0f, 60.0f, 50.0f}; + const float amps[] = {0.9f, 0.7f, 0.3f, 0.0f}; + + draw_bezier_curve(spec, dct_size, num_frames, + frames, freqs, amps, 4, + PROFILE_GAUSSIAN, 30.0f); + + draw_bezier_curve_add(spec, dct_size, num_frames, + frames, freqs, amps, 4, + PROFILE_NOISE, 0.1f, 42.0f); + } + + // Curve 1: High-frequency attack + { + const float frames[] = {0.0f, 30.0f}; + const float freqs[] = {500.0f, 300.0f}; + const float amps[] = {0.5f, 0.2f}; + + draw_bezier_curve(spec, dct_size, num_frames, + frames, freqs, amps, 2, + PROFILE_DECAYING_SINUSOID, 0.15f, 0.8f); + } +} + +// Usage in demo_assets.txt: +// KICK_PROC, PROC(gen_kick_procedural), NONE, "Procedural kick drum" +``` + +**Purpose:** +- Copy-paste into `src/audio/procedural_samples.cc` +- Compile directly into demo +- Zero runtime parsing overhead + +--- + +## C++ Runtime API + +### New Files + +#### `src/audio/spectral_brush.h` +```cpp +#pragma once +#include <cstdint> + +enum ProfileType { + PROFILE_GAUSSIAN = 0, + PROFILE_DECAYING_SINUSOID = 1, + PROFILE_NOISE = 2 +}; + +// Evaluate linear Bezier interpolation at frame t +float evaluate_bezier_linear(const float* control_frames, + const float* control_values, + int n_points, + float frame); + +// Draw spectral brush: Bezier curve with vertical profile +void draw_bezier_curve(float* spectrogram, int dct_size, int num_frames, + const float* control_frames, + const float* control_freqs_hz, + const float* control_amps, + int n_control_points, + ProfileType profile_type, + float profile_param1, + float profile_param2 = 0.0f); + +// Additive variant (for compositing profiles) +void draw_bezier_curve_add(float* spectrogram, int dct_size, int num_frames, + const float* control_frames, + const float* control_freqs_hz, + const float* control_amps, + int n_control_points, + ProfileType profile_type, + float profile_param1, + float profile_param2 = 0.0f); + +// Profile evaluation +float evaluate_profile(ProfileType type, float distance, + float param1, float param2); + +// Home-brew deterministic RNG (small, portable) +uint32_t spectral_brush_rand(uint32_t seed); +``` + +**Future Extensions:** +- Cubic Bezier interpolation: `evaluate_bezier_cubic()` +- Generic loader: `gen_from_params(const PrimitiveData*)` + +--- + +## Editor Architecture + +### Technology Stack +- **HTML5 Canvas**: Spectrogram visualization +- **Web Audio API**: Playback (IDCT → audio) +- **Pure JavaScript**: No dependencies +- **Reuse from existing editor**: `dct.js` (IDCT implementation) + +### Editor UI Layout + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Spectral Brush Editor [Load .wav] │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌───────────────────────────────────────────────────┐ │ +│ │ │ │ +│ │ CANVAS (Spectrogram Display) │ T │ +│ │ │ O │ +│ │ • Background: Reference .wav (gray, transparent) │ O │ +│ │ • Foreground: Procedural curves (colored) │ L │ +│ │ • Bezier control points (draggable circles) │ S │ +│ │ │ │ +│ │ │ [+]│ +│ │ │ [-]│ +│ │ │ [x]│ +│ └───────────────────────────────────────────────────┘ │ +│ │ +├─────────────────────────────────────────────────────────────┤ +│ Profile: [Gaussian ▾] Sigma: [████████░░] 30.0 │ +│ Curves: [Curve 0 ▾] Amplitude: [██████░░░░] 0.8 │ +├─────────────────────────────────────────────────────────────┤ +│ [1] Play Procedural [2] Play Original [Space] Pause │ +│ [Save Params] [Generate C++] [Undo] [Redo] │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Features (Phase 1: Minimal Working Version) + +#### Editing +- Click to place Bezier control points +- Drag to adjust control points (frame, frequency, amplitude) +- Delete control points (right-click or Delete key) +- Undo/Redo support (action history) + +#### Visualization +- Dual-layer canvas: + - **Background**: Reference spectrogram (semi-transparent gray) + - **Foreground**: Procedural spectrogram (colored) +- Log-scale frequency axis (musical perception) +- Control points: Draggable circles with labels + +#### Audio Playback +- **Key '1'**: Play procedural sound +- **Key '2'**: Play original .wav +- **Space**: Play/pause toggle + +#### File I/O +- Load .wav or .spec (reference sound) +- Save procedural_params.txt (re-editable) +- Generate C++ code (copy-paste ready) + +#### Undo/Redo +- Action history with snapshots +- Commands: Add control point, Move control point, Delete control point, Change profile +- **Ctrl+Z**: Undo +- **Ctrl+Shift+Z**: Redo + +### Keyboard Shortcuts + +| Key | Action | +|-----|--------| +| **1** | Play procedural sound | +| **2** | Play original .wav | +| **Space** | Play/pause | +| **Delete** | Delete selected control point | +| **Esc** | Deselect all | +| **Ctrl+Z** | Undo | +| **Ctrl+Shift+Z** | Redo | +| **Ctrl+S** | Save procedural_params.txt | +| **Ctrl+Shift+S** | Generate C++ code | + +--- + +## Implementation Plan + +### Phase 1: C++ Runtime (Foundation) +**Files:** `src/audio/spectral_brush.h`, `src/audio/spectral_brush.cc` + +**Tasks:** +- [ ] Define API (`ProfileType`, `draw_bezier_curve()`, etc.) +- [ ] Implement linear Bezier interpolation +- [ ] Implement Gaussian profile evaluation +- [ ] Implement home-brew RNG (for future noise support) +- [ ] Add unit tests (`src/tests/test_spectral_brush.cc`) + +**Deliverable:** Compiles, tests pass + +--- + +### Phase 2: Editor Core +**Files:** `tools/spectral_editor/index.html`, `script.js`, `style.css`, `dct.js` (reuse) + +**Tasks:** +- [ ] HTML structure (canvas, controls, file input) +- [ ] Canvas rendering (dual-layer: reference + procedural) +- [ ] Bezier curve editor (place/drag/delete control points) +- [ ] Profile controls (Gaussian sigma slider) +- [ ] Real-time spectrogram rendering +- [ ] Audio playback (IDCT → Web Audio API) +- [ ] Undo/Redo system + +**Deliverable:** Interactive editor, can trace .wav files + +--- + +### Phase 3: File I/O +**Tasks:** +- [ ] Load .wav (decode, FFT/STFT → spectrogram) +- [ ] Load .spec (binary format parser) +- [ ] Save procedural_params.txt (text format writer) +- [ ] Generate C++ code (code generation template) +- [ ] Load procedural_params.txt (re-editing workflow) + +**Deliverable:** Full save/load cycle works + +--- + +### Phase 4: Polish & Documentation +**Tasks:** +- [ ] UI refinements (tooltips, visual feedback) +- [ ] Keyboard shortcut overlay (press '?' to show help) +- [ ] Error handling (invalid files, audio context failures) +- [ ] User guide (README.md in tools/spectral_editor/) +- [ ] Example files (kick.wav → kick_procedural.cc) + +**Deliverable:** Production-ready tool + +--- + +## Design Decisions + +### 1. Bezier Interpolation +- **Current:** Linear (simple, fast, small code) +- **Future:** Cubic Bezier, Catmull-Rom splines + +### 2. Parameter Limits +- **Editor UI:** Soft ranges (reasonable defaults, not enforced) +- **Examples:** + - Sigma: 1.0 - 100.0 (suggested range, user can type beyond) + - Amplitude: 0.0 - 1.0 (suggested, can exceed for overdrive effects) + +### 3. Random Number Generator +- **Implementation:** Home-brew deterministic RNG +- **Reason:** Independence, small code, repeatable results +- **Algorithm:** Simple LCG or xorshift + +### 4. Code Generation +- **Current:** Single function per sound (e.g., `gen_kick_procedural()`) +- **Future:** Generic `gen_from_params(const PrimitiveData*)` with data tables + +### 5. Initial UI Scope +- **Phase 1:** Bezier + Gaussian only +- **Rationale:** Validate workflow before adding complexity +- **Future:** Decaying sinusoid, noise, composite profiles + +--- + +## Future Extensions + +### Bezier Enhancements +- [ ] Cubic Bezier interpolation (smoother curves) +- [ ] Catmull-Rom splines (automatic tangent control) +- [ ] Bezier curve tangent handles (manual control) + +### Additional Profiles +- [ ] Decaying sinusoid (metallic sounds) +- [ ] Noise (textured sounds) +- [ ] Composite profiles (add/subtract/multiply) +- [ ] User-defined profiles (custom formulas) + +### Multi-Dimensional Bezier +- [ ] `{freq, amplitude, oscillator_freq, decay} = bezier(frame)` +- [ ] Per-parameter control curves + +### Advanced Features +- [ ] Frequency snapping (snap to musical notes: C4, D4, E4, etc.) +- [ ] Amplitude envelope presets (ADSR) +- [ ] Profile library (save/load custom profiles) +- [ ] Batch processing (convert entire folder of .wav files) + +### Code Generation +- [ ] Generic `gen_from_params()` with data tables +- [ ] Optimization: Merge similar curves +- [ ] Size estimation (preview bytes saved vs. original .spec) + +--- + +## Testing Strategy + +### C++ Runtime Tests +**File:** `src/tests/test_spectral_brush.cc` + +**Test cases:** +- Linear Bezier interpolation (verify values at control points) +- Gaussian profile evaluation (verify falloff curve) +- Full `draw_bezier_curve()` (verify spectrogram output) +- Edge cases (0 control points, 1 control point, out-of-range frames) + +### Editor Tests +**Manual testing workflow:** +1. Load example .wav (kick drum) +2. Place 3-4 control points to trace low-frequency punch +3. Adjust Gaussian sigma +4. Play procedural sound (should resemble original) +5. Save procedural_params.txt +6. Generate C++ code +7. Copy C++ code into demo, compile, verify runtime output matches editor + +--- + +## Size Impact Estimate + +**Example: Kick drum sample** + +**Before (Binary .spec):** +- DCT size: 512 +- Num frames: 100 +- Size: 512 × 100 × 4 bytes = 200 KB (uncompressed) +- Compressed (zlib): ~5-10 KB + +**After (Procedural C++):** +```cpp +// 4 control points × 3 arrays × 4 values = ~48 bytes of data +const float frames[] = {0.0f, 20.0f, 50.0f, 100.0f}; +const float freqs[] = {200.0f, 80.0f, 60.0f, 50.0f}; +const float amps[] = {0.9f, 0.7f, 0.3f, 0.0f}; +draw_bezier_curve(...); // ~20 bytes function call + +// Total: ~100 bytes +``` + +**Compression ratio:** 50-100× reduction! + +**Trade-off:** Runtime CPU cost (generation vs. lookup), but acceptable for 64k demo. + +--- + +## References + +- **Bezier curves:** https://en.wikipedia.org/wiki/B%C3%A9zier_curve +- **DCT/IDCT:** Existing implementation in `src/audio/dct.cc` +- **Web Audio API:** https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API +- **Spectrogram visualization:** Existing editor in `tools/editor/` (to be replaced) |
