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 tiling: bool
124 ) -> Self {
125 event!(LogLevel::TRACE, "Creating wgpu texture {}.", label);
126
127 let mip_level_count = if mip_level_count == 0 {
129 (size.0.max(size.1) as f32).log2().floor() as u32 + 1
130 } else {
131 mip_level_count
132 };
133
134 let texture = instance.device.create_texture(&wgpu::TextureDescriptor {
136 label: Some(format!("{}-texture", label).as_str()),
137 size: wgpu::Extent3d {
138 width: size.0,
139 height: size.1,
140 depth_or_array_layers: layer_count
141 },
142 mip_level_count,
143 sample_count,
144 dimension: wgpu::TextureDimension::D2,
145 format,
146 usage: usage | wgpu::TextureUsages::COPY_DST,
147 view_formats: &[]
148 });
149
150 let view = texture.create_view(&wgpu::TextureViewDescriptor {
152 label: Some(format!("{}-texture-view", label).as_str()),
153 format: if format == DEPTH_FORMAT {
154 None
155 } else {
156 Some(format)
157 },
158 dimension: if format == DEPTH_FORMAT {
159 None
160 } else if layer_count > 1 {
161 Some(wgpu::TextureViewDimension::D2Array)
162 } else {
163 Some(wgpu::TextureViewDimension::D2)
164 },
165 aspect: wgpu::TextureAspect::All,
166 base_mip_level: 0,
167 base_array_layer: 0,
168 mip_level_count: None,
169 array_layer_count: if layer_count > 1 {
170 Some(layer_count)
171 } else {
172 None
173 },
174 usage: None
175 });
176
177 let address_mode = match tiling {
179 true => wgpu::AddressMode::Repeat,
180 false => wgpu::AddressMode::ClampToEdge
181 };
182 let sampler = instance.device.create_sampler(&wgpu::SamplerDescriptor {
183 label: Some(format!("{}-texture-sampler", label).as_str()),
184 address_mode_u: address_mode,
185 address_mode_v: address_mode,
186 address_mode_w: wgpu::AddressMode::ClampToEdge,
187 mag_filter: wgpu::FilterMode::Linear,
188 min_filter: wgpu::FilterMode::Linear,
189 mipmap_filter: if mip_level_count > 1 {
190 wgpu::FilterMode::Linear
191 } else {
192 wgpu::FilterMode::Nearest
193 },
194 lod_min_clamp: 0.0,
195 lod_max_clamp: mip_level_count as f32,
196 compare: None,
197 anisotropy_clamp: if tiling && mip_level_count > 1 { 16 } else { 1 },
198 border_color: None
199 });
200
201 Self {
203 label: label.to_string(),
204 texture,
205 format,
206 view,
207 sampler,
208 size,
209 sample_count,
210 layer_count,
211 mip_level_count,
212 filterable
213 }
214 }
215
216 pub fn copy_from_buffer(
227 &self,
228 instance: &RenderInstanceData,
229 texture_format: TextureFormat,
230 buffer: &[u8]
231 ) {
232 let format_size = match texture_format.block_dimensions() {
234 (1, 1) => texture_format.block_copy_size(None).unwrap() as usize,
235 _ => panic!("Using pixel_size for compressed textures is invalid")
236 };
237
238 instance.queue.write_texture(
240 wgpu::TexelCopyTextureInfo {
241 texture: &self.texture,
242 mip_level: 0,
243 origin: wgpu::Origin3d::ZERO,
244 aspect: wgpu::TextureAspect::All
245 },
246 buffer,
247 wgpu::TexelCopyBufferLayout {
248 offset: 0,
249 bytes_per_row: Some(self.size.0 * format_size as u32),
250 rows_per_image: None
251 },
252 wgpu::Extent3d {
253 width: self.size.0,
254 height: self.size.1,
255 depth_or_array_layers: 1
256 }
257 );
258 }
259
260 pub fn copy_from_buffer_layered(
272 &self,
273 instance: &RenderInstanceData,
274 texture_format: TextureFormat,
275 array_layer: u32,
276 buffer: &[u8]
277 ) {
278 let format_size = match texture_format.block_dimensions() {
280 (1, 1) => texture_format.block_copy_size(None).unwrap() as usize,
281 _ => panic!("Using pixel_size for compressed textures is invalid")
282 };
283
284 instance.queue.write_texture(
286 wgpu::TexelCopyTextureInfo {
287 texture: &self.texture,
288 mip_level: 0,
289 origin: wgpu::Origin3d {
290 x: 0,
291 y: 0,
292 z: array_layer
293 },
294 aspect: wgpu::TextureAspect::All
295 },
296 buffer,
297 wgpu::TexelCopyBufferLayout {
298 offset: 0,
299 bytes_per_row: Some(self.size.0 * format_size as u32),
300 rows_per_image: None
301 },
302 wgpu::Extent3d {
303 width: self.size.0,
304 height: self.size.1,
305 depth_or_array_layers: 1
306 }
307 );
308 }
309
310 pub fn copy_from_texture(
320 &self,
321 instance: &RenderInstanceData<'_>,
322 texture: &wgpu::Texture,
323 size: (u32, u32)
324 ) {
325 let mut command = crate::command_buffer::CommandBuffer::new(instance, "Copy Texture");
327
328 command.encoder().copy_texture_to_texture(
330 wgpu::TexelCopyTextureInfo {
331 texture,
332 mip_level: 0,
333 origin: wgpu::Origin3d::ZERO,
334 aspect: wgpu::TextureAspect::All
335 },
336 wgpu::TexelCopyTextureInfo {
337 texture: &self.texture,
338 mip_level: 0,
339 origin: wgpu::Origin3d::ZERO,
340 aspect: wgpu::TextureAspect::All
341 },
342 wgpu::Extent3d {
343 width: size.0,
344 height: size.1,
345 depth_or_array_layers: 1
346 }
347 );
348
349 command.submit(instance);
351 }
352
353 pub fn copy_from_surface_texture(
363 &self,
364 instance: &RenderInstanceData<'_>,
365 surface_texture: &SurfaceTexture,
366 size: (u32, u32)
367 ) {
368 self.copy_from_texture(instance, &surface_texture.texture, size);
369 }
370
371 pub fn copy_from_texture_layered(
382 &self,
383 instance: &RenderInstanceData<'_>,
384 texture: &wgpu::Texture,
385 array_layer: usize,
386 size: (u32, u32)
387 ) {
388 let mut command = crate::command_buffer::CommandBuffer::new(instance, "Copy Texture");
390
391 command.encoder().copy_texture_to_texture(
393 wgpu::TexelCopyTextureInfo {
394 texture,
395 mip_level: 0,
396 origin: wgpu::Origin3d::ZERO,
397 aspect: wgpu::TextureAspect::All
398 },
399 wgpu::TexelCopyTextureInfo {
400 texture: &self.texture,
401 mip_level: 0,
402 origin: wgpu::Origin3d {
403 x: 0,
404 y: 0,
405 z: array_layer as u32
406 },
407 aspect: wgpu::TextureAspect::All
408 },
409 wgpu::Extent3d {
410 width: size.0,
411 height: size.1,
412 depth_or_array_layers: 1
413 }
414 );
415
416 command.submit(instance);
418 }
419
420 pub fn generate_mipmaps(&self, instance: &RenderInstanceData<'_>) {
428 if self.mip_level_count <= 1 {
429 trace!(
430 "Texture {} does not have multiple mip levels ({}), skipping mipmap generation.",
431 self.label, self.mip_level_count
432 );
433 return;
434 }
435
436 trace!(
437 "Generating mipmaps for texture {} with {} mip levels and {} layers.",
438 self.label, self.mip_level_count, self.layer_count
439 );
440
441 let shader_source = r#"
443struct VertexOutput {
444 @builtin(position) position: vec4<f32>,
445 @location(0) tex_coord: vec2<f32>,
446}
447
448@vertex
449fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
450 var out: VertexOutput;
451 let x = f32((vertex_index << 1u) & 2u);
452 let y = f32(vertex_index & 2u);
453 out.position = vec4<f32>(x * 2.0 - 1.0, y * 2.0 - 1.0, 0.0, 1.0);
454 out.tex_coord = vec2<f32>(x, 1.0 - y);
455 return out;
456}
457
458@group(0) @binding(0) var src_texture: texture_2d<f32>;
459@group(0) @binding(1) var src_sampler: sampler;
460
461@fragment
462fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
463 return textureSample(src_texture, src_sampler, in.tex_coord);
464}
465"#;
466
467 let shader = instance
468 .device
469 .create_shader_module(wgpu::ShaderModuleDescriptor {
470 label: Some("mipmap_blit_shader"),
471 source: wgpu::ShaderSource::Wgsl(shader_source.into())
472 });
473
474 let blit_sampler = instance.device.create_sampler(&wgpu::SamplerDescriptor {
476 label: Some("mipmap_blit_sampler"),
477 address_mode_u: wgpu::AddressMode::ClampToEdge,
478 address_mode_v: wgpu::AddressMode::ClampToEdge,
479 address_mode_w: wgpu::AddressMode::ClampToEdge,
480 mag_filter: wgpu::FilterMode::Linear,
481 min_filter: wgpu::FilterMode::Linear,
482 mipmap_filter: wgpu::FilterMode::Nearest,
483 ..Default::default()
484 });
485
486 let bind_group_layout =
488 instance
489 .device
490 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
491 label: Some("mipmap_blit_bind_group_layout"),
492 entries: &[
493 wgpu::BindGroupLayoutEntry {
494 binding: 0,
495 visibility: wgpu::ShaderStages::FRAGMENT,
496 ty: wgpu::BindingType::Texture {
497 sample_type: wgpu::TextureSampleType::Float { filterable: true },
498 view_dimension: wgpu::TextureViewDimension::D2,
499 multisampled: false
500 },
501 count: None
502 },
503 wgpu::BindGroupLayoutEntry {
504 binding: 1,
505 visibility: wgpu::ShaderStages::FRAGMENT,
506 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
507 count: None
508 }
509 ]
510 });
511
512 let pipeline_layout =
514 instance
515 .device
516 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
517 label: Some("mipmap_blit_pipeline_layout"),
518 bind_group_layouts: &[&bind_group_layout],
519 push_constant_ranges: &[]
520 });
521
522 let pipeline = instance
524 .device
525 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
526 label: Some("mipmap_blit_pipeline"),
527 layout: Some(&pipeline_layout),
528 vertex: wgpu::VertexState {
529 module: &shader,
530 entry_point: Some("vs_main"),
531 buffers: &[],
532 compilation_options: Default::default()
533 },
534 fragment: Some(wgpu::FragmentState {
535 module: &shader,
536 entry_point: Some("fs_main"),
537 targets: &[Some(wgpu::ColorTargetState {
538 format: self.format,
539 blend: None,
540 write_mask: wgpu::ColorWrites::ALL
541 })],
542 compilation_options: Default::default()
543 }),
544 primitive: wgpu::PrimitiveState {
545 topology: wgpu::PrimitiveTopology::TriangleList,
546 ..Default::default()
547 },
548 depth_stencil: None,
549 multisample: wgpu::MultisampleState::default(),
550 multiview: None,
551 cache: None
552 });
553
554 let mut command = crate::command_buffer::CommandBuffer::new(instance, "Generate Mipmaps");
556
557 for layer in 0..self.layer_count {
558 for mip_level in 1..self.mip_level_count {
559 let src_view = self.texture.create_view(&wgpu::TextureViewDescriptor {
560 label: Some(&format!("{}_mip_{}_src", self.label, mip_level)),
561 format: Some(self.format),
562 dimension: Some(wgpu::TextureViewDimension::D2),
563 aspect: wgpu::TextureAspect::All,
564 base_mip_level: mip_level - 1,
565 mip_level_count: Some(1),
566 base_array_layer: layer,
567 array_layer_count: Some(1),
568 usage: None
569 });
570
571 let dst_view = self.texture.create_view(&wgpu::TextureViewDescriptor {
572 label: Some(&format!("{}_mip_{}_dst", self.label, mip_level)),
573 format: Some(self.format),
574 dimension: Some(wgpu::TextureViewDimension::D2),
575 aspect: wgpu::TextureAspect::All,
576 base_mip_level: mip_level,
577 mip_level_count: Some(1),
578 base_array_layer: layer,
579 array_layer_count: Some(1),
580 usage: None
581 });
582
583 let bind_group = instance
584 .device
585 .create_bind_group(&wgpu::BindGroupDescriptor {
586 label: Some(&format!("{}_mip_{}_bind_group", self.label, mip_level)),
587 layout: &bind_group_layout,
588 entries: &[
589 wgpu::BindGroupEntry {
590 binding: 0,
591 resource: wgpu::BindingResource::TextureView(&src_view)
592 },
593 wgpu::BindGroupEntry {
594 binding: 1,
595 resource: wgpu::BindingResource::Sampler(&blit_sampler)
596 }
597 ]
598 });
599
600 {
601 let mut render_pass =
602 command
603 .encoder()
604 .begin_render_pass(&wgpu::RenderPassDescriptor {
605 label: Some(&format!(
606 "{}_mip_{}_render_pass",
607 self.label, mip_level
608 )),
609 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
610 view: &dst_view,
611 resolve_target: None,
612 ops: wgpu::Operations {
613 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
614 store: wgpu::StoreOp::Store
615 },
616 depth_slice: None
617 })],
618 depth_stencil_attachment: None,
619 timestamp_writes: None,
620 occlusion_query_set: None
621 });
622
623 render_pass.set_pipeline(&pipeline);
624 render_pass.set_bind_group(0, &bind_group, &[]);
625 render_pass.draw(0..3, 0..1);
626 }
627 }
628 }
629
630 command.submit(instance);
631 event!(
632 LogLevel::TRACE,
633 "Finished generating mipmaps for texture {}.",
634 self.label
635 );
636 }
637}