Skip to content

Swapping Materials in a Component Hook panics #20992

@ChristopherBiscardi

Description

@ChristopherBiscardi

Bevy version and features

bevy 0.17.0-rc.1

What you did

Attempting to remove a standardmaterial, then insert a custom material in a component hook.

fn on_add(
    mut world: DeferredWorld,
    HookContext { entity, .. }: HookContext,
) {
    let test_mat =
        world.resource::<MaterialStore>().0.clone();

    world
        .commands()
        .entity(entity)
        .remove::<MeshMaterial3d<StandardMaterial>>()
        .insert(MeshMaterial3d(test_mat));
}
Reproduction program here
use bevy::{
    core_pipeline::prepass::DepthPrepass,
    ecs::{lifecycle::HookContext, world::DeferredWorld},
    prelude::*,
    render::render_resource::AsBindGroup,
    shader::ShaderRef,
};

fn main() {
    App::new()
        .add_plugins((
            DefaultPlugins
                .set(ImagePlugin::default_nearest()),
            MaterialPlugin::<TestMaterial>::default(),
        ))
        .add_systems(Startup, startup)
        .run();
}

#[derive(Resource)]
struct MaterialStore(Handle<TestMaterial>);

fn startup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<TestMaterial>>,
    mut materials_std: ResMut<Assets<StandardMaterial>>,
) {
    commands.insert_resource(MaterialStore(
        materials.add(TestMaterial {}),
    ));

    commands.spawn((
        Camera3d::default(),
        Transform::from_xyz(5., 5., 5.)
            .looking_at(Vec3::ZERO, Vec3::Y),
        DepthPrepass,
    ));
    commands.spawn((
        Mesh3d(meshes.add(Cuboid::default())),
        MeshMaterial3d(
            materials_std.add(StandardMaterial::default()),
        ),
        Transform::from_xyz(0.0, 0.5, 0.0),
        UseTestMaterial,
    ));
}

#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
struct TestMaterial {}

impl Material for TestMaterial {
    fn fragment_shader() -> ShaderRef {
        "test.wgsl".into()
    }
}

///
///
#[derive(Component, Reflect)]
#[reflect(Component)]
#[component(on_add = on_add)]
struct UseTestMaterial;

fn on_add(
    mut world: DeferredWorld,
    HookContext { entity, .. }: HookContext,
) {
    let test_mat =
        world.resource::<MaterialStore>().0.clone();

    world
        .commands()
        .entity(entity)
        .remove::<MeshMaterial3d<StandardMaterial>>()
        .insert(MeshMaterial3d(test_mat));
}

What went wrong

A panic, seemingly related to prepass/cold specialization.

2025-09-12T21:45:21.989257Z  INFO bevy_diagnostic::system_information_diagnostics_plugin::internal: SystemInfo { os: "macOS 14.2.1 Sonoma", kernel: "23.2.0", cpu: "Apple M1 Max", core_count: "10", memory: "64.0 GiB" }
2025-09-12T21:45:22.040910Z  INFO bevy_render::renderer: AdapterInfo { name: "Apple M1 Max", vendor: 0, device: 0, device_type: IntegratedGpu, driver: "", driver_info: "", backend: Metal }
2025-09-12T21:45:22.353579Z  INFO bevy_render::batching::gpu_preprocessing: GPU preprocessing is fully supported on this device.
2025-09-12T21:45:22.381321Z  INFO bevy_winit::system: Creating new window bevy_light_reflect (0v0)
2025-09-12T21:45:22.617382Z ERROR bevy_asset::server: Path not found: /Users/chris/tmp/bevy_light_reflect/assets/test.wgsl

thread 'Compute Task Pool (2)' panicked at /Users/chris/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_pbr-0.17.0-rc.1/src/material.rs:957:79:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

thread 'main' panicked at /Users/chris/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bevy_pbr-0.17.0-rc.1/src/prepass/mod.rs:891:79:
called `Option::unwrap()` on a `None` value
Encountered a panic in system `bevy_pbr::prepass::specialize_prepass_material_meshes`!
Encountered a panic in system `bevy_pbr::material::specialize_material_meshes`!

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-RenderingDrawing game state to the screenC-BugAn unexpected or incorrect behaviorS-Needs-ReviewNeeds reviewer attention (from anyone!) to move forward

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions