1#![allow(clippy::wrong_self_convention)]
2
3use crate::device::{DropManager, ResourceId};
4use crate::{Device, DeviceBackend};
5use notan_math::Rect;
6use std::fmt::{Debug, Formatter};
7use std::sync::Arc;
8
9pub trait TextureSource {
10 fn create(
11 &self,
12 device: &mut dyn DeviceBackend,
13 info: TextureInfo,
14 ) -> Result<(u64, TextureInfo), String>;
15
16 fn update(&self, device: &mut dyn DeviceBackend, opts: TextureUpdate) -> Result<(), String>;
17}
18
19#[derive(Debug)]
20pub struct TextureRead {
21 pub x_offset: u32,
22 pub y_offset: u32,
23 pub width: u32,
24 pub height: u32,
25 pub format: TextureFormat,
26}
27
28#[derive(Debug, Clone)]
29pub struct TextureUpdate {
30 pub x_offset: u32,
31 pub y_offset: u32,
32 pub width: u32,
33 pub height: u32,
34 pub format: TextureFormat,
35}
36
37#[derive(Debug, Clone)]
38pub struct TextureInfo {
39 pub width: u32,
40 pub height: u32,
41 pub format: TextureFormat,
42 pub min_filter: TextureFilter,
43 pub mag_filter: TextureFilter,
44 pub wrap_x: TextureWrap,
45 pub wrap_y: TextureWrap,
46 pub premultiplied_alpha: bool,
47 pub mipmap_filter: Option<TextureFilter>,
48
49 pub depth: bool,
51}
52
53impl Default for TextureInfo {
54 fn default() -> Self {
55 Self {
56 format: TextureFormat::Rgba32,
57 mag_filter: TextureFilter::Nearest,
58 min_filter: TextureFilter::Nearest,
59 wrap_x: TextureWrap::Clamp,
60 wrap_y: TextureWrap::Clamp,
61 width: 1,
62 height: 1,
63 depth: false,
64 premultiplied_alpha: false,
65 mipmap_filter: None,
66 }
67 }
68}
69
70impl TextureInfo {
71 #[inline]
72 pub fn bytes_per_pixel(&self) -> u8 {
73 self.format.bytes_per_pixel()
74 }
75}
76
77impl TextureFormat {
78 pub fn bytes_per_pixel(&self) -> u8 {
79 use TextureFormat::*;
80 match self {
81 R8 => 1,
82 R8Uint => 1,
83 R16Uint => 2,
84 Rgb24 => 3,
85 Rgba32Float => 4 * 4,
86 _ => 4,
87 }
88 }
89}
90
91struct TextureIdRef {
92 id: u64,
93 drop_manager: Arc<DropManager>,
94}
95
96impl Drop for TextureIdRef {
97 fn drop(&mut self) {
98 self.drop_manager.push(ResourceId::Texture(self.id));
99 }
100}
101
102impl Debug for TextureIdRef {
103 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
104 write!(f, "TextureIdRef({})", self.id)
105 }
106}
107
108#[derive(Debug, Clone)]
109pub struct Texture {
110 id: u64,
111 _id_ref: Arc<TextureIdRef>,
112 width: u32,
113 height: u32,
114 format: TextureFormat,
115 min_filter: TextureFilter,
116 mag_filter: TextureFilter,
117 frame: Rect,
118 pub(crate) is_render_texture: bool,
119}
120
121impl Texture {
123 pub(crate) fn new(id: u64, info: TextureInfo, drop_manager: Arc<DropManager>) -> Self {
124 let id_ref = Arc::new(TextureIdRef { id, drop_manager });
125
126 let TextureInfo {
127 width,
128 height,
129 format,
130 min_filter,
131 mag_filter,
132 ..
133 } = info;
134
135 let frame = Rect {
137 x: 0.0,
138 y: 0.0,
139 width: width as _,
140 height: height as _,
141 };
142
143 Self {
144 id,
145 _id_ref: id_ref,
146 width,
147 height,
148 format,
149 min_filter,
150 mag_filter,
151 frame,
152 is_render_texture: false,
153 }
154 }
155
156 #[inline(always)]
157 pub fn id(&self) -> u64 {
158 self.id
159 }
160
161 #[inline(always)]
162 pub fn format(&self) -> &TextureFormat {
163 &self.format
164 }
165
166 #[inline(always)]
167 pub fn min_filter(&self) -> &TextureFilter {
168 &self.min_filter
169 }
170
171 #[inline(always)]
172 pub fn mag_filter(&self) -> &TextureFilter {
173 &self.mag_filter
174 }
175
176 #[inline(always)]
177 pub fn frame(&self) -> &Rect {
178 &self.frame
179 }
180
181 #[inline]
182 pub fn with_frame(&self, x: f32, y: f32, width: f32, height: f32) -> Texture {
183 let frame = Rect {
184 x,
185 y,
186 width,
187 height,
188 };
189
190 let mut texture = self.clone();
191 texture.frame = frame;
192 texture
193 }
194
195 #[inline(always)]
196 pub fn width(&self) -> f32 {
197 self.frame.width
198 }
199
200 #[inline(always)]
201 pub fn height(&self) -> f32 {
202 self.frame.height
203 }
204
205 #[inline(always)]
206 pub fn base_width(&self) -> f32 {
207 self.width as _
208 }
209
210 #[inline(always)]
211 pub fn base_height(&self) -> f32 {
212 self.height as _
213 }
214
215 pub fn size(&self) -> (f32, f32) {
216 (self.frame.width, self.frame.height)
217 }
218
219 pub fn base_size(&self) -> (f32, f32) {
220 (self.width as _, self.height as _)
221 }
222
223 #[cfg(feature = "texture_to_file")]
224 pub fn to_file<P: AsRef<std::path::Path>>(
225 &self,
226 gfx: &mut Device,
227 path: P,
228 ) -> Result<(), String> {
229 crate::to_file::save_to_png_file(gfx, self, false, path)
230 }
231
232 pub fn is_render_texture(&self) -> bool {
233 self.is_render_texture
234 }
235}
236
237impl std::cmp::PartialEq for Texture {
238 fn eq(&self, other: &Self) -> bool {
239 self.id() == other.id() && self.frame() == other.frame()
240 }
241}
242
243impl AsRef<Texture> for Texture {
244 fn as_ref(&self) -> &Texture {
245 self
246 }
247}
248
249#[derive(Debug, Clone, Copy, Eq, PartialEq)]
251pub enum TextureFormat {
252 SRgba8,
253 Rgb24,
254 Rgba32,
255 R8,
256 R8Uint,
257 R16Uint,
258 R32Float,
259 R32Uint,
260 Depth16,
261 Rgba32Float,
262}
263
264#[derive(Debug, Clone, Copy, Eq, PartialEq)]
265pub enum TextureFilter {
266 Linear,
267 Nearest,
268}
269
270#[derive(Debug, Clone, Copy, Eq, PartialEq)]
271pub enum TextureWrap {
272 Clamp,
273 Repeat,
274}
275
276enum TextureKind<'a> {
277 Image(&'a [u8]),
278 Bytes(&'a [u8]),
279 EmptyBuffer,
280}
281
282pub enum TextureSourceKind {
283 Empty,
284 Image(Vec<u8>),
285 Bytes(Vec<u8>),
286 Raw(Box<dyn TextureSource>),
287}
288
289pub enum TextureUpdaterSourceKind<'a> {
290 Bytes(&'a [u8]),
291 Raw(Box<dyn TextureSource>),
292}
293
294impl Debug for TextureUpdaterSourceKind<'_> {
295 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
296 write!(
297 f,
298 "{}",
299 match self {
300 TextureUpdaterSourceKind::Bytes(bytes) => format!("Bytes({bytes:?})"),
301 TextureUpdaterSourceKind::Raw(_) => "Raw(dyn TextureSource)".to_string(), }
303 )
304 }
305}
306
307pub struct TextureBuilder<'a, 'b> {
308 device: &'a mut Device,
309 info: TextureInfo,
310 kind: Option<TextureKind<'b>>,
311 source: Option<TextureSourceKind>,
312}
313
314impl<'a, 'b> TextureBuilder<'a, 'b> {
315 pub fn new(device: &'a mut Device) -> Self {
316 Self {
317 device,
318 info: Default::default(),
319 kind: None,
320 source: None,
321 }
322 }
323
324 pub fn from_source<S: TextureSource + 'static>(mut self, source: S) -> Self {
327 self.kind = None;
328 self.source = Some(TextureSourceKind::Raw(Box::new(source)));
329 self
330 }
331
332 pub fn from_image(mut self, bytes: &'b [u8]) -> Self {
334 self.source = None;
335 self.kind = Some(TextureKind::Image(bytes)); self
337 }
338
339 pub fn from_bytes(mut self, bytes: &'b [u8], width: u32, height: u32) -> Self {
341 self.source = None;
342 self.kind = Some(TextureKind::Bytes(bytes));
343 self.info.width = width;
344 self.info.height = height;
345 self
346 }
347
348 pub fn from_empty_buffer(mut self, width: u32, height: u32) -> Self {
350 self.source = None;
351 self.kind = Some(TextureKind::EmptyBuffer);
352 self.with_size(width, height)
353 }
354
355 pub fn with_size(mut self, width: u32, height: u32) -> Self {
357 self.info.width = width;
358 self.info.height = height;
359 self
360 }
361
362 pub fn with_depth(mut self) -> Self {
364 self.info.depth = true;
365 self
366 }
367
368 pub fn with_format(mut self, format: TextureFormat) -> Self {
370 self.info.format = format;
371 self
372 }
373
374 pub fn with_filter(mut self, min: TextureFilter, mag: TextureFilter) -> Self {
376 self.info.min_filter = min;
377 self.info.mag_filter = mag;
378 self
379 }
380
381 pub fn with_wrap(mut self, x: TextureWrap, y: TextureWrap) -> Self {
383 self.info.wrap_x = x;
384 self.info.wrap_y = y;
385 self
386 }
387
388 pub fn with_premultiplied_alpha(mut self) -> Self {
390 self.info.premultiplied_alpha = true;
391 self
392 }
393
394 pub fn with_mipmaps(mut self, enable: bool) -> Self {
396 if enable {
397 self.info.mipmap_filter = Some(TextureFilter::Linear);
398 } else {
399 self.info.mipmap_filter = None;
400 }
401 self
402 }
403
404 pub fn with_mipmap_filter(mut self, filter: TextureFilter) -> Self {
406 self.info.mipmap_filter = Some(filter);
407 self
408 }
409
410 pub fn build(self) -> Result<Texture, String> {
411 let TextureBuilder {
412 info,
413 device,
414 kind,
415 mut source,
416 } = self;
417
418 match kind {
419 Some(TextureKind::Image(bytes)) => {
420 source = Some(TextureSourceKind::Image(bytes.to_vec()));
421 }
422 Some(TextureKind::Bytes(bytes)) => {
423 let size = (info.width * info.height * (info.bytes_per_pixel() as u32)) as usize;
424 if bytes.len() != size {
425 return Err(format!(
426 "Texture type {:?} with {} bytes, when it should be {} (width: {} * height: {} * bytes: {})",
427 info.format,
428 bytes.len(),
429 size,
430 info.width,
431 info.height,
432 info.bytes_per_pixel()
433 ));
434 }
435
436 source = Some(TextureSourceKind::Bytes(bytes.to_vec()));
437 }
438 Some(TextureKind::EmptyBuffer) => {
439 let size = info.width * info.height * (info.bytes_per_pixel() as u32);
440 source = Some(TextureSourceKind::Bytes(vec![0; size as _]));
441 }
442 None => {}
443 }
444
445 let s = source.unwrap_or(TextureSourceKind::Empty);
446 device.inner_create_texture(s, info)
447 }
448}
449
450pub struct TextureReader<'a> {
451 device: &'a mut Device,
452 texture: &'a Texture,
453 x_offset: u32,
454 y_offset: u32,
455 width: u32,
456 height: u32,
457 format: TextureFormat,
458}
459
460impl<'a> TextureReader<'a> {
461 pub fn new(device: &'a mut Device, texture: &'a Texture) -> Self {
462 let rect = *texture.frame();
463 let x_offset = rect.x as _;
464 let y_offset = rect.y as _;
465 let width = rect.width as _;
466 let height = rect.height as _;
467 let format = texture.format;
468 Self {
469 device,
470 texture,
471 x_offset,
472 y_offset,
473 width,
474 height,
475 format,
476 }
477 }
478
479 pub fn with_x_offset(mut self, offset: u32) -> Self {
481 self.x_offset = offset;
482 self
483 }
484
485 pub fn with_y_offset(mut self, offset: u32) -> Self {
487 self.y_offset = offset;
488 self
489 }
490
491 pub fn with_width(mut self, width: u32) -> Self {
493 self.width = width;
494 self
495 }
496
497 pub fn with_height(mut self, height: u32) -> Self {
499 self.height = height;
500 self
501 }
502
503 pub fn read_to(self, bytes: &mut [u8]) -> Result<(), String> {
504 let Self {
505 device,
506 texture,
507 x_offset,
508 y_offset,
509 width,
510 height,
511 format,
512 } = self;
513
514 let info = TextureRead {
515 x_offset,
516 y_offset,
517 width,
518 height,
519 format,
520 };
521
522 device.inner_read_pixels(texture, bytes, &info)
523 }
524}
525
526pub struct TextureUpdater<'a> {
527 device: &'a mut Device,
528 texture: &'a mut Texture,
529 x_offset: u32,
530 y_offset: u32,
531 width: u32,
532 height: u32,
533 format: TextureFormat,
534 source: Option<TextureUpdaterSourceKind<'a>>,
535}
536
537impl<'a> TextureUpdater<'a> {
538 pub fn new(device: &'a mut Device, texture: &'a mut Texture) -> Self {
539 let x_offset = texture.frame.x as _;
540 let y_offset = texture.frame.y as _;
541 let width = texture.frame.width as _;
542 let height = texture.frame.height as _;
543 let format = texture.format;
544
545 Self {
546 device,
547 texture,
548 x_offset,
549 y_offset,
550 width,
551 height,
552 format,
553 source: None,
554 }
555 }
556
557 pub fn with_x_offset(mut self, offset: u32) -> Self {
559 self.x_offset = offset;
560 self
561 }
562
563 pub fn with_y_offset(mut self, offset: u32) -> Self {
565 self.y_offset = offset;
566 self
567 }
568
569 pub fn with_width(mut self, width: u32) -> Self {
571 self.width = width;
572 self
573 }
574
575 pub fn with_height(mut self, height: u32) -> Self {
577 self.height = height;
578 self
579 }
580
581 pub fn with_source<S: TextureSource + 'static>(mut self, source: S) -> Self {
582 self.source = Some(TextureUpdaterSourceKind::Raw(Box::new(source)));
583 self
584 }
585
586 pub fn with_data(mut self, bytes: &'a [u8]) -> Self {
587 self.source = Some(TextureUpdaterSourceKind::Bytes(bytes));
588 self
589 }
590
591 pub fn update(self) -> Result<(), String> {
592 let Self {
593 device,
594 texture,
595 x_offset,
596 y_offset,
597 width,
598 height,
599 format,
600 source,
601 } = self;
602
603 let source =
604 source.ok_or_else(|| "You need to provide bytes to update a texture".to_string())?;
605
606 let info = TextureUpdate {
607 x_offset,
608 y_offset,
609 width,
610 height,
611 format,
612 };
613
614 device.inner_update_texture(texture, source, info)
615 }
616}