bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
36.67k stars 3.61k forks source link

Really slow rendering of tiled 3d floors #6032

Closed terhechte closed 2 years ago

terhechte commented 2 years ago

Bevy version

I tried:

[Optional] Relevant system information

`AdapterInfo { name: "Apple M1 Max", vendor: 0, device: 0, device_type: IntegratedGpu, backend: Metal }`

What you did

I'm trying to render something like a tiled floor in a 3d world. Initially I wanted to use a tiled texture, but I ran into this issue of tiled textures not working and the listed approaches somehow didn't work for me.

My next approach was to spawn a lot of cubes or planes instead. Here's the code:

use bevy::diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin};
use bevy::window::close_on_esc;
use bevy::{log::LogSettings, prelude::*};
mod split;

const SIZE: f32 = 0.25;
const PLAYER_Y: f32 = 4.5;

fn main() {
    App::new()
        .insert_resource(LogSettings {
            filter: "info,wgpu_core=warn,wgpu_hal=warn,pacbomber=debug".into(),
            level: bevy::log::Level::DEBUG,
        })
        .insert_resource(ClearColor(Color::rgb(20. / 255., 20. / 255., 20. / 255.)))
        .insert_resource(WindowDescriptor {
            title: "Test".to_string(),
            width: 900.,
            height: 660.,
            resizable: true,
            ..default()
        })
        .add_plugins(DefaultPlugins)
        .add_plugin(LogDiagnosticsPlugin::default())
        .add_plugin(FrameTimeDiagnosticsPlugin::default())
        .add_startup_system(build_level_system)
        .add_system(keyboard_system)
        .add_system(move_camera_system)
        .add_system(close_on_esc)
        .run();
}

#[derive(Component)]
struct MainCamera;

#[derive(Component)]
struct Player;

fn build_level_system(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
) {
    let camera_bundle = Camera3dBundle {
        transform: Transform::from_xyz(0.0, PLAYER_Y, 3.0).looking_at(Vec3::ZERO, Vec3::Y),
        ..default()
    };
    commands.spawn_bundle(camera_bundle).insert(MainCamera);

    let player = meshes.add(Mesh::from(shape::Cube { size: SIZE }));
    let player_material = materials.add(StandardMaterial {
        base_color: Color::YELLOW,
        metallic: 0.5,
        reflectance: 0.15,
        ..Default::default()
    });

    commands
        .spawn_bundle(PbrBundle {
            mesh: player,
            material: player_material,
            transform: Transform::from_xyz(10. * SIZE, 0.5, 10. * SIZE),
            ..Default::default()
        })
        .insert(Player);

    let floor = meshes.add(Mesh::from(shape::Cube { size: SIZE - 0.01 }));
    let floor_material = materials.add(StandardMaterial {
        base_color: Color::RED,
        metallic: 0.5,
        reflectance: 0.15,
        ..Default::default()
    });

    let offset = SIZE * -10.;
    let (mut x, mut z) = (offset, offset);
    for zc in 0..=1280 {
        z += SIZE;
        for xc in 0..=1280 {
            x += SIZE;
            commands.spawn_bundle(PbrBundle {
                mesh: floor.clone(),
                material: floor_material.clone(),
                transform: Transform::from_xyz(x, -0.1, z),
                ..Default::default()
            });
            // every corner a light
            match (zc, xc) {
                (1, 1) => spawn_light(&mut commands, x, z),
                (1, 127) => spawn_light(&mut commands, x, z),
                (127, 127) => spawn_light(&mut commands, x, z),
                (127, 1) => spawn_light(&mut commands, x, z),
                (64, 64) => spawn_light(&mut commands, x, z),
                (64, 1) => spawn_light(&mut commands, x, z),
                (1, 64) => spawn_light(&mut commands, x, z),
                _ => (),
            }
        }
        x = offset;
    }
}

