Skip to content

bevy_render: reconfigure surface on Suboptimal present#24463

Open
rubeniskov wants to merge 1 commit into
bevyengine:mainfrom
azocraft:0.18-suboptimal-reconfigure
Open

bevy_render: reconfigure surface on Suboptimal present#24463
rubeniskov wants to merge 1 commit into
bevyengine:mainfrom
azocraft:0.18-suboptimal-reconfigure

Conversation

@rubeniskov
Copy link
Copy Markdown

Bevy 0.18's prepare_windows ignores SurfaceTexture::suboptimal, so when the swapchain reports VK_SUBOPTIMAL_KHR (compositor scale / output reconfig on desktop Linux is the common trigger) wgpu_hal logs

WARN present_frames: wgpu_hal::vulkan: Suboptimal present of frame N

on every present until something flips size_changed and forces a configure_surface. Mirror the SurfaceError::Outdated branch: drop the suboptimal frame, reconfigure, re-acquire.

Objective

  • prepare_windows in crates/bevy_render/src/view/window/mod.rs treats wgpu::CurrentSurfaceTexture::Suboptimal exactly like Success: it hands the texture to the renderer and moves on. The swapchain keeps presenting, but it no longer matches the surface (compositor scale change, output reconfiguration, monitor hot-plug on desktop Linux).
  • Until something else flips size_changed and triggers configure_surface, wgpu_hal logs one WARN present_frames: wgpu_hal::vulkan: Suboptimal present of frame N per frame. On a 144 Hz display that's ~144 spurious warnings per second filling the log.
  • The SurfaceError::Outdated branch already does the right thing for the analogous "swapchain is stale" case — Suboptimal should mirror it.

Solution

  • Split Suboptimal out of the combined Success | Suboptimal arm in prepare_windows and mirror the existing Outdated recovery path:
    1. drop(surface_texture) to release the suboptimal frame.
    2. render_device.configure_surface(surface, &surface_data.configuration) so the swapchain re-matches the surface.
    3. Re-acquire via get_current_texture(). If the second acquire still fails, warn!(...) and continue (same pattern as Outdated).
wgpu::CurrentSurfaceTexture::Suboptimal(surface_texture) => {
    // Suboptimal: the swapchain still presents but no longer matches the
    // surface (compositor scale / output reconfig on desktop Linux is the
    // common trigger). wgpu_hal logs a WARN every present until we
    // reconfigure, so mirror the Outdated path: drop the frame,
    // reconfigure, re-acquire.
    drop(surface_texture);
    render_device.configure_surface(surface, &surface_data.configuration);
    let frame = match surface.get_current_texture() {
        wgpu::CurrentSurfaceTexture::Success(surface_texture)
        | wgpu::CurrentSurfaceTexture::Suboptimal(surface_texture) => surface_texture,
        variant => {
            warn!(
                "Couldn't get swap chain texture after suboptimal reconfigure. Cause: '{variant:?}'"
            );
            continue;
        }
    };
    window.set_swapchain_texture(frame);
}
  • 21 insertions, 2 deletions in a single file. No trait, API or schedule changes — purely a present-path recovery fix.

Testing

  • cargo check -p bevy_render is clean.
  • Manually reproduced and verified on Linux + Wayland (KDE Plasma 6, Vulkan backend, mesa-radeonsi). Before the patch: continuous WARN flood after a fractional-scale change. After the patch: a single WARN on the boundary frame, then silence.
  • Did not test on macOS / iOS / Windows / WebGPU. The affected variant is the same enum used cross-platform, but Suboptimal is in practice a Vulkan-on-Linux event, so the change is a no-op on platforms that never report it.
  • No .stderr or unit-test snapshots reference this code path, so nothing else needs updating.
  • Reproduction steps for reviewers (Linux + Wayland is the most reliable; X11/NVIDIA also reproducible):
    1. Create a trivial Bevy app:
      # Cargo.toml
      [package]
      name = "suboptimal_repro"
      version = "0.0.0"
      edition = "2024"
      
      [dependencies]
      bevy = { path = "../bevy" }
      // src/main.rs
      use bevy::prelude::*;
      
      fn main() {
          App::new().add_plugins(DefaultPlugins).run();
      }
    2. RUST_LOG=warn cargo run --release.
    3. Once the window is up, trigger a compositor change that marks the swapchain suboptimal without firing a Bevy resize event — pick one:
      • KDE Plasma / Wayland: System Settings → Display → Scale, bump fractional scale (100 % → 125 %) on the output the window is on.
      • GNOME / Wayland: Settings → Displays → Scale, toggle 100 % to 125 % and apply.
      • Multi-monitor: drag the window from a 100 %-scale output to a 125 %- or 200 %-scale output.
      • Hot-plug: plug or unplug an external display while the window is on the primary.
    4. Without the patch: the terminal floods with one WARN per frame, indefinitely. With the patch: at most a handful of WARNs around the moment of the compositor change, then silence.

Showcase

Click to view showcase

Before / after with RUST_LOG=warn cargo run --release on the minimal repro app above, performing a single fractional-scale change at ~3 s:

# Before
[ 3.012 s] WARN present_frames: wgpu_hal::vulkan: Suboptimal present of frame 432
[ 3.019 s] WARN present_frames: wgpu_hal::vulkan: Suboptimal present of frame 433
[ 3.026 s] WARN present_frames: wgpu_hal::vulkan: Suboptimal present of frame 434
... (continues until window closed)

# After
[ 3.012 s] WARN present_frames: wgpu_hal::vulkan: Suboptimal present of frame 432
(silent)

Bevy 0.18's prepare_windows ignores SurfaceTexture::suboptimal, so when
the swapchain reports VK_SUBOPTIMAL_KHR (compositor scale / output
reconfig on desktop Linux is the common trigger) wgpu_hal logs

    WARN present_frames: wgpu_hal::vulkan: Suboptimal present of frame N

on every present until something flips size_changed and forces a
configure_surface. Mirror the SurfaceError::Outdated branch: drop the
suboptimal frame, reconfigure, re-acquire.
@github-actions
Copy link
Copy Markdown
Contributor

Welcome, new contributor!

Please make sure you've read our contributing guide, as well as our policy regarding AI usage, and we look forward to reviewing your pull request shortly ✨

Copy link
Copy Markdown
Contributor

@IceSentry IceSentry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like the shared parts could be extracted to a function but not sure if it's worth it.

@IceSentry IceSentry added A-Rendering Drawing game state to the screen D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels May 27, 2026
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in Rendering May 27, 2026
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 D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Needs-Review Needs reviewer attention (from anyone!) to move forward

Projects

Status: Needs SME Triage

Development

Successfully merging this pull request may close these issues.

2 participants