embedded_3dgfx/pipeline/
command_buffer.rs1use 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}