1use crate::{
2 compress::compress_to_basis_raw,
3 gpu2cpu::{ExtractableImages, ImageExportBundle, ImageExportSource},
4};
5use bevy::{
6 prelude::*,
7 render::{
8 render_asset::RenderAssetUsages,
9 render_resource::{
10 Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
11 },
12 texture::{CompressedImageFormats, ImageSampler, ImageType},
13 view::RenderLayers,
14 },
15 tasks,
16 utils::HashMap,
17};
18
19#[derive(Default, Reflect, Clone, PartialEq)]
20pub enum RenderToTextureTaskStage {
21 #[default]
22 Initialized,
23 ReadyForRendering,
24 RenderedResultCopiedBack,
25 ReadyForReading,
26 ResultReceived,
27 TaskDone,
28}
29
30#[derive(Default, Reflect, Clone)]
31pub struct RenderToTextureTask {
32 width: u32,
33 height: u32,
34 should_compress: bool,
35 target: Handle<Image>,
36 pub stage: RenderToTextureTaskStage,
37 camera: Option<Entity>,
38 layer: u8,
39 is_srgb: bool,
40 bundle: Option<Entity>,
41 data: Vec<u8>,
42 allow_changes: bool,
43}
44
45impl RenderToTextureTask {
46 pub fn new(
47 width: u32,
48 height: u32,
49 should_compress: bool,
50 commands: &mut Commands,
51 images: &mut ResMut<Assets<Image>>,
52 allow_changes: bool,
53 ) -> Self {
54 let layer = 1;
56 let (target, camera_id) =
57 create_render_texture(width, height, commands, images, layer, false);
58
59 return Self {
60 width,
61 height,
62 layer,
63 target,
64 is_srgb: true, should_compress,
66 allow_changes,
67 camera: Some(camera_id),
68 stage: RenderToTextureTaskStage::Initialized,
69 ..Default::default()
70 };
71 }
72
73 pub fn get_layer(&self) -> RenderLayers {
74 RenderLayers::layer(self.layer)
75 }
76
77 pub fn size(&self) -> UVec2 {
78 UVec2::new(self.width, self.height)
79 }
80
81 pub fn ready(&self) -> bool {
82 self.stage == RenderToTextureTaskStage::ReadyForReading
83 }
84
85 pub fn free(&mut self, commands: &mut Commands) {
86 assert!(
87 self.stage == RenderToTextureTaskStage::TaskDone
88 || self.stage == RenderToTextureTaskStage::ReadyForReading
89 || self.stage == RenderToTextureTaskStage::ResultReceived,
90 "Task not done"
91 );
92 if let Some(c) = self.camera {
93 commands.entity(c).despawn_recursive();
94 self.camera = None;
95 }
96 if let Some(b) = self.bundle {
97 commands.entity(b).despawn_recursive();
98 self.bundle = None;
99 }
100 }
101
102 pub fn rerender(&mut self) {
103 self.stage = RenderToTextureTaskStage::ReadyForRendering;
104 }
105}
106
107#[derive(Default, Resource, Clone)]
108pub struct RenderToTextureTasks {
109 tasks: HashMap<String, RenderToTextureTask>,
110 supported_compressed_formats: CompressedImageFormats,
112}
113
114#[derive(Default, Component, Clone, Reflect)]
115pub struct TaskResource(pub String);
116
117impl RenderToTextureTasks {
118 pub fn add(
120 &mut self,
121 name: String,
122 width: u32,
123 height: u32,
124 should_compress: bool,
125 commands: &mut Commands,
126 images: &mut ResMut<Assets<Image>>,
127 allow_changes: bool,
128 ) {
129 let task = RenderToTextureTask::new(
130 width,
131 height,
132 should_compress,
133 commands,
134 images,
135 allow_changes,
136 );
137 assert!(
138 self.tasks.get(&name).is_none(),
139 "Task with name {} already exists",
140 name
141 );
142 self.tasks.insert(name, task);
143 }
144
145 pub fn get(&self, name: &str) -> Option<&RenderToTextureTask> {
146 self.tasks.get(name)
147 }
148
149 pub fn get_mut(&mut self, name: &str) -> Option<&mut RenderToTextureTask> {
150 self.tasks.get_mut(name)
151 }
152
153 pub fn read(&mut self, name: &str) -> Option<Vec<u8>> {
154 if let Some(task) = self.tasks.get_mut(name) {
155 if task.stage != RenderToTextureTaskStage::ReadyForReading {
156 return None;
157 }
158 task.stage = RenderToTextureTaskStage::TaskDone;
159 return Some(task.data.clone());
160 }
161 return None;
162 }
163
164 pub fn image(&mut self, name: &str, finish: bool) -> Option<Image> {
165 if let Some(task) = self.tasks.get_mut(name) {
168 if task.stage != RenderToTextureTaskStage::ReadyForReading {
169 return None;
170 }
171 task.stage = RenderToTextureTaskStage::ResultReceived;
172 if finish {
173 task.stage = RenderToTextureTaskStage::TaskDone;
174 }
175 if task.should_compress {
176 return Some(
177 Image::from_buffer(
178 &task.data,
179 ImageType::Format(bevy::render::texture::ImageFormat::Basis),
180 self.supported_compressed_formats,
181 true,
182 ImageSampler::linear(), RenderAssetUsages::default(),
184 )
185 .unwrap(),
186 );
187 } else {
188 return Some(Image::new_fill(
189 Extent3d {
190 width: task.width,
191 height: task.height,
192 depth_or_array_layers: 1,
193 },
194 TextureDimension::D2,
195 &task.data,
196 TextureFormat::Rgba8UnormSrgb,
197 RenderAssetUsages::default(),
198 ));
199 }
200 }
201 return None;
202 }
203}
204
205pub fn setup_supported_formats(
206 device: Res<bevy::render::renderer::RenderDevice>,
207 mut tasks: ResMut<RenderToTextureTasks>,
208) {
209 tasks.supported_compressed_formats = CompressedImageFormats::from_features(device.features());
210}
211
212pub fn update_render_to_texture(
213 mut tasks: ResMut<RenderToTextureTasks>,
214 mut cameras: Query<&mut Camera>,
215 mut commands: Commands,
216 mut image_exports: ResMut<Assets<ImageExportSource>>,
217 mut extractable_images: ResMut<ExtractableImages>,
218 ) {
220 tasks
222 .tasks
223 .retain(|_, task| task.stage != RenderToTextureTaskStage::TaskDone);
224
225 if extractable_images.raw.len() > 0 {
226 for (_, task) in tasks.tasks.iter_mut() {
227 if task.stage == RenderToTextureTaskStage::ReadyForRendering {
228 task.data = extractable_images.raw.clone(); extractable_images.raw.clear();
233 task.stage = RenderToTextureTaskStage::RenderedResultCopiedBack;
234 }
235 }
236
237 extractable_images.raw.clear();
240 }
241
242 let mut started_rendering = false;
243 for (_, task) in tasks.tasks.iter_mut() {
244 match task.stage {
245 RenderToTextureTaskStage::ReadyForRendering => {}
246 RenderToTextureTaskStage::RenderedResultCopiedBack => {
247 if task.should_compress {
249 let _prev_len = task.data.len();
251 task.data = compress_to_basis_raw(&task.data, task.size(), task.is_srgb);
252 task.stage = RenderToTextureTaskStage::ReadyForReading;
254 } else {
255 task.stage = RenderToTextureTaskStage::ReadyForReading;
256 }
257
258 if !task.allow_changes {
259 task.free(&mut commands);
260 }
261
262 cameras.get_mut(task.camera.unwrap()).unwrap().is_active = false;
263 }
264 RenderToTextureTaskStage::Initialized => {
265 let mut cam = cameras.get_mut(task.camera.unwrap()).unwrap();
266 assert!(
267 !started_rendering,
268 "Currently only one render to texture at a time is supported"
269 );
270
271 cam.is_active = true;
272 task.stage = RenderToTextureTaskStage::ReadyForRendering;
273
274 task.bundle = Some(
275 commands
276 .spawn(ImageExportBundle {
277 source: image_exports.add(ImageExportSource {
278 image: task.target.clone(),
279 }),
280 settings: crate::gpu2cpu::ImageExportSettings::default(),
281 })
282 .id(),
283 );
284 started_rendering = true;
285 }
286 _ => {}
287 };
288
289 if task.stage == RenderToTextureTaskStage::ReadyForRendering {
290 cameras.get_mut(task.camera.unwrap()).unwrap().is_active = true;
291 extractable_images.refresh = true;
293 }
294 }
295}
296
297pub fn create_render_texture(
298 width: u32,
299 height: u32,
300 commands: &mut Commands,
301 images: &mut ResMut<Assets<Image>>,
302 layer: u8,
303 direct_render: bool,
304) -> (Handle<Image>, Entity) {
305 let size = Extent3d {
306 width,
307 height,
308 ..default()
309 };
310
311 let mut usage = TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC;
312 if direct_render {
313 usage |= TextureUsages::TEXTURE_BINDING;
314 }
315
316 let mut image = Image {
320 texture_descriptor: TextureDescriptor {
321 label: None,
322 size,
323 dimension: TextureDimension::D2,
324 format: if direct_render {
325 TextureFormat::Bgra8UnormSrgb
326 } else {
327 TextureFormat::Rgba8UnormSrgb
328 },
329 mip_level_count: 1,
330 sample_count: 1,
331 usage,
332 view_formats: &[],
333 },
334 ..default()
335 };
336
337 image.resize(size);
339
340 let image_handle = images.add(image);
341
342 let camera_id = commands
343 .spawn((
344 Camera2dBundle {
345 camera_2d: Camera2d { ..default() },
346 camera: Camera {
347 order: -1,
349 clear_color: ClearColorConfig::Custom(Color::rgba(0.0, 0.0, 0.0, 0.0)),
350 target: image_handle.clone().into(),
351 ..default()
352 },
353 ..default()
354 },
355 RenderLayers::layer(layer),
356 ))
357 .id();
358
359 return (image_handle, camera_id);
360}