1use crate::{Result, memory_error};
8use std::sync::Arc;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum AddressMode {
13 Clamp,
15 Wrap,
17 Mirror,
19 Border,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum FilterMode {
26 Point,
28 Linear,
30}
31
32#[derive(Debug, Clone)]
34pub struct TextureDescriptor {
35 pub width: usize,
36 pub height: usize,
37 pub depth: usize,
38 pub address_mode: AddressMode,
39 pub filter_mode: FilterMode,
40 pub normalized_coords: bool,
41}
42
43impl TextureDescriptor {
44 pub fn new_1d(width: usize) -> Self {
46 Self {
47 width,
48 height: 1,
49 depth: 1,
50 address_mode: AddressMode::Clamp,
51 filter_mode: FilterMode::Point,
52 normalized_coords: false,
53 }
54 }
55
56 pub fn new_2d(width: usize, height: usize) -> Self {
58 Self {
59 width,
60 height,
61 depth: 1,
62 address_mode: AddressMode::Clamp,
63 filter_mode: FilterMode::Point,
64 normalized_coords: false,
65 }
66 }
67
68 pub fn new_3d(width: usize, height: usize, depth: usize) -> Self {
70 Self {
71 width,
72 height,
73 depth,
74 address_mode: AddressMode::Clamp,
75 filter_mode: FilterMode::Point,
76 normalized_coords: false,
77 }
78 }
79
80 pub fn with_address_mode(mut self, mode: AddressMode) -> Self {
82 self.address_mode = mode;
83 self
84 }
85
86 pub fn with_filter_mode(mut self, mode: FilterMode) -> Self {
88 self.filter_mode = mode;
89 self
90 }
91
92 pub fn with_normalized_coords(mut self, normalized: bool) -> Self {
94 self.normalized_coords = normalized;
95 self
96 }
97}
98
99pub struct TextureMemory {
104 data: Vec<f32>,
105 descriptor: TextureDescriptor,
106}
107
108impl TextureMemory {
109 pub fn new(data: Vec<f32>, descriptor: TextureDescriptor) -> Result<Self> {
111 let expected = descriptor.width * descriptor.height * descriptor.depth;
112 if data.len() != expected {
113 return Err(memory_error!(
114 "Texture data length {} doesn't match dimensions {}x{}x{} = {}",
115 data.len(), descriptor.width, descriptor.height, descriptor.depth, expected
116 ));
117 }
118 Ok(Self { data, descriptor })
119 }
120
121 pub fn zeroed(descriptor: TextureDescriptor) -> Self {
123 let size = descriptor.width * descriptor.height * descriptor.depth;
124 Self {
125 data: vec![0.0; size],
126 descriptor,
127 }
128 }
129
130 pub fn descriptor(&self) -> &TextureDescriptor {
132 &self.descriptor
133 }
134
135 pub fn width(&self) -> usize {
137 self.descriptor.width
138 }
139
140 pub fn height(&self) -> usize {
142 self.descriptor.height
143 }
144
145 pub fn depth(&self) -> usize {
147 self.descriptor.depth
148 }
149
150 pub fn bind(&mut self, data: &[f32]) -> Result<()> {
152 let expected = self.descriptor.width * self.descriptor.height * self.descriptor.depth;
153 if data.len() != expected {
154 return Err(memory_error!(
155 "Data length {} doesn't match texture size {}",
156 data.len(), expected
157 ));
158 }
159 self.data.copy_from_slice(data);
160 Ok(())
161 }
162
163 pub fn sample_1d(&self, x: f32) -> f32 {
165 let fx = if self.descriptor.normalized_coords {
166 x * self.descriptor.width as f32
167 } else {
168 x
169 };
170
171 match self.descriptor.filter_mode {
172 FilterMode::Point => {
173 let ix = self.address_coord(fx.round() as isize, self.descriptor.width);
174 self.data[ix]
175 }
176 FilterMode::Linear => {
177 let x0 = fx.floor();
178 let frac = fx - x0;
179 let i0 = self.address_coord(x0 as isize, self.descriptor.width);
180 let i1 = self.address_coord(x0 as isize + 1, self.descriptor.width);
181 self.data[i0] * (1.0 - frac) + self.data[i1] * frac
182 }
183 }
184 }
185
186 pub fn sample_2d(&self, x: f32, y: f32) -> f32 {
188 let fx = if self.descriptor.normalized_coords {
189 x * self.descriptor.width as f32
190 } else {
191 x
192 };
193 let fy = if self.descriptor.normalized_coords {
194 y * self.descriptor.height as f32
195 } else {
196 y
197 };
198
199 match self.descriptor.filter_mode {
200 FilterMode::Point => {
201 let ix = self.address_coord(fx.round() as isize, self.descriptor.width);
202 let iy = self.address_coord(fy.round() as isize, self.descriptor.height);
203 self.data[iy * self.descriptor.width + ix]
204 }
205 FilterMode::Linear => {
206 let x0 = fx.floor();
207 let y0 = fy.floor();
208 let fx_frac = fx - x0;
209 let fy_frac = fy - y0;
210
211 let ix0 = self.address_coord(x0 as isize, self.descriptor.width);
212 let ix1 = self.address_coord(x0 as isize + 1, self.descriptor.width);
213 let iy0 = self.address_coord(y0 as isize, self.descriptor.height);
214 let iy1 = self.address_coord(y0 as isize + 1, self.descriptor.height);
215
216 let w = self.descriptor.width;
217 let v00 = self.data[iy0 * w + ix0];
218 let v10 = self.data[iy0 * w + ix1];
219 let v01 = self.data[iy1 * w + ix0];
220 let v11 = self.data[iy1 * w + ix1];
221
222 let top = v00 * (1.0 - fx_frac) + v10 * fx_frac;
223 let bot = v01 * (1.0 - fx_frac) + v11 * fx_frac;
224 top * (1.0 - fy_frac) + bot * fy_frac
225 }
226 }
227 }
228
229 pub fn sample_3d(&self, x: f32, y: f32, z: f32) -> f32 {
231 let fx = if self.descriptor.normalized_coords {
232 x * self.descriptor.width as f32
233 } else {
234 x
235 };
236 let fy = if self.descriptor.normalized_coords {
237 y * self.descriptor.height as f32
238 } else {
239 y
240 };
241 let fz = if self.descriptor.normalized_coords {
242 z * self.descriptor.depth as f32
243 } else {
244 z
245 };
246
247 let ix = self.address_coord(fx.round() as isize, self.descriptor.width);
248 let iy = self.address_coord(fy.round() as isize, self.descriptor.height);
249 let iz = self.address_coord(fz.round() as isize, self.descriptor.depth);
250
251 let w = self.descriptor.width;
252 let h = self.descriptor.height;
253 self.data[iz * w * h + iy * w + ix]
254 }
255
256 pub fn read_data(&self) -> &[f32] {
258 &self.data
259 }
260
261 pub fn write_texel(&mut self, x: usize, y: usize, value: f32) -> Result<()> {
263 if x >= self.descriptor.width || y >= self.descriptor.height {
264 return Err(memory_error!(
265 "Texel ({}, {}) out of bounds ({}x{})",
266 x, y, self.descriptor.width, self.descriptor.height
267 ));
268 }
269 self.data[y * self.descriptor.width + x] = value;
270 Ok(())
271 }
272
273 fn address_coord(&self, coord: isize, dim: usize) -> usize {
275 let d = dim as isize;
276 match self.descriptor.address_mode {
277 AddressMode::Clamp => coord.clamp(0, d - 1) as usize,
278 AddressMode::Wrap => ((coord % d + d) % d) as usize,
279 AddressMode::Mirror => {
280 let c = ((coord % (2 * d) + 2 * d) % (2 * d)) as usize;
281 if c < dim { c } else { 2 * dim - c - 1 }
282 }
283 AddressMode::Border => {
284 if coord < 0 || coord >= d { 0 } else { coord as usize }
285 }
286 }
287 }
288}
289
290pub type SharedTexture = Arc<TextureMemory>;
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 #[test]
298 fn test_texture_1d_point_sampling() {
299 let data = vec![1.0, 2.0, 3.0, 4.0];
300 let desc = TextureDescriptor::new_1d(4);
301 let tex = TextureMemory::new(data, desc).unwrap();
302
303 assert_eq!(tex.sample_1d(0.0), 1.0);
304 assert_eq!(tex.sample_1d(1.0), 2.0);
305 assert_eq!(tex.sample_1d(3.0), 4.0);
306 }
307
308 #[test]
309 fn test_texture_1d_linear_sampling() {
310 let data = vec![0.0, 10.0, 20.0, 30.0];
311 let desc = TextureDescriptor::new_1d(4).with_filter_mode(FilterMode::Linear);
312 let tex = TextureMemory::new(data, desc).unwrap();
313
314 assert!((tex.sample_1d(0.5) - 5.0).abs() < 1e-5);
315 assert!((tex.sample_1d(1.5) - 15.0).abs() < 1e-5);
316 }
317
318 #[test]
319 fn test_texture_2d_point_sampling() {
320 let data = vec![
321 1.0, 2.0, 3.0,
322 4.0, 5.0, 6.0,
323 ];
324 let desc = TextureDescriptor::new_2d(3, 2);
325 let tex = TextureMemory::new(data, desc).unwrap();
326
327 assert_eq!(tex.sample_2d(0.0, 0.0), 1.0);
328 assert_eq!(tex.sample_2d(2.0, 0.0), 3.0);
329 assert_eq!(tex.sample_2d(0.0, 1.0), 4.0);
330 assert_eq!(tex.sample_2d(2.0, 1.0), 6.0);
331 }
332
333 #[test]
334 fn test_texture_2d_bilinear_sampling() {
335 let data = vec![
336 0.0, 10.0,
337 10.0, 20.0,
338 ];
339 let desc = TextureDescriptor::new_2d(2, 2).with_filter_mode(FilterMode::Linear);
340 let tex = TextureMemory::new(data, desc).unwrap();
341
342 let center = tex.sample_2d(0.5, 0.5);
344 assert!((center - 10.0).abs() < 1e-5);
345 }
346
347 #[test]
348 fn test_texture_address_clamp() {
349 let data = vec![1.0, 2.0, 3.0, 4.0];
350 let desc = TextureDescriptor::new_1d(4).with_address_mode(AddressMode::Clamp);
351 let tex = TextureMemory::new(data, desc).unwrap();
352
353 assert_eq!(tex.sample_1d(-1.0), 1.0);
355 assert_eq!(tex.sample_1d(10.0), 4.0);
356 }
357
358 #[test]
359 fn test_texture_address_wrap() {
360 let data = vec![10.0, 20.0, 30.0, 40.0];
361 let desc = TextureDescriptor::new_1d(4).with_address_mode(AddressMode::Wrap);
362 let tex = TextureMemory::new(data, desc).unwrap();
363
364 assert_eq!(tex.sample_1d(4.0), 10.0); assert_eq!(tex.sample_1d(5.0), 20.0); }
367
368 #[test]
369 fn test_texture_normalized_coords() {
370 let data = vec![1.0, 2.0, 3.0, 4.0];
371 let desc = TextureDescriptor::new_1d(4).with_normalized_coords(true);
372 let tex = TextureMemory::new(data, desc).unwrap();
373
374 assert_eq!(tex.sample_1d(0.0), 1.0);
376 assert_eq!(tex.sample_1d(0.5), 3.0);
377 }
378
379 #[test]
380 fn test_texture_bind_data() {
381 let desc = TextureDescriptor::new_1d(4);
382 let mut tex = TextureMemory::zeroed(desc);
383
384 assert_eq!(tex.sample_1d(0.0), 0.0);
385 tex.bind(&[5.0, 6.0, 7.0, 8.0]).unwrap();
386 assert_eq!(tex.sample_1d(0.0), 5.0);
387 assert_eq!(tex.sample_1d(3.0), 8.0);
388 }
389
390 #[test]
391 fn test_texture_write_texel() {
392 let desc = TextureDescriptor::new_2d(4, 4);
393 let mut tex = TextureMemory::zeroed(desc);
394
395 tex.write_texel(2, 1, 42.0).unwrap();
396 assert_eq!(tex.sample_2d(2.0, 1.0), 42.0);
397 }
398
399 #[test]
400 fn test_texture_write_texel_out_of_bounds() {
401 let desc = TextureDescriptor::new_2d(4, 4);
402 let mut tex = TextureMemory::zeroed(desc);
403
404 assert!(tex.write_texel(10, 0, 1.0).is_err());
405 }
406
407 #[test]
408 fn test_texture_data_size_mismatch() {
409 let desc = TextureDescriptor::new_2d(3, 3);
410 let result = TextureMemory::new(vec![0.0; 5], desc);
411 assert!(result.is_err());
412 }
413
414 #[test]
415 fn test_texture_3d_sampling() {
416 let data = vec![0.0; 2 * 2 * 2];
417 let desc = TextureDescriptor::new_3d(2, 2, 2);
418 let mut tex = TextureMemory::new(data, desc).unwrap();
419
420 tex.data[1 * 2 * 2 + 1 * 2 + 1] = 99.0;
422 assert_eq!(tex.sample_3d(1.0, 1.0, 1.0), 99.0);
423 }
424}