bevy_render/texture/
gpu_image.rs

1use crate::{
2    render_asset::{PrepareAssetError, RenderAsset},
3    render_resource::{DefaultImageSampler, Sampler, Texture, TextureView},
4    renderer::{RenderDevice, RenderQueue},
5};
6use bevy_asset::{AssetId, RenderAssetUsages};
7use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem};
8use bevy_image::{Image, ImageSampler};
9use bevy_math::{AspectRatio, UVec2};
10use tracing::warn;
11use wgpu::{Extent3d, TextureFormat, TextureViewDescriptor};
12
13/// The GPU-representation of an [`Image`].
14/// Consists of the [`Texture`], its [`TextureView`] and the corresponding [`Sampler`], and the texture's size.
15#[derive(Debug, Clone)]
16pub struct GpuImage {
17    pub texture: Texture,
18    pub texture_view: TextureView,
19    pub texture_format: TextureFormat,
20    pub sampler: Sampler,
21    pub size: Extent3d,
22    pub mip_level_count: u32,
23}
24
25impl RenderAsset for GpuImage {
26    type SourceAsset = Image;
27    type Param = (
28        SRes<RenderDevice>,
29        SRes<RenderQueue>,
30        SRes<DefaultImageSampler>,
31    );
32
33    #[inline]
34    fn asset_usage(image: &Self::SourceAsset) -> RenderAssetUsages {
35        image.asset_usage
36    }
37
38    #[inline]
39    fn byte_len(image: &Self::SourceAsset) -> Option<usize> {
40        image.data.as_ref().map(Vec::len)
41    }
42
43    /// Converts the extracted image into a [`GpuImage`].
44    fn prepare_asset(
45        image: Self::SourceAsset,
46        _: AssetId<Self::SourceAsset>,
47        (render_device, render_queue, default_sampler): &mut SystemParamItem<Self::Param>,
48        previous_asset: Option<&Self>,
49    ) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
50        let texture = if let Some(ref data) = image.data {
51            render_device.create_texture_with_data(
52                render_queue,
53                &image.texture_descriptor,
54                image.data_order,
55                data,
56            )
57        } else {
58            let new_texture = render_device.create_texture(&image.texture_descriptor);
59            if image.copy_on_resize {
60                if let Some(previous) = previous_asset {
61                    let mut command_encoder =
62                        render_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
63                            label: Some("copy_image_on_resize"),
64                        });
65                    let copy_size = Extent3d {
66                        width: image.texture_descriptor.size.width.min(previous.size.width),
67                        height: image
68                            .texture_descriptor
69                            .size
70                            .height
71                            .min(previous.size.height),
72                        depth_or_array_layers: image
73                            .texture_descriptor
74                            .size
75                            .depth_or_array_layers
76                            .min(previous.size.depth_or_array_layers),
77                    };
78
79                    command_encoder.copy_texture_to_texture(
80                        previous.texture.as_image_copy(),
81                        new_texture.as_image_copy(),
82                        copy_size,
83                    );
84                    render_queue.submit([command_encoder.finish()]);
85                } else {
86                    warn!("No previous asset to copy from for image: {:?}", image);
87                }
88            }
89            new_texture
90        };
91
92        let texture_view = texture.create_view(
93            image
94                .texture_view_descriptor
95                .or_else(|| Some(TextureViewDescriptor::default()))
96                .as_ref()
97                .unwrap(),
98        );
99        let sampler = match image.sampler {
100            ImageSampler::Default => (***default_sampler).clone(),
101            ImageSampler::Descriptor(descriptor) => {
102                render_device.create_sampler(&descriptor.as_wgpu())
103            }
104        };
105
106        Ok(GpuImage {
107            texture,
108            texture_view,
109            texture_format: image.texture_descriptor.format,
110            sampler,
111            size: image.texture_descriptor.size,
112            mip_level_count: image.texture_descriptor.mip_level_count,
113        })
114    }
115}
116
117impl GpuImage {
118    /// Returns the aspect ratio (width / height) of a 2D image.
119    #[inline]
120    pub fn aspect_ratio(&self) -> AspectRatio {
121        AspectRatio::try_from_pixels(self.size.width, self.size.height).expect(
122            "Failed to calculate aspect ratio: Image dimensions must be positive, non-zero values",
123        )
124    }
125
126    /// Returns the size of a 2D image.
127    #[inline]
128    pub fn size_2d(&self) -> UVec2 {
129        UVec2::new(self.size.width, self.size.height)
130    }
131}