Skip to content

Commit

Permalink
[wgl] Create a hidden window per instance (#4603)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zoxc authored Oct 30, 2023
1 parent 37fe6a5 commit ff1ba0b
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 47 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
177 changes: 130 additions & 47 deletions wgpu-hal/src/gles/wgl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
},
},
};
Expand Down Expand Up @@ -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 }
}
Expand Down Expand Up @@ -134,7 +138,7 @@ unsafe impl Sync for WglContext {}

struct Inner {
gl: glow::Context,
device: HDC,
device: InstanceDevice,
context: WglContext,
}

Expand Down Expand Up @@ -219,7 +223,7 @@ unsafe fn setup_pixel_format(dc: HDC) -> Result<(), crate::InstanceError> {
Ok(())
}

fn create_global_device_context() -> Result<HDC, crate::InstanceError> {
fn create_global_window_class() -> Result<CString, crate::InstanceError> {
let instance = unsafe { GetModuleHandleA(ptr::null()) };
if instance.is_null() {
return Err(crate::InstanceError::with_source(
Expand Down Expand Up @@ -269,54 +273,132 @@ fn create_global_device_context() -> Result<HDC, crate::InstanceError> {
));
}

// 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<CString, crate::InstanceError> {
static GLOBAL: Lazy<Result<CString, crate::InstanceError>> =
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<HDC, crate::InstanceError> {
fn create_instance_device() -> Result<InstanceDevice, crate::InstanceError> {
#[derive(Clone, Copy)]
struct SendDc(HDC);
unsafe impl Sync for SendDc {}
unsafe impl Send for SendDc {}

static GLOBAL: Lazy<Result<SendDc, crate::InstanceError>> =
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<super::Api> for Instance {
Expand All @@ -330,7 +412,8 @@ impl crate::Instance<super::Api> 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() {
Expand Down Expand Up @@ -420,7 +503,7 @@ impl crate::Instance<super::Api> for Instance {

Ok(Instance {
inner: Arc::new(Mutex::new(Inner {
device: dc,
device,
gl,
context,
})),
Expand Down

0 comments on commit ff1ba0b

Please sign in to comment.