bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
35.62k stars 3.52k forks source link

Assets<Image>'s get_mut prevents the texture of a mesh using the Image as a texture to be updated #8767

Open Selene-Amanita opened 1 year ago

Selene-Amanita commented 1 year ago

Bevy version

v0.10.1

What you did

Render a camera to an image, use that image as a texture of a mesh, use Assets<Image>'s get_mut on the handle of that image

What went wrong

The texture stops updating

Minimal code to reproduce

Comment/uncomment lines

use bevy::{
    prelude::*,
    render::{
        render_resource::*,
        camera::RenderTarget
    },
};

#[derive(Component)]
struct MainCamera; // Camera that is used to render to the window, not the image

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup)
        .add_system(rotate_other_camera)
        // comment and uncomment this line
        //.add_system(get_mut_image)
        .run();
}

fn get_mut_image (
    mut images: ResMut<Assets<Image>>,
    camera_query: Query<&Camera, Without<MainCamera>>
) {
    let camera = camera_query.single();
    if let RenderTarget::Image(handle) = camera.target.clone() {
        images.get_mut(&handle.clone()); // This line is the culprit, you can uncomment it too
    }
}

// Turn the texture rendering camera to make the texture update
fn rotate_other_camera(
    time: Res<Time>,
    mut camera_query: Query<&mut Transform, (With<Camera>, Without<MainCamera>)>,
) {
    let mut camera_transform = camera_query.single_mut();
    camera_transform.rotate_around(Vec3::ZERO, Quat::from_axis_angle(Vec3::Y, time.delta_seconds() * 9.))
}

// This is adapted from the Bevy example to render to a texture: https://github.com/bevyengine/bevy/blob/latest/examples/3d/render_to_texture.rs
fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut images: ResMut<Assets<Image>>,
    mut materials: ResMut<Assets<StandardMaterial>>
) {
    commands.insert_resource(AmbientLight {
        color: Color::WHITE,
        brightness: 0.3,
    });

    // Main camera (displayed in window)
    commands.spawn((
        Camera3dBundle {
            transform: Transform::from_xyz(0.0, 0., 20.0).looking_at(Vec3::ZERO, Vec3::Y),
            ..default()
        },
        MainCamera
    ));

    // Second camera (renders to an image, gets rotated, and the image doesn't update if get_mut_image)
    let size = Extent3d {width: 450, height: 450, ..default()};
    let mut camera_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()
    };
    camera_image.resize(size);
    let camera_image = images.add(camera_image);

    commands.spawn(
        Camera3dBundle {
            camera: Camera {
                order: -1,
                target: RenderTarget::Image(camera_image),
                ..default()
            },
            transform: Transform::from_xyz(0., 0., 10.),
            ..default()
        }
    );

    // Object the image is rendered to
    let material_handle = materials.add(StandardMaterial {
        base_color_texture: Some(camera_image.clone()),
        reflectance: 0.02,
        unlit: false,
        ..default()
    });
    commands.spawn((
        PbrBundle {
            mesh: meshes.add(Mesh::from(shape::Cube::new(5.))),
            material: material_handle,
            transform: Transform::from_xyz(0., 6., 0.),
            ..default()
        },
    ));

    // Normal object to have something to look at
    let sphere_mesh = meshes.add(Mesh::from(shape::Cube::new(5.)));
    let debug_material = materials.add(StandardMaterial {
        base_color_texture: Some(images.add(uv_debug_texture())),
        ..default()
    });
    commands.spawn(PbrBundle {
        mesh: sphere_mesh,
        material: debug_material,
        ..default()
    });
}

// This is taken from the 3d_shapes example: https://bevyengine.org/examples/3d/3d-shapes/
pub fn uv_debug_texture() -> Image {
    const TEXTURE_SIZE: usize = 8;

    let mut palette: [u8; 32] = [
        255, 102, 159, 255, 255, 159, 102, 255, 236, 255, 102, 255, 121, 255, 102, 255, 102, 255,
        198, 255, 102, 198, 255, 255, 121, 102, 255, 255, 236, 102, 255, 255,
    ];

    let mut texture_data = [0; TEXTURE_SIZE * TEXTURE_SIZE * 4];
    for y in 0..TEXTURE_SIZE {
        let offset = TEXTURE_SIZE * y * 4;
        texture_data[offset..(offset + TEXTURE_SIZE * 4)].copy_from_slice(&palette);
        palette.rotate_right(4);
    }

    Image::new_fill(
        Extent3d {
            width: TEXTURE_SIZE as u32,
            height: TEXTURE_SIZE as u32,
            depth_or_array_layers: 1,
        },
        TextureDimension::D2,
        &texture_data,
        TextureFormat::Rgba8UnormSrgb,
    )
}

Additional information

Discord threads (potentially) related:

Potential relevant system information

mockersf commented 1 year ago

There are two worlds: main world, and render world.

  1. You create the image in the main world, it's completely black at this point
  2. That image gets extracted from the main world to the render world
  3. In the render world, that image gets overwritten with the camera view
  4. In the render world, that image is rendered on a PbrBundle

What happens when you call Assets<Image>.get_mut() is that the asset is marked as changed, and will be extracted again to the render world. As the actual image was never retrieved from the render world to the main world, this is image is still just black in the main world. Also, the change on the image asset is not propagated to the standard material asset which is the one actually displayed.

What fo you expect to be able to do by calling get_mut on the asset?

Selene-Amanita commented 1 year ago

@mockersf I'm not 100% sure I understood that explaination (by "extracted", do you mean "cloned" and that there are actually two images?) but more details just in case:

Selene-Amanita commented 11 months ago

In case somebody finds this: it is possible to properly update the image by "touching" the material (materials.get_mut(handle)) too: https://discord.com/channels/691052431525675048/1150171576474554468

This is likely waiting on https://github.com/bevyengine/bevy/pull/5080 to be fixed