Skip to main content

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_log::warn;
10use bevy_math::{AspectRatio, UVec2};
11use wgpu::{Extent3d, TexelCopyBufferLayout, TextureFormat, TextureUsages};
12use wgpu_types::{TextureDescriptor, TextureViewDescriptor};
13
14/// The GPU-representation of an [`Image`].
15/// Consists of the [`Texture`], its [`TextureView`] and the corresponding [`Sampler`], and the texture's size.
16#[derive(Debug, Clone)]
17pub struct GpuImage {
18    pub texture: Texture,
19    pub texture_view: TextureView,
20    pub sampler: Sampler,
21    pub texture_descriptor: TextureDescriptor<Option<&'static str>, &'static [TextureFormat]>,
22    pub texture_view_descriptor: Option<TextureViewDescriptor<Option<&'static str>>>,
23    pub had_data: bool,
24}
25
26impl RenderAsset for GpuImage {
27    type SourceAsset = Image;
28    type Param = (
29        SRes<RenderDevice>,
30        SRes<RenderQueue>,
31        SRes<DefaultImageSampler>,
32    );
33
34    #[inline]
35    fn asset_usage(image: &Self::SourceAsset) -> RenderAssetUsages {
36        image.asset_usage
37    }
38
39    fn take_gpu_data(
40        source: &mut Self::SourceAsset,
41        previous_gpu_asset: Option<&Self>,
42    ) -> Result<Self::SourceAsset, AssetExtractionError> {
43        let data = source.data.take();
44
45        // check if this image originally had data and no longer does, that implies it
46        // has already been extracted
47        let valid_upload = data.is_some() || previous_gpu_asset.is_none_or(|prev| !prev.had_data);
48
49        valid_upload
50            .then(|| Self::SourceAsset {
51                data,
52                ..source.clone()
53            })
54            .ok_or(AssetExtractionError::AlreadyExtracted)
55    }
56
57    #[inline]
58    fn byte_len(image: &Self::SourceAsset) -> Option<usize> {
59        image.data.as_ref().map(Vec::len)
60    }
61
62    /// Converts the extracted image into a [`GpuImage`].
63    fn prepare_asset(
64        image: Self::SourceAsset,
65        _: AssetId<Self::SourceAsset>,
66        (render_device, render_queue, default_sampler): &mut SystemParamItem<Self::Param>,
67        previous_asset: Option<&Self>,
68    ) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
69        let had_data = image.data.is_some();
70        let texture = if let Some(prev) = previous_asset
71            && prev.texture_descriptor == image.texture_descriptor
72            && (!had_data
73                || prev
74                    .texture_descriptor
75                    .usage
76                    .contains(TextureUsages::COPY_DST))
77            && let Some(block_bytes) = image.texture_descriptor.format.block_copy_size(None)
78        {
79            if let Some(ref data) = image.data {
80                let (block_width, block_height) =
81                    image.texture_descriptor.format.block_dimensions();
82
83                // queue copy
84                render_queue.write_texture(
85                    prev.texture.as_image_copy(),
86                    data,
87                    TexelCopyBufferLayout {
88                        offset: 0,
89                        bytes_per_row: Some(image.width() / block_width * block_bytes),
90                        rows_per_image: Some(image.height() / block_height),
91                    },
92                    image.texture_descriptor.size,
93                );
94            }
95
96            if !image.copy_on_resize {
97                // TODO else could clear here? probably not necessary as textures without data are only useful as render
98                // targets and will normally be overwritten immediately anyway
99            }
100
101            // reuse previous texture
102            prev.texture.clone()
103        } else if let Some(ref data) = image.data {
104            render_device.create_texture_with_data(
105                render_queue,
106                &image.texture_descriptor,
107                image.data_order,
108                data,
109            )
110        } else {
111            let new_texture = render_device.create_texture(&image.texture_descriptor);
112            if image.copy_on_resize {
113                if let Some(previous) = previous_asset {
114                    let mut command_encoder =
115                        render_device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
116                            label: Some("copy_image_on_resize"),
117                        });
118                    let copy_size = Extent3d {
119                        width: image
120                            .texture_descriptor
121                            .size
122                            .width
123                            .min(previous.texture_descriptor.size.width),
124                        height: image
125                            .texture_descriptor
126                            .size
127                            .height
128                            .min(previous.texture_descriptor.size.height),
129                        depth_or_array_layers: image
130                            .texture_descriptor
131                            .size
132                            .depth_or_array_layers
133                            .min(previous.texture_descriptor.size.depth_or_array_layers),
134                    };
135
136                    command_encoder.copy_texture_to_texture(
137                        previous.texture.as_image_copy(),
138                        new_texture.as_image_copy(),
139                        copy_size,
140                    );
141                    render_queue.submit([command_encoder.finish()]);
142                } else {
143                    warn!("No previous asset to copy from for image: {:?}", image);
144                }
145            }
146            new_texture
147        };
148
149        let texture_view = if let Some(prev) = previous_asset.as_ref()
150            && prev.texture_descriptor == image.texture_descriptor
151            && prev
152                .texture_descriptor
153                .usage
154                .contains(TextureUsages::COPY_DST)
155            && prev.texture_view_descriptor == image.texture_view_descriptor
156        {
157            prev.texture_view.clone()
158        } else {
159            image
160                .texture_view_descriptor
161                .as_ref()
162                .map(|desc| texture.create_view(desc))
163                .unwrap_or_else(|| texture.create_view(&TextureViewDescriptor::default()))
164        };
165        let sampler = match image.sampler {
166            ImageSampler::Default => (***default_sampler).clone(),
167            ImageSampler::Descriptor(descriptor) => {
168                render_device.create_sampler(&descriptor.as_wgpu())
169            }
170        };
171
172        Ok(GpuImage {
173            texture,
174            texture_view,
175            sampler,
176            texture_descriptor: image.texture_descriptor,
177            texture_view_descriptor: image.texture_view_descriptor,
178            had_data,
179        })
180    }
181}
182
183impl GpuImage {
184    /// Returns the aspect ratio (width / height) of a 2D image.
185    #[inline]
186    pub fn aspect_ratio(&self) -> AspectRatio {
187        AspectRatio::try_from_pixels(
188            self.texture_descriptor.size.width,
189            self.texture_descriptor.size.height,
190        )
191        .expect(
192            "Failed to calculate aspect ratio: Image dimensions must be positive, non-zero values",
193        )
194    }
195
196    /// Returns the size of a 2D image.
197    #[inline]
198    pub fn size_2d(&self) -> UVec2 {
199        UVec2::new(
200            self.texture_descriptor.size.width,
201            self.texture_descriptor.size.height,
202        )
203    }
204
205    /// Gets the view format of this image.
206    /// If the view format is not explicitly provided, falls back to the base image format
207    #[inline]
208    pub fn view_format(&self) -> TextureFormat {
209        self.texture_view_descriptor
210            .as_ref()
211            .and_then(|view_desc| view_desc.format)
212            .unwrap_or(self.texture_descriptor.format)
213    }
214}