Skip to main content

embedded_3dgfx/pipeline/
command_buffer.rs

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