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
14pub mod pos;
17pub mod prelude;
18
19const DEFAULT_USAGE: u32 = TextureUsages::TEXTURE_BINDING.bits();
22
23pub const fn has_render_attachment(usage: u32) -> bool {
26 TextureUsages::from_bits_truncate(usage).contains(TextureUsages::RENDER_ATTACHMENT)
27 }
29
30#[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
40pub type RenderTargetTexture = Texture<
43 {
44 TextureUsages::TEXTURE_BINDING.bits()
45 | TextureUsages::RENDER_ATTACHMENT.bits()
46 | TextureUsages::COPY_SRC.bits()
47 },
48>;
49
50impl<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 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
236struct 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}