Changing backing image of a `RenderTarget::Image` causes rendering to that image to stop
- Dominant language
- Rust
- Stars
- 48.2k
- Forks
- 4.8k
- Avg merge
- 3d 16h
- Merged PRs (30d)
- 171
Description
## Bevy version
`f005a96dd4a9aa019fecd5ac874cbd2e962e25f2`
## \[Optional\] Relevant system information
```ignore
2024-10-29T20:08:15.195578Z INFO bevy_diagnostic::system_information_diagnostics_plugin::internal: SystemInfo { os: "Linux 20240728.0.249973 Arch Linux", kernel: "6.11.5-307.bazzite.fc41.x86_64", cpu: "AMD Ryzen 7 7840U w/ Radeon 780M Graphics", core_count: "8", memory: "27.2 GiB" }
2024-10-29T20:08:15.253214Z INFO bevy_render::renderer: AdapterInfo { name: "AMD Radeon 780M (RADV GFX1103_R1)", vendor: 4098, device: 5567, device_type: IntegratedGpu, driver: "radv", driver_info: "Mesa 24.2.5-arch1.1", backend: Vulkan }
```
## What you did
You can set a `Camera`'s `target: RenderTarget` to a `RenderTarget::Image` by giving it a `Handle`. However, if you swap out this backing `Image` on the GPU side (e.g. `image_assets.insert(&existing_handle, new_image`), then the camera which renders into that image will stop rendering properly, and it will effectively "freeze up", just displaying the last content of what was in that image.
Relevant discussion: https://discord.com/channels/691052431525675048/866787577687310356/1300848109861208084
Here's a modified version of the `render_to_texture` example which shows this:
```rs
//! Shows how to render to a texture. Useful for mirrors, UI, or exporting images.
use std::f32::consts::PI;
use bevy::{
prelude::*,
render::{
render_asset::RenderAssetUsages,
render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages},
view::RenderLayers,
},
};
use bevy_render::render_resource::TextureDescriptor;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, (cube_rotator_system, rotator_system))
.add_systems(
First,
|mut imgs: ResMut>, ih: Res, mut foo: Local| {
*foo += 1;
if (*foo + 15) % 60 != 0 {
return;
}
let size = Extent3d {
width: 512,
height: 512,
..default()
};
// This is the texture that will be rendered to.
let mut image = Image {
texture_descriptor: TextureDescriptor {
label: None,
size,
dimension: TextureDimension::D2,
format: TextureFormat::Bgra8UnormSrgb,
mip_level_count: 1,
sample_count: 1,
usage: TextureUsages::TEXTURE_BINDING
| TextureUsages::COPY_DST
| TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
},
..default()
};
// fill image.data with zeroes
image.resize(size);
imgs.insert(&ih.0, image);
},
)
.run();
}
#[derive(Resource)]
struct TodoTesting(Handle);
// Marks the first pass cube (rendered to a texture.)
#[derive(Component)]
struct FirstPassCube;
// Marks the main pass cube, to which the texture is applied.
#[derive(Component)]
struct MainPassCube;
fn setup(
mut commands: Commands,
mut meshes: ResMut>,
mut materials: ResMut>,
mut images: ResMut>,
) {
let size = Extent3d {
width: 512,
height: 512,
..default()
};
// This is the texture that will be rendered to.
let mut image = Image::new_fill(
size,
TextureDimension::D2,
&[0, 0, 0, 0],
TextureFormat::Bgra8UnormSrgb,
RenderAssetUsages::default(),
);
// You need to set these texture usage flags in order to use the image as a render target
image.texture_descriptor.usage =
TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT;
let image_handle = images.add(image);
let cube_handle = meshes.add(Cuboid::new(4.0, 4.0, 4.0));
let cube_material_handle = materials.add(StandardMaterial {
base_color: Color::srgb(0.8, 0.7, 0.6),
reflectance: 0.02,
unlit: false,
..default()
});
commands.insert_resource(TodoTesting(image_handle.clone()));
// This specifies the layer used for the first pass, which will be attached to the first pass camera and cube.
let first_pass_layer = RenderLayers::layer(1);
// The cube that will be rendered to the texture.
commands.spawn((
Mesh3d(cube_handle),
MeshMaterial3d(cube_material_handle),
Transform::from_translation(Vec3::new(0.0, 0.0, 1.0)),
FirstPassCube,
first_pass_layer.clone(),
));
// Light
// NOTE: we add the light to both layers so it affects both the rendered-to-texture cube, and the cube on which we display the texture
// Setting the layer to RenderLayers::layer(0) would cause the main view to be lit, but the rendered-to-texture cube to be unlit.
// Setting the layer to RenderLayers::layer(1) would cause the rendered-to-texture cube to be lit, but the main view to be unlit.
commands.spawn((
PointLight::default(),
Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)),
RenderLayers::layer(0).with(1),
));
commands.spawn((
Camera3d::default(),
Camera {
target: image_handle.clone().into(),
clear_color: Color::WHITE.into(),
..default()
},
Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)).looking_at(Vec3::ZERO, Vec3::Y),
first_pass_layer,
));
let cube_size = 4.0;
let cube_handle = meshes.add(Cuboid::new(cube_size, cube_size, cube_size));
// This material has the texture that has been rendered.
let material_handle = materials.add(StandardMaterial {
base_color_texture: Some(image_handle),
reflectance: 0.02,
unlit: false,
..default()
});
// Main pass cube, with material containing the rendered first pass texture.
commands.spawn((
Mesh3d(cube_handle),
MeshMaterial3d(material_handle),
Transform::from_xyz(0.0, 0.0, 1.5).with_rotation(Quat::from_rotation_x(-PI / 5.0)),
MainPassCube,
));
// The main pass camera.
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 0.0, 15.0).looking_at(Vec3::ZERO, Vec3::Y),
));
}
/// Rotates the inner cube (first pass)
fn rotator_system(time: Res
/// Rotates the outer cube (main pass)
fn cube_rotator_system(time: Res
## What went wrong
It should continue rendering and not freeze.
## Additional information
https://github.com/user-attachments/assets/bcdee1ed-e568-41d2-a96b-e0dd75ae4dd4
Contributor guide
Research direction
Start by running the modified `render_to_texture` example in the issue and reproduce the freeze after replacing the existing `Image` in `Assets`. Trace how `RenderTarget::Image` responds to that asset replacement; done means the camera continues rendering updated content instead of displaying the last frame.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- computer-graphics
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100