Skip to content

Spawning multiple entities with different mesh handles but same material handle causes bevy_render::renderer::render_system to panic on DX12 backend #21645

@NicholasCostanzo

Description

@NicholasCostanzo

Bevy version and features

  • Bevy version: 0.17 (and up)

Relevant system information

  • OS: Windows 10, Windows 11
  • WGPU backend: DX12
  • Rust version: cargo 1.90.0 (840b83a10 2025-07-30)
  • Adapter info:
AdapterInfo { name: "NVIDIA GeForce RTX 3070 Ti", vendor: 4318, device: 9346, device_type: DiscreteGpu, driver: "32.0.15.8142", driver_info: "", backend: Dx12 }

What you did

Attach identical material handles to different meshes while using Dx12 backend.

Reproduction steps

  1. Start with generate_custom_mesh example
  2. Replace setup with the following:
fn setup(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut meshes: ResMut<Assets<Mesh>>,
) {
    // Import the custom texture.
    let mesh_material = MeshMaterial3d(materials.add(StandardMaterial {
        base_color_texture: Some(asset_server.load("textures/array_texture.png")),
        ..default()
    }));

    // Case 1: Use unique meshes from vec
    // call create_cube_mesh in a loop 10 times and store them in a vec
    let ten_meshes = (0..10)
        .map(|_| meshes.add(create_cube_mesh()))
        .collect::<Vec<_>>();

    for i in 0..ten_meshes.len() {
        // Case 1: Use unique meshes from vec
        let mesh_handle = ten_meshes[i].clone();
        commands.spawn((
            Mesh3d(mesh_handle),
            mesh_material.clone(),
            Transform::from_xyz(i as f32, 0.0, 0.0),
            CustomUV,
        ));
    }

    // Transform for the camera and lighting, looking at (0,0,0) (the position of the mesh).
    let camera_and_light_transform =
        Transform::from_xyz(1.8, 1.8, 1.8).looking_at(Vec3::ZERO, Vec3::Y);

    // Camera in 3D space.
    commands.spawn((Camera3d::default(), camera_and_light_transform));

    // Light up the scene.
    commands.spawn((PointLight::default(), camera_and_light_transform));

    // Text to describe the controls.
    commands.spawn((
        Text::new("Controls:\nSpace: Change UVs\nX/Y/Z: Rotate\nR: Reset orientation"),
        Node {
            position_type: PositionType::Absolute,
            top: px(12),
            left: px(12),
            ..default()
        },
    ));
}
  1. Run generate_custom_mesh example with WGPU_BACKEND=dx12.

What went wrong

This results in the following panic:

thread 'Compute Task Pool (4)' panicked at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\wgpu-core-26.0.1\src\command\render.rs:2760:17:
assertion `left == right` failed
  left: 32
 right: 20
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Encountered a panic in system `bevy_render::renderer::render_system`!

Correct results can be seen when running with Vulkan WGPU backend.

Additional information

  • Ensuring all mesh handles are identical as well as the material handles avoids the issue:
up(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut meshes: ResMut<Assets<Mesh>>,
) {
    // Import the custom texture.
    let mesh_material = MeshMaterial3d(materials.add(StandardMaterial {
        base_color_texture: Some(asset_server.load("textures/array_texture.png")),
        ..default()
    }));

    // Case 2: Use instances of cloned mesh from vec
    // call create_cube_mesh once, clone the handle 10 times, and store them in a vec
    let cube_mesh_handle: Handle<Mesh> = meshes.add(create_cube_mesh());
    let ten_mesh_clones = (0..10)
        .map(|_| cube_mesh_handle.clone())
        .collect::<Vec<_>>();

    for i in 0..ten_mesh_clones.len() {
        // Case 2: Use instances of cloned mesh from vec
        let mesh_handle = ten_mesh_clones[i].clone();
        commands.spawn((
            Mesh3d(mesh_handle),
            mesh_material.clone(),
            Transform::from_xyz(i as f32, 0.0, 0.0),
            CustomUV,
        ));
    }

    // Transform for the camera and lighting, looking at (0,0,0) (the position of the mesh).
    let camera_and_light_transform =
        Transform::from_xyz(1.8, 1.8, 1.8).looking_at(Vec3::ZERO, Vec3::Y);

    // Camera in 3D space.
    commands.spawn((Camera3d::default(), camera_and_light_transform));

    // Light up the scene.
    commands.spawn((PointLight::default(), camera_and_light_transform));

    // Text to describe the controls.
    commands.spawn((
        Text::new("Controls:\nSpace: Change UVs\nX/Y/Z: Rotate\nR: Reset orientation"),
        Node {
            position_type: PositionType::Absolute,
            top: px(12),
            left: px(12),
            ..default()
        },
    ));
}
  • As does ensuring mesh handle and material handle pairings are unique:
