Detailed changes
@@ -2,6 +2,7 @@ mod clipboard;
mod destination_list;
mod direct_write;
mod directx_atlas;
+mod directx_devices;
mod directx_renderer;
mod dispatcher;
mod display;
@@ -18,6 +19,7 @@ pub(crate) use clipboard::*;
pub(crate) use destination_list::*;
pub(crate) use direct_write::*;
pub(crate) use directx_atlas::*;
+pub(crate) use directx_devices::*;
pub(crate) use directx_renderer::*;
pub(crate) use dispatcher::*;
pub(crate) use display::*;
@@ -1,7 +1,7 @@
use std::{borrow::Cow, sync::Arc};
use ::util::ResultExt;
-use anyhow::Result;
+use anyhow::{Context, Result};
use collections::HashMap;
use itertools::Itertools;
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
@@ -70,7 +70,7 @@ struct FontIdentifier {
}
impl DirectWriteComponent {
- pub fn new(gpu_context: &DirectXDevices) -> Result<Self> {
+ pub fn new(directx_devices: &DirectXDevices) -> Result<Self> {
// todo: ideally this would not be a large unsafe block but smaller isolated ones for easier auditing
unsafe {
let factory: IDWriteFactory5 = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED)?;
@@ -85,7 +85,7 @@ impl DirectWriteComponent {
let locale = String::from_utf16_lossy(&locale_vec);
let text_renderer = Arc::new(TextRendererWrapper::new(&locale));
- let gpu_state = GPUState::new(gpu_context)?;
+ let gpu_state = GPUState::new(directx_devices)?;
Ok(DirectWriteComponent {
locale,
@@ -100,9 +100,9 @@ impl DirectWriteComponent {
}
impl GPUState {
- fn new(gpu_context: &DirectXDevices) -> Result<Self> {
- let device = gpu_context.device.clone();
- let device_context = gpu_context.device_context.clone();
+ fn new(directx_devices: &DirectXDevices) -> Result<Self> {
+ let device = directx_devices.device.clone();
+ let device_context = directx_devices.device_context.clone();
let blend_state = {
let mut blend_state = None;
@@ -183,8 +183,8 @@ impl GPUState {
}
impl DirectWriteTextSystem {
- pub(crate) fn new(gpu_context: &DirectXDevices) -> Result<Self> {
- let components = DirectWriteComponent::new(gpu_context)?;
+ pub(crate) fn new(directx_devices: &DirectXDevices) -> Result<Self> {
+ let components = DirectWriteComponent::new(directx_devices)?;
let system_font_collection = unsafe {
let mut result = std::mem::zeroed();
components
@@ -210,6 +210,10 @@ impl DirectWriteTextSystem {
font_id_by_identifier: HashMap::default(),
})))
}
+
+ pub(crate) fn handle_gpu_lost(&self, directx_devices: &DirectXDevices) {
+ self.0.write().handle_gpu_lost(directx_devices);
+ }
}
impl PlatformTextSystem for DirectWriteTextSystem {
@@ -1211,6 +1215,20 @@ impl DirectWriteState {
));
result
}
+
+ fn handle_gpu_lost(&mut self, directx_devices: &DirectXDevices) {
+ try_to_recover_from_device_lost(
+ || GPUState::new(directx_devices).context("Recreating GPU state for DirectWrite"),
+ |gpu_state| self.components.gpu_state = gpu_state,
+ || {
+ log::error!(
+ "Failed to recreate GPU state for DirectWrite after multiple attempts."
+ );
+ // Do something here?
+ // At this point, the device loss is considered unrecoverable.
+ },
+ );
+ }
}
impl Drop for DirectWriteState {
@@ -0,0 +1,180 @@
+use anyhow::{Context, Result};
+use util::ResultExt;
+use windows::Win32::{
+ Foundation::HMODULE,
+ Graphics::{
+ Direct3D::{
+ D3D_DRIVER_TYPE_UNKNOWN, D3D_FEATURE_LEVEL, D3D_FEATURE_LEVEL_10_1,
+ D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1,
+ },
+ Direct3D11::{
+ D3D11_CREATE_DEVICE_BGRA_SUPPORT, D3D11_CREATE_DEVICE_DEBUG, D3D11_SDK_VERSION,
+ D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext,
+ },
+ Dxgi::{
+ CreateDXGIFactory2, DXGI_CREATE_FACTORY_DEBUG, DXGI_CREATE_FACTORY_FLAGS,
+ DXGI_GPU_PREFERENCE_MINIMUM_POWER, IDXGIAdapter1, IDXGIFactory6,
+ },
+ },
+};
+
+pub(crate) fn try_to_recover_from_device_lost<T>(
+ mut f: impl FnMut() -> Result<T>,
+ on_success: impl FnOnce(T),
+ on_error: impl FnOnce(),
+) {
+ let result = (0..5).find_map(|i| {
+ if i > 0 {
+ // Add a small delay before retrying
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ }
+ f().log_err()
+ });
+
+ if let Some(result) = result {
+ on_success(result);
+ } else {
+ on_error();
+ }
+}
+
+#[derive(Clone)]
+pub(crate) struct DirectXDevices {
+ pub(crate) adapter: IDXGIAdapter1,
+ pub(crate) dxgi_factory: IDXGIFactory6,
+ pub(crate) device: ID3D11Device,
+ pub(crate) device_context: ID3D11DeviceContext,
+}
+
+impl DirectXDevices {
+ pub(crate) fn new() -> Result<Self> {
+ let debug_layer_available = check_debug_layer_available();
+ let dxgi_factory =
+ get_dxgi_factory(debug_layer_available).context("Creating DXGI factory")?;
+ let adapter =
+ get_adapter(&dxgi_factory, debug_layer_available).context("Getting DXGI adapter")?;
+ let (device, device_context) = {
+ let mut device: Option<ID3D11Device> = None;
+ let mut context: Option<ID3D11DeviceContext> = None;
+ let mut feature_level = D3D_FEATURE_LEVEL::default();
+ get_device(
+ &adapter,
+ Some(&mut device),
+ Some(&mut context),
+ Some(&mut feature_level),
+ debug_layer_available,
+ )
+ .context("Creating Direct3D device")?;
+ match feature_level {
+ D3D_FEATURE_LEVEL_11_1 => {
+ log::info!("Created device with Direct3D 11.1 feature level.")
+ }
+ D3D_FEATURE_LEVEL_11_0 => {
+ log::info!("Created device with Direct3D 11.0 feature level.")
+ }
+ D3D_FEATURE_LEVEL_10_1 => {
+ log::info!("Created device with Direct3D 10.1 feature level.")
+ }
+ _ => unreachable!(),
+ }
+ (device.unwrap(), context.unwrap())
+ };
+
+ Ok(Self {
+ adapter,
+ dxgi_factory,
+ device,
+ device_context,
+ })
+ }
+}
+
+#[inline]
+fn check_debug_layer_available() -> bool {
+ #[cfg(debug_assertions)]
+ {
+ use windows::Win32::Graphics::Dxgi::{DXGIGetDebugInterface1, IDXGIInfoQueue};
+
+ unsafe { DXGIGetDebugInterface1::<IDXGIInfoQueue>(0) }
+ .log_err()
+ .is_some()
+ }
+ #[cfg(not(debug_assertions))]
+ {
+ false
+ }
+}
+
+#[inline]
+fn get_dxgi_factory(debug_layer_available: bool) -> Result<IDXGIFactory6> {
+ let factory_flag = if debug_layer_available {
+ DXGI_CREATE_FACTORY_DEBUG
+ } else {
+ #[cfg(debug_assertions)]
+ log::warn!(
+ "Failed to get DXGI debug interface. DirectX debugging features will be disabled."
+ );
+ DXGI_CREATE_FACTORY_FLAGS::default()
+ };
+ unsafe { Ok(CreateDXGIFactory2(factory_flag)?) }
+}
+
+#[inline]
+fn get_adapter(dxgi_factory: &IDXGIFactory6, debug_layer_available: bool) -> Result<IDXGIAdapter1> {
+ for adapter_index in 0.. {
+ let adapter: IDXGIAdapter1 = unsafe {
+ dxgi_factory
+ .EnumAdapterByGpuPreference(adapter_index, DXGI_GPU_PREFERENCE_MINIMUM_POWER)
+ }?;
+ if let Ok(desc) = unsafe { adapter.GetDesc1() } {
+ let gpu_name = String::from_utf16_lossy(&desc.Description)
+ .trim_matches(char::from(0))
+ .to_string();
+ log::info!("Using GPU: {}", gpu_name);
+ }
+ // Check to see whether the adapter supports Direct3D 11, but don't
+ // create the actual device yet.
+ if get_device(&adapter, None, None, None, debug_layer_available)
+ .log_err()
+ .is_some()
+ {
+ return Ok(adapter);
+ }
+ }
+
+ unreachable!()
+}
+
+#[inline]
+fn get_device(
+ adapter: &IDXGIAdapter1,
+ device: Option<*mut Option<ID3D11Device>>,
+ context: Option<*mut Option<ID3D11DeviceContext>>,
+ feature_level: Option<*mut D3D_FEATURE_LEVEL>,
+ debug_layer_available: bool,
+) -> Result<()> {
+ let device_flags = if debug_layer_available {
+ D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_DEBUG
+ } else {
+ D3D11_CREATE_DEVICE_BGRA_SUPPORT
+ };
+ unsafe {
+ D3D11CreateDevice(
+ adapter,
+ D3D_DRIVER_TYPE_UNKNOWN,
+ HMODULE::default(),
+ device_flags,
+ // 4x MSAA is required for Direct3D Feature Level 10.1 or better
+ Some(&[
+ D3D_FEATURE_LEVEL_11_1,
+ D3D_FEATURE_LEVEL_11_0,
+ D3D_FEATURE_LEVEL_10_1,
+ ]),
+ D3D11_SDK_VERSION,
+ device,
+ feature_level,
+ context,
+ )?;
+ }
+ Ok(())
+}
@@ -7,7 +7,7 @@ use ::util::ResultExt;
use anyhow::{Context, Result};
use windows::{
Win32::{
- Foundation::{HMODULE, HWND},
+ Foundation::HWND,
Graphics::{
Direct3D::*,
Direct3D11::*,
@@ -39,7 +39,7 @@ pub(crate) struct FontInfo {
pub(crate) struct DirectXRenderer {
hwnd: HWND,
atlas: Arc<DirectXAtlas>,
- devices: ManuallyDrop<DirectXDevices>,
+ devices: ManuallyDrop<DirectXRendererDevices>,
resources: ManuallyDrop<DirectXResources>,
globals: DirectXGlobalElements,
pipelines: DirectXRenderPipelines,
@@ -49,9 +49,9 @@ pub(crate) struct DirectXRenderer {
/// Direct3D objects
#[derive(Clone)]
-pub(crate) struct DirectXDevices {
- adapter: IDXGIAdapter1,
- dxgi_factory: IDXGIFactory6,
+pub(crate) struct DirectXRendererDevices {
+ pub(crate) adapter: IDXGIAdapter1,
+ pub(crate) dxgi_factory: IDXGIFactory6,
pub(crate) device: ID3D11Device,
pub(crate) device_context: ID3D11DeviceContext,
dxgi_device: Option<IDXGIDevice>,
@@ -96,39 +96,17 @@ struct DirectComposition {
comp_visual: IDCompositionVisual,
}
-impl DirectXDevices {
- pub(crate) fn new(disable_direct_composition: bool) -> Result<ManuallyDrop<Self>> {
- let debug_layer_available = check_debug_layer_available();
- let dxgi_factory =
- get_dxgi_factory(debug_layer_available).context("Creating DXGI factory")?;
- let adapter =
- get_adapter(&dxgi_factory, debug_layer_available).context("Getting DXGI adapter")?;
- let (device, device_context) = {
- let mut device: Option<ID3D11Device> = None;
- let mut context: Option<ID3D11DeviceContext> = None;
- let mut feature_level = D3D_FEATURE_LEVEL::default();
- get_device(
- &adapter,
- Some(&mut device),
- Some(&mut context),
- Some(&mut feature_level),
- debug_layer_available,
- )
- .context("Creating Direct3D device")?;
- match feature_level {
- D3D_FEATURE_LEVEL_11_1 => {
- log::info!("Created device with Direct3D 11.1 feature level.")
- }
- D3D_FEATURE_LEVEL_11_0 => {
- log::info!("Created device with Direct3D 11.0 feature level.")
- }
- D3D_FEATURE_LEVEL_10_1 => {
- log::info!("Created device with Direct3D 10.1 feature level.")
- }
- _ => unreachable!(),
- }
- (device.unwrap(), context.unwrap())
- };
+impl DirectXRendererDevices {
+ pub(crate) fn new(
+ directx_devices: &DirectXDevices,
+ disable_direct_composition: bool,
+ ) -> Result<ManuallyDrop<Self>> {
+ let DirectXDevices {
+ adapter,
+ dxgi_factory,
+ device,
+ device_context,
+ } = directx_devices;
let dxgi_device = if disable_direct_composition {
None
} else {
@@ -136,23 +114,27 @@ impl DirectXDevices {
};
Ok(ManuallyDrop::new(Self {
- adapter,
- dxgi_factory,
+ adapter: adapter.clone(),
+ dxgi_factory: dxgi_factory.clone(),
+ device: device.clone(),
+ device_context: device_context.clone(),
dxgi_device,
- device,
- device_context,
}))
}
}
impl DirectXRenderer {
- pub(crate) fn new(hwnd: HWND, disable_direct_composition: bool) -> Result<Self> {
+ pub(crate) fn new(
+ hwnd: HWND,
+ directx_devices: &DirectXDevices,
+ disable_direct_composition: bool,
+ ) -> Result<Self> {
if disable_direct_composition {
log::info!("Direct Composition is disabled.");
}
- let devices =
- DirectXDevices::new(disable_direct_composition).context("Creating DirectX devices")?;
+ let devices = DirectXRendererDevices::new(directx_devices, disable_direct_composition)
+ .context("Creating DirectX devices")?;
let atlas = Arc::new(DirectXAtlas::new(&devices.device, &devices.device_context));
let resources = DirectXResources::new(&devices, 1, 1, hwnd, disable_direct_composition)
@@ -218,28 +200,30 @@ impl DirectXRenderer {
Ok(())
}
+ #[inline]
fn present(&mut self) -> Result<()> {
- unsafe {
- let result = self.resources.swap_chain.Present(0, DXGI_PRESENT(0));
- // Presenting the swap chain can fail if the DirectX device was removed or reset.
- if result == DXGI_ERROR_DEVICE_REMOVED || result == DXGI_ERROR_DEVICE_RESET {
- let reason = self.devices.device.GetDeviceRemovedReason();
+ let result = unsafe { self.resources.swap_chain.Present(0, DXGI_PRESENT(0)) };
+ result.ok().context("Presenting swap chain failed")
+ }
+
+ pub(crate) fn handle_device_lost(&mut self, directx_devices: &DirectXDevices) {
+ try_to_recover_from_device_lost(
+ || {
+ self.handle_device_lost_impl(directx_devices)
+ .context("DirectXRenderer handling device lost")
+ },
+ |_| {},
+ || {
log::error!(
- "DirectX device removed or reset when drawing. Reason: {:?}",
- reason
+ "DirectXRenderer failed to recover from device lost after multiple attempts"
);
- self.handle_device_lost()?;
- } else {
- result.ok()?;
- }
- }
- Ok(())
+ // Do something here?
+ // At this point, the device loss is considered unrecoverable.
+ },
+ );
}
- fn handle_device_lost(&mut self) -> Result<()> {
- // Here we wait a bit to ensure the the system has time to recover from the device lost state.
- // If we don't wait, the final drawing result will be blank.
- std::thread::sleep(std::time::Duration::from_millis(300));
+ fn handle_device_lost_impl(&mut self, directx_devices: &DirectXDevices) -> Result<()> {
let disable_direct_composition = self.direct_composition.is_none();
unsafe {
@@ -262,7 +246,7 @@ impl DirectXRenderer {
ManuallyDrop::drop(&mut self.devices);
}
- let devices = DirectXDevices::new(disable_direct_composition)
+ let devices = DirectXRendererDevices::new(directx_devices, disable_direct_composition)
.context("Recreating DirectX devices")?;
let resources = DirectXResources::new(
&devices,
@@ -337,49 +321,39 @@ impl DirectXRenderer {
if self.resources.width == width && self.resources.height == height {
return Ok(());
}
+ self.resources.width = width;
+ self.resources.height = height;
+
+ // Clear the render target before resizing
+ unsafe { self.devices.device_context.OMSetRenderTargets(None, None) };
+ unsafe { ManuallyDrop::drop(&mut self.resources.render_target) };
+ drop(self.resources.render_target_view[0].take().unwrap());
+
+ // Resizing the swap chain requires a call to the underlying DXGI adapter, which can return the device removed error.
+ // The app might have moved to a monitor that's attached to a different graphics device.
+ // When a graphics device is removed or reset, the desktop resolution often changes, resulting in a window size change.
+ // But here we just return the error, because we are handling device lost scenarios elsewhere.
unsafe {
- // Clear the render target before resizing
- self.devices.device_context.OMSetRenderTargets(None, None);
- ManuallyDrop::drop(&mut self.resources.render_target);
- drop(self.resources.render_target_view[0].take().unwrap());
-
- let result = self.resources.swap_chain.ResizeBuffers(
- BUFFER_COUNT as u32,
- width,
- height,
- RENDER_TARGET_FORMAT,
- DXGI_SWAP_CHAIN_FLAG(0),
- );
- // Resizing the swap chain requires a call to the underlying DXGI adapter, which can return the device removed error.
- // The app might have moved to a monitor that's attached to a different graphics device.
- // When a graphics device is removed or reset, the desktop resolution often changes, resulting in a window size change.
- match result {
- Ok(_) => {}
- Err(e) => {
- if e.code() == DXGI_ERROR_DEVICE_REMOVED || e.code() == DXGI_ERROR_DEVICE_RESET
- {
- let reason = self.devices.device.GetDeviceRemovedReason();
- log::error!(
- "DirectX device removed or reset when resizing. Reason: {:?}",
- reason
- );
- self.resources.width = width;
- self.resources.height = height;
- self.handle_device_lost()?;
- return Ok(());
- } else {
- log::error!("Failed to resize swap chain: {:?}", e);
- return Err(e.into());
- }
- }
- }
-
self.resources
- .recreate_resources(&self.devices, width, height)?;
+ .swap_chain
+ .ResizeBuffers(
+ BUFFER_COUNT as u32,
+ width,
+ height,
+ RENDER_TARGET_FORMAT,
+ DXGI_SWAP_CHAIN_FLAG(0),
+ )
+ .context("Failed to resize swap chain")?;
+ }
+
+ self.resources
+ .recreate_resources(&self.devices, width, height)?;
+ unsafe {
self.devices
.device_context
.OMSetRenderTargets(Some(&self.resources.render_target_view), None);
}
+
Ok(())
}
@@ -680,7 +654,7 @@ impl DirectXRenderer {
impl DirectXResources {
pub fn new(
- devices: &DirectXDevices,
+ devices: &DirectXRendererDevices,
width: u32,
height: u32,
hwnd: HWND,
@@ -725,7 +699,7 @@ impl DirectXResources {
#[inline]
fn recreate_resources(
&mut self,
- devices: &DirectXDevices,
+ devices: &DirectXRendererDevices,
width: u32,
height: u32,
) -> Result<()> {
@@ -745,8 +719,6 @@ impl DirectXResources {
self.path_intermediate_msaa_view = path_intermediate_msaa_view;
self.path_intermediate_srv = path_intermediate_srv;
self.viewport = viewport;
- self.width = width;
- self.height = height;
Ok(())
}
}
@@ -1041,92 +1013,6 @@ impl Drop for DirectXResources {
}
}
-#[inline]
-fn check_debug_layer_available() -> bool {
- #[cfg(debug_assertions)]
- {
- unsafe { DXGIGetDebugInterface1::<IDXGIInfoQueue>(0) }
- .log_err()
- .is_some()
- }
- #[cfg(not(debug_assertions))]
- {
- false
- }
-}
-
-#[inline]
-fn get_dxgi_factory(debug_layer_available: bool) -> Result<IDXGIFactory6> {
- let factory_flag = if debug_layer_available {
- DXGI_CREATE_FACTORY_DEBUG
- } else {
- #[cfg(debug_assertions)]
- log::warn!(
- "Failed to get DXGI debug interface. DirectX debugging features will be disabled."
- );
- DXGI_CREATE_FACTORY_FLAGS::default()
- };
- unsafe { Ok(CreateDXGIFactory2(factory_flag)?) }
-}
-
-fn get_adapter(dxgi_factory: &IDXGIFactory6, debug_layer_available: bool) -> Result<IDXGIAdapter1> {
- for adapter_index in 0.. {
- let adapter: IDXGIAdapter1 = unsafe {
- dxgi_factory
- .EnumAdapterByGpuPreference(adapter_index, DXGI_GPU_PREFERENCE_MINIMUM_POWER)
- }?;
- if let Ok(desc) = unsafe { adapter.GetDesc1() } {
- let gpu_name = String::from_utf16_lossy(&desc.Description)
- .trim_matches(char::from(0))
- .to_string();
- log::info!("Using GPU: {}", gpu_name);
- }
- // Check to see whether the adapter supports Direct3D 11, but don't
- // create the actual device yet.
- if get_device(&adapter, None, None, None, debug_layer_available)
- .log_err()
- .is_some()
- {
- return Ok(adapter);
- }
- }
-
- unreachable!()
-}
-
-fn get_device(
- adapter: &IDXGIAdapter1,
- device: Option<*mut Option<ID3D11Device>>,
- context: Option<*mut Option<ID3D11DeviceContext>>,
- feature_level: Option<*mut D3D_FEATURE_LEVEL>,
- debug_layer_available: bool,
-) -> Result<()> {
- let device_flags = if debug_layer_available {
- D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_DEBUG
- } else {
- D3D11_CREATE_DEVICE_BGRA_SUPPORT
- };
- unsafe {
- D3D11CreateDevice(
- adapter,
- D3D_DRIVER_TYPE_UNKNOWN,
- HMODULE::default(),
- device_flags,
- // 4x MSAA is required for Direct3D Feature Level 10.1 or better
- Some(&[
- D3D_FEATURE_LEVEL_11_1,
- D3D_FEATURE_LEVEL_11_0,
- D3D_FEATURE_LEVEL_10_1,
- ]),
- D3D11_SDK_VERSION,
- device,
- feature_level,
- context,
- )?;
- }
- Ok(())
-}
-
#[inline]
fn get_comp_device(dxgi_device: &IDXGIDevice) -> Result<IDCompositionDevice> {
Ok(unsafe { DCompositionCreateDevice(dxgi_device)? })
@@ -1191,7 +1077,7 @@ fn create_swap_chain(
#[inline]
fn create_resources(
- devices: &DirectXDevices,
+ devices: &DirectXRendererDevices,
swap_chain: &IDXGISwapChain1,
width: u32,
height: u32,
@@ -25,6 +25,7 @@ pub(crate) const WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD: u32 = WM_USER + 3;
pub(crate) const WM_GPUI_DOCK_MENU_ACTION: u32 = WM_USER + 4;
pub(crate) const WM_GPUI_FORCE_UPDATE_WINDOW: u32 = WM_USER + 5;
pub(crate) const WM_GPUI_KEYBOARD_LAYOUT_CHANGED: u32 = WM_USER + 6;
+pub(crate) const WM_GPUI_GPU_DEVICE_LOST: u32 = WM_USER + 7;
const SIZE_MOVE_LOOP_TIMER_ID: usize = 1;
const AUTO_HIDE_TASKBAR_THICKNESS_PX: i32 = 1;
@@ -40,7 +41,6 @@ impl WindowsWindowInner {
let handled = match msg {
WM_ACTIVATE => self.handle_activate_msg(wparam),
WM_CREATE => self.handle_create_msg(handle),
- WM_DEVICECHANGE => self.handle_device_change_msg(handle, wparam),
WM_MOVE => self.handle_move_msg(handle, lparam),
WM_SIZE => self.handle_size_msg(wparam, lparam),
WM_GETMINMAXINFO => self.handle_get_min_max_info_msg(lparam),
@@ -104,6 +104,7 @@ impl WindowsWindowInner {
WM_SHOWWINDOW => self.handle_window_visibility_changed(handle, wparam),
WM_GPUI_CURSOR_STYLE_CHANGED => self.handle_cursor_changed(lparam),
WM_GPUI_FORCE_UPDATE_WINDOW => self.draw_window(handle, true),
+ WM_GPUI_GPU_DEVICE_LOST => self.handle_device_lost(lparam),
_ => None,
};
if let Some(n) = handled {
@@ -1167,26 +1168,12 @@ impl WindowsWindowInner {
None
}
- fn handle_device_change_msg(&self, handle: HWND, wparam: WPARAM) -> Option<isize> {
- if wparam.0 == DBT_DEVNODES_CHANGED as usize {
- // The reason for sending this message is to actually trigger a redraw of the window.
- unsafe {
- PostMessageW(
- Some(handle),
- WM_GPUI_FORCE_UPDATE_WINDOW,
- WPARAM(0),
- LPARAM(0),
- )
- .log_err();
- }
- // If the GPU device is lost, this redraw will take care of recreating the device context.
- // The WM_GPUI_FORCE_UPDATE_WINDOW message will take care of redrawing the window, after
- // the device context has been recreated.
- self.draw_window(handle, true)
- } else {
- // Other device change messages are not handled.
- None
- }
+ fn handle_device_lost(&self, lparam: LPARAM) -> Option<isize> {
+ let mut lock = self.state.borrow_mut();
+ let devices = lparam.0 as *const DirectXDevices;
+ let devices = unsafe { &*devices };
+ lock.renderer.handle_device_lost(&devices);
+ Some(0)
}
#[inline]
@@ -1,6 +1,7 @@
use std::{
cell::RefCell,
ffi::OsStr,
+ mem::ManuallyDrop,
path::{Path, PathBuf},
rc::{Rc, Weak},
sync::Arc,
@@ -17,7 +18,7 @@ use windows::{
UI::ViewManagement::UISettings,
Win32::{
Foundation::*,
- Graphics::Gdi::*,
+ Graphics::{Direct3D11::ID3D11Device, Gdi::*},
Security::Credentials::*,
System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*},
UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
@@ -55,6 +56,7 @@ pub(crate) struct WindowsPlatformState {
jump_list: JumpList,
// NOTE: standard cursor handles don't need to close.
pub(crate) current_cursor: Option<HCURSOR>,
+ directx_devices: ManuallyDrop<DirectXDevices>,
}
#[derive(Default)]
@@ -69,15 +71,17 @@ struct PlatformCallbacks {
}
impl WindowsPlatformState {
- fn new() -> Self {
+ fn new(directx_devices: DirectXDevices) -> Self {
let callbacks = PlatformCallbacks::default();
let jump_list = JumpList::new();
let current_cursor = load_cursor(CursorStyle::Arrow);
+ let directx_devices = ManuallyDrop::new(directx_devices);
Self {
callbacks,
jump_list,
current_cursor,
+ directx_devices,
menus: Vec::new(),
}
}
@@ -88,15 +92,21 @@ impl WindowsPlatform {
unsafe {
OleInitialize(None).context("unable to initialize Windows OLE")?;
}
+ let directx_devices = DirectXDevices::new().context("Creating DirectX devices")?;
let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
let validation_number = rand::random::<usize>();
let raw_window_handles = Arc::new(RwLock::new(SmallVec::new()));
+ let text_system = Arc::new(
+ DirectWriteTextSystem::new(&directx_devices)
+ .context("Error creating DirectWriteTextSystem")?,
+ );
register_platform_window_class();
let mut context = PlatformWindowCreateContext {
inner: None,
raw_window_handles: Arc::downgrade(&raw_window_handles),
validation_number,
main_receiver: Some(main_receiver),
+ directx_devices: Some(directx_devices),
};
let result = unsafe {
CreateWindowExW(
@@ -125,12 +135,7 @@ impl WindowsPlatform {
.is_ok_and(|value| value == "true" || value == "1");
let background_executor = BackgroundExecutor::new(dispatcher.clone());
let foreground_executor = ForegroundExecutor::new(dispatcher);
- let directx_devices = DirectXDevices::new(disable_direct_composition)
- .context("Unable to init directx devices.")?;
- let text_system = Arc::new(
- DirectWriteTextSystem::new(&directx_devices)
- .context("Error creating DirectWriteTextSystem")?,
- );
+
let drop_target_helper: IDropTargetHelper = unsafe {
CoCreateInstance(&CLSID_DragDropHelper, None, CLSCTX_INPROC_SERVER)
.context("Error creating drop target helper.")?
@@ -181,6 +186,7 @@ impl WindowsPlatform {
main_receiver: self.inner.main_receiver.clone(),
platform_window_handle: self.handle,
disable_direct_composition: self.disable_direct_composition,
+ directx_devices: (*self.inner.state.borrow().directx_devices).clone(),
}
}
@@ -228,11 +234,24 @@ impl WindowsPlatform {
}
fn begin_vsync_thread(&self) {
+ let mut directx_device = (*self.inner.state.borrow().directx_devices).clone();
+ let platform_window: SafeHwnd = self.handle.into();
+ let validation_number = self.inner.validation_number;
let all_windows = Arc::downgrade(&self.raw_window_handles);
+ let text_system = Arc::downgrade(&self.text_system);
std::thread::spawn(move || {
let vsync_provider = VSyncProvider::new();
loop {
vsync_provider.wait_for_vsync();
+ if check_device_lost(&directx_device.device) {
+ handle_gpu_device_lost(
+ &mut directx_device,
+ platform_window.as_raw(),
+ validation_number,
+ &all_windows,
+ &text_system,
+ );
+ }
let Some(all_windows) = all_windows.upgrade() else {
break;
};
@@ -647,7 +666,9 @@ impl Platform for WindowsPlatform {
impl WindowsPlatformInner {
fn new(context: &mut PlatformWindowCreateContext) -> Result<Rc<Self>> {
- let state = RefCell::new(WindowsPlatformState::new());
+ let state = RefCell::new(WindowsPlatformState::new(
+ context.directx_devices.take().unwrap(),
+ ));
Ok(Rc::new(Self {
state,
raw_window_handles: context.raw_window_handles.clone(),
@@ -667,7 +688,8 @@ impl WindowsPlatformInner {
WM_GPUI_CLOSE_ONE_WINDOW
| WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD
| WM_GPUI_DOCK_MENU_ACTION
- | WM_GPUI_KEYBOARD_LAYOUT_CHANGED => self.handle_gpui_events(msg, wparam, lparam),
+ | WM_GPUI_KEYBOARD_LAYOUT_CHANGED
+ | WM_GPUI_GPU_DEVICE_LOST => self.handle_gpui_events(msg, wparam, lparam),
_ => None,
};
if let Some(result) = handled {
@@ -692,6 +714,7 @@ impl WindowsPlatformInner {
WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD => self.run_foreground_task(),
WM_GPUI_DOCK_MENU_ACTION => self.handle_dock_action_event(lparam.0 as _),
WM_GPUI_KEYBOARD_LAYOUT_CHANGED => self.handle_keyboard_layout_change(),
+ WM_GPUI_GPU_DEVICE_LOST => self.handle_device_lost(lparam),
_ => unreachable!(),
}
}
@@ -749,6 +772,18 @@ impl WindowsPlatformInner {
self.state.borrow_mut().callbacks.keyboard_layout_change = Some(callback);
Some(0)
}
+
+ fn handle_device_lost(&self, lparam: LPARAM) -> Option<isize> {
+ let mut lock = self.state.borrow_mut();
+ let directx_devices = lparam.0 as *const DirectXDevices;
+ let directx_devices = unsafe { &*directx_devices };
+ unsafe {
+ ManuallyDrop::drop(&mut lock.directx_devices);
+ }
+ lock.directx_devices = ManuallyDrop::new(directx_devices.clone());
+
+ Some(0)
+ }
}
impl Drop for WindowsPlatform {
@@ -762,6 +797,14 @@ impl Drop for WindowsPlatform {
}
}
+impl Drop for WindowsPlatformState {
+ fn drop(&mut self) {
+ unsafe {
+ ManuallyDrop::drop(&mut self.directx_devices);
+ }
+ }
+}
+
pub(crate) struct WindowCreationInfo {
pub(crate) icon: HICON,
pub(crate) executor: ForegroundExecutor,
@@ -772,6 +815,7 @@ pub(crate) struct WindowCreationInfo {
pub(crate) main_receiver: flume::Receiver<Runnable>,
pub(crate) platform_window_handle: HWND,
pub(crate) disable_direct_composition: bool,
+ pub(crate) directx_devices: DirectXDevices,
}
struct PlatformWindowCreateContext {
@@ -779,6 +823,7 @@ struct PlatformWindowCreateContext {
raw_window_handles: std::sync::Weak<RwLock<SmallVec<[SafeHwnd; 4]>>>,
validation_number: usize,
main_receiver: Option<flume::Receiver<Runnable>>,
+ directx_devices: Option<DirectXDevices>,
}
fn open_target(target: impl AsRef<OsStr>) -> Result<()> {
@@ -951,6 +996,80 @@ fn should_auto_hide_scrollbars() -> Result<bool> {
Ok(ui_settings.AutoHideScrollBars()?)
}
+fn check_device_lost(device: &ID3D11Device) -> bool {
+ let device_state = unsafe { device.GetDeviceRemovedReason() };
+ match device_state {
+ Ok(_) => false,
+ Err(err) => {
+ log::error!("DirectX device lost detected: {:?}", err);
+ true
+ }
+ }
+}
+
+fn handle_gpu_device_lost(
+ directx_devices: &mut DirectXDevices,
+ platform_window: HWND,
+ validation_number: usize,
+ all_windows: &std::sync::Weak<RwLock<SmallVec<[SafeHwnd; 4]>>>,
+ text_system: &std::sync::Weak<DirectWriteTextSystem>,
+) {
+ // Here we wait a bit to ensure the the system has time to recover from the device lost state.
+ // If we don't wait, the final drawing result will be blank.
+ std::thread::sleep(std::time::Duration::from_millis(350));
+
+ try_to_recover_from_device_lost(
+ || {
+ DirectXDevices::new()
+ .context("Failed to recreate new DirectX devices after device lost")
+ },
+ |new_devices| *directx_devices = new_devices,
+ || {
+ log::error!("Failed to recover DirectX devices after multiple attempts.");
+ // Do something here?
+ // At this point, the device loss is considered unrecoverable.
+ // std::process::exit(1);
+ },
+ );
+ log::info!("DirectX devices successfully recreated.");
+
+ unsafe {
+ SendMessageW(
+ platform_window,
+ WM_GPUI_GPU_DEVICE_LOST,
+ Some(WPARAM(validation_number)),
+ Some(LPARAM(directx_devices as *const _ as _)),
+ );
+ }
+
+ if let Some(text_system) = text_system.upgrade() {
+ text_system.handle_gpu_lost(&directx_devices);
+ }
+ if let Some(all_windows) = all_windows.upgrade() {
+ for window in all_windows.read().iter() {
+ unsafe {
+ SendMessageW(
+ window.as_raw(),
+ WM_GPUI_GPU_DEVICE_LOST,
+ Some(WPARAM(validation_number)),
+ Some(LPARAM(directx_devices as *const _ as _)),
+ );
+ }
+ }
+ std::thread::sleep(std::time::Duration::from_millis(200));
+ for window in all_windows.read().iter() {
+ unsafe {
+ SendMessageW(
+ window.as_raw(),
+ WM_GPUI_FORCE_UPDATE_WINDOW,
+ Some(WPARAM(validation_number)),
+ None,
+ );
+ }
+ }
+ }
+}
+
const PLATFORM_WINDOW_CLASS_NAME: PCWSTR = w!("Zed::PlatformWindow");
fn register_platform_window_class() {
@@ -79,6 +79,7 @@ pub(crate) struct WindowsWindowInner {
impl WindowsWindowState {
fn new(
hwnd: HWND,
+ directx_devices: &DirectXDevices,
window_params: &CREATESTRUCTW,
current_cursor: Option<HCURSOR>,
display: WindowsDisplay,
@@ -104,7 +105,7 @@ impl WindowsWindowState {
};
let border_offset = WindowBorderOffset::default();
let restore_from_minimized = None;
- let renderer = DirectXRenderer::new(hwnd, disable_direct_composition)
+ let renderer = DirectXRenderer::new(hwnd, directx_devices, disable_direct_composition)
.context("Creating DirectX renderer")?;
let callbacks = Callbacks::default();
let input_handler = None;
@@ -205,9 +206,10 @@ impl WindowsWindowState {
}
impl WindowsWindowInner {
- fn new(context: &WindowCreateContext, hwnd: HWND, cs: &CREATESTRUCTW) -> Result<Rc<Self>> {
+ fn new(context: &mut WindowCreateContext, hwnd: HWND, cs: &CREATESTRUCTW) -> Result<Rc<Self>> {
let state = RefCell::new(WindowsWindowState::new(
hwnd,
+ &context.directx_devices,
cs,
context.current_cursor,
context.display,
@@ -345,6 +347,7 @@ struct WindowCreateContext {
platform_window_handle: HWND,
appearance: WindowAppearance,
disable_direct_composition: bool,
+ directx_devices: DirectXDevices,
}
impl WindowsWindow {
@@ -363,6 +366,7 @@ impl WindowsWindow {
main_receiver,
platform_window_handle,
disable_direct_composition,
+ directx_devices,
} = creation_info;
register_window_class(icon);
let hide_title_bar = params
@@ -422,6 +426,7 @@ impl WindowsWindow {
platform_window_handle,
appearance,
disable_direct_composition,
+ directx_devices,
};
let creation_result = unsafe {
CreateWindowExW(