Skip to main content

debug_overlay/
lib.rs

1#![allow(mismatched_lifetime_syntaxes)]
2
3//! A basic low-overhead debugging overlay for use with GPU APIs such as `wgpu`.
4//!
5//! # Features
6//!
7//! Enable one or several or the builtin runderers using the following cargo features:
8//! - `wgpu`
9//! - `wgpu-core` (TODO)
10//!
11
12mod counter;
13pub mod embedded_font;
14mod graph;
15mod table;
16#[cfg(feature = "wgpu")]
17pub mod wgpu;
18#[cfg(feature = "wgpu-core")]
19pub mod wgpu_core;
20#[cfg(any(feature = "wgpu", feature = "wgpu-core"))]
21mod wgpu_common;
22
23use bytemuck::{Pod, Zeroable};
24use embedded_font::*;
25
26pub use counter::*;
27pub use graph::*;
28pub use table::*;
29
30pub const BACKGROUND_LAYER: Layer = 0;
31pub const FRONT_LAYER: Layer = 1;
32
33/// A 2D position (in pixels).
34#[derive(Copy, Clone, Debug, PartialEq)]
35pub struct Point {
36    pub x: i32,
37    pub y: i32,
38}
39
40impl From<(f32, f32)> for Point {
41    fn from(val: (f32, f32)) -> Self {
42        Point {
43            x: val.0 as i32,
44            y: val.1 as i32,
45        }
46    }
47}
48
49impl From<(i32, i32)> for Point {
50    fn from(val: (i32, i32)) -> Self {
51        Point { x: val.0, y: val.1 }
52    }
53}
54
55/// A 2D position (in pixels).
56#[derive(Copy, Clone, Debug, PartialEq)]
57pub struct PointF {
58    pub x: f32,
59    pub y: f32,
60}
61
62/// An 8-bit per channel RGBA color value.
63pub type Color = (u8, u8, u8, u8);
64/// The index of an overlay layer.
65pub type Layer = usize;
66
67fn color_to_u32(color: Color) -> u32 {
68    (color.0 as u32) << 24 | (color.1 as u32) << 16 | (color.2 as u32) << 8 | color.3 as u32
69}
70
71#[repr(C)]
72#[derive(Copy, Clone, Debug)]
73pub struct Vertex {
74    pub x: f32,
75    pub y: f32,
76    pub uv: u32,
77    pub color: u32,
78}
79
80unsafe impl Pod for Vertex {}
81unsafe impl Zeroable for Vertex {}
82
83pub(crate) struct LayerGeometry {
84    pub indices: Vec<u16>,
85}
86
87pub struct OverlayGeometry {
88    vertices: Vec<Vertex>,
89    layers: Vec<LayerGeometry>,
90}
91
92impl OverlayGeometry {
93    pub fn new(layer_count: usize) -> Self {
94        let mut layers = Vec::new();
95        for _ in 0..layer_count {
96            layers.push(LayerGeometry {
97                indices: Vec::new(),
98            });
99        }
100        OverlayGeometry {
101            vertices: Vec::new(),
102            layers,
103        }
104    }
105
106    pub fn begin_frame(&mut self) {
107        self.vertices.clear();
108        for layer in &mut self.layers {
109            layer.indices.clear();
110        }
111    }
112
113    pub fn push_text(
114        &mut self,
115        layer: Layer,
116        text: &str,
117        mut position: Point,
118        color: Color,
119    ) -> (Point, Point) {
120        let color = color_to_u32(color);
121        let mut min = position;
122        let mut max = min;
123
124        for c in text.chars() {
125            if c == '\n' {
126                position.x = min.x;
127                position.y += FONT_HEIGHT as i32;
128                continue;
129            }
130
131            let idx = c as usize - FIRST_CHAR as usize;
132            if idx >= GLYPH_INFO.len() {
133                continue;
134            }
135            let glyph = &GLYPH_INFO[idx];
136
137            let uv0x = (glyph.uv0.0 as u32) << 16;
138            let uv0y = glyph.uv0.1 as u32;
139            let uv1x = (glyph.uv1.0 as u32) << 16;
140            let uv1y = glyph.uv1.1 as u32;
141
142            let x0 = position.x + glyph.offset.0 as i32;
143            let y0 = position.y + glyph.offset.1 as i32;
144            let x1 = x0 + (glyph.uv1.0 - glyph.uv0.0) as i32;
145            let y1 = y0 + (glyph.uv1.1 - glyph.uv0.1) as i32;
146
147            let offset = self.vertices.len() as u16;
148            self.vertices.push(Vertex {
149                x: x0 as f32,
150                y: y0 as f32,
151                uv: uv0x | uv0y,
152                color,
153            });
154            self.vertices.push(Vertex {
155                x: x1 as f32,
156                y: y0 as f32,
157                uv: uv1x | uv0y,
158                color,
159            });
160            self.vertices.push(Vertex {
161                x: x1 as f32,
162                y: y1 as f32,
163                uv: uv1x | uv1y,
164                color,
165            });
166            self.vertices.push(Vertex {
167                x: x0 as f32,
168                y: y1 as f32,
169                uv: uv0x | uv1y,
170                color,
171            });
172            let layer = &mut self.layers[layer];
173            for i in [0u16, 1, 2, 0, 2, 3] {
174                layer.indices.push(offset + i);
175            }
176
177            position.x += glyph.x_advance as i32;
178
179            min.x = min.x.min(x0);
180            min.y = min.y.min(y0);
181            max.x = max.x.max(x1);
182            max.y = max.y.max(y1);
183        }
184
185        (min, max)
186    }
187
188    pub fn push_rectangle(
189        &mut self,
190        layer: Layer,
191        rect: &(Point, Point),
192        color0: Color,
193        color1: Color,
194    ) {
195        let uv = (OPAQUE_PIXEL.0 as u32) << 16 | OPAQUE_PIXEL.1 as u32;
196        let x0 = rect.0.x;
197        let y0 = rect.0.y;
198        let x1 = rect.1.x;
199        let y1 = rect.1.y;
200        let color0 = color_to_u32(color0);
201        let color1 = color_to_u32(color1);
202
203        let offset = self.vertices.len() as u16;
204        self.vertices.push(Vertex {
205            x: x0 as f32,
206            y: y0 as f32,
207            uv,
208            color: color0,
209        });
210        self.vertices.push(Vertex {
211            x: x1 as f32,
212            y: y0 as f32,
213            uv,
214            color: color0,
215        });
216        self.vertices.push(Vertex {
217            x: x1 as f32,
218            y: y1 as f32,
219            uv,
220            color: color1,
221        });
222        self.vertices.push(Vertex {
223            x: x0 as f32,
224            y: y1 as f32,
225            uv,
226            color: color1,
227        });
228        let layer = &mut self.layers[layer];
229        for i in [0u16, 1, 2, 0, 2, 3] {
230            layer.indices.push(offset + i);
231        }
232    }
233
234    pub fn push_mesh(&mut self, layer: Layer, vertices: &[PointF], indices: &[u16], color: Color) {
235        let uv = (OPAQUE_PIXEL.0 as u32) << 16 | OPAQUE_PIXEL.1 as u32;
236        let layer = &mut self.layers[layer];
237        self.vertices.reserve(vertices.len());
238        layer.indices.reserve(indices.len());
239        let offset = self.vertices.len() as u16;
240        let color = color_to_u32(color);
241        for vertex in vertices {
242            self.vertices.push(Vertex {
243                x: vertex.x,
244                y: vertex.y,
245                uv,
246                color,
247            });
248        }
249        for idx in indices {
250            layer.indices.push(offset + *idx);
251        }
252    }
253}
254
255pub struct Overlay {
256    pub geometry: OverlayGeometry,
257    pub style: Style,
258    pub cursor: Point,
259    pub item_flow: Orientation,
260    pub group_flow: Orientation,
261    pub string_buffer: String,
262    group_area: (Point, Point),
263    in_group: bool,
264    max_x: i32,
265    max_y: i32,
266}
267
268impl Overlay {
269    pub fn new() -> Self {
270        let style = Style::default();
271        let cursor = Point {
272            x: style.margin,
273            y: style.margin,
274        };
275        Overlay {
276            geometry: OverlayGeometry::new(2),
277            style,
278            cursor,
279            item_flow: Orientation::Horizontal,
280            group_flow: Orientation::Vertical,
281            string_buffer: String::with_capacity(128),
282            group_area: (cursor, cursor),
283            in_group: false,
284            max_x: 0,
285            max_y: 0,
286        }
287    }
288
289    pub fn begin_frame(&mut self) {
290        self.geometry.begin_frame();
291
292        self.cursor = Point {
293            x: self.style.margin,
294            y: self.style.margin,
295        };
296        self.group_area = (self.cursor, self.cursor);
297        self.max_x = 0;
298        self.max_y = 0;
299        self.in_group = false;
300    }
301
302    pub fn current_group_width(&self) -> i32 {
303        self.group_area.1.x - self.group_area.0.x
304    }
305
306    pub fn current_group_height(&self) -> i32 {
307        self.group_area.1.y - self.group_area.0.y
308    }
309
310    pub fn draw_item(&mut self, item: &dyn OverlayItem) {
311        let first = !self.in_group;
312        if !self.in_group {
313            self.begin_group();
314        }
315
316        let margin = if first { 0 } else { self.style.margin };
317        self.cursor = match self.item_flow {
318            Orientation::Vertical => Point {
319                x: self.group_area.0.x,
320                y: self.group_area.1.y + margin,
321            },
322            Orientation::Horizontal => Point {
323                x: self.group_area.1.x + margin,
324                y: self.group_area.0.y,
325            },
326        };
327
328        let rect = item.draw(self.cursor, self);
329
330        self.group_area.0.x = self.group_area.0.x.min(rect.0.x);
331        self.group_area.0.y = self.group_area.0.y.min(rect.0.y);
332        self.group_area.1.x = self.group_area.1.x.max(rect.1.x);
333        self.group_area.1.y = self.group_area.1.y.max(rect.1.y);
334    }
335
336    pub fn push_separator(&mut self) {
337        if !self.in_group {
338            return;
339        }
340
341        match self.item_flow {
342            Orientation::Vertical => {
343                self.cursor.y += self.style.margin * 3;
344            }
345            Orientation::Horizontal => {
346                self.cursor.x += self.style.margin * 3;
347            }
348        }
349    }
350
351    pub fn push_column(&mut self) {
352        if self.in_group {
353            self.end_group();
354        }
355
356        let p = Point {
357            x: self.max_x + self.style.margin * 3,
358            y: self.style.margin,
359        };
360
361        self.group_area = (p, p);
362    }
363
364    fn begin_group(&mut self) {
365        match self.group_flow {
366            Orientation::Vertical => {
367                let margin = if self.group_area.1.y > self.style.margin {
368                    self.style.margin * 3
369                } else {
370                    0
371                };
372                self.cursor.x = self.group_area.0.x;
373                self.cursor.y = self.group_area.1.y + margin;
374            }
375            Orientation::Horizontal => {
376                let margin = if self.group_area.1.x > self.style.margin {
377                    self.style.margin * 3
378                } else {
379                    0
380                };
381                self.cursor.x = self.group_area.1.x + margin;
382                self.cursor.y = self.group_area.0.y;
383            }
384        }
385
386        self.group_area = (self.cursor, self.cursor);
387        self.in_group = true;
388    }
389
390    pub fn end_group(&mut self) {
391        self.in_group = false;
392        if self.group_area.0.x >= self.group_area.1.x || self.group_area.0.y >= self.group_area.1.y
393        {
394            return;
395        }
396
397        self.group_area.1.x = self
398            .group_area
399            .1
400            .x
401            .max(self.group_area.0.x + self.style.min_group_width);
402        self.group_area.1.y = self
403            .group_area
404            .1
405            .y
406            .max(self.group_area.0.y + self.style.min_group_height);
407
408        self.max_x = self.max_x.max(self.group_area.1.x);
409        self.max_y = self.max_y.max(self.group_area.1.y);
410
411        let margin = self.style.margin;
412        let mut bg = self.group_area;
413        bg.0.x -= margin;
414        bg.0.y -= margin;
415        bg.1.x += margin;
416        bg.1.y += margin;
417
418        self.geometry.push_rectangle(
419            BACKGROUND_LAYER,
420            &bg,
421            self.style.background[0],
422            self.style.background[1],
423        );
424    }
425
426    pub fn finish(&mut self) {
427        if self.in_group {
428            self.end_group();
429        }
430    }
431}
432
433pub trait OverlayItem {
434    fn draw(&self, position: Point, output: &mut Overlay) -> (Point, Point);
435}
436
437impl<'a> OverlayItem for &'a str {
438    fn draw(&self, position: Point, output: &mut Overlay) -> (Point, Point) {
439        let p = Point {
440            x: position.x,
441            y: position.y + FONT_HEIGHT as i32,
442        };
443
444        output
445            .geometry
446            .push_text(FRONT_LAYER, self, p, output.style.text_color[0])
447    }
448}
449
450#[derive(Copy, Clone, Debug, PartialEq)]
451pub struct Style {
452    pub margin: i32,
453    pub line_spacing: i32,
454    pub min_group_width: i32,
455    pub min_group_height: i32,
456    pub column_spacing: i32,
457    pub background: [Color; 2],
458    pub text_color: [Color; 2],
459    pub title_color: Color,
460    pub highlight_color: Color,
461}
462
463impl Default for Style {
464    fn default() -> Self {
465        Style {
466            margin: 10,
467            line_spacing: 2,
468            min_group_width: 0,
469            min_group_height: 0,
470            column_spacing: 20,
471            background: [(0, 0, 0, 255), (0, 0, 0, 200)],
472            text_color: [(255, 255, 255, 255), (200, 200, 200, 255)],
473            title_color: (120, 150, 255, 255),
474            highlight_color: (255, 100, 100, 255),
475        }
476    }
477}