Skip to main content

haggis/visualization/rendering/
renderer.rs

1//! Visualization Renderer
2//!
3//! Dedicated rendering system for visualization components, independent of scene objects.
4
5use super::materials::VisualizationMaterial;
6use cgmath::{Matrix4, Vector3};
7use wgpu::util::DeviceExt;
8use wgpu::*;
9
10/// Vertex data for visualization quads
11#[repr(C)]
12#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
13pub struct VisualizationVertex {
14    pub position: [f32; 3],
15    pub tex_coords: [f32; 2],
16}
17
18impl VisualizationVertex {
19    const ATTRIBUTES: [VertexAttribute; 2] = [
20        VertexAttribute {
21            offset: 0,
22            shader_location: 0,
23            format: VertexFormat::Float32x3,
24        },
25        VertexAttribute {
26            offset: std::mem::size_of::<[f32; 3]>() as BufferAddress,
27            shader_location: 1,
28            format: VertexFormat::Float32x2,
29        },
30    ];
31
32    pub fn desc<'a>() -> VertexBufferLayout<'a> {
33        VertexBufferLayout {
34            array_stride: std::mem::size_of::<VisualizationVertex>() as BufferAddress,
35            step_mode: VertexStepMode::Vertex,
36            attributes: &Self::ATTRIBUTES,
37        }
38    }
39}
40
41/// Camera uniform for visualization rendering
42#[repr(C)]
43#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
44pub struct VisualizationCameraUniform {
45    pub view_proj: [[f32; 4]; 4],
46}
47
48/// Visualization item to be rendered
49pub struct VisualizationItem {
50    pub vertices: Vec<VisualizationVertex>,
51    pub indices: Vec<u16>,
52    pub material: VisualizationMaterial,
53    pub transform: Matrix4<f32>,
54}
55
56impl VisualizationItem {
57    /// Create a quad for 2D visualization
58    pub fn create_quad(position: Vector3<f32>, size: f32, material: VisualizationMaterial) -> Self {
59        let half_size = size * 0.5;
60
61        let vertices = vec![
62            VisualizationVertex {
63                position: [position.x - half_size, position.y - half_size, position.z],
64                tex_coords: [0.0, 1.0],
65            },
66            VisualizationVertex {
67                position: [position.x + half_size, position.y - half_size, position.z],
68                tex_coords: [1.0, 1.0],
69            },
70            VisualizationVertex {
71                position: [position.x + half_size, position.y + half_size, position.z],
72                tex_coords: [1.0, 0.0],
73            },
74            VisualizationVertex {
75                position: [position.x - half_size, position.y + half_size, position.z],
76                tex_coords: [0.0, 0.0],
77            },
78        ];
79
80        let indices = vec![0, 1, 2, 2, 3, 0];
81
82        Self {
83            vertices,
84            indices,
85            material,
86            transform: Matrix4::from_translation(position),
87        }
88    }
89}
90
91/// Dedicated renderer for visualization components
92pub struct VisualizationRenderer {
93    render_pipeline: RenderPipeline,
94    camera_buffer: Buffer,
95    camera_bind_group: BindGroup,
96    vertex_buffer: Option<Buffer>,
97    index_buffer: Option<Buffer>,
98    vertex_count: u32,
99    index_count: u32,
100}
101
102impl VisualizationRenderer {
103    pub fn new(device: &Device, surface_format: TextureFormat) -> Self {
104        // Create shader
105        let shader = device.create_shader_module(ShaderModuleDescriptor {
106            label: Some("Visualization Shader"),
107            source: ShaderSource::Wgsl(super::shaders::VISUALIZATION_SHADER.into()),
108        });
109
110        // Create camera buffer
111        let camera_buffer = device.create_buffer(&BufferDescriptor {
112            label: Some("Visualization Camera Buffer"),
113            size: std::mem::size_of::<VisualizationCameraUniform>() as BufferAddress,
114            usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
115            mapped_at_creation: false,
116        });
117
118        // Create camera bind group layout
119        let camera_bind_group_layout =
120            device.create_bind_group_layout(&BindGroupLayoutDescriptor {
121                label: Some("Visualization Camera Bind Group Layout"),
122                entries: &[BindGroupLayoutEntry {
123                    binding: 0,
124                    visibility: ShaderStages::VERTEX,
125                    ty: BindingType::Buffer {
126                        ty: BufferBindingType::Uniform,
127                        has_dynamic_offset: false,
128                        min_binding_size: None,
129                    },
130                    count: None,
131                }],
132            });
133
134        // Create camera bind group
135        let camera_bind_group = device.create_bind_group(&BindGroupDescriptor {
136            label: Some("Visualization Camera Bind Group"),
137            layout: &camera_bind_group_layout,
138            entries: &[BindGroupEntry {
139                binding: 0,
140                resource: camera_buffer.as_entire_binding(),
141            }],
142        });
143
144        // Create material bind group layout
145        let material_bind_group_layout =
146            device.create_bind_group_layout(&BindGroupLayoutDescriptor {
147                label: Some("Visualization Material Bind Group Layout"),
148                entries: &[
149                    BindGroupLayoutEntry {
150                        binding: 0,
151                        visibility: ShaderStages::FRAGMENT,
152                        ty: BindingType::Texture {
153                            multisampled: false,
154                            view_dimension: TextureViewDimension::D2,
155                            sample_type: TextureSampleType::Float { filterable: true },
156                        },
157                        count: None,
158                    },
159                    BindGroupLayoutEntry {
160                        binding: 1,
161                        visibility: ShaderStages::FRAGMENT,
162                        ty: BindingType::Sampler(SamplerBindingType::Filtering),
163                        count: None,
164                    },
165                ],
166            });
167
168        // Create render pipeline layout
169        let render_pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor {
170            label: Some("Visualization Render Pipeline Layout"),
171            bind_group_layouts: &[&camera_bind_group_layout, &material_bind_group_layout],
172            push_constant_ranges: &[],
173        });
174
175        // Create render pipeline
176        let render_pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
177            label: Some("Visualization Render Pipeline"),
178            layout: Some(&render_pipeline_layout),
179            vertex: VertexState {
180                module: &shader,
181                entry_point: Some("vs_main"),
182                buffers: &[VisualizationVertex::desc()],
183                compilation_options: Default::default(),
184            },
185            fragment: Some(FragmentState {
186                module: &shader,
187                entry_point: Some("fs_main"),
188                targets: &[Some(ColorTargetState {
189                    format: surface_format,
190                    blend: Some(BlendState::ALPHA_BLENDING),
191                    write_mask: ColorWrites::ALL,
192                })],
193                compilation_options: Default::default(),
194            }),
195            primitive: PrimitiveState {
196                topology: PrimitiveTopology::TriangleList,
197                strip_index_format: None,
198                front_face: FrontFace::Ccw,
199                cull_mode: None, // No culling for visualization
200                unclipped_depth: false,
201                polygon_mode: PolygonMode::Fill,
202                conservative: false,
203            },
204            depth_stencil: Some(DepthStencilState {
205                format: TextureFormat::Depth32Float,
206                depth_write_enabled: true,
207                depth_compare: CompareFunction::Less,
208                stencil: StencilState::default(),
209                bias: DepthBiasState::default(),
210            }),
211            multisample: MultisampleState {
212                count: 1,
213                mask: !0,
214                alpha_to_coverage_enabled: false,
215            },
216            multiview: None,
217            cache: None,
218        });
219
220        Self {
221            render_pipeline,
222            camera_buffer,
223            camera_bind_group,
224            vertex_buffer: None,
225            index_buffer: None,
226            vertex_count: 0,
227            index_count: 0,
228        }
229    }
230
231    /// Update camera uniform
232    pub fn update_camera(&self, queue: &Queue, view_proj_matrix: Matrix4<f32>) {
233        let camera_uniform = VisualizationCameraUniform {
234            view_proj: view_proj_matrix.into(),
235        };
236        queue.write_buffer(
237            &self.camera_buffer,
238            0,
239            bytemuck::cast_slice(&[camera_uniform]),
240        );
241    }
242
243    /// Update vertex and index buffers with visualization items
244    pub fn update_buffers(&mut self, device: &Device, items: &[VisualizationItem]) {
245        let mut all_vertices = Vec::new();
246        let mut all_indices = Vec::new();
247        let mut index_offset = 0;
248
249        for item in items {
250            all_vertices.extend_from_slice(&item.vertices);
251            for &index in &item.indices {
252                all_indices.push(index + index_offset);
253            }
254            index_offset += item.vertices.len() as u16;
255        }
256
257        if !all_vertices.is_empty() {
258            // Create vertex buffer
259            self.vertex_buffer = Some(device.create_buffer_init(
260                &wgpu::util::BufferInitDescriptor {
261                    label: Some("Visualization Vertex Buffer"),
262                    contents: bytemuck::cast_slice(&all_vertices),
263                    usage: BufferUsages::VERTEX,
264                },
265            ));
266            self.vertex_count = all_vertices.len() as u32;
267
268            // Create index buffer
269            self.index_buffer = Some(device.create_buffer_init(
270                &wgpu::util::BufferInitDescriptor {
271                    label: Some("Visualization Index Buffer"),
272                    contents: bytemuck::cast_slice(&all_indices),
273                    usage: BufferUsages::INDEX,
274                },
275            ));
276            self.index_count = all_indices.len() as u32;
277        }
278    }
279
280    /// Render visualization items
281    pub fn render(
282        &self,
283        encoder: &mut CommandEncoder,
284        color_attachment: &TextureView,
285        depth_attachment: &TextureView,
286        materials: &[&VisualizationMaterial],
287    ) {
288        if self.vertex_buffer.is_none() || self.index_buffer.is_none() {
289            return;
290        }
291
292        let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
293            label: Some("Visualization Render Pass"),
294            color_attachments: &[Some(RenderPassColorAttachment {
295                view: color_attachment,
296                resolve_target: None,
297                ops: Operations {
298                    load: LoadOp::Load,
299                    store: StoreOp::Store,
300                },
301            })],
302            depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
303                view: depth_attachment,
304                depth_ops: Some(Operations {
305                    load: LoadOp::Load,
306                    store: StoreOp::Store,
307                }),
308                stencil_ops: None,
309            }),
310            occlusion_query_set: None,
311            timestamp_writes: None,
312        });
313
314        render_pass.set_pipeline(&self.render_pipeline);
315        render_pass.set_bind_group(0, &self.camera_bind_group, &[]);
316
317        if let (Some(vertex_buffer), Some(index_buffer)) = (&self.vertex_buffer, &self.index_buffer)
318        {
319            render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
320            render_pass.set_index_buffer(index_buffer.slice(..), IndexFormat::Uint16);
321
322            // Render each material group
323            for material in materials {
324                if let Some(bind_group) = &material.bind_group {
325                    render_pass.set_bind_group(1, bind_group, &[]);
326                    render_pass.draw_indexed(0..self.index_count, 0, 0..1);
327                }
328            }
329        }
330    }
331}