From 1b89a26a750cc32725564a0c5186a437f372fd93 Mon Sep 17 00:00:00 2001 From: skal Date: Sun, 8 Feb 2026 22:17:31 +0100 Subject: feat: Fix CircleMaskEffect and RotatingCubeEffect auxiliary texture masking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves critical shader composition and format mismatch issues, enabling the circle mask + rotating cube demonstration at 2.0s-4.0s in the timeline. **Key Fixes:** 1. **Shader Function Name** (masked_cube.wgsl): - Fixed call to `ray_box_intersection()` (was incorrectly `ray_box()`) - Updated return value handling to use `RayBounds` struct (`.hit`, `.t_entry`, `.t_exit`) - Removed unused `sky_tex` binding to match pipeline layout expectations (5 bindings → 4) 2. **Shader Lifetime Issue** (rotating_cube_effect.h/cc): - Added `std::string composed_shader_` member to persist shader source - Prevents use-after-free when WebGPU asynchronously parses shader code 3. **Format Mismatch** (circle_mask_effect.cc): - Changed compute pipeline format from hardcoded `RGBA8Unorm` to `ctx_.format` (Bgra8UnormSrgb) - Matches auxiliary texture format created by `MainSequence::register_auxiliary_texture()` - Added depth stencil state to render pipeline to match scene pass requirements: * Format: Depth24Plus * depthWriteEnabled: False (no depth writes needed) * depthCompare: Always (always pass depth test) 4. **WebGPU Descriptor Initialization** (circle_mask_effect.cc): - Added `depthSlice = WGPU_DEPTH_SLICE_UNDEFINED` for non-Windows builds - Ensures proper initialization of render pass color attachments 5. **Test Coverage** (test_demo_effects.cc): - Updated EXPECTED_SCENE_COUNT: 6 → 8 (added CircleMaskEffect, RotatingCubeEffect) - Marked both effects as requiring 3D pipeline setup (skipped in basic tests) **Technical Details:** - **Auxiliary Texture Flow**: CircleMaskEffect generates mask (1.0 inside, 0.0 outside) → RotatingCubeEffect samples mask to render only inside circle → GaussianBlurEffect post-processes - **Pipeline Format Matching**: All pipelines targeting same render pass must use matching formats: * Color: Bgra8UnormSrgb (system framebuffer format) * Depth: Depth24Plus (scene pass depth buffer) - **ShaderComposer Integration**: Relies on `InitShaderComposer()` (called in `gpu.cc:372`) registering snippets: `common_uniforms`, `math/sdf_utils`, `ray_box`, etc. **Effect Behavior:** - Runs from 2.0s to 4.0s in demo timeline - CircleMaskEffect (priority 0): Draws green outside circle, transparent inside - RotatingCubeEffect (priority 1): Renders bump-mapped cube inside circle - GaussianBlurEffect (priority 2): Post-process blur on entire composition **Test Results:** - All 33 tests pass (100%) - No WebGPU validation errors - Demo runs cleanly with `--seek 2.0` Co-Authored-By: Claude Sonnet 4.5 --- assets/demo.seq | 5 +++++ assets/final/demo_assets.txt | 3 +++ assets/final/shaders/masked_cube.wgsl | 9 ++++----- 3 files changed, 12 insertions(+), 5 deletions(-) (limited to 'assets') diff --git a/assets/demo.seq b/assets/demo.seq index 9c55fe5..0dfb108 100644 --- a/assets/demo.seq +++ b/assets/demo.seq @@ -29,6 +29,11 @@ SEQUENCE 0b 0 EFFECT + SolarizeEffect 0 4b # Priority 2 (was 3, now contiguous) EFFECT + VignetteEffect 0 6 radius=0.6 softness=0.1 +SEQUENCE 2.0 0 + EFFECT + CircleMaskEffect 0.0 2.0 0.35 # Priority 0 (mask generator, radius 0.35) + EFFECT + RotatingCubeEffect 0.0 2.0 # Priority 1 (renders inside circle) + EFFECT + GaussianBlurEffect 0.0 2.0 strength=2.0 # Priority 2 (post-process blur) + SEQUENCE 4b 0 EFFECT - FlashCubeEffect 0.1 3. # Priority -1 EFFECT + FlashEffect 0.0 0.2 # Priority 0 (was 4, now contiguous) diff --git a/assets/final/demo_assets.txt b/assets/final/demo_assets.txt index bf39c5d..05eee17 100644 --- a/assets/final/demo_assets.txt +++ b/assets/final/demo_assets.txt @@ -52,3 +52,6 @@ SHADER_MESH, NONE, shaders/mesh_render.wgsl, "Mesh Rasterization Shader" MESH_CUBE, NONE, test_mesh.obj, "A simple cube mesh" DODECAHEDRON, NONE, dodecahedron.obj, "A dodecahedron mesh" SHADER_VIGNETTE, NONE, shaders/vignette.wgsl, "Vignette Shader" +CIRCLE_MASK_COMPUTE_SHADER, NONE, shaders/circle_mask_compute.wgsl, "Circle mask compute shader" +CIRCLE_MASK_RENDER_SHADER, NONE, shaders/circle_mask_render.wgsl, "Circle mask render shader" +MASKED_CUBE_SHADER, NONE, shaders/masked_cube.wgsl, "Masked cube shader" diff --git a/assets/final/shaders/masked_cube.wgsl b/assets/final/shaders/masked_cube.wgsl index 77e2fb9..5e673a3 100644 --- a/assets/final/shaders/masked_cube.wgsl +++ b/assets/final/shaders/masked_cube.wgsl @@ -7,7 +7,6 @@ @group(0) @binding(1) var object_data: ObjectsBuffer; @group(0) @binding(3) var noise_tex: texture_2d; @group(0) @binding(4) var noise_sampler: sampler; -@group(0) @binding(5) var sky_tex: texture_2d; @group(1) @binding(0) var mask_tex: texture_2d; @group(1) @binding(1) var mask_sampler: sampler; @@ -89,13 +88,13 @@ fn fs_main(in: VertexOutput) -> FragmentOutput { let local_origin = (inv_model * vec4(ray_origin, 1.0)).xyz; let local_dir = normalize((inv_model * vec4(ray_dir, 0.0)).xyz); - let t = ray_box(local_origin, local_dir, vec3(-1.0), vec3(1.0)); - if (t.y < 0.0) { + let bounds = ray_box_intersection(local_origin, local_dir, vec3(1.0)); + if (!bounds.hit) { discard; } - let t_start = max(t.x, 0.0); - let t_end = t.y; + let t_start = bounds.t_entry; + let t_end = bounds.t_exit; var t_march = t_start; let max_steps = 128; -- cgit v1.2.3