// Copyright 2014 The Gfx-rs Developers. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::collections::HashSet; use std::{ffi, fmt, mem, str}; use gl; use gfx_core::Capabilities; /// A version number for a specific component of an OpenGL implementation #[derive(Copy, Clone, Eq, PartialEq)] pub struct Version { pub is_embedded: bool, pub major: u32, pub minor: u32, pub revision: Option, pub vendor_info: &'static str, } impl Version { /// Create a new OpenGL version number pub fn new( major: u32, minor: u32, revision: Option, vendor_info: &'static str, ) -> Self { Version { is_embedded: false, major, minor, revision, vendor_info, } } /// Create a new OpenGL ES version number 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, } } /// According to the OpenGL specification, the version information is /// expected to follow the following syntax: /// /// ~~~bnf /// ::= /// ::= /// ::= /// ::= /// ::= "." ["." ] /// ::= [" " ] /// ~~~ /// /// Note that this function is intentionally lenient in regards to parsing, /// and will try to recover at least the first two version numbers without /// resulting in an `Err`. pub fn parse(mut src: &'static str) -> Result { info!("parsing version '{}'", src); // cut out the extra (xxx) stuff 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, ""), }; // TODO: make this even more lenient so that we can also accept // ` "." []` 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 = ""; /// Get a statically allocated string from the implementation using /// `glGetString`. Fails if it `GLenum` cannot be handled by the /// implementation's `gl.GetString` function. 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() { // This should be safe to mark as statically allocated because // GlGetString only returns static strings. 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()) } /// A unique platform identifier that does not change between releases #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub struct PlatformName { /// The company responsible for the OpenGL implementation pub vendor: &'static str, /// The name of the renderer 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), } } } /// Private capabilities that don't need to be exposed. #[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, } /// OpenGL implementation information #[derive(Debug)] pub struct Info { /// The platform identifier pub platform_name: PlatformName, /// The OpenGL API vesion number pub version: Version, /// The GLSL vesion number pub shading_language: Version, /// The extensions supported by the implementation 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 { // Fallback 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) } /// Returns `true` if the implementation supports the extension 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), } }) } } /// Load the information pertaining to the driver and the corresponding device /// capabilities. pub fn get_all(gl: &gl::Gl) -> (Info, Capabilities, PrivateCaps) { use self::Requirement::*; let info = Info::get(gl); // These extensions should be covered by the GL ES version checks, // but Emscripten WebGL 2.0 support seems to be incomplete. 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)]), //TODO: extension separate_blending_slots_supported: info.is_supported(&[Core(4,0), Es (3,2), // see `glDisablei` 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), //TODO? 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, ""))); } }