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