use std::sync::atomic::AtomicBool; use std::sync::{Arc, Mutex}; use std::io; use std::ptr; use std::mem; use std::thread; use super::callback; use super::Window; use super::MonitorID; use super::ContextWrapper; use super::WindowWrapper; use super::make_current_guard::CurrentContextGuard; use Api; use BuilderAttribs; use CreationError; use CreationError::OsError; use CursorState; use GlRequest; use PixelFormat; use std::ffi::{CStr, CString}; use std::sync::mpsc::channel; use libc; use super::gl; use winapi; use kernel32; use user32; use gdi32; /// Work-around the fact that HGLRC doesn't implement Send pub struct ContextHack(pub winapi::HGLRC); unsafe impl Send for ContextHack {} pub fn new_window(builder: BuilderAttribs<'static>, builder_sharelists: Option) -> Result { // initializing variables to be sent to the task let title = builder.title.utf16_units() .chain(Some(0).into_iter()).collect::>(); // title to utf16 let (tx, rx) = channel(); // `GetMessage` must be called in the same thread as CreateWindow, so we create a new thread // dedicated to this window. thread::spawn(move || { unsafe { // creating and sending the `Window` match init(title, builder, builder_sharelists) { Ok(w) => tx.send(Ok(w)).ok(), Err(e) => { tx.send(Err(e)).ok(); return; } }; // now that the `Window` struct is initialized, the main `Window::new()` function will // return and this events loop will run in parallel loop { let mut msg = mem::uninitialized(); if user32::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) == 0 { break; } user32::TranslateMessage(&msg); user32::DispatchMessageW(&msg); // calls `callback` (see the callback module) } } }); rx.recv().unwrap() } unsafe fn init(title: Vec, builder: BuilderAttribs<'static>, builder_sharelists: Option) -> Result { let builder_sharelists = builder_sharelists.map(|s| s.0); // registering the window class let class_name = register_window_class(); // building a RECT object with coordinates let mut rect = winapi::RECT { left: 0, right: builder.dimensions.unwrap_or((1024, 768)).0 as winapi::LONG, top: 0, bottom: builder.dimensions.unwrap_or((1024, 768)).1 as winapi::LONG, }; // switching to fullscreen if necessary // this means adjusting the window's position so that it overlaps the right monitor, // and change the monitor's resolution if necessary if builder.monitor.is_some() { let monitor = builder.monitor.as_ref().unwrap(); try!(switch_to_fullscreen(&mut rect, monitor)); } // computing the style and extended style of the window let (ex_style, style) = if builder.monitor.is_some() { (winapi::WS_EX_APPWINDOW, winapi::WS_POPUP | winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN) } else { (winapi::WS_EX_APPWINDOW | winapi::WS_EX_WINDOWEDGE, winapi::WS_OVERLAPPEDWINDOW | winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN) }; // adjusting the window coordinates using the style user32::AdjustWindowRectEx(&mut rect, style, 0, ex_style); // the first step is to create a dummy window and a dummy context which we will use // to load the pointers to some functions in the OpenGL driver in `extra_functions` let extra_functions = { // creating a dummy invisible window let dummy_window = { let handle = user32::CreateWindowExW(ex_style, class_name.as_ptr(), title.as_ptr() as winapi::LPCWSTR, style | winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN, winapi::CW_USEDEFAULT, winapi::CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top, ptr::null_mut(), ptr::null_mut(), kernel32::GetModuleHandleW(ptr::null()), ptr::null_mut()); if handle.is_null() { return Err(OsError(format!("CreateWindowEx function failed: {}", format!("{}", io::Error::last_os_error())))); } let hdc = user32::GetDC(handle); if hdc.is_null() { let err = Err(OsError(format!("GetDC function failed: {}", format!("{}", io::Error::last_os_error())))); return err; } WindowWrapper(handle, hdc) }; // getting the pixel format that we will use and setting it { let formats = enumerate_native_pixel_formats(&dummy_window); let (id, _) = try!(builder.choose_pixel_format(formats.into_iter().map(|(a, b)| (b, a)))); try!(set_pixel_format(&dummy_window, id)); } // creating the dummy OpenGL context and making it current let dummy_context = try!(create_context(None, &dummy_window, None)); let current_context = try!(CurrentContextGuard::make_current(&dummy_window, &dummy_context)); // loading the extra WGL functions gl::wgl_extra::Wgl::load_with(|addr| { use libc; let addr = CString::new(addr.as_bytes()).unwrap(); let addr = addr.as_ptr(); gl::wgl::GetProcAddress(addr) as *const libc::c_void }) }; // creating the real window this time, by using the functions in `extra_functions` let real_window = { let (width, height) = if builder.monitor.is_some() || builder.dimensions.is_some() { (Some(rect.right - rect.left), Some(rect.bottom - rect.top)) } else { (None, None) }; let (x, y) = if builder.monitor.is_some() { (Some(rect.left), Some(rect.top)) } else { (None, None) }; let style = if !builder.visible || builder.headless { style } else { style | winapi::WS_VISIBLE }; let handle = user32::CreateWindowExW(ex_style, class_name.as_ptr(), title.as_ptr() as winapi::LPCWSTR, style | winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN, x.unwrap_or(winapi::CW_USEDEFAULT), y.unwrap_or(winapi::CW_USEDEFAULT), width.unwrap_or(winapi::CW_USEDEFAULT), height.unwrap_or(winapi::CW_USEDEFAULT), ptr::null_mut(), ptr::null_mut(), kernel32::GetModuleHandleW(ptr::null()), ptr::null_mut()); if handle.is_null() { return Err(OsError(format!("CreateWindowEx function failed: {}", format!("{}", io::Error::last_os_error())))); } let hdc = user32::GetDC(handle); if hdc.is_null() { return Err(OsError(format!("GetDC function failed: {}", format!("{}", io::Error::last_os_error())))); } WindowWrapper(handle, hdc) }; // calling SetPixelFormat { let formats = if extra_functions.GetPixelFormatAttribivARB.is_loaded() { enumerate_arb_pixel_formats(&extra_functions, &real_window) } else { enumerate_native_pixel_formats(&real_window) }; let (id, _) = try!(builder.choose_pixel_format(formats.into_iter().map(|(a, b)| (b, a)))); try!(set_pixel_format(&real_window, id)); } // creating the OpenGL context let context = try!(create_context(Some((&extra_functions, &builder)), &real_window, builder_sharelists)); // calling SetForegroundWindow if fullscreen if builder.monitor.is_some() { user32::SetForegroundWindow(real_window.0); } // Creating a mutex to track the current cursor state let cursor_state = Arc::new(Mutex::new(CursorState::Normal)); // filling the CONTEXT_STASH task-local storage so that we can start receiving events let events_receiver = { let (tx, rx) = channel(); let mut tx = Some(tx); callback::CONTEXT_STASH.with(|context_stash| { let data = callback::ThreadLocalData { win: real_window.0, sender: tx.take().unwrap(), cursor_state: cursor_state.clone() }; (*context_stash.borrow_mut()) = Some(data); }); rx }; // loading the opengl32 module let gl_library = try!(load_opengl32_dll()); // handling vsync if builder.vsync { if extra_functions.SwapIntervalEXT.is_loaded() { let _guard = try!(CurrentContextGuard::make_current(&real_window, &context)); if extra_functions.SwapIntervalEXT(1) == 0 { return Err(OsError(format!("wglSwapIntervalEXT failed"))); } } } // building the struct Ok(Window { window: real_window, context: context, gl_library: gl_library, events_receiver: events_receiver, is_closed: AtomicBool::new(false), cursor_state: cursor_state, }) } unsafe fn register_window_class() -> Vec { let class_name: Vec = "Window Class".utf16_units().chain(Some(0).into_iter()) .collect::>(); let class = winapi::WNDCLASSEXW { cbSize: mem::size_of::() as winapi::UINT, style: winapi::CS_HREDRAW | winapi::CS_VREDRAW | winapi::CS_OWNDC, lpfnWndProc: Some(callback::callback), cbClsExtra: 0, cbWndExtra: 0, hInstance: kernel32::GetModuleHandleW(ptr::null()), hIcon: ptr::null_mut(), hCursor: ptr::null_mut(), // must be null in order for cursor state to work properly hbrBackground: ptr::null_mut(), lpszMenuName: ptr::null(), lpszClassName: class_name.as_ptr(), hIconSm: ptr::null_mut(), }; // We ignore errors because registering the same window class twice would trigger // an error, and because errors here are detected during CreateWindowEx anyway. // Also since there is no weird element in the struct, there is no reason for this // call to fail. user32::RegisterClassExW(&class); class_name } unsafe fn switch_to_fullscreen(rect: &mut winapi::RECT, monitor: &MonitorID) -> Result<(), CreationError> { // adjusting the rect { let pos = monitor.get_position(); rect.left += pos.0 as winapi::LONG; rect.right += pos.0 as winapi::LONG; rect.top += pos.1 as winapi::LONG; rect.bottom += pos.1 as winapi::LONG; } // changing device settings let mut screen_settings: winapi::DEVMODEW = mem::zeroed(); screen_settings.dmSize = mem::size_of::() as winapi::WORD; screen_settings.dmPelsWidth = (rect.right - rect.left) as winapi::DWORD; screen_settings.dmPelsHeight = (rect.bottom - rect.top) as winapi::DWORD; screen_settings.dmBitsPerPel = 32; // TODO: ? screen_settings.dmFields = winapi::DM_BITSPERPEL | winapi::DM_PELSWIDTH | winapi::DM_PELSHEIGHT; let result = user32::ChangeDisplaySettingsExW(monitor.get_adapter_name().as_ptr(), &mut screen_settings, ptr::null_mut(), winapi::CDS_FULLSCREEN, ptr::null_mut()); if result != winapi::DISP_CHANGE_SUCCESSFUL { return Err(OsError(format!("ChangeDisplaySettings failed: {}", result))); } Ok(()) } unsafe fn create_context(extra: Option<(&gl::wgl_extra::Wgl, &BuilderAttribs<'static>)>, hdc: &WindowWrapper, share: Option) -> Result { let share = share.unwrap_or(ptr::null_mut()); let ctxt = if let Some((extra_functions, builder)) = extra { if extra_functions.CreateContextAttribsARB.is_loaded() { let mut attributes = Vec::new(); match builder.gl_version { GlRequest::Latest => {}, GlRequest::Specific(Api::OpenGl, (major, minor)) => { attributes.push(gl::wgl_extra::CONTEXT_MAJOR_VERSION_ARB as libc::c_int); attributes.push(major as libc::c_int); attributes.push(gl::wgl_extra::CONTEXT_MINOR_VERSION_ARB as libc::c_int); attributes.push(minor as libc::c_int); }, GlRequest::Specific(Api::OpenGlEs, (major, minor)) => { if is_extension_supported(extra_functions, hdc, "WGL_EXT_create_context_es2_profile") { attributes.push(gl::wgl_extra::CONTEXT_PROFILE_MASK_ARB as libc::c_int); attributes.push(gl::wgl_extra::CONTEXT_ES2_PROFILE_BIT_EXT as libc::c_int); } else { return Err(CreationError::NotSupported); } attributes.push(gl::wgl_extra::CONTEXT_MAJOR_VERSION_ARB as libc::c_int); attributes.push(major as libc::c_int); attributes.push(gl::wgl_extra::CONTEXT_MINOR_VERSION_ARB as libc::c_int); attributes.push(minor as libc::c_int); }, GlRequest::Specific(_, _) => return Err(CreationError::NotSupported), GlRequest::GlThenGles { opengl_version: (major, minor), .. } => { attributes.push(gl::wgl_extra::CONTEXT_MAJOR_VERSION_ARB as libc::c_int); attributes.push(major as libc::c_int); attributes.push(gl::wgl_extra::CONTEXT_MINOR_VERSION_ARB as libc::c_int); attributes.push(minor as libc::c_int); }, } if builder.gl_debug { attributes.push(gl::wgl_extra::CONTEXT_FLAGS_ARB as libc::c_int); attributes.push(gl::wgl_extra::CONTEXT_DEBUG_BIT_ARB as libc::c_int); } attributes.push(0); Some(extra_functions.CreateContextAttribsARB(hdc.1 as *const libc::c_void, share as *const libc::c_void, attributes.as_ptr())) } else { None } } else { None }; let ctxt = match ctxt { Some(ctxt) => ctxt, None => { let ctxt = gl::wgl::CreateContext(hdc.1 as *const libc::c_void); if !ctxt.is_null() && !share.is_null() { gl::wgl::ShareLists(share as *const libc::c_void, ctxt); }; ctxt } }; if ctxt.is_null() { return Err(OsError(format!("OpenGL context creation failed: {}", format!("{}", io::Error::last_os_error())))); } Ok(ContextWrapper(ctxt as winapi::HGLRC)) } unsafe fn enumerate_native_pixel_formats(hdc: &WindowWrapper) -> Vec<(PixelFormat, libc::c_int)> { let size_of_pxfmtdescr = mem::size_of::() as u32; let num = gdi32::DescribePixelFormat(hdc.1, 1, size_of_pxfmtdescr, ptr::null_mut()); let mut result = Vec::new(); for index in (0 .. num) { let mut output: winapi::PIXELFORMATDESCRIPTOR = mem::zeroed(); if gdi32::DescribePixelFormat(hdc.1, index, size_of_pxfmtdescr, &mut output) == 0 { continue; } if (output.dwFlags & winapi::PFD_DRAW_TO_WINDOW) == 0 { continue; } if (output.dwFlags & winapi::PFD_SUPPORT_OPENGL) == 0 { continue; } if output.iPixelType != winapi::PFD_TYPE_RGBA { continue; } result.push((PixelFormat { hardware_accelerated: (output.dwFlags & winapi::PFD_GENERIC_FORMAT) == 0, red_bits: output.cRedBits, green_bits: output.cGreenBits, blue_bits: output.cBlueBits, alpha_bits: output.cAlphaBits, depth_bits: output.cDepthBits, stencil_bits: output.cStencilBits, stereoscopy: (output.dwFlags & winapi::PFD_STEREO) != 0, double_buffer: (output.dwFlags & winapi::PFD_DOUBLEBUFFER) != 0, multisampling: None, srgb: false, }, index)); } result } unsafe fn enumerate_arb_pixel_formats(extra: &gl::wgl_extra::Wgl, hdc: &WindowWrapper) -> Vec<(PixelFormat, libc::c_int)> { let get_info = |index: u32, attrib: u32| { let mut value = mem::uninitialized(); extra.GetPixelFormatAttribivARB(hdc.1 as *const libc::c_void, index as libc::c_int, 0, 1, [attrib as libc::c_int].as_ptr(), &mut value); value as u32 }; // getting the number of formats // the `1` is ignored let num = get_info(1, gl::wgl_extra::NUMBER_PIXEL_FORMATS_ARB); let mut result = Vec::new(); for index in (0 .. num) { if get_info(index, gl::wgl_extra::DRAW_TO_WINDOW_ARB) == 0 { continue; } if get_info(index, gl::wgl_extra::SUPPORT_OPENGL_ARB) == 0 { continue; } if get_info(index, gl::wgl_extra::ACCELERATION_ARB) == gl::wgl_extra::NO_ACCELERATION_ARB { continue; } if get_info(index, gl::wgl_extra::PIXEL_TYPE_ARB) != gl::wgl_extra::TYPE_RGBA_ARB { continue; } result.push((PixelFormat { hardware_accelerated: true, red_bits: get_info(index, gl::wgl_extra::RED_BITS_ARB) as u8, green_bits: get_info(index, gl::wgl_extra::GREEN_BITS_ARB) as u8, blue_bits: get_info(index, gl::wgl_extra::BLUE_BITS_ARB) as u8, alpha_bits: get_info(index, gl::wgl_extra::ALPHA_BITS_ARB) as u8, depth_bits: get_info(index, gl::wgl_extra::DEPTH_BITS_ARB) as u8, stencil_bits: get_info(index, gl::wgl_extra::STENCIL_BITS_ARB) as u8, stereoscopy: get_info(index, gl::wgl_extra::STEREO_ARB) != 0, double_buffer: get_info(index, gl::wgl_extra::DOUBLE_BUFFER_ARB) != 0, multisampling: None, // FIXME: srgb: false, // FIXME: }, index as libc::c_int)); } result } unsafe fn set_pixel_format(hdc: &WindowWrapper, id: libc::c_int) -> Result<(), CreationError> { let mut output: winapi::PIXELFORMATDESCRIPTOR = mem::zeroed(); if gdi32::DescribePixelFormat(hdc.1, id, mem::size_of::() as winapi::UINT, &mut output) == 0 { return Err(OsError(format!("DescribePixelFormat function failed: {}", format!("{}", io::Error::last_os_error())))); } if gdi32::SetPixelFormat(hdc.1, id, &output) == 0 { return Err(OsError(format!("SetPixelFormat function failed: {}", format!("{}", io::Error::last_os_error())))); } Ok(()) } unsafe fn load_opengl32_dll() -> Result { let name = "opengl32.dll".utf16_units().chain(Some(0).into_iter()) .collect::>().as_ptr(); let lib = kernel32::LoadLibraryW(name); if lib.is_null() { return Err(OsError(format!("LoadLibrary function failed: {}", format!("{}", io::Error::last_os_error())))); } Ok(lib) } unsafe fn is_extension_supported(extra: &gl::wgl_extra::Wgl, hdc: &WindowWrapper, extension: &str) -> bool { let extensions = if extra.GetExtensionsStringARB.is_loaded() { let data = extra.GetExtensionsStringARB(hdc.1 as *const _); let data = CStr::from_ptr(data).to_bytes().to_vec(); String::from_utf8(data).unwrap() } else if extra.GetExtensionsStringEXT.is_loaded() { let data = extra.GetExtensionsStringEXT(); let data = CStr::from_ptr(data).to_bytes().to_vec(); String::from_utf8(data).unwrap() } else { return false; }; extensions.split(" ").find(|&e| e == extension).is_some() }