fn setup(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut meshes: ResMut<Assets<Mesh>>,
) {
    // Case 3: Use unique meshes and materials
    // call create_cube_mesh in a loop 10 times and store them in a vec
    let ten_meshes = (0..10)
        .map(|_| meshes.add(create_cube_mesh()))
        .collect::<Vec<_>>();

    for i in 0..ten_meshes.len() {
        // Case 3: Use unique meshes and materials
        let mesh_handle = ten_meshes[i].clone();
        commands.spawn((
            Mesh3d(mesh_handle),
            MeshMaterial3d(materials.add(StandardMaterial {
                base_color_texture: Some(asset_server.load("textures/array_texture.png")),
                ..default()
            })),
            Transform::from_xyz(i as f32, 0.0, 0.0),
            CustomUV,
        ));
    }

    // Transform for the camera and lighting, looking at (0,0,0) (the position of the mesh).
    let camera_and_light_transform =
        Transform::from_xyz(1.8, 1.8, 1.8).looking_at(Vec3::ZERO, Vec3::Y);

    // Camera in 3D space.
    commands.spawn((Camera3d::default(), camera_and_light_transform));

    // Light up the scene.
    commands.spawn((PointLight::default(), camera_and_light_transform));

    // Text to describe the controls.
    commands.spawn((
        Text::new("Controls:\nSpace: Change UVs\nX/Y/Z: Rotate\nR: Reset orientation"),
        Node {
            position_type: PositionType::Absolute,
            top: px(12),
            left: px(12),
            ..default()
        },
    ));
}
  • This only happens with DX12 backend, not with Metal or Vulkan. I suspect this is because DX12 is stricter about byte alignment and layout.
  • This panic only occurs after Bevy version 0.17. Could not reproduce issue on Bevy 0.16.
  • Based on avoiding this issue involving how mesh and material handles are paired, my immediate hunch is that this has something to do with how Bevy is handling auto-instancing.
Full backtrace

thread 'Compute Task Pool (2)' panicked at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\wgpu-core-26.0.1\src\command\render.rs:2760:17:
assertion `left == right` failed
  left: 32
 right: 20
