From 1e8cd93bc3e7a1edd83ea344ff644f023afbe065 Mon Sep 17 00:00:00 2001 From: xfy Date: Mon, 13 Jul 2026 10:00:42 +0800 Subject: [PATCH] fix: keep full-screen quad filled on window resize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The quad mesh was built once at startup with the initial aspect ratio, so widening the window afterward left empty edges. Add a FullscreenQuad marker component and a fit_quad_to_window system that rescales the quad's Transform to the live aspect each frame (only mutating on change). Also remove the misleading 'Render scale' slider — it isn't wired to a real sub-resolution render target in Phase 1 (the full-screen quad always renders at window resolution). Lowering Steps is the real perf lever. Documented in a code comment for future work. --- src/render/plugin.rs | 31 ++++++++++++++++++++++++++++--- src/ui.rs | 4 +++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/render/plugin.rs b/src/render/plugin.rs index 6fef76b..f5bb511 100644 --- a/src/render/plugin.rs +++ b/src/render/plugin.rs @@ -3,6 +3,11 @@ use bevy::sprite_render::Material2dPlugin; use super::material::BlackHoleMaterial; +/// Marks the full-screen quad so the resize system can find and rescale it +/// when the window's aspect ratio changes (the mesh is built once at startup). +#[derive(Component)] +struct FullscreenQuad; + pub struct BlackHolePlugin; impl Plugin for BlackHolePlugin { @@ -16,7 +21,7 @@ impl Plugin for BlackHolePlugin { .add_systems(Startup, crate::scene::planets::spawn_default_planet) .add_systems( Update, - (crate::camera::orbit_controller, mirror_params), + (crate::camera::orbit_controller, mirror_params, fit_quad_to_window), ) .add_systems(Update, crate::scene::planets::upload_planets) // bevy_egui 0.41 requires UI systems to run inside the egui context @@ -36,14 +41,34 @@ fn spawn_fullscreen_quad( Err(_) => return, }; let aspect = win.width() / win.height(); + // The mesh is a unit square (2x2 covers [-1,1]); we rescale via Transform + // in fit_quad_to_window whenever the aspect changes. commands.spawn(( - Mesh2d(meshes.add(Rectangle::new(2.0 * aspect, 2.0))), + Mesh2d(meshes.add(Rectangle::new(2.0, 2.0))), MeshMaterial2d(materials.add(BlackHoleMaterial::default())), - Transform::default(), + Transform::default().with_scale(Vec3::new(aspect, 1.0, 1.0)), + FullscreenQuad, )); commands.spawn(Camera2d); } +/// Rescale the full-screen quad so it always covers the camera's view, even +/// after the window is resized. Camera2d's default projection spans y∈[-1,1] +/// and x∈[-aspect, aspect]; scaling the unit quad by (aspect, 1, 1) fills it. +fn fit_quad_to_window(window: Query<&Window>, mut quad: Query<&mut Transform, With>) { + let win = match window.single() { + Ok(w) => w, + Err(_) => return, + }; + let aspect = win.width() / win.height(); + for mut transform in &mut quad { + // Only mutate on change to avoid spamming change detection. + if transform.scale.x != aspect { + transform.scale.x = aspect; + } + } +} + fn mirror_params( camera: Res, params: Res, diff --git a/src/ui.rs b/src/ui.rs index 24bba71..12706d3 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -35,7 +35,9 @@ pub fn ui_system( }); egui::CollapsingHeader::new("Renderer").show(ui, |ui| { ui.add(egui::Slider::new(&mut params.steps, 50..=600).text("Steps")); - ui.add(egui::Slider::new(&mut params.render_scale, 0.5..=1.0).text("Render scale")); + // NOTE: render_scale is intentionally not exposed — it isn't wired + // to a real sub-resolution render target in Phase 1 (the full-screen + // quad always renders at window resolution). Lower `Steps` to gain FPS. }); egui::CollapsingHeader::new("Background").show(ui, |ui| { ui.add(egui::Slider::new(&mut params.star_intensity, 0.0..=3.0).text("Star intensity"));