use gl;
use libc;
use std::env;
use std::mem;
use std::ptr;
use std::borrow::Cow;
use std::collections::HashMap;
use std::cell::{Cell, RefCell, RefMut};
use std::marker::PhantomData;
use std::ffi::CStr;
use std::rc::Rc;
use GliumCreationError;
use SwapBuffersError;
use ContextExt;
use backend::Backend;
use version;
use version::Api;
use version::Version;
use fbo;
use ops;
use sampler_object;
use texture;
use uniforms;
use vertex_array_object;
pub use self::capabilities::Capabilities;
pub use self::extensions::ExtensionsList;
pub use self::state::GlState;
mod capabilities;
mod extensions;
mod state;
pub struct Context {
gl: gl::Gl,
state: RefCell<GlState>,
version: Version,
extensions: ExtensionsList,
capabilities: Capabilities,
backend: RefCell<Box<Backend>>,
check_current_context: bool,
report_debug_output_errors: Cell<bool>,
framebuffer_objects: Option<fbo::FramebuffersContainer>,
vertex_array_objects: vertex_array_object::VertexAttributesSystem,
samplers: RefCell<HashMap<uniforms::SamplerBehavior, sampler_object::SamplerObject>>,
}
pub struct CommandContext<'a> {
pub gl: &'a gl::Gl,
pub state: RefMut<'a, GlState>,
pub version: &'a Version,
pub extensions: &'a ExtensionsList,
pub capabilities: &'a Capabilities,
pub report_debug_output_errors: &'a Cell<bool>,
pub vertex_array_objects: &'a vertex_array_object::VertexAttributesSystem,
pub framebuffer_objects: &'a fbo::FramebuffersContainer,
pub samplers: RefMut<'a, HashMap<uniforms::SamplerBehavior, sampler_object::SamplerObject>>,
marker: PhantomData<*mut u8>,
}
impl Context {
pub unsafe fn new<B, E>(backend: B, check_current_context: bool)
-> Result<Rc<Context>, GliumCreationError<E>>
where B: Backend + 'static
{
backend.make_current();
let gl = gl::Gl::load_with(|symbol| backend.get_proc_address(symbol));
let gl_state: RefCell<GlState> = RefCell::new(Default::default());
let version = version::get_gl_version(&gl);
let extensions = extensions::get_extensions(&gl, &version);
let capabilities = capabilities::get_capabilities(&gl, &version, &extensions);
let report_debug_output_errors = Cell::new(true);
let vertex_array_objects = vertex_array_object::VertexAttributesSystem::new();
let framebuffer_objects = fbo::FramebuffersContainer::new();
let samplers = RefCell::new(HashMap::with_capacity(16));
{
let mut ctxt = CommandContext {
gl: &gl,
state: gl_state.borrow_mut(),
version: &version,
extensions: &extensions,
capabilities: &capabilities,
report_debug_output_errors: &report_debug_output_errors,
vertex_array_objects: &vertex_array_objects,
framebuffer_objects: &framebuffer_objects,
samplers: samplers.borrow_mut(),
marker: PhantomData,
};
try!(check_gl_compatibility(&mut ctxt));
}
let context = Rc::new(Context {
gl: gl,
state: gl_state,
version: version,
extensions: extensions,
capabilities: capabilities,
report_debug_output_errors: report_debug_output_errors,
backend: RefCell::new(Box::new(backend)),
check_current_context: check_current_context,
framebuffer_objects: Some(framebuffer_objects),
vertex_array_objects: vertex_array_objects,
samplers: samplers,
});
init_debug_callback(&context);
{
let mut ctxt = context.make_current();
if ::get_gl_error(&mut ctxt).is_some() {
println!("glium has triggered an OpenGL error during initialization. Please report \
this error: https://github.com/tomaka/glium/issues");
}
}
Ok(context)
}
pub fn get_framebuffer_dimensions(&self) -> (u32, u32) {
self.backend.borrow().get_framebuffer_dimensions()
}
pub unsafe fn rebuild<B, E>(&self, new_backend: B)
-> Result<(), GliumCreationError<E>>
where B: Backend + 'static
{
{
let mut ctxt = self.make_current();
fbo::FramebuffersContainer::purge_all(&mut ctxt);
vertex_array_object::VertexAttributesSystem::purge_all(&mut ctxt);
}
new_backend.make_current();
*self.state.borrow_mut() = Default::default();
*self.backend.borrow_mut() = Box::new(new_backend);
Ok(())
}
pub fn swap_buffers(&self) -> Result<(), SwapBuffersError> {
let mut state = self.state.borrow_mut();
if state.lost_context {
return Err(SwapBuffersError::ContextLost);
}
let backend = self.backend.borrow();
if self.check_current_context {
if !backend.is_current() {
unsafe { backend.make_current() };
}
}
let err = backend.swap_buffers();
if let Err(SwapBuffersError::ContextLost) = err {
state.lost_context = true;
}
err
}
pub fn get_version(&self) -> &Version {
&self.version
}
pub fn get_supported_glsl_version(&self) -> Version {
version::get_supported_glsl_version(self.get_version())
}
pub fn is_glsl_version_supported(&self, version: &Version) -> bool {
self.capabilities().supported_glsl_versions.iter().find(|&v| v == version).is_some()
}
pub fn is_robust(&self) -> bool {
self.capabilities().robustness
}
pub fn is_context_loss_possible(&self) -> bool {
self.capabilities().can_lose_context
}
pub fn is_context_lost(&self) -> bool {
if self.state.borrow().lost_context {
return true;
}
let mut ctxt = self.make_current();
let lost = if ctxt.version >= &Version(Api::Gl, 4, 5) || ctxt.extensions.gl_khr_robustness {
unsafe { ctxt.gl.GetGraphicsResetStatus() != gl::NO_ERROR }
} else if ctxt.extensions.gl_ext_robustness {
unsafe { ctxt.gl.GetGraphicsResetStatusEXT() != gl::NO_ERROR }
} else if ctxt.extensions.gl_arb_robustness {
unsafe { ctxt.gl.GetGraphicsResetStatusARB() != gl::NO_ERROR }
} else {
false
};
if lost { ctxt.state.lost_context = true; }
lost
}
pub fn get_max_anisotropy_support(&self) -> Option<u16> {
self.capabilities().max_texture_max_anisotropy.map(|v| v as u16)
}
pub fn get_max_viewport_dimensions(&self) -> (u32, u32) {
let d = self.capabilities().max_viewport_dims;
(d.0 as u32, d.1 as u32)
}
pub fn release_shader_compiler(&self) {
unsafe {
let ctxt = self.make_current();
if ctxt.version >= &Version(Api::GlEs, 2, 0) ||
ctxt.version >= &Version(Api::Gl, 4, 1)
{
if !ctxt.capabilities.supported_glsl_versions.is_empty() {
ctxt.gl.ReleaseShaderCompiler();
}
}
}
}
pub fn get_free_video_memory(&self) -> Option<usize> {
unsafe {
let ctxt = self.make_current();
let mut value: [gl::types::GLint; 4] = mem::uninitialized();
if ctxt.extensions.gl_nvx_gpu_memory_info {
ctxt.gl.GetIntegerv(gl::GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX,
&mut value[0]);
Some(value[0] as usize * 1024)
} else if ctxt.extensions.gl_ati_meminfo {
ctxt.gl.GetIntegerv(gl::TEXTURE_FREE_MEMORY_ATI, &mut value[0]);
Some(value[0] as usize * 1024)
} else {
return None;
}
}
}
pub fn read_front_buffer<T>(&self) -> T
where T: texture::Texture2dDataSink<(u8, u8, u8, u8)>
{
let mut ctxt = self.make_current();
let dimensions = self.get_framebuffer_dimensions();
let rect = ::Rect { left: 0, bottom: 0, width: dimensions.0, height: dimensions.1 };
let mut data = Vec::with_capacity(0);
ops::read(&mut ctxt, ops::Source::DefaultFramebuffer(gl::FRONT_LEFT), &rect, &mut data);
T::from_raw(Cow::Owned(data), dimensions.0, dimensions.1)
}
pub unsafe fn exec_in_context<'a, T, F>(&self, action: F) -> T
where T: Send + 'static,
F: FnOnce() -> T + 'a
{
let _ctxt = self.make_current();
action()
}
pub fn assert_no_error(&self, user_msg: Option<&str>) {
let mut ctxt = self.make_current();
match (::get_gl_error(&mut ctxt), user_msg) {
(Some(msg), None) => panic!("{}", msg),
(Some(msg), Some(user_msg)) => panic!("{} : {}", user_msg, msg),
(None, _) => ()
};
}
pub fn synchronize(&self) {
let ctxt = self.make_current();
unsafe { ctxt.gl.Finish(); }
}
pub fn insert_debug_marker(&self, marker: &str) -> Result<(), ()> {
let ctxt = self.make_current();
if ctxt.extensions.gl_gremedy_string_marker {
let marker = marker.as_bytes();
unsafe { ctxt.gl.StringMarkerGREMEDY(marker.len() as gl::types::GLsizei,
marker.as_ptr() as *const _) };
Ok(())
} else if ctxt.extensions.gl_ext_debug_marker {
let marker = marker.as_bytes();
unsafe { ctxt.gl.InsertEventMarkerEXT(marker.len() as gl::types::GLsizei,
marker.as_ptr() as *const _) };
Ok(())
} else {
Err(())
}
}
pub fn debug_insert_debug_marker(&self, marker: &str) -> Result<(), ()> {
if cfg!(debug_assertions) {
self.insert_debug_marker(marker)
} else {
Ok(())
}
}
}
impl ContextExt for Context {
fn set_report_debug_output_errors(&self, value: bool) {
self.report_debug_output_errors.set(value);
}
fn make_current(&self) -> CommandContext {
if self.check_current_context {
let backend = self.backend.borrow();
if !backend.is_current() {
unsafe { backend.make_current() };
}
}
CommandContext {
gl: &self.gl,
state: self.state.borrow_mut(),
version: &self.version,
extensions: &self.extensions,
capabilities: &self.capabilities,
report_debug_output_errors: &self.report_debug_output_errors,
vertex_array_objects: &self.vertex_array_objects,
framebuffer_objects: self.framebuffer_objects.as_ref().unwrap(),
samplers: self.samplers.borrow_mut(),
marker: PhantomData,
}
}
fn capabilities(&self) -> &Capabilities {
&self.capabilities
}
fn get_extensions(&self) -> &ExtensionsList {
&self.extensions
}
}
impl Drop for Context {
fn drop(&mut self) {
unsafe {
if self.check_current_context {
let backend = self.backend.borrow();
if !backend.is_current() {
backend.make_current();
}
}
let mut ctxt = CommandContext {
gl: &self.gl,
state: self.state.borrow_mut(),
version: &self.version,
extensions: &self.extensions,
capabilities: &self.capabilities,
report_debug_output_errors: &self.report_debug_output_errors,
vertex_array_objects: &self.vertex_array_objects,
framebuffer_objects: self.framebuffer_objects.as_ref().unwrap(),
samplers: self.samplers.borrow_mut(),
marker: PhantomData,
};
fbo::FramebuffersContainer::cleanup(&mut ctxt);
vertex_array_object::VertexAttributesSystem::cleanup(&mut ctxt);
for (_, s) in mem::replace(&mut *ctxt.samplers, HashMap::with_capacity(0)) {
s.destroy(&mut ctxt);
}
if ctxt.state.enabled_debug_output != Some(false) {
if ctxt.version >= &Version(Api::Gl, 4,5) || ctxt.extensions.gl_khr_debug {
ctxt.gl.Disable(gl::DEBUG_OUTPUT);
} else if ctxt.extensions.gl_arb_debug_output {
ctxt.gl.DebugMessageCallbackARB(mem::transmute(0usize),
ptr::null());
}
ctxt.state.enabled_debug_output = Some(false);
ctxt.gl.Finish();
}
}
}
}
fn check_gl_compatibility<T>(ctxt: &mut CommandContext) -> Result<(), GliumCreationError<T>> {
let mut result = Vec::with_capacity(0);
if !(ctxt.version >= &Version(Api::Gl, 1, 5)) &&
!(ctxt.version >= &Version(Api::GlEs, 2, 0)) &&
(!ctxt.extensions.gl_arb_vertex_buffer_object || !ctxt.extensions.gl_arb_map_buffer_range)
{
result.push("OpenGL implementation doesn't support buffer objects");
}
if !(ctxt.version >= &Version(Api::Gl, 2, 0)) &&
!(ctxt.version >= &Version(Api::GlEs, 2, 0)) &&
(!ctxt.extensions.gl_arb_shader_objects ||
!ctxt.extensions.gl_arb_vertex_shader || !ctxt.extensions.gl_arb_fragment_shader)
{
result.push("OpenGL implementation doesn't support vertex/fragment shaders");
}
if !ctxt.extensions.gl_ext_framebuffer_object && !(ctxt.version >= &Version(Api::Gl, 3, 0)) &&
!(ctxt.version >= &Version(Api::GlEs, 2, 0))
{
result.push("OpenGL implementation doesn't support framebuffers");
}
if !ctxt.extensions.gl_ext_framebuffer_blit && !(ctxt.version >= &Version(Api::Gl, 3, 0)) &&
!(ctxt.version >= &Version(Api::GlEs, 2, 0))
{
result.push("OpenGL implementation doesn't support blitting framebuffers");
}
if cfg!(feature = "gl_uniform_blocks") && !(ctxt.version >= &Version(Api::Gl, 3, 1)) &&
!ctxt.extensions.gl_arb_uniform_buffer_object
{
result.push("OpenGL implementation doesn't support uniform blocks");
}
if cfg!(feature = "gl_sync") && !(ctxt.version >= &Version(Api::Gl, 3, 2)) &&
!(ctxt.version >= &Version(Api::GlEs, 3, 0)) && !ctxt.extensions.gl_arb_sync
{
result.push("OpenGL implementation doesn't support synchronization objects");
}
if cfg!(feature = "gl_program_binary") && !(ctxt.version >= &Version(Api::Gl, 4, 1)) &&
!ctxt.extensions.gl_arb_get_programy_binary
{
result.push("OpenGL implementation doesn't support program binary");
}
if cfg!(feature = "gl_tessellation") && !(ctxt.version >= &Version(Api::Gl, 4, 0)) &&
!ctxt.extensions.gl_arb_tessellation_shader
{
result.push("OpenGL implementation doesn't support tessellation");
}
if cfg!(feature = "gl_instancing") && !(ctxt.version >= &Version(Api::Gl, 3, 3)) &&
!ctxt.extensions.gl_arb_instanced_arrays
{
result.push("OpenGL implementation doesn't support instancing");
}
if cfg!(feature = "gl_integral_textures") && !(ctxt.version >= &Version(Api::Gl, 3, 0)) &&
!ctxt.extensions.gl_ext_texture_integer
{
result.push("OpenGL implementation doesn't support integral textures");
}
if cfg!(feature = "gl_depth_textures") && !(ctxt.version >= &Version(Api::Gl, 3, 0)) &&
(!ctxt.extensions.gl_arb_depth_texture || !ctxt.extensions.gl_ext_packed_depth_stencil) &&
(!ctxt.extensions.gl_oes_depth_texture || !ctxt.extensions.gl_oes_packed_depth_stencil)
{
result.push("OpenGL implementation doesn't support depth or depth-stencil textures");
}
if cfg!(feature = "gl_stencil_textures") && !(ctxt.version >= &Version(Api::Gl, 3, 0))
{
result.push("OpenGL implementation doesn't support stencil textures");
}
if cfg!(feature = "gl_texture_multisample") && !(ctxt.version >= &Version(Api::Gl, 3, 2))
{
result.push("OpenGL implementation doesn't support multisample textures");
}
if cfg!(feature = "gl_texture_multisample_array") &&
!(ctxt.version >= &Version(Api::Gl, 3, 2))
{
result.push("OpenGL implementation doesn't support arrays of multisample textures");
}
if result.len() == 0 {
Ok(())
} else {
Err(GliumCreationError::IncompatibleOpenGl(result.connect("\n")))
}
}
fn init_debug_callback(context: &Rc<Context>) {
if !cfg!(debug_assertions) {
return;
}
if env::var("GLIUM_DISABLE_DEBUG_OUTPUT").is_ok() {
return;
}
extern "system" fn callback_wrapper(source: gl::types::GLenum, ty: gl::types::GLenum,
id: gl::types::GLuint, severity: gl::types::GLenum,
_length: gl::types::GLsizei,
message: *const gl::types::GLchar,
user_param: *mut libc::c_void)
{
let user_param = user_param as *const Context;
let user_param: &Context = unsafe { mem::transmute(user_param) };
if (severity == gl::DEBUG_SEVERITY_HIGH || severity == gl::DEBUG_SEVERITY_MEDIUM) &&
(ty == gl::DEBUG_TYPE_ERROR || ty == gl::DEBUG_TYPE_UNDEFINED_BEHAVIOR ||
ty == gl::DEBUG_TYPE_PORTABILITY || ty == gl::DEBUG_TYPE_DEPRECATED_BEHAVIOR)
{
if user_param.report_debug_output_errors.get() {
let message = unsafe {
String::from_utf8(CStr::from_ptr(message).to_bytes().to_vec()).unwrap()
};
panic!("Debug message with high or medium severity: `{}`.\n\
Please report this error: https://github.com/tomaka/glium/issues",
message);
}
}
}
struct ContextRawPtr(*const Context);
unsafe impl Send for ContextRawPtr {}
let context_raw_ptr = ContextRawPtr(&**context);
unsafe {
let mut ctxt = context.make_current();
if ctxt.version >= &Version(Api::Gl, 4,5) || ctxt.extensions.gl_khr_debug ||
ctxt.extensions.gl_arb_debug_output
{
if ctxt.state.enabled_debug_output_synchronous != true {
ctxt.gl.Enable(gl::DEBUG_OUTPUT_SYNCHRONOUS);
ctxt.state.enabled_debug_output_synchronous = true;
}
if ctxt.version >= &Version(Api::Gl, 4, 5) ||
(ctxt.version >= &Version(Api::Gl, 1, 0) && ctxt.extensions.gl_khr_debug)
{
ctxt.gl.DebugMessageCallback(callback_wrapper, context_raw_ptr.0
as *const libc::c_void);
ctxt.gl.DebugMessageControl(gl::DONT_CARE, gl::DONT_CARE, gl::DONT_CARE, 0,
ptr::null(), gl::TRUE);
if ctxt.state.enabled_debug_output != Some(true) {
ctxt.gl.Enable(gl::DEBUG_OUTPUT);
ctxt.state.enabled_debug_output = Some(true);
}
} else if ctxt.version >= &Version(Api::GlEs, 2, 0) &&
ctxt.extensions.gl_khr_debug
{
ctxt.gl.DebugMessageCallbackKHR(callback_wrapper, context_raw_ptr.0
as *const libc::c_void);
ctxt.gl.DebugMessageControlKHR(gl::DONT_CARE, gl::DONT_CARE, gl::DONT_CARE, 0,
ptr::null(), gl::TRUE);
if ctxt.state.enabled_debug_output != Some(true) {
ctxt.gl.Enable(gl::DEBUG_OUTPUT);
ctxt.state.enabled_debug_output = Some(true);
}
} else {
ctxt.gl.DebugMessageCallbackARB(callback_wrapper, context_raw_ptr.0
as *const libc::c_void);
ctxt.gl.DebugMessageControlARB(gl::DONT_CARE, gl::DONT_CARE, gl::DONT_CARE,
0, ptr::null(), gl::TRUE);
ctxt.state.enabled_debug_output = Some(true);
}
}
}
}