stack backtrace:
   0: std::panicking::begin_panic_handler
             at /rustc/1159e78c4747b02ef996e55082b704c09b970588/library\std\src\panicking.rs:697
   1: core::panicking::panic_fmt
             at /rustc/1159e78c4747b02ef996e55082b704c09b970588/library\core\src\panicking.rs:75
   2: core::panicking::assert_failed_inner
             at /rustc/1159e78c4747b02ef996e55082b704c09b970588/library\core\src\panicking.rs:443
   3: core::panicking::assert_failed
             at /rustc/1159e78c4747b02ef996e55082b704c09b970588/library\core\src\panicking.rs:403
   4: wgpu_core::command::render::multi_draw_indirect
             at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\wgpu-core-26.0.1\src\command\render.rs:2760
   5: wgpu_core::command::render::impl$22::render_pass_end::closure$0
             at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\wgpu-core-26.0.1\src\command\render.rs:2064
   6: wgpu_core::command::RecordingGuard::record
             at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\wgpu-core-26.0.1\src\command\mod.rs:324
   7: enum2$::unlock_and_record
             at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\wgpu-core-26.0.1\src\command\mod.rs:245
   8: wgpu_core::global::Global::render_pass_end
             at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\wgpu-core-26.0.1\src\command\render.rs:1835
   9: wgpu::backend::wgpu_core::impl$50::end
             at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\wgpu-26.0.1\src\backend\wgpu_core.rs:3366
  10: wgpu::backend::wgpu_core::impl$51::drop
             at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\wgpu-26.0.1\src\backend\wgpu_core.rs:3379
  11: core::ptr::drop_in_place
             at ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\ptr\mod.rs:804
  12: core::ptr::drop_in_place >
             at ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\ptr\mod.rs:804
  13: core::ptr::drop_in_place
             at ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\ptr\mod.rs:804
  14: core::ptr::drop_in_place
             at ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\ptr\mod.rs:804
  15: core::mem::drop
             at ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\mem\mod.rs:961
  16: bevy_core_pipeline::core_3d::main_opaque_pass_3d_node::impl$0::run::closure$0
             at .\crates\bevy_core_pipeline\src\core_3d\main_opaque_pass_3d_node.rs:136
  17: core::ops::function::FnOnce::call_once >
             at ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\ops\function.rs:253
  18: alloc::boxed::impl$28::call_once,dyn$,assoc$ >,core::marker::
             at ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\alloc\src\boxed.rs:1971
  19: bevy_render::renderer::impl$0::finish::closure$0::async_block$0
             at .\crates\bevy_render\src\renderer\mod.rs:599
  20: core::panic::unwind_safe::impl$28::poll >
             at ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\panic\unwind_safe.rs:297
  21: futures_lite::future::impl$11::poll::closure$0 > >
             at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\futures-lite-2.6.1\src\future.rs:653
  22: core::panic::unwind_safe::impl$25::call_once > >,futures_lite::future::impl$11::poll::closure_env$0 > > >
             at ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\panicking.rs:589
  24: std::panic::catch_unwind > > >,enum2$ > >
             at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\futures-lite-2.6.1\src\future.rs:653
  28: async_executor::impl$21::poll > >,async_executor::impl$5::spawn_inner::closure_env$0 > >,async_executor::impl$5::spa
             at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\async-task-4.7.1\src\raw.rs:550
  30: core::ops::function::FnOnce::call_once,alloc::boxed::Box,alloc::alloc::Global> > > > >,async_task
             at ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\panic\unwind_safe.rs:272
  32: std::panicking::catch_unwind::do_call > >,async_executor::impl$5::spawn_inner::closu
             at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\async-task-4.7.1\src\raw.rs:549
  37: async_task::runnable::Runnable >::run >
             at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\async-task-4.7.1\src\runnable.rs:781
  38: async_executor::impl$13::run::async_fn$0::async_block$0,async_channel::RecvError> >,futures_lite::future::Or,async_channel::RecvError> >,futures_lite::future::Or,async_channel::R
             at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\futures-lite-2.6.1\src\future.rs:454
  40: async_executor::impl$13::run::async_fn$0,async_channel::RecvError> >,futures_lite::future::Or,async_chan
             at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\async-executor-1.13.3\src\lib.rs:758
  41: async_executor::impl$5::run::async_fn$0,async_channel::RecvError> >,futures_lite::future::Or,async_chann
             at ~\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\async-executor-1.13.3\src\lib.rs:344
  42: futures_lite::future::block_on::closure$0,async_channel::RecvError> >,enum2$,async_channel::RecvError> >,futures_lite::future::Or > >::try_with >,futures_lite::future::block_on::closure_env$0 > >::with >,futures_lite::future::block_on::closure_env$0,async_channel::RecvError> >,enum2$,async_channel::RecvError> >,futures_lite::future::Or,async_channel::RecvError> > >
             at ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\panicking.rs:589
  48: std::panic::catch_unwind,async_channel::RecvError> > >
  49: std::panicking::catch_unwind
             at ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\panicking.rs:552
  50: std::panic::catch_unwind,async_channel::RecvError> > >
             at ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\panic.rs:359
  51: bevy_tasks::task_pool::impl$2::new_internal::closure$0::closure$0::closure$0
             at .\crates\bevy_tasks\src\task_pool.rs:197
  52: std::thread::local::LocalKey::try_with >
             at ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\thread\local.rs:315
  53: std::thread::local::LocalKey::with >  
             at ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\thread\local.rs:279
  54: bevy_tasks::task_pool::impl$2::new_internal::closure$0::closure$0
             at .\crates\bevy_tasks\src\task_pool.rs:190
  55: core::hint::black_box
             at ~\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\hint.rs:482
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
Encountered a panic in system `bevy_render::renderer::render_system`!

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-RenderingDrawing game state to the screenC-BugAn unexpected or incorrect behaviorO-DX12Specific to the DX12 render APIP-CrashA sudden unexpected crashS-Needs-InvestigationThis issue requires detective work to figure out what's going wrong

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions