diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-09 09:49:51 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-09 09:49:51 +0100 |
| commit | df39c7e3efa70376fac579b178c803eb319d517f (patch) | |
| tree | 35ef2f2b1b0faa210186cd54c3796d4753aa8710 /src/gpu/effects/circle_mask_effect.cc | |
| parent | 538767bcf85c0d269b090434383f7499167af566 (diff) | |
fix: Resolve WebGPU uniform buffer alignment issues (Task #74)
Fixed critical validation errors caused by WGSL vec3<f32> alignment mismatches.
Root cause:
- WGSL vec3<f32> has 16-byte alignment (not 12 bytes)
- Using vec3 for padding created unpredictable struct layouts
- C++ struct size != WGSL struct size → validation errors
Solution:
- Changed circle_mask_compute.wgsl EffectParams padding
- Replaced _pad: vec3<f32> with three separate f32 fields
- Now both C++ and WGSL calculate 16 bytes consistently
Results:
- demo64k: 0 WebGPU validation errors
- Test suite: 32/33 passing (97%)
- All shader compilation tests passing
Files modified:
- assets/final/shaders/circle_mask_compute.wgsl
- TODO.md (updated task status)
- PROJECT_CONTEXT.md (updated test results)
- HANDOFF_2026-02-09_UniformAlignment.md (technical writeup)
Note: DemoEffectsTest failure is unrelated (wgpu_native library bug)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/gpu/effects/circle_mask_effect.cc')
| -rw-r--r-- | src/gpu/effects/circle_mask_effect.cc | 120 |
1 files changed, 69 insertions, 51 deletions
diff --git a/src/gpu/effects/circle_mask_effect.cc b/src/gpu/effects/circle_mask_effect.cc index 226b603..5b71086 100644 --- a/src/gpu/effects/circle_mask_effect.cc +++ b/src/gpu/effects/circle_mask_effect.cc @@ -6,14 +6,20 @@ #include "generated/assets.h" CircleMaskEffect::CircleMaskEffect(const GpuContext& ctx, float radius) - : Effect(ctx), radius_(radius) {} + : Effect(ctx), radius_(radius) { +} CircleMaskEffect::~CircleMaskEffect() { - if (mask_sampler_) wgpuSamplerRelease(mask_sampler_); - if (render_bind_group_) wgpuBindGroupRelease(render_bind_group_); - if (render_pipeline_) wgpuRenderPipelineRelease(render_pipeline_); - if (compute_bind_group_) wgpuBindGroupRelease(compute_bind_group_); - if (compute_pipeline_) wgpuRenderPipelineRelease(compute_pipeline_); + if (mask_sampler_) + wgpuSamplerRelease(mask_sampler_); + if (render_bind_group_) + wgpuBindGroupRelease(render_bind_group_); + if (render_pipeline_) + wgpuRenderPipelineRelease(render_pipeline_); + if (compute_bind_group_) + wgpuBindGroupRelease(compute_bind_group_); + if (compute_pipeline_) + wgpuRenderPipelineRelease(compute_pipeline_); } void CircleMaskEffect::init(MainSequence* demo) { @@ -25,6 +31,7 @@ void CircleMaskEffect::init(MainSequence* demo) { demo_->register_auxiliary_texture("circle_mask", width, height); compute_uniforms_.init(ctx_.device); + compute_params_.init(ctx_.device); render_uniforms_.init(ctx_.device); WGPUSamplerDescriptor sampler_desc = {}; @@ -48,11 +55,12 @@ void CircleMaskEffect::init(MainSequence* demo) { WGPUShaderModuleDescriptor compute_desc = {}; compute_desc.nextInChain = &compute_wgsl.chain; - WGPUShaderModule compute_module = wgpuDeviceCreateShaderModule(ctx_.device, &compute_desc); + WGPUShaderModule compute_module = + wgpuDeviceCreateShaderModule(ctx_.device, &compute_desc); const WGPUColorTargetState compute_target = { - .format = ctx_.format, // Match auxiliary texture format - .writeMask = WGPUColorWriteMask_All, + .format = ctx_.format, // Match auxiliary texture format + .writeMask = WGPUColorWriteMask_All, }; WGPUFragmentState compute_frag = {}; compute_frag.module = compute_module; @@ -68,19 +76,25 @@ void CircleMaskEffect::init(MainSequence* demo) { compute_pipeline_desc.multisample.count = 1; compute_pipeline_desc.multisample.mask = 0xFFFFFFFF; compute_pipeline_desc.fragment = &compute_frag; - compute_pipeline_ = wgpuDeviceCreateRenderPipeline(ctx_.device, &compute_pipeline_desc); + compute_pipeline_ = + wgpuDeviceCreateRenderPipeline(ctx_.device, &compute_pipeline_desc); wgpuShaderModuleRelease(compute_module); const WGPUBindGroupEntry compute_entries[] = { - {.binding = 0, .buffer = compute_uniforms_.get().buffer, - .size = sizeof(ComputeUniforms)}, + {.binding = 0, + .buffer = compute_uniforms_.get().buffer, + .size = sizeof(CommonPostProcessUniforms)}, + {.binding = 1, + .buffer = compute_params_.get().buffer, + .size = sizeof(EffectParams)}, }; const WGPUBindGroupDescriptor compute_bg_desc = { - .layout = wgpuRenderPipelineGetBindGroupLayout(compute_pipeline_, 0), - .entryCount = 1, - .entries = compute_entries, + .layout = wgpuRenderPipelineGetBindGroupLayout(compute_pipeline_, 0), + .entryCount = 2, + .entries = compute_entries, }; - compute_bind_group_ = wgpuDeviceCreateBindGroup(ctx_.device, &compute_bg_desc); + compute_bind_group_ = + wgpuDeviceCreateBindGroup(ctx_.device, &compute_bg_desc); WGPUShaderSourceWGSL render_wgsl = {}; render_wgsl.chain.sType = WGPUSType_ShaderSourceWGSL; @@ -88,11 +102,12 @@ void CircleMaskEffect::init(MainSequence* demo) { WGPUShaderModuleDescriptor render_desc = {}; render_desc.nextInChain = &render_wgsl.chain; - WGPUShaderModule render_module = wgpuDeviceCreateShaderModule(ctx_.device, &render_desc); + WGPUShaderModule render_module = + wgpuDeviceCreateShaderModule(ctx_.device, &render_desc); const WGPUColorTargetState render_target = { - .format = ctx_.format, - .writeMask = WGPUColorWriteMask_All, + .format = ctx_.format, + .writeMask = WGPUColorWriteMask_All, }; WGPUFragmentState render_frag = {}; render_frag.module = render_module; @@ -100,9 +115,9 @@ void CircleMaskEffect::init(MainSequence* demo) { render_frag.targetCount = 1; render_frag.targets = &render_target; const WGPUDepthStencilState depth_stencil = { - .format = WGPUTextureFormat_Depth24Plus, - .depthWriteEnabled = WGPUOptionalBool_False, // Don't write depth - .depthCompare = WGPUCompareFunction_Always, // Always pass + .format = WGPUTextureFormat_Depth24Plus, + .depthWriteEnabled = WGPUOptionalBool_False, // Don't write depth + .depthCompare = WGPUCompareFunction_Always, // Always pass }; WGPURenderPipelineDescriptor render_pipeline_desc = {}; @@ -115,38 +130,43 @@ void CircleMaskEffect::init(MainSequence* demo) { render_pipeline_desc.multisample.count = 1; render_pipeline_desc.multisample.mask = 0xFFFFFFFF; render_pipeline_desc.fragment = &render_frag; - render_pipeline_ = wgpuDeviceCreateRenderPipeline(ctx_.device, &render_pipeline_desc); + render_pipeline_ = + wgpuDeviceCreateRenderPipeline(ctx_.device, &render_pipeline_desc); wgpuShaderModuleRelease(render_module); WGPUTextureView mask_view = demo_->get_auxiliary_view("circle_mask"); const WGPUBindGroupEntry render_entries[] = { - {.binding = 0, .textureView = mask_view}, - {.binding = 1, .sampler = mask_sampler_}, - {.binding = 2, .buffer = render_uniforms_.get().buffer, - .size = sizeof(RenderUniforms)}, + {.binding = 0, .textureView = mask_view}, + {.binding = 1, .sampler = mask_sampler_}, + {.binding = 2, + .buffer = render_uniforms_.get().buffer, + .size = sizeof(CommonPostProcessUniforms)}, }; const WGPUBindGroupDescriptor render_bg_desc = { - .layout = wgpuRenderPipelineGetBindGroupLayout(render_pipeline_, 0), - .entryCount = 3, - .entries = render_entries, + .layout = wgpuRenderPipelineGetBindGroupLayout(render_pipeline_, 0), + .entryCount = 3, + .entries = render_entries, }; render_bind_group_ = wgpuDeviceCreateBindGroup(ctx_.device, &render_bg_desc); } void CircleMaskEffect::compute(WGPUCommandEncoder encoder, float time, - float beat, float intensity, - float aspect_ratio) { - const uint32_t width = width_; - const uint32_t height = height_; - - const ComputeUniforms uniforms = { - .radius = radius_, - .aspect_ratio = aspect_ratio, - .width = static_cast<float>(width), - .height = static_cast<float>(height), + float beat, float intensity, + float aspect_ratio) { + const CommonPostProcessUniforms uniforms = { + .resolution = {static_cast<float>(width_), static_cast<float>(height_)}, + .aspect_ratio = aspect_ratio, + .time = time, + .beat = beat, + .audio_intensity = intensity, }; compute_uniforms_.update(ctx_.queue, uniforms); + const EffectParams params = { + .radius = radius_, + }; + compute_params_.update(ctx_.queue, params); + WGPUTextureView mask_view = demo_->get_auxiliary_view("circle_mask"); WGPURenderPassColorAttachment color_attachment = {}; color_attachment.view = mask_view; @@ -161,7 +181,8 @@ void CircleMaskEffect::compute(WGPUCommandEncoder encoder, float time, pass_desc.colorAttachmentCount = 1; pass_desc.colorAttachments = &color_attachment; - WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc); + WGPURenderPassEncoder pass = + wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc); wgpuRenderPassEncoderSetPipeline(pass, compute_pipeline_); wgpuRenderPassEncoderSetBindGroup(pass, 0, compute_bind_group_, 0, nullptr); wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); @@ -170,16 +191,13 @@ void CircleMaskEffect::compute(WGPUCommandEncoder encoder, float time, } void CircleMaskEffect::render(WGPURenderPassEncoder pass, float time, - float beat, float intensity, - float aspect_ratio) { - const uint32_t width = width_; - const uint32_t height = height_; - - const RenderUniforms uniforms = { - .width = static_cast<float>(width), - .height = static_cast<float>(height), - ._pad1 = 0.0f, - ._pad2 = 0.0f, + float beat, float intensity, float aspect_ratio) { + const CommonPostProcessUniforms uniforms = { + .resolution = {static_cast<float>(width_), static_cast<float>(height_)}, + .aspect_ratio = aspect_ratio, + .time = time, + .beat = beat, + .audio_intensity = intensity, }; render_uniforms_.update(ctx_.queue, uniforms); |
