bevyengine / bevy

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

Resizing 2D camera's viewport drops FPS and crashes (with HDR and bloom enabled) #16182

Open bloopyboop opened 2 days ago

bloopyboop commented 2 days ago

Bevy version

0.14.2, 0.15.0-dev (rev #4656698), 0.15.0-rc.2

All of them are affected.

Relevant system information

What you did

Added a system which animates the viewport's size on a 2D camera which has HDR enabled and a Bloom component with intensity > 0 (BloomSettings component instead for 0.14.2).

What went wrong

Caused by: In Device::create_texture note: label = bloom_texture Dimension X value 35170 exceeds the limit of 32768



## Additional information

Click [here](https://github.com/bloopyboop/bevy_camera_bug) for a minimal repo that demonstrates the bug. There is also additional info and steps to reproduce in the README.

EDIT: Added details to "What went wrong" section.
EDIT2: Tested bevy versions 0.15.0-dev and 0.15.0-rc.2, too. Bug occurs there, too. Linked repo has branches for each bevy version for reproduction.
bloopyboop commented 14 hours ago

I have added branches to the linked repo which test bevy versions 0.15.0-dev and 0.15.0-rc.2 as well. I have found the bug happens on all of them. I have edited the OP to reflect this. Please re-triage.

BenjaminBrienen commented 12 hours ago

Thanks for the deep dive

bloopyboop commented 12 hours ago

I have found the likely source of the bug.

In bloom/mod.rs:

fn prepare_bloom_textures(
    <snip>
    views: Query<(Entity, &ExtractedCamera, &Bloom)>,
) {
    for (entity, camera, bloom) in &views {
        if let Some(UVec2 {
            x: width,
            y: height,
        }) = camera.physical_viewport_size
        {
            // How many times we can halve the resolution minus one so we don't go unnecessarily low
            let mip_count = bloom.max_mip_dimension.ilog2().max(2) - 1;
            let mip_height_ratio = if height != 0 {
                bloom.max_mip_dimension as f32 / height as f32
            } else {
                0.
            };

            let texture_descriptor = TextureDescriptor {
                label: Some("bloom_texture"),
                size: Extent3d {
                    width: ((width as f32 * mip_height_ratio).round() as u32).max(1),
                    height: ((height as f32 * mip_height_ratio).round() as u32).max(1),
                    depth_or_array_layers: 1,
                },
                mip_level_count: mip_count,

                <snip>
            };

            <snip>
        }
    }
}

A new texture is created and cached for each different viewport size (caching is not seen in the code snippet, happens elsewhere). This leads to the FPS drops. If you set the Bloom component's max_mip_dimension to 1 or another low value, the bug disappears (both FPS drops and crash). This snippet shows that's because the size of the created bloom texture depends on max_mip_dimension. Of course, the consequence of setting it low is the bloom looking unusably bad.

In fact, the bloom looks unusably bad for any viewports with small heights, even with the default setting for max_mip_dimension. Additionally, this explains why FPS are dropping in Release builds only for one axis: Both width and height in the Extent3d are based on mip_height_ratio.

The crash occurs, because wgpu_core seems to enforce a size limit on textures (32768 for me). The width and height calculation in the Extent3d can easily go way beyond that size limit. This is exactly what happens when the viewport is very long and thin.