windows: Keep just one copy of GPU instance (#37445)

Now we only keep a single copy of the GPU device. The GPU lost handling
got broken after #35376, but it’s properly handled again now.

Release Notes:

- N/A
This commit is contained in:
张小白
2025-09-04 21:31:12 +08:00
committed by GitHub
parent fca44f89c1
commit 28c78d2d85
7 changed files with 431 additions and 234 deletions
+2
View File
@@ -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();
log::error!(
"DirectX device removed or reset when drawing. Reason: {:?}",
reason
);
self.handle_device_lost()?;
} else {
result.ok()?;
}
}
Ok(())
let result = unsafe { self.resources.swap_chain.Present(0, DXGI_PRESENT(0)) };
result.ok().context("Presenting swap chain failed")
}
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));
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!(
"DirectXRenderer failed to recover from device lost after multiple attempts"
);
// Do something here?
// At this point, the device loss is considered unrecoverable.
},
);
}
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,
+8 -21
View File
@@ -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]
+129 -10
View File
@@ -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() {
+7 -2
View File
@@ -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(