bevyengine / bevyengine/bevy

# `prepare_windows` panics on transient swap chain error, crashes with cascade of GPU resource failures

Open
#23,221 0 comments 0 reactions 0 assignees View on GitHub
C-Bug I-Crash S-Needs-Investigation
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 22h
Merged PRs (30d)
161

Description

## Bevy version and features

0.18.1, default features + `bevy_camera_controller` + `free_camera`.

## Relevant system information

- Rust: 1.93.1
- OS: Windows 11 Home China 10.0.26200
- `AdapterInfo { name: "NVIDIA GeForce RTX 5060 Laptop GPU", vendor: 4318, device: 11609, device_type: DiscreteGpu, driver: "NVIDIA", driver_info: "576.65", backend: Vulkan }`

## What you did

Run this app repeatedly. Most launches work fine. Occasionally (non-deterministic — sometimes it fails on two or even three launches in a row, sometimes not after 30 tries) it panics at startup.Note: this is not a minimal reproduction — the trigger frequency seemed to decrease as I simplified the scene, so I stopped reducing and kept the original code that reliably exhibits the issue.

```rust
use bevy::{
anti_alias::taa::TemporalAntiAliasing,
camera::Exposure,
camera_controller::free_camera::{FreeCamera, FreeCameraPlugin},
core_pipeline::{
prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass},
tonemapping::Tonemapping,
},
light::{AtmosphereEnvironmentMapLight, VolumetricFog, VolumetricLight},
pbr::{
Atmosphere, DefaultOpaqueRendererMethod, ScatteringMedium,
ScreenSpaceAmbientOcclusion, ScreenSpaceAmbientOcclusionQualityLevel,
ScreenSpaceReflections,
},
post_process::bloom::{Bloom, BloomCompositeMode, BloomPrefilter},
prelude::*,
render::view::{ColorGrading, Hdr},
};

fn main() {
App::new()
.insert_resource(DefaultOpaqueRendererMethod::deferred())
.insert_resource(ClearColor(Color::BLACK))
.insert_resource(GlobalAmbientLight::NONE)
.add_plugins(DefaultPlugins)
.add_plugins(FreeCameraPlugin)
.add_systems(Startup, setup)
.run();
}

fn setup(
mut commands: Commands,
mut meshes: ResMut>,
mut materials: ResMut>,
mut scattering_mediums: ResMut>,
) {
commands.spawn((
Mesh3d(meshes.add(Cuboid::default())),
MeshMaterial3d(materials.add(Color::srgb(0.4, 0.8, 0.3))),
Transform::from_xyz(0.0, 0.5, 0.0),
));
commands.spawn((
Mesh3d(meshes.add(Plane3d::default().mesh().size(5.0, 5.0))),
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
));
commands.spawn((
DirectionalLight {
illuminance: light_consts::lux::RAW_SUNLIGHT,
shadows_enabled: true,
..default()
},
Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -std::f32::consts::FRAC_PI_4)),
VolumetricLight,
));
commands
.spawn((
Camera3d::default(),
Transform::from_translation(Vec3::new(-2.0, 2.5, 5.0)).looking_at(Vec3::ZERO, Vec3::Y),
FreeCamera::default(),
Atmosphere::earthlike(scattering_mediums.add(ScatteringMedium::default())),
AtmosphereEnvironmentMapLight::default(),
Exposure { ev100: 13.0 },
ColorGrading::default(),
Hdr,
Msaa::Off,
DepthPrepass,
MotionVectorPrepass,
DeferredPrepass,
Tonemapping::TonyMcMapface,
))
.insert((
Bloom {
intensity: 0.12,
low_frequency_boost: 0.7,
low_frequency_boost_curvature: 0.95,
high_pass_frequency: 1.0,
prefilter: BloomPrefilter {
threshold: 1.0,
threshold_softness: 0.3,
},
composite_mode: BloomCompositeMode::Additive,
..Bloom::NATURAL
},
ScreenSpaceAmbientOcclusion {
quality_level: ScreenSpaceAmbientOcclusionQualityLevel::Custom {
slice_count: 2,
samples_per_slice_side: 3,
},
constant_object_thickness: 0.25,
},
TemporalAntiAliasing::default(),
ScreenSpaceReflections::default(),
VolumetricFog {
ambient_intensity: 0.0,
..default()
},
));
}
```

## What went wrong

Expected: the app starts normally every time.

Actual: sporadic panic at startup:

```
thread 'Compute Task Pool (9)' panicked at bevy_render/src/view/window/mod.rs:304:17:
Couldn't get swap chain texture, operation unrecoverable: Acquiring a texture failed with a generic error. Check error callbacks for more information
```

Followed by a cascade of 10+ panics across Compute Task Pool threads:

```
thread 'main' panicked at bevy_render/src/render_resource/uniform_buffer.rs:312:18:
called `Option::unwrap()` on a `None` value

thread 'Compute Task Pool (19)' panicked at wgpu/src/backend/wgpu_core.rs:2169:18:
Error in Buffer::get_mapped_range: Validation Error
Caused by: Buffer with 'thickness_buffer' label is invalid
```

(Full trace trimmed — dozens of similar panics follow for various buffers and textures.)

## Additional information

- The failure is **non-deterministic**, pointing to a race condition rather than a resource limit (the "Too many textures" warning appears on every launch, including successful ones).
- The initial trigger is `surface.get_current_texture()` returning a "generic error" in `prepare_windows`.
- Once the first panic occurs, the GPU device enters a permanent error state, causing all subsequent GPU operations to fail — hence the cascade.
- Setting `WGPU_BACKEND=dx12` completely eliminates the issue (tested 60 consecutive launches with zero failures), i guess this is **Vulkan-specific**.
- The catch-all handler at `view/window/mod.rs:303` panics unconditionally, unlike `SurfaceError::Outdated` which already has graceful recovery (reconfigure + retry + skip frame).
- I attempted patching `prepare_windows` to handle this error the same way as `Outdated` (reconfigure + skip frame) and adding `run_if` conditions to skip `Queue`/`Prepare`/`Render` sets. The cascading panics were eliminated, but the device never recovered — the app entered an infinite skip-frame loop. A complete fix would likely require device loss detection and render context recreation.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.