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/3d/renderer_pipelines.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/3d/renderer_pipelines.cc')
| -rw-r--r-- | src/3d/renderer_pipelines.cc | 85 |
1 files changed, 74 insertions, 11 deletions
diff --git a/src/3d/renderer_pipelines.cc b/src/3d/renderer_pipelines.cc index 1fdf30d..54499af 100644 --- a/src/3d/renderer_pipelines.cc +++ b/src/3d/renderer_pipelines.cc @@ -44,29 +44,43 @@ WGPURenderPipeline Renderer3D::create_pipeline_impl(bool use_bvh) { bgl_entries.push_back( {.binding = 0, // B0: uniforms .visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment, - .buffer = {.type = WGPUBufferBindingType_Uniform}}); + .buffer = {.type = WGPUBufferBindingType_Uniform, + .minBindingSize = sizeof(GlobalUniforms)}, + .sampler = {}, + .texture = {}}); bgl_entries.push_back( {.binding = 1, // B1: object storage .visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment, - .buffer = {.type = WGPUBufferBindingType_ReadOnlyStorage}}); + .buffer = {.type = WGPUBufferBindingType_ReadOnlyStorage, + .minBindingSize = sizeof(ObjectData) * kMaxObjects}, + .sampler = {}, + .texture = {}}); if (use_bvh) { bgl_entries.push_back( {.binding = 2, // B2: bvh storage .visibility = WGPUShaderStage_Fragment, - .buffer = {.type = WGPUBufferBindingType_ReadOnlyStorage}}); + .buffer = {.type = WGPUBufferBindingType_ReadOnlyStorage, + .minBindingSize = sizeof(BVHNode) * kMaxObjects * 2}, + .sampler = {}, + .texture = {}}); } bgl_entries.push_back( {.binding = 3, // B3: noise texture .visibility = WGPUShaderStage_Fragment, + .buffer = {}, + .sampler = {}, .texture = {.sampleType = WGPUTextureSampleType_Float, .viewDimension = WGPUTextureViewDimension_2D}}); - bgl_entries.push_back( - {.binding = 4, // B4: default sampler - .visibility = WGPUShaderStage_Fragment, - .sampler = {.type = WGPUSamplerBindingType_Filtering}}); + bgl_entries.push_back({.binding = 4, // B4: default sampler + .visibility = WGPUShaderStage_Fragment, + .buffer = {}, + .sampler = {.type = WGPUSamplerBindingType_Filtering}, + .texture = {}}); bgl_entries.push_back( {.binding = 5, // B5: sky texture .visibility = WGPUShaderStage_Fragment, + .buffer = {}, + .sampler = {}, .texture = {.sampleType = WGPUTextureSampleType_Float, .viewDimension = WGPUTextureViewDimension_2D}}); @@ -140,13 +154,61 @@ void Renderer3D::create_mesh_pipeline() { WGPUShaderModule shader_module = wgpuDeviceCreateShaderModule(device_, &shader_desc); - WGPUBindGroupLayout bgl = wgpuRenderPipelineGetBindGroupLayout(pipeline_, 0); + // BIND GROUP LAYOUT (matches create_pipeline_impl) + std::vector<WGPUBindGroupLayoutEntry> bgl_entries; + bgl_entries.push_back( + {.binding = 0, // B0: uniforms + .visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment, + .buffer = {.type = WGPUBufferBindingType_Uniform, + .minBindingSize = sizeof(GlobalUniforms)}, + .sampler = {}, + .texture = {}}); + bgl_entries.push_back( + {.binding = 1, // B1: object storage + .visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment, + .buffer = {.type = WGPUBufferBindingType_ReadOnlyStorage, + .minBindingSize = sizeof(ObjectData) * kMaxObjects}, + .sampler = {}, + .texture = {}}); + if (bvh_enabled_) { + bgl_entries.push_back( + {.binding = 2, // B2: bvh storage + .visibility = WGPUShaderStage_Fragment, + .buffer = {.type = WGPUBufferBindingType_ReadOnlyStorage, + .minBindingSize = sizeof(BVHNode) * kMaxObjects * 2}}); + } + bgl_entries.push_back( + {.binding = 3, // B3: noise texture + .visibility = WGPUShaderStage_Fragment, + .buffer = {}, + .sampler = {}, + .texture = {.sampleType = WGPUTextureSampleType_Float, + .viewDimension = WGPUTextureViewDimension_2D}}); + bgl_entries.push_back({.binding = 4, // B4: default sampler + .visibility = WGPUShaderStage_Fragment, + .buffer = {}, + .sampler = {.type = WGPUSamplerBindingType_Filtering}, + .texture = {}}); + bgl_entries.push_back( + {.binding = 5, // B5: sky texture + .visibility = WGPUShaderStage_Fragment, + .buffer = {}, + .sampler = {}, + .texture = {.sampleType = WGPUTextureSampleType_Float, + .viewDimension = WGPUTextureViewDimension_2D}}); + + WGPUBindGroupLayoutDescriptor bgl_desc = {}; + bgl_desc.entryCount = bgl_entries.size(); + bgl_desc.entries = bgl_entries.data(); + WGPUBindGroupLayout bind_group_layout = + wgpuDeviceCreateBindGroupLayout(device_, &bgl_desc); + WGPUPipelineLayoutDescriptor pl_desc = {}; pl_desc.bindGroupLayoutCount = 1; - pl_desc.bindGroupLayouts = &bgl; + pl_desc.bindGroupLayouts = &bind_group_layout; WGPUPipelineLayout pipeline_layout = wgpuDeviceCreatePipelineLayout(device_, &pl_desc); - wgpuBindGroupLayoutRelease(bgl); + wgpuBindGroupLayoutRelease(bind_group_layout); WGPUDepthStencilState depth_stencil = {}; depth_stencil.format = WGPUTextureFormat_Depth24Plus; @@ -223,7 +285,8 @@ void Renderer3D::create_skybox_pipeline() { .sampler = {.type = WGPUSamplerBindingType_Filtering}}; bgl_entries[2] = {.binding = 2, .visibility = WGPUShaderStage_Fragment, - .buffer = {.type = WGPUBufferBindingType_Uniform}}; + .buffer = {.type = WGPUBufferBindingType_Uniform, + .minBindingSize = sizeof(GlobalUniforms)}}; WGPUBindGroupLayoutDescriptor bgl_desc = {}; bgl_desc.entryCount = 3; |
