Skip to content

Fix Camera::is_active and delayed spawned no cpu culling camera not rendering anything - #25690

Merged
alice-i-cecile merged 10 commits into
bevyengine:mainfrom
CodingDaniel1:fix-camera-is-active
Sep 11, 2026
Merged

alice-i-cecile merged 10 commits into
bevyengine:mainfrom
CodingDaniel1:fix-camera-is-active

Conversation

@CodingDaniel1

Copy link
Copy Markdown
Contributor

Objective

This is a followup pr for #25670

I have found issues related to toggling is_active at runtime causes the rendering to stop working, and this pr fixes that. While fixing on that, ive found another issues when using NoCpuCulling on Mesh entity, if the camera is spawned after the entity got collected for rendering, then the camera wont render that entity. The reason why NoCpuCulling on Mesh causes this but not on camera, is the gpu mesh collect pass looks for ViewVisibility changes, and cpu culling system will trigger the change detection even if the camera is tagged with NoCpuCulling. But it wont trigger it when mesh have NoCpuCulling .

Solution

For the is_active issue, I removed RenderVisibleEntities from the render camera when its inactive, but dont remove it when the window is minized. This behaviour matches what bevy other places does, which most places dont care if window is minized.

For the second issue, I check to see is RenderVisibleEntities added this frame in collect_gpu_culled_meshes and do a full table flush when RenderVisibleEntities is confirmed to be new. This means a freshly spawned camera will pick up previously registered mesh, and is_active toggling camera will still do the same. Be aware that this is solely for Mesh3d tagged with NoCpuCulling since thats purpose of this function, any Mesh3d not tagged with NoCpuCulling will still go through the cpu collect pass instead.

Testing

I used the following functions to toggle is_active field and use gpu culling path, put it in 3d_scene and ssao example. Live test it, the rendering stays the same when toggling is_active at runtime. But the issue remains on bevy/main.

fn use_no_cpu_culling(add: On<Add<Mesh3d>>, mut commands: Commands) {
    commands
        .entity(add.entity)
        .insert(bevy::camera::visibility::NoCpuCulling);
}
fn set_active(query: Query<&mut Camera>, keyboard: Res<ButtonInput<KeyCode>>) {
    if keyboard.just_pressed(KeyCode::KeyP) {
        for mut cam in query {
            cam.is_active = !cam.is_active;
        }
    }
}

Here is the full code snippet i used for testing both issues. The camera spawning will be delayed by 2sec. On main you will not see anything rendered, but with this pr, rendering is normal.

//! A simple 3D scene with light shining over a cube sitting on a plane.

use bevy::{camera::visibility::NoCpuCulling, prelude::*};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, (scene.spawn(), spawn_cam))
        .add_systems(Update, set_active)
        .add_observer(obs_test)
        .run();
}

/// set up a simple 3D scene
fn scene() -> impl SceneList {
    bsn_list! [
        (
            #CircularBase
            Mesh3d(asset_value(Circle::new(4.0)))
            MeshMaterial3d::<StandardMaterial>(asset_value(Color::WHITE))
            Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2))
        ),
        (
            #Cube
            Mesh3d(asset_value(Cuboid::new(1.0, 1.0, 1.0)))
            MeshMaterial3d::<StandardMaterial>(asset_value(Color::srgb_u8(124, 144, 255)))
            Transform::from_xyz(0.0, 0.5, 0.0)
        ),
        (
            PointLight {
                shadow_maps_enabled: true,
            }
            Transform::from_xyz(4.0, 8.0, 4.0)
        ),
        // (
        //     Camera3d
        //     Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y)
        // )
    ]
}

fn obs_test(add: On<Add<Mesh3d>>, mut commands: Commands) {
    commands.entity(add.entity).insert(NoCpuCulling);
}

fn spawn_cam(mut commands: Commands) {
    commands.delayed().secs(2.0).spawn((
        Camera3d::default(),
        Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
    ));
}

fn set_active(query: Query<&mut Camera>, keyboard: Res<ButtonInput<KeyCode>>) {
    if keyboard.just_pressed(KeyCode::KeyP) {
        for mut cam in query {
            cam.is_active = !cam.is_active;
        }
    }
}

@beicause beicause left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran an AI review. See CodingDaniel1#1.

I manually tested and this fixed the issue (the testing on the main branch always shows a black screen).

@beicause beicause 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 Sep 8, 2026
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in Rendering Sep 8, 2026
@CodingDaniel1

CodingDaniel1 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Latest commit fixes directional light shadow cascades changing path issue.

By using the following code, im able to produce shadows missing issues on main, but not with this pr.

When changing the cascade count using KeyP, you can see the shadows missing behavior on main.

//! A simple 3D scene with light shining over a cube sitting on a plane.

use bevy::{
    camera::visibility::NoCpuCulling,
    light::{CascadeShadowConfig, CascadeShadowConfigBuilder},
    prelude::*,
};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, scene.spawn())
        .add_systems(Update, change_cascade)
        .add_observer(use_no_cpu_culling)
        .run();
}

/// set up a simple 3D scene
fn scene() -> impl SceneList {
    bsn! {
        #CircularBase
        Mesh3d(asset_value(Circle::new(4.0)))
        MeshMaterial3d::<StandardMaterial>(asset_value(Color::WHITE))
        Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2))
        --
        #Cube
        Mesh3d(asset_value(Cuboid::new(1.0, 1.0, 1.0)))
        MeshMaterial3d::<StandardMaterial>(asset_value(Color::srgb_u8(124, 144, 255)))
        Transform::from_xyz(0.0, 0.5, 0.0)
        --
        // PointLight {
        //     shadow_maps_enabled: true,
        // }
        // Transform::from_xyz(4.0, 8.0, 4.0)
        // --
        DirectionalLight {
            shadow_maps_enabled: true
        }
        CascadeShadowConfigBuilder {
            num_cascades: 4,
            ..Default::default()
        }.build()
        Transform::from_xyz(4.0, 8.0, 4.0).looking_at(Vec3::ZERO, Vec3::Y)
        --
        Camera3d
        Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y)
    }
}
fn use_no_cpu_culling(add: On<Add<Mesh3d>>, mut commands: Commands) {
    commands.entity(add.entity).insert(NoCpuCulling);
}

fn change_cascade(
    query: Query<&mut CascadeShadowConfig, With<DirectionalLight>>,
    mut cascade_count: Local<u32>,
    keyboard: Res<ButtonInput<KeyCode>>,
) {
    if keyboard.just_pressed(KeyCode::KeyP) {
        *cascade_count += 1;
        let cur_cascade_count = *cascade_count;
        if *cascade_count > 4 {
            *cascade_count = 1;
        }

        for mut config in query {
            *config = CascadeShadowConfigBuilder {
                num_cascades: cur_cascade_count as usize,
                ..default()
            }
            .build();
        }
    }
}

@stuartparmenter stuartparmenter 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 Sep 9, 2026
@alice-i-cecile
alice-i-cecile added this pull request to the merge queue Sep 11, 2026
Merged via the queue into bevyengine:main with commit cd801d2 Sep 11, 2026
40 checks passed
@github-project-automation github-project-automation Bot moved this from Needs SME Triage to Done in Rendering Sep 11, 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 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: Done

Development

Successfully merging this pull request may close these issues.

4 participants