Skip to content

Fix sorted batching without indirect drawing#24708

Queued
issam3105 wants to merge 1 commit into
bevyengine:mainfrom
issam3105:Fix_sorted_batching_without_indirect_drawing
Queued

Fix sorted batching without indirect drawing#24708
issam3105 wants to merge 1 commit into
bevyengine:mainfrom
issam3105:Fix_sorted_batching_without_indirect_drawing

Conversation

@issam3105

@issam3105 issam3105 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Objective

Fix incorrect sorted batching when indirect drawing is unavailable.

While testing some glTF files on the web, I noticed transparent meshes rendering with the wrong geometry or transforms: some objects appeared duplicated or at incorrect positions. The same scenes rendered correctly on native Windows.

Disabling automatic batching for Transparent3d avoided the issue by setting AUTOMATIC_BATCHING to false, which pointed the investigation toward batching.

After investigation, the issue came from the NoIndirectDrawing path used when indirect drawing is unavailable. Different meshes could still be grouped as if indirect multi-draw was supported.

Solution

When NoIndirectDrawing is active, sorted phase items with different meshes now start a new batch set instead of using BreakBatch.

Testing

The original glTF asset that exposed the issue is confidential, but I can share it privately with maintainers if needed.

I was able to reproduce the issue natively on Windows by adding NoIndirectDrawing, which matches the path used on web when indirect drawing is unavailable. Press Space to test the NoIndirectDrawing path.

use bevy::{prelude::*, render::view::NoIndirectDrawing};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .insert_resource(ClearColor(Color::srgb(0.48, 0.48, 0.48)))
        .add_systems(Startup, setup)
        .add_systems(Update, toggle_no_indirect_drawing)
        .run();
}

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    let meshes = [
        meshes.add(Cuboid::new(0.55, 0.55, 0.55)),
        meshes.add(Sphere::new(0.36).mesh().uv(32, 18)),
        meshes.add(Cylinder::new(0.32, 0.68).mesh().resolution(36)),
    ];
    let material = materials.add(StandardMaterial {
        base_color: Color::srgba(0.05, 0.75, 1.0, 0.48),
        alpha_mode: AlphaMode::Blend,
        ..default()
    });

    for (mesh, position) in [
        (0, Vec3::new(-1.0, -0.35, -1.8)),
        (1, Vec3::new(0.0, -0.25, -1.35)),
        (2, Vec3::new(1.0, -0.15, -0.9)),
        (0, Vec3::new(-1.0, 0.35, -0.45)),
        (1, Vec3::new(0.0, 0.45, 0.0)),
        (2, Vec3::new(1.0, 0.55, 0.45)),
    ] {
        commands.spawn((
            Mesh3d(meshes[mesh].clone()),
            MeshMaterial3d(material.clone()),
            Transform::from_translation(position),
        ));
    }

    commands.spawn((
        DirectionalLight::default(),
        Transform::from_xyz(4.0, 6.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
    ));

    let transform = Transform::from_xyz(0.0, 1.4, 4.4).looking_at(Vec3::ZERO, Vec3::Y);
    commands.spawn((Camera3d::default(), transform));
    commands.spawn((
        Camera3d::default(),
        Camera {
            is_active: false,
            ..default()
        },
        NoIndirectDrawing,
        transform,
    ));
}

fn toggle_no_indirect_drawing(
    keys: Res<ButtonInput<KeyCode>>,
    mut indirect: Single<&mut Camera, Without<NoIndirectDrawing>>,
    mut direct: Single<&mut Camera, With<NoIndirectDrawing>>,
) {
    if keys.just_pressed(KeyCode::Space) {
        let direct_enabled = indirect.is_active;
        indirect.is_active = !direct_enabled;
        direct.is_active = direct_enabled;
    }
}

Enregistrement.de.l.ecran.2026-06-22.142624.mp4

Showcase

Before this change, transparent glTF meshes on web could render with duplicated geometry or wrong transforms when automatic batching was enabled.

Enregistrement.de.l.ecran.2026-06-22.120126.mp4

After this change, the same scenes render correctly:

Enregistrement.de.l.ecran.2026-06-22.144337.mp4

@kfc35 kfc35 added C-Bug An unexpected or incorrect behavior A-Rendering Drawing game state to the screen S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 22, 2026
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in Rendering Jun 22, 2026
@kfc35 kfc35 added the D-Straightforward Simple bug fixes and API improvements, docs, test and examples label Jun 22, 2026
@kfc35 kfc35 added this to the 0.19.1 milestone Jun 22, 2026
@kfc35 kfc35 added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 22, 2026
@alice-i-cecile alice-i-cecile added the O-WebGL2 Specific to the WebGL2 render API label Jun 22, 2026
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Rendering Drawing game state to the screen C-Bug An unexpected or incorrect behavior D-Straightforward Simple bug fixes and API improvements, docs, test and examples O-WebGL2 Specific to the WebGL2 render API S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it

Projects

Status: Needs SME Triage

Development

Successfully merging this pull request may close these issues.

3 participants