From 962c20e56e2a3989bb4fd1cada6fae5bb2830d0d Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Mon, 13 Jul 2026 10:01:30 +0200 Subject: [PATCH 01/10] wip --- src/wrappers/win32/window.rs | 35 ++++++++++++++++++++++- src/wrappers/win32/window/dc.rs | 26 +++++++++++++++++ src/wrappers/win32/window/window_class.rs | 4 +-- 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 src/wrappers/win32/window/dc.rs diff --git a/src/wrappers/win32/window.rs b/src/wrappers/win32/window.rs index b6ac247e..90a7b14f 100644 --- a/src/wrappers/win32/window.rs +++ b/src/wrappers/win32/window.rs @@ -3,6 +3,12 @@ mod handle; mod proc; mod window_class; +#[cfg(feature = "opengl")] +mod dc; + +#[cfg(feature = "opengl")] +pub use dc::*; + use data::WindowData; use dpi::PhysicalSize; pub use handle::HWnd; @@ -16,7 +22,7 @@ use crate::wrappers::win32::h_instance::HInstance; use crate::wrappers::win32::style::WindowStyle; use crate::wrappers::win32::DpiAwarenessContext; use windows_sys::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM}; -use windows_sys::Win32::UI::WindowsAndMessaging::CreateWindowExW; +use windows_sys::Win32::UI::WindowsAndMessaging::{CreateWindowExW, CW_USEDEFAULT}; pub trait WindowImpl: 'static { /// Called during the processing of the WM_CREATE message, but after this type was properly @@ -82,3 +88,30 @@ pub fn create_window( Ok(hwnd) } + +pub fn with_dummy_window(handler: impl FnOnce(HWnd) -> Result) -> Result { + let instance = HInstance::get_from_dll(); + let window_class = RegisteredClass::register_new(instance, None)?; + 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(), + ) + }; + + if hwnd.is_null() { + return Err(Error::from_thread()); + } + + handler(hwnd) +} diff --git a/src/wrappers/win32/window/dc.rs b/src/wrappers/win32/window/dc.rs new file mode 100644 index 00000000..95e11e91 --- /dev/null +++ b/src/wrappers/win32/window/dc.rs @@ -0,0 +1,26 @@ +use crate::wrappers::win32::window::HWnd; +use std::ffi::c_void; +use std::ptr::NonNull; +use windows_core::{Error, Result}; +use windows_sys::Win32::Graphics::Gdi::{GetDC, ReleaseDC}; + +pub struct DeviceContext { + window: HWnd, + inner: NonNull, +} + +impl DeviceContext { + 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 { window, inner: dc }) + } +} + +impl Drop for DeviceContext { + fn drop(&mut self) { + unsafe { ReleaseDC(self.window.as_raw(), self.inner.as_ptr()) }; + } +} diff --git a/src/wrappers/win32/window/window_class.rs b/src/wrappers/win32/window/window_class.rs index 10a42454..92cc5a2a 100644 --- a/src/wrappers/win32/window/window_class.rs +++ b/src/wrappers/win32/window/window_class.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use windows_core::{Error, Result, HSTRING}; use windows_sys::core::PCWSTR; use windows_sys::Win32::UI::WindowsAndMessaging::{ - LoadCursorW, RegisterClassW, UnregisterClassW, CS_OWNDC, IDC_ARROW, WNDCLASSW, WNDPROC, + LoadCursorW, RegisterClassW, UnregisterClassW, IDC_ARROW, WNDCLASSW, WNDPROC, }; #[derive(Clone)] @@ -22,7 +22,7 @@ impl RegisteredClass { hInstance: instance.as_raw(), lpszClassName: class_name.as_ptr(), - style: CS_OWNDC, // TODO: this is very suspicious + style: 0, cbClsExtra: 0, cbWndExtra: 0, hIcon: null_mut(), // Default icon From db0b8ed5d18f7d8fe5c0f86a0282230010522359 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Mon, 13 Jul 2026 11:27:01 +0200 Subject: [PATCH 02/10] wip --- src/platform/win/gl.rs | 149 +++++++------------- src/wrappers/win32/window.rs | 6 +- src/wrappers/win32/window/dc.rs | 163 ++++++++++++++++++++-- src/wrappers/win32/window/handle.rs | 5 + src/wrappers/win32/window/window_class.rs | 4 +- 5 files changed, 215 insertions(+), 112 deletions(-) diff --git a/src/platform/win/gl.rs b/src/platform/win/gl.rs index 3cc0585a..ae272eb4 100644 --- a/src/platform/win/gl.rs +++ b/src/platform/win/gl.rs @@ -1,5 +1,4 @@ -use std::ffi::{c_void, CString, OsStr}; -use std::os::windows::ffi::OsStrExt; +use std::ffi::{c_void, CString}; use std::rc::Rc; use windows_sys::{ core::s, @@ -15,17 +14,12 @@ use windows_sys::{ }, }, System::LibraryLoader::{GetProcAddress, LoadLibraryA}, - UI::WindowsAndMessaging::{ - CreateWindowExW, DefWindowProcW, DestroyWindow, RegisterClassW, UnregisterClassW, - CS_OWNDC, CW_USEDEFAULT, WNDCLASSW, - }, + UI::WindowsAndMessaging::{DestroyWindow, UnregisterClassW}, }, }; use crate::gl::*; -use crate::wrappers::win32::h_instance::HInstance; -use crate::wrappers::win32::uuid::Uuid; -use crate::wrappers::win32::window::HWnd; +use crate::wrappers::win32::window::{with_dummy_window, HWnd, PixelFormat}; // See https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_create_context.txt type WglCreateContextAttribsARB = extern "system" fn(HDC, HGLRC, *const i32) -> HGLRC; @@ -70,9 +64,37 @@ const WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB: i32 = 0x20A9; type WglSwapIntervalEXT = extern "system" fn(i32) -> i32; -pub type CreationFailedError = (); +pub type CreationFailedError = windows_core::Error; pub type GlContext = Rc; +impl From for GlError { + fn from(e: CreationFailedError) -> Self { + GlError::CreationFailed(e) + } +} + +#[allow(non_snake_case)] +struct WglExtra { + wglCreateContextAttribsARB: Option, + wglChoosePixelFormatARB: Option, + wglSwapIntervalEXT: Option, +} + +impl WglExtra { + pub fn load() -> Self { + unsafe { + Self { + wglCreateContextAttribsARB: wglGetProcAddress(s!("wglCreateContextAttribsARB")) + .map(|addr| std::mem::transmute(addr)), + wglChoosePixelFormatARB: wglGetProcAddress(s!("wglChoosePixelFormatARB")) + .map(|addr| std::mem::transmute(addr)), + wglSwapIntervalEXT: wglGetProcAddress(s!("wglSwapIntervalEXT")) + .map(|addr| std::mem::transmute(addr)), + } + } + } +} + pub struct GlContextInner { hwnd: HWND, hdc: HDC, @@ -84,97 +106,21 @@ 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 extra = with_dummy_window(|hwnd_tmp| { + let hdc = hwnd_tmp.get_own_dc()?; + hdc.set_pixel_format(&PixelFormat::default())?; - 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); - - 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(())); - } + let wgl_ctx = hdc.create_wgl_context()?; + wgl_ctx.make_current(&hdc)?; - 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)) }; - - 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); + Ok(WglExtra::load()) + })?; // Create actual context let hwnd = window.as_raw(); - let hdc = GetDC(hwnd); + let hdc = window.get_own_dc()?; // Try to choose pixel format with requested config #[rustfmt::skip] @@ -198,7 +144,7 @@ impl GlContextInner { let mut pixel_format = 0; let mut num_formats = 0; - wglChoosePixelFormatARB.unwrap()( + extra.wglChoosePixelFormatARB.unwrap()( hdc, pixel_format_attribs.as_ptr(), std::ptr::null(), @@ -230,7 +176,7 @@ impl GlContextInner { 0, ]; - wglChoosePixelFormatARB.unwrap()( + extra.wglChoosePixelFormatARB.unwrap()( hdc, pixel_format_attribs_fallback.as_ptr(), std::ptr::null(), @@ -262,6 +208,8 @@ impl GlContextInner { return Err(GlError::CreationFailed(())); } + hdc.set_pixel_format_from_index(pixel_format)?; + let mut pfd: PIXELFORMATDESCRIPTOR = std::mem::zeroed(); DescribePixelFormat( hdc, @@ -284,8 +232,11 @@ impl GlContextInner { 0 ]; - let hglrc = - wglCreateContextAttribsARB.unwrap()(hdc, std::ptr::null_mut(), ctx_attribs.as_ptr()); + let hglrc = extra.wglCreateContextAttribsARB.unwrap()( + hdc, + std::ptr::null_mut(), + ctx_attribs.as_ptr(), + ); if hglrc.is_null() { return Err(GlError::CreationFailed(())); } @@ -293,7 +244,7 @@ impl GlContextInner { let gl_library = LoadLibraryA(s!("opengl32.dll")); wglMakeCurrent(hdc, hglrc); - wglSwapIntervalEXT.unwrap()(config.vsync as i32); + extra.wglSwapIntervalEXT.unwrap()(config.vsync as i32); wglMakeCurrent(hdc, std::ptr::null_mut()); Ok(Self { hwnd, hdc, hglrc, gl_library }) diff --git a/src/wrappers/win32/window.rs b/src/wrappers/win32/window.rs index 90a7b14f..00d5214f 100644 --- a/src/wrappers/win32/window.rs +++ b/src/wrappers/win32/window.rs @@ -109,9 +109,9 @@ pub fn with_dummy_window(handler: impl FnOnce(HWnd) -> Result) -> Result, } -impl DeviceContext { +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 { window, inner: dc }) + Ok(Self { inner: dc }) } + + 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 struct WglContext { + inner: NonNull, } -impl Drop for DeviceContext { +impl WglContext { + pub unsafe fn make_current(&self, dc: &OwnDeviceContext) -> 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) -> 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) -> 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) { - unsafe { ReleaseDC(self.window.as_raw(), self.inner.as_ptr()) }; + let _ = unsafe { self.make_not_current() }; + unsafe { wglDeleteContext(self.inner.as_ptr()) }; // TODO: warn on error + } +} + +#[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 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..b675fe5d 100644 --- a/src/wrappers/win32/window/handle.rs +++ b/src/wrappers/win32/window/handle.rs @@ -1,5 +1,6 @@ use crate::wrappers::win32::style::WindowStyle; use crate::wrappers::win32::user32::ExtendedUser32; +use crate::wrappers::win32::window::OwnDeviceContext; use crate::wrappers::win32::{Dpi, DpiAwarenessContext, Rect}; use dpi::{PhysicalPosition, PhysicalSize}; use std::ffi::c_void; @@ -257,4 +258,8 @@ impl HWnd { Ok(PhysicalPosition::new(pt.x, pt.y)) } + + pub fn get_own_dc(&self) -> Result { + OwnDeviceContext::from_window(*self) + } } diff --git a/src/wrappers/win32/window/window_class.rs b/src/wrappers/win32/window/window_class.rs index 92cc5a2a..aa7fa8e2 100644 --- a/src/wrappers/win32/window/window_class.rs +++ b/src/wrappers/win32/window/window_class.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use windows_core::{Error, Result, HSTRING}; use windows_sys::core::PCWSTR; use windows_sys::Win32::UI::WindowsAndMessaging::{ - LoadCursorW, RegisterClassW, UnregisterClassW, IDC_ARROW, WNDCLASSW, WNDPROC, + LoadCursorW, RegisterClassW, UnregisterClassW, CS_OWNDC, IDC_ARROW, WNDCLASSW, WNDPROC, }; #[derive(Clone)] @@ -22,7 +22,7 @@ impl RegisteredClass { hInstance: instance.as_raw(), lpszClassName: class_name.as_ptr(), - style: 0, + style: CS_OWNDC, cbClsExtra: 0, cbWndExtra: 0, hIcon: null_mut(), // Default icon From 4dade24309c0ccba0c46d433a09a8440b17243d7 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 14 Jul 2026 01:19:48 +0200 Subject: [PATCH 03/10] wip --- src/platform/win/gl.rs | 117 +++++++------------------------ src/wrappers/win32/window.rs | 5 +- src/wrappers/win32/window/dc.rs | 26 +++++-- src/wrappers/win32/window/wgl.rs | 54 ++++++++++++++ 4 files changed, 107 insertions(+), 95 deletions(-) create mode 100644 src/wrappers/win32/window/wgl.rs diff --git a/src/platform/win/gl.rs b/src/platform/win/gl.rs index ae272eb4..1a666450 100644 --- a/src/platform/win/gl.rs +++ b/src/platform/win/gl.rs @@ -3,26 +3,17 @@ 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, - }, - }, + Foundation::{FreeLibrary, HMODULE}, + Graphics::OpenGL::{wglGetProcAddress, wglMakeCurrent}, System::LibraryLoader::{GetProcAddress, LoadLibraryA}, - UI::WindowsAndMessaging::{DestroyWindow, UnregisterClassW}, }, }; use crate::gl::*; -use crate::wrappers::win32::window::{with_dummy_window, HWnd, PixelFormat}; -// See https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_create_context.txt - -type WglCreateContextAttribsARB = extern "system" fn(HDC, HGLRC, *const i32) -> HGLRC; +use crate::wrappers::win32::window::{ + with_dummy_window, HWnd, MissingExtensionFunctionError, OwnDeviceContext, PixelFormat, + WglContext, WglExtra, +}; const WGL_CONTEXT_MAJOR_VERSION_ARB: i32 = 0x2091; const WGL_CONTEXT_MINOR_VERSION_ARB: i32 = 0x2092; @@ -31,11 +22,6 @@ 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; @@ -60,11 +46,18 @@ const WGL_SAMPLES_ARB: i32 = 0x2042; const WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB: i32 = 0x20A9; -// See https://www.khronos.org/registry/OpenGL/extensions/EXT/WGL_EXT_swap_control.txt +#[derive(Debug)] +pub enum CreationFailedError { + Win32(windows_core::Error), + MissingWglExtension(MissingExtensionFunctionError), +} -type WglSwapIntervalEXT = extern "system" fn(i32) -> i32; +impl From for CreationFailedError { + fn from(err: windows_core::Error) -> Self { + CreationFailedError::Win32(err) + } +} -pub type CreationFailedError = windows_core::Error; pub type GlContext = Rc; impl From for GlError { @@ -73,48 +66,23 @@ impl From for GlError { } } -#[allow(non_snake_case)] -struct WglExtra { - wglCreateContextAttribsARB: Option, - wglChoosePixelFormatARB: Option, - wglSwapIntervalEXT: Option, -} - -impl WglExtra { - pub fn load() -> Self { - unsafe { - Self { - wglCreateContextAttribsARB: wglGetProcAddress(s!("wglCreateContextAttribsARB")) - .map(|addr| std::mem::transmute(addr)), - wglChoosePixelFormatARB: wglGetProcAddress(s!("wglChoosePixelFormatARB")) - .map(|addr| std::mem::transmute(addr)), - wglSwapIntervalEXT: wglGetProcAddress(s!("wglSwapIntervalEXT")) - .map(|addr| std::mem::transmute(addr)), - } - } - } -} - pub struct GlContextInner { - hwnd: HWND, - hdc: HDC, - hglrc: HGLRC, + hwnd: HWnd, + hdc: OwnDeviceContext, + hglrc: WglContext, gl_library: HMODULE, } impl GlContextInner { pub unsafe fn create(window: HWnd, config: GlConfig) -> Result { // 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())?; let wgl_ctx = hdc.create_wgl_context()?; - wgl_ctx.make_current(&hdc)?; - - Ok(WglExtra::load()) - })?; + wgl_ctx.with_current(&hdc, || WglExtra::load()) + })??; // Create actual context @@ -145,7 +113,7 @@ impl GlContextInner { let mut pixel_format = 0; let mut num_formats = 0; extra.wglChoosePixelFormatARB.unwrap()( - hdc, + hdc.as_raw(), pixel_format_attribs.as_ptr(), std::ptr::null(), 1, @@ -177,7 +145,7 @@ impl GlContextInner { ]; extra.wglChoosePixelFormatARB.unwrap()( - hdc, + hdc.as_raw(), pixel_format_attribs_fallback.as_ptr(), std::ptr::null(), 1, @@ -188,37 +156,11 @@ impl GlContextInner { // 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); - } - - if pixel_format == 0 { - ReleaseDC(hwnd, hdc); - return Err(GlError::CreationFailed(())); + hdc.set_pixel_format(&PixelFormat::from_config(&config))?; } hdc.set_pixel_format_from_index(pixel_format)?; - let mut pfd: PIXELFORMATDESCRIPTOR = std::mem::zeroed(); - DescribePixelFormat( - hdc, - pixel_format, - std::mem::size_of::() as u32, - &mut pfd, - ); - SetPixelFormat(hdc, pixel_format, &pfd); - let profile_mask = match config.profile { Profile::Core => WGL_CONTEXT_CORE_PROFILE_BIT_ARB, Profile::Compatibility => WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, @@ -251,11 +193,11 @@ impl GlContextInner { } pub unsafe fn make_current(&self) { - wglMakeCurrent(self.hdc, self.hglrc); + let _ = self.hglrc.make_current(&self.hdc); } pub unsafe fn make_not_current(&self) { - wglMakeCurrent(self.hdc, std::ptr::null_mut()); + let _ = self.hglrc.make_not_current(); } pub fn get_proc_address(&self, symbol: &str) -> *const c_void { @@ -273,18 +215,13 @@ impl GlContextInner { } 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); } } diff --git a/src/wrappers/win32/window.rs b/src/wrappers/win32/window.rs index 00d5214f..daaa71f0 100644 --- a/src/wrappers/win32/window.rs +++ b/src/wrappers/win32/window.rs @@ -5,9 +5,12 @@ 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; diff --git a/src/wrappers/win32/window/dc.rs b/src/wrappers/win32/window/dc.rs index 87629d14..6a905906 100644 --- a/src/wrappers/win32/window/dc.rs +++ b/src/wrappers/win32/window/dc.rs @@ -1,3 +1,4 @@ +use crate::gl::GlConfig; use crate::wrappers::win32::window::HWnd; use std::ffi::c_void; use std::num::{NonZero, NonZeroI32}; @@ -5,9 +6,9 @@ use std::ptr::{null_mut, NonNull}; use windows_core::{Error, Result}; use windows_sys::Win32::Graphics::Gdi::{GetDC, HDC}; use windows_sys::Win32::Graphics::OpenGL::{ - wglCreateContext, wglDeleteContext, wglGetCurrentContext, wglMakeCurrent, ChoosePixelFormat, - DescribePixelFormat, SetPixelFormat, PFD_DOUBLEBUFFER, PFD_DRAW_TO_WINDOW, PFD_MAIN_PLANE, - PFD_SUPPORT_OPENGL, PFD_TYPE_RGBA, PIXELFORMATDESCRIPTOR, + wglCreateContext, wglDeleteContext, wglGetCurrentContext, wglGetProcAddress, wglMakeCurrent, + ChoosePixelFormat, DescribePixelFormat, SetPixelFormat, SwapBuffers, PFD_DOUBLEBUFFER, + PFD_DRAW_TO_WINDOW, PFD_MAIN_PLANE, PFD_SUPPORT_OPENGL, PFD_TYPE_RGBA, PIXELFORMATDESCRIPTOR, }; pub struct OwnDeviceContext { @@ -23,7 +24,7 @@ impl OwnDeviceContext { Ok(Self { inner: dc }) } - fn as_raw(&self) -> HDC { + pub fn as_raw(&self) -> HDC { self.inner.as_ptr() } @@ -80,6 +81,15 @@ impl OwnDeviceContext { 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(()) + } } pub struct WglContext { @@ -156,6 +166,14 @@ impl Default for PixelFormat { } 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, diff --git a/src/wrappers/win32/window/wgl.rs b/src/wrappers/win32/window/wgl.rs new file mode 100644 index 00000000..b399a97d --- /dev/null +++ b/src/wrappers/win32/window/wgl.rs @@ -0,0 +1,54 @@ +use std::ffi::CStr; +use std::fmt::{Display, Formatter}; +use std::mem::transmute; +use windows_sys::Win32::Graphics::Gdi::HDC; +use windows_sys::Win32::Graphics::OpenGL::{wglGetProcAddress, HGLRC}; + +// See https://www.khronos.org/registry/OpenGL/extensions/EXT/WGL_EXT_swap_control.txt +type WglSwapIntervalEXT = extern "system" fn(i32) -> i32; + +// 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; + +// See https://www.khronos.org/registry/OpenGL/extensions/ARB/WGL_ARB_create_context.txt +type WglCreateContextAttribsARB = 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(Self::load_fn( + c"wglCreateContextAttribsARB", + )?), + wglChoosePixelFormatARB: transmute(Self::load_fn(c"wglChoosePixelFormatARB")?), + wglSwapIntervalEXT: transmute(Self::load_fn(c"wglSwapIntervalEXT")?), + }) + } + } + + fn load_fn( + name: &'static CStr, + ) -> Result isize, MissingExtensionFunctionError> { + unsafe { wglGetProcAddress(name.as_ptr() as *const u8) } + .ok_or_else(|| MissingExtensionFunctionError { name }) + } +} + +#[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()) + } +} From 256eb5c59ad8b0c18b0b7455b66620762c45d591 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 14 Jul 2026 02:31:11 +0200 Subject: [PATCH 04/10] wip --- src/platform/win/gl.rs | 211 ++++++++----------------------- src/wrappers/win32.rs | 2 + src/wrappers/win32/library.rs | 28 ++++ src/wrappers/win32/user32.rs | 30 +---- src/wrappers/win32/window/dc.rs | 70 +--------- src/wrappers/win32/window/wgl.rs | 204 +++++++++++++++++++++++++++++- 6 files changed, 289 insertions(+), 256 deletions(-) create mode 100644 src/wrappers/win32/library.rs diff --git a/src/platform/win/gl.rs b/src/platform/win/gl.rs index 1a666450..aaad228b 100644 --- a/src/platform/win/gl.rs +++ b/src/platform/win/gl.rs @@ -1,50 +1,15 @@ use std::ffi::{c_void, CString}; +use std::num::NonZeroI32; use std::rc::Rc; -use windows_sys::{ - core::s, - Win32::{ - Foundation::{FreeLibrary, HMODULE}, - Graphics::OpenGL::{wglGetProcAddress, wglMakeCurrent}, - System::LibraryLoader::{GetProcAddress, LoadLibraryA}, - }, -}; +use windows_core::{s, PCSTR}; +use windows_sys::Win32::Graphics::OpenGL::wglGetProcAddress; use crate::gl::*; use crate::wrappers::win32::window::{ with_dummy_window, HWnd, MissingExtensionFunctionError, OwnDeviceContext, PixelFormat, - WglContext, WglExtra, + PixelFormatAttribs, WglContext, WglExtra, }; - -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; - -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; +use crate::wrappers::win32::LibraryModule; #[derive(Debug)] pub enum CreationFailedError { @@ -58,6 +23,12 @@ impl From for CreationFailedError { } } +impl From for CreationFailedError { + fn from(err: MissingExtensionFunctionError) -> Self { + CreationFailedError::MissingWglExtension(err) + } +} + pub type GlContext = Rc; impl From for GlError { @@ -67,151 +38,61 @@ impl From for GlError { } pub struct GlContextInner { - hwnd: HWnd, hdc: OwnDeviceContext, - hglrc: WglContext, - gl_library: HMODULE, + wgl_ctx: WglContext, + gl_library: LibraryModule, } impl GlContextInner { - pub unsafe fn create(window: HWnd, config: GlConfig) -> Result { + pub unsafe fn create(window: HWnd, config: GlConfig) -> Result { + let gl_library = LibraryModule::load(s!("opengl32.dll"))?; + // 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())?; let wgl_ctx = hdc.create_wgl_context()?; - wgl_ctx.with_current(&hdc, || WglExtra::load()) + wgl_ctx.with_current(&hdc, WglExtra::load) })??; // Create actual context - - let hwnd = window.as_raw(); - let hdc = window.get_own_dc()?; - - // 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; - extra.wglChoosePixelFormatARB.unwrap()( - hdc.as_raw(), - 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, - ]; - - extra.wglChoosePixelFormatARB.unwrap()( - hdc.as_raw(), - pixel_format_attribs_fallback.as_ptr(), - std::ptr::null(), - 1, - &mut pixel_format, - &mut num_formats, - ); + 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 no num_formats are found which happens in Wine for child windows, use fallback - if num_formats == 0 { - hdc.set_pixel_format(&PixelFormat::from_config(&config))?; - } + let wgl_ctx = extra.create_context_for_config(&hdc, &config)?; - hdc.set_pixel_format_from_index(pixel_format)?; - - 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 = extra.wglCreateContextAttribsARB.unwrap()( - hdc, - std::ptr::null_mut(), - ctx_attribs.as_ptr(), - ); - if hglrc.is_null() { - return Err(GlError::CreationFailed(())); - } + wgl_ctx.with_current(&hdc, || extra.set_vsync(config.vsync))??; - let gl_library = LoadLibraryA(s!("opengl32.dll")); - - wglMakeCurrent(hdc, hglrc); - extra.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) { - let _ = self.hglrc.make_current(&self.hdc); + let _ = self.wgl_ctx.make_current(&self.hdc); } pub unsafe fn make_not_current(&self) { - let _ = self.hglrc.make_not_current(); + 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) { @@ -219,10 +100,22 @@ impl GlContextInner { } } -impl Drop for GlContextInner { - fn drop(&mut self) { - unsafe { - 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/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/dc.rs b/src/wrappers/win32/window/dc.rs index 6a905906..e6507bcf 100644 --- a/src/wrappers/win32/window/dc.rs +++ b/src/wrappers/win32/window/dc.rs @@ -1,14 +1,14 @@ use crate::gl::GlConfig; -use crate::wrappers::win32::window::HWnd; +use crate::wrappers::win32::window::{HWnd, WglContext}; use std::ffi::c_void; use std::num::{NonZero, NonZeroI32}; -use std::ptr::{null_mut, NonNull}; +use std::ptr::NonNull; use windows_core::{Error, Result}; use windows_sys::Win32::Graphics::Gdi::{GetDC, HDC}; use windows_sys::Win32::Graphics::OpenGL::{ - wglCreateContext, wglDeleteContext, wglGetCurrentContext, wglGetProcAddress, wglMakeCurrent, - ChoosePixelFormat, DescribePixelFormat, SetPixelFormat, SwapBuffers, PFD_DOUBLEBUFFER, - PFD_DRAW_TO_WINDOW, PFD_MAIN_PLANE, PFD_SUPPORT_OPENGL, PFD_TYPE_RGBA, PIXELFORMATDESCRIPTOR, + wglCreateContext, ChoosePixelFormat, DescribePixelFormat, SetPixelFormat, SwapBuffers, + PFD_DOUBLEBUFFER, PFD_DRAW_TO_WINDOW, PFD_MAIN_PLANE, PFD_SUPPORT_OPENGL, PFD_TYPE_RGBA, + PIXELFORMATDESCRIPTOR, }; pub struct OwnDeviceContext { @@ -92,66 +92,6 @@ impl OwnDeviceContext { } } -pub struct WglContext { - inner: NonNull, -} - -impl WglContext { - pub unsafe fn make_current(&self, dc: &OwnDeviceContext) -> 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) -> 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) -> 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 - } -} - #[derive(Copy, Clone)] pub struct PixelFormat { pub alpha_bits: u8, diff --git a/src/wrappers/win32/window/wgl.rs b/src/wrappers/win32/window/wgl.rs index b399a97d..e5af848c 100644 --- a/src/wrappers/win32/window/wgl.rs +++ b/src/wrappers/win32/window/wgl.rs @@ -1,18 +1,93 @@ -use std::ffi::CStr; +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::{wglGetProcAddress, HGLRC}; +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 = extern "system" fn(i32) -> i32; +type WglSwapIntervalEXT = unsafe extern "system" fn(i32) -> i32; // 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; + 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 = extern "system" fn(HDC, HGLRC, *const i32) -> HGLRC; +type WglCreateContextAttribsARB = unsafe extern "system" fn(HDC, HGLRC, *const i32) -> HGLRC; #[allow(non_snake_case)] pub struct WglExtra { @@ -40,6 +115,125 @@ impl WglExtra { unsafe { wglGetProcAddress(name.as_ptr() as *const u8) } .ok_or_else(|| 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)] From 5a4c7c2552a07af04fcfc24a568002ccb61b14f3 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 14 Jul 2026 02:33:12 +0200 Subject: [PATCH 05/10] wip --- src/platform/win/gl.rs | 4 ++-- src/platform/win/window.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/win/gl.rs b/src/platform/win/gl.rs index aaad228b..01a2a952 100644 --- a/src/platform/win/gl.rs +++ b/src/platform/win/gl.rs @@ -44,8 +44,8 @@ pub struct GlContextInner { } impl GlContextInner { - pub unsafe fn create(window: HWnd, config: GlConfig) -> Result { - let gl_library = LibraryModule::load(s!("opengl32.dll"))?; + pub fn create(window: HWnd, config: GlConfig) -> Result { + let gl_library = unsafe { LibraryModule::load(s!("opengl32.dll"))? }; // Create temporary window and context to load function pointers let extra = with_dummy_window(|hwnd_tmp| { 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 { From ab1dd4d7b9c1e2367f7257cba5f6bdf8fff6c7a9 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 14 Jul 2026 02:37:38 +0200 Subject: [PATCH 06/10] Fix non-OpenGL build --- Cargo.toml | 4 ++-- src/wrappers/win32/window/handle.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) 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/wrappers/win32/window/handle.rs b/src/wrappers/win32/window/handle.rs index b675fe5d..4294c95a 100644 --- a/src/wrappers/win32/window/handle.rs +++ b/src/wrappers/win32/window/handle.rs @@ -1,6 +1,5 @@ use crate::wrappers::win32::style::WindowStyle; use crate::wrappers::win32::user32::ExtendedUser32; -use crate::wrappers::win32::window::OwnDeviceContext; use crate::wrappers::win32::{Dpi, DpiAwarenessContext, Rect}; use dpi::{PhysicalPosition, PhysicalSize}; use std::ffi::c_void; @@ -259,7 +258,8 @@ impl HWnd { Ok(PhysicalPosition::new(pt.x, pt.y)) } - pub fn get_own_dc(&self) -> Result { - OwnDeviceContext::from_window(*self) + #[cfg(feature = "opengl")] + pub fn get_own_dc(&self) -> Result { + super::OwnDeviceContext::from_window(*self) } } From a27785dd7355299c5effeea18901974423e312e0 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 14 Jul 2026 02:43:07 +0200 Subject: [PATCH 07/10] Fix non-OpenGL build --- src/wrappers/win32/window.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wrappers/win32/window.rs b/src/wrappers/win32/window.rs index daaa71f0..60d6e4f9 100644 --- a/src/wrappers/win32/window.rs +++ b/src/wrappers/win32/window.rs @@ -92,6 +92,7 @@ pub fn create_window( Ok(hwnd) } +#[cfg(feature = "opengl")] pub fn with_dummy_window(handler: impl FnOnce(HWnd) -> Result) -> Result { let instance = HInstance::get_from_dll(); let window_class = RegisteredClass::register_new(instance, None)?; From 6d021975144419f55ebbb1c02a87409755b136c9 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 14 Jul 2026 05:09:37 +0200 Subject: [PATCH 08/10] Fix non-OpenGL build --- src/wrappers/win32/window.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wrappers/win32/window.rs b/src/wrappers/win32/window.rs index 60d6e4f9..64681f90 100644 --- a/src/wrappers/win32/window.rs +++ b/src/wrappers/win32/window.rs @@ -25,7 +25,7 @@ use crate::wrappers::win32::h_instance::HInstance; use crate::wrappers::win32::style::WindowStyle; use crate::wrappers::win32::DpiAwarenessContext; use windows_sys::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM}; -use windows_sys::Win32::UI::WindowsAndMessaging::{CreateWindowExW, CW_USEDEFAULT}; +use windows_sys::Win32::UI::WindowsAndMessaging::CreateWindowExW; pub trait WindowImpl: 'static { /// Called during the processing of the WM_CREATE message, but after this type was properly @@ -94,6 +94,8 @@ pub fn create_window( #[cfg(feature = "opengl")] pub fn with_dummy_window(handler: impl FnOnce(HWnd) -> Result) -> Result { + use windows_sys::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT; + let instance = HInstance::get_from_dll(); let window_class = RegisteredClass::register_new(instance, None)?; let hwnd = unsafe { From 0ee5a1f5391e36e228e91d86aa88d56e57459cba Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 14 Jul 2026 05:19:53 +0200 Subject: [PATCH 09/10] Clippy fixes --- src/wrappers/win32/window/dc.rs | 2 +- src/wrappers/win32/window/wgl.rs | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/wrappers/win32/window/dc.rs b/src/wrappers/win32/window/dc.rs index e6507bcf..c0b83e1d 100644 --- a/src/wrappers/win32/window/dc.rs +++ b/src/wrappers/win32/window/dc.rs @@ -114,7 +114,7 @@ impl PixelFormat { } } - pub fn to_raw_descriptor(&self) -> PIXELFORMATDESCRIPTOR { + pub fn to_raw_descriptor(self) -> PIXELFORMATDESCRIPTOR { PIXELFORMATDESCRIPTOR { nSize: size_of::() as u16, nVersion: 1, diff --git a/src/wrappers/win32/window/wgl.rs b/src/wrappers/win32/window/wgl.rs index e5af848c..a82d28e4 100644 --- a/src/wrappers/win32/window/wgl.rs +++ b/src/wrappers/win32/window/wgl.rs @@ -100,20 +100,26 @@ impl WglExtra { pub fn load() -> Result { unsafe { Ok(Self { - wglCreateContextAttribsARB: transmute(Self::load_fn( - c"wglCreateContextAttribsARB", + 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", )?), - wglChoosePixelFormatARB: transmute(Self::load_fn(c"wglChoosePixelFormatARB")?), - wglSwapIntervalEXT: transmute(Self::load_fn(c"wglSwapIntervalEXT")?), }) } } - fn load_fn( - name: &'static CStr, - ) -> Result isize, MissingExtensionFunctionError> { - unsafe { wglGetProcAddress(name.as_ptr() as *const u8) } - .ok_or_else(|| MissingExtensionFunctionError { name }) + 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( From d996d7dcbe1d588eccd1fa78f265340db262d93b Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 14 Jul 2026 05:45:37 +0200 Subject: [PATCH 10/10] Fix dummy window creation --- src/wrappers/win32/window.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wrappers/win32/window.rs b/src/wrappers/win32/window.rs index 64681f90..6bb9e3a0 100644 --- a/src/wrappers/win32/window.rs +++ b/src/wrappers/win32/window.rs @@ -94,10 +94,10 @@ pub fn create_window( #[cfg(feature = "opengl")] pub fn with_dummy_window(handler: impl FnOnce(HWnd) -> Result) -> Result { - use windows_sys::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT; + use windows_sys::Win32::UI::WindowsAndMessaging::{DefWindowProcW, CW_USEDEFAULT}; let instance = HInstance::get_from_dll(); - let window_class = RegisteredClass::register_new(instance, None)?; + let window_class = RegisteredClass::register_new(instance, Some(DefWindowProcW))?; let hwnd = unsafe { CreateWindowExW( 0,