On windows, recreate renderer swap chain on restore from minimized (#21756)

Michael Sloan created

Closes #21688

Release Notes:

- Windows: Fix freeze after window minimize and maximize

Change summary

crates/gpui/src/platform/blade/blade_renderer.rs | 14 +++++++++++++-
crates/gpui/src/platform/windows/events.rs       | 15 +++++++++++++--
crates/gpui/src/platform/windows/window.rs       |  3 +++
3 files changed, 29 insertions(+), 3 deletions(-)

Detailed changes

crates/gpui/src/platform/blade/blade_renderer.rs 🔗

@@ -431,13 +431,25 @@ impl BladeRenderer {
     }
 
     pub fn update_drawable_size(&mut self, size: Size<DevicePixels>) {
+        self.update_drawable_size_impl(size, false);
+    }
+
+    /// Like `update_drawable_size` but skips the check that the size has changed. This is useful in
+    /// cases like restoring a window from minimization where the size is the same but the
+    /// renderer's swap chain needs to be recreated.
+    #[cfg_attr(any(target_os = "macos", target_os = "linux"), allow(dead_code))]
+    pub fn update_drawable_size_even_if_unchanged(&mut self, size: Size<DevicePixels>) {
+        self.update_drawable_size_impl(size, true);
+    }
+
+    fn update_drawable_size_impl(&mut self, size: Size<DevicePixels>, always_resize: bool) {
         let gpu_size = gpu::Extent {
             width: size.width.0 as u32,
             height: size.height.0 as u32,
             depth: 1,
         };
 
-        if gpu_size != self.surface_config.size {
+        if always_resize || gpu_size != self.surface_config.size {
             self.wait_for_gpu();
             self.surface_config.size = gpu_size;
             self.gpu.resize(self.surface_config);

crates/gpui/src/platform/windows/events.rs 🔗

@@ -141,16 +141,27 @@ fn handle_size_msg(
     lparam: LPARAM,
     state_ptr: Rc<WindowsWindowStatePtr>,
 ) -> Option<isize> {
+    let mut lock = state_ptr.state.borrow_mut();
+
+    // Don't resize the renderer when the window is minimized, but record that it was minimized so
+    // that on restore the swap chain can be recreated via `update_drawable_size_even_if_unchanged`.
     if wparam.0 == SIZE_MINIMIZED as usize {
+        lock.is_minimized = Some(true);
         return Some(0);
     }
+    let may_have_been_minimized = lock.is_minimized.unwrap_or(true);
+    lock.is_minimized = Some(false);
 
     let width = lparam.loword().max(1) as i32;
     let height = lparam.hiword().max(1) as i32;
-    let mut lock = state_ptr.state.borrow_mut();
     let new_size = size(DevicePixels(width), DevicePixels(height));
     let scale_factor = lock.scale_factor;
-    lock.renderer.update_drawable_size(new_size);
+    if may_have_been_minimized {
+        lock.renderer
+            .update_drawable_size_even_if_unchanged(new_size);
+    } else {
+        lock.renderer.update_drawable_size(new_size);
+    }
     let new_size = new_size.to_pixels(scale_factor);
     lock.logical_size = new_size;
     if let Some(mut callback) = lock.callbacks.resize.take() {

crates/gpui/src/platform/windows/window.rs 🔗

@@ -38,6 +38,7 @@ pub struct WindowsWindowState {
     pub fullscreen_restore_bounds: Bounds<Pixels>,
     pub border_offset: WindowBorderOffset,
     pub scale_factor: f32,
+    pub is_minimized: Option<bool>,
 
     pub callbacks: Callbacks,
     pub input_handler: Option<PlatformInputHandler>,
@@ -92,6 +93,7 @@ impl WindowsWindowState {
             size: logical_size,
         };
         let border_offset = WindowBorderOffset::default();
+        let is_minimized = None;
         let renderer = windows_renderer::windows_renderer(hwnd, transparent)?;
         let callbacks = Callbacks::default();
         let input_handler = None;
@@ -109,6 +111,7 @@ impl WindowsWindowState {
             fullscreen_restore_bounds,
             border_offset,
             scale_factor,
+            is_minimized,
             callbacks,
             input_handler,
             system_key_handled,