fn spawn_light(commands: &mut Commands, x: f32, z: f32) {
    commands.spawn_bundle(PointLightBundle {
        point_light: PointLight {
            intensity: 50.0,
            shadows_enabled: true,
            ..default()
        },
        transform: Transform::from_xyz(x, 1.0, z),
        ..default()
    });
}

fn keyboard_system(
    keyboard_input: Res<Input<KeyCode>>,
    mut player: Query<&mut Transform, With<Player>>,
) {
    let speed = 0.15;
    for (code, (d_x, d_z)) in [
        (KeyCode::Left, (-1, 0)),
        (KeyCode::Right, (1, 0)),
        (KeyCode::Up, (0, -1)),
        (KeyCode::Down, (0, 1)),
    ] {
        if keyboard_input.pressed(code) || keyboard_input.just_pressed(code) {
            let (x, z) = ((d_x as f32) * speed, (d_z as f32) * speed);
            for mut transform in player.iter_mut() {
                transform.translation.x += x;
                transform.translation.z += z;
            }
        }
    }
}

fn move_camera_system(
    player_query: Query<&Transform, (With<Player>, Without<MainCamera>)>,
    mut camera_query: Query<(&Camera, &mut Transform), With<MainCamera>>,
) {
    if player_query.is_empty() {
        return;
    }
    if camera_query.is_empty() {
        return;
    }
    let player_pos = player_query.single().translation;
    let mut value = camera_query.single_mut().1;
    *value = Transform::from_xyz(player_pos.x, PLAYER_Y - player_pos.y, player_pos.z + 3.0)
        .looking_at(player_pos, Vec3::Y);
}

What went wrong

The FPS goes down to ~35 when I spawn 128 x 128 cubes. Which is not a particularly large level size. It crawls to ~10 fps when I spawn 1280 x 1280 cubes. I also tried rendering with the high entity count PR, but that didn't really change much either. Now, I understand that 1280 x 1280 is quite a lot of entities, but wouldn't frustum culling do away with anything not visible? Also, even if tiling would work, constructing bigger levels with lots of floor texture changes would still quickly run into a high entity situation. So either I'm doing something wrong or something is really slow here.

Additional information

Other information that can be used to further reproduce or isolate the problem.

Screenshot 2022-09-20 at 11 04 17
konsti219 commented 2 years ago

Did you compile with --release?

afonsolage commented 2 years ago

I have some doubts if this is a bug or just a heavy unoptimized way to rendering a floor. So here are some things to keep in mind:

  1. If you are running in debug mode, there are a lot of checks which is done, that in release mode aren't (you can compile only Bevy dependencies in release, by adding opt-level = 3 or a [profile.dev.package."*"] section)
  2. Every floor tile is being spawned as a cube, which has 16 vertices;
  3. You are spawning 1.6m cubes, even tho there is a frustum culling, that is a lot of entities;
  4. Debug printing will slow down your FPS and should not be used to measure performance, it most intended as a quick and dirty way to check the current FPS with some error margin;

With all being said, if you have made this previous work with other engine and it acted as you expected, that may be a bug in Bevy, but I don't expect any market engine to have a good performance under those conditions.

Note: I tried your conde on my machine and I got 0.1 FPS

terhechte commented 2 years ago

@afonsolage I did try a release build before I submitted here, yes.

Good to know that debug printing will slow down the FPS. This is really the first time I'm using a game engine to do 3d stuff, so I wasn't sure about the limitations. I did try to recreate the scene in Godot but I gave up quickly because I found everything a tad unintuitive. The 1280 x 1280 was mostly for testing the limits, but even with 128 x 128 I had problems. However spawning planes instead of cubes does speed this up quite a bit. I'll try some more. Thanks!

afonsolage commented 2 years ago

Since it seems that it isn't a bug on Bevy, the issue will likely to be closed, but you may consider joining the Discord, so we may help you out with what you are trying to achieve.

Also, you can use the discussion tab on GitHub, that way, other users with experience in 3D rendering may help you also.

terhechte commented 2 years ago

Will do, thanks