| 1 | /// A version number for a specific component of an OpenGL implementation
|
| 2 | #[derive (Clone, Eq, Ord, PartialEq, PartialOrd)]
|
| 3 | pub struct Version {
|
| 4 | pub major: u32,
|
| 5 | pub minor: u32,
|
| 6 | pub is_embedded: bool,
|
| 7 | pub revision: Option<u32>,
|
| 8 | pub vendor_info: String,
|
| 9 | }
|
| 10 |
|
| 11 | impl Version {
|
| 12 | /// Create a new OpenGL version number
|
| 13 | #[allow (dead_code)]
|
| 14 | pub(crate) fn new(major: u32, minor: u32, revision: Option<u32>, vendor_info: String) -> Self {
|
| 15 | Version {
|
| 16 | major: major,
|
| 17 | minor: minor,
|
| 18 | is_embedded: false,
|
| 19 | revision: revision,
|
| 20 | vendor_info,
|
| 21 | }
|
| 22 | }
|
| 23 |
|
| 24 | /// Create a new OpenGL ES version number
|
| 25 | #[allow (dead_code)]
|
| 26 | pub(crate) fn new_embedded(major: u32, minor: u32, vendor_info: String) -> Self {
|
| 27 | Version {
|
| 28 | major,
|
| 29 | minor,
|
| 30 | is_embedded: true,
|
| 31 | revision: None,
|
| 32 | vendor_info,
|
| 33 | }
|
| 34 | }
|
| 35 |
|
| 36 | /// According to the OpenGL specification, the version information is
|
| 37 | /// expected to follow the following syntax:
|
| 38 | ///
|
| 39 | /// ~~~bnf
|
| 40 | /// <major> ::= <number>
|
| 41 | /// <minor> ::= <number>
|
| 42 | /// <revision> ::= <number>
|
| 43 | /// <vendor-info> ::= <string>
|
| 44 | /// <release> ::= <major> "." <minor> ["." <release>]
|
| 45 | /// <version> ::= <release> [" " <vendor-info>]
|
| 46 | /// ~~~
|
| 47 | ///
|
| 48 | /// Note that this function is intentionally lenient in regards to parsing,
|
| 49 | /// and will try to recover at least the first two version numbers without
|
| 50 | /// resulting in an `Err`.
|
| 51 | /// # Notes
|
| 52 | /// `WebGL 2` version returned as `OpenGL ES 3.0`
|
| 53 | pub(crate) fn parse(mut src: &str) -> Result<Version, &str> {
|
| 54 | let webgl_sig = "WebGL " ;
|
| 55 | // According to the WebGL specification
|
| 56 | // VERSION WebGL<space>1.0<space><vendor-specific information>
|
| 57 | // SHADING_LANGUAGE_VERSION WebGL<space>GLSL<space>ES<space>1.0<space><vendor-specific information>
|
| 58 | let is_webgl = src.starts_with(webgl_sig);
|
| 59 | let is_es = if is_webgl {
|
| 60 | let pos = src.rfind(webgl_sig).unwrap_or(0);
|
| 61 | src = &src[pos + webgl_sig.len()..];
|
| 62 | true
|
| 63 | } else {
|
| 64 | let es_sig = " ES " ;
|
| 65 | match src.rfind(es_sig) {
|
| 66 | Some(pos) => {
|
| 67 | src = &src[pos + es_sig.len()..];
|
| 68 | true
|
| 69 | }
|
| 70 | None => false,
|
| 71 | }
|
| 72 | };
|
| 73 |
|
| 74 | let glsl_es_sig = "GLSL ES " ;
|
| 75 | let is_glsl = match src.find(glsl_es_sig) {
|
| 76 | Some(pos) => {
|
| 77 | src = &src[pos + glsl_es_sig.len()..];
|
| 78 | true
|
| 79 | }
|
| 80 | None => false,
|
| 81 | };
|
| 82 |
|
| 83 | let (version, vendor_info) = match src.find(' ' ) {
|
| 84 | Some(i) => (&src[..i], src[i + 1..].to_string()),
|
| 85 | None => (src, String::new()),
|
| 86 | };
|
| 87 |
|
| 88 | // TODO: make this even more lenient so that we can also accept
|
| 89 | // `<major> "." <minor> [<???>]`
|
| 90 | let mut it = version.split('.' );
|
| 91 | let major = it.next().and_then(|s| s.parse().ok());
|
| 92 | let minor = it.next().and_then(|s| {
|
| 93 | let trimmed = if s.starts_with('0' ) {
|
| 94 | "0"
|
| 95 | } else {
|
| 96 | s.trim_end_matches('0' )
|
| 97 | };
|
| 98 | trimmed.parse().ok()
|
| 99 | });
|
| 100 | let revision = if is_webgl {
|
| 101 | None
|
| 102 | } else {
|
| 103 | it.next().and_then(|s| s.parse().ok())
|
| 104 | };
|
| 105 |
|
| 106 | match (major, minor, revision) {
|
| 107 | (Some(major), Some(minor), revision) => Ok(Version {
|
| 108 | // Return WebGL 2.0 version as OpenGL ES 3.0
|
| 109 | major: if is_webgl && !is_glsl {
|
| 110 | major + 1
|
| 111 | } else {
|
| 112 | major
|
| 113 | },
|
| 114 | minor,
|
| 115 | is_embedded: is_es,
|
| 116 | revision,
|
| 117 | vendor_info,
|
| 118 | }),
|
| 119 | (_, _, _) => Err(src),
|
| 120 | }
|
| 121 | }
|
| 122 | }
|
| 123 |
|
| 124 | impl std::fmt::Debug for Version {
|
| 125 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
| 126 | match (
|
| 127 | self.major,
|
| 128 | self.minor,
|
| 129 | self.revision,
|
| 130 | self.vendor_info.as_str(),
|
| 131 | ) {
|
| 132 | (major: u32, minor: u32, Some(revision: u32), "" ) => write!(f, " {}. {}. {}" , major, minor, revision),
|
| 133 | (major: u32, minor: u32, None, "" ) => write!(f, " {}. {}" , major, minor),
|
| 134 | (major: u32, minor: u32, Some(revision: u32), vendor_info: &str) => {
|
| 135 | write!(f, " {}. {}. {}, {}" , major, minor, revision, vendor_info)
|
| 136 | }
|
| 137 | (major: u32, minor: u32, None, vendor_info: &str) => write!(f, " {}. {}, {}" , major, minor, vendor_info),
|
| 138 | }
|
| 139 | }
|
| 140 | }
|
| 141 |
|
| 142 | #[cfg (test)]
|
| 143 | mod tests {
|
| 144 | use super::Version;
|
| 145 |
|
| 146 | #[test ]
|
| 147 | fn test_version_parse() {
|
| 148 | assert_eq!(Version::parse("1" ), Err("1" ));
|
| 149 | assert_eq!(Version::parse("1." ), Err("1." ));
|
| 150 | assert_eq!(Version::parse("1 h3l1o. W0rld" ), Err("1 h3l1o. W0rld" ));
|
| 151 | assert_eq!(Version::parse("1. h3l1o. W0rld" ), Err("1. h3l1o. W0rld" ));
|
| 152 | assert_eq!(
|
| 153 | Version::parse("1.2.3" ),
|
| 154 | Ok(Version::new(1, 2, Some(3), String::new()))
|
| 155 | );
|
| 156 | assert_eq!(
|
| 157 | Version::parse("1.2" ),
|
| 158 | Ok(Version::new(1, 2, None, String::new()))
|
| 159 | );
|
| 160 | assert_eq!(
|
| 161 | Version::parse("1.2 h3l1o. W0rld" ),
|
| 162 | Ok(Version::new(1, 2, None, "h3l1o. W0rld" .to_string()))
|
| 163 | );
|
| 164 | assert_eq!(
|
| 165 | Version::parse("1.2.h3l1o. W0rld" ),
|
| 166 | Ok(Version::new(1, 2, None, "W0rld" .to_string()))
|
| 167 | );
|
| 168 | assert_eq!(
|
| 169 | Version::parse("1.2. h3l1o. W0rld" ),
|
| 170 | Ok(Version::new(1, 2, None, "h3l1o. W0rld" .to_string()))
|
| 171 | );
|
| 172 | assert_eq!(
|
| 173 | Version::parse("1.2.3.h3l1o. W0rld" ),
|
| 174 | Ok(Version::new(1, 2, Some(3), "W0rld" .to_string()))
|
| 175 | );
|
| 176 | assert_eq!(
|
| 177 | Version::parse("1.2.3 h3l1o. W0rld" ),
|
| 178 | Ok(Version::new(1, 2, Some(3), "h3l1o. W0rld" .to_string()))
|
| 179 | );
|
| 180 | assert_eq!(
|
| 181 | Version::parse("OpenGL ES 3.1" ),
|
| 182 | Ok(Version::new_embedded(3, 1, String::new()))
|
| 183 | );
|
| 184 | assert_eq!(
|
| 185 | Version::parse("OpenGL ES 2.0 Google Nexus" ),
|
| 186 | Ok(Version::new_embedded(2, 0, "Google Nexus" .to_string()))
|
| 187 | );
|
| 188 | assert_eq!(
|
| 189 | Version::parse("GLSL ES 1.1" ),
|
| 190 | Ok(Version::new_embedded(1, 1, String::new()))
|
| 191 | );
|
| 192 | assert_eq!(
|
| 193 | Version::parse("OpenGL ES GLSL ES 3.20" ),
|
| 194 | Ok(Version::new_embedded(3, 2, String::new()))
|
| 195 | );
|
| 196 | assert_eq!(
|
| 197 | // WebGL 2.0 should parse as OpenGL ES 3.0
|
| 198 | Version::parse("WebGL 2.0 (OpenGL ES 3.0 Chromium)" ),
|
| 199 | Ok(Version::new_embedded(
|
| 200 | 3,
|
| 201 | 0,
|
| 202 | "(OpenGL ES 3.0 Chromium)" .to_string()
|
| 203 | ))
|
| 204 | );
|
| 205 | assert_eq!(
|
| 206 | Version::parse("WebGL GLSL ES 3.00 (OpenGL ES GLSL ES 3.0 Chromium)" ),
|
| 207 | Ok(Version::new_embedded(
|
| 208 | 3,
|
| 209 | 0,
|
| 210 | "(OpenGL ES GLSL ES 3.0 Chromium)" .to_string()
|
| 211 | ))
|
| 212 | );
|
| 213 | }
|
| 214 | }
|
| 215 | |