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            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
121//https://sotrh.github.io/learn-wgpu/beginner/tutorial5-textures/#getting-data-into-a-texture
122impl 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 data = Arc::new(bytes);
136        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// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texImage2D
250#[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(), // todo assign an id to TextureSource?
302            }
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    /// Creates a texture from source's raw type
325    /// Check [TextureSource]
326    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    /// Creates a Texture from an image
333    pub fn from_image(mut self, bytes: &'b [u8]) -> Self {
334        self.source = None;
335        self.kind = Some(TextureKind::Image(bytes)); // TODO remove
336        self
337    }
338
339    /// Creates a Texture from a buffer of pixels
340    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    /// Creates a buffer for the size passed in and creates a Texture with it
349    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    /// Set the size of the texture (ignored if used with `from_image`, image size will be used instead)
356    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    /// Enable depth
363    pub fn with_depth(mut self) -> Self {
364        self.info.depth = true;
365        self
366    }
367
368    /// Set the Texture format (ignored if used with `from_image`, Rgba will be used instead )
369    pub fn with_format(mut self, format: TextureFormat) -> Self {
370        self.info.format = format;
371        self
372    }
373
374    /// Set the Texture filter modes
375    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    /// Set the texture wrap modes (x -> s, y -> t)
382    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    /// Process the texels to multiply the rgb values by the alpha
389    pub fn with_premultiplied_alpha(mut self) -> Self {
390        self.info.premultiplied_alpha = true;
391        self
392    }
393
394    /// Toggle mipmap generation (with Linear filter if enabled)
395    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    /// Set mipmap filtering function
405    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    /// Read pixels from the axis x offset
480    pub fn with_x_offset(mut self, offset: u32) -> Self {
481        self.x_offset = offset;
482        self
483    }
484
485    /// Read pixels from the axis y offset
486    pub fn with_y_offset(mut self, offset: u32) -> Self {
487        self.y_offset = offset;
488        self
489    }
490
491    /// Read pixels until this width from the x offset value
492    pub fn with_width(mut self, width: u32) -> Self {
493        self.width = width;
494        self
495    }
496
497    /// Read pixels until this height from the y offset value
498    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    /// Update pixels from the axis x offset
558    pub fn with_x_offset(mut self, offset: u32) -> Self {
559        self.x_offset = offset;
560        self
561    }
562
563    /// Update pixels from the axis y offset
564    pub fn with_y_offset(mut self, offset: u32) -> Self {
565        self.y_offset = offset;
566        self
567    }
568
569    /// Update pixels until this width from the x offset value
570    pub fn with_width(mut self, width: u32) -> Self {
571        self.width = width;
572        self
573    }
574
575    /// Update pixels until this height from the y offset value
576    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}