Skip to main content

cvkg_render_gpu/passes/
volumetric.rs

1use crate::kvasir::node::{ExecutionContext, KvasirNode};
2use crate::kvasir::nodes::{PassId, RES_SCENE};
3
4/// Volumetric pass node.
5/// Renders a fullscreen triangle with SDF raymarching for fog/light shaft effects.
6/// Uses scene-aware uniforms (time, resolution, light position) for animated output.
7/// Writes directly to the scene texture with additive blending.
8/// Now reads hologram instance data from the renderer to constrain rendering
9/// to the hologram bounding rect and add per-hologram variation.
10pub struct VolumetricNode {
11    pub inputs: Vec<crate::kvasir::resource::ResourceId>,
12    pub outputs: Vec<crate::kvasir::resource::ResourceId>,
13}
14
15impl VolumetricNode {
16    pub fn new() -> Self {
17        Self {
18            inputs: vec![RES_SCENE],
19            outputs: vec![RES_SCENE],
20        }
21    }
22}
23
24impl Default for VolumetricNode {
25    fn default() -> Self {
26        Self::new()
27    }
28}
29
30impl KvasirNode for VolumetricNode {
31    fn label(&self) -> &'static str {
32        "Volumetric"
33    }
34
35    fn inputs(&self) -> &[crate::kvasir::resource::ResourceId] {
36        &self.inputs
37    }
38
39    fn outputs(&self) -> &[crate::kvasir::resource::ResourceId] {
40        &self.outputs
41    }
42
43    fn pass_id(&self) -> PassId {
44        PassId::Volumetric
45    }
46
47    fn execute(&self, ctx: &mut ExecutionContext) {
48        // Get scene view for writing
49        let scene_view = match ctx.registry.get_texture_view(RES_SCENE) {
50            Some(v) => v,
51            None => {
52                tracing::error!("[GPU] Volumetric: missing scene texture view");
53                return;
54            }
55        };
56
57        // Write volumetric uniforms from scene state
58        let current_time = ctx.renderer.current_time();
59        let resolution = [
60            ctx.renderer.current_width() as f32,
61            ctx.renderer.current_height() as f32,
62        ];
63        // Default light position (top-right, elevated)
64        let light_pos = [0.5_f32, 0.3, 2.0];
65        let light_color = [0.8_f32, 0.85, 1.0]; // Cool white light
66
67        // Pack hologram instance data into extended uniform buffer.
68        let instances = ctx.renderer.hologram_instances();
69        let holo = instances.first(); // Primary hologram (single-instance fast path)
70        let holo_rect_x = holo.map_or(0.0, |h| h.rect.x);
71        let holo_rect_y = holo.map_or(0.0, |h| h.rect.y);
72        let holo_rect_w = holo.map_or(0.0, |h| h.rect.width);
73        let holo_rect_h = holo.map_or(0.0, |h| h.rect.height);
74        let holo_id_hash = holo.map_or(0.0f32, |h| h.id_hash as f32);
75        let holo_time = holo.map_or(0.0f32, |h| h.time);
76        let holo_count = instances.len() as f32;
77
78        // Get MSAA count for depth texture selection
79        let msaa_count = ctx.renderer.quality_level.msaa_sample_count() as f32;
80
81        let uniform_data: [f32; 24] = [
82            current_time,   // 0: time
83            resolution[0],  // 1: resolution.x
84            resolution[1],  // 2: resolution.y
85            msaa_count,     // 3: msaa_count (was _pad)
86            light_pos[0],   // 4: light_pos.x
87            light_pos[1],   // 5: light_pos.y
88            light_pos[2],   // 6: light_pos.z
89            0.0,            // 7: _pad
90            light_color[0], // 8: light_color.x
91            light_color[1], // 9: light_color.y
92            light_color[2], // 10: light_color.z
93            1.0,            // 11: density
94            0.15,           // 12: falloff
95            0.0,            // 13: _pad0
96            0.0,            // 14: _pad1
97            0.0,            // 15: struct alignment pad to 64 bytes
98            // -- Hologram extension (bytes 64..96) --
99            holo_rect_x,  // 16: holo_rect.x
100            holo_rect_y,  // 17: holo_rect.y
101            holo_rect_w,  // 18: holo_rect.width
102            holo_rect_h,  // 19: holo_rect.height
103            holo_id_hash, // 20: hologram_id hash (f32 cast)
104            holo_time,    // 21: hologram instance time
105            holo_count,   // 22: number of active hologram instances
106            0.0,          // 23: _pad2
107        ];
108        ctx.renderer.queue.write_buffer(
109            &ctx.renderer.volumetric_uniform_buffer,
110            0,
111            bytemuck::cast_slice(&uniform_data),
112        );
113
114        // Get depth texture view for volumetric occlusion testing
115        let is_msaa = ctx.renderer.quality_level.msaa_sample_count() > 1;
116        let (depth_view_single, depth_view_msaa) = if is_msaa {
117            (&ctx.renderer.dummy_depth_view, ctx.depth_view)
118        } else {
119            (ctx.depth_view, &ctx.renderer.dummy_depth_view_msaa)
120        };
121
122        // Create bind group with uniform buffer + depth textures + comparison sampler
123        let bind_group = ctx.get_or_create_bind_group(
124            (crate::kvasir::resource::ResourceId(99999), 0, false),
125            &ctx.renderer.volumetric_bind_group_layout,
126            &[
127                wgpu::BindGroupEntry {
128                    binding: 0,
129                    resource: wgpu::BindingResource::Buffer(
130                        ctx.renderer
131                            .volumetric_uniform_buffer
132                            .as_entire_buffer_binding(),
133                    ),
134                },
135                wgpu::BindGroupEntry {
136                    binding: 1,
137                    resource: wgpu::BindingResource::TextureView(depth_view_single),
138                },
139                wgpu::BindGroupEntry {
140                    binding: 2,
141                    resource: wgpu::BindingResource::TextureView(depth_view_msaa),
142                },
143                wgpu::BindGroupEntry {
144                    binding: 3,
145                    resource: wgpu::BindingResource::Sampler(
146                        &ctx.renderer.volumetric_depth_sampler,
147                    ),
148                },
149            ],
150            Some("Volumetric Bind Group"),
151        );
152
153        let mut p = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
154            label: Some("Surtr Volumetric Raymarching"),
155            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
156                view: &scene_view,
157                resolve_target: None,
158                ops: wgpu::Operations {
159                    load: wgpu::LoadOp::Load,
160                    store: wgpu::StoreOp::Store,
161                },
162                depth_slice: None,
163            })],
164            depth_stencil_attachment: None,
165            ..Default::default()
166        });
167
168        p.set_pipeline(&ctx.renderer.volumetric_pipeline);
169        p.set_bind_group(0, &bind_group, &[]);
170        p.draw(0..3, 0..1); // Fullscreen triangle
171    }
172}