-
So I have a glb model of a rat that I want to render many instances of. How do I tell if GPU instancing is working correctly? Currently I am only getting 6 FPS for 2,500 rats. I feel like it would be higher if instancing was working but I am unsure on how to prove that. Here is the simple code that I am using to spawn them in: use bevy::prelude::*;
use bevy_asset_loader::prelude::*;
use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin};
use std::time::Duration;
use iyes_perf_ui::prelude::*;
#[derive(Resource)]
struct Animations {
graph: Handle<AnimationGraph>,
node_indices: Vec<AnimationNodeIndex>,
}
#[derive(Component)]
struct RatsSpawned;
#[derive(Component)]
struct Rat;
fn main() {
App::new()
.add_plugins((DefaultPlugins, PanOrbitCameraPlugin))
.add_plugins(bevy::diagnostic::FrameTimeDiagnosticsPlugin::default())
.add_plugins(bevy::diagnostic::EntityCountDiagnosticsPlugin)
.add_plugins(bevy::diagnostic::SystemInformationDiagnosticsPlugin)
.add_plugins(bevy::render::diagnostic::RenderDiagnosticsPlugin)
.add_plugins(PerfUiPlugin)
.init_state::<MyStates>()
.add_loading_state(
LoadingState::new(MyStates::AssetLoading)
.continue_to_state(MyStates::Next)
.load_collection::<RatModels>()
.load_collection::<WorldMaterial>(),
)
.add_systems(OnEnter(MyStates::Next), show_model)
.add_systems(
Update,
(play_animation_when_ready.run_if(in_state(MyStates::Next)),
//test_system.run_if(in_state(MyStates::Next)),)
)
)
//.add_observer(play_animation_when_ready)
.run();
}
fn show_model(
mut commands: Commands,
rat_models: Res<RatModels>,
mut graphs: ResMut<Assets<AnimationGraph>>,
env: Res<WorldMaterial>,
) {
commands.spawn(PerfUiAllEntries::default());
// Also spawn a camera to view the model
commands.spawn((
Transform::from_translation(Vec3::new(0.0, 1.5, 5.0)),
PanOrbitCamera::default(),
EnvironmentMapLight {
diffuse_map: env.diffuse_map.clone(),
specular_map: env.specular_map.clone(),
intensity: 900.0,
..default()
},
));
// Add a light to illuminate the scene
commands.spawn((
DirectionalLight::default(),
Transform::from_rotation(Quat::from_euler(EulerRot::XYZ, -0.5, -0.5, 0.0)),
));
let (graph, index) = AnimationGraph::from_clip(rat_models.animation_clip.clone());
let graph_handle = graphs.add(graph);
// Create a component that stores a reference to our animation.
commands.insert_resource(Animations {
graph: graph_handle.clone(),
node_indices: vec![index],
});
let spawn_base = commands
.spawn((Transform::default(), Visibility::default(), Rat))
.id();
let rat_handle = rat_models.rat.clone();
commands.spawn_batch((0..50).flat_map(|x| (0..50).map(move |y| (x, y))).map(
move |(_x, _y)| {
(
SceneRoot(rat_handle.clone()),
Transform::from_xyz(_x as f32 / 10.0, _y as f32 / 10.0, 0.0)
.with_scale(Vec3::splat(1.0)),
AnimationGraphHandle(graph_handle.clone()),
//AnimationTransitions::new(),
ChildOf(spawn_base),
)
},
));
}
fn play_animation_when_ready(
animations: Res<Animations>,
mut commands: Commands,
rats: Query<(Entity, &mut AnimationPlayer), Added<AnimationPlayer>>,
) {
for (entity, mut player) in rats {
eprintln!("{entity}");
let mut animation_transitions = AnimationTransitions::new();
animation_transitions
.play(
&mut player,
animations.node_indices[0],
Duration::from_millis(15),
)
.repeat();
commands
.entity(entity)
.insert(AnimationGraphHandle(animations.graph.clone()))
.insert(animation_transitions);
}
}
#[derive(AssetCollection, Resource)]
struct RatModels {
#[asset(path = "blackrat_free_glb\\blackrat.glb#Scene0")]
rat: Handle<Scene>,
#[asset(path = "blackrat_free_glb\\blackrat.glb#Animation1")]
animation_clip: Handle<AnimationClip>,
}
#[derive(AssetCollection, Resource)]
struct WorldMaterial {
#[asset(path = "EnviromentMaps\\pisa_diffuse_rgb9e5_zstd.ktx2")]
diffuse_map: Handle<Image>,
#[asset(path = "EnviromentMaps\\pisa_specular_rgb9e5_zstd.ktx2")]
specular_map: Handle<Image>,
}
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]
enum MyStates {
#[default]
AssetLoading,
Next,
} |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments
-
I'm porting a scene from Three.js to Bevy that includes thousands of trees. In Three.js, everything renders smoothly at 114fps. I tried using Bevy’s auto-instancing (similar to your code), but the performance is horrible, I'm barely getting 10 FPS. It seems like auto-instancing isn't working as expected... I will keep digging for a solution, if you find a solution first please let me know, thanks. |
Beta Was this translation helpful? Give feedback.
-
You have a couple of options:
If you're getting lower than expected FPS, please profile https://github.com/bevyengine/bevy/blob/main/docs/profiling.md to find out where the bottleneck is. It's impossible to know without concrete data :) |
Beta Was this translation helpful? Give feedback.
-
I found the reason it was running so slowly for me. I had the EGUI WorldInspectorPlugin enabled, and whenever it was open, the framerate dropped significantly. However, when I collapsed it, I got the expected performance on the computer I'm currently using. Screen.Recording.2025-07-29.at.12.52.37.PM.mp4This is my code: let tree_scene: Handle<Scene> = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/default-tree.glb"));
let batch: Vec<_> = positions
.chunks_exact(3)
.map(|position| Vec3::from_slice(position))
.map(|position| {
(
SceneRoot(tree_scene.clone()),
Transform::from_translation(position),
)
})
.collect();
println!("{:?}", batch.len()); // 48386
commands.spawn_batch(batch); |
Beta Was this translation helpful? Give feedback.
I found the reason it was running so slowly for me. I had the EGUI WorldInspectorPlugin enabled, and whenever it was open, the framerate dropped significantly. However, when I collapsed it, I got the expected performance on the computer I'm currently using.
Screen.Recording.2025-07-29.at.12.52.37.PM.mp4
This is my code: