From 2d05509b31b44a5b5254b90f1adaa1b4a12196df Mon Sep 17 00:00:00 2001 From: Frizi Date: Fri, 11 Jun 2021 19:09:50 +0200 Subject: [PATCH 1/5] expose webgl extensions through parameter api --- src/web_sys.rs | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/web_sys.rs b/src/web_sys.rs index 4a06ca6..ca8ecfc 100644 --- a/src/web_sys.rs +++ b/src/web_sys.rs @@ -1544,7 +1544,12 @@ impl HasContext for Context { unsafe fn get_parameter_i32(&self, parameter: u32) -> i32 { match self.raw { RawRenderingContext::WebGl1(ref gl) => gl.get_parameter(parameter), - RawRenderingContext::WebGl2(ref gl) => gl.get_parameter(parameter), + RawRenderingContext::WebGl2(ref gl) => { + if parameter == NUM_EXTENSIONS { + return gl.get_supported_extensions().unwrap().length() as i32; + } + gl.get_parameter(parameter) + } } .unwrap() .as_f64() @@ -1615,16 +1620,37 @@ impl HasContext for Context { RawRenderingContext::WebGl1(ref _gl) => { panic!("Get parameter indexed is not supported") } - RawRenderingContext::WebGl2(ref gl) => gl.get_indexed_parameter(parameter, index), + RawRenderingContext::WebGl2(ref gl) => { + if parameter == EXTENSIONS { + gl.get_supported_extensions() + .unwrap() + .get(index) + .as_string() + .unwrap_or_else(|| String::from("")) + } else { + gl.get_indexed_parameter(parameter, index) + .unwrap() + .as_string() + // Errors will be caught by the browser or through `get_error` + // so return a default instead + .unwrap_or_else(|| String::from("")) + } + } } - .unwrap() - .as_string() - // Errors will be caught by the browser or through `get_error` - // so return a default instead - .unwrap_or_else(|| String::from("")) } unsafe fn get_parameter_string(&self, parameter: u32) -> String { + if parameter == EXTENSIONS { + return match self.raw { + RawRenderingContext::WebGl1(ref gl) => gl.get_supported_extensions(), + RawRenderingContext::WebGl2(ref gl) => gl.get_supported_extensions(), + } + .unwrap() + .join(" ") + .as_string() + .unwrap(); + } + match self.raw { RawRenderingContext::WebGl1(ref gl) => gl.get_parameter(parameter), RawRenderingContext::WebGl2(ref gl) => gl.get_parameter(parameter), @@ -1909,7 +1935,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"); } From 2b3a767c4c70cc460a43390a054c6f7b151ce5af Mon Sep 17 00:00:00 2001 From: Frizi Date: Sat, 12 Jun 2021 15:17:12 +0200 Subject: [PATCH 2/5] create dedicated get_supported_extensions api call --- src/lib.rs | 3 +++ src/native.rs | 16 ++++++++++++++-- src/web_sys.rs | 49 ++++++++++++++++--------------------------------- 3 files changed, 33 insertions(+), 35 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fe4b72e..f421c62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ use core::fmt::Debug; use core::hash::Hash; +use std::collections::HashSet; #[cfg(not(target_arch = "wasm32"))] mod native; @@ -90,6 +91,8 @@ pub trait HasContext { type TransformFeedback: Copy + Clone + Debug + Eq + Hash + Ord + PartialEq + PartialOrd; type UniformLocation: Clone + Debug; + fn get_supported_extensions(&self) -> HashSet; + fn supports_debug(&self) -> bool; unsafe fn create_framebuffer(&self) -> Result; diff --git a/src/native.rs b/src/native.rs index 7858cdd..a4f03f7 100644 --- a/src/native.rs +++ b/src/native.rs @@ -47,6 +47,14 @@ impl Context { context.extensions.insert(extension_name); } + // 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) context.constants.max_label_length = if context.supports_debug() { @@ -79,6 +87,10 @@ impl HasContext for Context { type UniformLocation = native_gl::GLuint; type TransformFeedback = native_gl::GLuint; + fn get_supported_extensions(&self) -> HashSet { + self.extensions.clone() + } + fn supports_debug(&self) -> bool { self.extensions.contains("GL_KHR_debug") } @@ -1138,7 +1150,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( @@ -1147,7 +1159,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 }, ); } diff --git a/src/web_sys.rs b/src/web_sys.rs index ca8ecfc..de18ef6 100644 --- a/src/web_sys.rs +++ b/src/web_sys.rs @@ -436,6 +436,15 @@ impl HasContext for Context { type UniformLocation = WebGlUniformLocation; type TransformFeedback = WebTransformFeedbackKey; + fn get_supported_extensions(&self) -> HashSet { + 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()).collect() + } + fn supports_debug(&self) -> bool { false } @@ -1544,12 +1553,7 @@ impl HasContext for Context { unsafe fn get_parameter_i32(&self, parameter: u32) -> i32 { match self.raw { RawRenderingContext::WebGl1(ref gl) => gl.get_parameter(parameter), - RawRenderingContext::WebGl2(ref gl) => { - if parameter == NUM_EXTENSIONS { - return gl.get_supported_extensions().unwrap().length() as i32; - } - gl.get_parameter(parameter) - } + RawRenderingContext::WebGl2(ref gl) => gl.get_parameter(parameter), } .unwrap() .as_f64() @@ -1620,37 +1624,16 @@ impl HasContext for Context { RawRenderingContext::WebGl1(ref _gl) => { panic!("Get parameter indexed is not supported") } - RawRenderingContext::WebGl2(ref gl) => { - if parameter == EXTENSIONS { - gl.get_supported_extensions() - .unwrap() - .get(index) - .as_string() - .unwrap_or_else(|| String::from("")) - } else { - gl.get_indexed_parameter(parameter, index) - .unwrap() - .as_string() - // Errors will be caught by the browser or through `get_error` - // so return a default instead - .unwrap_or_else(|| String::from("")) - } - } + RawRenderingContext::WebGl2(ref gl) => gl.get_indexed_parameter(parameter, index), } + .unwrap() + .as_string() + // Errors will be caught by the browser or through `get_error` + // so return a default instead + .unwrap_or_else(|| String::from("")) } unsafe fn get_parameter_string(&self, parameter: u32) -> String { - if parameter == EXTENSIONS { - return match self.raw { - RawRenderingContext::WebGl1(ref gl) => gl.get_supported_extensions(), - RawRenderingContext::WebGl2(ref gl) => gl.get_supported_extensions(), - } - .unwrap() - .join(" ") - .as_string() - .unwrap(); - } - match self.raw { RawRenderingContext::WebGl1(ref gl) => gl.get_parameter(parameter), RawRenderingContext::WebGl2(ref gl) => gl.get_parameter(parameter), From 371103cfbdfa67d7c978c819b17d96f178e2061d Mon Sep 17 00:00:00 2001 From: Frizi Date: Sat, 12 Jun 2021 15:38:34 +0200 Subject: [PATCH 3/5] add version parsing and use fallback for extension listing on older native version --- src/lib.rs | 3 + src/native.rs | 34 ++++---- src/version.rs | 216 +++++++++++++++++++++++++++++++++++++++++++++++++ src/web_sys.rs | 5 +- 4 files changed, 243 insertions(+), 15 deletions(-) create mode 100644 src/version.rs diff --git a/src/lib.rs b/src/lib.rs index f421c62..2f7ee0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,9 @@ use core::fmt::Debug; use core::hash::Hash; use std::collections::HashSet; +mod version; +pub use version::Version; + #[cfg(not(target_arch = "wasm32"))] mod native; #[cfg(not(target_arch = "wasm32"))] diff --git a/src/native.rs b/src/native.rs index a4f03f7..5c5c8e0 100644 --- a/src/native.rs +++ b/src/native.rs @@ -39,21 +39,27 @@ impl Context { constants: Constants::default(), }; - // 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); - } + let raw_version = context.get_parameter_string(VERSION); + let version = Version::parse(&raw_version).unwrap(); - // Fallback - context.extensions.extend( - context - .get_parameter_string(EXTENSIONS) - .split(' ') - .map(|s| s.to_string()), - ); + // Use core-only functions to populate extension list + 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) diff --git a/src/version.rs b/src/version.rs new file mode 100644 index 0000000..78e33f1 --- /dev/null +++ b/src/version.rs @@ -0,0 +1,216 @@ +/// A version number for a specific component of an OpenGL implementation +#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] +pub struct Version { + pub major: u32, + pub minor: u32, + pub is_embedded: bool, + pub revision: Option, + pub vendor_info: String, +} + +impl Version { + /// Create a new OpenGL version number + pub fn new(major: u32, minor: u32, revision: Option, 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 + /// ::= + /// ::= + /// ::= + /// ::= + /// ::= "." ["." ] + /// ::= [" " ] + /// ~~~ + /// + /// 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 { + let webgl_sig = "WebGL "; + // According to the WebGL specification + // VERSION WebGL1.0 + // SHADING_LANGUAGE_VERSION WebGLGLSLES1.0 + 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 + // ` "." []` + 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() + )) + ); + } +} diff --git a/src/web_sys.rs b/src/web_sys.rs index de18ef6..70984ca 100644 --- a/src/web_sys.rs +++ b/src/web_sys.rs @@ -442,7 +442,10 @@ impl HasContext for Context { RawRenderingContext::WebGl2(ref gl) => gl.get_supported_extensions(), } .unwrap(); - extensions_array.iter().map(|val| val.as_string()).collect() + extensions_array + .iter() + .map(|val| val.as_string().unwrap()) + .collect() } fn supports_debug(&self) -> bool { From 505e799f8dba811da22690bb9b2f3b38bc3619b5 Mon Sep 17 00:00:00 2001 From: Frizi Date: Sat, 12 Jun 2021 18:47:26 +0200 Subject: [PATCH 4/5] do not expose version as public api yet --- src/lib.rs | 1 - src/native.rs | 1 + src/version.rs | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2f7ee0e..ba7e427 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,6 @@ use core::hash::Hash; use std::collections::HashSet; mod version; -pub use version::Version; #[cfg(not(target_arch = "wasm32"))] mod native; diff --git a/src/native.rs b/src/native.rs index 5c5c8e0..209e23b 100644 --- a/src/native.rs +++ b/src/native.rs @@ -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 { diff --git a/src/version.rs b/src/version.rs index 78e33f1..7b5b870 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1,6 +1,6 @@ /// A version number for a specific component of an OpenGL implementation #[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] -pub struct Version { +pub(crate) struct Version { pub major: u32, pub minor: u32, pub is_embedded: bool, From 3a315fbe8d2ae68f0ee52b63348c62eb1e97e062 Mon Sep 17 00:00:00 2001 From: Frizi Date: Tue, 15 Jun 2021 16:52:07 +0200 Subject: [PATCH 5/5] expose supported_extensions by reference --- src/lib.rs | 2 +- src/native.rs | 4 ++-- src/web_sys.rs | 32 ++++++++++++++++++-------------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ba7e427..aaf7958 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,7 +93,7 @@ pub trait HasContext { type TransformFeedback: Copy + Clone + Debug + Eq + Hash + Ord + PartialEq + PartialOrd; type UniformLocation: Clone + Debug; - fn get_supported_extensions(&self) -> HashSet; + fn supported_extensions(&self) -> &HashSet; fn supports_debug(&self) -> bool; diff --git a/src/native.rs b/src/native.rs index 209e23b..a8af5ea 100644 --- a/src/native.rs +++ b/src/native.rs @@ -94,8 +94,8 @@ impl HasContext for Context { type UniformLocation = native_gl::GLuint; type TransformFeedback = native_gl::GLuint; - fn get_supported_extensions(&self) -> HashSet { - self.extensions.clone() + fn supported_extensions(&self) -> &HashSet { + &self.extensions } fn supports_debug(&self) -> bool { diff --git a/src/web_sys.rs b/src/web_sys.rs index 70984ca..a804f36 100644 --- a/src/web_sys.rs +++ b/src/web_sys.rs @@ -65,6 +65,7 @@ fn tracked_resource() -> TrackedResource { pub struct Context { raw: RawRenderingContext, extensions: Extensions, + supported_extensions: HashSet, shaders: TrackedResource, programs: TrackedResource, buffers: TrackedResource, @@ -102,7 +103,7 @@ macro_rules! build_extensions { .and_then(|maybe_ext| maybe_ext.map(|_| ())) } - Extensions { + let extensions = Extensions { angle_instanced_arrays: get_extension::( &$context, "ANGLE_instanced_arrays", @@ -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::>(); + + (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(), @@ -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(), @@ -436,16 +448,8 @@ impl HasContext for Context { type UniformLocation = WebGlUniformLocation; type TransformFeedback = WebTransformFeedbackKey; - fn get_supported_extensions(&self) -> HashSet { - 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 supported_extensions(&self) -> &HashSet { + &self.supported_extensions } fn supports_debug(&self) -> bool {