Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 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 supported_extensions(&self) -> &HashSet<String> {
&self.extensions
}

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()
))
);
}
}
26 changes: 21 additions & 5 deletions src/web_sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ fn tracked_resource<K: slotmap::Key, V>() -> TrackedResource<K, V> {
pub struct Context {
raw: RawRenderingContext,
extensions: Extensions,
supported_extensions: HashSet<String>,
shaders: TrackedResource<WebShaderKey, WebGlShader>,
programs: TrackedResource<WebProgramKey, WebGlProgram>,
buffers: TrackedResource<WebBufferKey, WebGlBuffer>,
Expand Down Expand Up @@ -102,7 +103,7 @@ macro_rules! build_extensions {
.and_then(|maybe_ext| maybe_ext.map(|_| ()))
}

Extensions {
let extensions = Extensions {
angle_instanced_arrays: get_extension::<web_sys::AngleInstancedArrays>(
&$context,
"ANGLE_instanced_arrays",
Expand Down Expand Up @@ -229,16 +230,26 @@ macro_rules! build_extensions {
&$context,
"WEBGL_lose_context",
),
}
};

let supported_extensions = $context
.get_supported_extensions()
.unwrap()
.iter()
.map(|val| val.as_string().unwrap())
.collect::<HashSet<String>>();

(extensions, supported_extensions)
}};
}

impl Context {
pub fn from_webgl1_context(context: WebGlRenderingContext) -> Self {
let extensions = build_extensions!(context, WebGlRenderingContext);
let (extensions, supported_extensions) = build_extensions!(context, WebGlRenderingContext);
Self {
raw: RawRenderingContext::WebGl1(context),
extensions,
supported_extensions,
shaders: tracked_resource(),
programs: tracked_resource(),
buffers: tracked_resource(),
Expand All @@ -254,10 +265,11 @@ impl Context {
}

pub fn from_webgl2_context(context: WebGl2RenderingContext) -> Self {
let extensions = build_extensions!(context, WebGl2RenderingContext);
let (extensions, supported_extensions) = build_extensions!(context, WebGl2RenderingContext);
Self {
raw: RawRenderingContext::WebGl2(context),
extensions,
supported_extensions,
shaders: tracked_resource(),
programs: tracked_resource(),
buffers: tracked_resource(),
Expand Down Expand Up @@ -436,6 +448,10 @@ impl HasContext for Context {
type UniformLocation = WebGlUniformLocation;
type TransformFeedback = WebTransformFeedbackKey;

fn supported_extensions(&self) -> &HashSet<String> {
&self.supported_extensions
}

fn supports_debug(&self) -> bool {
false
}
Expand Down Expand Up @@ -1909,7 +1925,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