1use crate::*;
2
3use image::GenericImageView;
4use image::ImageResult;
5
6#[derive(Debug)]
7pub struct TextureCreationParams<'a> {
8 pub label: Option<&'a str>,
9 pub width: u32,
10 pub height: u32,
11 pub format: wgpu::TextureFormat,
12 pub mip_level_count: u32,
13 pub filter_mode: wgpu::FilterMode,
14 pub render_scale: f32,
15 pub view_formats: &'a [wgpu::TextureFormat],
16}
17
18impl Default for TextureCreationParams<'_> {
19 fn default() -> Self {
20 Self {
21 label: None,
22 width: 0,
23 height: 0,
24 format: wgpu::TextureFormat::Rgba16Float,
25 mip_level_count: 1,
26 filter_mode: wgpu::FilterMode::Linear,
27 render_scale: 1.0,
28 view_formats: &[],
29 }
30 }
31}
32
33#[derive(Debug)]
34pub struct BindableTexture {
35 pub texture: Texture,
36 pub bind_group: wgpu::BindGroup,
37}
38
39impl BindableTexture {
40 pub fn new(
41 device: &wgpu::Device,
42 layout: &wgpu::BindGroupLayout,
43 params: &TextureCreationParams,
44 ) -> Self {
45 let texture = Texture::create_with_params(device, params);
46
47 let label = params.label.map(|x| format!("{} Bind Group", x));
48
49 let bind_group =
50 device.simple_bind_group(label.as_deref(), &texture, layout);
51
52 Self { texture, bind_group }
53 }
54}
55
56#[derive(Debug)]
57pub struct Texture {
58 pub texture: wgpu::Texture,
59 pub view: wgpu::TextureView,
60 pub sampler: wgpu::Sampler,
61}
62
63impl Texture {
64 pub const DEPTH_FORMAT: wgpu::TextureFormat =
65 wgpu::TextureFormat::Depth32Float;
66
67 pub fn handle(&self) -> TextureHandle {
68 TextureHandle::Raw(default_hash(&self.texture.global_id()))
69 }
70
71 pub fn create_depth_texture(
72 device: &wgpu::Device,
73 config: &wgpu::SurfaceConfiguration,
74 label: &str,
75 ) -> Self {
76 let size = wgpu::Extent3d {
77 width: config.width,
78 height: config.height,
79 depth_or_array_layers: 1,
80 };
81
82 let desc = wgpu::TextureDescriptor {
83 label: Some(label),
84 size,
85 mip_level_count: 1,
86 sample_count: 1,
87 dimension: wgpu::TextureDimension::D2,
88 format: Self::DEPTH_FORMAT,
89 usage: wgpu::TextureUsages::RENDER_ATTACHMENT |
90 wgpu::TextureUsages::TEXTURE_BINDING,
91 view_formats: &[],
92 };
93
94 let texture = device.create_texture(&desc);
95
96 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
97 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
98 address_mode_u: wgpu::AddressMode::ClampToEdge,
99 address_mode_v: wgpu::AddressMode::ClampToEdge,
100 address_mode_w: wgpu::AddressMode::ClampToEdge,
101 mag_filter: wgpu::FilterMode::Linear,
102 min_filter: wgpu::FilterMode::Linear,
103 mipmap_filter: wgpu::FilterMode::Nearest,
104 compare: Some(wgpu::CompareFunction::LessEqual),
105 lod_min_clamp: 0.0,
106 lod_max_clamp: 100.0,
107 ..Default::default()
108 });
109
110 Self { texture, view, sampler }
111 }
112
113 pub fn create_with_params(
114 device: &wgpu::Device,
115 params: &TextureCreationParams,
116 ) -> Self {
117 let size = wgpu::Extent3d {
118 width: ((params.width as f32) * params.render_scale.sqrt()).round()
119 as u32,
120 height: ((params.height as f32) * params.render_scale.sqrt())
121 .round() as u32,
122 depth_or_array_layers: 1,
123 };
124
125 let texture = device.create_texture(&wgpu::TextureDescriptor {
126 label: params.label,
127 size,
128 mip_level_count: params.mip_level_count,
129 sample_count: 1,
130 dimension: wgpu::TextureDimension::D2,
131 format: params.format,
132 usage: wgpu::TextureUsages::TEXTURE_BINDING |
133 wgpu::TextureUsages::COPY_DST |
134 wgpu::TextureUsages::RENDER_ATTACHMENT,
135 view_formats: params.view_formats,
136 });
137
138 let view_label = params.label.map(|x| format!("{} View", x));
139
140 let view = texture.create_view(&wgpu::TextureViewDescriptor {
141 label: view_label.as_deref(),
142 mip_level_count: if params.mip_level_count > 0 {
144 Some(1)
145 } else {
146 None
147 },
148 ..Default::default()
149 });
150
151 let sampler_label = params.label.map(|x| format!("{} Sampler", x));
152
153 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
154 label: sampler_label.as_deref(),
155 address_mode_u: wgpu::AddressMode::ClampToEdge,
156 address_mode_v: wgpu::AddressMode::ClampToEdge,
157 address_mode_w: wgpu::AddressMode::ClampToEdge,
158 mag_filter: params.filter_mode,
159 min_filter: params.filter_mode,
160 mipmap_filter: wgpu::FilterMode::Nearest,
161 ..Default::default()
162 });
163
164 Self {
165 texture,
166 view,
167 sampler,
168 }
170 }
171
172 pub fn create_scaled_mip_filter_surface_texture(
173 device: &wgpu::Device,
174 config: &wgpu::SurfaceConfiguration,
175 format: wgpu::TextureFormat,
176 render_scale: f32,
177 mip_level_count: u32,
178 filter_mode: wgpu::FilterMode,
179 label: &str,
180 ) -> Self {
181 Self::create_with_params(device, &TextureCreationParams {
182 label: Some(label),
183 width: config.width,
184 height: config.height,
185 format,
186 mip_level_count,
187 filter_mode,
188 render_scale,
189 view_formats: &[],
190 })
191 }
192
193 pub fn from_bytes(
194 device: &wgpu::Device,
195 queue: &wgpu::Queue,
196 bytes: &[u8],
197 label: &str,
198 is_normal_map: bool,
199 ) -> ImageResult<(DynamicImage, Self)> {
200 let img = image::load_from_memory(bytes)?;
201 let tex =
202 Self::from_image(device, queue, &img, Some(label), is_normal_map)?;
203
204 Ok((img, tex))
205 }
206
207 pub fn from_image(
208 device: &wgpu::Device,
209 queue: &wgpu::Queue,
210 img: &image::DynamicImage,
211 label: Option<&str>,
212 is_normal_map: bool,
213 ) -> ImageResult<Self> {
214 Self::from_image_ex(
215 device,
216 queue,
217 img,
218 label,
219 is_normal_map,
220 wgpu::AddressMode::Repeat,
221 )
222 }
223
224 pub fn from_image_ex(
225 device: &wgpu::Device,
226 queue: &wgpu::Queue,
227 img: &image::DynamicImage,
228 label: Option<&str>,
229 is_normal_map: bool,
230 address_mode: wgpu::AddressMode,
231 ) -> ImageResult<Self> {
232 let format = if is_normal_map {
233 wgpu::TextureFormat::Rgba8Unorm
234 } else {
235 wgpu::TextureFormat::Rgba8UnormSrgb
236 };
237
238 Self::from_image_with_format(
239 device,
240 queue,
241 img,
242 label,
243 address_mode,
244 format,
245 )
246 }
247
248 pub fn from_image_with_format(
249 device: &wgpu::Device,
250 queue: &wgpu::Queue,
251 img: &image::DynamicImage,
252 label: Option<&str>,
253 address_mode: wgpu::AddressMode,
254 format: wgpu::TextureFormat,
255 ) -> ImageResult<Self> {
256 let img = img.flipv();
257 let rgba = img.to_rgba8();
258 let dimensions = img.dimensions();
259
260 Self::from_image_data_with_format(
261 device,
262 queue,
263 &rgba,
264 label,
265 address_mode,
266 format,
267 dimensions,
268 4,
269 )
270 }
271
272 pub fn from_image_data_with_format(
273 device: &wgpu::Device,
274 queue: &wgpu::Queue,
275 img_data: &[u8],
276 label: Option<&str>,
277 address_mode: wgpu::AddressMode,
278 format: wgpu::TextureFormat,
279 dimensions: (u32, u32),
280 bytes_per_pixel: u32,
281 ) -> ImageResult<Self> {
282 let size = wgpu::Extent3d {
283 width: dimensions.0,
284 height: dimensions.1,
285 depth_or_array_layers: 1,
286 };
287
288 let texture = device.create_texture(&wgpu::TextureDescriptor {
289 label,
290 size,
291 mip_level_count: 1,
292 sample_count: 1,
293 dimension: wgpu::TextureDimension::D2,
294 format,
295 usage: wgpu::TextureUsages::TEXTURE_BINDING |
296 wgpu::TextureUsages::COPY_DST,
297 view_formats: &[],
298 });
299
300 queue.write_texture(
301 wgpu::ImageCopyTexture {
302 aspect: wgpu::TextureAspect::All,
303 texture: &texture,
304 mip_level: 0,
305 origin: wgpu::Origin3d::ZERO,
306 },
307 img_data,
308 wgpu::ImageDataLayout {
309 offset: 0,
310 bytes_per_row: Some(bytes_per_pixel * dimensions.0),
311 rows_per_image: Some(dimensions.1),
312 },
313 size,
314 );
315
316 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
317
318 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
319 address_mode_u: address_mode,
320 address_mode_v: address_mode,
321 address_mode_w: address_mode,
322 mag_filter: wgpu::FilterMode::Nearest,
324 min_filter: wgpu::FilterMode::Nearest,
325 mipmap_filter: wgpu::FilterMode::Nearest,
326 ..Default::default()
327 });
328
329 Ok(Self { texture, view, sampler })
330 }
331
332 pub fn from_image_uninit(
333 device: &wgpu::Device,
334 img: &image::DynamicImage,
335 label: Option<&str>,
336 ) -> ImageResult<Self> {
337 let dimensions = img.dimensions();
338 assert!(dimensions.0 > 0 && dimensions.1 > 0);
339 Self::create_uninit(device, dimensions.0, dimensions.1, label)
340 }
341
342 pub fn create_uninit(
343 device: &wgpu::Device,
344 width: u32,
345 height: u32,
346 label: Option<&str>,
347 ) -> ImageResult<Self> {
348 let size = wgpu::Extent3d { width, height, depth_or_array_layers: 1 };
349
350 let format = wgpu::TextureFormat::Rgba8UnormSrgb;
351
352 let texture = device.create_texture(&wgpu::TextureDescriptor {
353 label,
354 size,
355 mip_level_count: 1,
356 sample_count: 1,
357 dimension: wgpu::TextureDimension::D2,
358 format,
359 usage: wgpu::TextureUsages::TEXTURE_BINDING |
360 wgpu::TextureUsages::COPY_DST,
361 view_formats: &[],
362 });
363
364 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
365
366 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
367 address_mode_u: wgpu::AddressMode::ClampToEdge,
368 address_mode_v: wgpu::AddressMode::ClampToEdge,
369 address_mode_w: wgpu::AddressMode::ClampToEdge,
370 mag_filter: wgpu::FilterMode::Nearest,
371 min_filter: wgpu::FilterMode::Nearest,
372 mipmap_filter: wgpu::FilterMode::Nearest,
373 ..Default::default()
374 });
375
376 Ok(Self { texture, view, sampler })
377 }
378}