Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Renderer Optimization Round 1 #958

Merged
merged 14 commits into from
Dec 1, 2020
Merged

Conversation

cart
Copy link
Member

@cart cart commented Nov 30, 2020

Its time to start making the renderer fast! This first round is just picking a lot of low hanging fruit.

I ran my tests using the Bevymark example with 10,000 entities.

Starting Performance

// these numbers apply to both moving and non-moving sprites
frame_time    : 0.038522    (avg 0.038975)
fps           : 26.670384   (avg 26.018069)

This PR

// when sprites are static / don't move
frame_time    : 0.017887    (avg 0.017899)
fps           : 55.642482   (avg 55.782829)
// when sprites are moving
frame_time    : 0.023547    (avg 0.022696)
fps           : 41.949551   (avg 42.599443)

Summary of changes

(applied in order, so each item's numbers includes the previous item's changes)

  • Added change detection to RenderResourceNode, Sprites, and Transforms, which improved performance when those values don't change. (static: 0.0267, dynamic: 0.0336)
  • Mesh provider system now only updates mesh specialization when it needs to (static: 0.0250, dynamic: 0.0331)
  • Stop clearing bind groups every frame and remove stale bind groups every other frame. (static: 0.0265)
    • this is slightly more expensive on bevymark, but i think its probably still the right thing to do. ill keep an eye on this.
  • Store unmatched render resource binding results (which prevents redundant computations per-entity per-frame) (static: 0.02411)
  • Don't send render pass state change commands when the state has not actually changed (static: 0.0179, dynamic: 0.0227)

@cart cart added C-Feature A new feature, making something new possible A-Rendering Drawing game state to the screen labels Nov 30, 2020
@cart
Copy link
Member Author

cart commented Nov 30, 2020

Looks like the load_gltf example isn't displaying anything

@cart
Copy link
Member Author

cart commented Nov 30, 2020

Actually that seems unrelated. I'm also hitting the gltf issue on the master branch.

@alec-deason
Copy link
Member

I'm seeing a problem using this branch. I see a panic on this unwrap https://github.com/cart/bevy/blob/render-opt-round1/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs#L489

The issue seems to be that one part of the infrastructure has a BufferId that has been invalidated because the buffer it references has been resized here: https://github.com/cart/bevy/blob/render-opt-round1/crates/bevy_render/src/render_graph/nodes/render_resources_node.rs#L454

I think this call to write_uniform_buffers should be fixing the BufferId handle that's been removed but it's now behind a Changed filter that didn't used to be there and that is preventing it from getting called: https://github.com/cart/bevy/blob/render-opt-round1/crates/bevy_render/src/render_graph/nodes/render_resources_node.rs#L468

I'm not sure what the relationship is between the RenderResources component that controls the Changed filter and the arrays that are getting resized. But I assume that whatever change it is that causes the array to resize isn't getting caught by the change tracker. If I remove the Changed filter from the query then the panic goes away for me.

@alec-deason
Copy link
Member

I'm also seeing this panic, which I think is related to this branch as I've never noticed it before but I'm not sure what exactly I'm doing to trigger it. It appears intermittently.

