Introduce SystemLabel's for RenderAssetPlugin, and change Image preparation system to run before others (#3917)

# Objective

Fixes `StandardMaterial` texture update (see sample code below).

Most probably fixes #3674 (did not test)

## Solution

Material updates, such as PBR update, reference the underlying `GpuImage`. Like here: 9a7852db0f/crates/bevy_pbr/src/pbr_material.rs (L177)

However, currently the `GpuImage` update may actually happen *after* the material update fetches the gpu image. Resulting in the material actually not being updated for the correct gpu image.

In this pull req, I introduce new systemlabels for the renderassetplugin. Also assigned the RenderAssetPlugin::<Image> to the `PreAssetExtract` stage, so that it is executed before any material updates.

Code to test.

Expected behavior:
* should update to red texture

Unexpected behavior (before this merge):
* texture stays randomly as green one (depending on the execution order of systems)

```rust
use bevy::{
    prelude::*,
    render::render_resource::{Extent3d, TextureDimension, TextureFormat},
};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup)
        .add_system(changes)
        .run();
}

struct Iteration(usize);

#[derive(Component)]
struct MyComponent;

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut images: ResMut<Assets<Image>>,
) {
    commands.spawn_bundle(PointLightBundle {
        point_light: PointLight {
            ..Default::default()
        },
        transform: Transform::from_xyz(4.0, 8.0, 4.0),
        ..Default::default()
    });

    commands.spawn_bundle(PerspectiveCameraBundle {
        transform: Transform::from_xyz(-2.0, 0.0, 5.0)
            .looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
        ..Default::default()
    });

    commands.insert_resource(Iteration(0));

    commands
        .spawn_bundle(PbrBundle {
            mesh: meshes.add(Mesh::from(shape::Quad::new(Vec2::new(3., 2.)))),
            material: materials.add(StandardMaterial {
                base_color_texture: Some(images.add(Image::new(
                    Extent3d {
                        width: 600,
                        height: 400,
                        depth_or_array_layers: 1,
                    },
                    TextureDimension::D2,
                    [0, 255, 0, 128].repeat(600 * 400), // GREEN
                    TextureFormat::Rgba8Unorm,
                ))),
                ..Default::default()
            }),
            ..Default::default()
        })
        .insert(MyComponent);
}

fn changes(
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut images: ResMut<Assets<Image>>,
    mut iteration: ResMut<Iteration>,
    webview_query: Query<&Handle<StandardMaterial>, With<MyComponent>>,
) {
    if iteration.0 == 2 {
        let material = materials.get_mut(webview_query.single()).unwrap();

        let image = images
            .get_mut(material.base_color_texture.as_ref().unwrap())
            .unwrap();

        image
            .data
            .copy_from_slice(&[255, 0, 0, 255].repeat(600 * 400));
    }

    iteration.0 += 1;
}
```

Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
Mika 2022-03-29 19:44:45 +00:00
parent 31bd4ecbbc
commit 6844a6f4fd
2 changed files with 51 additions and 6 deletions

View file

@ -39,28 +39,68 @@ pub trait RenderAsset: Asset {
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>>;
}
#[derive(Clone, Hash, Debug, PartialEq, Eq, SystemLabel)]
pub enum PrepareAssetLabel {
PreAssetPrepare,
AssetPrepare,
PostAssetPrepare,
}
impl Default for PrepareAssetLabel {
fn default() -> Self {
Self::AssetPrepare
}
}
/// This plugin extracts the changed assets from the "app world" into the "render world"
/// and prepares them for the GPU. They can then be accessed from the [`RenderAssets`] resource.
///
/// Therefore it sets up the [`RenderStage::Extract`](crate::RenderStage::Extract) and
/// [`RenderStage::Prepare`](crate::RenderStage::Prepare) steps for the specified [`RenderAsset`].
pub struct RenderAssetPlugin<A: RenderAsset>(PhantomData<fn() -> A>);
pub struct RenderAssetPlugin<A: RenderAsset> {
prepare_asset_label: PrepareAssetLabel,
phantom: PhantomData<fn() -> A>,
}
impl<A: RenderAsset> RenderAssetPlugin<A> {
pub fn with_prepare_asset_label(prepare_asset_label: PrepareAssetLabel) -> Self {
Self {
prepare_asset_label,
phantom: PhantomData,
}
}
}
impl<A: RenderAsset> Default for RenderAssetPlugin<A> {
fn default() -> Self {
Self(PhantomData)
Self {
prepare_asset_label: Default::default(),
phantom: PhantomData,
}
}
}
impl<A: RenderAsset> Plugin for RenderAssetPlugin<A> {
fn build(&self, app: &mut App) {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
let prepare_asset_system = prepare_assets::<A>.label(self.prepare_asset_label.clone());
let prepare_asset_system = match self.prepare_asset_label {
PrepareAssetLabel::PreAssetPrepare => prepare_asset_system,
PrepareAssetLabel::AssetPrepare => {
prepare_asset_system.after(PrepareAssetLabel::PreAssetPrepare)
}
PrepareAssetLabel::PostAssetPrepare => {
prepare_asset_system.after(PrepareAssetLabel::AssetPrepare)
}
};
render_app
.init_resource::<ExtractedAssets<A>>()
.init_resource::<RenderAssets<A>>()
.init_resource::<PrepareNextFrameAssets<A>>()
.add_system_to_stage(RenderStage::Extract, extract_render_asset::<A>)
.add_system_to_stage(RenderStage::Prepare, prepare_assets::<A>);
.add_system_to_stage(RenderStage::Prepare, prepare_asset_system);
}
}
}

View file

@ -24,7 +24,10 @@ pub use hdr_texture_loader::*;
pub use image_texture_loader::*;
pub use texture_cache::*;
use crate::{render_asset::RenderAssetPlugin, RenderApp, RenderStage};
use crate::{
render_asset::{PrepareAssetLabel, RenderAssetPlugin},
RenderApp, RenderStage,
};
use bevy_app::{App, Plugin};
use bevy_asset::{AddAsset, Assets};
@ -52,7 +55,9 @@ impl Plugin for ImagePlugin {
app.init_asset_loader::<HdrTextureLoader>();
}
app.add_plugin(RenderAssetPlugin::<Image>::default())
app.add_plugin(RenderAssetPlugin::<Image>::with_prepare_asset_label(
PrepareAssetLabel::PreAssetPrepare,
))
.add_asset::<Image>();
app.world
.resource_mut::<Assets<Image>>()