render_to_texture/
render.rs

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        // TODO: always 1 is not sufficient
55        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, // TODO
65            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    // TODO: populate this!
111    supported_compressed_formats: CompressedImageFormats,
112}
113
114#[derive(Default, Component, Clone, Reflect)]
115pub struct TaskResource(pub String);
116
117impl RenderToTextureTasks {
118    /// should_compress: whether to use universal basis compression. This will also generate mipmaps.
119    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        // TODO: Delete the image when not in use anymore
166
167        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(), // TODO: mipmap trilinear?
183                        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    // mut settings: Query<&mut ImageExportSettings>,
219) {
220    // remove finished tasks
221    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(); // TODO: avoid cloning
229
230                //println!("Image data received");
231
232                extractable_images.raw.clear();
233                task.stage = RenderToTextureTaskStage::RenderedResultCopiedBack;
234            }
235        }
236
237        // TODO: For some reason, the renderer always sends the image data twice... so we have to clear it here
238        // clear anyway
239        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                // commands.remove(task.target);
248                if task.should_compress {
249                    // TODO: do this in a separate thread / TaskPool
250                    let _prev_len = task.data.len();
251                    task.data = compress_to_basis_raw(&task.data, task.size(), task.is_srgb);
252                    // println!("{} -> {} Kb", _prev_len / 1024, task.data.len() / 1024);
253                    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            //settings.get_mut(task.bundle.unwrap()).unwrap().remaining = 1;
292            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    // TODO: Delete the image when not in use anymore
317
318    // This is the texture that will be rendered to.
319    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    // fill image.data with zeroes
338    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                    // render before the "main pass" camera
348                    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}