Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 23 additions & 0 deletions crates/gpui/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,23 @@ pub enum TextInputStateChange {
ContentChanged,
}

/// Raw composition handles for embedding native surfaces (Windows:
/// `IDCompositionDevice*` and an `IDCompositionVisual*` layer; `None` on
/// other platforms). Both are borrowed: never release them, and treat them
/// as valid only while `generation` is unchanged — GPU device-lost recovery
/// releases them and creates new ones. Take a reference (`AddRef`) on
/// anything kept beyond the current frame, and remount native children when
/// the generation changes.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct NativeComposition {
/// The device that owns the layer.
pub device: *mut std::ffi::c_void,
/// The container beneath GPUI's overlay plane and above its base plane.
pub layer: *mut std::ffi::c_void,
/// Changes when GPU recovery recreates the device; remount children.
pub generation: u64,
}

#[expect(missing_docs)]
pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn bounds(&self) -> Bounds<Pixels>;
Expand Down Expand Up @@ -855,6 +872,12 @@ pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
anyhow::bail!("Native scene overlays are unavailable on this platform")
}

/// Where native children composite between the base and overlay planes.
/// Available on Windows once [`Self::enable_scene_overlay`] succeeded.
fn native_composition(&self) -> Option<NativeComposition> {
None
}

fn completed_frame(&self) {}
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
fn is_subpixel_rendering_supported(&self) -> bool;
Expand Down
5 changes: 5 additions & 0 deletions crates/gpui/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2915,6 +2915,11 @@ impl Window {
self.platform_window.enable_scene_overlay()
}

/// The composition layer native children mount into, when supported.
pub fn native_composition(&self) -> Option<crate::NativeComposition> {
self.platform_window.native_composition()
}

