est_render/gpu/texture/
mod.rs

1pub mod atlas;
2
3mod types;
4pub use types::*;
5
6use std::sync::atomic::AtomicUsize;
7use crate::{gpu::ArcRef, math::Point2};
8
9use super::{
10    GPUInner,
11    buffer::{BufferBuilder, BufferUsage},
12};
13
14#[derive(Debug, Clone)]
15pub struct Texture {
16    pub(crate) graphics: ArcRef<GPUInner>,
17    pub(crate) inner: ArcRef<TextureInner>,
18
19    pub(crate) mapped_buffer: Vec<u8>,
20    pub(crate) mapped_type: TextureMappedType,
21}
22
23static TEXTURE_REF_ID: AtomicUsize = AtomicUsize::new(0);
24
25impl Texture {
26    pub(crate) fn from_builder(builder: TextureBuilder) -> Result<Self, TextureError> {
27        if builder.graphics.borrow().is_invalid {
28            // return Err("Graphics context is invalid".to_string());
29            return Err(TextureError::InvalidGPUContext);
30        }
31
32        let texture = match builder.data {
33            TextureBuilderData::Data(data) => {
34                let image = image::load_from_memory(data).map_err(|e| e.to_string());
35                if image.is_err() {
36                    crate::dbg_log!(
37                        "Failed to load image from memory: {}",
38                        image.as_ref().err().unwrap()
39                    );
40                    return Err(TextureError::InvalidTextureData);
41                }
42
43                let image = image.unwrap();
44
45                let rgba = image.to_rgba8();
46                let dimensions = rgba.dimensions();
47                let size = Point2::new(dimensions.0 as i32, dimensions.1 as i32);
48
49                let texture = Self::create_texture(
50                    builder.graphics,
51                    size,
52                    builder.sample_count,
53                    builder.mip_level_count,
54                    wgpu::TextureDimension::D2,
55                    TextureFormat::Rgba8Unorm,
56                    builder.usage,
57                );
58
59                if texture.is_err() {
60                    crate::dbg_log!(
61                        "Failed to create texture: {}",
62                        texture.as_ref().err().unwrap()
63                    );
64                    return Err(TextureError::InvalidTextureData);
65                }
66
67                let mut texture = texture.unwrap();
68
69                if let Err(e) = texture.write::<u8>(&rgba) {
70                    return Err(e);
71                }
72
73                Ok(texture)
74            }
75
76            TextureBuilderData::File(file_path) => {
77                let image = image::open(file_path).map_err(|e| e.to_string());
78                if image.is_err() {
79                    crate::dbg_log!(
80                        "Failed to load image from file: {}",
81                        image.as_ref().err().unwrap()
82                    );
83                    return Err(TextureError::InvalidTextureData);
84                }
85
86                let image = image.unwrap();
87
88                let rgba = image.to_rgba8();
89                let dimensions = rgba.dimensions();
90                let size = Point2::new(dimensions.0 as i32, dimensions.1 as i32);
91
92                let texture = Self::create_texture(
93                    builder.graphics,
94                    size,
95                    builder.sample_count,
96                    builder.mip_level_count,
97                    wgpu::TextureDimension::D2,
98                    TextureFormat::Rgba8Unorm,
99                    builder.usage,
100                );
101
102                if texture.is_err() {
103                    crate::dbg_log!(
104                        "Failed to create texture: {}",
105                        texture.as_ref().err().unwrap()
106                    );
107                    return Err(TextureError::InvalidTextureData);
108                }
109
110                let mut texture = texture.unwrap();
111
112                if let Err(e) = texture.write::<u8>(&rgba) {
113                    crate::dbg_log!("Failed to write texture data: {}", e);
114                    return Err(e);
115                }
116
117                Ok(texture)
118            }
119
120            TextureBuilderData::Raw(size, data, format) => {
121                let texture = Self::create_texture(
122                    builder.graphics,
123                    size,
124                    builder.sample_count,
125                    builder.mip_level_count,
126                    wgpu::TextureDimension::D2,
127                    format,
128                    builder.usage,
129                );
130
131                if texture.is_err() {
132                    crate::dbg_log!(
133                        "Failed to create texture: {}",
134                        texture.as_ref().err().unwrap()
135                    );
136                    return Err(TextureError::InvalidTextureData);
137                }
138
139                let mut texture = texture.unwrap();
140                if let Err(e) = texture.write::<u8>(data) {
141                    crate::dbg_log!("Failed to write texture data: {}", e);
142                    return Err(e);
143                }
144
145                Ok(texture)
146            }
147
148            TextureBuilderData::DepthStencil(size, format) => {
149                let texture = Self::create_texture(
150                    builder.graphics,
151                    size,
152                    builder.sample_count,
153                    builder.mip_level_count,
154                    wgpu::TextureDimension::D2,
155                    format.unwrap(),
156                    builder.usage | TextureUsage::RenderAttachment,
157                );
158
159                if texture.is_err() {
160                    crate::dbg_log!(
161                        "Failed to create depth stencil texture: {}",
162                        texture.as_ref().err().unwrap()
163                    );
164                    return Err(TextureError::InvalidTextureData);
165                }
166
167                texture
168            }
169
170            TextureBuilderData::RenderTarget(size, format) => {
171                let format = {
172                    if format.is_none() {
173                        let graphics_ref = builder.graphics.borrow();
174
175                        if graphics_ref.config.is_none() {
176                            crate::dbg_log!(
177                                "Using default format (RGBA8_UNORM_SRGB) for render target texture"
178                            );
179                            TextureFormat::Rgba8UnormSrgb
180                        } else {
181                            let config = graphics_ref.config.as_ref().unwrap();
182                            crate::dbg_log!(
183                                "Using swapchain format ({:?}) for render target texture",
184                                config.format
185                            );
186                            config.format.into()
187                        }
188                    } else {
189                        format.unwrap()
190                    }
191                };
192
193                let texture = Self::create_texture(
194                    builder.graphics,
195                    size,
196                    builder.sample_count,
197                    builder.mip_level_count,
198                    wgpu::TextureDimension::D2,
199                    TextureFormat::from(format),
200                    builder.usage | TextureUsage::RenderAttachment,
201                );
202
203                if texture.is_err() {
204                    crate::dbg_log!(
205                        "Failed to create render target texture: {}",
206                        texture.as_ref().err().unwrap()
207                    );
208                    return Err(TextureError::InvalidTextureData);
209                }
210
211                texture
212            }
213
214            _ => {
215                return Err(TextureError::InvalidTextureData);
216            }
217        };
218
219        texture
220    }
221
222    fn create_texture(
223        graphics: ArcRef<GPUInner>,
224        size: Point2,
225        sample_count: SampleCount,
226        mip_level_count: u32,
227        dimension: wgpu::TextureDimension,
228        format: TextureFormat,
229        usages: TextureUsage,
230    ) -> Result<Self, TextureError> {
231        if size.x == 0 || size.y == 0 {
232            return Err(TextureError::InvalidTextureSize);
233        }
234
235        let texture_size = wgpu::Extent3d {
236            width: size.x as u32,
237            height: size.y as u32,
238            depth_or_array_layers: 1,
239        };
240
241        let ref_id_label = TEXTURE_REF_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
242        let tex_label = format!("Texture {}", ref_id_label);
243        let view_label = format!("Texture View {}", ref_id_label);
244
245        let texture_create_info = wgpu::TextureDescriptor {
246            size: texture_size,
247            mip_level_count,
248            sample_count: sample_count.clone().into(),
249            dimension,
250            format: format.clone().into(),
251            usage: (wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC)
252                | usages.clone().into(),
253            label: Some(tex_label.as_str()),
254            view_formats: &[],
255        };
256
257        let graphics_ref = graphics.borrow();
258        let texture = graphics_ref
259            .device()
260            .create_texture(&texture_create_info);
261
262        let view = texture.create_view(&wgpu::TextureViewDescriptor {
263            label: Some(view_label.as_str()),
264            ..Default::default()
265        });
266
267        let inner = TextureInner {
268            wgpu_texture: texture,
269            wgpu_view: view,
270
271            sample_count,
272            usages,
273            size,
274            format,
275
276            mapped: false,
277        };
278
279        Ok(Self {
280            graphics: ArcRef::clone(&graphics),
281            inner: ArcRef::new(inner),
282            mapped_buffer: vec![],
283            mapped_type: TextureMappedType::Write,
284        })
285    }
286
287    pub fn size(&self) -> Point2 {
288        self.inner.borrow().size
289    }
290
291    pub fn format(&self) -> TextureFormat {
292        self.inner.borrow().format
293    }
294
295    pub fn sample_count(&self) -> SampleCount {
296        self.inner.borrow().sample_count
297    }
298
299    pub fn usages(&self) -> TextureUsage {
300        self.inner.borrow().usages
301    }
302
303    pub fn write<T: bytemuck::Pod>(&mut self, data: &[T]) -> Result<(), TextureError> {
304        if data.is_empty() {
305            return Err(TextureError::InvalidTextureData);
306        }
307
308        let inner = self.inner.borrow();
309
310        let data: Vec<u8> = bytemuck::cast_slice(data).to_vec();
311        let bytes_per_pixel = inner.format.get_size();
312        let unpadded_bytes_per_row = bytes_per_pixel * inner.size.x as u32;
313        let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
314        let padded_bytes_per_row = ((unpadded_bytes_per_row + align - 1) / align) * align;
315
316        let mut padded_data =
317            Vec::with_capacity((padded_bytes_per_row * inner.size.y as u32) as usize);
318
319        for row in 0..inner.size.y as usize {
320            let start = row * unpadded_bytes_per_row as usize;
321            let end = start + unpadded_bytes_per_row as usize;
322            padded_data.extend_from_slice(&data[start..end]);
323            padded_data.extend(vec![
324                0;
325                (padded_bytes_per_row - unpadded_bytes_per_row) as usize
326            ]);
327        }
328
329        let buffer = BufferBuilder::<u8>::new(self.graphics.clone())
330            .set_data_vec(padded_data)
331            .set_usage(BufferUsage::COPY_SRC)
332            .build();
333
334        if buffer.is_err() {
335            return Err(TextureError::FailedToWrite);
336        }
337
338        let buffer = buffer.unwrap();
339
340        let mut encoder = self.graphics.borrow().device().create_command_encoder(
341            &wgpu::CommandEncoderDescriptor {
342                label: Some("texture write encoder"),
343            },
344        );
345
346        encoder.copy_buffer_to_texture(
347            wgpu::TexelCopyBufferInfoBase {
348                buffer: &buffer.inner.borrow().buffer,
349                layout: wgpu::TexelCopyBufferLayout {
350                    offset: 0,
351                    bytes_per_row: Some(padded_bytes_per_row),
352                    rows_per_image: Some(inner.size.y as u32),
353                },
354            },
355            wgpu::TexelCopyTextureInfo {
356                texture: &inner.wgpu_texture,
357                mip_level: 0,
358                origin: wgpu::Origin3d::ZERO,
359                aspect: wgpu::TextureAspect::All,
360            },
361            wgpu::Extent3d {
362                width: inner.size.x as u32,
363                height: inner.size.y as u32,
364                depth_or_array_layers: 1,
365            },
366        );
367
368        self.graphics
369            .borrow()
370            .queue()
371            .submit(Some(encoder.finish()));
372        _ = self
373            .graphics
374            .borrow()
375            .device()
376            .poll(wgpu::PollType::Wait);
377
378        Ok(())
379    }
380
381    pub fn read<T: bytemuck::Pod>(&self) -> Result<Vec<T>, TextureError> {
382        if self.inner.borrow().size.x == 0 || self.inner.borrow().size.y == 0 {
383            return Err(TextureError::InvalidTextureSize);
384        }
385
386        let inner = self.inner.borrow();
387        let inner_graphics = self.graphics.borrow();
388
389        let bytes_per_pixel = 4; // For RGBA8/BGRA8, etc. Adjust if needed.
390        let unpadded_bytes_per_row = bytes_per_pixel * inner.size.x as u32;
391        let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
392        let padded_bytes_per_row = ((unpadded_bytes_per_row + align - 1) / align) * align;
393
394        let buffer = BufferBuilder::<u8>::new(self.graphics.clone())
395            .set_data_empty((padded_bytes_per_row * inner.size.y as u32) as usize)
396            .set_usage(BufferUsage::COPY_DST | BufferUsage::MAP_READ)
397            .build();
398
399        if buffer.is_err() {
400            return Err(TextureError::FailedToRead);
401        }
402
403        let buffer = buffer.unwrap();
404
405        let mut encoder =
406            inner_graphics
407                .device()
408                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
409                    label: Some("texture read encoder"),
410                });
411
412        encoder.copy_texture_to_buffer(
413            wgpu::TexelCopyTextureInfo {
414                texture: &inner.wgpu_texture,
415                mip_level: 0,
416                origin: wgpu::Origin3d::ZERO,
417                aspect: wgpu::TextureAspect::All,
418            },
419            wgpu::TexelCopyBufferInfo {
420                buffer: &buffer.inner.borrow().buffer,
421                layout: wgpu::TexelCopyBufferLayout {
422                    offset: 0,
423                    bytes_per_row: Some(padded_bytes_per_row),
424                    rows_per_image: Some(inner.size.y as u32),
425                },
426            },
427            inner.size.into(),
428        );
429
430        inner_graphics.queue().submit(Some(encoder.finish()));
431        _ = inner_graphics.device().poll(wgpu::PollType::Wait);
432
433        drop(inner_graphics);
434
435        // Remove row padding
436        let raw = buffer.read::<u8>();
437
438        if raw.is_err() {
439            return Err(TextureError::FailedToRead);
440        }
441
442        let raw = raw.unwrap();
443
444        let height = inner.size.y as u32;
445        let padded_bytes_per_row = padded_bytes_per_row as u32;
446
447        let mut result = Vec::with_capacity((unpadded_bytes_per_row * height) as usize);
448        for row in 0..height as usize {
449            let start = row * padded_bytes_per_row as usize;
450            let end = start + unpadded_bytes_per_row as usize;
451            result.extend_from_slice(&raw[start..end]);
452        }
453
454        // Cast to T
455        let ptr = result.as_ptr();
456        let len = result.len() / std::mem::size_of::<T>();
457        let mut out = Vec::with_capacity(len);
458        unsafe {
459            out.set_len(len);
460            std::ptr::copy_nonoverlapping(ptr as *const T, out.as_mut_ptr(), len);
461        }
462        Ok(out)
463    }
464
465    pub fn map(&mut self, map_type: TextureMappedType) -> Result<&mut Vec<u8>, TextureError> {
466        let mut inner = self.inner.borrow_mut();
467        if inner.mapped {
468            crate::dbg_log!("Texture is already mapped");
469            return Err(TextureError::AlreadyMapped);
470        }
471
472        match map_type {
473            TextureMappedType::Read => {
474                inner.mapped = true;
475                drop(inner);
476
477                self.mapped_type = TextureMappedType::Read;
478                self.mapped_buffer = self.read::<u8>()?;
479
480                return Ok(&mut self.mapped_buffer);
481            }
482            TextureMappedType::Write => {
483                inner.mapped = true;
484                drop(inner);
485
486                self.mapped_type = TextureMappedType::Write;
487                self.mapped_buffer =
488                    vec![0; (self.inner.borrow().size.x * self.inner.borrow().size.y * 4) as usize];
489
490                return Ok(&mut self.mapped_buffer);
491            }
492        }
493    }
494
495    pub fn unmap(&mut self) -> Result<(), TextureError> {
496        let mut inner = self.inner.borrow_mut();
497        if !inner.mapped {
498            crate::dbg_log!("Texture is not mapped");
499            return Err(TextureError::NotMapped);
500        }
501
502        match self.mapped_type {
503            TextureMappedType::Read => {
504                inner.mapped = false;
505                self.mapped_buffer.clear();
506            }
507            TextureMappedType::Write => {
508                inner.mapped = false;
509
510                drop(inner);
511
512                let buffer = self.mapped_buffer.clone();
513
514                if let Err(e) = self.write::<u8>(&buffer) {
515                    crate::dbg_log!("Failed to write texture data: {}", e);
516                    return Err(e);
517                }
518
519                self.mapped_buffer = vec![];
520            }
521        }
522
523        Ok(())
524    }
525}
526
527impl PartialEq for Texture {
528    fn eq(&self, other: &Self) -> bool {
529        self.inner == other.inner
530    }
531}
532
533impl Eq for Texture {}
534
535impl PartialEq for TextureInner {
536    fn eq(&self, other: &Self) -> bool {
537        self.wgpu_texture == other.wgpu_texture &&
538        self.wgpu_view == other.wgpu_view &&
539        self.size == other.size &&
540        self.usages == other.usages &&
541        self.sample_count == other.sample_count &&
542        // self.blend == other.blend &&
543        // self.sampler_info == other.sampler_info &&
544        self.format == other.format
545    }
546}
547
548impl Eq for TextureInner {}
549
550pub struct TextureInner {
551    pub(crate) wgpu_texture: wgpu::Texture,
552    pub(crate) wgpu_view: wgpu::TextureView,
553
554    pub(crate) size: Point2,
555    pub(crate) usages: TextureUsage,
556    pub(crate) sample_count: SampleCount,
557    pub(crate) format: TextureFormat,
558
559    pub(crate) mapped: bool,
560}
561
562#[derive(Debug, Clone, Copy, PartialEq, Eq)]
563pub enum TextureMappedType {
564    Read,
565    Write,
566}
567
568impl std::fmt::Display for TextureMappedType {
569    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
570        match self {
571            TextureMappedType::Read => write!(f, "Read"),
572            TextureMappedType::Write => write!(f, "Write"),
573        }
574    }
575}
576
577#[derive(Debug, Clone, Copy)]
578pub enum TextureError {
579    InvalidGPUContext,
580    InvalidTextureData,
581    InvalidTextureSize,
582    InvalidTextureFormat,
583    FailedToWrite,
584    FailedToRead,
585    AlreadyMapped,
586    NotMapped,
587}
588
589impl std::fmt::Display for TextureError {
590    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
591        match self {
592            TextureError::InvalidGPUContext => write!(f, "Invalid GPU context"),
593            TextureError::InvalidTextureData => write!(f, "Invalid texture data"),
594            TextureError::InvalidTextureSize => write!(f, "Invalid texture size"),
595            TextureError::InvalidTextureFormat => write!(f, "Invalid texture format"),
596            TextureError::FailedToWrite => write!(f, "Failed to write to texture"),
597            TextureError::FailedToRead => write!(f, "Failed to read from texture"),
598            TextureError::AlreadyMapped => write!(f, "Texture is already mapped"),
599            TextureError::NotMapped => write!(f, "Texture is not mapped"),
600        }
601    }
602}
603
604pub enum TextureBuilderData<'a> {
605    None,
606    File(&'a str),
607    Data(&'a [u8]),
608    Raw(Point2, &'a [u8], TextureFormat),
609    DepthStencil(Point2, Option<TextureFormat>),
610    RenderTarget(Point2, Option<TextureFormat>),
611}
612
613pub struct TextureBuilder<'a> {
614    pub(crate) graphics: ArcRef<GPUInner>,
615    pub(crate) sample_count: SampleCount,
616    pub(crate) mip_level_count: u32,
617    pub(crate) usage: TextureUsage,
618    pub(crate) data: TextureBuilderData<'a>,
619}
620
621impl<'a> TextureBuilder<'a> {
622    pub(crate) fn new(graphics: ArcRef<GPUInner>) -> Self {
623        if graphics.borrow().is_invalid {
624            panic!("Graphics context is invalid");
625        }
626
627        Self {
628            graphics,
629            sample_count: SampleCount::SampleCount1,
630            mip_level_count: 1,
631            usage: TextureUsage::None,
632            data: TextureBuilderData::None,
633        }
634    }
635
636    /// Create the texture with file path.
637    pub fn set_file(mut self, file_path: &'a str) -> Self {
638        self.data = TextureBuilderData::File(file_path);
639        self
640    }
641
642    /// Sets the texture data from a file byte data.
643    pub fn set_file_data(mut self, data: &'a [u8]) -> Self {
644        self.data = TextureBuilderData::Data(data);
645        self
646    }
647
648    /// Initializes a texture with raw image data.
649    pub fn set_raw_image(mut self, data: &'a [u8], size: Point2, format: TextureFormat) -> Self {
650        if format >= TextureFormat::Stencil8 && format <= TextureFormat::Depth32FloatStencil8 {
651            panic!("Depth and stencil formats are not supported in raw data");
652        }
653
654        self.data = TextureBuilderData::Raw(size, data, format);
655        self
656    }
657
658    /// Initializes a texture as a render target.
659    ///
660    /// This method sets the texture as a render target with the specified size and format.
661    /// The size must be non-zero, and the format can be specified or defaulted to the swapchain format or RGBA8_UNORM_SRGB if the
662    /// swapchain format is not available.
663    pub fn set_render_target(mut self, size: Point2, format: Option<TextureFormat>) -> Self {
664        if size.x == 0 || size.y == 0 {
665            panic!("Render target texture must have a size");
666        }
667
668        self.data = TextureBuilderData::RenderTarget(size, format);
669        self
670    }
671
672    /// Sets the sample count for the texture.
673    ///
674    /// This method allows you to specify the sample count for the texture. The default is 1.
675    /// **NOTE:** Will panic in WASM (not supported atm) which only support 1x and 4x.
676    pub fn set_sample_count(mut self, sample_count: SampleCount) -> Self {
677        self.sample_count = sample_count.into();
678        self
679    }
680
681    /// Initializes a texture as a depth stencil texture.
682    pub fn set_depth_stencil(mut self, size: Point2, format: Option<TextureFormat>) -> Self {
683        if size.x == 0 || size.y == 0 {
684            panic!("Depth stencil texture must have a size");
685        }
686
687        self.data = TextureBuilderData::DepthStencil(
688            size,
689            Some(format.unwrap_or(TextureFormat::Depth32Float)),
690        );
691        self
692    }
693
694    /// Sets the number of mip levels for the texture.
695    pub fn set_mip_level_count(mut self, mip_level_count: u32) -> Self {
696        self.mip_level_count = mip_level_count;
697        self
698    }
699
700    /// Sets the usage of the texture.
701    ///
702    /// This method allows you to specify the usage of the texture. However it cannot set the texture as
703    /// a render target, as that must be done using the `with_render_target` method.
704    pub fn set_usage(mut self, usage: TextureUsage) -> Self {
705        if usage.contains(TextureUsage::RenderAttachment) {
706            panic!("Render attachment textures must be created with the render target method");
707        }
708
709        self.usage = usage;
710        self
711    }
712
713    pub fn build(self) -> Result<Texture, TextureError> {
714        Texture::from_builder(self)
715    }
716}