embedded_3dgfx/
texture.rs1use embedded_graphics_core::pixelcolor::Rgb565;
8use heapless::Vec as HeaplessVec;
9
10#[derive(Debug, Clone, Copy)]
15pub struct Texture {
16 pub data: &'static [Rgb565],
18 pub width: u32,
20 pub height: u32,
22 width_mask: u32,
24 height_mask: u32,
26}
27
28impl Texture {
29 pub fn new(data: &'static [Rgb565], width: u32, height: u32) -> Self {
39 assert!(width.is_power_of_two(), "Texture width must be power of 2");
40 assert!(
41 height.is_power_of_two(),
42 "Texture height must be power of 2"
43 );
44 assert_eq!(
45 data.len(),
46 (width * height) as usize,
47 "Texture data length must match width × height"
48 );
49
50 Self {
51 data,
52 width,
53 height,
54 width_mask: width - 1,
55 height_mask: height - 1,
56 }
57 }
58
59 #[inline]
70 pub fn sample(&self, u: f32, v: f32) -> Rgb565 {
71 let tex_x = (u * self.width as f32) as u32;
73 let tex_y = (v * self.height as f32) as u32;
74
75 let tex_x = tex_x & self.width_mask;
77 let tex_y = tex_y & self.height_mask;
78
79 self.data[(tex_y * self.width + tex_x) as usize]
81 }
82
83 #[inline]
91 pub fn sample_fixed(&self, u_fixed: u32, v_fixed: u32) -> Rgb565 {
92 let tex_x = ((u_fixed >> 16) * self.width) >> 16;
95 let tex_y = ((v_fixed >> 16) * self.height) >> 16;
96
97 let tex_x = tex_x & self.width_mask;
99 let tex_y = tex_y & self.height_mask;
100
101 self.data[(tex_y * self.width + tex_x) as usize]
102 }
103
104 pub fn dimensions(&self) -> (u32, u32) {
106 (self.width, self.height)
107 }
108}
109
110pub struct TextureManager<const N: usize> {
115 textures: HeaplessVec<Texture, N>,
116}
117
118impl<const N: usize> TextureManager<N> {
119 pub fn new() -> Self {
121 Self {
122 textures: HeaplessVec::new(),
123 }
124 }
125
126 pub fn add_texture(&mut self, texture: Texture) -> Option<u32> {
133 self.textures.push(texture).ok()?;
134 Some((self.textures.len() - 1) as u32)
135 }
136
137 pub fn get(&self, id: u32) -> Option<&Texture> {
145 self.textures.get(id as usize)
146 }
147
148 pub fn len(&self) -> usize {
150 self.textures.len()
151 }
152
153 pub fn is_empty(&self) -> bool {
155 self.textures.is_empty()
156 }
157
158 pub fn is_full(&self) -> bool {
160 self.textures.len() >= N
161 }
162}
163
164impl<const N: usize> Default for TextureManager<N> {
165 fn default() -> Self {
166 Self::new()
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 extern crate std;
173 use super::*;
174 use embedded_graphics_core::pixelcolor::{Rgb565, WebColors};
175
176 #[test]
177 fn test_texture_creation() {
178 static DATA: [Rgb565; 64] = [Rgb565::CSS_RED; 64];
179 let texture = Texture::new(&DATA, 8, 8);
180
181 assert_eq!(texture.width, 8);
182 assert_eq!(texture.height, 8);
183 assert_eq!(texture.dimensions(), (8, 8));
184 }
185
186 #[test]
187 #[should_panic(expected = "width must be power of 2")]
188 fn test_texture_non_power_of_2_width() {
189 static DATA: [Rgb565; 60] = [Rgb565::CSS_RED; 60];
190 let _texture = Texture::new(&DATA, 10, 6); }
192
193 #[test]
194 #[should_panic(expected = "height must be power of 2")]
195 fn test_texture_non_power_of_2_height() {
196 static DATA: [Rgb565; 48] = [Rgb565::CSS_RED; 48];
197 let _texture = Texture::new(&DATA, 8, 6); }
199
200 #[test]
201 #[should_panic(expected = "length must match")]
202 fn test_texture_wrong_data_length() {
203 static DATA: [Rgb565; 60] = [Rgb565::CSS_RED; 60];
204 let _texture = Texture::new(&DATA, 8, 8); }
206
207 #[test]
208 fn test_texture_sampling() {
209 static DATA: [Rgb565; 16] = [
210 Rgb565::CSS_RED,
211 Rgb565::CSS_GREEN,
212 Rgb565::CSS_BLUE,
213 Rgb565::CSS_YELLOW,
214 Rgb565::CSS_CYAN,
215 Rgb565::CSS_MAGENTA,
216 Rgb565::CSS_WHITE,
217 Rgb565::CSS_BLACK,
218 Rgb565::CSS_RED,
219 Rgb565::CSS_GREEN,
220 Rgb565::CSS_BLUE,
221 Rgb565::CSS_YELLOW,
222 Rgb565::CSS_CYAN,
223 Rgb565::CSS_MAGENTA,
224 Rgb565::CSS_WHITE,
225 Rgb565::CSS_BLACK,
226 ];
227
228 let texture = Texture::new(&DATA, 4, 4);
229
230 let tl = texture.sample(0.0, 0.0);
232 assert_eq!(tl, Rgb565::CSS_RED);
233
234 let mid = texture.sample(0.5, 0.5);
236 assert_eq!(mid, Rgb565::CSS_BLUE);
237 }
238
239 #[test]
240 fn test_texture_wrapping() {
241 static DATA: [Rgb565; 16] = [Rgb565::CSS_RED; 16];
242 let texture = Texture::new(&DATA, 4, 4);
243
244 let wrapped = texture.sample(1.5, 1.5);
246 assert_eq!(wrapped, Rgb565::CSS_RED);
247 }
248
249 #[test]
250 fn test_texture_manager() {
251 static DATA1: [Rgb565; 16] = [Rgb565::CSS_RED; 16];
252 static DATA2: [Rgb565; 64] = [Rgb565::CSS_GREEN; 64];
253
254 let mut manager = TextureManager::<4>::new();
255
256 assert!(manager.is_empty());
257 assert!(!manager.is_full());
258
259 let id1 = manager.add_texture(Texture::new(&DATA1, 4, 4));
260 assert_eq!(id1, Some(0));
261 assert_eq!(manager.len(), 1);
262
263 let id2 = manager.add_texture(Texture::new(&DATA2, 8, 8));
264 assert_eq!(id2, Some(1));
265 assert_eq!(manager.len(), 2);
266
267 let tex1 = manager.get(0).unwrap();
269 assert_eq!(tex1.width, 4);
270
271 let tex2 = manager.get(1).unwrap();
272 assert_eq!(tex2.width, 8);
273 }
274
275 #[test]
276 fn test_texture_manager_full() {
277 static DATA: [Rgb565; 16] = [Rgb565::CSS_RED; 16];
278
279 let mut manager = TextureManager::<2>::new();
280
281 assert!(manager.add_texture(Texture::new(&DATA, 4, 4)).is_some());
283 assert!(manager.add_texture(Texture::new(&DATA, 4, 4)).is_some());
284 assert!(manager.is_full());
285
286 assert!(manager.add_texture(Texture::new(&DATA, 4, 4)).is_none());
288 }
289}