Skip to main content

gizmo_renderer/
debug_renderer.rs

1use gizmo_math::Vec3;
2#[derive(Clone, Default)]
3pub struct Gizmos {
4    pub lines: Vec<GizmoVertex>,
5    pub depth_test: bool,
6}
7
8impl Gizmos {
9    pub fn clear(&mut self) {
10        self.lines.clear();
11    }
12
13    pub fn draw_line(&mut self, start: Vec3, end: Vec3, color: [f32; 4]) {
14        // Prevent GPU driver crashes from NaN/Infinity vertices
15        if !start.x.is_finite()
16            || !start.y.is_finite()
17            || !start.z.is_finite()
18            || !end.x.is_finite()
19            || !end.y.is_finite()
20            || !end.z.is_finite()
21        {
22            return;
23        }
24
25        // Prevent zero-length lines which crash some Vulkan/Mesa drivers
26        if start.distance_squared(end) < 1e-8 {
27            return;
28        }
29
30        self.lines.push(GizmoVertex {
31            position: start.to_array(),
32            color,
33        });
34        self.lines.push(GizmoVertex {
35            position: end.to_array(),
36            color,
37        });
38    }
39
40    pub fn draw_box(&mut self, min: Vec3, max: Vec3, color: [f32; 4]) {
41        let p0 = Vec3::new(min.x, min.y, min.z);
42        let p1 = Vec3::new(max.x, min.y, min.z);
43        let p2 = Vec3::new(max.x, max.y, min.z);
44        let p3 = Vec3::new(min.x, max.y, min.z);
45        let p4 = Vec3::new(min.x, min.y, max.z);
46        let p5 = Vec3::new(max.x, min.y, max.z);
47        let p6 = Vec3::new(max.x, max.y, max.z);
48        let p7 = Vec3::new(min.x, max.y, max.z);
49        // Bottom
50        self.draw_line(p0, p1, color);
51        self.draw_line(p1, p2, color);
52        self.draw_line(p2, p3, color);
53        self.draw_line(p3, p0, color);
54        // Top
55        self.draw_line(p4, p5, color);
56        self.draw_line(p5, p6, color);
57        self.draw_line(p6, p7, color);
58        self.draw_line(p7, p4, color);
59        // Pillers
60        self.draw_line(p0, p4, color);
61        self.draw_line(p1, p5, color);
62        self.draw_line(p2, p6, color);
63        self.draw_line(p3, p7, color);
64    }
65
66    pub fn draw_aabb(&mut self, aabb: gizmo_math::Aabb, color: [f32; 4]) {
67        self.draw_box(aabb.min.into(), aabb.max.into(), color);
68    }
69
70    pub fn draw_frustum(&mut self, view_proj: gizmo_math::Mat4, color: [f32; 4]) {
71        let inv = view_proj.inverse();
72        
73        let corners_ndc = [
74            Vec3::new(-1.0, -1.0, 1.0), // Near bottom left
75            Vec3::new(1.0, -1.0, 1.0),  // Near bottom right
76            Vec3::new(1.0, 1.0, 1.0),   // Near top right
77            Vec3::new(-1.0, 1.0, 1.0),  // Near top left
78            Vec3::new(-1.0, -1.0, 0.0), // Far bottom left
79            Vec3::new(1.0, -1.0, 0.0),  // Far bottom right
80            Vec3::new(1.0, 1.0, 0.0),   // Far top right
81            Vec3::new(-1.0, 1.0, 0.0),  // Far top left
82        ];
83
84        let mut corners_world = [Vec3::ZERO; 8];
85        for i in 0..8 {
86            let p = inv.project_point3(corners_ndc[i]);
87            corners_world[i] = p;
88        }
89
90        // Near plane
91        self.draw_line(corners_world[0], corners_world[1], color);
92        self.draw_line(corners_world[1], corners_world[2], color);
93        self.draw_line(corners_world[2], corners_world[3], color);
94        self.draw_line(corners_world[3], corners_world[0], color);
95
96        // Far plane
97        self.draw_line(corners_world[4], corners_world[5], color);
98        self.draw_line(corners_world[5], corners_world[6], color);
99        self.draw_line(corners_world[6], corners_world[7], color);
100        self.draw_line(corners_world[7], corners_world[4], color);
101
102        // Connecting lines
103        self.draw_line(corners_world[0], corners_world[4], color);
104        self.draw_line(corners_world[1], corners_world[5], color);
105        self.draw_line(corners_world[2], corners_world[6], color);
106        self.draw_line(corners_world[3], corners_world[7], color);
107    }
108}
109
110#[derive(Clone, Copy, Debug)]
111pub struct GizmoVertex {
112    pub position: [f32; 3],
113    pub color: [f32; 4],
114}
115
116#[repr(C)]
117#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
118pub struct GpuGizmoVertex {
119    pub position: [f32; 3],
120    pub color: [f32; 4],
121}
122
123impl GpuGizmoVertex {
124    pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
125        wgpu::VertexBufferLayout {
126            array_stride: std::mem::size_of::<GpuGizmoVertex>() as wgpu::BufferAddress,
127            step_mode: wgpu::VertexStepMode::Vertex,
128            attributes: &[
129                wgpu::VertexAttribute {
130                    offset: 0,
131                    shader_location: 0,
132                    format: wgpu::VertexFormat::Float32x3,
133                },
134                wgpu::VertexAttribute {
135                    offset: 12,
136                    shader_location: 1,
137                    format: wgpu::VertexFormat::Float32x4,
138                },
139            ],
140        }
141    }
142}
143
144pub struct GizmoRendererSystem {
145    pub pipeline: wgpu::RenderPipeline,
146    pub pipeline_no_depth: wgpu::RenderPipeline,
147    pub vertex_buffer: wgpu::Buffer,
148    pub max_vertices: u32,
149    pub index_count: u32,
150}
151
152impl GizmoRendererSystem {
153    pub fn new(
154        device: &wgpu::Device,
155        global_bind_group_layout: &wgpu::BindGroupLayout,
156        output_format: wgpu::TextureFormat,
157        depth_format: wgpu::TextureFormat,
158    ) -> Self {
159        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
160            label: Some("Debug Lines Shader"),
161            source: wgpu::ShaderSource::Wgsl(include_str!("shaders/debug_lines.wgsl").into()),
162        });
163
164        let render_pipeline_layout =
165            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
166                label: Some("Debug Lines Pipeline Layout"),
167                bind_group_layouts: &[Some(global_bind_group_layout)],
168                immediate_size: 0,
169            });
170
171        let mut desc = wgpu::RenderPipelineDescriptor {
172            label: Some("Debug Lines Pipeline"),
173            layout: Some(&render_pipeline_layout),
174            vertex: wgpu::VertexState {
175                module: &shader,
176                entry_point: Some("vs_main"),
177                compilation_options: Default::default(),
178                buffers: &[GpuGizmoVertex::desc()],
179            },
180            fragment: Some(wgpu::FragmentState {
181                module: &shader,
182                entry_point: Some("fs_main"),
183                compilation_options: Default::default(),
184                targets: &[Some(wgpu::ColorTargetState {
185                    format: output_format,
186                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
187                    write_mask: wgpu::ColorWrites::ALL,
188                })],
189            }),
190            primitive: wgpu::PrimitiveState {
191                topology: wgpu::PrimitiveTopology::LineList,
192                front_face: wgpu::FrontFace::Ccw,
193                cull_mode: None,
194                ..Default::default()
195            },
196            depth_stencil: Some(wgpu::DepthStencilState {
197                format: depth_format,
198                depth_write_enabled: Some(false),
199                depth_compare: Some(wgpu::CompareFunction::Always), // Unconditionally pass depth testing for all gizmos
200                stencil: wgpu::StencilState::default(),
201                bias: wgpu::DepthBiasState::default(),
202            }),
203            multisample: wgpu::MultisampleState::default(),
204            multiview_mask: None,
205            cache: None,
206        };
207
208        let pipeline = device.create_render_pipeline(&desc);
209
210        // Variant without depth testing (to overlay unconditionally)
211        desc.depth_stencil = Some(wgpu::DepthStencilState {
212            format: depth_format,
213            depth_write_enabled: Some(false),
214            depth_compare: Some(wgpu::CompareFunction::Always),
215            stencil: wgpu::StencilState::default(),
216            bias: wgpu::DepthBiasState::default(),
217        });
218        desc.label = Some("Debug Lines No-Depth Pipeline");
219        let pipeline_no_depth = device.create_render_pipeline(&desc);
220
221        let max_vertices = 200_000;
222        let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
223            label: Some("Gizmo Vertex Buffer"),
224            size: (max_vertices as usize * std::mem::size_of::<GpuGizmoVertex>())
225                as wgpu::BufferAddress,
226            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
227            mapped_at_creation: false,
228        });
229
230        Self {
231            pipeline,
232            pipeline_no_depth,
233            vertex_buffer,
234            max_vertices,
235            index_count: 0,
236        }
237    }
238
239    pub fn update(&mut self, queue: &wgpu::Queue, gizmos: &Gizmos) {
240        self.index_count = gizmos.lines.len() as u32;
241        if self.index_count > 0 {
242            let to_write = self.index_count.min(self.max_vertices) as usize;
243
244            // Map gizmo_core::GizmoVertex to GpuGizmoVertex
245            let mut gpu_data = Vec::with_capacity(to_write);
246            for v in &gizmos.lines[0..to_write] {
247                gpu_data.push(GpuGizmoVertex {
248                    position: v.position,
249                    color: v.color,
250                });
251            }
252
253            queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&gpu_data));
254        }
255    }
256
257    pub fn render<'a>(
258        &'a self,
259        rpass: &mut wgpu::RenderPass<'a>,
260        global_bind_group: &'a wgpu::BindGroup,
261        depth_test: bool,
262    ) {
263        if self.index_count == 0 {
264            return;
265        }
266        let draw_count = self.index_count.min(self.max_vertices);
267        if depth_test {
268            rpass.set_pipeline(&self.pipeline);
269        } else {
270            rpass.set_pipeline(&self.pipeline_no_depth);
271        }
272        rpass.set_bind_group(0, global_bind_group, &[]);
273        rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
274        rpass.draw(0..draw_count, 0..1);
275    }
276}