Skip to content

Commit

Permalink
Merge #165
Browse files Browse the repository at this point in the history
165: expose webgl extensions through parameter api r=grovesNL a=Frizi

Currently it is impossible to query for webgl extensions, due to it using specialized unexposed api for retreiving the list. This PR exposes the list with desktop gl api by intercepting parameter calls and implementing them using the web-specific api under the hood.

Co-authored-by: Frizi <[email protected]>
  • Loading branch information
bors[bot] and Frizi authored Jun 17, 2021
2 parents 41581ef + 3a315fb commit 23d1627
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 13 deletions.
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 @@ -1923,7 +1939,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

0 comments on commit 23d1627

Please sign in to comment.