From 7d689fb6a220bb01b8259fecdcba6be5ccbcfdb9 Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Wed, 14 Oct 2026 19:47:04 -0400 Subject: [PATCH] UI: the CPU viewport image never exceeds the texture size limit A Retina window 2300 pixels wide drew its CPU viewport (the offscreen capture's path) into a 2300 x 1412 texture, past egui's 2048 limit, and the app aborted. The image is now scaled to fit the context's max_texture_side and drawn stretched, as an image over the renderer's own limit already was. Fixes #237. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo --- crates/ui-egui/src/viewport.rs | 35 ++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/viewport.rs b/crates/ui-egui/src/viewport.rs index dbcc5ab..b825ed3 100644 --- a/crates/ui-egui/src/viewport.rs +++ b/crates/ui-egui/src/viewport.rs @@ -312,8 +312,9 @@ fn paint_gpu(app: &mut ModelApp, ui: &egui::Ui, painter: &egui::Painter, rect: R /// changes. fn paint_cpu(app: &mut ModelApp, ui: &egui::Ui, painter: &egui::Painter, rect: Rect) { let ppp = ui.ctx().pixels_per_point(); - // A window larger than the renderer's limit is drawn smaller and scaled up. - let (w, h) = fieldcraft_render::fit_size((rect.width() * ppp) as usize, (rect.height() * ppp) as usize); + // A window larger than the renderer's limit, or than the largest texture the context takes, + // is drawn smaller and scaled up. + let (w, h) = cpu_size((rect.width() * ppp) as usize, (rect.height() * ppp) as usize, ui.ctx().input(|i| i.max_texture_side)); let size = [w, h]; let cam = app.cam; let (overlay_key, overlay) = app.overlay(); @@ -346,6 +347,19 @@ fn paint_cpu(app: &mut ModelApp, ui: &egui::Ui, painter: &egui::Painter, rect: R } } +/// The CPU viewport image's size for a viewport of `w` × `h` pixels: within the renderer's limits +/// and no side above `max_side`, the aspect kept. +fn cpu_size(w: usize, h: usize, max_side: usize) -> (usize, usize) { + let (w, h) = fieldcraft_render::fit_size(w, h); + let longest = w.max(h); + if max_side == 0 || longest <= max_side { + return (w, h); + } + let k = max_side as f64 / longest as f64; + let fit = |v: usize| ((v as f64 * k).floor() as usize).clamp(1, max_side); + (fit(w), fit(h)) +} + /// The scene's labels (reference point names) beside their points, with the interface's font. /// The CPU renderer draws them into its image. fn labels(app: &mut ModelApp, painter: &egui::Painter, t: &Tokens) { @@ -533,3 +547,20 @@ pub fn to_screen(app: &ModelApp, p: Vec3) -> Option { let (x, y, _) = app.cam.to_screen(p, f64::from(rect.width()), f64::from(rect.height()), app.scene.1.radius)?; Some(pos2(rect.left() + x as f32, rect.top() + y as f32)) } + +#[cfg(test)] +mod tests { + use super::cpu_size; + + /// The CPU viewport never asks for a texture larger than the context takes (#237: a Retina + /// window 2300 pixels wide on a 2048 limit). + #[test] + fn the_cpu_image_fits_the_texture_limit() { + assert_eq!(cpu_size(2300, 1412, 2048), (2048, 1257)); + assert_eq!(cpu_size(1412, 2300, 2048), (1257, 2048)); + assert_eq!(cpu_size(800, 600, 2048), (800, 600)); + assert_eq!(cpu_size(800, 600, 0), (800, 600)); + let (w, h) = cpu_size(100_000, 3, 2048); + assert!(w <= 2048 && h >= 1); + } +}