/// Schedule a native-view update from an element's paint pass. Painting
/// can run solely to refresh input hit testing, without presenting a frame.
/// These updates run only when the corresponding scene reaches the screen,
Expand Down
204 changes: 195 additions & 9 deletions crates/gpui_windows/src/directx_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ pub(crate) struct DirectXRenderer {
globals: DirectXGlobalElements,
pipelines: DirectXRenderPipelines,
direct_composition: Option<DirectComposition>,
/// Deferred GPUI content above native children, once enabled.
overlay: Option<OverlayPlane>,
/// Set once [`Self::enable_overlay`] succeeds. Device-lost recovery
/// restores the overlay from this, not from `overlay` — a failed
/// recovery attempt has already dropped it before the retry.
overlay_requested: bool,
overlay_generation: u64,
/// While drawing the overlay: the base frame its backdrop blurs sample,
/// so frosted popovers blur the UI beneath rather than an empty plane.
backdrop_source: Option<ID3D11Texture2D>,
font_info: &'static FontInfo,

width: u32,
Expand Down Expand Up @@ -127,6 +137,60 @@ struct DirectComposition {
comp_visual: IDCompositionVisual,
}

/// The base swap chain's visual hosts two children: `layer`, where native
/// surfaces (WebView2) mount, and above it `visual`, a second swap chain for
/// deferred GPUI content. An empty overlay detaches its content and draws
/// nothing, so frames without menus cost exactly what they did before.
struct OverlayPlane {
resources: DirectXResources,
layer: IDCompositionVisual,
visual: IDCompositionVisual,
attached: bool,
}

impl OverlayPlane {
fn new(
devices: &DirectXRendererDevices,
composition: &DirectComposition,
width: u32,
height: u32,
hwnd: HWND,
) -> Result<Self> {
let resources = DirectXResources::new(devices, width, height, hwnd, false)
.context("Creating overlay resources")?;
unsafe {
let layer = composition.comp_device.CreateVisual()?;
let visual = composition.comp_device.CreateVisual()?;
visual.SetContent(&resources.swap_chain)?;
composition
.comp_visual
.AddVisual(&layer, false, None::<&IDCompositionVisual>)?;
composition.comp_visual.AddVisual(&visual, true, &layer)?;
composition.comp_device.Commit()?;
Ok(Self {
resources,
layer,
visual,
attached: true,
})
}
}

fn set_attached(&mut self, attached: bool) -> Result<()> {
if self.attached != attached {
unsafe {
if attached {
self.visual.SetContent(&self.resources.swap_chain)?;
} else {
self.visual.SetContent(None::<&windows::core::IUnknown>)?;
}
}
self.attached = attached;
}
Ok(())
}
}

impl DirectXRendererDevices {
pub(crate) fn new(
directx_devices: &DirectXDevices,
Expand Down Expand Up @@ -196,6 +260,10 @@ impl DirectXRenderer {
globals,
pipelines,
direct_composition,
overlay: None,
overlay_requested: false,
overlay_generation: 0,
backdrop_source: None,
font_info: Self::get_font_info(),
width: 1,
height: 1,
Expand Down Expand Up @@ -272,6 +340,7 @@ impl DirectXRenderer {
}

self.resources.take();
self.overlay.take();
if let Some(devices) = &self.devices {
devices.device_context.OMSetRenderTargets(None, None);
devices.device_context.ClearState();
Expand Down Expand Up @@ -324,6 +393,12 @@ impl DirectXRenderer {
self.pipelines = pipelines;
self.direct_composition = direct_composition;
self.skip_draws = true;
if self.overlay_requested {
// Native children were mounted on the lost device; the new
// generation tells their owners to remount.
self.enable_overlay()
.context("Re-enabling the scene overlay")?;
}
Ok(())
}

Expand All @@ -339,6 +414,105 @@ impl DirectXRenderer {
self.present()
}

/// Create the native layer and overlay plane (idempotent).
pub(crate) fn enable_overlay(&mut self) -> Result<()> {
if self.overlay.is_some() {
return Ok(());
}
let composition = self
.direct_composition
.as_ref()
.context("Native scene overlays require DirectComposition")?;
let devices = self.devices.as_ref().context("devices missing")?;
self.overlay = Some(OverlayPlane::new(
devices,
composition,
self.width,
self.height,
self.hwnd,
)?);
self.overlay_requested = true;
self.overlay_generation += 1;
Ok(())
}

pub(crate) fn has_overlay(&self) -> bool {
self.overlay.is_some()
}

pub(crate) fn native_composition(&self) -> Option<NativeComposition> {
let composition = self.direct_composition.as_ref()?;
let overlay = self.overlay.as_ref()?;
Some(NativeComposition {
device: composition.comp_device.as_raw(),
layer: overlay.layer.as_raw(),
generation: self.overlay_generation,
})
}

/// Draw `scene[..overlay_start]` beneath native children and the rest
/// above them. Without deferred content the scene is drawn as-is (no
/// copy) and the overlay stays detached. Commits native-layer geometry
/// that presentation callbacks changed for this frame.
pub(crate) fn draw_layered(
&mut self,
scene: &Scene,
overlay_start: usize,
background_appearance: WindowBackgroundAppearance,
) -> Result<()> {
if self.overlay.is_none() {
return self.draw(scene, background_appearance);
}
if self.skip_draws {
return Ok(());
}
let split = overlay_start.min(scene.len());
if split == scene.len() {
self.draw_scene(scene, background_appearance)?;
self.present()?;
self.overlay.as_mut().unwrap().set_attached(false)?;
} else {
let mut base = Scene::default();
base.replay(0..split, scene);
base.finish();
let mut overlay = Scene::default();
overlay.replay(split..scene.len(), scene);
overlay.finish();
self.draw_scene(&base, background_appearance)?;
// Present the base after the overlay is drawn: its back buffer
// is the overlay's blur source until then.
if !overlay.backdrop_blurs.is_empty() {
self.backdrop_source = self
.resources
.as_ref()
.and_then(|resources| resources.render_target.clone());
}
// The overlay shares devices, pipelines and the atlas; only its
// swap chain and render targets differ.
self.swap_overlay_resources();
let drawn = self
.draw_scene(&overlay, WindowBackgroundAppearance::Transparent)
.and_then(|_| self.present());
self.swap_overlay_resources();
self.backdrop_source = None;
drawn?;
self.present()?;
self.overlay.as_mut().unwrap().set_attached(true)?;
}
if let Some(composition) = &self.direct_composition {
unsafe { composition.comp_device.Commit()? };
}
Ok(())
}

fn swap_overlay_resources(&mut self) {
let overlay = self.overlay.as_mut().expect("overlay missing");
std::mem::swap(
self.resources.as_mut().expect("resources missing"),
&mut overlay.resources,
);
}

// Kept separate from presentation so WARP tests can read the actual frame.
fn draw_scene(
&mut self,
Expand Down Expand Up @@ -441,7 +615,27 @@ impl DirectXRenderer {
// Clear the render target before resizing
let devices = self.devices.as_ref().context("devices missing")?;
unsafe { devices.device_context.OMSetRenderTargets(None, None) };
if let Some(overlay) = self.overlay.as_mut() {
Self::resize_resources(devices, &mut overlay.resources, width, height)?;
}
let resources = self.resources.as_mut().context("resources missing")?;
Self::resize_resources(devices, resources, width, height)?;

unsafe {
devices
.device_context
.OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None);
}

Ok(())
}

fn resize_resources(
devices: &DirectXRendererDevices,
resources: &mut DirectXResources,
width: u32,
height: u32,
) -> Result<()> {
resources.backdrop = None;
resources.render_target.take();
resources.render_target_view.take();
Expand All @@ -463,15 +657,7 @@ impl DirectXRenderer {
.context("Failed to resize swap chain")?;
}

resources.recreate_resources(devices, width, height)?;

unsafe {
devices
.device_context
.OMSetRenderTargets(Some(slice::from_ref(&resources.render_target_view)), None);
}

Ok(())
resources.recreate_resources(devices, width, height)
}

fn upload_scene_buffers(&mut self, scene: &Scene) -> Result<()> {
Expand Down
5 changes: 3 additions & 2 deletions crates/gpui_windows/src/directx_renderer/backdrop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ impl DirectXRenderer {
}
let downsample = ((sigma / 8.) as u32).clamp(1, 4);
let devices = self.devices.as_ref().context("devices missing")?;
let source = self.backdrop_source.clone();
let resources = self.resources.as_mut().context("resources missing")?;
if resources.backdrop.is_none() {
resources.backdrop = Some(BackdropResources::new(&devices.device)?);
Expand Down Expand Up @@ -306,9 +307,9 @@ impl DirectXRenderer {
0,
0,
0,
resources
.render_target
source
.as_ref()
.or(resources.render_target.as_ref())
.context("missing render target")?,
0,
Some(&D3D11_BOX {
Expand Down
3 changes: 3 additions & 0 deletions crates/gpui_windows/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,9 @@ impl WindowsWindowInner {
lparam: LPARAM,
) -> Option<isize> {
unsafe { SetCapture(handle) };
// Pressing GPUI content takes the keyboard from an embedded native
// child; a press the app forwards to that child refocuses it.
reclaim_focus_from_native_child(handle);

let Some(mut func) = self.state.callbacks.input.take() else {
return Some(1);
Expand Down
Loading
Loading