glow/
version.rs

1/// A version number for a specific component of an OpenGL implementation
2#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
3pub 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
11impl 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
124impl 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, minor, Some(revision), "") => write!(f, "{}.{}.{}", major, minor, revision),
133            (major, minor, None, "") => write!(f, "{}.{}", major, minor),
134            (major, minor, Some(revision), vendor_info) => {
135                write!(f, "{}.{}.{}, {}", major, minor, revision, vendor_info)
136            }
137            (major, minor, None, vendor_info) => write!(f, "{}.{}, {}", major, minor, vendor_info),
138        }
139    }
140}
141
142#[cfg(test)]
143mod 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}