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
67#[derive(Clone, Copy, Debug)]
68pub struct GizmoVertex {
69    pub position: [f32; 3],
70    pub color: [f32; 4],
71}
72
73#[repr(C)]
74#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
75pub struct GpuGizmoVertex {
76    pub position: [f32; 3],
77    pub color: [f32; 4],
78}
79
80impl GpuGizmoVertex {
81    pub fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
82        wgpu::VertexBufferLayout {
83            array_stride: std::mem::size_of::<GpuGizmoVertex>() as wgpu::BufferAddress,
84            step_mode: wgpu::VertexStepMode::Vertex,
85            attributes: &[
86                wgpu::VertexAttribute {
87                    offset: 0,
88                    shader_location: 0,
89                    format: wgpu::VertexFormat::Float32x3,
90                },
91                wgpu::VertexAttribute {
92                    offset: 12,
93                    shader_location: 1,
94                    format: wgpu::VertexFormat::Float32x4,
95                },
96            ],
97        }
98    }
99}
100
101pub struct GizmoRendererSystem {
102    pub pipeline: wgpu::RenderPipeline,
103    pub pipeline_no_depth: wgpu::RenderPipeline,
104    pub vertex_buffer: wgpu::Buffer,
105    pub max_vertices: u32,
106    pub index_count: u32,
107}
108
109impl GizmoRendererSystem {
110    pub fn new(
111        device: &wgpu::Device,
112        global_bind_group_layout: &wgpu::BindGroupLayout,
113        output_format: wgpu::TextureFormat,
114        depth_format: wgpu::TextureFormat,
115    ) -> Self {
116        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
117            label: Some("Debug Lines Shader"),
118            source: wgpu::ShaderSource::Wgsl(include_str!("shaders/debug_lines.wgsl").into()),
119        });
120
121        let render_pipeline_layout =
122            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
123                label: Some("Debug Lines Pipeline Layout"),
124                bind_group_layouts: &[global_bind_group_layout],
125                push_constant_ranges: &[],
126            });
127
128        let mut desc = wgpu::RenderPipelineDescriptor {
129            label: Some("Debug Lines Pipeline"),
130            layout: Some(&render_pipeline_layout),
131            vertex: wgpu::VertexState {
132                module: &shader,
133                entry_point: "vs_main",
134                compilation_options: Default::default(),
135                buffers: &[GpuGizmoVertex::desc()],
136            },
137            fragment: Some(wgpu::FragmentState {
138                module: &shader,
139                entry_point: "fs_main",
140                compilation_options: Default::default(),
141                targets: &[Some(wgpu::ColorTargetState {
142                    format: output_format,
143                    blend: Some(wgpu::BlendState::ALPHA_BLENDING),
144                    write_mask: wgpu::ColorWrites::ALL,
145                })],
146            }),
147            primitive: wgpu::PrimitiveState {
148                topology: wgpu::PrimitiveTopology::LineList,
149                front_face: wgpu::FrontFace::Ccw,
150                cull_mode: None,
151                ..Default::default()
152            },
153            depth_stencil: Some(wgpu::DepthStencilState {
154                format: depth_format,
155                depth_write_enabled: false,
156                depth_compare: wgpu::CompareFunction::LessEqual,
157                stencil: wgpu::StencilState::default(),
158                bias: wgpu::DepthBiasState::default(),
159            }),
160            multisample: wgpu::MultisampleState::default(),
161            multiview: None,
162        };
163
164        let pipeline = device.create_render_pipeline(&desc);
165
166        // Variant without depth testing (to overlay unconditionally)
167        desc.depth_stencil = Some(wgpu::DepthStencilState {
168            format: depth_format,
169            depth_write_enabled: false,
170            depth_compare: wgpu::CompareFunction::Always,
171            stencil: wgpu::StencilState::default(),
172            bias: wgpu::DepthBiasState::default(),
173        });
174        desc.label = Some("Debug Lines No-Depth Pipeline");
175        let pipeline_no_depth = device.create_render_pipeline(&desc);
176
177        let max_vertices = 200_000;
178        let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
179            label: Some("Gizmo Vertex Buffer"),
180            size: (max_vertices as usize * std::mem::size_of::<GpuGizmoVertex>())
181                as wgpu::BufferAddress,
182            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
183            mapped_at_creation: false,
184        });
185
186        Self {
187            pipeline,
188            pipeline_no_depth,
189            vertex_buffer,
190            max_vertices,
191            index_count: 0,
192        }
193    }
194
195    pub fn update(&mut self, queue: &wgpu::Queue, gizmos: &Gizmos) {
196        self.index_count = gizmos.lines.len() as u32;
197        if self.index_count > 0 {
198            let to_write = self.index_count.min(self.max_vertices) as usize;
199
200            // Map gizmo_core::GizmoVertex to GpuGizmoVertex
201            let mut gpu_data = Vec::with_capacity(to_write);
202            for v in &gizmos.lines[0..to_write] {
203                gpu_data.push(GpuGizmoVertex {
204                    position: v.position,
205                    color: v.color,
206                });
207            }
208
209            queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&gpu_data));
210        }
211    }
212
213    pub fn render<'a>(
214        &'a self,
215        rpass: &mut wgpu::RenderPass<'a>,
216        global_bind_group: &'a wgpu::BindGroup,
217        depth_test: bool,
218    ) {
219        if self.index_count == 0 {
220            return;
221        }
222        let draw_count = self.index_count.min(self.max_vertices);
223        if depth_test {
224            rpass.set_pipeline(&self.pipeline);
225        } else {
226            rpass.set_pipeline(&self.pipeline_no_depth);
227        }
228        rpass.set_bind_group(0, global_bind_group, &[]);
229        rpass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
230        rpass.draw(0..draw_count, 0..1);
231    }
232}