use std::collections::HashSet;
use std::{ffi, fmt, mem, str};
use gl;
use gfx_core::Capabilities;
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct Version {
pub is_embedded: bool,
pub major: u32,
pub minor: u32,
pub revision: Option<u32>,
pub vendor_info: &'static str,
}
impl Version {
pub fn new(
major: u32, minor: u32,
revision: Option<u32>,
vendor_info: &'static str,
) -> Self {
Version {
is_embedded: false,
major,
minor,
revision,
vendor_info,
}
}
pub fn new_embedded(
major: u32, minor: u32, vendor_info: &'static str
) -> Version {
Version {
is_embedded: true,
major: major,
minor: minor,
revision: None,
vendor_info: vendor_info,
}
}
pub fn parse(mut src: &'static str) -> Result<Version, &'static str> {
info!("parsing version '{}'", src);
if let Some(pos) = src.find(" (") {
src = &src[.. pos];
}
let es_sig = " ES ";
let is_embedded = match src.rfind(es_sig) {
Some(pos) => {
src = &src[pos + es_sig.len() ..];
true
},
None => false,
};
let (version, vendor_info) = match src.find(' ') {
Some(i) => (&src[..i], &src[i+1..]),
None => (src, ""),
};
let mut it = version.split('.');
let major = it.next().and_then(|s| s.parse().ok());
let minor = it.next().and_then(|s| s.parse().ok());
let revision = it.next().and_then(|s| s.parse().ok());
match (major, minor, revision) {
(Some(major), Some(minor), revision) => Ok(Version {
is_embedded,
major,
minor,
revision,
vendor_info,
}),
(_, _, _) => Err(src),
}
}
}
impl fmt::Debug for Version {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match (self.major, self.minor, self.revision, self.vendor_info) {
(major, minor, Some(revision), "") =>
write!(f, "{}.{}.{}", major, minor, revision),
(major, minor, None, "") =>
write!(f, "{}.{}", major, minor),
(major, minor, Some(revision), vendor_info) =>
write!(f, "{}.{}.{}, {}", major, minor, revision, vendor_info),
(major, minor, None, vendor_info) =>
write!(f, "{}.{}, {}", major, minor, vendor_info),
}
}
}
const EMPTY_STRING: &'static str = "";
fn get_string(gl: &gl::Gl, name: gl::types::GLenum) -> &'static str {
let ptr = unsafe { gl.GetString(name) } as *const i8;
if !ptr.is_null() {
unsafe { c_str_as_static_str(ptr) }
} else {
error!("Invalid GLenum passed to `get_string`: {:x}", name);
EMPTY_STRING
}
}
fn get_usize(gl: &gl::Gl, name: gl::types::GLenum) -> usize {
let mut value = 0 as gl::types::GLint;
unsafe { gl.GetIntegerv(name, &mut value) };
value as usize
}
unsafe fn c_str_as_static_str(c_str: *const i8) -> &'static str {
mem::transmute(str::from_utf8(ffi::CStr::from_ptr(c_str as *const _).to_bytes()).unwrap())
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct PlatformName {
pub vendor: &'static str,
pub renderer: &'static str,
}
impl PlatformName {
fn get(gl: &gl::Gl) -> Self {
PlatformName {
vendor: get_string(gl, gl::VENDOR),
renderer: get_string(gl, gl::RENDERER),
}
}
}
#[derive(Debug)]
pub struct PrivateCaps {
pub array_buffer_supported: bool,
pub frame_buffer_supported: bool,
pub immutable_storage_supported: bool,
pub sampler_objects_supported: bool,
pub program_interface_supported: bool,
pub buffer_storage_supported: bool,
pub clear_buffer_supported: bool,
pub frag_data_location_supported: bool,
pub sampler_lod_bias_supported: bool,
pub texture_border_clamp_supported: bool,
pub texture_view_supported: bool,
}
#[derive(Debug)]
pub struct Info {
pub platform_name: PlatformName,
pub version: Version,
pub shading_language: Version,
pub extensions: HashSet<&'static str>,
}
#[derive(Copy, Clone)]
enum Requirement {
Core(u32,u32),
Es(u32, u32),
Ext(&'static str),
}
impl Info {
fn get(gl: &gl::Gl) -> Info {
let platform_name = PlatformName::get(gl);
let version = Version::parse(get_string(gl, gl::VERSION)).unwrap();
let shading_language = Version::parse(get_string(gl, gl::SHADING_LANGUAGE_VERSION)).unwrap();
let extensions = if !version.is_embedded && version.major >= 3 {
let num_exts = get_usize(gl, gl::NUM_EXTENSIONS) as gl::types::GLuint;
(0..num_exts)
.map(|i| unsafe {
c_str_as_static_str(gl.GetStringi(gl::EXTENSIONS, i) as *const i8)
})
.collect()
} else {
get_string(gl, gl::EXTENSIONS).split(' ').collect()
};
Info {
platform_name,
version,
shading_language,
extensions,
}
}
pub fn is_version_supported(&self, major: u32, minor: u32) -> bool {
!self.version.is_embedded &&
(self.version.major, self.version.minor) >= (major, minor)
}
pub fn is_embedded_version_supported(&self, major: u32, minor: u32) -> bool {
self.version.is_embedded &&
(self.version.major, self.version.minor) >= (major, minor)
}
pub fn is_extension_supported(&self, s: &'static str) -> bool {
self.extensions.contains(&s)
}
pub fn is_version_or_extension_supported(&self, major: u32, minor: u32, ext: &'static str) -> bool {
self.is_version_supported(major, minor) || self.is_extension_supported(ext)
}
pub fn is_any_extension_supported(&self, exts: &[&'static str]) -> bool {
exts.iter().any(|e| self.extensions.contains(e))
}
fn is_supported(&self, requirements: &[Requirement]) -> bool {
use self::Requirement::*;
requirements.iter().any(|r| {
match *r {
Core(major, minor) => self.is_version_supported(major, minor),
Es(major, minor) => self.is_embedded_version_supported(major, minor),
Ext(extension) => self.is_extension_supported(extension),
}
})
}
}
pub fn get_all(gl: &gl::Gl) -> (Info, Capabilities, PrivateCaps) {
use self::Requirement::*;
let info = Info::get(gl);
let is_emscripten = cfg!(target_os = "emscripten");
let tessellation_supported = info.is_supported(&[Core(4,0),
Ext("GL_ARB_tessellation_shader")]);
let caps = Capabilities {
max_vertex_count: get_usize(gl, gl::MAX_ELEMENTS_VERTICES),
max_index_count: get_usize(gl, gl::MAX_ELEMENTS_INDICES),
max_texture_size: get_usize(gl, gl::MAX_TEXTURE_SIZE),
max_patch_size: if tessellation_supported { get_usize(gl, gl::MAX_PATCH_VERTICES) } else {0},
instance_base_supported: info.is_supported(&[Core(4,2),
Ext ("GL_ARB_base_instance")]),
instance_call_supported: info.is_supported(&[Core(3,1),
Es (3,0),
Ext ("GL_ARB_draw_instanced")]),
instance_rate_supported: info.is_supported(&[Core(3,3),
Es (3,0),
Ext ("GL_ARB_instanced_arrays")]),
vertex_base_supported: info.is_supported(&[Core(3,2),
Es (3,2),
Ext ("GL_ARB_draw_elements_base_vertex")]),
srgb_color_supported: info.is_supported(&[Core(3,2),
Ext ("GL_ARB_framebuffer_sRGB")]),
constant_buffer_supported: info.is_supported(&[Core(3,1),
Es (3,0),
Ext ("GL_ARB_uniform_buffer_object")]),
unordered_access_view_supported: info.is_supported(&[Core(4,0)]),
separate_blending_slots_supported: info.is_supported(&[Core(4,0),
Es (3,2),
Ext ("GL_ARB_draw_buffers_blend")]),
copy_buffer_supported: info.is_supported(&[Core(3,1),
Es (3,0),
Ext ("GL_ARB_copy_buffer"),
Ext ("GL_NV_copy_buffer")]),
};
let private = PrivateCaps {
array_buffer_supported: info.is_supported(&[Core(3,0),
Es (3,0),
Ext ("GL_ARB_vertex_array_object")]),
frame_buffer_supported: info.is_supported(&[Core(3,0),
Es (2,0),
Ext ("GL_ARB_framebuffer_object")]),
immutable_storage_supported: info.is_supported(&[Core(3,2),
Ext ("GL_ARB_texture_storage")]),
sampler_objects_supported: info.is_supported(&[Core(3,3),
Es (3,0),
Ext ("GL_ARB_sampler_objects")])
&& !is_emscripten,
program_interface_supported: info.is_supported(&[Core(4,3),
Ext ("GL_ARB_program_interface_query")]),
buffer_storage_supported: info.is_supported(&[Core(4,4),
Ext ("GL_ARB_buffer_storage")]),
clear_buffer_supported: info.is_supported(&[Core(3,0),
Es (3,0)])
&& !is_emscripten,
frag_data_location_supported: !info.version.is_embedded,
sampler_lod_bias_supported: !info.version.is_embedded,
texture_border_clamp_supported: info.is_supported(&[Core(2,0),
Es (3,2),
Ext ("GL_EXT_texture_border_clamp")]),
texture_view_supported: info.is_supported(&[Core(4,3),
Ext ("GL_EXT_texture_view")]),
};
(info, caps, private)
}
#[cfg(test)]
mod tests {
use super::Version;
#[test]
fn test_version_parse() {
assert_eq!(Version::parse("1"), Err("1"));
assert_eq!(Version::parse("1."), Err("1."));
assert_eq!(Version::parse("1 h3l1o. W0rld"), Err("1 h3l1o. W0rld"));
assert_eq!(Version::parse("1. h3l1o. W0rld"), Err("1. h3l1o. W0rld"));
assert_eq!(Version::parse("1.2.3"), Ok(Version::new(1, 2, Some(3), "")));
assert_eq!(Version::parse("1.2"), Ok(Version::new(1, 2, None, "")));
assert_eq!(Version::parse("1.2 h3l1o. W0rld"), Ok(Version::new(1, 2, None, "h3l1o. W0rld")));
assert_eq!(Version::parse("1.2.h3l1o. W0rld"), Ok(Version::new(1, 2, None, "W0rld")));
assert_eq!(Version::parse("1.2. h3l1o. W0rld"), Ok(Version::new(1, 2, None, "h3l1o. W0rld")));
assert_eq!(Version::parse("1.2.3.h3l1o. W0rld"), Ok(Version::new(1, 2, Some(3), "W0rld")));
assert_eq!(Version::parse("1.2.3 h3l1o. W0rld"), Ok(Version::new(1, 2, Some(3), "h3l1o. W0rld")));
assert_eq!(Version::parse("OpenGL ES 3.1"), Ok(Version::new_embedded(3, 1, "")));
assert_eq!(Version::parse("OpenGL ES 2.0 Google Nexus"), Ok(Version::new_embedded(2, 0, "Google Nexus")));
assert_eq!(Version::parse("GLSL ES 1.1"), Ok(Version::new_embedded(1, 1, "")));
}
}