Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

use core::fmt::Debug;
use core::hash::Hash;
use std::collections::HashSet;

mod version;

#[cfg(not(target_arch = "wasm32"))]
mod native;
Expand Down Expand Up @@ -90,6 +93,8 @@ pub trait HasContext {
type TransformFeedback: Copy + Clone + Debug + Eq + Hash + Ord + PartialEq + PartialOrd;
type UniformLocation: Clone + Debug;

fn get_supported_extensions(&self) -> HashSet<String>;

fn supports_debug(&self) -> bool;

unsafe fn create_framebuffer(&self) -> Result<Self::Framebuffer, String>;
Expand Down
35 changes: 27 additions & 8 deletions src/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::collections::HashSet;
use std::ffi::CString;

use crate::gl46 as native_gl;
use crate::version::Version;

#[derive(Default)]
struct Constants {
Expand Down Expand Up @@ -39,13 +40,27 @@ impl Context {
constants: Constants::default(),
};

let raw_version = context.get_parameter_string(VERSION);
let version = Version::parse(&raw_version).unwrap();

// Use core-only functions to populate extension list
// TODO: Use a fallback for versions < 3.0
let num_extensions = context.get_parameter_i32(NUM_EXTENSIONS);
for i in 0..num_extensions {
let extension_name = context.get_parameter_indexed_string(EXTENSIONS, i as u32);
context.extensions.insert(extension_name);
}
if (version >= Version::new(3, 0, None, String::from("")))
|| (version >= Version::new_embedded(3, 0, String::from("")))
{
let num_extensions = context.get_parameter_i32(NUM_EXTENSIONS);
for i in 0..num_extensions {
let extension_name = context.get_parameter_indexed_string(EXTENSIONS, i as u32);
context.extensions.insert(extension_name);
}
} else {
// Fallback
context.extensions.extend(
context
.get_parameter_string(EXTENSIONS)
.split(' ')
.map(|s| s.to_string()),
);
};

// After the extensions are known, we can populate constants (including
// constants that depend on extensions being enabled)
Expand Down Expand Up @@ -79,6 +94,10 @@ impl HasContext for Context {
type UniformLocation = native_gl::GLuint;
type TransformFeedback = native_gl::GLuint;

fn get_supported_extensions(&self) -> HashSet<String> {
self.extensions.clone()
}

fn supports_debug(&self) -> bool {
self.extensions.contains("GL_KHR_debug")
}
Expand Down Expand Up @@ -1138,7 +1157,7 @@ impl HasContext for Context {
internal_format: i32,
width: i32,
height: i32,
fixed_sample_locations: bool
fixed_sample_locations: bool,
) {
let gl = &self.raw;
gl.TexImage2DMultisample(
Expand All @@ -1147,7 +1166,7 @@ impl HasContext for Context {
internal_format as u32,
width,
height,
if fixed_sample_locations { 1 } else { 0 }
if fixed_sample_locations { 1 } else { 0 },
);
}

Expand Down
216 changes: 216 additions & 0 deletions src/version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/// A version number for a specific component of an OpenGL implementation
#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
pub(crate) struct Version {
pub major: u32,
pub minor: u32,
pub is_embedded: bool,
pub revision: Option<u32>,
pub vendor_info: String,
}

impl Version {
/// Create a new OpenGL version number
pub fn new(major: u32, minor: u32, revision: Option<u32>, vendor_info: String) -> Self {
Version {
major: major,
minor: minor,
is_embedded: false,
revision: revision,
vendor_info,
}
}
/// Create a new OpenGL ES version number
pub fn new_embedded(major: u32, minor: u32, vendor_info: String) -> Self {
Version {
major,
minor,
is_embedded: true,
revision: None,
vendor_info,
}
}

/// Get a tuple of (major, minor) versions
pub fn tuple(&self) -> (u32, u32) {
(self.major, self.minor)
}

/// According to the OpenGL specification, the version information is
/// expected to follow the following syntax:
///
/// ~~~bnf
/// <major> ::= <number>
/// <minor> ::= <number>
/// <revision> ::= <number>
/// <vendor-info> ::= <string>
/// <release> ::= <major> "." <minor> ["." <release>]
/// <version> ::= <release> [" " <vendor-info>]
/// ~~~
///
/// 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`.
/// # Notes
/// `WebGL 2` version returned as `OpenGL ES 3.0`
pub fn parse(mut src: &str) -> Result<Version, &str> {
let webgl_sig = "WebGL ";
// According to the WebGL specification
// VERSION WebGL<space>1.0<space><vendor-specific information>
// SHADING_LANGUAGE_VERSION WebGL<space>GLSL<space>ES<space>1.0<space><vendor-specific information>
let is_webgl = src.starts_with(webgl_sig);
let is_es = if is_webgl {
let pos = src.rfind(webgl_sig).unwrap_or(0);
src = &src[pos + webgl_sig.len()..];
true
} else {
let es_sig = " ES ";
match src.rfind(es_sig) {
Some(pos) => {
src = &src[pos + es_sig.len()..];
true
}
None => false,
}
};

let glsl_es_sig = "GLSL ES ";
let is_glsl = match src.find(glsl_es_sig) {
Some(pos) => {
src = &src[pos + glsl_es_sig.len()..];
true
}
None => false,
};

let (version, vendor_info) = match src.find(' ') {
Some(i) => (&src[..i], src[i + 1..].to_string()),
None => (src, String::new()),
};

// TODO: make this even more lenient so that we can also accept
// `<major> "." <minor> [<???>]`
let mut it = version.split('.');
let major = it.next().and_then(|s| s.parse().ok());
let minor = it.next().and_then(|s| {
let trimmed = if s.starts_with('0') {
"0"
} else {
s.trim_end_matches('0')
};
trimmed.parse().ok()
});
let revision = if is_webgl {
None
} else {
it.next().and_then(|s| s.parse().ok())
};

match (major, minor, revision) {
(Some(major), Some(minor), revision) => Ok(Version {
// Return WebGL 2.0 version as OpenGL ES 3.0
major: if is_webgl && !is_glsl {
major + 1
} else {
major
},
minor,
is_embedded: is_es,
revision,
vendor_info,
}),
(_, _, _) => Err(src),
}
}
}

impl std::fmt::Debug for Version {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match (
self.major,
self.minor,
self.revision,
self.vendor_info.as_str(),
) {
(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),
}
}
}

#[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), String::new()))
);
assert_eq!(
Version::parse("1.2"),
Ok(Version::new(1, 2, None, String::new()))
);
assert_eq!(
Version::parse("1.2 h3l1o. W0rld"),
Ok(Version::new(1, 2, None, "h3l1o. W0rld".to_string()))
);
assert_eq!(
Version::parse("1.2.h3l1o. W0rld"),
Ok(Version::new(1, 2, None, "W0rld".to_string()))
);
assert_eq!(
Version::parse("1.2. h3l1o. W0rld"),
Ok(Version::new(1, 2, None, "h3l1o. W0rld".to_string()))
);
assert_eq!(
Version::parse("1.2.3.h3l1o. W0rld"),
Ok(Version::new(1, 2, Some(3), "W0rld".to_string()))
);
assert_eq!(
Version::parse("1.2.3 h3l1o. W0rld"),
Ok(Version::new(1, 2, Some(3), "h3l1o. W0rld".to_string()))
);
assert_eq!(
Version::parse("OpenGL ES 3.1"),
Ok(Version::new_embedded(3, 1, String::new()))
);
assert_eq!(
Version::parse("OpenGL ES 2.0 Google Nexus"),
Ok(Version::new_embedded(2, 0, "Google Nexus".to_string()))
);
assert_eq!(
Version::parse("GLSL ES 1.1"),
Ok(Version::new_embedded(1, 1, String::new()))
);
assert_eq!(
Version::parse("OpenGL ES GLSL ES 3.20"),
Ok(Version::new_embedded(3, 2, String::new()))
);
assert_eq!(
// WebGL 2.0 should parse as OpenGL ES 3.0
Version::parse("WebGL 2.0 (OpenGL ES 3.0 Chromium)"),
Ok(Version::new_embedded(
3,
0,
"(OpenGL ES 3.0 Chromium)".to_string()
))
);
assert_eq!(
Version::parse("WebGL GLSL ES 3.00 (OpenGL ES GLSL ES 3.0 Chromium)"),
Ok(Version::new_embedded(
3,
0,
"(OpenGL ES GLSL ES 3.0 Chromium)".to_string()
))
);
}
}
14 changes: 13 additions & 1 deletion src/web_sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,18 @@ impl HasContext for Context {
type UniformLocation = WebGlUniformLocation;
type TransformFeedback = WebTransformFeedbackKey;

fn get_supported_extensions(&self) -> HashSet<String> {
let extensions_array = match self.raw {
RawRenderingContext::WebGl1(ref gl) => gl.get_supported_extensions(),
RawRenderingContext::WebGl2(ref gl) => gl.get_supported_extensions(),
}
.unwrap();
extensions_array
.iter()
.map(|val| val.as_string().unwrap())
.collect()
}

fn supports_debug(&self) -> bool {
false
}
Expand Down Expand Up @@ -1909,7 +1921,7 @@ impl HasContext for Context {
internal_format: i32,
width: i32,
height: i32,
fixed_sample_locations: bool
fixed_sample_locations: bool,
) {
panic!("Tex image 2D multisample is not supported");
}
Expand Down