use std::mem;
use super::wgl_attributes::*;
use std::ffi::{CStr, CString, OsStr};
use std::os::raw::{c_void, c_int};
use std::os::windows::ffi::OsStrExt;
use std::io;
use std::ptr;
use winapi::shared::minwindef::{LPARAM, LRESULT, UINT, WPARAM};
use winapi::shared::windef::{HDC, HWND, HGLRC, RECT};
use winapi::um::libloaderapi::GetModuleHandleW;
use winapi::um::wingdi::PIXELFORMATDESCRIPTOR;
use winapi::um::wingdi::{PFD_MAIN_PLANE, PFD_DOUBLEBUFFER, PFD_STEREO, PFD_DRAW_TO_WINDOW};
use winapi::um::wingdi::{PFD_TYPE_RGBA, PFD_SUPPORT_OPENGL, PFD_GENERIC_FORMAT};
use winapi::um::wingdi::{SetPixelFormat, DescribePixelFormat, ChoosePixelFormat};
use winapi::um::winnt::LONG;
use winapi::um::winuser::{CS_OWNDC, CS_HREDRAW, CS_VREDRAW, WNDCLASSEXW};
use winapi::um::winuser::{CW_USEDEFAULT, WINDOWPLACEMENT, WS_EX_APPWINDOW, WS_POPUP};
use winapi::um::winuser::{WS_CLIPCHILDREN, WM_ERASEBKGND, WM_PAINT};
use winapi::um::winuser::{WS_EX_WINDOWEDGE, WS_OVERLAPPEDWINDOW, WS_CLIPSIBLINGS};
use winapi::um::winuser::{AdjustWindowRectEx, CreateWindowExW, GetDC, ReleaseDC};
use winapi::um::winuser::{DestroyWindow, RegisterClassExW, DefWindowProcW, GetClassNameW};
use winapi::um::winuser::{GetWindowPlacement};
use super::wgl;
use super::wgl_ext;
pub unsafe fn create_offscreen(shared_with: HGLRC,
settings: &WGLAttributes)
-> Result<(HGLRC, HDC), String> {
let mut ctx = WGLScopedContext::default();
ctx.window = create_hidden_window()?;
ctx.device_ctx = GetDC(ctx.window);
if ctx.device_ctx.is_null() {
return Err("GetDC function failed".to_owned());
}
let extra = load_extra_functions(ctx.window)?;
let extensions = if extra.GetExtensionsStringARB.is_loaded() {
let data = extra.GetExtensionsStringARB(ctx.device_ctx 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 {
String::new()
};
let (id, _) = if extensions.split(' ').find(|&i| i == "WGL_ARB_pixel_format").is_some() {
choose_arb_pixel_format(&extra, &extensions, ctx.device_ctx, &settings.pixel_format)
.map_err(|_| "ARB pixel format not available".to_owned())?
} else {
choose_native_pixel_format(ctx.device_ctx, &settings.pixel_format)
.map_err(|_| "Native pixel format not available".to_owned())?
};
set_pixel_format(ctx.device_ctx, id)?;
let result = create_full_context(settings, &extra, &extensions, ctx.device_ctx, shared_with);
if result.is_ok() {
mem::forget(ctx); }
result
}
unsafe fn create_basic_context(hdc: HDC,
share: HGLRC)
-> Result<(HGLRC, HDC), String> {
let mut ctx = WGLScopedContext::default();
ctx.render_ctx = wgl::CreateContext(hdc as *const _) as HGLRC;
if ctx.render_ctx.is_null() {
return Err(format!("wglCreateContext failed: {}", io::Error::last_os_error()));
}
if !share.is_null() {
if wgl::ShareLists(share as *const _, ctx.render_ctx as *const _) == 0 {
return Err(format!("wglShareLists failed: {}", io::Error::last_os_error()));
}
};
let result = (ctx.render_ctx, hdc);
mem::forget(ctx); Ok(result)
}
unsafe fn create_full_context(settings: &WGLAttributes,
extra: &wgl_ext::Wgl,
extensions: &str,
hdc: HDC,
share: HGLRC)
-> Result<(HGLRC, HDC), String> {
let mut extensions = extensions.split(' ');
if extensions.find(|&i| i == "WGL_ARB_create_context").is_none() {
let ctx = create_basic_context(hdc, share);
if let Ok(ctx) = ctx {
if wgl::MakeCurrent(ctx.1 as *const _, ctx.0 as *const _) == 0 {
return Err("wglMakeCurrent failed creating a basic context".to_owned());
}
}
return ctx;
}
let mut attributes = Vec::new();
if settings.opengl_es {
if extensions.find(|&i| i == "WGL_EXT_create_context_es2_profile").is_some() {
attributes.push(wgl_ext::CONTEXT_PROFILE_MASK_ARB as c_int);
attributes.push(wgl_ext::CONTEXT_ES2_PROFILE_BIT_EXT as c_int);
} else {
return Err("OpenGl Version Not Supported".to_owned());
}
}
if settings.major_version > 0 {
attributes.push(wgl_ext::CONTEXT_MAJOR_VERSION_ARB as c_int);
attributes.push(settings.major_version as c_int);
attributes.push(wgl_ext::CONTEXT_MINOR_VERSION_ARB as c_int);
attributes.push(settings.minor_version as c_int);
}
attributes.push(wgl_ext::CONTEXT_FLAGS_ARB as c_int);
attributes.push((if settings.debug {
wgl_ext::CONTEXT_FLAGS_ARB
} else {
0
}) as c_int);
attributes.push(0);
let mut ctx = WGLScopedContext::default();
ctx.render_ctx = extra.CreateContextAttribsARB(hdc as *const _,
share as *const _,
attributes.as_ptr()) as HGLRC;
if ctx.render_ctx.is_null() {
return Err(format!("wglCreateContextAttribsARB failed: {}",
io::Error::last_os_error()));
}
if wgl::MakeCurrent(hdc as *const _, ctx.render_ctx as *const _) == 0 {
return Err("wglMakeCurrent failed creating full context".to_owned());
}
if extensions.find(|&i| i == "WGL_EXT_swap_control").is_some() {
if extra.SwapIntervalEXT(if settings.vsync { 1 } else { 0 }) == 0 {
return Err("wglSwapIntervalEXT failed".to_owned());
}
}
let result = (ctx.render_ctx as HGLRC, hdc);
mem::forget(ctx);
Ok(result)
}
unsafe fn create_hidden_window() -> Result<HWND, &'static str> {
let class_name = register_window_class();
let mut rect = RECT {
left: 0,
right: 1024 as LONG,
top: 0,
bottom: 768 as LONG,
};
let ex_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
let style = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
AdjustWindowRectEx(&mut rect, style, 0, ex_style);
let title = OsStr::new("WGLwindow")
.encode_wide()
.chain(Some(0).into_iter())
.collect::<Vec<_>>();
let win = CreateWindowExW(ex_style,
class_name.as_ptr(),
title.as_ptr(),
style,
CW_USEDEFAULT,
CW_USEDEFAULT,
rect.right - rect.left,
rect.bottom - rect.top,
ptr::null_mut(),
ptr::null_mut(),
GetModuleHandleW(ptr::null()),
ptr::null_mut());
if win.is_null() {
return Err("CreateWindowEx function failed");
}
Ok(win)
}
struct WGLScopedContext {
pub window: HWND,
pub device_ctx: HDC,
pub render_ctx: HGLRC,
}
impl Drop for WGLScopedContext {
fn drop(&mut self) {
unsafe {
if !self.render_ctx.is_null() {
wgl::DeleteContext(self.render_ctx as *const _);
}
if !self.device_ctx.is_null() && !self.window.is_null() {
ReleaseDC(self.window, self.device_ctx);
}
if !self.window.is_null() {
DestroyWindow(self.window);
}
}
}
}
impl Default for WGLScopedContext {
#[inline]
fn default() -> WGLScopedContext {
WGLScopedContext {
window: ptr::null_mut(),
device_ctx: ptr::null_mut(),
render_ctx: ptr::null_mut(),
}
}
}
impl WGLScopedContext {
fn new(window: HWND, device_ctx: HDC, render_ctx: HGLRC) -> WGLScopedContext {
WGLScopedContext {
window: window,
device_ctx: device_ctx,
render_ctx: render_ctx,
}
}
}
unsafe fn register_window_class() -> Vec<u16> {
let class_name = OsStr::new("Window Class")
.encode_wide()
.chain(Some(0).into_iter())
.collect::<Vec<_>>();
let class = WNDCLASSEXW {
cbSize: mem::size_of::<WNDCLASSEXW>() as UINT,
style: CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
lpfnWndProc: Some(proc_callback),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: GetModuleHandleW(ptr::null()),
hIcon: ptr::null_mut(),
hCursor: ptr::null_mut(), hbrBackground: ptr::null_mut(),
lpszMenuName: ptr::null(),
lpszClassName: class_name.as_ptr(),
hIconSm: ptr::null_mut(),
};
RegisterClassExW(&class);
class_name
}
pub unsafe extern "system" fn proc_callback(window: HWND,
msg: UINT,
wparam: WPARAM,
lparam: LPARAM)
-> LRESULT {
match msg {
WM_PAINT => 0,
WM_ERASEBKGND => 0,
_ => DefWindowProcW(window, msg, wparam, lparam),
}
}
#[derive(Debug, Clone)]
pub struct PixelFormat {
pub hardware_accelerated: bool,
pub color_bits: u8,
pub alpha_bits: u8,
pub depth_bits: u8,
pub stencil_bits: u8,
pub stereoscopy: bool,
pub double_buffer: bool,
pub multisampling: Option<u16>,
pub srgb: bool,
}
unsafe fn choose_arb_pixel_format(extra: &wgl_ext::Wgl,
extensions: &str,
hdc: HDC,
reqs: &WGLPixelFormat)
-> Result<(c_int, PixelFormat), ()> {
let descriptor = {
let mut out: Vec<c_int> = Vec::with_capacity(37);
out.push(wgl_ext::DRAW_TO_WINDOW_ARB as c_int);
out.push(1);
out.push(wgl_ext::SUPPORT_OPENGL_ARB as c_int);
out.push(1);
out.push(wgl_ext::PIXEL_TYPE_ARB as c_int);
if reqs.float_color_buffer {
if extensions.split(' ').find(|&i| i == "WGL_ARB_pixel_format_float").is_some() {
out.push(wgl_ext::TYPE_RGBA_FLOAT_ARB as c_int);
} else {
return Err(());
}
} else {
out.push(wgl_ext::TYPE_RGBA_ARB as c_int);
}
out.push(wgl_ext::ACCELERATION_ARB as c_int);
out.push(wgl_ext::FULL_ACCELERATION_ARB as c_int);
if let Some(color) = reqs.color_bits {
out.push(wgl_ext::COLOR_BITS_ARB as c_int);
out.push(color as c_int);
}
if let Some(alpha) = reqs.alpha_bits {
out.push(wgl_ext::ALPHA_BITS_ARB as c_int);
out.push(alpha as c_int);
}
if let Some(depth) = reqs.depth_bits {
out.push(wgl_ext::DEPTH_BITS_ARB as c_int);
out.push(depth as c_int);
}
if let Some(stencil) = reqs.stencil_bits {
out.push(wgl_ext::STENCIL_BITS_ARB as c_int);
out.push(stencil as c_int);
}
let double_buffer = reqs.double_buffer.unwrap_or(true);
out.push(wgl_ext::DOUBLE_BUFFER_ARB as c_int);
out.push(if double_buffer { 1 } else { 0 });
if let Some(multisampling) = reqs.multisampling {
if extensions.split(' ').find(|&i| i == "WGL_ARB_multisample").is_some() {
out.push(wgl_ext::SAMPLE_BUFFERS_ARB as c_int);
out.push(if multisampling == 0 { 0 } else { 1 });
out.push(wgl_ext::SAMPLES_ARB as c_int);
out.push(multisampling as c_int);
} else {
return Err(());
}
}
out.push(wgl_ext::STEREO_ARB as c_int);
out.push(if reqs.stereoscopy { 1 } else { 0 });
if reqs.srgb {
if extensions.split(' ').find(|&i| i == "WGL_ARB_framebuffer_sRGB").is_some() {
out.push(wgl_ext::FRAMEBUFFER_SRGB_CAPABLE_ARB as c_int);
out.push(1);
} else if extensions.split(' ').find(|&i| i == "WGL_EXT_framebuffer_sRGB").is_some() {
out.push(wgl_ext::FRAMEBUFFER_SRGB_CAPABLE_EXT as c_int);
out.push(1);
} else {
return Err(());
}
}
out.push(0);
out
};
let mut format_id = mem::uninitialized();
let mut num_formats = mem::uninitialized();
if extra.ChoosePixelFormatARB(hdc as *const _,
descriptor.as_ptr(),
ptr::null(),
1,
&mut format_id,
&mut num_formats) == 0 {
return Err(());
}
if num_formats == 0 {
return Err(());
}
let get_info = |attrib: u32| {
let mut value = mem::uninitialized();
extra.GetPixelFormatAttribivARB(hdc as *const _,
format_id as c_int,
0,
1,
[attrib as c_int].as_ptr(),
&mut value);
value as u32
};
let pf_desc = PixelFormat {
hardware_accelerated: get_info(wgl_ext::ACCELERATION_ARB) != wgl_ext::NO_ACCELERATION_ARB,
color_bits: get_info(wgl_ext::RED_BITS_ARB) as u8 +
get_info(wgl_ext::GREEN_BITS_ARB) as u8 +
get_info(wgl_ext::BLUE_BITS_ARB) as u8,
alpha_bits: get_info(wgl_ext::ALPHA_BITS_ARB) as u8,
depth_bits: get_info(wgl_ext::DEPTH_BITS_ARB) as u8,
stencil_bits: get_info(wgl_ext::STENCIL_BITS_ARB) as u8,
stereoscopy: get_info(wgl_ext::STEREO_ARB) != 0,
double_buffer: get_info(wgl_ext::DOUBLE_BUFFER_ARB) != 0,
multisampling: {
if extensions.split(' ').find(|&i| i == "WGL_ARB_multisample").is_some() {
match get_info(wgl_ext::SAMPLES_ARB) {
0 => None,
a => Some(a as u16),
}
} else {
None
}
},
srgb: if extensions.split(' ').find(|&i| i == "WGL_ARB_framebuffer_sRGB").is_some() {
get_info(wgl_ext::FRAMEBUFFER_SRGB_CAPABLE_ARB) != 0
} else if extensions.split(' ')
.find(|&i| i == "WGL_EXT_framebuffer_sRGB")
.is_some() {
get_info(wgl_ext::FRAMEBUFFER_SRGB_CAPABLE_EXT) != 0
} else {
false
},
};
Ok((format_id, pf_desc))
}
unsafe fn choose_native_pixel_format(hdc: HDC,
reqs: &WGLPixelFormat)
-> Result<(c_int, PixelFormat), ()> {
if reqs.float_color_buffer {
return Err(());
}
match reqs.multisampling {
Some(0) | None => (),
Some(_) => return Err(()),
};
if reqs.stereoscopy {
return Err(());
}
if reqs.srgb {
return Err(());
}
let descriptor = PIXELFORMATDESCRIPTOR {
nSize: mem::size_of::<PIXELFORMATDESCRIPTOR>() as u16,
nVersion: 1,
dwFlags: {
let f1 = match reqs.double_buffer {
None => PFD_DOUBLEBUFFER,
Some(true) => PFD_DOUBLEBUFFER,
Some(false) => 0,
};
let f2 = if reqs.stereoscopy {
PFD_STEREO
} else {
0
};
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | f1 | f2
},
iPixelType: PFD_TYPE_RGBA,
cColorBits: reqs.color_bits.unwrap_or(0),
cRedBits: 0,
cRedShift: 0,
cGreenBits: 0,
cGreenShift: 0,
cBlueBits: 0,
cBlueShift: 0,
cAlphaBits: reqs.alpha_bits.unwrap_or(0),
cAlphaShift: 0,
cAccumBits: 0,
cAccumRedBits: 0,
cAccumGreenBits: 0,
cAccumBlueBits: 0,
cAccumAlphaBits: 0,
cDepthBits: reqs.depth_bits.unwrap_or(0),
cStencilBits: reqs.stencil_bits.unwrap_or(0),
cAuxBuffers: 0,
iLayerType: PFD_MAIN_PLANE,
bReserved: 0,
dwLayerMask: 0,
dwVisibleMask: 0,
dwDamageMask: 0,
};
let pf_id = ChoosePixelFormat(hdc, &descriptor);
if pf_id == 0 {
return Err(());
}
let mut output: PIXELFORMATDESCRIPTOR = mem::zeroed();
if DescribePixelFormat(hdc,
pf_id,
mem::size_of::<PIXELFORMATDESCRIPTOR>() as u32,
&mut output) == 0 {
return Err(());
}
if (output.dwFlags & PFD_DRAW_TO_WINDOW) == 0 {
return Err(());
}
if (output.dwFlags & PFD_SUPPORT_OPENGL) == 0 {
return Err(());
}
if output.iPixelType != PFD_TYPE_RGBA {
return Err(());
}
let pf_desc = PixelFormat {
hardware_accelerated: (output.dwFlags & PFD_GENERIC_FORMAT) == 0,
color_bits: output.cRedBits + output.cGreenBits + output.cBlueBits,
alpha_bits: output.cAlphaBits,
depth_bits: output.cDepthBits,
stencil_bits: output.cStencilBits,
stereoscopy: (output.dwFlags & PFD_STEREO) != 0,
double_buffer: (output.dwFlags & PFD_DOUBLEBUFFER) != 0,
multisampling: None,
srgb: false,
};
if pf_desc.alpha_bits < reqs.alpha_bits.unwrap_or(0) {
return Err(());
}
if pf_desc.depth_bits < reqs.depth_bits.unwrap_or(0) {
return Err(());
}
if pf_desc.stencil_bits < reqs.stencil_bits.unwrap_or(0) {
return Err(());
}
if pf_desc.color_bits < reqs.color_bits.unwrap_or(0) {
return Err(());
}
if !pf_desc.hardware_accelerated {
return Err(());
}
if let Some(req) = reqs.double_buffer {
if pf_desc.double_buffer != req {
return Err(());
}
}
Ok((pf_id, pf_desc))
}
unsafe fn set_pixel_format(hdc: HDC, id: c_int) -> Result<(), String> {
let mut output: PIXELFORMATDESCRIPTOR = mem::zeroed();
if DescribePixelFormat(hdc, id, mem::size_of::<PIXELFORMATDESCRIPTOR>()
as UINT, &mut output) == 0 {
return Err(format!("DescribePixelFormat function failed: {}",io::Error::last_os_error()));
}
if SetPixelFormat(hdc, id, &output) == 0 {
return Err(format!("SetPixelFormat function failed: {}",
io::Error::last_os_error()));
}
Ok(())
}
unsafe fn load_extra_functions(window: HWND) -> Result<wgl_ext::Wgl, String> {
let (ex_style, style) = (WS_EX_APPWINDOW,
WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
let mut dummy_window = {
let rect = {
let mut placement: WINDOWPLACEMENT = mem::zeroed();
placement.length = mem::size_of::<WINDOWPLACEMENT>() as UINT;
if GetWindowPlacement(window, &mut placement) == 0 {
return Err("GetWindowPlacement failed".to_owned());
}
placement.rcNormalPosition
};
let mut class_name = [0u16; 128];
if GetClassNameW(window, class_name.as_mut_ptr(), 128) == 0 {
return Err(format!("GetClassNameW function failed: {}",
io::Error::last_os_error()));
}
let title = OsStr::new("Dummy")
.encode_wide()
.chain(Some(0).into_iter())
.collect::<Vec<_>>();
let win = CreateWindowExW(ex_style,
class_name.as_ptr(),
title.as_ptr(),
style,
CW_USEDEFAULT,
CW_USEDEFAULT,
rect.right - rect.left,
rect.bottom - rect.top,
ptr::null_mut(),
ptr::null_mut(),
GetModuleHandleW(ptr::null()),
ptr::null_mut());
if win.is_null() {
return Err(format!("CreateWindowEx function failed: {}", io::Error::last_os_error()));
}
let hdc = GetDC(win);
let ctx = WGLScopedContext::new(win, hdc, ptr::null_mut());
if hdc.is_null() {
return Err(format!("GetDC function failed: {}", io::Error::last_os_error()));
}
ctx
};
{
let id = choose_dummy_pixel_format(dummy_window.device_ctx)?;
set_pixel_format(dummy_window.device_ctx, id)?;
}
dummy_window.render_ctx = create_basic_context(dummy_window.device_ctx, ptr::null_mut())?.0;
if wgl::MakeCurrent(dummy_window.device_ctx as *const _, dummy_window.render_ctx as *const _) == 0 {
return Err("WGL::MakeCurrent failed before loading extra WGL functions".to_owned());
}
let result = wgl_ext::Wgl::load_with(|addr| {
let addr = CString::new(addr.as_bytes()).unwrap();
let addr = addr.as_ptr();
wgl::GetProcAddress(addr) as *const c_void
});
wgl::MakeCurrent(ptr::null_mut(), ptr::null_mut());
Ok(result)
}
fn choose_dummy_pixel_format(hdc: HDC) -> Result<c_int, &'static str> {
let descriptor = PIXELFORMATDESCRIPTOR {
nSize: mem::size_of::<PIXELFORMATDESCRIPTOR>() as u16,
nVersion: 1,
dwFlags: PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
iPixelType: PFD_TYPE_RGBA,
cColorBits: 24,
cRedBits: 0,
cRedShift: 0,
cGreenBits: 0,
cGreenShift: 0,
cBlueBits: 0,
cBlueShift: 0,
cAlphaBits: 8,
cAlphaShift: 0,
cAccumBits: 0,
cAccumRedBits: 0,
cAccumGreenBits: 0,
cAccumBlueBits: 0,
cAccumAlphaBits: 0,
cDepthBits: 24,
cStencilBits: 8,
cAuxBuffers: 0,
iLayerType: PFD_MAIN_PLANE,
bReserved: 0,
dwLayerMask: 0,
dwVisibleMask: 0,
dwDamageMask: 0,
};
let pf_id = unsafe { ChoosePixelFormat(hdc, &descriptor) };
if pf_id == 0 {
return Err("No available pixel format");
}
Ok(pf_id)
}