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