Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
da9f460
feat(HUB-7425): Add a render-to-image feature for Window::render_to_i…
francois-unity Sep 23, 2026
af86d36
Relax the exact core-foundation pin to a 0.10 range
chris-addison Sep 26, 2026
a7259e0
Trim unused dependencies: image AVIF encoder, bindgen, postage, windo…
chris-addison Sep 27, 2026
ea0d75e
Upgrade wgpu and naga to 30, accesskit to 0.25
chris-addison Sep 27, 2026
e06ccba
Bump rand to 0.10, getrandom to 0.4, flume to 0.12, which to 8
chris-addison Sep 27, 2026
1cb08a2
Compose the DirectComposition tree behind child HWNDs on Windows
ChrisUnityArto Oct 1, 2026
0a1be1a
Follow wgpu 30's fallible get_mapped_range in the offscreen readback
chris-addison Oct 2, 2026
6bcf179
Let render-to-image build with the wgpu renderer
chris-addison Oct 2, 2026
7744451
elements/div: add an aria_url passthrough
francois-unity Sep 29, 2026
ff29c2b
linux/wayland: send the app_id and title with get_toplevel
chris-addison Oct 3, 2026
06855cf
Wrap text in the test text system
chris-addison Oct 5, 2026
cda5eed
Hang the whitespace that ends a paragraph on its last row
francois-unity Oct 5, 2026
b6094c9
Count the whitespace that ends a paragraph in its block width
francois-unity Oct 6, 2026
0144845
Read a Linux window’s frame back with render-to-image
francois-unity Oct 6, 2026
2cee99b
Refuse a window readback in a format it cannot copy
francois-unity Oct 6, 2026
3136d53
Let TestAppContext render with a real text system, renderer and scale
francois-unity Oct 7, 2026
ecbc509
Give Windows Han text more than one fallback face
kaylafortin Oct 13, 2026
b095e11
Declare gpui_media’s bindgen build dependency on every host
francois-unity Oct 2, 2026
e369684
gpui_wgpu: let applications opt in to experimental wgpu features
chris-addison Oct 8, 2026
2c20569
Windows: WGPU renderer option + custom GPU controls (following #287)
boybeater Oct 2, 2026
857a1bb
gpui_wgpu: headless test for Windows custom-gpu texture compositing
boybeater Oct 2, 2026
442fd89
Windows wgpu renderer: retry scheduling, review fixes, rebase onto main
boybeater Oct 3, 2026
f4fcb34
Windows wgpu renderer: never draw with the pre-recovery scene
boybeater Oct 3, 2026
6785e94
Windows wgpu renderer: don't create DirectX devices
armadillonoah Oct 6, 2026
a73e43d
Windows wgpu renderer: render scenes to images for visual tests
armadillonoah Oct 6, 2026
e484e79
Windows wgpu renderer: warn when GPU requirements arrive too late
armadillonoah Oct 6, 2026
6f85f6b
gpui_windows: wgpu feature uses the Parley text system, vsync fixes
boybeater Oct 7, 2026
fba61a1
gpui_windows: render-to-image reads the WGPU renderer's frame back
chris-addison Oct 8, 2026
04d8b81
gpui_wgpu: build the tests and the custom_gpu example again
chris-addison Oct 8, 2026
2ec6704
gpui_wgpu: the headless renderer lends its typed GPU context
chris-addison Oct 8, 2026
f0d2c83
gpui_wgpu: headless renderers take device requirements and WGPU_POWER…
chris-addison Oct 8, 2026
ab8c65f
gpui_wgpu: wrap the Windows capture texture on wgpu 30
chris-addison Oct 8, 2026
2170f3b
gpui_wgpu: let applications prefer a native backend
chris-addison Oct 8, 2026
2e8f5ed
gpui_windows: choose the renderer per window
chris-addison Oct 8, 2026
87792a4
gpui_windows: resize the renderer when a minimized window is restored
chris-addison Oct 17, 2026
428a790
Fix the branch's own CI: a renamed Parley test helper and wgpu format…
chris-addison Oct 17, 2026
bdbab26
Merge branch 'unity/hub-consolidated' of ssh://github.com/Unity-Techn…
chris-addison Oct 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 18 additions & 16 deletions crates/gpui_wgpu/src/wgpu_renderer/surfaces/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ pub(super) fn retain_surface_cache(renderer: &WgpuRenderer, surfaces: &[PaintSur
let active_custom_textures = surfaces
.iter()
.filter_map(|surface| match &surface.source {
gpui::SurfaceSource::Texture { texture, .. } => {
texture.downcast_ref::<wgpu::Texture>()
}
gpui::SurfaceSource::Texture { texture, .. } => texture.downcast_ref::<wgpu::Texture>(),
_ => None,
})
.collect::<smallvec::SmallVec<[&wgpu::Texture; 4]>>();
Expand Down Expand Up @@ -76,16 +74,14 @@ pub(super) fn draw_surfaces(
log::error!("surface source is not a WGPU texture");
return Err(frame::DrawError::ExternalSurface);
};
let binding = cache.custom_textures.entry(texture.clone()).or_insert_with(|| {
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
SurfaceBinding::new(renderer, view.clone(), view)
});
renderer.draw_surface_binding(
surface,
SurfaceColorFormat::Rgba,
binding,
pass,
)?;
let binding = cache
.custom_textures
.entry(texture.clone())
.or_insert_with(|| {
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
SurfaceBinding::new(renderer, view.clone(), view)
});
renderer.draw_surface_binding(surface, SurfaceColorFormat::Rgba, binding, pass)?;
continue;
}
let Some(frame) = capture_frame(surface) else {
Expand Down Expand Up @@ -341,7 +337,7 @@ mod tests {
mod custom_gpu_tests {
use std::sync::Arc;

use gpui::{Bounds, Point, Scene, ScaledPixels, Size, SurfaceSource};
use gpui::{Bounds, Point, ScaledPixels, Scene, Size, SurfaceSource};

use super::*;
use crate::{WgpuContext, WgpuRenderer};
Expand Down Expand Up @@ -410,14 +406,20 @@ mod custom_gpu_tests {
let mut scene = Scene::default();
scene.insert_primitive(gpui::Quad {
bounds: full.clone(),
content_mask: gpui::ContentMask { bounds: full.clone(), ..Default::default() },
content_mask: gpui::ContentMask {
bounds: full.clone(),
..Default::default()
},
background: gpui::solid_background(gpui::white()),
..Default::default()
});
scene.insert_primitive(gpui::PaintSurface {
order: 1,
bounds: bounds(2.0, 2.0),
content_mask: gpui::ContentMask { bounds: full.clone(), ..Default::default() },
content_mask: gpui::ContentMask {
bounds: full.clone(),
..Default::default()
},
source: SurfaceSource::Texture {
texture,
size: gpui::size(gpui::DevicePixels(2), gpui::DevicePixels(2)),
Expand Down
9 changes: 9 additions & 0 deletions crates/gpui_windows/src/directx_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,15 @@ impl DirectXRenderer {
.context("failed to build RGBA image from DirectX staging readback")
}

/// The size the swap chain was last resized to.
#[cfg(test)]
pub(crate) fn drawable_size(&self) -> Size<DevicePixels> {
Size {
width: DevicePixels(self.width as i32),
height: DevicePixels(self.height as i32),
}
}

pub(crate) fn resize(&mut self, new_size: Size<DevicePixels>) -> Result<()> {
let width = new_size.width.0.max(1) as u32;
let height = new_size.height.0.max(1) as u32;
Expand Down
27 changes: 11 additions & 16 deletions crates/gpui_windows/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@ impl WindowsWindowInner {
}

fn handle_size_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
// 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`.
// A minimized window has no client area to draw, so stop requesting frames until it is
// restored. The renderer keeps its size: `WM_SIZE` reports 0x0 while minimized.
if wparam.0 == SIZE_MINIMIZED as usize {
self.state
.restore_from_minimized
Expand All @@ -251,32 +251,27 @@ impl WindowsWindowInner {
let new_size = size(DevicePixels(width), DevicePixels(height));

let scale_factor = self.state.scale_factor.get();
let mut should_resize_renderer = false;
if let Some(restore_from_minimized) = self.state.restore_from_minimized.take() {
self.state
.callbacks
.request_frame
.set(Some(restore_from_minimized));
} else {
should_resize_renderer = true;
}

self.handle_size_change(new_size, scale_factor, should_resize_renderer);
// Resize the renderer on restore too. The restored size can differ from the size the
// window had when it was minimized (a display, scale or resolution change while it sat in
// the taskbar, or restoring to maximized). Layout follows the new size either way, so a
// swap chain left at the old size clips the right and bottom of the window. The renderer
// returns early when the size is unchanged.
self.handle_size_change(new_size, scale_factor);
Some(0)
}

fn handle_size_change(
&self,
device_size: Size<DevicePixels>,
scale_factor: f32,
should_resize_renderer: bool,
) {
fn handle_size_change(&self, device_size: Size<DevicePixels>, scale_factor: f32) {
let new_logical_size = device_size.to_pixels(scale_factor);

self.state.logical_size.set(new_logical_size);
if should_resize_renderer
&& let Err(e) = self.state.renderer.borrow_mut().resize(device_size)
{
if let Err(e) = self.state.renderer.borrow_mut().resize(device_size) {
log::error!("Failed to resize renderer, invalidating devices: {}", e);
self.state
.invalidate_devices
Expand Down Expand Up @@ -924,7 +919,7 @@ impl WindowsWindowInner {
// SetWindowPos may not send WM_SIZE for maximized windows in some cases,
// so we manually update the size to ensure proper rendering
let device_size = size(DevicePixels(width), DevicePixels(height));
self.handle_size_change(device_size, new_scale_factor, true);
self.handle_size_change(device_size, new_scale_factor);
}
} else {
// For non-maximized windows, use the suggested RECT from the system
Expand Down
99 changes: 99 additions & 0 deletions crates/gpui_windows/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1599,6 +1599,105 @@ mod tests {
);
}

/// A window restored from the taskbar at a different size than it was minimized at (a display,
/// scale or resolution change while minimized, or restoring to maximized) must resize its
/// renderer. Layout follows the restored size, so a swap chain left at the old size clips the
/// right and bottom of the window until the user resizes it.
#[test]
fn restoring_a_minimized_window_resizes_the_renderer() -> anyhow::Result<()> {
use super::WindowsPlatform;
use crate::WindowsWindow;
use gpui::{
AnyWindowHandle, Bounds, DevicePixels, PlatformWindow as _, WindowHandle, WindowId,
WindowKind, WindowParams, point, px, size,
};
use windows::Win32::Foundation::RECT;
use windows::Win32::UI::WindowsAndMessaging::{
DestroyWindow, GetClientRect, GetWindowPlacement, SW_RESTORE, SW_SHOWMINNOACTIVE,
SW_SHOWNOACTIVATE, SetWindowPlacement, ShowWindow, WINDOWPLACEMENT,
};

struct Root;
impl gpui::Render for Root {
fn render(
&mut self,
_: &mut gpui::Window,
_: &mut gpui::Context<Self>,
) -> impl gpui::IntoElement {
gpui::Empty
}
}

let platform = WindowsPlatform::new(false)?;
let handle: AnyWindowHandle = WindowHandle::<Root>::new(WindowId::from(1)).into();
let window = WindowsWindow::new(
handle,
WindowParams {
bounds: Bounds::new(point(px(100.), px(100.)), size(px(400.), px(300.))),
titlebar: None,
kind: WindowKind::Normal,
is_movable: true,
app_owns_titlebar_drag: false,
is_resizable: true,
is_minimizable: true,
focus: false,
show: false,
icon: None,
display_id: None,
app_id: None,
window_min_size: None,
windows_renderer: Default::default(),
},
platform.generate_creation_info(),
)?;
let hwnd = window.get_raw_handle();
let client_size = || -> anyhow::Result<gpui::Size<DevicePixels>> {
let mut rect = RECT::default();
unsafe { GetClientRect(hwnd, &mut rect)? };
Ok(size(
DevicePixels(rect.right - rect.left),
DevicePixels(rect.bottom - rect.top),
))
};
let renderer_size = || window.0.state.renderer.borrow().drawable_size();
// Every gpui window registers a frame callback, and minimizing stashes it. Without one, the
// restore looks like an ordinary resize and the bug never shows.
window.on_request_frame(Box::new(|_| {}));

unsafe {
let _ = ShowWindow(hwnd, SW_SHOWNOACTIVATE);
}
let before = client_size()?;
assert_eq!(renderer_size(), before);

// Minimize, then grow the size the window restores to while it sits in the taskbar, the
// way a display or scale change does.
unsafe {
let _ = ShowWindow(hwnd, SW_SHOWMINNOACTIVE);
let mut placement = WINDOWPLACEMENT {
length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
..Default::default()
};
GetWindowPlacement(hwnd, &mut placement)?;
placement.rcNormalPosition.right += 200;
placement.rcNormalPosition.bottom += 100;
placement.showCmd = SW_SHOWMINNOACTIVE.0 as u32;
SetWindowPlacement(hwnd, &placement)?;
let _ = ShowWindow(hwnd, SW_RESTORE);
}

let after = client_size()?;
assert_ne!(after, before, "the restored window should be larger");
assert_eq!(
renderer_size(),
after,
"the renderer must take the restored size, not keep the pre-minimize size"
);

unsafe { DestroyWindow(hwnd)? };
Ok(())
}

#[test]
fn test_clipboard() {
let item = ClipboardItem::new_string("你好,我是张小白".to_string());
Expand Down
9 changes: 9 additions & 0 deletions crates/gpui_windows/src/wgpu_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,15 @@ impl WindowRenderer {
}
}

/// The size the renderer was last resized to.
#[cfg(test)]
pub(crate) fn drawable_size(&self) -> Size<gpui::DevicePixels> {
match self {
Self::DirectX(r) => r.drawable_size(),
Self::Wgpu(r) => r.size,
}
}

pub fn resize(&mut self, size: Size<gpui::DevicePixels>) -> anyhow::Result<()> {
match self {
Self::DirectX(r) => r.resize(size),
Expand Down
Loading