bevy_render/texture/
gpu_image.rs1use 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#[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 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 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 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 }
100
101 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 #[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 #[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 #[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}