bevy_render/texture/
gpu_image.rs

1use crate::{
2    render_asset::{AssetExtractionError, 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 texture_view_format: Option<TextureFormat>,
21    pub sampler: Sampler,
22    pub size: Extent3d,
23    pub mip_level_count: u32,
24    pub had_data: bool,
25}
26
27impl RenderAsset for GpuImage {
28    type SourceAsset = Image;
29    type Param = (
30        SRes<RenderDevice>,
31        SRes<RenderQueue>,
32        SRes<DefaultImageSampler>,
33    );
34
35    #[inline]
36    fn asset_usage(image: &Self::SourceAsset) -> RenderAssetUsages {
37        image.asset_usage
38    }
39
40    fn take_gpu_data(
41        source: &mut Self::SourceAsset,
42        previous_gpu_asset: Option<&Self>,
43    ) -> Result<Self::SourceAsset, AssetExtractionError> {
44        let data = source.data.take();
45
46        // check if this image originally had data and no longer does, that implies it
47        // has already been extracted
48        let valid_upload = data.is_some() || previous_gpu_asset.is_none_or(|prev| !prev.had_data);
49
50        valid_upload
51            .then(|| Self::SourceAsset {
52                data,
53                ..source.clone()
54            })
55            .ok_or(AssetExtractionError::AlreadyExtracted)
56    }
57
58    #[inline]
59    fn byte_len(image: &Self::SourceAsset) -> Option<usize> {
60        image.data.as_ref().map(Vec::len)
61    }
62
63    /// Converts the extracted image into a [`GpuImage`].
64    fn prepare_asset(
65        image: Self::SourceAsset,
66        _: AssetId<Self::SourceAsset>,
67        (render_device, render_queue, default_sampler): &mut SystemParamItem<Self::Param>,
68        previous_asset: Option<&Self>,
69    ) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
70        let had_data = image.data.is_some();
71        let texture = if let Some(ref data) = image.data {
72            render_device.create_texture_with_data(
73                render_queue,
74                &image.texture_descriptor,
75                image.data_order,
76                data,
77            )
78        } else {
79            let new_texture = render_device.create_texture(&image.texture_descriptor);
80            if image.copy_on_resize {
81                if let Some(previous) = previous_asset {
82                    let mut command_encoder =
83                        render_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
84                            label: Some("copy_image_on_resize"),
85                        });
86                    let copy_size = Extent3d {
87                        width: image.texture_descriptor.size.width.min(previous.size.width),
88                        height: image
89                            .texture_descriptor
90                            .size
91                            .height
92                            .min(previous.size.height),
93                        depth_or_array_layers: image
94                            .texture_descriptor
95                            .size
96                            .depth_or_array_layers
97                            .min(previous.size.depth_or_array_layers),
98                    };
99
100                    command_encoder.copy_texture_to_texture(
101                        previous.texture.as_image_copy(),
102                        new_texture.as_image_copy(),
103                        copy_size,
104                    );
105                    render_queue.submit([command_encoder.finish()]);
106                } else {
107                    warn!("No previous asset to copy from for image: {:?}", image);
108                }
109            }
110            new_texture
111        };
112
113        let texture_view = texture.create_view(
114            image
115                .texture_view_descriptor
116                .as_ref()
117                .unwrap_or(&TextureViewDescriptor::default()),
118        );
119        let sampler = match image.sampler {
120            ImageSampler::Default => (***default_sampler).clone(),
121            ImageSampler::Descriptor(descriptor) => {
122                render_device.create_sampler(&descriptor.as_wgpu())
123            }
124        };
125
126        Ok(GpuImage {
127            texture,
128            texture_view,
129            texture_format: image.texture_descriptor.format,
130            texture_view_format: image.texture_view_descriptor.and_then(|v| v.format),
131            sampler,
132            size: image.texture_descriptor.size,
133            mip_level_count: image.texture_descriptor.mip_level_count,
134            had_data,
135        })
136    }
137}
138
139impl GpuImage {
140    /// Returns the aspect ratio (width / height) of a 2D image.
141    #[inline]
142    pub fn aspect_ratio(&self) -> AspectRatio {
143        AspectRatio::try_from_pixels(self.size.width, self.size.height).expect(
144            "Failed to calculate aspect ratio: Image dimensions must be positive, non-zero values",
145        )
146    }
147
148    /// Returns the size of a 2D image.
149    #[inline]
150    pub fn size_2d(&self) -> UVec2 {
151        UVec2::new(self.size.width, self.size.height)
152    }
153}