summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-09 10:34:01 +0100
committerskal <pascal.massimino@gmail.com>2026-02-09 10:34:01 +0100
commite50db1721f67a6c1b565b0ac151ba32fcae21cbf (patch)
tree306bf626fb0283e1c3b173eb6f3abf556679d1ec
parentdf39c7e3efa70376fac579b178c803eb319d517f (diff)
fix: Resolve WebGPU uniform buffer alignment issues (Task #74)
Fixed multiple WGSL/C++ struct alignment mismatches causing validation errors: Padding fixes: - fade_effect.cc: Changed EffectParams padding from vec3<f32> to _pad0/1/2 - theme_modulation_effect.cc: Same padding fix for EffectParams - Root cause: WGSL vec3<f32> has 16-byte alignment, creating 32-byte structs ODR violation fix: - demo_effects.h: Added includes for fade_effect.h, theme_modulation_effect.h - Removed incomplete forward declarations (88 bytes) conflicting with complete definitions (96 bytes), causing heap buffer overflow in make_shared Member shadowing cleanup: - Renamed Effect::uniforms_ shadowing members to descriptive names: - FadeEffect: uniforms_ -> common_uniforms_ - FlashEffect: uniforms_ -> flash_uniforms_ - ThemeModulationEffect: uniforms_ -> common_uniforms_ Results: - demo64k runs without crashes - 33/33 tests passing (100%) - Added Task #75: WGSL uniform validation tool handoff(Claude): Uniform buffer alignment debugged and fixed
-rw-r--r--TODO.md74
-rw-r--r--src/gpu/demo_effects.h20
-rw-r--r--src/gpu/effects/fade_effect.cc10
-rw-r--r--src/gpu/effects/fade_effect.h2
-rw-r--r--src/gpu/effects/flash_effect.cc6
-rw-r--r--src/gpu/effects/flash_effect.h2
-rw-r--r--src/gpu/effects/theme_modulation_effect.cc10
-rw-r--r--src/gpu/effects/theme_modulation_effect.h2
8 files changed, 95 insertions, 31 deletions
diff --git a/TODO.md b/TODO.md
index b936041..4b5819b 100644
--- a/TODO.md
+++ b/TODO.md
@@ -6,7 +6,13 @@ This file tracks prioritized tasks with detailed attack plans.
## Recently Completed (February 9, 2026)
-- [x] **Uniform Buffer Alignment (Task #74)**: Fixed WGSL struct alignment issues. Changed `vec3<f32>` padding to individual `f32` fields in circle_mask_compute.wgsl. Demo runs with 0 validation errors, 32/33 tests passing.
+- [x] **Uniform Buffer Alignment (Task #74)**: Fixed WGSL struct alignment issues across multiple shaders:
+ - `circle_mask_compute.wgsl`: Changed `_pad: vec3<f32>` to three `f32` fields
+ - `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, 33/33 tests passing (100%)
## Previously Completed (February 8, 2026)
@@ -37,6 +43,72 @@ This file tracks prioritized tasks with detailed attack plans.
---
+## Priority 1: WGSL Uniform Buffer Validation & Consolidation (Task #75)
+
+**Goal**: Prevent alignment bugs by consolidating uniform buffer patterns and creating automated validation.
+
+**Background**: Recent bugs (Task #74) revealed WGSL `vec3<f32>` alignment issues causing 16-byte padding where 12 bytes expected. Need systematic approach to prevent recurrence.
+
+**Attack Plan**:
+
+### Phase 1: Audit & Document (1-2 hours)
+- [ ] **1.1**: Audit all WGSL shaders for uniform struct definitions
+ - List all uniform structs, their sizes, and padding strategies
+ - Identify inconsistencies (vec3 padding vs individual f32 fields)
+ - Document in `doc/UNIFORM_BUFFER_GUIDELINES.md`
+- [ ] **1.2**: Audit C++ struct definitions (CommonPostProcessUniforms, etc.)
+ - Verify static_assert size checks exist for all uniform structs
+ - Check for missing size validation
+
+### Phase 2: Consolidation (2-3 hours)
+- [ ] **2.1**: Standardize on CommonUniforms pattern
+ - All post-process effects should use CommonPostProcessUniforms for binding 2
+ - Effect-specific params at binding 3 (16 or 32 bytes, properly padded)
+- [ ] **2.2**: Eliminate `vec3<f32>` in padding fields
+ - Replace all `_pad: vec3<f32>` with `_pad0/1/2: f32`
+ - Apply to: FadeEffect, ThemeModulationEffect, any other effects
+- [ ] **2.3**: Add C++ wrapper structs with static_assert
+ - Every WGSL uniform struct should have matching C++ struct
+ - All structs require `static_assert(sizeof(...) == EXPECTED_SIZE)`
+
+### Phase 3: Validation Tool (3-4 hours)
+- [ ] **3.1**: Create `tools/validate_uniforms.py`
+ - Parse WGSL shader files for uniform struct definitions
+ - Calculate expected size using WGSL alignment rules:
+ - `f32`: 4-byte aligned
+ - `vec2<f32>`: 8-byte aligned
+ - `vec3<f32>`: **16-byte aligned** (not 12!)
+ - `vec4<f32>`: 16-byte aligned
+ - Struct size: rounded to largest member alignment
+- [ ] **3.2**: Parse C++ headers for matching structs
+ - Extract `sizeof()` from static_assert statements
+ - Match WGSL struct names to C++ struct names
+- [ ] **3.3**: Report mismatches
+ - Exit non-zero if C++ size != WGSL size
+ - Print detailed alignment breakdown for debugging
+- [ ] **3.4**: Integrate into CI/build system
+ - Add CMake custom command to run validation
+ - Fail build if validation fails (development builds only)
+ - Add to `scripts/check_all.sh`
+
+### Phase 4: Documentation (1 hour)
+- [ ] **4.1**: Write `doc/UNIFORM_BUFFER_GUIDELINES.md`
+ - Explain WGSL alignment rules (with examples)
+ - Document standard patterns (CommonUniforms, effect params)
+ - Show correct padding techniques
+ - Add examples of common mistakes
+- [ ] **4.2**: Update CONTRIBUTING.md
+ - Add "Uniform Buffer Checklist" section
+ - Require validation tool passes before commit
+
+**Size Impact**: Negligible (consolidation may save 50-100 bytes)
+
+**Priority**: High (prevents entire class of subtle bugs)
+
+**Dependencies**: None
+
+---
+
## Priority 1: Spectral Brush Editor (Task #5) [IN PROGRESS]
**Goal:** Create a web-based tool for procedurally tracing audio spectrograms. Replaces large `.spec` binary assets with tiny procedural C++ code (50-100× compression).
diff --git a/src/gpu/demo_effects.h b/src/gpu/demo_effects.h
index 7ad8261..54bf657 100644
--- a/src/gpu/demo_effects.h
+++ b/src/gpu/demo_effects.h
@@ -7,10 +7,12 @@
#include "3d/scene.h"
#include "effect.h"
#include "gpu/effects/circle_mask_effect.h"
+#include "gpu/effects/fade_effect.h" // FadeEffect with full definition
#include "gpu/effects/flash_effect.h" // FlashEffect with params support
#include "gpu/effects/post_process_helper.h"
#include "gpu/effects/rotating_cube_effect.h"
#include "gpu/effects/shaders.h"
+#include "gpu/effects/theme_modulation_effect.h" // ThemeModulationEffect with full definition
#include "gpu/gpu.h"
#include "gpu/texture_manager.h"
#include "gpu/uniform_helper.h"
@@ -197,22 +199,8 @@ class FlashCubeEffect : public Effect {
float flash_intensity_;
};
-class ThemeModulationEffect : public PostProcessEffect {
- public:
- ThemeModulationEffect(const GpuContext& ctx);
- void render(WGPURenderPassEncoder pass, float time, float beat,
- float intensity, float aspect_ratio) override;
- void update_bind_group(WGPUTextureView input_view) override;
-};
-
-class FadeEffect : public PostProcessEffect {
- public:
- FadeEffect(const GpuContext& ctx);
- void render(WGPURenderPassEncoder pass, float time, float beat,
- float intensity, float aspect_ratio) override;
- void update_bind_group(WGPUTextureView input_view) override;
-};
-
+// ThemeModulationEffect now defined in gpu/effects/theme_modulation_effect.h (included above)
+// FadeEffect now defined in gpu/effects/fade_effect.h (included above)
// FlashEffect now defined in gpu/effects/flash_effect.h (included above)
// Auto-generated functions
diff --git a/src/gpu/effects/fade_effect.cc b/src/gpu/effects/fade_effect.cc
index c70177e..3efc583 100644
--- a/src/gpu/effects/fade_effect.cc
+++ b/src/gpu/effects/fade_effect.cc
@@ -24,7 +24,9 @@ FadeEffect::FadeEffect(const GpuContext& ctx) : PostProcessEffect(ctx) {
struct EffectParams {
fade_amount: f32,
- _pad: vec3<f32>,
+ _pad0: f32,
+ _pad1: f32,
+ _pad2: f32,
};
@group(0) @binding(0) var inputSampler: sampler;
@@ -55,14 +57,14 @@ FadeEffect::FadeEffect(const GpuContext& ctx) : PostProcessEffect(ctx) {
pipeline_ =
create_post_process_pipeline(ctx_.device, ctx_.format, shader_code);
- uniforms_.init(ctx_.device);
+ common_uniforms_.init(ctx_.device);
params_buffer_ = gpu_create_buffer(
ctx_.device, 16, WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst);
}
void FadeEffect::update_bind_group(WGPUTextureView input_view) {
pp_update_bind_group(ctx_.device, pipeline_, &bind_group_, input_view,
- uniforms_.get(), params_buffer_);
+ common_uniforms_.get(), params_buffer_);
}
void FadeEffect::render(WGPURenderPassEncoder pass, float time, float beat,
@@ -74,7 +76,7 @@ void FadeEffect::render(WGPURenderPassEncoder pass, float time, float beat,
.beat = beat,
.audio_intensity = intensity,
};
- uniforms_.update(ctx_.queue, u);
+ common_uniforms_.update(ctx_.queue, u);
// Example fade pattern: fade in at start, fade out at end
// Customize this based on your needs
diff --git a/src/gpu/effects/fade_effect.h b/src/gpu/effects/fade_effect.h
index 485cc01..22b8f76 100644
--- a/src/gpu/effects/fade_effect.h
+++ b/src/gpu/effects/fade_effect.h
@@ -16,6 +16,6 @@ class FadeEffect : public PostProcessEffect {
void update_bind_group(WGPUTextureView input_view) override;
private:
- UniformBuffer<CommonPostProcessUniforms> uniforms_;
+ UniformBuffer<CommonPostProcessUniforms> common_uniforms_;
GpuBuffer params_buffer_;
};
diff --git a/src/gpu/effects/flash_effect.cc b/src/gpu/effects/flash_effect.cc
index ccf0756..57c1d73 100644
--- a/src/gpu/effects/flash_effect.cc
+++ b/src/gpu/effects/flash_effect.cc
@@ -55,12 +55,12 @@ FlashEffect::FlashEffect(const GpuContext& ctx, const FlashEffectParams& params)
pipeline_ =
create_post_process_pipeline(ctx_.device, ctx_.format, shader_code);
- uniforms_.init(ctx_.device);
+ flash_uniforms_.init(ctx_.device);
}
void FlashEffect::update_bind_group(WGPUTextureView input_view) {
pp_update_bind_group(ctx_.device, pipeline_, &bind_group_, input_view,
- uniforms_.get(), {});
+ flash_uniforms_.get(), {});
}
void FlashEffect::render(WGPURenderPassEncoder pass, float time, float beat,
@@ -88,7 +88,7 @@ void FlashEffect::render(WGPURenderPassEncoder pass, float time, float beat,
._pad1 = {0.0f, 0.0f}, // Padding for vec3 alignment
.color = {r, g, b}, // Time-dependent, computed every frame
._pad2 = 0.0f};
- uniforms_.update(ctx_.queue, u);
+ flash_uniforms_.update(ctx_.queue, u);
wgpuRenderPassEncoderSetPipeline(pass, pipeline_);
wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr);
diff --git a/src/gpu/effects/flash_effect.h b/src/gpu/effects/flash_effect.h
index d7a438b..55bad7b 100644
--- a/src/gpu/effects/flash_effect.h
+++ b/src/gpu/effects/flash_effect.h
@@ -39,7 +39,7 @@ class FlashEffect : public PostProcessEffect {
private:
FlashEffectParams params_;
- UniformBuffer<FlashUniforms> uniforms_;
+ UniformBuffer<FlashUniforms> flash_uniforms_;
UniformBuffer<FlashEffectParams> params_buffer_;
float flash_intensity_ = 0.0f;
};
diff --git a/src/gpu/effects/theme_modulation_effect.cc b/src/gpu/effects/theme_modulation_effect.cc
index fc52b62..f9ae636 100644
--- a/src/gpu/effects/theme_modulation_effect.cc
+++ b/src/gpu/effects/theme_modulation_effect.cc
@@ -26,7 +26,9 @@ ThemeModulationEffect::ThemeModulationEffect(const GpuContext& ctx)
struct EffectParams {
theme_brightness: f32,
- _pad: vec3<f32>,
+ _pad0: f32,
+ _pad1: f32,
+ _pad2: f32,
};
@group(0) @binding(0) var inputSampler: sampler;
@@ -59,14 +61,14 @@ ThemeModulationEffect::ThemeModulationEffect(const GpuContext& ctx)
pipeline_ =
create_post_process_pipeline(ctx_.device, ctx_.format, shader_code);
- uniforms_.init(ctx_.device);
+ common_uniforms_.init(ctx_.device);
params_buffer_ = gpu_create_buffer(
ctx_.device, 16, WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst);
}
void ThemeModulationEffect::update_bind_group(WGPUTextureView input_view) {
pp_update_bind_group(ctx_.device, pipeline_, &bind_group_, input_view,
- uniforms_.get(), params_buffer_);
+ common_uniforms_.get(), params_buffer_);
}
void ThemeModulationEffect::render(WGPURenderPassEncoder pass, float time,
@@ -79,7 +81,7 @@ void ThemeModulationEffect::render(WGPURenderPassEncoder pass, float time,
.beat = beat,
.audio_intensity = intensity,
};
- uniforms_.update(ctx_.queue, u);
+ common_uniforms_.update(ctx_.queue, u);
// Alternate between bright and dark every 4 seconds (2 pattern changes)
// Music patterns change every 2 seconds at 120 BPM
diff --git a/src/gpu/effects/theme_modulation_effect.h b/src/gpu/effects/theme_modulation_effect.h
index b972509..107529b 100644
--- a/src/gpu/effects/theme_modulation_effect.h
+++ b/src/gpu/effects/theme_modulation_effect.h
@@ -16,6 +16,6 @@ class ThemeModulationEffect : public PostProcessEffect {
void update_bind_group(WGPUTextureView input_view) override;
private:
- UniformBuffer<CommonPostProcessUniforms> uniforms_;
+ UniformBuffer<CommonPostProcessUniforms> common_uniforms_;
GpuBuffer params_buffer_;
};