Skip to main content

embedded_3dgfx/
command_buffer.rs

1use heapless::Vec;
2
3use crate::{
4    DrawPrimitive,
5    error::{BudgetKind, RenderError},
6};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct PrimitiveHeader {
10    pub min_x: i32,
11    pub min_y: i32,
12    pub max_x: i32,
13    pub max_y: i32,
14}
15
16impl PrimitiveHeader {
17    pub fn new(min_x: i32, min_y: i32, max_x: i32, max_y: i32) -> Self {
18        Self {
19            min_x,
20            min_y,
21            max_x,
22            max_y,
23        }
24    }
25
26    pub fn from_primitive(primitive: &DrawPrimitive) -> Self {
27        let (min_x, min_y, max_x, max_y) = primitive.bounds();
28        Self {
29            min_x,
30            min_y,
31            max_x,
32            max_y,
33        }
34    }
35
36    pub fn bounds(&self) -> (i32, i32, i32, i32) {
37        (self.min_x, self.min_y, self.max_x, self.max_y)
38    }
39}
40
41#[derive(Debug, Clone)]
42pub enum RenderCommand {
43    ClearColor(embedded_graphics_core::pixelcolor::Rgb565),
44    ClearDepth(crate::ZDepth),
45    Draw(DrawPrimitive),
46}
47
48impl RenderCommand {
49    pub fn bounds(&self) -> Option<(i32, i32, i32, i32)> {
50        match self {
51            RenderCommand::Draw(primitive) => Some(primitive.bounds()),
52            _ => None,
53        }
54    }
55}
56
57pub struct CommandBuffer<const MAX: usize> {
58    commands: Vec<RenderCommand, MAX>,
59}
60
61impl<const MAX: usize> CommandBuffer<MAX> {
62    pub const fn new() -> Self {
63        Self {
64            commands: Vec::new(),
65        }
66    }
67
68    pub fn clear(&mut self) {
69        self.commands.clear();
70    }
71
72    pub fn len(&self) -> usize {
73        self.commands.len()
74    }
75
76    pub fn is_empty(&self) -> bool {
77        self.commands.is_empty()
78    }
79
80    pub fn push(&mut self, cmd: RenderCommand) -> Result<(), RenderError> {
81        self.commands.push(cmd).map_err(|_| {
82            RenderError::OutOfBudget(BudgetKind::DrawPrimitives {
83                attempted: self.commands.len() + 1,
84                max: MAX,
85            })
86        })
87    }
88
89    pub fn iter(&self) -> core::slice::Iter<'_, RenderCommand> {
90        self.commands.iter()
91    }
92
93    pub fn get(&self, index: usize) -> Option<&RenderCommand> {
94        self.commands.get(index)
95    }
96}
97
98impl<const MAX: usize> Default for CommandBuffer<MAX> {
99    fn default() -> Self {
100        Self::new()
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use embedded_graphics_core::pixelcolor::Rgb565;
108    use nalgebra::Point2;
109
110    #[test]
111    fn new_buffer_starts_empty() {
112        let buf: CommandBuffer<4> = CommandBuffer::new();
113        assert_eq!(buf.len(), 0);
114        assert!(buf.is_empty());
115    }
116
117    #[test]
118    fn push_and_get_roundtrip() {
119        let mut buf: CommandBuffer<4> = CommandBuffer::new();
120        buf.push(RenderCommand::Draw(DrawPrimitive::ColoredPoint(
121            Point2::new(3, 7),
122            Rgb565::new(31, 0, 0),
123        )))
124        .unwrap();
125        assert_eq!(buf.len(), 1);
126        assert!(matches!(
127            buf.get(0),
128            Some(RenderCommand::Draw(DrawPrimitive::ColoredPoint(_, _)))
129        ));
130    }
131
132    #[test]
133    fn push_over_capacity_returns_budget_error() {
134        let mut buf: CommandBuffer<1> = CommandBuffer::new();
135        buf.push(RenderCommand::ClearDepth(0)).unwrap();
136        let err = buf
137            .push(RenderCommand::ClearDepth(1))
138            .expect_err("overflow must fail");
139        assert_eq!(
140            err,
141            RenderError::OutOfBudget(BudgetKind::DrawPrimitives {
142                attempted: 2,
143                max: 1
144            })
145        );
146    }
147}