thread 'main' panicked at 'current render pipeline has a layout which is incompatible with a currently set bind group, first differing at entry index 1', /home/alec/.cargo/registry/src/gh.neting.cc-1ecc6299db9ec823/
wgpu-0.6.2/src/backend/direct.rs:1355:35
stack backtrace:
   0: rust_begin_unwind
             at /rustc/1773f60ea5d42e86b8fdf78d2fc5221ead222bc1/library/std/src/panicking.rs:495:5
   1: std::panicking::begin_panic_fmt
             at /rustc/1773f60ea5d42e86b8fdf78d2fc5221ead222bc1/library/std/src/panicking.rs:437:5
   2: <core::result::Result<T,E> as wgpu::backend::direct::PrettyResult<T>>::unwrap_pretty::{{closure}}
             at /home/alec/.cargo/registry/src/gh.neting.cc-1ecc6299db9ec823/wgpu-0.6.2/src/backend/direct.rs:1355:35
   3: core::result::Result<T,E>::unwrap_or_else
             at /home/alec/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/result.rs:825:23
   4: <core::result::Result<T,E> as wgpu::backend::direct::PrettyResult<T>>::unwrap_pretty
             at /home/alec/.cargo/registry/src/gh.neting.cc-1ecc6299db9ec823/wgpu-0.6.2/src/backend/direct.rs:1355:9
   5: <wgpu::backend::direct::Context as wgpu::Context>::command_encoder_end_render_pass
             at /home/alec/.cargo/registry/src/gh.neting.cc-1ecc6299db9ec823/wgpu-0.6.2/src/backend/direct.rs:1253:9
   6: <wgpu::RenderPass as core::ops::drop::Drop>::drop
             at /home/alec/.cargo/registry/src/gh.neting.cc-1ecc6299db9ec823/wgpu-0.6.2/src/lib.rs:2280:13
   7: core::ptr::drop_in_place
             at /home/alec/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:175:1
   8: core::ptr::drop_in_place
             at /home/alec/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:175:1
   9: <bevy_wgpu::renderer::wgpu_render_context::WgpuRenderContext as bevy_render::renderer::render_context::RenderContext>::begin_pass
             at /home/alec/Code/Vendor/bevy/crates/bevy_wgpu/src/renderer/wgpu_render_context.rs:152:9
  10: <bevy_render::render_graph::nodes::pass_node::PassNode<Q> as bevy_render::render_graph::node::Node>::update
             at /home/alec/Code/Vendor/bevy/crates/bevy_render/src/render_graph/nodes/pass_node.rs:207:9
  11: bevy_wgpu::renderer::wgpu_render_graph_executor::WgpuRenderGraphExecutor::execute
             at /home/alec/Code/Vendor/bevy/crates/bevy_wgpu/src/renderer/wgpu_render_graph_executor.rs:73:25
  12: bevy_wgpu::wgpu_renderer::WgpuRenderer::run_graph
             at /home/alec/Code/Vendor/bevy/crates/bevy_wgpu/src/wgpu_renderer.rs:103:9
  13: bevy_wgpu::wgpu_renderer::WgpuRenderer::update
             at /home/alec/Code/Vendor/bevy/crates/bevy_wgpu/src/wgpu_renderer.rs:114:9
  14: bevy_wgpu::get_wgpu_render_system::{{closure}}
             at /home/alec/Code/Vendor/bevy/crates/bevy_wgpu/src/lib.rs:39:9

@mockersf
Copy link
Member

I'm also seeing the same panic as above randomly

thread 'main' panicked at 'current render pipeline has a layout which is incompatible with a currently set bind group, first differing at entry index 2', /Users/francois/.cargo/registry/src/gh.neting.cc-1ecc6299db9ec823/wgpu-0.6.2/src/backend/direct.rs:1355:35

most of the time with at entry index 1, got a few times at entry index 2...

Also, I'm having a missing sprite sometimes but always the same, will try to reduce to something minimal

@mockersf
Copy link
Member

this will not display the sprite, even though it's logged:

use bevy::prelude::*;

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup)
        .add_system(display)
        .run();
}

fn setup(commands: &mut Commands) {
    commands.spawn(Camera2dBundle::default());
}

fn display(
    commands: &mut Commands,
    mut first: Local<bool>,
    asset_server: Res<AssetServer>,
    mut materials: ResMut<Assets<ColorMaterial>>,
) {
    if !*first {
        let texture_handle = asset_server.load("branding/icon.png");
        info!(
            "displaying sprite: {:?}",
            commands
                .spawn(SpriteBundle {
                    material: materials.add(texture_handle.into()),
                    ..Default::default()
                })
                .current_entity()
        );
        *first = true;
    }
}

If I put display as a startup system, the sprite is displayed. If I removed the check on first, the sprite is displayed (but a new sprite is created on each frame, hard to know which one is displayed...)

@mockersf
Copy link
Member

this will crash with thread 'main' panicked at 'current render pipeline has a layout which is incompatible with a currently set bind group, first differing at entry index 1', /Users/francois/.cargo/registry/src/gh.neting.cc-1ecc6299db9ec823/wgpu-0.6.2/src/backend/direct.rs:1355:35 at the second time spawning text

use bevy::prelude::*;

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup)
        .add_system(display)
        .run();
}

fn setup(commands: &mut Commands) {
    commands
        .spawn(UiCameraBundle::default())
        .with(Timer::from_seconds(1.0, true));
}

fn display(
    commands: &mut Commands,
    mut state: Local<bool>,
    time: Res<Time>,
    mut timers: Query<&mut Timer>,
    texts: Query<Entity, With<Text>>,
    asset_server: Res<AssetServer>,
) {
    for mut timer in timers.iter_mut() {
        timer.tick(time.delta_seconds());

        if timer.just_finished() {
            if *state {
                info!("removing all texts");
                for entity in texts.iter() {
                    commands.despawn_recursive(entity);
                }
            } else {
                info!("spawning texts");

                commands.spawn(TextBundle {
                    style: Style {
                        align_self: AlignSelf::FlexEnd,
                        ..Default::default()
                    },
                    text: Text {
                        value: "text".to_string(),
                        font: asset_server.load("fonts/FiraSans-Bold.ttf"),
                        style: TextStyle {
                            font_size: 60.0,
                            color: Color::WHITE,
                            ..Default::default()
                        },
                    },
                    ..Default::default()
                });
            }
            *state = !*state;
        }
    }
}

