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: 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)]
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}
215