srs2dge_core/texture/
mod.rs

1use crate::{
2    label,
3    prelude::{PositionedRect, Rect},
4    target::Target,
5};
6use image::{DynamicImage, GrayImage, RgbaImage};
7use std::{mem, num::NonZeroU32, ops::Deref};
8use wgpu::{
9    util::DeviceExt, BufferDescriptor, BufferUsages, CommandEncoderDescriptor, Extent3d,
10    ImageCopyBuffer, ImageCopyTexture, ImageDataLayout, MapMode, Origin3d, TextureAspect,
11    TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView,
12};
13
14//
15
16pub mod pos;
17pub mod prelude;
18
19//
20
21const DEFAULT_USAGE: u32 = TextureUsages::TEXTURE_BINDING.bits();
22
23//
24
25pub const fn has_render_attachment(usage: u32) -> bool {
26    TextureUsages::from_bits_truncate(usage).contains(TextureUsages::RENDER_ATTACHMENT)
27    // usage & TextureUsages::RENDER_ATTACHMENT.bits() != 0
28}
29
30//
31
32#[derive(Debug)]
33pub struct Texture<const USAGE: u32 = DEFAULT_USAGE> {
34    texture: wgpu::Texture,
35    format: TextureFormat,
36    view: TextureView,
37    dim: Rect,
38}
39
40//
41
42pub type RenderTargetTexture = Texture<
43    {
44        TextureUsages::TEXTURE_BINDING.bits()
45            | TextureUsages::RENDER_ATTACHMENT.bits()
46            | TextureUsages::COPY_SRC.bits()
47    },
48>;
49
50//
51
52impl<const USAGE: u32> Texture<USAGE> {
53    pub fn new(target: &Target, format: TextureFormat, dim: Rect) -> Self {
54        Self::new_inner(target, format, dim, None)
55    }
56
57    pub fn new_rgba(target: &Target, dim: Rect) -> Self {
58        Self::new_inner(target, TextureFormat::Rgba8Unorm, dim, None)
59    }
60
61    pub fn new_rgba_with(target: &Target, data: &RgbaImage) -> Self {
62        Self::new_inner(
63            target,
64            TextureFormat::Rgba8Unorm,
65            Rect::from(data.dimensions()),
66            Some(data.as_raw()),
67        )
68    }
69
70    pub fn new_grey(target: &Target, dim: Rect) -> Self {
71        Self::new_inner(target, TextureFormat::R8Unorm, dim, None)
72    }
73
74    pub fn new_grey_with(target: &Target, data: &GrayImage) -> Self {
75        Self::new_inner(
76            target,
77            TextureFormat::R8Unorm,
78            Rect::from(data.dimensions()),
79            Some(data.as_raw()),
80        )
81    }
82
83    pub fn new_format(target: &Target, dim: Rect, format: TextureFormat) -> Self {
84        Self::new_inner(target, format, dim, None)
85    }
86
87    pub fn get_dim(&self) -> Rect {
88        self.dim
89    }
90
91    pub fn get_format(&self) -> TextureFormat {
92        self.format
93    }
94
95    pub fn write(
96        &self,
97        target: &Target,
98        spot: PositionedRect,
99        image: DynamicImage,
100    ) -> Result<(), &'static str> {
101        if spot.width != image.width() || spot.height != image.height() {
102            return Err("Image dimensions do not match the spot dimension");
103        }
104
105        if spot.x + spot.width > self.dim.width || spot.y + spot.height > self.dim.height {
106            return Err("Spot out of the texture's bounds");
107        }
108
109        const INVALID_FORMAT: &str = "Image format doesn't match with the texture format";
110        let image = match self.format {
111            TextureFormat::Rgba8Unorm | TextureFormat::Rgba8UnormSrgb => {
112                image.as_rgba8().ok_or(INVALID_FORMAT)?.as_raw()
113            }
114            TextureFormat::R8Unorm => image.as_luma8().ok_or(INVALID_FORMAT)?.as_raw(),
115            _ => unimplemented!(),
116        };
117
118        target.queue.write_texture(
119            ImageCopyTexture {
120                texture: &self.texture,
121                mip_level: 0,
122                origin: Origin3d {
123                    x: spot.x,
124                    y: spot.y,
125                    z: 0,
126                },
127                aspect: TextureAspect::All,
128            },
129            image,
130            ImageDataLayout {
131                offset: 0,
132                bytes_per_row: Some(NonZeroU32::new(spot.width).unwrap()),
133                rows_per_image: Some(NonZeroU32::new(spot.height).unwrap()),
134            },
135            spot.rect().into(),
136        );
137
138        Ok(())
139    }
140
141    pub async fn read(&self, target: &Target) -> DynamicImage {
142        let dim = BufferDimensions::new(self.dim.width as _, self.dim.height as _, self.format);
143
144        // cache these buffers
145        let read_buffer = target.device.create_buffer(&BufferDescriptor {
146            label: label!(),
147            size: (dim.padded_bytes_per_row * dim.height) as u64,
148            usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
149            mapped_at_creation: false,
150        });
151
152        let mut encoder = target
153            .device
154            .create_command_encoder(&CommandEncoderDescriptor { label: label!() });
155
156        encoder.copy_texture_to_buffer(
157            self.texture.as_image_copy(),
158            ImageCopyBuffer {
159                buffer: &read_buffer,
160                layout: ImageDataLayout {
161                    offset: 0,
162                    bytes_per_row: Some(NonZeroU32::new(dim.padded_bytes_per_row as _).unwrap()),
163                    rows_per_image: None,
164                },
165            },
166            Extent3d {
167                width: self.dim.width,
168                height: self.dim.height,
169                depth_or_array_layers: 1,
170            },
171        );
172
173        target.queue.submit([encoder.finish()]);
174
175        let range = read_buffer.slice(..);
176        range.map_async(MapMode::Read).await.unwrap();
177
178        let bytes = range
179            .get_mapped_range()
180            .chunks(dim.padded_bytes_per_row)
181            .flat_map(|s| &s[..dim.unpadded_bytes_per_row])
182            .copied()
183            .collect();
184
185        match self.format {
186            TextureFormat::Rgba8Unorm
187            | TextureFormat::Rgba8UnormSrgb
188            | TextureFormat::Bgra8Unorm
189            | TextureFormat::Bgra8UnormSrgb => {
190                RgbaImage::from_raw(dim.width as _, dim.height as _, bytes)
191                    .unwrap()
192                    .into()
193            }
194            TextureFormat::R8Unorm => GrayImage::from_raw(dim.width as _, dim.height as _, bytes)
195                .unwrap()
196                .into(),
197            _ => unimplemented!(),
198        }
199    }
200
201    fn new_inner(target: &Target, format: TextureFormat, dim: Rect, data: Option<&[u8]>) -> Self {
202        let desc = TextureDescriptor {
203            label: label!(),
204            size: dim.into(),
205            mip_level_count: 1,
206            sample_count: 1,
207            dimension: TextureDimension::D2,
208            format,
209            usage: TextureUsages::from_bits_truncate(USAGE),
210        };
211        let texture = match data {
212            None => target.device.create_texture(&desc),
213            Some(data) => target
214                .device
215                .create_texture_with_data(&target.queue, &desc, data),
216        };
217        let view = texture.create_view(&Default::default());
218
219        Self {
220            texture,
221            format,
222            view,
223            dim,
224        }
225    }
226}
227
228impl<const USAGE: u32> Deref for Texture<USAGE> {
229    type Target = TextureView;
230
231    fn deref(&self) -> &Self::Target {
232        &self.view
233    }
234}
235
236//
237
238struct BufferDimensions {
239    width: usize,
240    height: usize,
241    unpadded_bytes_per_row: usize,
242    padded_bytes_per_row: usize,
243}
244
245impl BufferDimensions {
246    fn new(width: usize, height: usize, format: TextureFormat) -> Self {
247        let pixel_size = match format {
248            TextureFormat::Rgba8Unorm
249            | TextureFormat::Rgba8UnormSrgb
250            | TextureFormat::Bgra8Unorm
251            | TextureFormat::Bgra8UnormSrgb => mem::size_of::<f32>(),
252            TextureFormat::R8Unorm => mem::size_of::<u8>(),
253            _ => unimplemented!(),
254        };
255
256        let unpadded_bytes_per_row = width * pixel_size;
257        let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT as usize;
258        let padded_bytes_per_row_padding = (align - unpadded_bytes_per_row % align) % align;
259        let padded_bytes_per_row = unpadded_bytes_per_row + padded_bytes_per_row_padding;
260        Self {
261            width,
262            height,
263            unpadded_bytes_per_row,
264            padded_bytes_per_row,
265        }
266    }
267}