@alec-deason
Copy link
Member

@mockersf That example makes sense based on when I'm seeing the panic. I think in my app it only happens when I switch between a state with UI components and one without or vice versa. But it doesn't happen every time the state changes, maybe 50% of the time which makes me think it might be a timing issue.

@cart
Copy link
Member Author

cart commented Dec 1, 2020

A big thanks to everyone for the testing / investigation. I'm diving in now.

@cart
Copy link
Member Author

cart commented Dec 1, 2020

this will not display the sprite, even though it's logged

@mockersf this was a result of us now caching NoMatch bind group lookup results (which prevented future changes to RenderResourceBindings from producing a match). I fixed this in the latest commit. Inserting new bindings now invalidates non-matching results.

@cart
Copy link
Member Author

cart commented Dec 1, 2020

I'm also seeing this panic, which I think is related to this branch as I've never noticed it before but I'm not sure what exactly I'm doing to trigger it. It appears intermittently.

@alec-deason I can trigger it consistently in the bevymark example by spawning in new entities via clicks (as opposed to spawning them all at startup like i did for my benchmarking)

@cart
Copy link
Member Author

cart commented Dec 1, 2020

I'm also seeing this panic, which I think is related to this branch as I've never noticed it before but I'm not sure what exactly I'm doing to trigger it. It appears intermittently.

@alec-deason dropping this commit resolves this issue, so its definitely scoped to that change set.

image

Lol past @cart was right about it needing a quality check.

@mockersf your text add/remove example is not fixed by reverting that commit, so it comes from somewhere else

@cart
Copy link
Member Author

cart commented Dec 1, 2020

@mockersf the add/remove example panic comes from the "remove stale bind groups" commit. interesting

@cart
Copy link
Member Author

cart commented Dec 1, 2020

@mockersf The add/remove example was broken because RenderResourceBindings isn't made aware when RenderResourceContext removes a "stale" bind group. Therefore even if the BindGroupStatus is "unchanged", we still need to try creating the bind group in case it doesn't exist.

Performance-wise these are our options:

  • "broken" behavior: ~0.0162
  • revert the commit entirely: ~0.0180
  • the "fix": ~0.0173

We're leaving some performance on the table with the "fix", but its still an improvement over the previous behavior.

@cart
Copy link
Member Author

cart commented Dec 1, 2020

@mockersf @alec-deason

Alrighty I think I've fixed all of the problems! Let me know if everything works on your end

@cart cart force-pushed the render-opt-round1 branch from e194eaa to ea5f41f Compare December 1, 2020 06:26
@alec-deason
Copy link
Member

alec-deason commented Dec 1, 2020

Something is definitely wrong. It seems like bevy isn't getting the window size right, or maybe isn't handling window resizes correctly.

This is the button example running full screen:
image

My screenshot tool isn't capturing the cursor for some reason but in this image the cursor is in the middle of the screen, near the lower right hand corner of the gray box. That triggers the button's hover behavior and if you click there the button presses. I assume it's where the button should be.

Resizing the window doesn't seem to effect things the way it normally does, none of the UI resizes or adjusts its layout. I see similar results with other examples and with my application.

@alec-deason
Copy link
Member

Also I just noticed that the button example is using large amounts of CPU across all my cores, even though it's not doing anything or even visible on the screen right now.

@alec-deason
Copy link
Member

Never mind. Both problems happen on the current master so they aren't part of this branch.

@cart
Copy link
Member Author

cart commented Dec 1, 2020

Hmm the windowing issue is probably related to #947

@cart
Copy link
Member Author

cart commented Dec 1, 2020

And im planning on optimizing text rendering next, so hopefully that will resolve the button cpu usage issue

@alec-deason
Copy link
Member

Hmm the windowing issue is probably related to #947

Yeah, that's the commit that caused the regression. I just wrote up an issue: #967

@alec-deason
Copy link
Member

Ok, having discovered that #967 is an upstream issue I'm back to looking at this. All the issues I was having seem to be resolved. And everything is so much faster!

@mockersf
Copy link
Member

mockersf commented Dec 1, 2020

no more issues for me too 🎉

@cart
Copy link
Member Author

cart commented Dec 1, 2020

Score! Lets merge this.

@cart cart merged commit b5ffab7 into bevyengine:master Dec 1, 2020
@cart cart mentioned this pull request Dec 2, 2020
@fopsdev fopsdev mentioned this pull request Jan 24, 2021
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-Feature A new feature, making something new possible
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants