visor_nannou_wgpu/texture/
image.rs

1//! Items related to the inter-operation of the `image` crate (images on disk and in RAM) and
2//! textures from the wgpu crate (images in GPU memory).
3//!
4//! This module can be enabled via the `image` feature.
5
6use crate as wgpu;
7use std::ops::Deref;
8use std::path::Path;
9
10/// The set of pixel types from the image crate that can be loaded directly into a texture.
11///
12/// The `Rgba8` and `Bgra8` color types are assumed to be non-linear sRGB.
13///
14/// Note that wgpu only supports texture formats whose size are a power of 2. If you notice a
15/// `image::Pixel` type that does not implement `Pixel`, this is likely why.
16pub trait Pixel: image::Pixel {
17    /// The wgpu texture format of the pixel type.
18    const TEXTURE_FORMAT: wgpu::TextureFormat;
19}
20
21/// A wrapper around a slice of bytes representing an image.
22///
23/// An `ImageReadMapping` may only be created via `RowPaddedBuffer::read()`.
24pub struct ImageReadMapping<'buffer> {
25    buffer: &'buffer wgpu::RowPaddedBuffer,
26    view: wgpu::BufferView<'buffer>,
27}
28
29/// Workaround for the fact that `image::SubImage` requires a `Deref` impl on the wrapped image.
30pub struct ImageHolder<'b, P: Pixel>(image::ImageBuffer<P, &'b [P::Subpixel]>);
31impl<'b, P: Pixel> Deref for ImageHolder<'b, P> {
32    type Target = image::ImageBuffer<P, &'b [P::Subpixel]>;
33    fn deref(&self) -> &Self::Target {
34        &self.0
35    }
36}
37
38impl wgpu::TextureBuilder {
39    /// The minimum required texture usage when loading from an image.
40    pub const REQUIRED_IMAGE_TEXTURE_USAGE: wgpu::TextureUsages = wgpu::TextureUsages::COPY_DST;
41
42    /// Produce a texture descriptor from an image.
43    ///
44    /// Specifically, this supports any image type implementing `image::GenericImageView` whose
45    /// `Pixel` type implements `Pixel`.
46    ///
47    /// By default, the produced builder will have the `wgpu::TextureUsages` returned by
48    /// `wgpu::TextureBuilder::default_image_texture_usage()`. This is a general-purpose usage that
49    /// should allow for copying to and from the texture, sampling the texture and rendering to the
50    /// texture. Specifying only the texture usage required may result in better performance. It
51    /// may be necessary to manually specify the the usage if `STORAGE` is required.
52    pub fn from_image_view<T>(image_view: &T) -> Self
53    where
54        T: image::GenericImageView,
55        T::Pixel: Pixel,
56    {
57        builder_from_image_view(image_view)
58    }
59
60    /// The default texture usage for the case where a user has loaded a texture from an image.
61    pub fn default_image_texture_usage() -> wgpu::TextureUsages {
62        wgpu::TextureUsages::COPY_SRC
63            | wgpu::TextureUsages::COPY_DST
64            | wgpu::TextureUsages::TEXTURE_BINDING
65            | wgpu::TextureUsages::RENDER_ATTACHMENT
66    }
67}
68
69/// Types that may provide access to a `wgpu::Device` and an associated `wgpu::Queue` for loading
70/// a texture from an image.
71///
72/// Notably, implementations exist for `&App`, `&Window`, `&wgpu::DeviceQueuePair` and `(&Device,
73/// &Queue)`.
74pub trait WithDeviceQueuePair {
75    fn with_device_queue_pair<F, O>(self, f: F) -> O
76    where
77        F: FnOnce(&wgpu::Device, &wgpu::Queue) -> O;
78}
79
80impl wgpu::Texture {
81    /// Load an image from the given path and upload it as a texture.
82    ///
83    /// The device and queue `src` can be either the `App`, a `Window`, a `wgpu::DeviceQueuePair`
84    /// or a tuple `(&wgpu::Device, &mut wgpu::Queue)`. Access to a `Device` is necessary in order
85    /// to create the texture and buffer GPU resources, and access to a `Queue` is necessary for
86    /// submitting the commands responsible for copying the buffer contents to the texture. Note
87    /// that a texture may only be used with the device with which it was created. This is worth
88    /// keeping in mind if you have more than one window and they do not share the same device.
89    ///
90    /// By default, the texture will have the `COPY_SRC`, `COPY_DST`, `SAMPLED` and
91    /// `RENDER_ATTACHMENT` usages enabled. If you wish to specify the usage yourself, see the
92    /// `load_from_path` constructor.
93    ///
94    /// If the `&App` is passed as the `src`, the window returned via `app.main_window()` will be
95    /// used as the source of the device and queue.
96    pub fn from_path<T, P>(src: T, path: P) -> image::ImageResult<Self>
97    where
98        T: WithDeviceQueuePair,
99        P: AsRef<Path>,
100    {
101        let path = path.as_ref();
102        let usage = wgpu::TextureBuilder::default_image_texture_usage();
103        src.with_device_queue_pair(|device, queue| {
104            wgpu::Texture::load_from_path(device, queue, usage, path)
105        })
106    }
107
108    /// Load a texture from the given image.
109    ///
110    /// The device and queue `src` can be either the `App`, a `Window`, a `wgpu::DeviceQueuePair`
111    /// or a tuple `(&wgpu::Device, &mut wgpu::Queue)`. Access to a `Device` is necessary in order
112    /// to create the texture and buffer GPU resources, and access to a `Queue` is necessary for
113    /// submitting the commands responsible for copying the buffer contents to the texture. Note
114    /// that a texture may only be used with the device with which it was created. This is worth
115    /// keeping in mind if you have more than one window and they do not share the same device.
116    ///
117    /// By default, the texture will have the `COPY_SRC`, `COPY_DST`, `SAMPLED` and
118    /// `RENDER_ATTACHMENT` usages enabled. If you wish to specify the usage yourself, see the
119    /// `load_from_path` constructor.
120    ///
121    /// If the `&App` is passed as the `src`, the window returned via `app.main_window()` will be
122    /// used as the source of the device and queue.
123    ///
124    /// The `DeviceQueuePairSource` can be either the `App`, a `Window`, a `DeviceQueuePair` or a
125    /// tuple `(&Device, &Queue)`.
126    pub fn from_image<T>(src: T, image: &image::DynamicImage) -> Self
127    where
128        T: WithDeviceQueuePair,
129    {
130        let usage = wgpu::TextureBuilder::default_image_texture_usage();
131        src.with_device_queue_pair(|device, queue| {
132            wgpu::Texture::load_from_image(device, queue, usage, image)
133        })
134    }
135
136    /// Read an image file from the given path and load it directly into a texture.
137    ///
138    /// This is short-hand for calling `image::open` and then `Texture::load_from_image`.
139    pub fn load_from_path<P>(
140        device: &wgpu::Device,
141        queue: &wgpu::Queue,
142        usage: wgpu::TextureUsages,
143        path: P,
144    ) -> image::ImageResult<Self>
145    where
146        P: AsRef<Path>,
147    {
148        let path = path.as_ref();
149        let image = image::open(path)?;
150        Ok(Self::load_from_image(device, queue, usage, &image))
151    }
152
153    /// Load a texture directly from a dynamic image.
154    ///
155    /// If the image is already in a format supported by wgpu, no conversions are performed and the
156    /// image is loaded directly as-is with a texture format that matches the original image color
157    /// type.
158    ///
159    /// If the image is of an unsupported format, it will be converted to the closest supported format
160    /// before being uploaded.
161    pub fn load_from_image(
162        device: &wgpu::Device,
163        queue: &wgpu::Queue,
164        usage: wgpu::TextureUsages,
165        image: &image::DynamicImage,
166    ) -> Self {
167        load_texture_from_image(device, queue, usage, image)
168    }
169
170    /// Load a texture directly from an image buffer using the given device queue.
171    ///
172    /// No format or size conversions are performed - the given buffer is loaded directly into GPU
173    /// memory.
174    ///
175    /// Pixel type compatibility is ensured via the `Pixel` trait.
176    pub fn load_from_image_buffer<P, Container>(
177        device: &wgpu::Device,
178        queue: &wgpu::Queue,
179        usage: wgpu::TextureUsages,
180        buffer: &image::ImageBuffer<P, Container>,
181    ) -> Self
182    where
183        P: 'static + Pixel,
184        Container: std::ops::Deref<Target = [P::Subpixel]>,
185    {
186        load_texture_from_image_buffer(device, queue, usage, buffer)
187    }
188
189    /// Load a texture array directly from a sequence of image buffers.
190    ///
191    /// No format or size conversions are performed - the given buffer is loaded directly into GPU
192    /// memory.
193    ///
194    /// Pixel type compatibility is ensured via the `Pixel` trait.
195    ///
196    /// Returns `None` if there are no images in the given sequence.
197    pub fn load_array_from_image_buffers<'a, I, P, Container>(
198        device: &wgpu::Device,
199        queue: &wgpu::Queue,
200        usage: wgpu::TextureUsages,
201        buffers: I,
202    ) -> Option<Self>
203    where
204        I: IntoIterator<Item = &'a image::ImageBuffer<P, Container>>,
205        I::IntoIter: ExactSizeIterator,
206        P: 'static + Pixel,
207        Container: 'a + std::ops::Deref<Target = [P::Subpixel]>,
208    {
209        load_texture_array_from_image_buffers(device, queue, usage, buffers)
210    }
211
212    /// Encode the necessary commands to load a texture directly from a dynamic image.
213    ///
214    /// If the image is already in a format supported by wgpu, no conversions are performed and the
215    /// image is loaded directly as-is with a texture format that matches the original image color
216    /// type.
217    ///
218    /// If the image is of an unsupported format, it will be converted to the closest supported format
219    /// before being uploaded.
220    ///
221    /// NOTE: The returned texture will remain empty until the given `encoder` has its command buffer
222    /// submitted to the given `device`'s queue.
223    pub fn encode_load_from_image(
224        device: &wgpu::Device,
225        encoder: &mut wgpu::CommandEncoder,
226        usage: wgpu::TextureUsages,
227        image: &image::DynamicImage,
228    ) -> Self {
229        encode_load_texture_from_image(device, encoder, usage, image)
230    }
231
232    /// Encode the necessary commands to load a texture from the given image buffer.
233    ///
234    /// NOTE: The returned texture will remain empty until the given `encoder` has its command
235    /// buffer submitted to the given `device`'s queue.
236    ///
237    /// No format or size conversions are performed - the given buffer is loaded directly into GPU
238    /// memory.
239    ///
240    /// Pixel type compatibility is ensured via the `Pixel` trait.
241    pub fn encode_load_from_image_buffer<P, Container>(
242        device: &wgpu::Device,
243        encoder: &mut wgpu::CommandEncoder,
244        usage: wgpu::TextureUsages,
245        buffer: &image::ImageBuffer<P, Container>,
246    ) -> Self
247    where
248        P: 'static + Pixel,
249        Container: std::ops::Deref<Target = [P::Subpixel]>,
250    {
251        encode_load_texture_from_image_buffer(device, encoder, usage, buffer)
252    }
253
254    /// Encode the necessary commands to load a 3d texture directly from a sequence of image
255    /// buffers.
256    ///
257    /// NOTE: The returned texture will remain empty until the given `encoder` has its command buffer
258    /// submitted to the given `device`'s queue.
259    ///
260    /// NOTE: The returned texture will be 3d; you must create
261    ///
262    /// No format or size conversions are performed - the given buffer is loaded directly into GPU
263    /// memory.
264    ///
265    /// Pixel type compatibility is ensured via the `Pixel` trait.
266    ///
267    /// Returns `None` if there are no images in the given sequence.
268    pub fn encode_load_3d_from_image_buffers<'a, I, P, Container>(
269        device: &wgpu::Device,
270        encoder: &mut wgpu::CommandEncoder,
271        usage: wgpu::TextureUsages,
272        buffers: I,
273    ) -> Option<Self>
274    where
275        I: IntoIterator<Item = &'a image::ImageBuffer<P, Container>>,
276        I::IntoIter: ExactSizeIterator,
277        P: 'static + Pixel,
278        Container: 'a + std::ops::Deref<Target = [P::Subpixel]>,
279    {
280        encode_load_texture_array_from_image_buffers(device, encoder, usage, buffers)
281    }
282}
283
284impl wgpu::RowPaddedBuffer {
285    /// Initialize from an image buffer (i.e. an image on CPU).
286    pub fn from_image_buffer<P, Container>(
287        device: &wgpu::Device,
288        image_buffer: &image::ImageBuffer<P, Container>,
289    ) -> Self
290    where
291        P: 'static + Pixel,
292        Container: std::ops::Deref<Target = [P::Subpixel]>,
293    {
294        let result = Self::new(
295            device,
296            image_buffer.width() * P::COLOR_TYPE.bytes_per_pixel() as u32,
297            image_buffer.height(),
298            wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC,
299        );
300        // TODO:
301        // This can theoretically be exploited by implementing `image::Primitive` for some type
302        // that has padding. Instead, should make some `Subpixel` trait that we can control and is
303        // only guaranteed to be implemented for safe types.
304        result.write(unsafe { wgpu::bytes::from_slice(&*image_buffer) });
305        result
306    }
307
308    /// Asynchronously maps the buffer of bytes from GPU to host memory.
309    ///
310    /// Note: The returned future will not be ready until the memory is mapped and the device is
311    /// polled. You should *not* rely on the being ready immediately.
312    pub async fn read<'b>(&'b self) -> Result<ImageReadMapping<'b>, wgpu::BufferAsyncError> {
313        let slice = self.buffer.slice(..);
314        let (tx, rx) = futures::channel::oneshot::channel();
315
316        slice.map_async(wgpu::MapMode::Read, |res| {
317            tx.send(res).expect("Failed to send map_async result");
318        });
319
320        rx.await.expect("Failed to receive map_async result")?;
321
322        Ok(wgpu::ImageReadMapping {
323            buffer: self,
324            // fun exercise:
325            // read the signature of wgpu::BufferSlice::get_mapped_range()
326            // and try to figure out why we don't need another lifetime in ImageReadMapping :)
327            view: slice.get_mapped_range(),
328        })
329    }
330}
331
332impl<'buffer> ImageReadMapping<'buffer> {
333    /// View as an image::SubImage.
334    ///
335    /// Unsafe: `P::TEXTURE_FORMAT` MUST match the texture format / image type used to create the
336    /// wrapped RowPaddedBuffer! If this is not the case, may result in undefined behavior!
337    pub unsafe fn as_image<P>(&self) -> image::SubImage<ImageHolder<P>>
338    where
339        P: Pixel + 'static,
340    {
341        let subpixel_size = std::mem::size_of::<P::Subpixel>() as u32;
342        let pixel_size = subpixel_size * P::CHANNEL_COUNT as u32;
343        assert_eq!(pixel_size, P::COLOR_TYPE.bytes_per_pixel() as u32);
344
345        assert_eq!(
346            self.buffer.padded_width() % pixel_size,
347            0,
348            "buffer padded width not an even multiple of primitive size"
349        );
350        assert_eq!(
351            self.buffer.width() % pixel_size,
352            0,
353            "buffer row width not an even multiple of primitive size"
354        );
355
356        let width_pixels = self.buffer.width() / pixel_size;
357        let padded_width_pixels = self.buffer.padded_width() / pixel_size;
358
359        // ways this cast could go wrong:
360        // - buffer is the wrong size: checked in to_slice, panics
361        // - buffer is the wrong alignment: checked in to_slice, panics
362        // - buffer rows are the wrong size: checked above, panics
363        // - buffer has not been initialized / has invalid data for primitive type:
364        //   very possible. That's why this function is `unsafe`.
365        let container = wgpu::bytes::to_slice::<P::Subpixel>(&self.view[..]);
366
367        let full_image =
368            image::ImageBuffer::from_raw(padded_width_pixels, self.buffer.height(), container)
369                .expect("nannou internal error: incorrect buffer size");
370        image::SubImage::new(
371            ImageHolder(full_image),
372            0,
373            0,
374            width_pixels,
375            self.buffer.height(),
376        )
377    }
378}
379
380impl Pixel for image::Bgra<u8> {
381    const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb;
382}
383
384impl Pixel for image::Luma<u8> {
385    const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R8Unorm;
386}
387
388impl Pixel for image::Luma<i8> {
389    const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R8Snorm;
390}
391
392impl Pixel for image::Luma<u16> {
393    const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R16Uint;
394}
395
396impl Pixel for image::Luma<i16> {
397    const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::R16Sint;
398}
399
400impl Pixel for image::LumaA<u8> {
401    const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rg8Unorm;
402}
403
404impl Pixel for image::LumaA<i8> {
405    const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rg8Snorm;
406}
407
408impl Pixel for image::LumaA<u16> {
409    const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rg16Uint;
410}
411
412impl Pixel for image::LumaA<i16> {
413    const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rg16Sint;
414}
415
416impl Pixel for image::Rgba<u8> {
417    const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
418}
419
420impl Pixel for image::Rgba<i8> {
421    const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Snorm;
422}
423
424impl Pixel for image::Rgba<u16> {
425    const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Uint;
426}
427
428impl Pixel for image::Rgba<i16> {
429    const TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba16Sint;
430}
431
432impl<'a> WithDeviceQueuePair for (&'a wgpu::Device, &'a wgpu::Queue) {
433    fn with_device_queue_pair<F, O>(self, f: F) -> O
434    where
435        F: FnOnce(&wgpu::Device, &wgpu::Queue) -> O,
436    {
437        let (device, queue) = self;
438        f(device, queue)
439    }
440}
441
442impl<'a> WithDeviceQueuePair for &'a wgpu::DeviceQueuePair {
443    fn with_device_queue_pair<F, O>(self, f: F) -> O
444    where
445        F: FnOnce(&wgpu::Device, &wgpu::Queue) -> O,
446    {
447        let device = self.device();
448        let queue = self.queue();
449        f(&*device, &*queue)
450    }
451}
452
453impl<'a, 'b, T> wgpu::WithDeviceQueuePair for &'a std::cell::Ref<'b, T>
454where
455    &'a T: wgpu::WithDeviceQueuePair,
456{
457    fn with_device_queue_pair<F, O>(self, f: F) -> O
458    where
459        F: FnOnce(&wgpu::Device, &wgpu::Queue) -> O,
460    {
461        (**self).with_device_queue_pair(f)
462    }
463}
464
465/// Convert the given color type from the `image` crate to the corresponding wgpu texture format.
466///
467/// Returns `None` if there is no directly compatible texture format - this is normally the case if
468/// the `ColorType` would have a bits_per_pixel that is not equal to a power of 2.
469///
470/// The `Rgba8` and `Bgra8` color types are assumed to be non-linear sRGB.
471pub fn format_from_image_color_type(color_type: image::ColorType) -> Option<wgpu::TextureFormat> {
472    let format = match color_type {
473        image::ColorType::L8 => wgpu::TextureFormat::R8Unorm,
474        image::ColorType::La8 => wgpu::TextureFormat::Rg8Unorm,
475        image::ColorType::Rgba8 => wgpu::TextureFormat::Rgba8UnormSrgb,
476        image::ColorType::L16 => wgpu::TextureFormat::R16Uint,
477        image::ColorType::La16 => wgpu::TextureFormat::Rg16Uint,
478        image::ColorType::Rgba16 => wgpu::TextureFormat::Rgba16Uint,
479        image::ColorType::Bgra8 => wgpu::TextureFormat::Bgra8UnormSrgb,
480        _ => return None,
481    };
482    Some(format)
483}
484
485/// Produce a texture descriptor from any type implementing `image::GenericImageView` whose `Pixel`
486/// type implements `Pixel`.
487///
488/// By default, the produced builder will have the `wgpu::TextureUsages` returned by
489/// `wgpu::TextureBuilder::default_image_texture_usage()`. This is a general-purpose usage that
490/// should allow for copying to and from the texture, sampling the texture and rendering to the
491/// texture. Specifying only the texture usage required may result in better performance. It
492/// may be necessary to manually specify the the usage if `STORAGE` is required.
493pub fn builder_from_image_view<T>(image: &T) -> wgpu::TextureBuilder
494where
495    T: image::GenericImageView,
496    T::Pixel: Pixel,
497{
498    let (width, height) = image.dimensions();
499    let format = <T::Pixel as Pixel>::TEXTURE_FORMAT;
500    wgpu::TextureBuilder::new()
501        .size([width, height])
502        .format(format)
503        .usage(wgpu::TextureBuilder::default_image_texture_usage())
504}
505
506/// Load a texture directly from a dynamic image.
507///
508/// This uses the `Queue::write_texture` method, meaning that the texture is not immediately
509/// written. Rather, the write is enqueued internally and scheduled to happen at the start of the
510/// next call to `Queue::submit`.
511///
512/// If the image is already in a format supported by wgpu, no conversions are performed and the
513/// image is loaded directly as-is with a texture format that matches the original image color
514/// type.
515///
516/// If the image is of an unsupported format, it will be converted to the closest supported format
517/// before being uploaded.
518pub fn load_texture_from_image(
519    device: &wgpu::Device,
520    queue: &wgpu::Queue,
521    usage: wgpu::TextureUsages,
522    image: &image::DynamicImage,
523) -> wgpu::Texture {
524    use image::DynamicImage::*;
525    match image {
526        ImageLuma8(img) => load_texture_from_image_buffer(device, queue, usage, img),
527        ImageLumaA8(img) => load_texture_from_image_buffer(device, queue, usage, img),
528        ImageRgba8(img) => load_texture_from_image_buffer(device, queue, usage, img),
529        ImageBgra8(img) => load_texture_from_image_buffer(device, queue, usage, img),
530        ImageLuma16(img) => load_texture_from_image_buffer(device, queue, usage, img),
531        ImageLumaA16(img) => load_texture_from_image_buffer(device, queue, usage, img),
532        ImageRgba16(img) => load_texture_from_image_buffer(device, queue, usage, img),
533        ImageRgb8(_img) => {
534            let img = image.to_rgba8();
535            load_texture_from_image_buffer(device, queue, usage, &img)
536        }
537        ImageBgr8(_img) => {
538            let img = image.to_bgra8();
539            load_texture_from_image_buffer(device, queue, usage, &img)
540        }
541        ImageRgb16(_img) => {
542            let img = image.to_rgba16();
543            load_texture_from_image_buffer(device, queue, usage, &img)
544        }
545    }
546}
547
548/// Load a texture directly from an image buffer using the given device queue.
549///
550/// This uses the `Queue::write_texture` method, meaning that the texture is not immediately
551/// written. Rather, the write is enqueued internally and scheduled to happen at the start of the
552/// next call to `Queue::submit`.
553///
554/// No format or size conversions are performed - the given buffer is loaded directly into GPU
555/// memory.
556///
557/// Pixel type compatibility is ensured via the `Pixel` trait.
558pub fn load_texture_from_image_buffer<P, Container>(
559    device: &wgpu::Device,
560    queue: &wgpu::Queue,
561    usage: wgpu::TextureUsages,
562    buffer: &image::ImageBuffer<P, Container>,
563) -> wgpu::Texture
564where
565    P: 'static + Pixel,
566    Container: std::ops::Deref<Target = [P::Subpixel]>,
567{
568    // Create the texture.
569    let texture = wgpu::TextureBuilder::from_image_view(buffer)
570        .usage(wgpu::TextureBuilder::REQUIRED_IMAGE_TEXTURE_USAGE | usage)
571        .build(device);
572
573    // Describe the layout of the data.
574    let extent = texture.extent();
575    let format = texture.format();
576    let block_size = format
577        .block_copy_size(None)
578        .expect("Expected the format to have a block size");
579    let bytes_per_row = extent.width * block_size as u32;
580    let image_data_layout = wgpu::ImageDataLayout {
581        offset: 0,
582        bytes_per_row: Some(bytes_per_row),
583        rows_per_image: None,
584    };
585
586    // Copy into the entire texture.
587    let image_copy_texture = texture.as_image_copy();
588
589    // TODO:
590    // This can theoretically be exploited by implementing our `image::Pixel` trait for some type
591    // that has padding. Perhaps it should be an unsafe trait? Should investigate how to achieve
592    // this in a safer manner.
593    let data = unsafe { wgpu::bytes::from_slice(&*buffer) };
594
595    queue.write_texture(image_copy_texture, data, image_data_layout, extent);
596    texture
597}
598
599/// Load a 3d texture directly from a sequence of image buffers.
600///
601/// No format or size conversions are performed - the given buffer is loaded directly into GPU
602/// memory.
603///
604/// Pixel type compatibility is ensured via the `Pixel` trait.
605///
606/// Returns `None` if there are no images in the given sequence.
607pub fn load_texture_array_from_image_buffers<'a, I, P, Container>(
608    device: &wgpu::Device,
609    queue: &wgpu::Queue,
610    usage: wgpu::TextureUsages,
611    buffers: I,
612) -> Option<wgpu::Texture>
613where
614    I: IntoIterator<Item = &'a image::ImageBuffer<P, Container>>,
615    I::IntoIter: ExactSizeIterator,
616    P: 'static + Pixel,
617    Container: 'a + std::ops::Deref<Target = [P::Subpixel]>,
618{
619    let mut buffers = buffers.into_iter();
620    let array_layers = buffers.len() as u32;
621    let first_buffer = buffers.next()?;
622
623    // Build the texture ready to receive the data.
624    let (width, height) = first_buffer.dimensions();
625    let extent = wgpu::Extent3d {
626        width,
627        height,
628        depth_or_array_layers: array_layers,
629    };
630    let texture = wgpu::TextureBuilder::from_image_view(first_buffer)
631        .extent(extent)
632        .dimension(wgpu::TextureDimension::D2) // force an array
633        .usage(wgpu::TextureBuilder::REQUIRED_IMAGE_TEXTURE_USAGE | usage)
634        .build(device);
635
636    // Describe the layout of the data.
637    let format = texture.format();
638    let block_size = format
639        .block_copy_size(None)
640        .expect("Expected the format to have a block size");
641    let bytes_per_row = extent.width * block_size as u32;
642    let image_data_layout = wgpu::ImageDataLayout {
643        offset: 0,
644        bytes_per_row: Some(bytes_per_row),
645        rows_per_image: Some(height),
646    };
647
648    // Collect the data into a single slice.
649    //
650    // NOTE: Previously we used `encode_load_texture_array_from_image_buffers` which avoids
651    // collecting the image data into a single slice. However, the `wgpu::Texture::from_*`
652    // constructors have been changed to avoid submitting an extra command buffer in favour
653    // of using `Queue::write_texture` which schedules the write for the next call to
654    // `Queue::submit`. This is to avoid an Intel driver bug where submitting more than one command
655    // buffer per frame appears to be causing issues:
656    // https://github.com/gfx-rs/wgpu/issues/1672#issuecomment-917510810
657    //
658    // While this likely means consuming more RAM, it also likely results in slightly better
659    // performance due to reducing the number of command buffers submitted.
660    //
661    // Users can still use `encode_load_texture_array_from_image_buffers` directly if they wish.
662    let capacity = bytes_per_row as usize * height as usize * array_layers as usize;
663    let mut data: Vec<u8> = Vec::with_capacity(capacity);
664    for buffer in Some(first_buffer).into_iter().chain(buffers) {
665        let layer_data = unsafe { wgpu::bytes::from_slice(&*buffer) };
666        data.extend_from_slice(layer_data);
667    }
668
669    // Copy into the entire texture.
670    let image_copy_texture = texture.as_image_copy();
671
672    queue.write_texture(image_copy_texture, &data, image_data_layout, extent);
673
674    Some(texture)
675}
676
677/// Encode the necessary commands to load a texture directly from a dynamic image.
678///
679/// If the image is already in a format supported by wgpu, no conversions are performed and the
680/// image is loaded directly as-is with a texture format that matches the original image color
681/// type.
682///
683/// If the image is of an unsupported format, it will be converted to the closest supported format
684/// before being uploaded.
685///
686/// NOTE: The returned texture will remain empty until the given `encoder` has its command buffer
687/// submitted to the given `device`'s queue.
688pub fn encode_load_texture_from_image(
689    device: &wgpu::Device,
690    encoder: &mut wgpu::CommandEncoder,
691    usage: wgpu::TextureUsages,
692    image: &image::DynamicImage,
693) -> wgpu::Texture {
694    use image::DynamicImage::*;
695    match image {
696        ImageLuma8(img) => encode_load_texture_from_image_buffer(device, encoder, usage, img),
697        ImageLumaA8(img) => encode_load_texture_from_image_buffer(device, encoder, usage, img),
698        ImageRgba8(img) => encode_load_texture_from_image_buffer(device, encoder, usage, img),
699        ImageBgra8(img) => encode_load_texture_from_image_buffer(device, encoder, usage, img),
700        ImageLuma16(img) => encode_load_texture_from_image_buffer(device, encoder, usage, img),
701        ImageLumaA16(img) => encode_load_texture_from_image_buffer(device, encoder, usage, img),
702        ImageRgba16(img) => encode_load_texture_from_image_buffer(device, encoder, usage, img),
703        ImageRgb8(_img) => {
704            let img = image.to_rgba8();
705            encode_load_texture_from_image_buffer(device, encoder, usage, &img)
706        }
707        ImageBgr8(_img) => {
708            let img = image.to_bgra8();
709            encode_load_texture_from_image_buffer(device, encoder, usage, &img)
710        }
711        ImageRgb16(_img) => {
712            let img = image.to_rgba16();
713            encode_load_texture_from_image_buffer(device, encoder, usage, &img)
714        }
715    }
716}
717
718/// Encode the necessary commands to load a texture directly from an image buffer.
719///
720/// NOTE: The returned texture will remain empty until the given `encoder` has its command buffer
721/// submitted to the given `device`'s queue.
722///
723/// No format or size conversions are performed - the given buffer is loaded directly into GPU
724/// memory.
725///
726/// Pixel type compatibility is ensured via the `Pixel` trait.
727pub fn encode_load_texture_from_image_buffer<P, Container>(
728    device: &wgpu::Device,
729    encoder: &mut wgpu::CommandEncoder,
730    usage: wgpu::TextureUsages,
731    buffer: &image::ImageBuffer<P, Container>,
732) -> wgpu::Texture
733where
734    P: 'static + Pixel,
735    Container: std::ops::Deref<Target = [P::Subpixel]>,
736{
737    // Create the texture.
738    let texture = wgpu::TextureBuilder::from_image_view(buffer)
739        .usage(wgpu::TextureBuilder::REQUIRED_IMAGE_TEXTURE_USAGE | usage)
740        .build(device);
741
742    let buffer_image = wgpu::RowPaddedBuffer::from_image_buffer(device, buffer);
743    buffer_image.encode_copy_into(encoder, &texture);
744
745    texture
746}
747
748/// Encode the necessary commands to load a texture array directly from a sequence of image
749/// buffers.
750///
751/// NOTE: The returned texture will remain empty until the given `encoder` has its command buffer
752/// submitted to the given `device`'s queue.
753///
754/// No format or size conversions are performed - the given buffer is loaded directly into GPU
755/// memory.
756///
757/// Pixel type compatibility is ensured via the `Pixel` trait.
758///
759/// Returns `None` if there are no images in the given sequence.
760pub fn encode_load_texture_array_from_image_buffers<'a, I, P, Container>(
761    device: &wgpu::Device,
762    encoder: &mut wgpu::CommandEncoder,
763    usage: wgpu::TextureUsages,
764    buffers: I,
765) -> Option<wgpu::Texture>
766where
767    I: IntoIterator<Item = &'a image::ImageBuffer<P, Container>>,
768    I::IntoIter: ExactSizeIterator,
769    P: 'static + Pixel,
770    Container: 'a + std::ops::Deref<Target = [P::Subpixel]>,
771{
772    let mut buffers = buffers.into_iter();
773    let array_layers = buffers.len() as u32;
774    let first_buffer = buffers.next()?;
775
776    let (width, height) = first_buffer.dimensions();
777
778    // Build the texture ready to receive the data.
779    let texture = wgpu::TextureBuilder::from_image_view(first_buffer)
780        .extent(wgpu::Extent3d {
781            width,
782            height,
783            depth_or_array_layers: array_layers,
784        })
785        .dimension(wgpu::TextureDimension::D2) // force an array
786        .usage(wgpu::TextureBuilder::REQUIRED_IMAGE_TEXTURE_USAGE | usage)
787        .build(device);
788
789    // Copy each buffer to the texture, one layer at a time.
790    for (layer, buffer) in Some(first_buffer).into_iter().chain(buffers).enumerate() {
791        // Upload the pixel data.
792        let buffer = wgpu::RowPaddedBuffer::from_image_buffer(device, &buffer);
793        buffer.encode_copy_into_at(encoder, &texture, layer as u32);
794    }
795
796    Some(texture)
797}