1use wde_logger::prelude::*;
4
5use crate::RenderInstanceData;
6
7pub type SurfaceTexture = wgpu::SurfaceTexture;
9
10pub type TextureView = wgpu::TextureView;
12
13pub type TextureUsages = wgpu::TextureUsages;
15
16pub type TextureFormat = wgpu::TextureFormat;
18
19pub type FilterMode = wgpu::FilterMode;
21
22pub const SWAPCHAIN_FORMAT: TextureFormat = TextureFormat::Bgra8UnormSrgb;
24pub const DEPTH_FORMAT: TextureFormat = TextureFormat::Depth24PlusStencil8;
26
27pub struct Texture {
73 pub label: String,
74 pub texture: wgpu::Texture,
75 pub format: TextureFormat,
76 pub view: TextureView,
77 pub sampler: wgpu::Sampler,
78 pub size: (u32, u32),
79 pub sample_count: u32,
80 pub layer_count: u32,
81 pub mip_level_count: u32,
82 pub filterable: bool
83}
84
85impl std::fmt::Debug for Texture {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 f.debug_struct("Texture")
88 .field("label", &self.label)
89 .field("sampler", &self.sampler)
90 .field("size", &self.size)
91 .field("sample_count", &self.sample_count)
92 .field("layer_count", &self.layer_count)
93 .field("mip_level_count", &self.mip_level_count)
94 .field("filterable", &self.filterable)
95 .finish()
96 }
97}
98
99impl Texture {
100 #[allow(clippy::too_many_arguments)]
113 pub fn new(
114 instance: &RenderInstanceData<'_>,
115 label: &str,
116 size: (u32, u32),
117 format: TextureFormat,
118 usage: TextureUsages,
119 sample_count: u32,
120 layer_count: u32,
121 mip_level_count: u32,
122 filterable: bool
123 ) -> Self {
124 event!(LogLevel::TRACE, "Creating wgpu texture {}.", label);
125
126 let mip_level_count = if mip_level_count == 0 {
128 (size.0.max(size.1) as f32).log2().floor() as u32 + 1
129 } else {
130 mip_level_count
131 };
132
133 let texture = instance.device.create_texture(&wgpu::TextureDescriptor {
135 label: Some(format!("{}-texture", label).as_str()),
136 size: wgpu::Extent3d {
137 width: size.0,
138 height: size.1,
139 depth_or_array_layers: layer_count
140 },
141 mip_level_count,
142 sample_count,
143 dimension: wgpu::TextureDimension::D2,
144 format,
145 usage: usage | wgpu::TextureUsages::COPY_DST,
146 view_formats: &[]
147 });
148
149 let view = texture.create_view(&wgpu::TextureViewDescriptor {
151 label: Some(format!("{}-texture-view", label).as_str()),
152 format: if format == DEPTH_FORMAT {
153 None
154 } else {
155 Some(format)
156 },
157 dimension: if format == DEPTH_FORMAT {
158 None
159 } else if layer_count > 1 {
160 Some(wgpu::TextureViewDimension::D2Array)
161 } else {
162 Some(wgpu::TextureViewDimension::D2)
163 },
164 aspect: wgpu::TextureAspect::All,
165 base_mip_level: 0,
166 base_array_layer: 0,
167 mip_level_count: None,
168 array_layer_count: if layer_count > 1 {
169 Some(layer_count)
170 } else {
171 None
172 },
173 usage: None
174 });
175
176 let sampler = instance.device.create_sampler(&wgpu::SamplerDescriptor {
178 label: Some(format!("{}-texture-sampler", label).as_str()),
179 address_mode_u: wgpu::AddressMode::ClampToEdge,
180 address_mode_v: wgpu::AddressMode::ClampToEdge,
181 address_mode_w: wgpu::AddressMode::ClampToEdge,
182 mag_filter: wgpu::FilterMode::Linear,
183 min_filter: wgpu::FilterMode::Linear,
184 mipmap_filter: if mip_level_count > 1 {
185 wgpu::FilterMode::Linear
186 } else {
187 wgpu::FilterMode::Nearest
188 },
189 lod_min_clamp: 0.0,
190 lod_max_clamp: mip_level_count as f32,
191 compare: None,
192 anisotropy_clamp: 1,
193 border_color: None
194 });
195
196 Self {
198 label: label.to_string(),
199 texture,
200 format,
201 view,
202 sampler,
203 size,
204 sample_count,
205 layer_count,
206 mip_level_count,
207 filterable
208 }
209 }
210
211 pub fn copy_from_buffer(
222 &self,
223 instance: &RenderInstanceData,
224 texture_format: TextureFormat,
225 buffer: &[u8]
226 ) {
227 let format_size = match texture_format.block_dimensions() {
229 (1, 1) => texture_format.block_copy_size(None).unwrap() as usize,
230 _ => panic!("Using pixel_size for compressed textures is invalid")
231 };
232
233 instance.queue.write_texture(
235 wgpu::TexelCopyTextureInfo {
236 texture: &self.texture,
237 mip_level: 0,
238 origin: wgpu::Origin3d::ZERO,
239 aspect: wgpu::TextureAspect::All
240 },
241 buffer,
242 wgpu::TexelCopyBufferLayout {
243 offset: 0,
244 bytes_per_row: Some(self.size.0 * format_size as u32),
245 rows_per_image: None
246 },
247 wgpu::Extent3d {
248 width: self.size.0,
249 height: self.size.1,
250 depth_or_array_layers: 1
251 }
252 );
253 }
254
255 pub fn copy_from_buffer_layered(
267 &self,
268 instance: &RenderInstanceData,
269 texture_format: TextureFormat,
270 array_layer: u32,
271 buffer: &[u8]
272 ) {
273 let format_size = match texture_format.block_dimensions() {
275 (1, 1) => texture_format.block_copy_size(None).unwrap() as usize,
276 _ => panic!("Using pixel_size for compressed textures is invalid")
277 };
278
279 instance.queue.write_texture(
281 wgpu::TexelCopyTextureInfo {
282 texture: &self.texture,
283 mip_level: 0,
284 origin: wgpu::Origin3d {
285 x: 0,
286 y: 0,
287 z: array_layer
288 },
289 aspect: wgpu::TextureAspect::All
290 },
291 buffer,
292 wgpu::TexelCopyBufferLayout {
293 offset: 0,
294 bytes_per_row: Some(self.size.0 * format_size as u32),
295 rows_per_image: None
296 },
297 wgpu::Extent3d {
298 width: self.size.0,
299 height: self.size.1,
300 depth_or_array_layers: 1
301 }
302 );
303 }
304
305 pub fn copy_from_texture(
315 &self,
316 instance: &RenderInstanceData<'_>,
317 texture: &wgpu::Texture,
318 size: (u32, u32)
319 ) {
320 let mut command = crate::command_buffer::CommandBuffer::new(instance, "Copy Texture");
322
323 command.encoder().copy_texture_to_texture(
325 wgpu::TexelCopyTextureInfo {
326 texture,
327 mip_level: 0,
328 origin: wgpu::Origin3d::ZERO,
329 aspect: wgpu::TextureAspect::All
330 },
331 wgpu::TexelCopyTextureInfo {
332 texture: &self.texture,
333 mip_level: 0,
334 origin: wgpu::Origin3d::ZERO,
335 aspect: wgpu::TextureAspect::All
336 },
337 wgpu::Extent3d {
338 width: size.0,
339 height: size.1,
340 depth_or_array_layers: 1
341 }
342 );
343
344 command.submit(instance);
346 }
347
348 pub fn copy_from_surface_texture(
358 &self,
359 instance: &RenderInstanceData<'_>,
360 surface_texture: &SurfaceTexture,
361 size: (u32, u32)
362 ) {
363 self.copy_from_texture(instance, &surface_texture.texture, size);
364 }
365
366 pub fn copy_from_texture_layered(
377 &self,
378 instance: &RenderInstanceData<'_>,
379 texture: &wgpu::Texture,
380 array_layer: usize,
381 size: (u32, u32)
382 ) {
383 let mut command = crate::command_buffer::CommandBuffer::new(instance, "Copy Texture");
385
386 command.encoder().copy_texture_to_texture(
388 wgpu::TexelCopyTextureInfo {
389 texture,
390 mip_level: 0,
391 origin: wgpu::Origin3d::ZERO,
392 aspect: wgpu::TextureAspect::All
393 },
394 wgpu::TexelCopyTextureInfo {
395 texture: &self.texture,
396 mip_level: 0,
397 origin: wgpu::Origin3d {
398 x: 0,
399 y: 0,
400 z: array_layer as u32
401 },
402 aspect: wgpu::TextureAspect::All
403 },
404 wgpu::Extent3d {
405 width: size.0,
406 height: size.1,
407 depth_or_array_layers: 1
408 }
409 );
410
411 command.submit(instance);
413 }
414
415 pub fn generate_mipmaps(&self, instance: &RenderInstanceData<'_>) {
423 if self.mip_level_count <= 1 {
424 trace!(
425 "Texture {} does not have multiple mip levels ({}), skipping mipmap generation.",
426 self.label, self.mip_level_count
427 );
428 return;
429 }
430
431 trace!(
432 "Generating mipmaps for texture {} with {} mip levels and {} layers.",
433 self.label, self.mip_level_count, self.layer_count
434 );
435
436 let shader_source = r#"
438struct VertexOutput {
439 @builtin(position) position: vec4<f32>,
440 @location(0) tex_coord: vec2<f32>,
441}
442
443@vertex
444fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
445 var out: VertexOutput;
446 let x = f32((vertex_index << 1u) & 2u);
447 let y = f32(vertex_index & 2u);
448 out.position = vec4<f32>(x * 2.0 - 1.0, y * 2.0 - 1.0, 0.0, 1.0);
449 out.tex_coord = vec2<f32>(x, 1.0 - y);
450 return out;
451}
452
453@group(0) @binding(0) var src_texture: texture_2d<f32>;
454@group(0) @binding(1) var src_sampler: sampler;
455
456@fragment
457fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
458 return textureSample(src_texture, src_sampler, in.tex_coord);
459}
460"#;
461
462 let shader = instance
463 .device
464 .create_shader_module(wgpu::ShaderModuleDescriptor {
465 label: Some("mipmap_blit_shader"),
466 source: wgpu::ShaderSource::Wgsl(shader_source.into())
467 });
468
469 let blit_sampler = instance.device.create_sampler(&wgpu::SamplerDescriptor {
471 label: Some("mipmap_blit_sampler"),
472 address_mode_u: wgpu::AddressMode::ClampToEdge,
473 address_mode_v: wgpu::AddressMode::ClampToEdge,
474 address_mode_w: wgpu::AddressMode::ClampToEdge,
475 mag_filter: wgpu::FilterMode::Linear,
476 min_filter: wgpu::FilterMode::Linear,
477 mipmap_filter: wgpu::FilterMode::Nearest,
478 ..Default::default()
479 });
480
481 let bind_group_layout =
483 instance
484 .device
485 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
486 label: Some("mipmap_blit_bind_group_layout"),
487 entries: &[
488 wgpu::BindGroupLayoutEntry {
489 binding: 0,
490 visibility: wgpu::ShaderStages::FRAGMENT,
491 ty: wgpu::BindingType::Texture {
492 sample_type: wgpu::TextureSampleType::Float { filterable: true },
493 view_dimension: wgpu::TextureViewDimension::D2,
494 multisampled: false
495 },
496 count: None
497 },
498 wgpu::BindGroupLayoutEntry {
499 binding: 1,
500 visibility: wgpu::ShaderStages::FRAGMENT,
501 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
502 count: None
503 }
504 ]
505 });
506
507 let pipeline_layout =
509 instance
510 .device
511 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
512 label: Some("mipmap_blit_pipeline_layout"),
513 bind_group_layouts: &[&bind_group_layout],
514 push_constant_ranges: &[]
515 });
516
517 let pipeline = instance
519 .device
520 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
521 label: Some("mipmap_blit_pipeline"),
522 layout: Some(&pipeline_layout),
523 vertex: wgpu::VertexState {
524 module: &shader,
525 entry_point: Some("vs_main"),
526 buffers: &[],
527 compilation_options: Default::default()
528 },
529 fragment: Some(wgpu::FragmentState {
530 module: &shader,
531 entry_point: Some("fs_main"),
532 targets: &[Some(wgpu::ColorTargetState {
533 format: self.format,
534 blend: None,
535 write_mask: wgpu::ColorWrites::ALL
536 })],
537 compilation_options: Default::default()
538 }),
539 primitive: wgpu::PrimitiveState {
540 topology: wgpu::PrimitiveTopology::TriangleList,
541 ..Default::default()
542 },
543 depth_stencil: None,
544 multisample: wgpu::MultisampleState::default(),
545 multiview: None,
546 cache: None
547 });
548
549 let mut command = crate::command_buffer::CommandBuffer::new(instance, "Generate Mipmaps");
551
552 for layer in 0..self.layer_count {
553 for mip_level in 1..self.mip_level_count {
554 let src_view = self.texture.create_view(&wgpu::TextureViewDescriptor {
555 label: Some(&format!("{}_mip_{}_src", self.label, mip_level)),
556 format: Some(self.format),
557 dimension: Some(wgpu::TextureViewDimension::D2),
558 aspect: wgpu::TextureAspect::All,
559 base_mip_level: mip_level - 1,
560 mip_level_count: Some(1),
561 base_array_layer: layer,
562 array_layer_count: Some(1),
563 usage: None
564 });
565
566 let dst_view = self.texture.create_view(&wgpu::TextureViewDescriptor {
567 label: Some(&format!("{}_mip_{}_dst", self.label, mip_level)),
568 format: Some(self.format),
569 dimension: Some(wgpu::TextureViewDimension::D2),
570 aspect: wgpu::TextureAspect::All,
571 base_mip_level: mip_level,
572 mip_level_count: Some(1),
573 base_array_layer: layer,
574 array_layer_count: Some(1),
575 usage: None
576 });
577
578 let bind_group = instance
579 .device
580 .create_bind_group(&wgpu::BindGroupDescriptor {
581 label: Some(&format!("{}_mip_{}_bind_group", self.label, mip_level)),
582 layout: &bind_group_layout,
583 entries: &[
584 wgpu::BindGroupEntry {
585 binding: 0,
586 resource: wgpu::BindingResource::TextureView(&src_view)
587 },
588 wgpu::BindGroupEntry {
589 binding: 1,
590 resource: wgpu::BindingResource::Sampler(&blit_sampler)
591 }
592 ]
593 });
594
595 {
596 let mut render_pass =
597 command
598 .encoder()
599 .begin_render_pass(&wgpu::RenderPassDescriptor {
600 label: Some(&format!(
601 "{}_mip_{}_render_pass",
602 self.label, mip_level
603 )),
604 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
605 view: &dst_view,
606 resolve_target: None,
607 ops: wgpu::Operations {
608 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
609 store: wgpu::StoreOp::Store
610 },
611 depth_slice: None
612 })],
613 depth_stencil_attachment: None,
614 timestamp_writes: None,
615 occlusion_query_set: None
616 });
617
618 render_pass.set_pipeline(&pipeline);
619 render_pass.set_bind_group(0, &bind_group, &[]);
620 render_pass.draw(0..3, 0..1);
621 }
622 }
623 }
624
625 command.submit(instance);
626 event!(
627 LogLevel::TRACE,
628 "Finished generating mipmaps for texture {}.",
629 self.label
630 );
631 }
632}