use std::{
collections::HashSet,
ffi::{c_void, CStr, CString},
mem::{self, size_of, size_of_val, ManuallyDrop},
os::raw::c_int,
ptr,
sync::{
mpsc::{sync_channel, SyncSender},
Arc,
},
thread,
time::Duration,
};
use glow::HasContext;
use glutin_wgl_sys::wgl_extra::{
Wgl, CONTEXT_CORE_PROFILE_BIT_ARB, CONTEXT_DEBUG_BIT_ARB, CONTEXT_FLAGS_ARB,
CONTEXT_PROFILE_MASK_ARB,
};
use once_cell::sync::Lazy;
use parking_lot::{Mutex, MutexGuard, RwLock};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
use wgt::InstanceFlags;
use windows::{
core::{Error, PCSTR},
Win32::{
Foundation,
Graphics::{Gdi, OpenGL},
System::LibraryLoader,
UI::WindowsAndMessaging,
},
};
const CONTEXT_LOCK_TIMEOUT_SECS: u64 = 1;
pub struct AdapterContext {
inner: Arc<Mutex<Inner>>,
}
unsafe impl Sync for AdapterContext {}
unsafe impl Send for AdapterContext {}
impl AdapterContext {
pub fn is_owned(&self) -> bool {
true
}
pub fn raw_context(&self) -> *mut c_void {
match self.inner.lock().context {
Some(ref wgl) => wgl.context.0,
None => ptr::null_mut(),
}
}
#[track_caller]
pub fn lock(&self) -> AdapterContextLock<'_> {
let inner = self
.inner
.try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS))
.expect("Could not lock adapter context. This is most-likely a deadlock.");
if let Some(wgl) = &inner.context {
wgl.make_current(inner.device.dc).unwrap()
};
AdapterContextLock { inner }
}
#[track_caller]
fn lock_with_dc(&self, device: Gdi::HDC) -> windows::core::Result<AdapterContextLock<'_>> {
let inner = self
.inner
.try_lock_for(Duration::from_secs(CONTEXT_LOCK_TIMEOUT_SECS))
.expect("Could not lock adapter context. This is most-likely a deadlock.");
if let Some(wgl) = &inner.context {
wgl.make_current(device)?;
}
Ok(AdapterContextLock { inner })
}
}
pub struct AdapterContextLock<'a> {
inner: MutexGuard<'a, Inner>,
}
impl<'a> std::ops::Deref for AdapterContextLock<'a> {
type Target = glow::Context;
fn deref(&self) -> &Self::Target {
&self.inner.gl
}
}
impl<'a> Drop for AdapterContextLock<'a> {
fn drop(&mut self) {
if let Some(wgl) = &self.inner.context {
wgl.unmake_current().unwrap()
}
}
}
struct WglContext {
context: OpenGL::HGLRC,
}
impl WglContext {
fn make_current(&self, device: Gdi::HDC) -> windows::core::Result<()> {
unsafe { OpenGL::wglMakeCurrent(device, self.context) }
}
fn unmake_current(&self) -> windows::core::Result<()> {
if unsafe { OpenGL::wglGetCurrentContext() }.is_invalid() {
return Ok(());
}
unsafe { OpenGL::wglMakeCurrent(None, None) }
}
}
impl Drop for WglContext {
fn drop(&mut self) {
if let Err(e) = unsafe { OpenGL::wglDeleteContext(self.context) } {
log::error!("failed to delete WGL context: {e}");
}
}
}
unsafe impl Send for WglContext {}
unsafe impl Sync for WglContext {}
struct Inner {
gl: ManuallyDrop<glow::Context>,
device: InstanceDevice,
context: Option<WglContext>,
}
impl Drop for Inner {
fn drop(&mut self) {
struct CurrentGuard<'a>(&'a WglContext);
impl Drop for CurrentGuard<'_> {
fn drop(&mut self) {
self.0.unmake_current().unwrap();
}
}
let _guard = self.context.as_ref().map(|wgl| {
wgl.make_current(self.device.dc).unwrap();
CurrentGuard(wgl)
});
unsafe { ManuallyDrop::drop(&mut self.gl) };
}
}
unsafe impl Send for Inner {}
unsafe impl Sync for Inner {}
pub struct Instance {
srgb_capable: bool,
inner: Arc<Mutex<Inner>>,
}
unsafe impl Send for Instance {}
unsafe impl Sync for Instance {}
fn load_gl_func(name: &str, module: Option<Foundation::HMODULE>) -> *const c_void {
let addr = CString::new(name.as_bytes()).unwrap();
let mut ptr = unsafe { OpenGL::wglGetProcAddress(PCSTR(addr.as_ptr().cast())) };
if ptr.is_none() {
if let Some(module) = module {
ptr = unsafe { LibraryLoader::GetProcAddress(module, PCSTR(addr.as_ptr().cast())) };
}
}
ptr.map_or_else(ptr::null_mut, |p| p as *mut c_void)
}
fn get_extensions(extra: &Wgl, dc: Gdi::HDC) -> HashSet<String> {
if extra.GetExtensionsStringARB.is_loaded() {
unsafe { CStr::from_ptr(extra.GetExtensionsStringARB(dc.0)) }
.to_str()
.unwrap_or("")
} else {
""
}
.split(' ')
.map(|s| s.to_owned())
.collect()
}
unsafe fn setup_pixel_format(dc: Gdi::HDC) -> Result<(), crate::InstanceError> {
{
let format = OpenGL::PIXELFORMATDESCRIPTOR {
nVersion: 1,
nSize: size_of::<OpenGL::PIXELFORMATDESCRIPTOR>() as u16,
dwFlags: OpenGL::PFD_DRAW_TO_WINDOW
| OpenGL::PFD_SUPPORT_OPENGL
| OpenGL::PFD_DOUBLEBUFFER,
iPixelType: OpenGL::PFD_TYPE_RGBA,
cColorBits: 8,
..unsafe { mem::zeroed() }
};
let index = unsafe { OpenGL::ChoosePixelFormat(dc, &format) };
if index == 0 {
return Err(crate::InstanceError::with_source(
String::from("unable to choose pixel format"),
Error::from_win32(),
));
}
let current = unsafe { OpenGL::GetPixelFormat(dc) };
if index != current {
unsafe { OpenGL::SetPixelFormat(dc, index, &format) }.map_err(|e| {
crate::InstanceError::with_source(String::from("unable to set pixel format"), e)
})?;
}
}
{
let index = unsafe { OpenGL::GetPixelFormat(dc) };
if index == 0 {
return Err(crate::InstanceError::with_source(
String::from("unable to get pixel format index"),
Error::from_win32(),
));
}
let mut format = Default::default();
if unsafe {
OpenGL::DescribePixelFormat(dc, index, size_of_val(&format) as u32, Some(&mut format))
} == 0
{
return Err(crate::InstanceError::with_source(
String::from("unable to read pixel format"),
Error::from_win32(),
));
}
if !format.dwFlags.contains(OpenGL::PFD_SUPPORT_OPENGL)
|| format.iPixelType != OpenGL::PFD_TYPE_RGBA
{
return Err(crate::InstanceError::new(String::from(
"unsuitable pixel format",
)));
}
}
Ok(())
}
fn create_global_window_class() -> Result<CString, crate::InstanceError> {
let instance = unsafe { LibraryLoader::GetModuleHandleA(None) }.map_err(|e| {
crate::InstanceError::with_source(String::from("unable to get executable instance"), e)
})?;
static UNIQUE: Mutex<u8> = Mutex::new(0);
let class_addr: *const _ = &UNIQUE;
let name = format!("wgpu Device Class {:x}\0", class_addr as usize);
let name = CString::from_vec_with_nul(name.into_bytes()).unwrap();
unsafe extern "system" fn wnd_proc(
window: Foundation::HWND,
msg: u32,
wparam: Foundation::WPARAM,
lparam: Foundation::LPARAM,
) -> Foundation::LRESULT {
unsafe { WindowsAndMessaging::DefWindowProcA(window, msg, wparam, lparam) }
}
let window_class = WindowsAndMessaging::WNDCLASSEXA {
cbSize: size_of::<WindowsAndMessaging::WNDCLASSEXA>() as u32,
style: WindowsAndMessaging::CS_OWNDC,
lpfnWndProc: Some(wnd_proc),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: instance.into(),
hIcon: WindowsAndMessaging::HICON::default(),
hCursor: WindowsAndMessaging::HCURSOR::default(),
hbrBackground: Gdi::HBRUSH::default(),
lpszMenuName: PCSTR::null(),
lpszClassName: PCSTR(name.as_ptr().cast()),
hIconSm: WindowsAndMessaging::HICON::default(),
};
let atom = unsafe { WindowsAndMessaging::RegisterClassExA(&window_class) };
if atom == 0 {
return Err(crate::InstanceError::with_source(
String::from("unable to register window class"),
Error::from_win32(),
));
}
Ok(name)
}
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: Gdi::HDC,
_tx: SyncSender<()>,
}
fn create_instance_device() -> Result<InstanceDevice, crate::InstanceError> {
#[derive(Clone, Copy)]
struct SendDc(Gdi::HDC);
unsafe impl Sync for SendDc {}
unsafe impl Send for SendDc {}
struct Window {
window: Foundation::HWND,
}
impl Drop for Window {
fn drop(&mut self) {
if let Err(e) = unsafe { WindowsAndMessaging::DestroyWindow(self.window) } {
log::error!("failed to destroy window: {e}");
}
}
}
let window_class = get_global_window_class()?;
let (drop_tx, drop_rx) = sync_channel(0);
let (setup_tx, setup_rx) = sync_channel(0);
thread::Builder::new()
.stack_size(256 * 1024)
.name("wgpu-hal WGL Instance Thread".to_owned())
.spawn(move || {
let setup = (|| {
let instance = unsafe { LibraryLoader::GetModuleHandleA(None) }.map_err(|e| {
crate::InstanceError::with_source(
String::from("unable to get executable instance"),
e,
)
})?;
let window = unsafe {
WindowsAndMessaging::CreateWindowExA(
WindowsAndMessaging::WINDOW_EX_STYLE::default(),
PCSTR(window_class.as_ptr().cast()),
PCSTR(window_class.as_ptr().cast()),
WindowsAndMessaging::WINDOW_STYLE::default(),
0,
0,
1,
1,
None,
None,
instance,
None,
)
}
.map_err(|e| {
crate::InstanceError::with_source(
String::from("unable to create hidden instance window"),
e,
)
})?;
let window = Window { window };
let dc = unsafe { Gdi::GetDC(window.window) };
if dc.is_invalid() {
return Err(crate::InstanceError::with_source(
String::from("unable to create memory device"),
Error::from_win32(),
));
}
let dc = DeviceContextHandle {
device: dc,
window: window.window,
};
unsafe { setup_pixel_format(dc.device)? };
Ok((window, dc))
})();
match setup {
Ok((_window, dc)) => {
setup_tx.send(Ok(SendDc(dc.device))).unwrap();
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 {
type A = super::Api;
unsafe fn init(desc: &crate::InstanceDescriptor) -> Result<Self, crate::InstanceError> {
profiling::scope!("Init OpenGL (WGL) Backend");
let opengl_module =
unsafe { LibraryLoader::LoadLibraryA(PCSTR("opengl32.dll\0".as_ptr())) }.map_err(
|e| {
crate::InstanceError::with_source(
String::from("unable to load the OpenGL library"),
e,
)
},
)?;
let device = create_instance_device()?;
let dc = device.dc;
let context = unsafe { OpenGL::wglCreateContext(dc) }.map_err(|e| {
crate::InstanceError::with_source(
String::from("unable to create initial OpenGL context"),
e,
)
})?;
let context = WglContext { context };
context.make_current(dc).map_err(|e| {
crate::InstanceError::with_source(
String::from("unable to set initial OpenGL context as current"),
e,
)
})?;
let extra = Wgl::load_with(|name| load_gl_func(name, None));
let extensions = get_extensions(&extra, dc);
let can_use_profile = extensions.contains("WGL_ARB_create_context_profile")
&& extra.CreateContextAttribsARB.is_loaded();
let context = if can_use_profile {
let attributes = [
CONTEXT_PROFILE_MASK_ARB as c_int,
CONTEXT_CORE_PROFILE_BIT_ARB as c_int,
CONTEXT_FLAGS_ARB as c_int,
if desc.flags.contains(InstanceFlags::DEBUG) {
CONTEXT_DEBUG_BIT_ARB as c_int
} else {
0
},
0, ];
let context =
unsafe { extra.CreateContextAttribsARB(dc.0, ptr::null(), attributes.as_ptr()) };
if context.is_null() {
return Err(crate::InstanceError::with_source(
String::from("unable to create OpenGL context"),
Error::from_win32(),
));
}
WglContext {
context: OpenGL::HGLRC(context.cast_mut()),
}
} else {
context
};
context.make_current(dc).map_err(|e| {
crate::InstanceError::with_source(
String::from("unable to set OpenGL context as current"),
e,
)
})?;
let mut gl = unsafe {
glow::Context::from_loader_function(|name| load_gl_func(name, Some(opengl_module)))
};
let extra = Wgl::load_with(|name| load_gl_func(name, None));
let extensions = get_extensions(&extra, dc);
let srgb_capable = extensions.contains("WGL_EXT_framebuffer_sRGB")
|| extensions.contains("WGL_ARB_framebuffer_sRGB")
|| gl
.supported_extensions()
.contains("GL_ARB_framebuffer_sRGB");
if srgb_capable {
unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) };
}
if desc.flags.contains(InstanceFlags::VALIDATION) && gl.supports_debug() {
log::debug!("Enabling GL debug output");
unsafe { gl.enable(glow::DEBUG_OUTPUT) };
unsafe { gl.debug_message_callback(super::gl_debug_message_callback) };
}
let gl = ManuallyDrop::new(gl);
context.unmake_current().map_err(|e| {
crate::InstanceError::with_source(
String::from("unable to unset the current WGL context"),
e,
)
})?;
Ok(Instance {
inner: Arc::new(Mutex::new(Inner {
device,
gl,
context: Some(context),
})),
srgb_capable,
})
}
#[cfg_attr(target_os = "macos", allow(unused, unused_mut, unreachable_code))]
unsafe fn create_surface(
&self,
_display_handle: RawDisplayHandle,
window_handle: RawWindowHandle,
) -> Result<Surface, crate::InstanceError> {
let window = if let RawWindowHandle::Win32(handle) = window_handle {
handle
} else {
return Err(crate::InstanceError::new(format!(
"unsupported window: {window_handle:?}"
)));
};
Ok(Surface {
window: Foundation::HWND(window.hwnd.get() as *mut _),
presentable: true,
swapchain: RwLock::new(None),
srgb_capable: self.srgb_capable,
})
}
unsafe fn enumerate_adapters(
&self,
_surface_hint: Option<&Surface>,
) -> Vec<crate::ExposedAdapter<super::Api>> {
unsafe {
super::Adapter::expose(AdapterContext {
inner: self.inner.clone(),
})
}
.into_iter()
.collect()
}
}
impl super::Adapter {
pub unsafe fn new_external(
fun: impl FnMut(&str) -> *const c_void,
) -> Option<crate::ExposedAdapter<super::Api>> {
let context = unsafe { glow::Context::from_loader_function(fun) };
unsafe {
Self::expose(AdapterContext {
inner: Arc::new(Mutex::new(Inner {
gl: ManuallyDrop::new(context),
device: create_instance_device().ok()?,
context: None,
})),
})
}
}
pub fn adapter_context(&self) -> &AdapterContext {
&self.shared.context
}
}
impl super::Device {
pub fn context(&self) -> &AdapterContext {
&self.shared.context
}
}
struct DeviceContextHandle {
device: Gdi::HDC,
window: Foundation::HWND,
}
impl Drop for DeviceContextHandle {
fn drop(&mut self) {
unsafe {
Gdi::ReleaseDC(self.window, self.device);
};
}
}
pub struct Swapchain {
framebuffer: glow::Framebuffer,
renderbuffer: glow::Renderbuffer,
extent: wgt::Extent3d,
format: wgt::TextureFormat,
format_desc: super::TextureFormatDesc,
#[allow(unused)]
sample_type: wgt::TextureSampleType,
}
pub struct Surface {
window: Foundation::HWND,
pub(super) presentable: bool,
swapchain: RwLock<Option<Swapchain>>,
srgb_capable: bool,
}
unsafe impl Send for Surface {}
unsafe impl Sync for Surface {}
impl Surface {
pub(super) unsafe fn present(
&self,
_suf_texture: super::Texture,
context: &AdapterContext,
) -> Result<(), crate::SurfaceError> {
let swapchain = self.swapchain.read();
let sc = swapchain.as_ref().unwrap();
let dc = unsafe { Gdi::GetDC(self.window) };
if dc.is_invalid() {
log::error!(
"unable to get the device context from window: {}",
Error::from_win32()
);
return Err(crate::SurfaceError::Other(
"unable to get the device context from window",
));
}
let dc = DeviceContextHandle {
device: dc,
window: self.window,
};
let gl = context.lock_with_dc(dc.device).map_err(|e| {
log::error!("unable to make the OpenGL context current for surface: {e}",);
crate::SurfaceError::Other("unable to make the OpenGL context current for surface")
})?;
unsafe { gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None) };
unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(sc.framebuffer)) };
if self.srgb_capable {
unsafe { gl.disable(glow::FRAMEBUFFER_SRGB) };
}
unsafe {
gl.blit_framebuffer(
0,
sc.extent.height as i32,
sc.extent.width as i32,
0,
0,
0,
sc.extent.width as i32,
sc.extent.height as i32,
glow::COLOR_BUFFER_BIT,
glow::NEAREST,
)
};
if self.srgb_capable {
unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) };
}
unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, None) };
unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) };
if let Err(e) = unsafe { OpenGL::SwapBuffers(dc.device) } {
log::error!("unable to swap buffers: {e}");
return Err(crate::SurfaceError::Other("unable to swap buffers"));
}
Ok(())
}
pub fn supports_srgb(&self) -> bool {
self.srgb_capable
}
}
impl crate::Surface for Surface {
type A = super::Api;
unsafe fn configure(
&self,
device: &super::Device,
config: &crate::SurfaceConfiguration,
) -> Result<(), crate::SurfaceError> {
unsafe { self.unconfigure(device) };
let dc = unsafe { Gdi::GetDC(self.window) };
if dc.is_invalid() {
log::error!(
"unable to get the device context from window: {}",
Error::from_win32()
);
return Err(crate::SurfaceError::Other(
"unable to get the device context from window",
));
}
let dc = DeviceContextHandle {
device: dc,
window: self.window,
};
if let Err(e) = unsafe { setup_pixel_format(dc.device) } {
log::error!("unable to setup surface pixel format: {e}",);
return Err(crate::SurfaceError::Other(
"unable to setup surface pixel format",
));
}
let format_desc = device.shared.describe_texture_format(config.format);
let gl = &device.shared.context.lock_with_dc(dc.device).map_err(|e| {
log::error!("unable to make the OpenGL context current for surface: {e}",);
crate::SurfaceError::Other("unable to make the OpenGL context current for surface")
})?;
let renderbuffer = unsafe { gl.create_renderbuffer() }.map_err(|error| {
log::error!("Internal swapchain renderbuffer creation failed: {error}");
crate::DeviceError::OutOfMemory
})?;
unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, Some(renderbuffer)) };
unsafe {
gl.renderbuffer_storage(
glow::RENDERBUFFER,
format_desc.internal,
config.extent.width as _,
config.extent.height as _,
)
};
let framebuffer = unsafe { gl.create_framebuffer() }.map_err(|error| {
log::error!("Internal swapchain framebuffer creation failed: {error}");
crate::DeviceError::OutOfMemory
})?;
unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(framebuffer)) };
unsafe {
gl.framebuffer_renderbuffer(
glow::READ_FRAMEBUFFER,
glow::COLOR_ATTACHMENT0,
glow::RENDERBUFFER,
Some(renderbuffer),
)
};
unsafe { gl.bind_renderbuffer(glow::RENDERBUFFER, None) };
unsafe { gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None) };
let extra = Wgl::load_with(|name| load_gl_func(name, None));
let extensions = get_extensions(&extra, dc.device);
if !(extensions.contains("WGL_EXT_swap_control") && extra.SwapIntervalEXT.is_loaded()) {
log::error!("WGL_EXT_swap_control is unsupported");
return Err(crate::SurfaceError::Other(
"WGL_EXT_swap_control is unsupported",
));
}
let vsync = match config.present_mode {
wgt::PresentMode::Immediate => false,
wgt::PresentMode::Fifo => true,
_ => {
log::error!("unsupported present mode: {:?}", config.present_mode);
return Err(crate::SurfaceError::Other("unsupported present mode"));
}
};
if unsafe { extra.SwapIntervalEXT(if vsync { 1 } else { 0 }) } == Foundation::FALSE.0 {
log::error!("unable to set swap interval: {}", Error::from_win32());
return Err(crate::SurfaceError::Other("unable to set swap interval"));
}
self.swapchain.write().replace(Swapchain {
renderbuffer,
framebuffer,
extent: config.extent,
format: config.format,
format_desc,
sample_type: wgt::TextureSampleType::Float { filterable: false },
});
Ok(())
}
unsafe fn unconfigure(&self, device: &super::Device) {
let gl = &device.shared.context.lock();
if let Some(sc) = self.swapchain.write().take() {
unsafe {
gl.delete_renderbuffer(sc.renderbuffer);
gl.delete_framebuffer(sc.framebuffer)
};
}
}
unsafe fn acquire_texture(
&self,
_timeout_ms: Option<Duration>,
_fence: &super::Fence,
) -> Result<Option<crate::AcquiredSurfaceTexture<super::Api>>, crate::SurfaceError> {
let swapchain = self.swapchain.read();
let sc = swapchain.as_ref().unwrap();
let texture = super::Texture {
inner: super::TextureInner::Renderbuffer {
raw: sc.renderbuffer,
},
drop_guard: None,
array_layer_count: 1,
mip_level_count: 1,
format: sc.format,
format_desc: sc.format_desc.clone(),
copy_size: crate::CopyExtent {
width: sc.extent.width,
height: sc.extent.height,
depth: 1,
},
};
Ok(Some(crate::AcquiredSurfaceTexture {
texture,
suboptimal: false,
}))
}
unsafe fn discard_texture(&self, _texture: super::Texture) {}
}