From ff1ba0b4fec93bf4c56f4bbb0e206cd85cd16371 Mon Sep 17 00:00:00 2001 From: Zoxc Date: Mon, 30 Oct 2023 06:10:01 +0100 Subject: [PATCH] [wgl] Create a hidden window per instance (#4603) --- CHANGELOG.md | 7 ++ wgpu-hal/src/gles/wgl.rs | 177 ++++++++++++++++++++++++++++----------- 2 files changed, 137 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 725f72b991..0e47daa9bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,13 @@ Bottom level categories: For naga changelogs at or before v0.14.0. See [naga's changelog](naga/CHANGELOG.md). +### Bug Fixes + + +#### WGL + +- Create a hidden window per `wgpu::Instance` instead of sharing a global one. + ## v0.18.0 (2023-10-25) ### Desktop OpenGL 3.3+ Support on Windows diff --git a/wgpu-hal/src/gles/wgl.rs b/wgpu-hal/src/gles/wgl.rs index 46642f6be6..4554d7a705 100644 --- a/wgpu-hal/src/gles/wgl.rs +++ b/wgpu-hal/src/gles/wgl.rs @@ -13,7 +13,11 @@ use std::{ mem, os::raw::c_int, ptr, - sync::Arc, + sync::{ + mpsc::{sync_channel, SyncSender}, + Arc, + }, + thread, time::Duration, }; use wgt::InstanceFlags; @@ -31,8 +35,8 @@ use winapi::{ PIXELFORMATDESCRIPTOR, }, winuser::{ - CreateWindowExA, DefWindowProcA, GetDC, RegisterClassExA, ReleaseDC, CS_OWNDC, - WNDCLASSEXA, + CreateWindowExA, DefWindowProcA, DestroyWindow, GetDC, RegisterClassExA, ReleaseDC, + CS_OWNDC, WNDCLASSEXA, }, }, }; @@ -69,7 +73,7 @@ impl AdapterContext { .try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS)) .expect("Could not lock adapter context. This is most-likely a deadlock."); - inner.context.make_current(inner.device).unwrap(); + inner.context.make_current(inner.device.dc).unwrap(); AdapterContextLock { inner } } @@ -134,7 +138,7 @@ unsafe impl Sync for WglContext {} struct Inner { gl: glow::Context, - device: HDC, + device: InstanceDevice, context: WglContext, } @@ -219,7 +223,7 @@ unsafe fn setup_pixel_format(dc: HDC) -> Result<(), crate::InstanceError> { Ok(()) } -fn create_global_device_context() -> Result { +fn create_global_window_class() -> Result { let instance = unsafe { GetModuleHandleA(ptr::null()) }; if instance.is_null() { return Err(crate::InstanceError::with_source( @@ -269,54 +273,132 @@ fn create_global_device_context() -> Result { )); } - // Create a hidden window since we don't pass `WS_VISIBLE`. - let window = unsafe { - CreateWindowExA( - 0, - name.as_ptr(), - name.as_ptr(), - 0, - 0, - 0, - 1, - 1, - ptr::null_mut(), - ptr::null_mut(), - instance, - ptr::null_mut(), - ) - }; - if window.is_null() { - return Err(crate::InstanceError::with_source( - String::from("unable to create hidden instance window"), - Error::last_os_error(), - )); - } - let dc = unsafe { GetDC(window) }; - if dc.is_null() { - return Err(crate::InstanceError::with_source( - String::from("unable to create memory device"), - Error::last_os_error(), - )); - } - unsafe { setup_pixel_format(dc)? }; + // We intentionally leak the window class as we only need one per process. + + Ok(name) +} - // We intentionally leak the window class, window and device context handle to avoid - // spawning a thread to destroy them. We cannot use `DestroyWindow` and `ReleaseDC` on - // different threads. +fn get_global_window_class() -> Result { + static GLOBAL: Lazy> = + Lazy::new(create_global_window_class); + GLOBAL.clone() +} + +struct InstanceDevice { + dc: HDC, - Ok(dc) + /// This is used to keep the thread owning `dc` alive until this struct is dropped. + _tx: SyncSender<()>, } -fn get_global_device_context() -> Result { +fn create_instance_device() -> Result { #[derive(Clone, Copy)] struct SendDc(HDC); unsafe impl Sync for SendDc {} unsafe impl Send for SendDc {} - static GLOBAL: Lazy> = - Lazy::new(|| create_global_device_context().map(SendDc)); - GLOBAL.clone().map(|dc| dc.0) + struct Window { + window: HWND, + } + impl Drop for Window { + fn drop(&mut self) { + unsafe { + if DestroyWindow(self.window) == FALSE { + log::error!("failed to destroy window {}", Error::last_os_error()); + } + }; + } + } + struct DeviceContextHandle { + dc: HDC, + window: HWND, + } + impl Drop for DeviceContextHandle { + fn drop(&mut self) { + unsafe { + ReleaseDC(self.window, self.dc); + }; + } + } + + let window_class = get_global_window_class()?; + + let (drop_tx, drop_rx) = sync_channel(0); + let (setup_tx, setup_rx) = sync_channel(0); + + // We spawn a thread which owns the hidden window for this instance. + thread::Builder::new() + .stack_size(256 * 1024) + .name("wgpu-hal WGL Instance Thread".to_owned()) + .spawn(move || { + let setup = (|| { + let instance = unsafe { GetModuleHandleA(ptr::null()) }; + if instance.is_null() { + return Err(crate::InstanceError::with_source( + String::from("unable to get executable instance"), + Error::last_os_error(), + )); + } + + // Create a hidden window since we don't pass `WS_VISIBLE`. + let window = unsafe { + CreateWindowExA( + 0, + window_class.as_ptr(), + window_class.as_ptr(), + 0, + 0, + 0, + 1, + 1, + ptr::null_mut(), + ptr::null_mut(), + instance, + ptr::null_mut(), + ) + }; + if window.is_null() { + return Err(crate::InstanceError::with_source( + String::from("unable to create hidden instance window"), + Error::last_os_error(), + )); + } + let window = Window { window }; + + let dc = unsafe { GetDC(window.window) }; + if dc.is_null() { + return Err(crate::InstanceError::with_source( + String::from("unable to create memory device"), + Error::last_os_error(), + )); + } + let dc = DeviceContextHandle { + dc, + window: window.window, + }; + unsafe { setup_pixel_format(dc.dc)? }; + + Ok((window, dc)) + })(); + + match setup { + Ok((_window, dc)) => { + setup_tx.send(Ok(SendDc(dc.dc))).unwrap(); + // Wait for the shutdown event to free the window and device context handle. + drop_rx.recv().ok(); + } + Err(err) => { + setup_tx.send(Err(err)).unwrap(); + } + } + }) + .map_err(|e| { + crate::InstanceError::with_source(String::from("unable to create instance thread"), e) + })?; + + let dc = setup_rx.recv().unwrap()?.0; + + Ok(InstanceDevice { dc, _tx: drop_tx }) } impl crate::Instance for Instance { @@ -330,7 +412,8 @@ impl crate::Instance for Instance { )); } - let dc = get_global_device_context()?; + let device = create_instance_device()?; + let dc = device.dc; let context = unsafe { wglCreateContext(dc) }; if context.is_null() { @@ -420,7 +503,7 @@ impl crate::Instance for Instance { Ok(Instance { inner: Arc::new(Mutex::new(Inner { - device: dc, + device, gl, context, })),