embedded_3dgfx/
tilebin.rs1use crate::DrawPrimitive;
2use crate::command_buffer::{CommandBuffer, RenderCommand};
3use crate::error::RenderError;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub struct TileConfig {
7 pub tile_width: usize,
8 pub tile_height: usize,
9}
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct TileGrid {
13 pub cols: usize,
14 pub rows: usize,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct TileBinStats {
19 pub draw_commands: usize,
20 pub bins_used: usize,
21}
22
23fn primitive_bounds(primitive: &DrawPrimitive) -> (i32, i32, i32, i32) {
24 match primitive {
25 DrawPrimitive::ColoredPoint(p, _) => (p.x, p.y, p.x, p.y),
26 DrawPrimitive::Line([a, b], _) => (a.x.min(b.x), a.y.min(b.y), a.x.max(b.x), a.y.max(b.y)),
27 DrawPrimitive::ColoredTriangle(points, _)
28 | DrawPrimitive::ColoredTriangleWithDepth { points, .. }
29 | DrawPrimitive::GouraudTriangle { points, .. }
30 | DrawPrimitive::GouraudTriangleWithDepth { points, .. }
31 | DrawPrimitive::TexturedTriangle { points, .. }
32 | DrawPrimitive::TexturedTriangleWithDepth { points, .. } => {
33 let min_x = points.iter().map(|p| p.x).min().unwrap_or(0);
34 let min_y = points.iter().map(|p| p.y).min().unwrap_or(0);
35 let max_x = points.iter().map(|p| p.x).max().unwrap_or(0);
36 let max_y = points.iter().map(|p| p.y).max().unwrap_or(0);
37 (min_x, min_y, max_x, max_y)
38 }
39 }
40}
41
42pub fn tile_grid(width: usize, height: usize, config: TileConfig) -> Result<TileGrid, RenderError> {
43 if config.tile_width == 0 || config.tile_height == 0 {
44 return Err(RenderError::InvalidInput("tile dimensions must be >= 1"));
45 }
46 let cols = width.div_ceil(config.tile_width);
47 let rows = height.div_ceil(config.tile_height);
48 Ok(TileGrid { cols, rows })
49}
50
51pub fn build_bins<const MAX: usize, const BIN_CAP: usize>(
52 commands: &CommandBuffer<MAX>,
53 width: usize,
54 height: usize,
55 config: TileConfig,
56) -> Result<
57 (
58 heapless::Vec<heapless::Vec<usize, BIN_CAP>, BIN_CAP>,
59 TileBinStats,
60 ),
61 RenderError,
62> {
63 let grid = tile_grid(width, height, config)?;
64 let bin_count = grid.cols * grid.rows;
65 if bin_count > BIN_CAP {
66 return Err(RenderError::InvalidInput("tile bin count exceeds BIN_CAP"));
67 }
68
69 let mut bins: heapless::Vec<heapless::Vec<usize, BIN_CAP>, BIN_CAP> = heapless::Vec::new();
70 for _ in 0..bin_count {
71 bins.push(heapless::Vec::new())
72 .map_err(|_| RenderError::InvalidInput("unable to allocate tile bins"))?;
73 }
74
75 let mut draw_commands = 0usize;
76 for (idx, command) in commands.iter().enumerate() {
77 let RenderCommand::Draw(primitive) = command else {
78 continue;
79 };
80 draw_commands += 1;
81 let (min_x, min_y, max_x, max_y) = primitive_bounds(primitive);
82 let clamp =
83 |v: i32, max_v: usize| -> usize { v.clamp(0, max_v.saturating_sub(1) as i32) as usize };
84 let x0 = clamp(min_x, width) / config.tile_width;
85 let y0 = clamp(min_y, height) / config.tile_height;
86 let x1 = clamp(max_x, width) / config.tile_width;
87 let y1 = clamp(max_y, height) / config.tile_height;
88 for ty in y0..=y1 {
89 for tx in x0..=x1 {
90 let bin_index = ty * grid.cols + tx;
91 bins[bin_index].push(idx).map_err(|_| {
92 RenderError::OutOfBudget(crate::error::BudgetKind::DrawPrimitives {
93 attempted: idx + 1,
94 max: BIN_CAP,
95 })
96 })?;
97 }
98 }
99 }
100
101 let bins_used = bins.iter().filter(|bin| !bin.is_empty()).count();
102 Ok((
103 bins,
104 TileBinStats {
105 draw_commands,
106 bins_used,
107 },
108 ))
109}