diff --git a/Cargo.lock b/Cargo.lock index 5a4157bf..b330931b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1270,6 +1270,11 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "dpi" +version = "0.1.1" +source = "git+https://github.com/rust-windowing/winit?rev=39c4009c41e5977d779cf8a365792c8153ca77c5#39c4009c41e5977d779cf8a365792c8153ca77c5" + [[package]] name = "dpi" version = "0.1.2" @@ -2696,7 +2701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6744ab645ebe79f9c428455c7cb9dfa4275fa3b50a25408e08f927da8dc618c" dependencies = [ "crossbeam-channel", - "dpi", + "dpi 0.1.2", "keyboard-types", "objc2 0.6.4", "objc2-app-kit 0.3.2", @@ -5565,8 +5570,7 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winit" version = "0.30.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +source = "git+https://github.com/rust-windowing/winit?rev=39c4009c41e5977d779cf8a365792c8153ca77c5#39c4009c41e5977d779cf8a365792c8153ca77c5" dependencies = [ "ahash", "android-activity", @@ -5580,7 +5584,7 @@ dependencies = [ "core-foundation", "core-graphics", "cursor-icon", - "dpi", + "dpi 0.1.1", "js-sys", "libc", "memmap2", diff --git a/Cargo.toml b/Cargo.toml index 45fe8f62..eeaf809d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,3 +102,13 @@ opt-level = 3 lto = "thin" codegen-units = 16 debug = 0 + +# Windows 11: winit 0.30.13 resizes a window wrongly while it is dragged across monitors with +# different display scales (rust-windowing/winit#4041, #4600). It flips between the two scales, +# grows by the scale ratio (3600×2250 → 9048×5757 between 250 % and 100 %) and outgrows the GPU +# surface. Pinned to the one commit of rust-windowing/winit#4694, the 0.30.x backport of the fix +# winit master has had since 488c036a (#4341): during a live drag it applies the rect Windows +# suggests (+21/−34 lines in src/platform_impl/windows/event_loop.rs on top of v0.30.13). Remove +# once eframe depends on a winit release with the fix. +[patch.crates-io] +winit = { git = "https://github.com/rust-windowing/winit", rev = "39c4009c41e5977d779cf8a365792c8153ca77c5" } diff --git a/ROADMAP.md b/ROADMAP.md index c9da5d25..2ec5ab02 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -153,6 +153,7 @@ Newest first. - **2026-10-12** · Anchored objects with text wrap push text aside: other stories' text under them and the lines of their own story after the anchor's line (#557). No readiness percentage change. - **2026-10-12** · Hyphenation Zone applies to the Single-line Composer only, as in InDesign. With the default Paragraph Composer, ragged text hyphenates where InDesign does (aper-natem), giving the same line breaks. No readiness percentage change. - **2026-10-12** · Orderly native exit writes the latest recovery state after finalizing current quit approvals; incomplete or stale plans keep their documents, and Undo to the saved state clears stale recovery. Unavailable or failed recovery is reported. Cancelable macOS Dock Quit remains open (#544). No readiness percentage change. +- **2026-10-12** · Windows: dragging the window across monitors with different display scales keeps its size instead of growing until it crashes (winit pinned to its 0.30.x backport of the fix). The GPU surface may be as large as the adapter's largest texture (16384 px on DirectX 12, Vulkan and Metal hardware) instead of 8192, and a wgpu error is logged instead of closing the app. No readiness percentage change. - **2026-10-12** · Recovered documents keep a checkpoint until saved, closed or reverted, including a second crash before autosave. Restarted document IDs and explicit recovery folders preserve other snapshots; failed republishing retains the original. No readiness percentage change. - **2026-10-12** · Book numbering and style updates prepare every chapter before saving. Only closed native chapters are changed; imported IDML remains a read-only style/PDF source. If a later disk write fails, the error names the completed chapters; the batch is not a whole-book transaction. No readiness percentage change. - **2026-10-12** · Desktop write callbacks and CLI raster exports use the atomic writer too; open readers retain the original bytes, while private Unix modes and symlink targets survive replacement. Permanent tests cover both actual callers. No readiness percentage change. diff --git a/apps/designcraft/src/gpu_limits.rs b/apps/designcraft/src/gpu_limits.rs new file mode 100644 index 00000000..5615e565 --- /dev/null +++ b/apps/designcraft/src/gpu_limits.rs @@ -0,0 +1,84 @@ +//! What the graphics device may do with a window that outgrows it, on every native platform. +//! +//! The window's surface is a texture, so its sides can't exceed the device's largest texture side, +//! and eframe asks for exactly 8192 whatever the GPU can do. A window across monitors that add up +//! to more (3440 + 3840 + 1080 pixels side by side), or one a mixed-DPI drag on Windows scaled up +//! (9048×5757 between 250 % and 100 % monitors), then failed `Surface::configure`, and wgpu's +//! default error handler panics: the app closed with the documents in it. DirectX 12, Vulkan and +//! Metal hardware offer 16384. + +use eframe::{ + NativeOptions, + egui_wgpu::WgpuSetup, + wgpu::{Device, Limits}, +}; +use std::sync::{Arc, Mutex, PoisonError}; + +/// `required` with the largest texture side the adapter's `supported` limits offer. Taking the +/// adapter's own value also never asks a smaller (downlevel) adapter for more than it has. +pub fn surface_texture_limits(mut required: Limits, supported: &Limits) -> Limits { + required.max_texture_dimension_2d = supported.max_texture_dimension_2d; + required +} + +/// Makes the device eframe creates ask for the adapter's largest texture side +/// ([`surface_texture_limits`]), keeping eframe's other device requirements. +pub fn request_adapter_texture_size(options: &mut NativeOptions) { + if let WgpuSetup::CreateNew(create) = &mut options.wgpu_options.wgpu_setup { + let device_descriptor = Arc::clone(&create.device_descriptor); + create.device_descriptor = Arc::new(move |adapter| { + let mut descriptor = device_descriptor(adapter); + descriptor.required_limits = surface_texture_limits(descriptor.required_limits, &adapter.limits()); + descriptor + }); + } +} + +/// Logs wgpu errors no error scope caught instead of panicking. A window larger than even the +/// adapter's texture side then keeps its last surface (wgpu validates a configuration before +/// replacing the old one) and draws wrongly until it shrinks, instead of closing the app. +pub fn log_uncaptured_errors(device: &Device) { + // A surface that can't be configured fails again on every frame: log each message once in a row. + let last = Mutex::new(String::new()); + device.on_uncaptured_error(Arc::new(move |error| { + let message = error.to_string(); + let mut last = last.lock().unwrap_or_else(PoisonError::into_inner); + if *last != message { + log::error!("graphics: {message}"); + *last = message; + } + })); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn surface_texture_size_is_the_adapters_own() { + // A mixed-DPI drag's 9048×5757 needs more than eframe's 8192; a downlevel adapter must not + // be asked for more than it has. + for base in [Limits::default(), Limits::downlevel_webgl2_defaults()] { + let eframe_request = Limits { max_texture_dimension_2d: 8192, ..base.clone() }; + for side in [4096, 8192, 16384, 32768] { + let supported = Limits { max_texture_dimension_2d: side, ..base.clone() }; + let limits = surface_texture_limits(eframe_request.clone(), &supported); + assert_eq!(limits.max_texture_dimension_2d, side); + // The drag's 9048×5757 surface fits from 16384 up. + assert_eq!(side >= 16384, 9048 <= limits.max_texture_dimension_2d); + // Everything else eframe asks for is unchanged. + assert_eq!(Limits { max_texture_dimension_2d: 8192, ..limits.clone() }, eframe_request); + assert!(limits.check_limits(&supported)); + } + } + } + + #[test] + fn native_options_keep_creating_their_own_device() { + // The wrapper only replaces the device descriptor: eframe still creates the instance, + // adapter and device, and main.rs can still choose the backends. + let mut options = NativeOptions::default(); + request_adapter_texture_size(&mut options); + assert!(matches!(options.wgpu_options.wgpu_setup, WgpuSetup::CreateNew(_))); + } +} diff --git a/apps/designcraft/src/main.rs b/apps/designcraft/src/main.rs index c6d8648b..56e51ddc 100644 --- a/apps/designcraft/src/main.rs +++ b/apps/designcraft/src/main.rs @@ -17,6 +17,7 @@ mod apple_events; mod control_server; mod gpu; +mod gpu_limits; mod logging; #[cfg(target_os = "macos")] mod mac_window; @@ -382,6 +383,9 @@ fn main() -> std::process::ExitCode { if let Some(icon) = app_icon() { options.viewport = options.viewport.with_icon(icon); } + // The adapter's largest texture side instead of eframe's 8192, so a window across several + // monitors keeps its surface (see `gpu_limits`). + gpu_limits::request_adapter_texture_size(&mut options); // The graphics backend, decided before the window exists: a driver that faults takes the // process down before any Rust code can catch it (see `gpu::backend`). let fallback = gpu::backend::Fallback::begin( @@ -439,6 +443,7 @@ fn main() -> std::process::ExitCode { Box::new(move |cc| { let mut restarted = None; if let Some(rs) = &cc.wgpu_render_state { + gpu_limits::log_uncaptured_errors(&rs.device); // What a bug report needs to know about the graphics driver. let info = rs.adapter.get_info(); log::info!( diff --git a/docs/hardware-parity.md b/docs/hardware-parity.md index 8bee7331..9c4bb17e 100644 --- a/docs/hardware-parity.md +++ b/docs/hardware-parity.md @@ -1,6 +1,6 @@ # Hardware and platform parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (font managers on macOS; shared print spooler; Windows CI passes) · **Target:** Adobe InDesign 2026 (21.6) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (font managers on macOS; shared print spooler; Windows CI passes; Windows mixed-DPI drag and windows wider than 8192 px don't crash) · **Target:** Adobe InDesign 2026 (21.6) What InDesign does with hardware, per platform, against DesignCraft. Layout is not a GPU-hungry workload, so this dimension carries little weight, but a start that fails on a @@ -17,9 +17,9 @@ graphics driver loses the user entirely. Overall numbers: [target-app-parity.md] | Backend fallback | n/a | `gpu.json` skips a backend whose start never showed a frame (#297) | same | same | | | Adapter choice and restart | n/a | power saving; a GPU that fails or hangs at start-up is left out and the app starts again on the other one; indirect-call check off (#334) | the GPU that drives the primary display (`EnumDisplayDevices`); DirectX 12 adapters, then OpenGL, then Vulkan | the GPU that drives the panel or a monitor (sysfs), then the system's order | | | Animated / scrubby zoom | ✓ (GPU) | ✓ (CPU tiles, damage regions) | ✓ | ✓ | ✓ | -| HiDPI / Retina | ✓ | ✓ | ✓ | ✓ | ✓ | +| HiDPI / Retina | ✓ | ✓ | ✓ (drags across monitors with different scales keep the window's size: winit pinned to the rust-windowing/winit#4694 backport) | ✓ | ✓ | | UI scaling | ✓ | ✓ (menus and dialogs usable at larger scales, #281) | ✓ | ✓ | ✓ | -| Multi-monitor, floating document windows | ✓ | ~ one window; New Window for the same document | ~ | ~ | ✗ | +| Multi-monitor, floating document windows | ✓ | ~ one window; New Window for the same document; the window may span monitors up to the GPU's largest texture (16384 px on DirectX 12, Vulkan and Metal hardware), and beyond it draws wrongly instead of closing | ~ | ~ | ✗ | | Printing | system print with PPDs, PostScript, separations | PDF to CUPS (`craft-print`) | Windows PDF raster spooler (`craft-print`) | PDF to CUPS (`craft-print`) | browser download | | Colour-managed display (monitor profile) | ✓ | ✗ (working CMYK profile simulated; monitor profile not applied) | ✗ | ✗ | ✗ | | Touch / pen | Touch workspace (Windows), gestures | trackpad pinch/scroll | ~ | ~ | ~ | @@ -54,6 +54,7 @@ the developers don't use daily. | Date | Change | Summary | |---|---|---| +| 2026-10-12 | trivial | Windows: dragging across monitors with different display scales doesn't grow the window and crash (winit pinned to the rust-windowing/winit#4694 backport); a window wider or taller than 8192 px keeps its surface, and a wgpu error logs instead of closing the app | | 2026-10-10 | major | Created from the code, InDesign 2026's GPU Performance features and the issue tracker | Windows submission now uses shared craft-print. It selects matching or default driver paper,