diff --git a/Cargo.toml b/Cargo.toml index 257eb834..30c740cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,8 @@ rust-version = "1.82" [features] default = [] opengl = [ + "windows/Win32_Graphics_OpenGL", + "windows-sys/Win32_Graphics_OpenGL", "objc2-core-foundation/CFBundle", "objc2-app-kit/NSOpenGL", "objc2-app-kit/NSOpenGLView", @@ -47,7 +49,6 @@ bytemuck = "1.25.0" [target.'cfg(target_os="windows")'.dependencies] windows = { version = "0.62.2", features = [ "Win32_Graphics_Gdi", - "Win32_Graphics_OpenGL", "Win32_System_Com_StructuredStorage", "Win32_System_Ole", "Win32_System_SystemServices", @@ -55,7 +56,6 @@ windows = { version = "0.62.2", features = [ ] } windows-sys = { version = "0.61.2", features = [ "Win32_Graphics_Gdi", - "Win32_Graphics_OpenGL", "Win32_System_Rpc", "Win32_System_LibraryLoader", "Win32_System_Ole", diff --git a/src/platform/win/gl.rs b/src/platform/win/gl.rs index 3cc0585a..01a2a952 100644 --- a/src/platform/win/gl.rs +++ b/src/platform/win/gl.rs @@ -1,340 +1,121 @@ -use std::ffi::{c_void, CString, OsStr}; -use std::os::windows::ffi::OsStrExt; +use std::ffi::{c_void, CString}; +use std::num::NonZeroI32; use std::rc::Rc; -use windows_sys::{ - core::s, - Win32::{ - Foundation::{FreeLibrary, HMODULE, HWND}, - Graphics::{ - Gdi::{GetDC, ReleaseDC, HDC}, - OpenGL::{ - wglCreateContext, wglDeleteContext, wglGetProcAddress, wglMakeCurrent, - ChoosePixelFormat, DescribePixelFormat, SetPixelFormat, SwapBuffers, HGLRC, - PFD_DOUBLEBUFFER, PFD_DRAW_TO_WINDOW, PFD_MAIN_PLANE, PFD_SUPPORT_OPENGL, - PFD_TYPE_RGBA, PIXELFORMATDESCRIPTOR, - }, - }, - System::LibraryLoader::{GetProcAddress, LoadLibraryA}, - UI::WindowsAndMessaging::{ - CreateWindowExW, DefWindowProcW, DestroyWindow, RegisterClassW, UnregisterClassW, - CS_OWNDC, CW_USEDEFAULT, WNDCLASSW, - }, - }, -}; +use windows_core::{s, PCSTR}; +use windows_sys::Win32::Graphics::OpenGL::wglGetProcAddress; use crate::gl::*; -use crate::wrappers::win32::h_instance::HInstance; -use crate::wrappers::win32::uuid::Uuid; -use crate::wrappers::win32::window::HWnd; -// See https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_create_context.txt - -type WglCreateContextAttribsARB = extern "system" fn(HDC, HGLRC, *const i32) -> HGLRC; - -const WGL_CONTEXT_MAJOR_VERSION_ARB: i32 = 0x2091; -const WGL_CONTEXT_MINOR_VERSION_ARB: i32 = 0x2092; -const WGL_CONTEXT_PROFILE_MASK_ARB: i32 = 0x9126; - -const WGL_CONTEXT_CORE_PROFILE_BIT_ARB: i32 = 0x00000001; -const WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB: i32 = 0x00000002; - -// See https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_pixel_format.txt - -type WglChoosePixelFormatARB = - extern "system" fn(HDC, *const i32, *const f32, u32, *mut i32, *mut u32) -> i32; - -const WGL_DRAW_TO_WINDOW_ARB: i32 = 0x2001; -const WGL_ACCELERATION_ARB: i32 = 0x2003; -const WGL_SUPPORT_OPENGL_ARB: i32 = 0x2010; -const WGL_DOUBLE_BUFFER_ARB: i32 = 0x2011; -const WGL_PIXEL_TYPE_ARB: i32 = 0x2013; -const WGL_RED_BITS_ARB: i32 = 0x2015; -const WGL_GREEN_BITS_ARB: i32 = 0x2017; -const WGL_BLUE_BITS_ARB: i32 = 0x2019; -const WGL_ALPHA_BITS_ARB: i32 = 0x201B; -const WGL_DEPTH_BITS_ARB: i32 = 0x2022; -const WGL_STENCIL_BITS_ARB: i32 = 0x2023; - -const WGL_FULL_ACCELERATION_ARB: i32 = 0x2027; -const WGL_TYPE_RGBA_ARB: i32 = 0x202B; - -// See https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_multisample.txt - -const WGL_SAMPLE_BUFFERS_ARB: i32 = 0x2041; -const WGL_SAMPLES_ARB: i32 = 0x2042; - -// See https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_framebuffer_sRGB.txt +use crate::wrappers::win32::window::{ + with_dummy_window, HWnd, MissingExtensionFunctionError, OwnDeviceContext, PixelFormat, + PixelFormatAttribs, WglContext, WglExtra, +}; +use crate::wrappers::win32::LibraryModule; -const WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB: i32 = 0x20A9; +#[derive(Debug)] +pub enum CreationFailedError { + Win32(windows_core::Error), + MissingWglExtension(MissingExtensionFunctionError), +} -// See https://www.khronos.org/registry/OpenGL/extensions/EXT/WGL_EXT_swap_control.txt +impl From for CreationFailedError { + fn from(err: windows_core::Error) -> Self { + CreationFailedError::Win32(err) + } +} -type WglSwapIntervalEXT = extern "system" fn(i32) -> i32; +impl From for CreationFailedError { + fn from(err: MissingExtensionFunctionError) -> Self { + CreationFailedError::MissingWglExtension(err) + } +} -pub type CreationFailedError = (); pub type GlContext = Rc; +impl From for GlError { + fn from(e: CreationFailedError) -> Self { + GlError::CreationFailed(e) + } +} + pub struct GlContextInner { - hwnd: HWND, - hdc: HDC, - hglrc: HGLRC, - gl_library: HMODULE, + hdc: OwnDeviceContext, + wgl_ctx: WglContext, + gl_library: LibraryModule, } impl GlContextInner { - pub unsafe fn create(window: HWnd, config: GlConfig) -> Result { - // Create temporary window and context to load function pointers - - let class_name_str = format!("raw-gl-context-window-{}", Uuid::new()); - let mut class_name: Vec = OsStr::new(&class_name_str).encode_wide().collect(); - class_name.push(0); - - let hinstance = HInstance::get_from_dll(); - - let wnd_class = WNDCLASSW { - style: CS_OWNDC, - lpfnWndProc: Some(DefWindowProcW), - hInstance: hinstance.as_raw(), - lpszClassName: class_name.as_ptr(), - ..std::mem::zeroed() - }; - - let class = RegisterClassW(&wnd_class); - if class == 0 { - return Err(GlError::CreationFailed(())); - } - - let hwnd_tmp = CreateWindowExW( - 0, - class as *const _, - [0].as_ptr(), - 0, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - std::ptr::null_mut(), - std::ptr::null_mut(), - hinstance.as_raw(), - std::ptr::null_mut(), - ); - - if hwnd_tmp.is_null() { - return Err(GlError::CreationFailed(())); - } - - let hdc_tmp = GetDC(hwnd_tmp); - - let pfd_tmp = PIXELFORMATDESCRIPTOR { - nSize: std::mem::size_of::() as u16, - nVersion: 1, - dwFlags: PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, - iPixelType: PFD_TYPE_RGBA, - cColorBits: 32, - cAlphaBits: 8, - cDepthBits: 24, - cStencilBits: 8, - iLayerType: PFD_MAIN_PLANE as u8, - ..std::mem::zeroed() - }; - - SetPixelFormat(hdc_tmp, ChoosePixelFormat(hdc_tmp, &pfd_tmp), &pfd_tmp); + pub fn create(window: HWnd, config: GlConfig) -> Result { + let gl_library = unsafe { LibraryModule::load(s!("opengl32.dll"))? }; - let hglrc_tmp = wglCreateContext(hdc_tmp); - if hglrc_tmp.is_null() { - ReleaseDC(hwnd_tmp, hdc_tmp); - UnregisterClassW(class as *const _, hinstance.as_raw()); - DestroyWindow(hwnd_tmp); - return Err(GlError::CreationFailed(())); - } - - wglMakeCurrent(hdc_tmp, hglrc_tmp); - - #[allow(non_snake_case)] - let wglCreateContextAttribsARB: Option = { - wglGetProcAddress(s!("wglCreateContextAttribsARB")) - .map(|addr| std::mem::transmute(addr)) - }; - - #[allow(non_snake_case)] - let wglChoosePixelFormatARB: Option = { - wglGetProcAddress(s!("wglChoosePixelFormatARB")).map(|addr| std::mem::transmute(addr)) - }; - - #[allow(non_snake_case)] - let wglSwapIntervalEXT: Option = - { wglGetProcAddress(s!("wglSwapIntervalEXT")).map(|addr| std::mem::transmute(addr)) }; + // Create temporary window and context to load function pointers + let extra = with_dummy_window(|hwnd_tmp| { + let hdc = hwnd_tmp.get_own_dc()?; + hdc.set_pixel_format(&PixelFormat::default())?; - wglMakeCurrent(hdc_tmp, std::ptr::null_mut()); - wglDeleteContext(hglrc_tmp); - ReleaseDC(hwnd_tmp, hdc_tmp); - UnregisterClassW(class as *const _, hinstance.as_raw()); - DestroyWindow(hwnd_tmp); + let wgl_ctx = hdc.create_wgl_context()?; + wgl_ctx.with_current(&hdc, WglExtra::load) + })??; // Create actual context - - let hwnd = window.as_raw(); - - let hdc = GetDC(hwnd); - - // Try to choose pixel format with requested config - #[rustfmt::skip] - let pixel_format_attribs = [ - WGL_DRAW_TO_WINDOW_ARB, 1, - WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, - WGL_SUPPORT_OPENGL_ARB, 1, - WGL_DOUBLE_BUFFER_ARB, config.double_buffer as i32, - WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, - WGL_RED_BITS_ARB, config.red_bits as i32, - WGL_GREEN_BITS_ARB, config.green_bits as i32, - WGL_BLUE_BITS_ARB, config.blue_bits as i32, - WGL_ALPHA_BITS_ARB, config.alpha_bits as i32, - WGL_DEPTH_BITS_ARB, config.depth_bits as i32, - WGL_STENCIL_BITS_ARB, config.stencil_bits as i32, - WGL_SAMPLE_BUFFERS_ARB, config.samples.is_some() as i32, - WGL_SAMPLES_ARB, config.samples.unwrap_or(0) as i32, - WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, config.srgb as i32, - 0, - ]; - - let mut pixel_format = 0; - let mut num_formats = 0; - wglChoosePixelFormatARB.unwrap()( - hdc, - pixel_format_attribs.as_ptr(), - std::ptr::null(), - 1, - &mut pixel_format, - &mut num_formats, - ); - - // If no matching format found and sRGB was requested, try again without sRGB - if num_formats == 0 && config.srgb { - eprintln!("Warning: sRGB framebuffer not supported, falling back to non-sRGB"); - - #[rustfmt::skip] - let pixel_format_attribs_fallback = [ - WGL_DRAW_TO_WINDOW_ARB, 1, - WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, - WGL_SUPPORT_OPENGL_ARB, 1, - WGL_DOUBLE_BUFFER_ARB, config.double_buffer as i32, - WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, - WGL_RED_BITS_ARB, config.red_bits as i32, - WGL_GREEN_BITS_ARB, config.green_bits as i32, - WGL_BLUE_BITS_ARB, config.blue_bits as i32, - WGL_ALPHA_BITS_ARB, config.alpha_bits as i32, - WGL_DEPTH_BITS_ARB, config.depth_bits as i32, - WGL_STENCIL_BITS_ARB, config.stencil_bits as i32, - WGL_SAMPLE_BUFFERS_ARB, config.samples.is_some() as i32, - WGL_SAMPLES_ARB, config.samples.unwrap_or(0) as i32, - // WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB omitted - 0, - ]; - - wglChoosePixelFormatARB.unwrap()( - hdc, - pixel_format_attribs_fallback.as_ptr(), - std::ptr::null(), - 1, - &mut pixel_format, - &mut num_formats, - ); - } - - // if no num_formats are found which happens in Wine for child windows, use fallback - if num_formats == 0 { - let fallback_pfd = PIXELFORMATDESCRIPTOR { - nSize: std::mem::size_of::() as u16, - nVersion: 1, - dwFlags: PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, - iPixelType: PFD_TYPE_RGBA, - cColorBits: 32, - cAlphaBits: config.alpha_bits, - cDepthBits: config.depth_bits, - cStencilBits: config.stencil_bits, - iLayerType: PFD_MAIN_PLANE as u8, - ..std::mem::zeroed() - }; - pixel_format = ChoosePixelFormat(hdc, &fallback_pfd); + let hdc = window.get_own_dc()?; + match find_wgl_pixel_format(&extra, &hdc, &config) { + Some(format) => hdc.set_pixel_format_from_index(format)?, + // if no formats are found, which happens in Wine for child windows, use fallback + None => hdc.set_pixel_format(&PixelFormat::from_config(&config))?, } - if pixel_format == 0 { - ReleaseDC(hwnd, hdc); - return Err(GlError::CreationFailed(())); - } + let wgl_ctx = extra.create_context_for_config(&hdc, &config)?; - let mut pfd: PIXELFORMATDESCRIPTOR = std::mem::zeroed(); - DescribePixelFormat( - hdc, - pixel_format, - std::mem::size_of::() as u32, - &mut pfd, - ); - SetPixelFormat(hdc, pixel_format, &pfd); + wgl_ctx.with_current(&hdc, || extra.set_vsync(config.vsync))??; - let profile_mask = match config.profile { - Profile::Core => WGL_CONTEXT_CORE_PROFILE_BIT_ARB, - Profile::Compatibility => WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, - }; - - #[rustfmt::skip] - let ctx_attribs = [ - WGL_CONTEXT_MAJOR_VERSION_ARB, config.version.0 as i32, - WGL_CONTEXT_MINOR_VERSION_ARB, config.version.1 as i32, - WGL_CONTEXT_PROFILE_MASK_ARB, profile_mask, - 0 - ]; - - let hglrc = - wglCreateContextAttribsARB.unwrap()(hdc, std::ptr::null_mut(), ctx_attribs.as_ptr()); - if hglrc.is_null() { - return Err(GlError::CreationFailed(())); - } - - let gl_library = LoadLibraryA(s!("opengl32.dll")); - - wglMakeCurrent(hdc, hglrc); - wglSwapIntervalEXT.unwrap()(config.vsync as i32); - wglMakeCurrent(hdc, std::ptr::null_mut()); - - Ok(Self { hwnd, hdc, hglrc, gl_library }) + Ok(Self { hdc, wgl_ctx, gl_library }) } pub unsafe fn make_current(&self) { - wglMakeCurrent(self.hdc, self.hglrc); + let _ = self.wgl_ctx.make_current(&self.hdc); } pub unsafe fn make_not_current(&self) { - wglMakeCurrent(self.hdc, std::ptr::null_mut()); + let _ = self.wgl_ctx.make_not_current(); } pub fn get_proc_address(&self, symbol: &str) -> *const c_void { let symbol = CString::new(symbol).unwrap(); let symbol_ptr = symbol.as_ptr().cast(); - let addr = unsafe { - wglGetProcAddress(symbol_ptr).or_else(|| GetProcAddress(self.gl_library, symbol_ptr)) - }; + if let Some(addr) = unsafe { wglGetProcAddress(symbol_ptr) } { + return addr as *const c_void; + } - match addr { - Some(addr) => addr as *const c_void, - None => std::ptr::null(), + let symbol_ptr = PCSTR::from_raw(symbol_ptr); + if let Some(addr) = unsafe { self.gl_library.get_proc_address(symbol_ptr) } { + return addr; } + + core::ptr::null() } pub fn swap_buffers(&self) { - unsafe { - SwapBuffers(self.hdc); - } + let _ = self.hdc.swap_buffers(); } } -impl Drop for GlContextInner { - fn drop(&mut self) { - unsafe { - wglMakeCurrent(std::ptr::null_mut(), std::ptr::null_mut()); - wglDeleteContext(self.hglrc); - ReleaseDC(self.hwnd, self.hdc); - FreeLibrary(self.gl_library); - } - } +fn find_wgl_pixel_format( + extra: &WglExtra, dc: &OwnDeviceContext, config: &GlConfig, +) -> Option { + let mut format_attribs = PixelFormatAttribs::from_config(config); + + // TODO: log errors + if let Ok(Some(format)) = extra.choose_pixel_format_from_attribs(&format_attribs, dc) { + return Some(format); + }; + + eprintln!("Warning: sRGB framebuffer not supported, falling back to non-sRGB"); + format_attribs.set_without_srgb_ext(); + + if let Ok(Some(format)) = extra.choose_pixel_format_from_attribs(&format_attribs, dc) { + return Some(format); + }; + + None } diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 177a4a41..4a0d10a9 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -135,7 +135,7 @@ impl WindowImpl for BaseviewWindow { #[cfg(feature = "opengl")] if let Some(gl_config) = self.gl_config.clone() { - let gl_context = unsafe { gl::GlContextInner::create(window, gl_config) } + let gl_context = gl::GlContextInner::create(window, gl_config) .expect("Could not create OpenGL context"); let Ok(()) = self.window_state.gl_context.set(Rc::new(gl_context)) else { diff --git a/src/wrappers/win32.rs b/src/wrappers/win32.rs index d024dfb7..4f2c5b01 100644 --- a/src/wrappers/win32.rs +++ b/src/wrappers/win32.rs @@ -1,6 +1,7 @@ pub mod cursor; mod dpi; pub mod h_instance; +mod library; mod rect; mod style; mod user32; @@ -8,6 +9,7 @@ pub mod uuid; pub mod window; pub use dpi::*; +pub use library::*; pub use rect::Rect; pub use style::*; pub use user32::*; diff --git a/src/wrappers/win32/library.rs b/src/wrappers/win32/library.rs new file mode 100644 index 00000000..707ada59 --- /dev/null +++ b/src/wrappers/win32/library.rs @@ -0,0 +1,28 @@ +use std::ffi::c_void; +use std::ptr::NonNull; +use windows_core::{Error, PCSTR}; +use windows_sys::Win32::Foundation::FreeLibrary; +use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA}; + +pub struct LibraryModule(NonNull); + +impl LibraryModule { + pub unsafe fn load(module_name: PCSTR) -> Result { + let library = unsafe { LoadLibraryA(module_name.as_ptr()) }; + let Some(library) = NonNull::new(library) else { return Err(Error::from_thread()) }; + + Ok(Self(library)) + } + + pub unsafe fn get_proc_address(&self, name: PCSTR) -> Option<*const c_void> { + let addr = unsafe { GetProcAddress(self.0.as_ptr(), name.as_ptr()) }; + + addr.map(|f| f as _) + } +} + +impl Drop for LibraryModule { + fn drop(&mut self) { + unsafe { FreeLibrary(self.0.as_ptr()) }; + } +} diff --git a/src/wrappers/win32/user32.rs b/src/wrappers/win32/user32.rs index 2f260b5b..e4b789ae 100644 --- a/src/wrappers/win32/user32.rs +++ b/src/wrappers/win32/user32.rs @@ -1,10 +1,9 @@ +use crate::wrappers::win32::LibraryModule; use std::ffi::c_void; use std::mem::transmute; -use std::ptr::NonNull; -use windows_core::{s, Error, PCSTR}; +use windows_core::{s, Error}; use windows_sys::core::BOOL; -use windows_sys::Win32::Foundation::{FreeLibrary, HWND, RECT}; -use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA}; +use windows_sys::Win32::Foundation::{HWND, RECT}; use windows_sys::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT; use windows_sys::Win32::UI::WindowsAndMessaging::{WINDOW_EX_STYLE, WINDOW_STYLE}; @@ -61,26 +60,3 @@ impl Clone for ExtendedUser32 { } } } - -struct LibraryModule(NonNull); - -impl LibraryModule { - pub unsafe fn load(module_name: PCSTR) -> Result { - let library = unsafe { LoadLibraryA(module_name.as_ptr()) }; - let Some(library) = NonNull::new(library) else { return Err(Error::from_thread()) }; - - Ok(Self(library)) - } - - pub unsafe fn get_proc_address(&self, name: PCSTR) -> Option<*const c_void> { - let addr = unsafe { GetProcAddress(self.0.as_ptr(), name.as_ptr()) }; - - addr.map(|f| f as _) - } -} - -impl Drop for LibraryModule { - fn drop(&mut self) { - unsafe { FreeLibrary(self.0.as_ptr()) }; - } -} diff --git a/src/wrappers/win32/window.rs b/src/wrappers/win32/window.rs index b6ac247e..6bb9e3a0 100644 --- a/src/wrappers/win32/window.rs +++ b/src/wrappers/win32/window.rs @@ -3,6 +3,15 @@ mod handle; mod proc; mod window_class; +#[cfg(feature = "opengl")] +mod dc; +#[cfg(feature = "opengl")] +pub use dc::*; +#[cfg(feature = "opengl")] +mod wgl; +#[cfg(feature = "opengl")] +pub use wgl::*; + use data::WindowData; use dpi::PhysicalSize; pub use handle::HWnd; @@ -82,3 +91,33 @@ pub fn create_window( Ok(hwnd) } + +#[cfg(feature = "opengl")] +pub fn with_dummy_window(handler: impl FnOnce(HWnd) -> Result) -> Result { + use windows_sys::Win32::UI::WindowsAndMessaging::{DefWindowProcW, CW_USEDEFAULT}; + + let instance = HInstance::get_from_dll(); + let window_class = RegisteredClass::register_new(instance, Some(DefWindowProcW))?; + let hwnd = unsafe { + CreateWindowExW( + 0, + window_class.as_atom_ptr(), + null_mut(), + 0, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + null_mut(), + null_mut(), + instance.as_raw(), + null_mut(), + ) + }; + + let Some(hwnd) = NonNull::new(hwnd) else { return Err(Error::from_thread()) }; + // SAFETY: This Hwnd is valid since it came from CreateWindowExW + let hwnd = unsafe { HWnd::from_raw(hwnd) }; + + handler(hwnd) +} diff --git a/src/wrappers/win32/window/dc.rs b/src/wrappers/win32/window/dc.rs new file mode 100644 index 00000000..c0b83e1d --- /dev/null +++ b/src/wrappers/win32/window/dc.rs @@ -0,0 +1,131 @@ +use crate::gl::GlConfig; +use crate::wrappers::win32::window::{HWnd, WglContext}; +use std::ffi::c_void; +use std::num::{NonZero, NonZeroI32}; +use std::ptr::NonNull; +use windows_core::{Error, Result}; +use windows_sys::Win32::Graphics::Gdi::{GetDC, HDC}; +use windows_sys::Win32::Graphics::OpenGL::{ + wglCreateContext, ChoosePixelFormat, DescribePixelFormat, SetPixelFormat, SwapBuffers, + PFD_DOUBLEBUFFER, PFD_DRAW_TO_WINDOW, PFD_MAIN_PLANE, PFD_SUPPORT_OPENGL, PFD_TYPE_RGBA, + PIXELFORMATDESCRIPTOR, +}; + +pub struct OwnDeviceContext { + inner: NonNull, +} + +impl OwnDeviceContext { + pub(super) fn from_window(window: HWnd) -> Result { + let dc = unsafe { GetDC(window.as_raw()) }; + + let dc = NonNull::new(dc).ok_or_else(Error::from_thread)?; + + Ok(Self { inner: dc }) + } + + pub fn as_raw(&self) -> HDC { + self.inner.as_ptr() + } + + fn describe_pixel_format(&self, index: NonZeroI32) -> Result { + let mut desc = PIXELFORMATDESCRIPTOR { + nSize: size_of::() as u16, + ..Default::default() + }; + + let result = unsafe { + DescribePixelFormat( + self.as_raw(), + index.get(), + size_of::() as u32, + &mut desc, + ) + }; + + if result == 0 { + return Err(Error::from_thread()); + } + + Ok(desc) + } + + pub fn set_pixel_format(&self, pixel_format: &PixelFormat) -> Result<()> { + let desc = pixel_format.to_raw_descriptor(); + let index = unsafe { ChoosePixelFormat(self.as_raw(), &desc) }; + let Some(index) = NonZero::new(index) else { return Err(Error::from_thread()) }; + + let result = unsafe { SetPixelFormat(self.as_raw(), index.get(), &desc) }; + + if result == 0 { + return Err(Error::from_thread()); + } + + Ok(()) + } + + pub fn set_pixel_format_from_index(&self, index: NonZeroI32) -> Result<()> { + let desc = self.describe_pixel_format(index)?; + let result = unsafe { SetPixelFormat(self.as_raw(), index.get(), &desc) }; + + if result == 0 { + return Err(Error::from_thread()); + } + + Ok(()) + } + + pub fn create_wgl_context(&self) -> Result { + let ctx = unsafe { wglCreateContext(self.as_raw()) }; + let ctx = NonNull::new(ctx).ok_or_else(Error::from_thread)?; + + Ok(WglContext { inner: ctx }) + } + + pub fn swap_buffers(&self) -> Result<()> { + let result = unsafe { SwapBuffers(self.as_raw()) }; + if result == 0 { + return Err(Error::from_thread()); + } + + Ok(()) + } +} + +#[derive(Copy, Clone)] +pub struct PixelFormat { + pub alpha_bits: u8, + pub depth_bits: u8, + pub stencil_bits: u8, +} + +impl Default for PixelFormat { + fn default() -> Self { + Self { alpha_bits: 8, depth_bits: 24, stencil_bits: 8 } + } +} + +impl PixelFormat { + pub fn from_config(config: &GlConfig) -> Self { + Self { + alpha_bits: config.alpha_bits, + depth_bits: config.depth_bits, + stencil_bits: config.stencil_bits, + } + } + + pub fn to_raw_descriptor(self) -> PIXELFORMATDESCRIPTOR { + PIXELFORMATDESCRIPTOR { + nSize: size_of::() as u16, + nVersion: 1, + dwFlags: PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + iPixelType: PFD_TYPE_RGBA, + cColorBits: 32, + cAlphaBits: self.alpha_bits, + cDepthBits: self.depth_bits, + cStencilBits: self.stencil_bits, + iLayerType: PFD_MAIN_PLANE as u8, + ..Default::default() + } + } +} diff --git a/src/wrappers/win32/window/handle.rs b/src/wrappers/win32/window/handle.rs index 4a14d41e..4294c95a 100644 --- a/src/wrappers/win32/window/handle.rs +++ b/src/wrappers/win32/window/handle.rs @@ -257,4 +257,9 @@ impl HWnd { Ok(PhysicalPosition::new(pt.x, pt.y)) } + + #[cfg(feature = "opengl")] + pub fn get_own_dc(&self) -> Result { + super::OwnDeviceContext::from_window(*self) + } } diff --git a/src/wrappers/win32/window/wgl.rs b/src/wrappers/win32/window/wgl.rs new file mode 100644 index 00000000..a82d28e4 --- /dev/null +++ b/src/wrappers/win32/window/wgl.rs @@ -0,0 +1,254 @@ +use crate::gl::{GlConfig, Profile}; +use crate::wrappers::win32::window::OwnDeviceContext; +use std::ffi::{c_void, CStr}; +use std::fmt::{Display, Formatter}; +use std::mem::transmute; +use std::num::NonZeroI32; +use std::ptr::{null_mut, NonNull}; +use windows_core::Error; +use windows_sys::Win32::Graphics::Gdi::HDC; +use windows_sys::Win32::Graphics::OpenGL::{ + wglDeleteContext, wglGetCurrentContext, wglGetProcAddress, wglMakeCurrent, HGLRC, +}; + +const WGL_CONTEXT_MAJOR_VERSION_ARB: i32 = 0x2091; +const WGL_CONTEXT_MINOR_VERSION_ARB: i32 = 0x2092; +const WGL_CONTEXT_PROFILE_MASK_ARB: i32 = 0x9126; +const WGL_CONTEXT_CORE_PROFILE_BIT_ARB: i32 = 0x00000001; +const WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB: i32 = 0x00000002; + +pub struct WglContext { + pub(super) inner: NonNull, +} + +impl WglContext { + pub unsafe fn make_current(&self, dc: &OwnDeviceContext) -> windows_core::Result<()> { + let result = unsafe { wglMakeCurrent(dc.as_raw(), self.inner.as_ptr()) }; + if result == 0 { + return Err(Error::from_thread()); + } + + Ok(()) + } + + pub unsafe fn make_not_current(&self) -> windows_core::Result<()> { + let current = unsafe { wglGetCurrentContext() }; + + if current.is_null() { + return Ok(()); + }; + + if current != self.inner.as_ptr() { + return Ok(()); + }; + + let result = unsafe { wglMakeCurrent(null_mut(), null_mut()) }; + if result == 0 { + return Err(Error::from_thread()); + } + + Ok(()) + } + + pub fn with_current( + &self, dc: &OwnDeviceContext, f: impl FnOnce() -> T, + ) -> windows_core::Result { + struct Guard<'a>(&'a WglContext); + impl<'a> Drop for Guard<'a> { + fn drop(&mut self) { + let _ = unsafe { self.0.make_not_current() }; + } + } + + unsafe { self.make_current(dc)? }; + + let _guard = Guard(self); + + let result = f(); + + drop(_guard); + + Ok(result) + } +} + +impl Drop for WglContext { + fn drop(&mut self) { + let _ = unsafe { self.make_not_current() }; + unsafe { wglDeleteContext(self.inner.as_ptr()) }; // TODO: warn on error + } +} + +// See https://www.khronos.org/registry/OpenGL/extensions/EXT/WGL_EXT_swap_control.txt +type WglSwapIntervalEXT = unsafe extern "system" fn(i32) -> i32; + +// See https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_pixel_format.txt +type WglChoosePixelFormatARB = + unsafe extern "system" fn(HDC, *const i32, *const f32, u32, *mut i32, *mut u32) -> i32; + +// See https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_create_context.txt +type WglCreateContextAttribsARB = unsafe extern "system" fn(HDC, HGLRC, *const i32) -> HGLRC; + +#[allow(non_snake_case)] +pub struct WglExtra { + wglCreateContextAttribsARB: WglCreateContextAttribsARB, + wglChoosePixelFormatARB: WglChoosePixelFormatARB, + wglSwapIntervalEXT: WglSwapIntervalEXT, +} + +impl WglExtra { + pub fn load() -> Result { + unsafe { + Ok(Self { + wglCreateContextAttribsARB: transmute::<*const c_void, WglCreateContextAttribsARB>( + Self::load_fn(c"wglCreateContextAttribsARB")?, + ), + wglChoosePixelFormatARB: transmute::<*const c_void, WglChoosePixelFormatARB>( + Self::load_fn(c"wglChoosePixelFormatARB")?, + ), + wglSwapIntervalEXT: transmute::<*const c_void, WglSwapIntervalEXT>(Self::load_fn( + c"wglSwapIntervalEXT", + )?), + }) + } + } + + fn load_fn(name: &'static CStr) -> Result<*const c_void, MissingExtensionFunctionError> { + let ptr = unsafe { wglGetProcAddress(name.as_ptr() as *const u8) }; + + match ptr { + Some(ptr) => Ok(ptr as *const c_void), + None => Err(MissingExtensionFunctionError { name }), + } + } + + pub fn create_context_for_config( + &self, dc: &OwnDeviceContext, config: &GlConfig, + ) -> windows_core::Result { + let profile_mask = match config.profile { + Profile::Core => WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + Profile::Compatibility => WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, + }; + + #[rustfmt::skip] + let ctx_attribs = [ + WGL_CONTEXT_MAJOR_VERSION_ARB, config.version.0 as i32, + WGL_CONTEXT_MINOR_VERSION_ARB, config.version.1 as i32, + WGL_CONTEXT_PROFILE_MASK_ARB, profile_mask, + 0 + ]; + + let ctx = unsafe { + (self.wglCreateContextAttribsARB)(dc.as_raw(), null_mut(), ctx_attribs.as_ptr()) + }; + + let ctx = NonNull::new(ctx).ok_or_else(Error::from_thread)?; + + Ok(WglContext { inner: ctx }) + } + + pub fn set_vsync(&self, vsync: bool) -> windows_core::Result<()> { + let result = unsafe { (self.wglSwapIntervalEXT)(vsync.into()) }; + if result == 0 { + return Err(Error::from_thread()); + } + + Ok(()) + } + + pub fn choose_pixel_format_from_attribs( + &self, attribs: &PixelFormatAttribs, dc: &OwnDeviceContext, + ) -> windows_core::Result> { + let mut pixel_formats = [0]; + let mut num_formats = 0; + let result = unsafe { + (self.wglChoosePixelFormatARB)( + dc.as_raw(), + attribs.inner.as_ptr(), + std::ptr::null(), + 1, + pixel_formats.as_mut_ptr(), + &mut num_formats, + ) + }; + + if result == 0 { + return Err(Error::from_thread()); + } + + if num_formats == 0 { + return Ok(None); + } + + Ok(NonZeroI32::new(pixel_formats[0])) + } +} + +const WGL_DRAW_TO_WINDOW_ARB: i32 = 0x2001; +const WGL_ACCELERATION_ARB: i32 = 0x2003; +const WGL_SUPPORT_OPENGL_ARB: i32 = 0x2010; +const WGL_DOUBLE_BUFFER_ARB: i32 = 0x2011; +const WGL_PIXEL_TYPE_ARB: i32 = 0x2013; +const WGL_RED_BITS_ARB: i32 = 0x2015; +const WGL_GREEN_BITS_ARB: i32 = 0x2017; +const WGL_BLUE_BITS_ARB: i32 = 0x2019; +const WGL_ALPHA_BITS_ARB: i32 = 0x201B; +const WGL_DEPTH_BITS_ARB: i32 = 0x2022; +const WGL_STENCIL_BITS_ARB: i32 = 0x2023; + +const WGL_FULL_ACCELERATION_ARB: i32 = 0x2027; +const WGL_TYPE_RGBA_ARB: i32 = 0x202B; + +// See https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_multisample.txt + +const WGL_SAMPLE_BUFFERS_ARB: i32 = 0x2041; +const WGL_SAMPLES_ARB: i32 = 0x2042; + +// See https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_framebuffer_sRGB.txt + +const WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB: i32 = 0x20A9; + +pub struct PixelFormatAttribs { + inner: [i32; 29], +} + +impl PixelFormatAttribs { + #[rustfmt::skip] + pub fn from_config(config: &GlConfig) -> Self { + Self { + inner: [ + WGL_DRAW_TO_WINDOW_ARB, 1, + WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, + WGL_SUPPORT_OPENGL_ARB, 1, + WGL_DOUBLE_BUFFER_ARB, config.double_buffer as i32, + WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + WGL_RED_BITS_ARB, config.red_bits as i32, + WGL_GREEN_BITS_ARB, config.green_bits as i32, + WGL_BLUE_BITS_ARB, config.blue_bits as i32, + WGL_ALPHA_BITS_ARB, config.alpha_bits as i32, + WGL_DEPTH_BITS_ARB, config.depth_bits as i32, + WGL_STENCIL_BITS_ARB, config.stencil_bits as i32, + WGL_SAMPLE_BUFFERS_ARB, config.samples.is_some() as i32, + WGL_SAMPLES_ARB, config.samples.unwrap_or(0) as i32, + WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, config.srgb as i32, + 0, + ] + } + } + + pub fn set_without_srgb_ext(&mut self) { + self.inner[26] = 0; + self.inner[27] = 0; + } +} + +#[derive(Debug)] +pub struct MissingExtensionFunctionError { + name: &'static CStr, +} + +impl Display for MissingExtensionFunctionError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "Missing WGL function extension: {}", self.name.to_string_lossy()) + } +} diff --git a/src/wrappers/win32/window/window_class.rs b/src/wrappers/win32/window/window_class.rs index 10a42454..aa7fa8e2 100644 --- a/src/wrappers/win32/window/window_class.rs +++ b/src/wrappers/win32/window/window_class.rs @@ -22,7 +22,7 @@ impl RegisteredClass { hInstance: instance.as_raw(), lpszClassName: class_name.as_ptr(), - style: CS_OWNDC, // TODO: this is very suspicious + style: CS_OWNDC, cbClsExtra: 0, cbWndExtra: 0, hIcon: null_mut(), // Default icon