easy_wgpu/
texture.rs

1use gloss_img::DynImage;
2use image::imageops::FilterType;
3// use image::GenericImage;
4use image::{EncodableLayout, GenericImageView, ImageBuffer};
5use log::{debug, warn};
6use pollster::FutureExt;
7use std::borrow::Cow;
8use wgpu::{util::DeviceExt, CommandEncoderDescriptor, TextureFormat}; //enabled create_texture_with_data
9
10// use gloss_utils::gloss_image;
11use gloss_utils::numerical;
12
13use crate::{buffer::Buffer, mipmap::RenderMipmapGenerator};
14
15//aditional parameters for texture creation that usually you can leave as
16// default
17#[derive(Clone, Copy)]
18pub struct TexParams {
19    pub sample_count: u32,
20    pub mip_level_count: u32,
21    pub scale_factor: u32,
22}
23impl Default for TexParams {
24    fn default() -> Self {
25        Self {
26            sample_count: 1,
27            mip_level_count: 1,
28            scale_factor: 1,
29        }
30    }
31}
32impl TexParams {
33    pub fn from_desc(desc: &wgpu::TextureDescriptor) -> Self {
34        Self {
35            sample_count: desc.sample_count,
36            mip_level_count: desc.mip_level_count,
37            scale_factor: 1,
38        }
39    }
40    pub fn apply(&self, desc: &mut wgpu::TextureDescriptor) {
41        desc.sample_count = self.sample_count;
42        desc.mip_level_count = self.mip_level_count;
43    }
44}
45
46pub struct Texture {
47    pub texture: wgpu::Texture,
48    pub view: wgpu::TextureView,
49    pub sampler: wgpu::Sampler, //TODO should be optional or rather we should create a nearest and linear sampler as a global per frame uniform
50    // pub width: u32,
51    // pub height: u32,
52    // pub bind_group: Option<wgpu::BindGroup>, //cannot lazily create because it depends on the binding locations
53    pub tex_params: TexParams,
54}
55
56impl Texture {
57    pub fn new(
58        device: &wgpu::Device,
59        width: u32,
60        height: u32,
61        format: wgpu::TextureFormat,
62        usage: wgpu::TextureUsages,
63        tex_params: TexParams,
64    ) -> Self {
65        debug!("New texture");
66        // let format = wgpu::TextureFormat::Rgba8UnormSrgb;
67        let mut texture_desc = wgpu::TextureDescriptor {
68            size: wgpu::Extent3d {
69                width,
70                height,
71                depth_or_array_layers: 1,
72            },
73            mip_level_count: 1,
74            sample_count: 1,
75            dimension: wgpu::TextureDimension::D2,
76            format,
77            // usage: wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT,
78            usage,
79            label: None,
80            view_formats: &[],
81        };
82        tex_params.apply(&mut texture_desc);
83
84        let texture = device.create_texture(&texture_desc);
85        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
86        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
87            address_mode_u: wgpu::AddressMode::ClampToEdge,
88            address_mode_v: wgpu::AddressMode::ClampToEdge,
89            address_mode_w: wgpu::AddressMode::ClampToEdge,
90            mag_filter: wgpu::FilterMode::Linear,
91            min_filter: wgpu::FilterMode::Linear,
92            mipmap_filter: wgpu::FilterMode::Linear,
93            ..Default::default()
94        });
95
96        Self {
97            texture,
98            view,
99            sampler,
100            tex_params,
101            // width,
102            // height,
103            // bind_group: None,
104        }
105    }
106
107    /// # Panics
108    /// Will panic if bytes cannot be decoded into a image representation
109    pub fn from_bytes(device: &wgpu::Device, queue: &wgpu::Queue, bytes: &[u8], label: &str) -> Self {
110        let img = image::load_from_memory(bytes).unwrap();
111        Self::from_image(device, queue, &img, Some(label))
112    }
113
114    pub fn from_image(device: &wgpu::Device, queue: &wgpu::Queue, img: &image::DynamicImage, label: Option<&str>) -> Self {
115        let rgba = img.to_rgba8();
116        let dimensions = img.dimensions();
117
118        let size = wgpu::Extent3d {
119            width: dimensions.0,
120            height: dimensions.1,
121            depth_or_array_layers: 1,
122        };
123        let format = wgpu::TextureFormat::Rgba8UnormSrgb;
124        let desc = wgpu::TextureDescriptor {
125            label,
126            size,
127            mip_level_count: 1,
128            sample_count: 1,
129            dimension: wgpu::TextureDimension::D2,
130            format,
131            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
132            view_formats: &[],
133        };
134        let tex_params = TexParams::from_desc(&desc);
135        let texture = device.create_texture(&desc);
136
137        queue.write_texture(
138            wgpu::ImageCopyTexture {
139                aspect: wgpu::TextureAspect::All,
140                texture: &texture,
141                mip_level: 0,
142                origin: wgpu::Origin3d::ZERO,
143            },
144            &rgba,
145            wgpu::ImageDataLayout {
146                offset: 0,
147                bytes_per_row: Some(4 * dimensions.0),
148                rows_per_image: Some(dimensions.1),
149            },
150            size,
151        );
152
153        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
154        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
155            address_mode_u: wgpu::AddressMode::ClampToEdge,
156            address_mode_v: wgpu::AddressMode::ClampToEdge,
157            address_mode_w: wgpu::AddressMode::ClampToEdge,
158            mag_filter: wgpu::FilterMode::Linear,
159            min_filter: wgpu::FilterMode::Nearest,
160            mipmap_filter: wgpu::FilterMode::Nearest,
161            ..Default::default()
162        });
163
164        Self {
165            texture,
166            view,
167            sampler,
168            tex_params, /* width: dimensions.0,
169                         * height: dimensions.1,
170                         * bind_group: None, */
171        }
172    }
173
174    /// reads image from format and into this texture
175    /// if `is_srgb` is set then the reading will perform a conversion from
176    /// gamma space to linear space when sampling the texture in a shader
177    /// When writing to the texture, the opposite conversion takes place.
178    /// # Panics
179    /// Will panic if the path cannot be found
180    pub fn from_path(path: &str, device: &wgpu::Device, queue: &wgpu::Queue, is_srgb: bool) -> Self {
181        //read to cpu
182        let img = image::ImageReader::open(path).unwrap().decode().unwrap();
183        Self::from_img(
184            &img.try_into().unwrap(),
185            device,
186            queue,
187            is_srgb,
188            true,
189            false, //TODO what do we set as default here?
190            None,
191            None,
192        )
193    }
194
195    /// # Panics
196    /// Will panic if textures that have more than 1 byte per channel or more
197    /// than 4 channels.
198    #[allow(clippy::too_many_lines)]
199    #[allow(clippy::too_many_arguments)]
200    pub fn from_img(
201        img: &DynImage,
202        device: &wgpu::Device,
203        queue: &wgpu::Queue,
204        is_srgb: bool,
205        generate_mipmaps: bool,
206        mipmap_generation_cpu: bool,
207        staging_buffer: Option<&Buffer>,
208        mipmaper: Option<&RenderMipmapGenerator>,
209    ) -> Self {
210        let dimensions = img.dimensions();
211        let nr_channels = img.color().channel_count();
212        let bytes_per_channel = img.color().bytes_per_pixel() / nr_channels;
213        assert!(bytes_per_channel == 1, "We are only supporting textures which have 1 byte per channel.");
214        //convert 3 channels to 4 channels and keep 2 channels as 2 channels
215        let img_vec;
216        let img_buf = match nr_channels {
217            1 | 2 | 4 => img.as_bytes(),
218            3 => {
219                img_vec = img.to_rgba8().into_vec();
220                img_vec.as_bytes()
221            }
222            _ => panic!("Format with more than 4 channels not supported"),
223        };
224
225        let tex_format = Self::format_from_img(img, is_srgb);
226
227        let size = wgpu::Extent3d {
228            width: dimensions.0,
229            height: dimensions.1,
230            depth_or_array_layers: 1,
231        };
232        let mut nr_mip_maps = 1;
233        let mut usages = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST;
234        if generate_mipmaps {
235            nr_mip_maps = size.max_mips(wgpu::TextureDimension::D2);
236        }
237        if mipmaper.is_some() && generate_mipmaps {
238            usages |= RenderMipmapGenerator::required_usage();
239        }
240
241        let desc = wgpu::TextureDescriptor {
242            label: None,
243            size,
244            mip_level_count: nr_mip_maps,
245            sample_count: 1,
246            dimension: wgpu::TextureDimension::D2,
247            format: tex_format,
248            usage: usages,
249            view_formats: &[],
250        };
251        let tex_params = TexParams::from_desc(&desc);
252
253        let texture = device.create_texture(&desc); //create with all mips but upload only 1 mip
254
255        Self::upload_single_mip(&texture, device, queue, &desc, img_buf, staging_buffer, 0);
256
257        //mipmaps
258        if generate_mipmaps {
259            Self::generate_mipmaps(
260                img,
261                &texture,
262                device,
263                queue,
264                &desc,
265                nr_mip_maps,
266                mipmap_generation_cpu,
267                staging_buffer,
268                mipmaper,
269            );
270        }
271
272        // let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
273        let view = texture.create_view(&wgpu::TextureViewDescriptor {
274            mip_level_count: Some(nr_mip_maps),
275            ..Default::default()
276        });
277        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
278            address_mode_u: wgpu::AddressMode::ClampToEdge,
279            address_mode_v: wgpu::AddressMode::ClampToEdge,
280            address_mode_w: wgpu::AddressMode::ClampToEdge,
281            mag_filter: wgpu::FilterMode::Linear,
282            min_filter: wgpu::FilterMode::Nearest,
283            mipmap_filter: wgpu::FilterMode::Nearest,
284            ..Default::default()
285        });
286
287        Self {
288            texture,
289            view,
290            sampler,
291            tex_params, /* width: dimensions.0,
292                         * height: dimensions.1,
293                         * bind_group: None, */
294        }
295    }
296
297    /// # Panics
298    /// Will panic if the image has more than 1 byte per channel
299    #[allow(clippy::too_many_arguments)]
300    pub fn update_from_img(
301        &mut self,
302        img: &DynImage,
303        device: &wgpu::Device,
304        queue: &wgpu::Queue,
305        is_srgb: bool,
306        generate_mipmaps: bool,
307        mipmap_generation_cpu: bool,
308        staging_buffer: Option<&Buffer>,
309        mipmaper: Option<&RenderMipmapGenerator>,
310    ) {
311        // let dimensions = img.dimensions();
312        let nr_channels = img.color().channel_count();
313        let bytes_per_channel = img.color().bytes_per_pixel() / nr_channels;
314        assert!(bytes_per_channel == 1, "We are only supporting textures which have 1 byte per channel.");
315
316        // TODO refactor this into its own func because there is a lot of duplication
317        // with the from_img function convert 3 channels to 4 channels and keep
318        // 2 channels as 2 channels
319        let img_vec;
320        let img_buf = match nr_channels {
321            1 | 2 | 4 => img.as_bytes(),
322            3 => {
323                img_vec = img.to_rgba8().into_vec();
324                img_vec.as_bytes()
325            }
326            _ => panic!("Format with more than 4 channels not supported"),
327        };
328
329        let size = Self::extent_from_img(img);
330        let tex_format = Self::format_from_img(img, is_srgb);
331        let mut nr_mip_maps = 1;
332        let mut usages = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST;
333        if generate_mipmaps {
334            nr_mip_maps = size.max_mips(wgpu::TextureDimension::D2);
335        }
336        if mipmaper.is_some() && generate_mipmaps {
337            usages |= RenderMipmapGenerator::required_usage();
338        }
339
340        let desc = wgpu::TextureDescriptor {
341            label: None,
342            size,
343            mip_level_count: nr_mip_maps,
344            sample_count: 1,
345            dimension: wgpu::TextureDimension::D2,
346            format: tex_format,
347            usage: usages,
348            view_formats: &[],
349        };
350
351        Self::upload_single_mip(&self.texture, device, queue, &desc, img_buf, staging_buffer, 0);
352
353        //mipmaps
354        if generate_mipmaps {
355            Self::generate_mipmaps(
356                img,
357                &self.texture,
358                device,
359                queue,
360                &desc,
361                nr_mip_maps,
362                mipmap_generation_cpu,
363                staging_buffer,
364                mipmaper,
365            );
366        }
367
368        // let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
369        let view = self.texture.create_view(&wgpu::TextureViewDescriptor {
370            mip_level_count: Some(nr_mip_maps),
371            ..Default::default()
372        });
373
374        //update
375        self.view = view;
376    }
377
378    #[allow(clippy::too_many_arguments)]
379    pub fn generate_mipmaps(
380        img: &DynImage,
381        texture: &wgpu::Texture,
382        device: &wgpu::Device,
383        queue: &wgpu::Queue,
384        desc: &wgpu::TextureDescriptor,
385        nr_mip_maps: u32,
386        mipmap_generation_cpu: bool,
387        staging_buffer: Option<&Buffer>,
388        mipmaper: Option<&RenderMipmapGenerator>,
389    ) {
390        let nr_channels = img.color().channel_count();
391        if mipmap_generation_cpu {
392            //CPU generation
393            //similar to https://github.com/DGriffin91/bevy_mod_mipmap_generator/blob/main/src/lib.rs
394            let mut img_mip = DynImage::new(1, 1, image::ColorType::L8);
395            for mip_lvl in 1..nr_mip_maps {
396                let mip_size = desc.mip_level_size(mip_lvl).unwrap();
397                let prev_img_mip = if mip_lvl == 1 { img } else { &img_mip };
398                img_mip = prev_img_mip.resize_exact(mip_size.width, mip_size.height, FilterType::Triangle);
399                debug!("mip lvl {} has size {:?}", mip_lvl, mip_size);
400
401                let img_mip_vec;
402                let img_mip_buf = match nr_channels {
403                    1 | 2 | 4 => img_mip.as_bytes(),
404                    3 => {
405                        img_mip_vec = img_mip.to_rgba8().into_vec();
406                        img_mip_vec.as_bytes()
407                    }
408                    _ => panic!("Format with more than 4 channels not supported"),
409                };
410
411                Self::upload_single_mip(texture, device, queue, desc, img_mip_buf, staging_buffer, mip_lvl);
412            }
413        } else {
414            //GPU mipmaps generation
415            if let Some(mipmaper) = mipmaper {
416                let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor::default());
417                mipmaper.generate(device, &mut encoder, texture, desc).unwrap();
418                queue.submit(std::iter::once(encoder.finish()));
419            } else {
420                warn!("Couldn't generate mipmaps since the mipmapper was not provided");
421            }
422        }
423    }
424
425    pub fn extent_from_img(img: &DynImage) -> wgpu::Extent3d {
426        let dimensions = img.dimensions();
427        wgpu::Extent3d {
428            width: dimensions.0,
429            height: dimensions.1,
430            depth_or_array_layers: 1,
431        }
432    }
433
434    /// # Panics
435    /// Will panic if the image has more than 1 byte per channel
436    pub fn format_from_img(img: &DynImage, is_srgb: bool) -> wgpu::TextureFormat {
437        let nr_channels = img.color().channel_count();
438        let bytes_per_channel = img.color().bytes_per_pixel() / nr_channels;
439        assert!(bytes_per_channel == 1, "We are only supporting textures which have 1 byte per channel.");
440
441        //get a format for the texture
442        let mut tex_format = match nr_channels {
443            1 => wgpu::TextureFormat::R8Unorm,
444            2 => wgpu::TextureFormat::Rg8Unorm,
445            3 | 4 => wgpu::TextureFormat::Rgba8Unorm,
446            _ => panic!("Format with more than 4 channels not supported"),
447        };
448        if is_srgb {
449            tex_format = tex_format.add_srgb_suffix();
450        }
451
452        tex_format
453    }
454
455    /// Basically the same as `device.create_texture_with_data` but without the
456    /// creation part and the data is assumed to contain only one mip # Panics
457    /// Will panic if the data does not fit in the defined mipmaps described in
458    /// textureDescriptor
459    pub fn upload_single_mip(
460        texture: &wgpu::Texture,
461        device: &wgpu::Device,
462        queue: &wgpu::Queue,
463        desc: &wgpu::TextureDescriptor,
464        data: &[u8],
465        staging_buffer: Option<&Buffer>,
466        mip: u32,
467    ) {
468        let mut mip_size = desc.mip_level_size(mip).unwrap();
469        // copying layers separately
470        if desc.dimension != wgpu::TextureDimension::D3 {
471            mip_size.depth_or_array_layers = 1;
472        }
473
474        // Will return None only if it's a combined depth-stencil format
475        // If so, default to 4, validation will fail later anyway since the depth or
476        // stencil aspect needs to be written to individually
477        let block_size = desc.format.block_copy_size(None).unwrap_or(4);
478        let (block_width, block_height) = desc.format.block_dimensions();
479
480        // When uploading mips of compressed textures and the mip is supposed to be
481        // a size that isn't a multiple of the block size, the mip needs to be uploaded
482        // as its "physical size" which is the size rounded up to the nearest block
483        // size.
484        let mip_physical = mip_size.physical_size(desc.format);
485
486        // All these calculations are performed on the physical size as that's the
487        // data that exists in the buffer.
488        let width_blocks = mip_physical.width / block_width;
489        let height_blocks = mip_physical.height / block_height;
490
491        let bytes_per_row = width_blocks * block_size;
492        // let data_size = bytes_per_row * height_blocks *
493        // mip_size.depth_or_array_layers;
494
495        // let end_offset = binary_offset + data_size as usize;
496
497        if let Some(staging_buffer) = staging_buffer {
498            warn!("Using slow CPU->GPU transfer for texture upload. Might use less memory that staging buffer using by wgpu but it will be slower.");
499
500            //get some metadata
501            let bytes_per_row_unpadded = texture.format().block_copy_size(None).unwrap() * mip_size.width;
502            let bytes_per_row_padded = numerical::align(bytes_per_row_unpadded, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
503
504            //map buffer and copy into it
505            // https://docs.rs/wgpu/latest/wgpu/struct.Buffer.html#mapping-buffers
506            //the mapping range has to be aligned to COPY_BUFFER_ALIGNMENT(4 bytes)
507            let slice_size = numerical::align(u32::try_from(data.len()).unwrap(), u32::try_from(wgpu::COPY_BUFFER_ALIGNMENT).unwrap());
508            {
509                let buffer_slice = staging_buffer.buffer.slice(0..u64::from(slice_size));
510                // NOTE: We have to create the mapping THEN device.poll() before await
511                // the future. Otherwise the application will freeze.
512                let (tx, rx) = futures::channel::oneshot::channel();
513                buffer_slice.map_async(wgpu::MapMode::Write, move |result| {
514                    tx.send(result).unwrap();
515                });
516                device.poll(wgpu::Maintain::Wait);
517                rx.block_on().unwrap().unwrap();
518                let mut buf_data = buffer_slice.get_mapped_range_mut();
519
520                //copy into it
521                buf_data.get_mut(0..data.len()).unwrap().clone_from_slice(data);
522            }
523
524            //finish
525            staging_buffer.buffer.unmap();
526
527            //copy from buffer to texture
528            let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
529            encoder.copy_buffer_to_texture(
530                wgpu::ImageCopyBuffer {
531                    buffer: &staging_buffer.buffer,
532                    layout: wgpu::ImageDataLayout {
533                        offset: 0,
534                        bytes_per_row: Some(bytes_per_row_padded),
535                        rows_per_image: Some(mip_size.height),
536                    },
537                },
538                wgpu::ImageCopyTexture {
539                    aspect: wgpu::TextureAspect::All,
540                    texture,
541                    mip_level: mip,
542                    origin: wgpu::Origin3d::ZERO,
543                },
544                wgpu::Extent3d {
545                    width: mip_size.width,
546                    height: mip_size.height,
547                    depth_or_array_layers: 1,
548                },
549            );
550            queue.submit(Some(encoder.finish()));
551
552            //wait to finish because we might be reusing the staging buffer for
553            // something else later TODO maybe this is not needed
554            // since the mapping will block either way if the buffer is still in
555            // use device.poll(wgpu::Maintain::Wait);
556        } else {
557            //Use wgpu write_texture which schedules internally the transfer to happen
558            // later
559            queue.write_texture(
560                wgpu::ImageCopyTexture {
561                    texture,
562                    mip_level: mip,
563                    origin: wgpu::Origin3d { x: 0, y: 0, z: 0 },
564                    aspect: wgpu::TextureAspect::All,
565                },
566                data,
567                wgpu::ImageDataLayout {
568                    offset: 0,
569                    bytes_per_row: Some(bytes_per_row),
570                    rows_per_image: Some(height_blocks),
571                },
572                mip_physical,
573            );
574        }
575
576        //-=---------------------
577    }
578
579    /// Basically the same as `device.create_texture_with_data` but without the
580    /// creation part Assumes the data contains info for all mips
581    /// # Panics
582    /// Will panic if the data does not fit in the defined mipmaps described in
583    /// textureDescriptor
584    pub fn upload_all_mips(
585        texture: &wgpu::Texture,
586        device: &wgpu::Device,
587        queue: &wgpu::Queue,
588        desc: &wgpu::TextureDescriptor,
589        data: &[u8],
590        staging_buffer: Option<&Buffer>,
591    ) {
592        // Will return None only if it's a combined depth-stencil format
593        // If so, default to 4, validation will fail later anyway since the depth or
594        // stencil aspect needs to be written to individually
595        let block_size = desc.format.block_copy_size(None).unwrap_or(4);
596        let (block_width, block_height) = desc.format.block_dimensions();
597        let layer_iterations = desc.array_layer_count();
598
599        let (min_mip, max_mip) = (0, desc.mip_level_count);
600
601        let mut binary_offset = 0;
602        for layer in 0..layer_iterations {
603            for mip in min_mip..max_mip {
604                let mut mip_size = desc.mip_level_size(mip).unwrap();
605                // copying layers separately
606                if desc.dimension != wgpu::TextureDimension::D3 {
607                    mip_size.depth_or_array_layers = 1;
608                }
609
610                // When uploading mips of compressed textures and the mip is supposed to be
611                // a size that isn't a multiple of the block size, the mip needs to be uploaded
612                // as its "physical size" which is the size rounded up to the nearest block
613                // size.
614                let mip_physical = mip_size.physical_size(desc.format);
615
616                // All these calculations are performed on the physical size as that's the
617                // data that exists in the buffer.
618                let width_blocks = mip_physical.width / block_width;
619                let height_blocks = mip_physical.height / block_height;
620
621                let bytes_per_row = width_blocks * block_size;
622                let data_size = bytes_per_row * height_blocks * mip_size.depth_or_array_layers;
623
624                let end_offset = binary_offset + data_size as usize;
625
626                if let Some(staging_buffer) = staging_buffer {
627                    warn!("Using slow CPU->GPU transfer for texture upload. Might use less memory that staging buffer using by wgpu but it will be slower.");
628
629                    //get some metadata
630                    let bytes_per_row_unpadded = texture.format().block_copy_size(None).unwrap() * mip_size.width;
631                    let bytes_per_row_padded = numerical::align(bytes_per_row_unpadded, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
632
633                    //map buffer and copy into it
634                    // https://docs.rs/wgpu/latest/wgpu/struct.Buffer.html#mapping-buffers
635                    let data_to_copy = &data[binary_offset..end_offset];
636                    //the mapping range has to be aligned to COPY_BUFFER_ALIGNMENT(4 bytes)
637                    let slice_size = numerical::align(
638                        u32::try_from(data_to_copy.len()).unwrap(),
639                        u32::try_from(wgpu::COPY_BUFFER_ALIGNMENT).unwrap(),
640                    );
641                    {
642                        let buffer_slice = staging_buffer.buffer.slice(0..u64::from(slice_size));
643                        // NOTE: We have to create the mapping THEN device.poll() before await
644                        // the future. Otherwise the application will freeze.
645                        let (tx, rx) = futures::channel::oneshot::channel();
646                        buffer_slice.map_async(wgpu::MapMode::Write, move |result| {
647                            tx.send(result).unwrap();
648                        });
649                        device.poll(wgpu::Maintain::Wait);
650                        rx.block_on().unwrap().unwrap();
651                        let mut buf_data = buffer_slice.get_mapped_range_mut();
652
653                        //copy into it
654                        buf_data.get_mut(0..data_to_copy.len()).unwrap().clone_from_slice(data_to_copy);
655                    }
656
657                    //finish
658                    staging_buffer.buffer.unmap();
659
660                    //copy from buffer to texture
661                    let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
662                    encoder.copy_buffer_to_texture(
663                        wgpu::ImageCopyBuffer {
664                            buffer: &staging_buffer.buffer,
665                            layout: wgpu::ImageDataLayout {
666                                offset: 0,
667                                bytes_per_row: Some(bytes_per_row_padded),
668                                rows_per_image: Some(mip_size.height),
669                            },
670                        },
671                        wgpu::ImageCopyTexture {
672                            aspect: wgpu::TextureAspect::All,
673                            texture,
674                            mip_level: mip,
675                            origin: wgpu::Origin3d::ZERO,
676                        },
677                        wgpu::Extent3d {
678                            width: mip_size.width,
679                            height: mip_size.height,
680                            depth_or_array_layers: 1,
681                        },
682                    );
683                    queue.submit(Some(encoder.finish()));
684
685                    //wait to finish because we might be reusing the staging
686                    // buffer for something else later
687                    // TODO maybe this is not needed since the mapping will
688                    // block either way if the buffer is still in use
689                    // device.poll(wgpu::Maintain::Wait);
690                } else {
691                    //Use wgpu write_texture which schedules internally the transfer to happen
692                    // later
693                    queue.write_texture(
694                        wgpu::ImageCopyTexture {
695                            texture,
696                            mip_level: mip,
697                            origin: wgpu::Origin3d { x: 0, y: 0, z: layer },
698                            aspect: wgpu::TextureAspect::All,
699                        },
700                        &data[binary_offset..end_offset],
701                        wgpu::ImageDataLayout {
702                            offset: 0,
703                            bytes_per_row: Some(bytes_per_row),
704                            rows_per_image: Some(height_blocks),
705                        },
706                        mip_physical,
707                    );
708                }
709
710                binary_offset = end_offset;
711            }
712        }
713    }
714
715    pub fn upload_from_cpu_with_staging_buffer(
716        texture: &wgpu::Texture,
717        device: &wgpu::Device,
718        queue: &wgpu::Queue,
719        desc: &wgpu::TextureDescriptor,
720        data: &[u8],
721        staging_buffer: &Buffer,
722        mip_lvl: u32,
723    ) {
724        let mip_size = desc.mip_level_size(mip_lvl).unwrap();
725
726        //map buffer and copy into it
727        // https://docs.rs/wgpu/latest/wgpu/struct.Buffer.html#mapping-buffers
728        {
729            let buffer_slice = staging_buffer.buffer.slice(0..data.len() as u64);
730            // NOTE: We have to create the mapping THEN device.poll() before await
731            // the future. Otherwise the application will freeze.
732            let (tx, rx) = futures::channel::oneshot::channel();
733            buffer_slice.map_async(wgpu::MapMode::Write, move |result| {
734                tx.send(result).unwrap();
735            });
736            device.poll(wgpu::Maintain::Wait);
737            rx.block_on().unwrap().unwrap();
738            let mut buf_data = buffer_slice.get_mapped_range_mut();
739
740            //copy into it
741            buf_data.clone_from_slice(data);
742        }
743
744        //finish
745        staging_buffer.buffer.unmap();
746
747        //get some metadata
748        let bytes_per_row_unpadded = texture.format().block_copy_size(None).unwrap() * mip_size.width;
749        let bytes_per_row_padded = numerical::align(bytes_per_row_unpadded, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
750
751        //copy from buffer to texture
752        let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
753        encoder.copy_buffer_to_texture(
754            wgpu::ImageCopyBuffer {
755                buffer: &staging_buffer.buffer,
756                layout: wgpu::ImageDataLayout {
757                    offset: 0,
758                    bytes_per_row: Some(bytes_per_row_padded),
759                    rows_per_image: Some(mip_size.height),
760                },
761            },
762            wgpu::ImageCopyTexture {
763                aspect: wgpu::TextureAspect::All,
764                texture,
765                mip_level: mip_lvl,
766                origin: wgpu::Origin3d::ZERO,
767            },
768            wgpu::Extent3d {
769                width: mip_size.width,
770                height: mip_size.height,
771                depth_or_array_layers: 1,
772            },
773        );
774        queue.submit(Some(encoder.finish()));
775
776        //wait to finish because we might be reusing the staging buffer for something
777        // else later
778        device.poll(wgpu::Maintain::Wait);
779    }
780
781    /// This functions downloads the texture to the cpu and returns a `DynImage`
782    /// The aspect of the texture to download is specified by the aspect parameter
783    /// This is required since we use texture format `Depth32FloatStencil8`
784    /// which has both a depth and a stencil component
785    pub async fn download_to_cpu(&self, device: &wgpu::Device, queue: &wgpu::Queue, aspect: wgpu::TextureAspect) -> DynImage {
786        // create buffer
787        let bytes_per_row_unpadded = self.texture.format().block_copy_size(None).unwrap_or(4) * self.width();
788        let bytes_per_row_padded = numerical::align(bytes_per_row_unpadded, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
789        let output_buffer_size = u64::from(bytes_per_row_padded * self.height());
790        let output_buffer_desc = wgpu::BufferDescriptor {
791            size: output_buffer_size,
792            usage: wgpu::BufferUsages::COPY_DST
793        // this tells wpgu that we want to read this buffer from the cpu
794        | wgpu::BufferUsages::MAP_READ,
795            label: None,
796            mapped_at_creation: false,
797        };
798
799        let output_buffer = device.create_buffer(&output_buffer_desc);
800
801        //copy from texture to buffer
802        let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
803        encoder.copy_texture_to_buffer(
804            wgpu::ImageCopyTexture {
805                aspect,
806                texture: &self.texture,
807                mip_level: 0,
808                origin: wgpu::Origin3d::ZERO,
809            },
810            wgpu::ImageCopyBuffer {
811                buffer: &output_buffer,
812                layout: wgpu::ImageDataLayout {
813                    offset: 0,
814                    bytes_per_row: Some(bytes_per_row_padded),
815                    rows_per_image: Some(self.height()),
816                },
817            },
818            wgpu::Extent3d {
819                width: self.width(),
820                height: self.height(),
821                depth_or_array_layers: 1,
822            },
823        );
824        queue.submit(Some(encoder.finish()));
825
826        // map and get to cpu
827        // We need to scope the mapping variables so that we can unmap the buffer
828
829        // let mut buffer = DynImage::new(self.width(), self.height(),
830        // self.texture.format()); let mut buffer = match self.texture.format()
831        // {     TextureFormat::Rgba8Unorm => DynImage::new_rgba8(self.width(),
832        // self.height()),     TextureFormat::Depth32Float =>
833        // DynImage::new_luma32f(self.width(), self.height()),     _ => panic!("
834        // Texture format not implemented!"), };
835
836        let img: Option<DynImage> = {
837            let buffer_slice = output_buffer.slice(..);
838
839            // NOTE: We have to create the mapping THEN device.poll() before await
840            // the future. Otherwise the application will freeze.
841            //TODO maybe change the future_intrusive to futures. Future_intrusive seems to
842            // give some issues on wasm
843            let (tx, rx) = futures_intrusive::channel::shared::oneshot_channel();
844            buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
845                tx.send(result).unwrap();
846            });
847            device.poll(wgpu::Maintain::Wait);
848            rx.receive().await.unwrap().unwrap();
849
850            let data = buffer_slice.get_mapped_range();
851
852            //TODO remove padding and copy into image
853            // https://github.com/rerun-io/rerun/blob/93146b6d04f8f494258901c8b892eee0bb31b1a8/crates/re_renderer/src/texture_info.rs#L57
854            let data_unpadded = Texture::remove_padding(data.as_bytes(), bytes_per_row_unpadded, bytes_per_row_padded, self.height());
855
856            // let copy_from = data_unpadded.as_bytes();
857            // buffer.copy_from_bytes(self.width(), self.height(), copy_from);
858            let w = self.width();
859            let h = self.height();
860            match self.texture.format() {
861                TextureFormat::Rgba8Unorm => ImageBuffer::from_raw(w, h, data_unpadded.to_vec()).map(DynImage::ImageRgba8),
862                TextureFormat::Bgra8Unorm => {
863                    let bgra_data = data_unpadded.to_vec();
864                    // Convert BGRA to RGBA by swapping channels
865                    let mut rgba_data = bgra_data.clone();
866                    for chunk in rgba_data.chunks_exact_mut(4) {
867                        chunk.swap(0, 2); // Swap B and R
868                    }
869                    ImageBuffer::from_raw(w, h, rgba_data).map(DynImage::ImageRgba8)
870                }
871                TextureFormat::Rgba32Float => ImageBuffer::from_raw(w, h, numerical::u8_to_f32_vec(&data_unpadded)).map(DynImage::ImageRgba32F),
872                TextureFormat::Depth32Float | TextureFormat::Depth32FloatStencil8 => {
873                    ImageBuffer::from_raw(w, h, numerical::u8_to_f32_vec(&data_unpadded)).map(DynImage::ImageLuma32F)
874                }
875                x => panic!("Texture format not implemented! {x:?}"),
876            }
877        };
878        output_buffer.unmap();
879        img.unwrap()
880    }
881
882    pub async fn download_pixel_to_cpu(&self, device: &wgpu::Device, queue: &wgpu::Queue, aspect: wgpu::TextureAspect, x: u32, y: u32) -> DynImage {
883        // Create a single pixel buffer to read back the selected pixel
884        let output_buffer_desc = wgpu::BufferDescriptor {
885            label: Some("ID Readback Buffer"),
886            size: 4, // 4 bytes for a single u32
887            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
888            mapped_at_creation: false,
889        };
890        let output_buffer = device.create_buffer(&output_buffer_desc);
891
892        let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
893            label: Some("ID Readback Encoder"),
894        });
895
896        // let scaled_x = x / self.tex_params.scale_factor;
897        // let scaled_y = y / self.tex_params.scale_factor;
898
899        encoder.copy_texture_to_buffer(
900            wgpu::ImageCopyTexture {
901                aspect,
902                texture: &self.texture,
903                mip_level: 0,
904                origin: wgpu::Origin3d { x, y, z: 0 },
905            },
906            wgpu::ImageCopyBuffer {
907                buffer: &output_buffer,
908                layout: wgpu::ImageDataLayout {
909                    offset: 0,
910                    bytes_per_row: None,
911                    rows_per_image: None,
912                },
913            },
914            wgpu::Extent3d {
915                width: 1,
916                height: 1,
917                depth_or_array_layers: 1,
918            },
919        );
920
921        queue.submit(Some(encoder.finish()));
922
923        let pixel: Option<DynImage> = {
924            let buffer_slice = output_buffer.slice(..);
925
926            let (tx, rx) = futures_intrusive::channel::shared::oneshot_channel();
927            buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
928                tx.send(result).unwrap();
929            });
930            device.poll(wgpu::Maintain::Wait);
931            rx.receive().await.unwrap().unwrap();
932
933            let data = buffer_slice.get_mapped_range();
934            match self.texture.format() {
935                TextureFormat::Rgba8Unorm => {
936                    // This would be a ingle byte image
937                    let single_pixel_bytes = *data.to_vec().first().unwrap();
938                    ImageBuffer::from_raw(1, 1, [single_pixel_bytes].to_vec()).map(DynImage::ImageLuma8)
939                }
940                x => panic!("Texture format not implemented! {x:?}"),
941            }
942        };
943        output_buffer.unmap();
944        pixel.unwrap()
945    }
946
947    pub fn remove_padding(buffer: &[u8], bytes_per_row_unpadded: u32, bytes_per_row_padded: u32, nr_rows: u32) -> Cow<'_, [u8]> {
948        // re_tracing::profile_function!();
949
950        // assert_eq!(buffer.len() as wgpu::BufferAddress, self.buffer_size_padded);
951
952        if bytes_per_row_padded == bytes_per_row_unpadded {
953            return Cow::Borrowed(buffer);
954        }
955
956        let mut unpadded_buffer = Vec::with_capacity((bytes_per_row_unpadded * nr_rows) as _);
957
958        for row in 0..nr_rows {
959            let offset = (bytes_per_row_padded * row) as usize;
960            unpadded_buffer.extend_from_slice(&buffer[offset..(offset + bytes_per_row_unpadded as usize)]);
961        }
962
963        unpadded_buffer.into()
964    }
965
966    pub fn create_bind_group_layout(device: &wgpu::Device, binding_tex: u32, binding_sampler: u32) -> wgpu::BindGroupLayout {
967        device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
968            entries: &[
969                wgpu::BindGroupLayoutEntry {
970                    binding: binding_tex, //matches with the @binding in the shader
971                    visibility: wgpu::ShaderStages::FRAGMENT,
972                    ty: wgpu::BindingType::Texture {
973                        multisampled: false,
974                        view_dimension: wgpu::TextureViewDimension::D2,
975                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
976                    },
977                    count: None,
978                },
979                wgpu::BindGroupLayoutEntry {
980                    binding: binding_sampler, //matches with the @binding in the shader
981                    visibility: wgpu::ShaderStages::FRAGMENT,
982                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
983                    count: None,
984                },
985            ],
986            label: Some("texture_bind_group_layout"),
987        })
988    }
989    #[must_use]
990    pub fn depth_linearize(&self, device: &wgpu::Device, queue: &wgpu::Queue, near: f32, far: f32) -> DynImage {
991        //panics if depth map retrieval is attempted with MSAA sample count set to > 1
992        assert!(
993            !(self.texture.sample_count() > 1 && self.texture.format() == TextureFormat::Depth32Float),
994            "InvalidSampleCount: Depth maps not supported for MSAA sample count {} (Use a config to set msaa_nr_samples as 1)",
995            self.texture.sample_count()
996        );
997
998        // This download specifically happens for a depth map so we only want to download the depth component
999        let aspect = wgpu::TextureAspect::DepthOnly;
1000        let dynamic_img = pollster::block_on(self.download_to_cpu(device, queue, aspect));
1001        let w = dynamic_img.width();
1002        let h = dynamic_img.height();
1003        let c = dynamic_img.channels();
1004        assert!(c == 1, "Depth maps should have only 1 channel");
1005
1006        let linearized_img = match dynamic_img {
1007            DynImage::ImageLuma32F(v) => {
1008                let img_vec_ndc = v.to_vec();
1009                let img_vec: Vec<f32> = img_vec_ndc.iter().map(|&x| numerical::linearize_depth_reverse_z(x, near, far)).collect();
1010                DynImage::ImageLuma32F(ImageBuffer::from_raw(w, h, img_vec).unwrap())
1011            }
1012            _ => panic!("Texture format not implemented for remap (Only for depths)!"),
1013        };
1014        linearized_img
1015    }
1016
1017    pub fn create_bind_group(&self, device: &wgpu::Device, binding_tex: u32, binding_sampler: u32) -> wgpu::BindGroup {
1018        //create bind group
1019        //recreate the bind group
1020        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1021            layout: &Self::create_bind_group_layout(device, binding_tex, binding_sampler),
1022            entries: &[
1023                wgpu::BindGroupEntry {
1024                    binding: binding_tex,
1025                    resource: wgpu::BindingResource::TextureView(&self.view),
1026                },
1027                wgpu::BindGroupEntry {
1028                    binding: binding_sampler,
1029                    resource: wgpu::BindingResource::Sampler(&self.sampler),
1030                },
1031            ],
1032            label: Some("bind_group"),
1033        });
1034        bind_group
1035    }
1036
1037    pub fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32) {
1038        //essentially creates a whole new texture with the same format and usage
1039        let format = self.texture.format();
1040        let usage = self.texture.usage();
1041        let mut new = Self::new(device, width, height, format, usage, self.tex_params);
1042        std::mem::swap(self, &mut new);
1043    }
1044
1045    //make a default 4x4 texture that can be used as a dummy texture
1046    pub fn create_default_texture(device: &wgpu::Device, queue: &wgpu::Queue) -> Self {
1047        // //read to cpu
1048        // let img = ImageReader::open(path).unwrap().decode().unwrap();
1049        // let rgba = img.to_rgba8();
1050
1051        //we make a 4x4 texture because some gbus don't allow 1x1 or 2x2 so 4x4 seems
1052        // to be the minimum allowed
1053        let width = 4;
1054        let height = 4;
1055
1056        let mut img_data: Vec<u8> = Vec::new();
1057        for _ in 0..height {
1058            for _ in 0..width {
1059                //assume 4 channels
1060                img_data.push(255);
1061                img_data.push(0);
1062                img_data.push(0);
1063                img_data.push(0);
1064            }
1065        }
1066
1067        // let rgba = img.to_rgba8();
1068        // let dimensions = img.dimensions();
1069
1070        let size = wgpu::Extent3d {
1071            width,
1072            height,
1073            depth_or_array_layers: 1,
1074        };
1075        // let format = wgpu::TextureFormat::Rgba8UnormSrgb;
1076        let format = wgpu::TextureFormat::Rgba8UnormSrgb;
1077        let desc = wgpu::TextureDescriptor {
1078            label: None,
1079            size,
1080            mip_level_count: 1,
1081            sample_count: 1,
1082            dimension: wgpu::TextureDimension::D2,
1083            format,
1084            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1085            view_formats: &[],
1086        };
1087        let tex_params = TexParams::from_desc(&desc);
1088        let texture = device.create_texture_with_data(queue, &desc, wgpu::util::TextureDataOrder::LayerMajor, img_data.as_slice());
1089
1090        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1091        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1092            address_mode_u: wgpu::AddressMode::ClampToEdge,
1093            address_mode_v: wgpu::AddressMode::ClampToEdge,
1094            address_mode_w: wgpu::AddressMode::ClampToEdge,
1095            mag_filter: wgpu::FilterMode::Linear,
1096            min_filter: wgpu::FilterMode::Nearest,
1097            mipmap_filter: wgpu::FilterMode::Nearest,
1098            ..Default::default()
1099        });
1100
1101        Self {
1102            texture,
1103            view,
1104            sampler,
1105            tex_params, /* width,
1106                         * height, */
1107        }
1108    }
1109
1110    pub fn create_default_cubemap(device: &wgpu::Device, queue: &wgpu::Queue) -> Self {
1111        // //read to cpu
1112        // let img = ImageReader::open(path).unwrap().decode().unwrap();
1113        // let rgba = img.to_rgba8();
1114
1115        //we make a 4x4 texture because some gbus don't allow 1x1 or 2x2 so 4x4 seems
1116        // to be the minimum allowed
1117        let width = 4;
1118        let height = 4;
1119
1120        let mut img_data: Vec<u8> = Vec::new();
1121        for _ in 0..6 {
1122            for _ in 0..height {
1123                for _ in 0..width {
1124                    //assume 4 channels
1125                    img_data.push(255);
1126                    img_data.push(0);
1127                    img_data.push(0);
1128                    img_data.push(0);
1129                }
1130            }
1131        }
1132
1133        let size = wgpu::Extent3d {
1134            width,
1135            height,
1136            depth_or_array_layers: 6,
1137        };
1138        // let format = wgpu::TextureFormat::Rgba8UnormSrgb;
1139        let format = wgpu::TextureFormat::Rgba8UnormSrgb;
1140        let desc = wgpu::TextureDescriptor {
1141            label: None,
1142            size,
1143            mip_level_count: 1,
1144            sample_count: 1,
1145            dimension: wgpu::TextureDimension::D2,
1146            format,
1147            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1148            view_formats: &[],
1149        };
1150        let tex_params = TexParams::from_desc(&desc);
1151        let texture = device.create_texture_with_data(queue, &desc, wgpu::util::TextureDataOrder::LayerMajor, img_data.as_slice());
1152
1153        let view = texture.create_view(&wgpu::TextureViewDescriptor {
1154            dimension: Some(wgpu::TextureViewDimension::Cube),
1155            ..Default::default()
1156        });
1157        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1158            address_mode_u: wgpu::AddressMode::ClampToEdge,
1159            address_mode_v: wgpu::AddressMode::ClampToEdge,
1160            address_mode_w: wgpu::AddressMode::ClampToEdge,
1161            mag_filter: wgpu::FilterMode::Linear,
1162            min_filter: wgpu::FilterMode::Linear,
1163            mipmap_filter: wgpu::FilterMode::Linear,
1164            ..Default::default()
1165        });
1166
1167        Self {
1168            texture,
1169            view,
1170            sampler,
1171            tex_params, /* width,
1172                         * height, */
1173        }
1174    }
1175
1176    pub fn width(&self) -> u32 {
1177        self.texture.width()
1178    }
1179    pub fn height(&self) -> u32 {
1180        self.texture.height()
1181    }
1182    pub fn extent(&self) -> wgpu::Extent3d {
1183        wgpu::Extent3d {
1184            width: self.width(),
1185            height: self.height(),
1186            depth_or_array_layers: 1,
1187        }
1188    }
1189    // pub fn clone(&self) -> Self {
1190    //     Self {
1191    //         texture: self.texture,
1192    //         view: (),
1193    //         sampler: (),
1194    //         width: (),
1195    //         height: (),
1196    //     }
1197    // }
1198}