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