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: &[global_bind_group_layout],
168                push_constant_ranges: &[],
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: "vs_main",
177                compilation_options: Default::default(),
178                buffers: &[GpuGizmoVertex::desc()],
179            },
180            fragment: Some(wgpu::FragmentState {
181                module: &shader,
182                entry_point: "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: false,
199                depth_compare: 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: None,
205        };
206
207        let pipeline = device.create_render_pipeline(&desc);
208
209        // Variant without depth testing (to overlay unconditionally)
210        desc.depth_stencil = Some(wgpu::DepthStencilState {
211            format: depth_format,
212            depth_write_enabled: false,
213            depth_compare: wgpu::CompareFunction::Always,
214            stencil: wgpu::StencilState::default(),
215            bias: wgpu::DepthBiasState::default(),
216        });
217        desc.label = Some("Debug Lines No-Depth Pipeline");
218        let pipeline_no_depth = device.create_render_pipeline(&desc);
219
220        let max_vertices = 200_000;
221        let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
222            label: Some("Gizmo Vertex Buffer"),
223            size: (max_vertices as usize * std::mem::size_of::<GpuGizmoVertex>())
224                as wgpu::BufferAddress,
225            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
226            mapped_at_creation: false,
227        });
228
229        Self {
230            pipeline,
231            pipeline_no_depth,
232            vertex_buffer,
233            max_vertices,
234            index_count: 0,
235        }
236    }
237
238    pub fn update(&mut self, queue: &wgpu::Queue, gizmos: &Gizmos) {
239        self.index_count = gizmos.lines.len() as u32;
240        if self.index_count > 0 {
241            let to_write = self.index_count.min(self.max_vertices) as usize;
242
243            // Map gizmo_core::GizmoVertex to GpuGizmoVertex
244            let mut gpu_data = Vec::with_capacity(to_write);
245            for v in &gizmos.lines[0..to_write] {
246                gpu_data.push(GpuGizmoVertex {
247                    position: v.position,
248                    color: v.color,
249                });
250            }
251
252            queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&gpu_data));
253        }
254    }
255
256    pub fn render<'a>(
257        &'a self,
258        rpass: &mut wgpu::RenderPass<'a>,
259        global_bind_group: &'a wgpu::BindGroup,
260        depth_test: bool,
261    ) {
262        if self.index_count == 0 {
263            return;
264        }
265        let draw_count = self.index_count.min(self.max_vertices);
266        if depth_test {
267            rpass.set_pipeline(&self.pipeline);
268        } else {
269            rpass.set_pipeline(&self.pipeline_no_depth);
270        }
271        rpass.set_bind_group(0, global_bind_group, &[]);
272        rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
273        rpass.draw(0..draw_count, 0..1);
274    }
275}