use gl;
use gl::types::*;
use std::cmp;
use std::ffi;
use std::mem;
use crate::errors::*;
#[derive(Debug, Copy, Clone)]
pub enum Profile {
Core,
Compatibility,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Version {
GL(u8, u8),
ES(u8, u8),
}
impl PartialOrd for Version {
#[inline]
fn partial_cmp(&self, other: &Version) -> Option<cmp::Ordering> {
let (es1, major1, minor1) = match *self {
Version::GL(major, minor) => (false, major, minor),
Version::ES(major, minor) => (true, major, minor),
};
let (es2, major2, minor2) = match *other {
Version::GL(major, minor) => (false, major, minor),
Version::ES(major, minor) => (true, major, minor),
};
if es1 != es2 {
None
} else {
match major1.cmp(&major2) {
cmp::Ordering::Equal => Some(minor1.cmp(&minor2)),
v => Some(v),
}
}
}
}
impl Version {
pub unsafe fn parse() -> Result<Version> {
let desc = gl::GetString(gl::VERSION);
let desc = String::from_utf8(ffi::CStr::from_ptr(desc as *const _).to_bytes().to_vec())
.map_err(|_| format_err!("[GL] String is unformaled."))?;
let (es, desc) = if desc.starts_with("OpenGL ES ") {
(true, &desc[10..])
} else if desc.starts_with("OpenGL ES-") {
(true, &desc[13..])
} else {
(false, &desc[..])
};
let desc = desc
.split(' ')
.next()
.ok_or_else(|| format_err!("[GL] String is unformaled."))?;
let mut iter = desc.split(move |c: char| c == '.');
let major = iter.next().unwrap();
let minor = iter.next().unwrap();
let major = major.parse().expect("failed to parse GL major version");
let minor = minor.parse().expect("failed to parse GL minor version");
if es {
Ok(Version::ES(major, minor))
} else {
Ok(Version::GL(major, minor))
}
}
}
macro_rules! extensions {
($($string:expr => $field:ident,)+) => {
#[derive(Debug, Clone, Copy)]
pub struct Extensions {
$(
pub $field: bool,
)+
}
impl Extensions {
pub unsafe fn parse(version: Version) -> Result<Extensions> {
let strings: Vec<String> = if version >= Version::GL(3, 0) || version >= Version::ES(3, 0) {
let mut num_extensions = 0;
gl::GetIntegerv(gl::NUM_EXTENSIONS, &mut num_extensions);
(0 .. num_extensions).map(|i| {
let ext = gl::GetStringi(gl::EXTENSIONS, i as gl::types::GLuint);
String::from_utf8(ffi::CStr::from_ptr(ext as *const _).to_bytes().to_vec()).unwrap()
}).collect()
} else {
let list = gl::GetString(gl::EXTENSIONS);
assert!(!list.is_null());
let list = String::from_utf8(ffi::CStr::from_ptr(list as *const _).to_bytes().to_vec())
.unwrap();
list.split(' ').map(|e| e.to_owned()).collect()
};
let mut extensions = Extensions {
$(
$field: false,
)+
};
for extension in strings {
match &extension[..] {
$(
$string => extensions.$field = true,
)+
_ => ()
}
}
Ok(extensions)
}
}
}
}
extensions! {
"GL_ARB_shader_objects" => gl_arb_shader_objects,
"GL_ARB_vertex_shader" => gl_arb_vertex_shader,
"GL_ARB_fragment_shader" => gl_arb_fragment_shader,
"GL_ARB_vertex_buffer_object" => gl_arb_vertex_buffer_object,
"GL_ARB_map_buffer_range" => gl_arb_map_buffer_range,
"GL_ARB_uniform_buffer_object" => gl_arb_uniform_buffer_object,
"GL_ARB_framebuffer_no_attachments" => gl_arb_framebuffer_no_attachments,
"GL_ARB_framebuffer_object" => gl_arb_framebuffer_object,
"GL_ARB_vertex_array_object" => gl_arb_vertex_array_object,
"GL_APPLE_vertex_array_object" => gl_apple_vertex_array_object,
"GL_EXT_framebuffer_object" => gl_ext_framebuffer_object,
"GL_EXT_framebuffer_blit" => gl_ext_framebuffer_blit,
"GL_NV_fbo_color_attachments" => gl_nv_fbo_color_attachments,
"GL_OES_vertex_array_object" => gl_oes_vertex_array_object,
"GL_IMG_texture_compression_pvrtc" => gl_img_texture_compression_pvrtc,
"GL_EXT_texture_compression_s3tc" => gl_ext_texture_compression_s3tc,
"GL_ARB_ES3_compatibility" => gl_arb_es3_compatibility,
"GL_OES_compressed_ETC2_RGB8_texture" => gl_oes_compressed_etc2_rgb8_texture,
"GL_OES_compressed_ETC2_RGBA8_texture" => gl_oes_compressed_etc2_rgba8_texture,
}
#[derive(Debug, Copy, Clone)]
pub enum TextureCompression {
ETC2,
PVRTC,
S3TC,
}
#[derive(Debug)]
pub struct Capabilities {
pub version: Version,
pub vendor: String,
pub extensions: Extensions,
pub renderer: String,
pub profile: Option<Profile>,
pub debug: bool,
pub forward_compatible: bool,
pub max_viewport_dims: (u32, u32),
pub max_combined_texture_image_units: u8,
pub max_indexed_uniform_buffer: u32,
pub max_color_attachments: u32,
}
impl Capabilities {
pub unsafe fn parse() -> Result<Capabilities> {
let version = Version::parse()?;
let extensions = Extensions::parse(version)?;
let (debug, forward_compatible) = if version >= Version::GL(3, 0) {
let mut val = mem::uninitialized();
gl::GetIntegerv(gl::CONTEXT_FLAGS, &mut val);
let val = val as gl::types::GLenum;
(
(val & gl::CONTEXT_FLAG_DEBUG_BIT) != 0,
(val & gl::CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT) != 0,
)
} else {
(false, false)
};
Ok(Capabilities {
version,
extensions,
vendor: Capabilities::parse_str(gl::VENDOR)?,
renderer: Capabilities::parse_str(gl::RENDERER)?,
profile: Capabilities::parse_profile(version),
debug,
forward_compatible,
max_viewport_dims: Capabilities::parse_viewport_dims(),
max_combined_texture_image_units: Capabilities::parse_texture_image_units(),
max_indexed_uniform_buffer: Capabilities::parse_uniform_buffers(version, &extensions),
max_color_attachments: Capabilities::parse_color_attachments(version, &extensions),
})
}
pub fn has_compression(&self, compression: TextureCompression) -> bool {
match compression {
TextureCompression::ETC2 => {
self.version >= Version::ES(3, 0)
|| self.extensions.gl_arb_es3_compatibility
|| (self.extensions.gl_oes_compressed_etc2_rgb8_texture
&& self.extensions.gl_oes_compressed_etc2_rgba8_texture)
}
TextureCompression::PVRTC => self.extensions.gl_img_texture_compression_pvrtc,
TextureCompression::S3TC => self.extensions.gl_ext_texture_compression_s3tc,
}
}
#[inline]
unsafe fn parse_str(id: GLenum) -> Result<String> {
let s = gl::GetString(gl::RENDERER);
if s.is_null() {
bail!("[GL] String of {} is null.", id);
}
String::from_utf8(ffi::CStr::from_ptr(s as *const _).to_bytes().to_vec())
.map_err(|_| format_err!("[GL] String of {} is unformaled.", id))
}
#[inline]
unsafe fn parse_viewport_dims() -> (u32, u32) {
let mut val: [gl::types::GLint; 2] = [0, 0];
gl::GetIntegerv(gl::MAX_VIEWPORT_DIMS, val.as_mut_ptr());
(val[0] as u32, val[1] as u32)
}
#[inline]
unsafe fn parse_profile(version: Version) -> Option<Profile> {
if version >= Version::GL(3, 2) {
let mut val = mem::uninitialized();
gl::GetIntegerv(gl::CONTEXT_PROFILE_MASK, &mut val);
let val = val as gl::types::GLenum;
if (val & gl::CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0 {
Some(Profile::Compatibility)
} else if (val & gl::CONTEXT_CORE_PROFILE_BIT) != 0 {
Some(Profile::Core)
} else {
None
}
} else {
None
}
}
#[inline]
unsafe fn parse_texture_image_units() -> u8 {
let mut val = 2;
gl::GetIntegerv(gl::MAX_COMBINED_TEXTURE_IMAGE_UNITS, &mut val);
val as u8
}
#[inline]
unsafe fn parse_uniform_buffers(version: Version, exts: &Extensions) -> u32 {
if version >= Version::GL(3, 1) || exts.gl_arb_uniform_buffer_object {
let mut val = mem::uninitialized();
gl::GetIntegerv(gl::MAX_UNIFORM_BUFFER_BINDINGS, &mut val);
val as u32
} else {
0
}
}
#[inline]
unsafe fn parse_color_attachments(version: Version, exts: &Extensions) -> u32 {
if version >= Version::GL(3, 0)
|| version >= Version::ES(3, 0)
|| exts.gl_arb_framebuffer_object
|| exts.gl_ext_framebuffer_object
|| exts.gl_nv_fbo_color_attachments
{
let mut val = 4;
gl::GetIntegerv(gl::MAX_COLOR_ATTACHMENTS, &mut val);
val as u32
} else if version >= Version::ES(2, 0) {
1
} else {
0
}
}
}