notan_graphics/
texture.rs

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    /// Used for render textures
50    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            Rgba32Float => 4 * 4,
85            _ => 4,
86        }
87    }
88}
89
90struct TextureIdRef {
91    id: u64,
92    drop_manager: Arc<DropManager>,
93}
94
95impl Drop for TextureIdRef {
96    fn drop(&mut self) {
97        self.drop_manager.push(ResourceId::Texture(self.id));
98    }
99}
100
101impl Debug for TextureIdRef {
102    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
103        write!(f, "TextureIdRef({})", self.id)
104    }
105}
106
107#[derive(Debug, Clone)]
108pub struct Texture {
109    id: u64,
110    _id_ref: Arc<TextureIdRef>,
111    width: u32,
112    height: u32,
113    format: TextureFormat,
114    min_filter: TextureFilter,
115    mag_filter: TextureFilter,
116    frame: Rect,
117    pub(crate) is_render_texture: bool,
118}
119
120//https://sotrh.github.io/learn-wgpu/beginner/tutorial5-textures/#getting-data-into-a-texture
121impl Texture {
122    pub(crate) fn new(id: u64, info: TextureInfo, drop_manager: Arc<DropManager>) -> Self {
123        let id_ref = Arc::new(TextureIdRef { id, drop_manager });
124
125        let TextureInfo {
126            width,
127            height,
128            format,
129            min_filter,
130            mag_filter,
131            ..
132        } = info;
133
134        // let data = Arc::new(bytes);
135        let frame = Rect {
136            x: 0.0,
137            y: 0.0,
138            width: width as _,
139            height: height as _,
140        };
141
142        Self {
143            id,
144            _id_ref: id_ref,
145            width,
146            height,
147            format,
148            min_filter,
149            mag_filter,
150            frame,
151            is_render_texture: false,
152        }
153    }
154
155    #[inline(always)]
156    pub fn id(&self) -> u64 {
157        self.id
158    }
159
160    #[inline(always)]
161    pub fn format(&self) -> &TextureFormat {
162        &self.format
163    }
164
165    #[inline(always)]
166    pub fn min_filter(&self) -> &TextureFilter {
167        &self.min_filter
168    }
169
170    #[inline(always)]
171    pub fn mag_filter(&self) -> &TextureFilter {
172        &self.mag_filter
173    }
174
175    #[inline(always)]
176    pub fn frame(&self) -> &Rect {
177        &self.frame
178    }
179
180    #[inline]
181    pub fn with_frame(&self, x: f32, y: f32, width: f32, height: f32) -> Texture {
182        let frame = Rect {
183            x,
184            y,
185            width,
186            height,
187        };
188
189        let mut texture = self.clone();
190        texture.frame = frame;
191        texture
192    }
193
194    #[inline(always)]
195    pub fn width(&self) -> f32 {
196        self.frame.width
197    }
198
199    #[inline(always)]
200    pub fn height(&self) -> f32 {
201        self.frame.height
202    }
203
204    #[inline(always)]
205    pub fn base_width(&self) -> f32 {
206        self.width as _
207    }
208
209    #[inline(always)]
210    pub fn base_height(&self) -> f32 {
211        self.height as _
212    }
213
214    pub fn size(&self) -> (f32, f32) {
215        (self.frame.width, self.frame.height)
216    }
217
218    pub fn base_size(&self) -> (f32, f32) {
219        (self.width as _, self.height as _)
220    }
221
222    #[cfg(feature = "texture_to_file")]
223    pub fn to_file<P: AsRef<std::path::Path>>(
224        &self,
225        gfx: &mut Device,
226        path: P,
227    ) -> Result<(), String> {
228        crate::to_file::save_to_png_file(gfx, self, false, path)
229    }
230
231    pub fn is_render_texture(&self) -> bool {
232        self.is_render_texture
233    }
234}
235
236impl std::cmp::PartialEq for Texture {
237    fn eq(&self, other: &Self) -> bool {
238        self.id() == other.id() && self.frame() == other.frame()
239    }
240}
241
242impl AsRef<Texture> for Texture {
243    fn as_ref(&self) -> &Texture {
244        self
245    }
246}
247
248// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texImage2D
249#[derive(Debug, Clone, Copy, Eq, PartialEq)]
250pub enum TextureFormat {
251    SRgba8,
252    Rgba32,
253    R8,
254    R8Uint,
255    R16Uint,
256    R32Float,
257    R32Uint,
258    Depth16,
259    Rgba32Float,
260}
261
262#[derive(Debug, Clone, Copy, Eq, PartialEq)]
263pub enum TextureFilter {
264    Linear,
265    Nearest,
266}
267
268#[derive(Debug, Clone, Copy, Eq, PartialEq)]
269pub enum TextureWrap {
270    Clamp,
271    Repeat,
272}
273
274enum TextureKind<'a> {
275    Image(&'a [u8]),
276    Bytes(&'a [u8]),
277    EmptyBuffer,
278}
279
280pub enum TextureSourceKind {
281    Empty,
282    Image(Vec<u8>),
283    Bytes(Vec<u8>),
284    Raw(Box<dyn TextureSource>),
285}
286
287pub enum TextureUpdaterSourceKind<'a> {
288    Bytes(&'a [u8]),
289    Raw(Box<dyn TextureSource>),
290}
291
292impl Debug for TextureUpdaterSourceKind<'_> {
293    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
294        write!(
295            f,
296            "{}",
297            match self {
298                TextureUpdaterSourceKind::Bytes(bytes) => format!("Bytes({bytes:?})"),
299                TextureUpdaterSourceKind::Raw(_) => "Raw(dyn TextureSource)".to_string(), // todo assign an id to TextureSource?
300            }
301        )
302    }
303}
304
305pub struct TextureBuilder<'a, 'b> {
306    device: &'a mut Device,
307    info: TextureInfo,
308    kind: Option<TextureKind<'b>>,
309    source: Option<TextureSourceKind>,
310}
311
312impl<'a, 'b> TextureBuilder<'a, 'b> {
313    pub fn new(device: &'a mut Device) -> Self {
314        Self {
315            device,
316            info: Default::default(),
317            kind: None,
318            source: None,
319        }
320    }
321
322    /// Creates a texture from source's raw type
323    /// Check [TextureSource]
324    pub fn from_source<S: TextureSource + 'static>(mut self, source: S) -> Self {
325        self.kind = None;
326        self.source = Some(TextureSourceKind::Raw(Box::new(source)));
327        self
328    }
329
330    /// Creates a Texture from an image
331    pub fn from_image(mut self, bytes: &'b [u8]) -> Self {
332        self.source = None;
333        self.kind = Some(TextureKind::Image(bytes)); // TODO remove
334        self
335    }
336
337    /// Creates a Texture from a buffer of pixels
338    pub fn from_bytes(mut self, bytes: &'b [u8], width: u32, height: u32) -> Self {
339        self.source = None;
340        self.kind = Some(TextureKind::Bytes(bytes));
341        self.info.width = width;
342        self.info.height = height;
343        self
344    }
345
346    /// Creates a buffer for the size passed in and creates a Texture with it
347    pub fn from_empty_buffer(mut self, width: u32, height: u32) -> Self {
348        self.source = None;
349        self.kind = Some(TextureKind::EmptyBuffer);
350        self.with_size(width, height)
351    }
352
353    /// Set the size of the texture (ignored if used with `from_image`, image size will be used instead)
354    pub fn with_size(mut self, width: u32, height: u32) -> Self {
355        self.info.width = width;
356        self.info.height = height;
357        self
358    }
359
360    /// Enable depth
361    pub fn with_depth(mut self) -> Self {
362        self.info.depth = true;
363        self
364    }
365
366    /// Set the Texture format (ignored if used with `from_image`, Rgba will be used instead )
367    pub fn with_format(mut self, format: TextureFormat) -> Self {
368        self.info.format = format;
369        self
370    }
371
372    /// Set the Texture filter modes
373    pub fn with_filter(mut self, min: TextureFilter, mag: TextureFilter) -> Self {
374        self.info.min_filter = min;
375        self.info.mag_filter = mag;
376        self
377    }
378
379    /// Set the texture wrap modes (x -> s, y -> t)
380    pub fn with_wrap(mut self, x: TextureWrap, y: TextureWrap) -> Self {
381        self.info.wrap_x = x;
382        self.info.wrap_y = y;
383        self
384    }
385
386    /// Process the texels to multiply the rgb values by the alpha
387    pub fn with_premultiplied_alpha(mut self) -> Self {
388        self.info.premultiplied_alpha = true;
389        self
390    }
391
392    /// Toggle mipmap generation (with Linear filter if enabled)
393    pub fn with_mipmaps(mut self, enable: bool) -> Self {
394        if enable {
395            self.info.mipmap_filter = Some(TextureFilter::Linear);
396        } else {
397            self.info.mipmap_filter = None;
398        }
399        self
400    }
401
402    /// Set mipmap filtering function
403    pub fn with_mipmap_filter(mut self, filter: TextureFilter) -> Self {
404        self.info.mipmap_filter = Some(filter);
405        self
406    }
407
408    pub fn build(self) -> Result<Texture, String> {
409        let TextureBuilder {
410            info,
411            device,
412            kind,
413            mut source,
414        } = self;
415
416        match kind {
417            Some(TextureKind::Image(bytes)) => {
418                source = Some(TextureSourceKind::Image(bytes.to_vec()));
419            }
420            Some(TextureKind::Bytes(bytes)) => {
421                let size = (info.width * info.height * (info.bytes_per_pixel() as u32)) as usize;
422                if bytes.len() != size {
423                    return Err(format!(
424                        "Texture type {:?} with {} bytes, when it should be {} (width: {} * height: {} * bytes: {})",
425                        info.format,
426                        bytes.len(),
427                        size,
428                        info.width,
429                        info.height,
430                        info.bytes_per_pixel()
431                    ));
432                }
433
434                source = Some(TextureSourceKind::Bytes(bytes.to_vec()));
435            }
436            Some(TextureKind::EmptyBuffer) => {
437                let size = info.width * info.height * (info.bytes_per_pixel() as u32);
438                source = Some(TextureSourceKind::Bytes(vec![0; size as _]));
439            }
440            None => {}
441        }
442
443        let s = source.unwrap_or(TextureSourceKind::Empty);
444        device.inner_create_texture(s, info)
445    }
446}
447
448pub struct TextureReader<'a> {
449    device: &'a mut Device,
450    texture: &'a Texture,
451    x_offset: u32,
452    y_offset: u32,
453    width: u32,
454    height: u32,
455    format: TextureFormat,
456}
457
458impl<'a> TextureReader<'a> {
459    pub fn new(device: &'a mut Device, texture: &'a Texture) -> Self {
460        let rect = *texture.frame();
461        let x_offset = rect.x as _;
462        let y_offset = rect.y as _;
463        let width = rect.width as _;
464        let height = rect.height as _;
465        let format = texture.format;
466        Self {
467            device,
468            texture,
469            x_offset,
470            y_offset,
471            width,
472            height,
473            format,
474        }
475    }
476
477    /// Read pixels from the axis x offset
478    pub fn with_x_offset(mut self, offset: u32) -> Self {
479        self.x_offset = offset;
480        self
481    }
482
483    /// Read pixels from the axis y offset
484    pub fn with_y_offset(mut self, offset: u32) -> Self {
485        self.y_offset = offset;
486        self
487    }
488
489    /// Read pixels until this width from the x offset value
490    pub fn with_width(mut self, width: u32) -> Self {
491        self.width = width;
492        self
493    }
494
495    /// Read pixels until this height from the y offset value
496    pub fn with_height(mut self, height: u32) -> Self {
497        self.height = height;
498        self
499    }
500
501    pub fn read_to(self, bytes: &mut [u8]) -> Result<(), String> {
502        let Self {
503            device,
504            texture,
505            x_offset,
506            y_offset,
507            width,
508            height,
509            format,
510        } = self;
511
512        let info = TextureRead {
513            x_offset,
514            y_offset,
515            width,
516            height,
517            format,
518        };
519
520        device.inner_read_pixels(texture, bytes, &info)
521    }
522}
523
524pub struct TextureUpdater<'a> {
525    device: &'a mut Device,
526    texture: &'a mut Texture,
527    x_offset: u32,
528    y_offset: u32,
529    width: u32,
530    height: u32,
531    format: TextureFormat,
532    source: Option<TextureUpdaterSourceKind<'a>>,
533}
534
535impl<'a> TextureUpdater<'a> {
536    pub fn new(device: &'a mut Device, texture: &'a mut Texture) -> Self {
537        let x_offset = texture.frame.x as _;
538        let y_offset = texture.frame.y as _;
539        let width = texture.frame.width as _;
540        let height = texture.frame.height as _;
541        let format = texture.format;
542
543        Self {
544            device,
545            texture,
546            x_offset,
547            y_offset,
548            width,
549            height,
550            format,
551            source: None,
552        }
553    }
554
555    /// Update pixels from the axis x offset
556    pub fn with_x_offset(mut self, offset: u32) -> Self {
557        self.x_offset = offset;
558        self
559    }
560
561    /// Update pixels from the axis y offset
562    pub fn with_y_offset(mut self, offset: u32) -> Self {
563        self.y_offset = offset;
564        self
565    }
566
567    /// Update pixels until this width from the x offset value
568    pub fn with_width(mut self, width: u32) -> Self {
569        self.width = width;
570        self
571    }
572
573    /// Update pixels until this height from the y offset value
574    pub fn with_height(mut self, height: u32) -> Self {
575        self.height = height;
576        self
577    }
578
579    pub fn with_source<S: TextureSource + 'static>(mut self, source: S) -> Self {
580        self.source = Some(TextureUpdaterSourceKind::Raw(Box::new(source)));
581        self
582    }
583
584    pub fn with_data(mut self, bytes: &'a [u8]) -> Self {
585        self.source = Some(TextureUpdaterSourceKind::Bytes(bytes));
586        self
587    }
588
589    pub fn update(self) -> Result<(), String> {
590        let Self {
591            device,
592            texture,
593            x_offset,
594            y_offset,
595            width,
596            height,
597            format,
598            source,
599        } = self;
600
601        let source =
602            source.ok_or_else(|| "You need to provide bytes to update a texture".to_string())?;
603
604        let info = TextureUpdate {
605            x_offset,
606            y_offset,
607            width,
608            height,
609            format,
610        };
611
612        device.inner_update_texture(texture, source, info)
613    }
614}