1use glow::HasContext;
2use parking_lot::Mutex;
3use std::sync::{atomic::AtomicU8, Arc};
4use wgt::AstcChannel;
5
6use crate::auxil::db;
7use crate::gles::ShaderClearProgram;
8
9const GL_UNMASKED_VENDOR_WEBGL: u32 = 0x9245;
12const GL_UNMASKED_RENDERER_WEBGL: u32 = 0x9246;
13
14impl super::Adapter {
15 fn parse_version(mut src: &str) -> Result<(u8, u8), crate::InstanceError> {
21 let webgl_sig = "WebGL ";
22 let is_webgl = src.starts_with(webgl_sig);
26 if is_webgl {
27 let pos = src.rfind(webgl_sig).unwrap_or(0);
28 src = &src[pos + webgl_sig.len()..];
29 } else {
30 let es_sig = " ES ";
31 match src.rfind(es_sig) {
32 Some(pos) => {
33 src = &src[pos + es_sig.len()..];
34 }
35 None => {
36 return Err(crate::InstanceError::new(format!(
37 "OpenGL version {src:?} does not contain 'ES'"
38 )));
39 }
40 }
41 };
42
43 let glsl_es_sig = "GLSL ES ";
44 let is_glsl = match src.find(glsl_es_sig) {
45 Some(pos) => {
46 src = &src[pos + glsl_es_sig.len()..];
47 true
48 }
49 None => false,
50 };
51
52 Self::parse_full_version(src).map(|(major, minor)| {
53 (
54 if is_webgl && !is_glsl {
56 major + 1
57 } else {
58 major
59 },
60 minor,
61 )
62 })
63 }
64
65 pub(super) fn parse_full_version(src: &str) -> Result<(u8, u8), crate::InstanceError> {
81 let (version, _vendor_info) = match src.find(' ') {
82 Some(i) => (&src[..i], src[i + 1..].to_string()),
83 None => (src, String::new()),
84 };
85
86 let mut it = version.split('.');
89 let major = it.next().and_then(|s| s.parse().ok());
90 let minor = it.next().and_then(|s| {
91 let trimmed = if s.starts_with('0') {
92 "0"
93 } else {
94 s.trim_end_matches('0')
95 };
96 trimmed.parse().ok()
97 });
98
99 match (major, minor) {
100 (Some(major), Some(minor)) => Ok((major, minor)),
101 _ => Err(crate::InstanceError::new(format!(
102 "unable to extract OpenGL version from {version:?}"
103 ))),
104 }
105 }
106
107 fn make_info(vendor_orig: String, renderer_orig: String, version: String) -> wgt::AdapterInfo {
108 let vendor = vendor_orig.to_lowercase();
109 let renderer = renderer_orig.to_lowercase();
110
111 let strings_that_imply_integrated = [
113 " xpress", "amd renoir",
115 "radeon hd 4200",
116 "radeon hd 4250",
117 "radeon hd 4290",
118 "radeon hd 4270",
119 "radeon hd 4225",
120 "radeon hd 3100",
121 "radeon hd 3200",
122 "radeon hd 3000",
123 "radeon hd 3300",
124 "radeon(tm) r4 graphics",
125 "radeon(tm) r5 graphics",
126 "radeon(tm) r6 graphics",
127 "radeon(tm) r7 graphics",
128 "radeon r7 graphics",
129 "nforce", "tegra", "shield", "igp",
133 "mali",
134 "intel",
135 "v3d",
136 "apple m", ];
138 let strings_that_imply_cpu = ["mesa offscreen", "swiftshader", "llvmpipe"];
139
140 let inferred_device_type = if vendor.contains("qualcomm")
142 || vendor.contains("intel")
143 || strings_that_imply_integrated
144 .iter()
145 .any(|&s| renderer.contains(s))
146 {
147 wgt::DeviceType::IntegratedGpu
148 } else if strings_that_imply_cpu.iter().any(|&s| renderer.contains(s)) {
149 wgt::DeviceType::Cpu
150 } else {
151 wgt::DeviceType::Other
157 };
158
159 let vendor_id = if vendor.contains("amd") {
161 db::amd::VENDOR
162 } else if vendor.contains("imgtec") {
163 db::imgtec::VENDOR
164 } else if vendor.contains("nvidia") {
165 db::nvidia::VENDOR
166 } else if vendor.contains("arm") {
167 db::arm::VENDOR
168 } else if vendor.contains("qualcomm") {
169 db::qualcomm::VENDOR
170 } else if vendor.contains("intel") {
171 db::intel::VENDOR
172 } else if vendor.contains("broadcom") {
173 db::broadcom::VENDOR
174 } else if vendor.contains("mesa") {
175 db::mesa::VENDOR
176 } else if vendor.contains("apple") {
177 db::apple::VENDOR
178 } else {
179 0
180 };
181
182 wgt::AdapterInfo {
183 name: renderer_orig,
184 vendor: vendor_id,
185 device: 0,
186 device_type: inferred_device_type,
187 driver: "".to_owned(),
188 driver_info: version,
189 backend: wgt::Backend::Gl,
190 }
191 }
192
193 pub(super) unsafe fn expose(
194 context: super::AdapterContext,
195 ) -> Option<crate::ExposedAdapter<super::Api>> {
196 let gl = context.lock();
197 let extensions = gl.supported_extensions();
198
199 let (vendor_const, renderer_const) = if extensions.contains("WEBGL_debug_renderer_info") {
200 #[cfg(Emscripten)]
203 if unsafe { super::emscripten::enable_extension("WEBGL_debug_renderer_info\0") } {
204 (GL_UNMASKED_VENDOR_WEBGL, GL_UNMASKED_RENDERER_WEBGL)
205 } else {
206 (glow::VENDOR, glow::RENDERER)
207 }
208 #[cfg(not(Emscripten))]
210 (GL_UNMASKED_VENDOR_WEBGL, GL_UNMASKED_RENDERER_WEBGL)
211 } else {
212 (glow::VENDOR, glow::RENDERER)
213 };
214
215 let vendor = unsafe { gl.get_parameter_string(vendor_const) };
216 let renderer = unsafe { gl.get_parameter_string(renderer_const) };
217 let version = unsafe { gl.get_parameter_string(glow::VERSION) };
218 log::debug!("Vendor: {}", vendor);
219 log::debug!("Renderer: {}", renderer);
220 log::debug!("Version: {}", version);
221
222 let full_ver = Self::parse_full_version(&version).ok();
223 let es_ver = full_ver.map_or_else(|| Self::parse_version(&version).ok(), |_| None);
224
225 if let Some(full_ver) = full_ver {
226 let core_profile = (full_ver >= (3, 2)).then(|| unsafe {
227 gl.get_parameter_i32(glow::CONTEXT_PROFILE_MASK)
228 & glow::CONTEXT_CORE_PROFILE_BIT as i32
229 != 0
230 });
231 log::trace!(
232 "Profile: {}",
233 core_profile
234 .map(|core_profile| if core_profile {
235 "Core"
236 } else {
237 "Compatibility"
238 })
239 .unwrap_or("Legacy")
240 );
241 }
242
243 if es_ver.is_none() && full_ver.is_none() {
244 log::warn!("Unable to parse OpenGL version");
245 return None;
246 }
247
248 if let Some(es_ver) = es_ver {
249 if es_ver < (3, 0) {
250 log::warn!(
251 "Returned GLES context is {}.{}, when 3.0+ was requested",
252 es_ver.0,
253 es_ver.1
254 );
255 return None;
256 }
257 }
258
259 if let Some(full_ver) = full_ver {
260 if full_ver < (3, 3) {
261 log::warn!(
262 "Returned GL context is {}.{}, when 3.3+ is needed",
263 full_ver.0,
264 full_ver.1
265 );
266 return None;
267 }
268 }
269
270 let shading_language_version = {
271 let sl_version = unsafe { gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) };
272 log::debug!("SL version: {}", &sl_version);
273 if full_ver.is_some() {
274 let (sl_major, sl_minor) = Self::parse_full_version(&sl_version).ok()?;
275 let mut value = sl_major as u16 * 100 + sl_minor as u16 * 10;
276 if value > 450 {
278 value = 450;
279 }
280 naga::back::glsl::Version::Desktop(value)
281 } else {
282 let (sl_major, sl_minor) = Self::parse_version(&sl_version).ok()?;
283 let value = sl_major as u16 * 100 + sl_minor as u16 * 10;
284 naga::back::glsl::Version::Embedded {
285 version: value,
286 is_webgl: cfg!(any(webgl, Emscripten)),
287 }
288 }
289 };
290
291 log::debug!("Supported GL Extensions: {:#?}", extensions);
292
293 let supported = |(req_es_major, req_es_minor), (req_full_major, req_full_minor)| {
294 let es_supported = es_ver
295 .map(|es_ver| es_ver >= (req_es_major, req_es_minor))
296 .unwrap_or_default();
297
298 let full_supported = full_ver
299 .map(|full_ver| full_ver >= (req_full_major, req_full_minor))
300 .unwrap_or_default();
301
302 es_supported || full_supported
303 };
304
305 let supports_storage =
306 supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_shader_storage_buffer_object");
307 let supports_compute =
308 supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_compute_shader");
309 let supports_work_group_params = supports_compute;
310
311 let is_angle = renderer.contains("ANGLE");
313
314 let vertex_shader_storage_blocks = if supports_storage {
315 let value =
316 (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_SHADER_STORAGE_BLOCKS) } as u32);
317
318 if value == 0 && extensions.contains("GL_ARB_shader_storage_buffer_object") {
319 let new = (unsafe { gl.get_parameter_i32(glow::MAX_COMPUTE_SHADER_STORAGE_BLOCKS) }
322 as u32);
323 log::warn!("Max vertex shader storage blocks is zero, but GL_ARB_shader_storage_buffer_object is specified. Assuming the compute value {new}");
324 new
325 } else {
326 value
327 }
328 } else {
329 0
330 };
331 let fragment_shader_storage_blocks = if supports_storage {
332 (unsafe { gl.get_parameter_i32(glow::MAX_FRAGMENT_SHADER_STORAGE_BLOCKS) } as u32)
333 } else {
334 0
335 };
336 let vertex_shader_storage_textures = if supports_storage {
337 (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_IMAGE_UNIFORMS) } as u32)
338 } else {
339 0
340 };
341 let fragment_shader_storage_textures = if supports_storage {
342 (unsafe { gl.get_parameter_i32(glow::MAX_FRAGMENT_IMAGE_UNIFORMS) } as u32)
343 } else {
344 0
345 };
346 let max_storage_block_size = if supports_storage {
347 (unsafe { gl.get_parameter_i32(glow::MAX_SHADER_STORAGE_BLOCK_SIZE) } as u32)
348 } else {
349 0
350 };
351 let max_element_index = unsafe { gl.get_parameter_i32(glow::MAX_ELEMENT_INDEX) } as u32;
352
353 let vertex_ssbo_false_zero =
359 vertex_shader_storage_blocks == 0 && vertex_shader_storage_textures != 0;
360 if vertex_ssbo_false_zero {
361 log::warn!("Max vertex shader SSBO == 0 and SSTO != 0. Interpreting as false zero.");
363 }
364
365 let max_storage_buffers_per_shader_stage = if vertex_shader_storage_blocks == 0 {
366 fragment_shader_storage_blocks
367 } else {
368 vertex_shader_storage_blocks.min(fragment_shader_storage_blocks)
369 };
370 let max_storage_textures_per_shader_stage = if vertex_shader_storage_textures == 0 {
371 fragment_shader_storage_textures
372 } else {
373 vertex_shader_storage_textures.min(fragment_shader_storage_textures)
374 };
375 let indirect_execution =
376 supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_multi_draw_indirect");
377
378 let mut downlevel_flags = wgt::DownlevelFlags::empty()
379 | wgt::DownlevelFlags::NON_POWER_OF_TWO_MIPMAPPED_TEXTURES
380 | wgt::DownlevelFlags::CUBE_ARRAY_TEXTURES
381 | wgt::DownlevelFlags::COMPARISON_SAMPLERS
382 | wgt::DownlevelFlags::VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW;
383 downlevel_flags.set(wgt::DownlevelFlags::COMPUTE_SHADERS, supports_compute);
384 downlevel_flags.set(
385 wgt::DownlevelFlags::FRAGMENT_WRITABLE_STORAGE,
386 max_storage_block_size != 0,
387 );
388 downlevel_flags.set(wgt::DownlevelFlags::INDIRECT_EXECUTION, indirect_execution);
389 downlevel_flags.set(wgt::DownlevelFlags::BASE_VERTEX, supported((3, 2), (3, 2)));
390 downlevel_flags.set(
391 wgt::DownlevelFlags::INDEPENDENT_BLEND,
392 supported((3, 2), (4, 0)) || extensions.contains("GL_EXT_draw_buffers_indexed"),
393 );
394 downlevel_flags.set(
395 wgt::DownlevelFlags::VERTEX_STORAGE,
396 max_storage_block_size != 0
397 && max_storage_buffers_per_shader_stage != 0
398 && (vertex_shader_storage_blocks != 0 || vertex_ssbo_false_zero),
399 );
400 downlevel_flags.set(wgt::DownlevelFlags::FRAGMENT_STORAGE, supports_storage);
401 if extensions.contains("EXT_texture_filter_anisotropic")
402 || extensions.contains("GL_EXT_texture_filter_anisotropic")
403 {
404 let max_aniso =
405 unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_MAX_ANISOTROPY_EXT) } as u32;
406 downlevel_flags.set(wgt::DownlevelFlags::ANISOTROPIC_FILTERING, max_aniso >= 16);
407 }
408 downlevel_flags.set(
409 wgt::DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED,
410 !(cfg!(any(webgl, Emscripten)) || is_angle),
411 );
412 downlevel_flags.set(
414 wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER,
415 !cfg!(any(webgl, Emscripten)),
416 );
417 downlevel_flags.set(
418 wgt::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES,
419 !cfg!(any(webgl, Emscripten)),
420 );
421 downlevel_flags.set(
422 wgt::DownlevelFlags::FULL_DRAW_INDEX_UINT32,
423 max_element_index == u32::MAX,
424 );
425 downlevel_flags.set(
426 wgt::DownlevelFlags::MULTISAMPLED_SHADING,
427 supported((3, 2), (4, 0)) || extensions.contains("OES_sample_variables"),
428 );
429 let query_buffers = extensions.contains("GL_ARB_query_buffer_object")
430 || extensions.contains("GL_AMD_query_buffer_object");
431 if query_buffers {
432 downlevel_flags.set(wgt::DownlevelFlags::NONBLOCKING_QUERY_RESOLVE, true);
433 }
434
435 let mut features = wgt::Features::empty()
436 | wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
437 | wgt::Features::CLEAR_TEXTURE
438 | wgt::Features::PUSH_CONSTANTS
439 | wgt::Features::DEPTH32FLOAT_STENCIL8;
440 features.set(
441 wgt::Features::ADDRESS_MODE_CLAMP_TO_BORDER | wgt::Features::ADDRESS_MODE_CLAMP_TO_ZERO,
442 extensions.contains("GL_EXT_texture_border_clamp")
443 || extensions.contains("GL_ARB_texture_border_clamp"),
444 );
445 features.set(
446 wgt::Features::DEPTH_CLIP_CONTROL,
447 extensions.contains("GL_EXT_depth_clamp") || extensions.contains("GL_ARB_depth_clamp"),
448 );
449 features.set(
450 wgt::Features::VERTEX_WRITABLE_STORAGE,
451 downlevel_flags.contains(wgt::DownlevelFlags::VERTEX_STORAGE)
452 && vertex_shader_storage_textures != 0,
453 );
454 features.set(
455 wgt::Features::MULTIVIEW,
456 extensions.contains("OVR_multiview2") || extensions.contains("GL_OVR_multiview2"),
457 );
458 features.set(
459 wgt::Features::DUAL_SOURCE_BLENDING,
460 extensions.contains("GL_EXT_blend_func_extended")
461 || extensions.contains("GL_ARB_blend_func_extended"),
462 );
463 features.set(
464 wgt::Features::SHADER_PRIMITIVE_INDEX,
465 supported((3, 2), (3, 2))
466 || extensions.contains("OES_geometry_shader")
467 || extensions.contains("GL_ARB_geometry_shader4"),
468 );
469 features.set(
470 wgt::Features::SHADER_EARLY_DEPTH_TEST,
471 supported((3, 1), (4, 2)) || extensions.contains("GL_ARB_shader_image_load_store"),
472 );
473 features.set(wgt::Features::MULTI_DRAW_INDIRECT, indirect_execution);
475 if extensions.contains("GL_ARB_timer_query") {
476 features.set(wgt::Features::TIMESTAMP_QUERY, true);
477 features.set(wgt::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS, true);
478 features.set(wgt::Features::TIMESTAMP_QUERY_INSIDE_PASSES, true);
479 }
480 let gl_bcn_exts = [
481 "GL_EXT_texture_compression_s3tc",
482 "GL_EXT_texture_compression_rgtc",
483 "GL_ARB_texture_compression_bptc",
484 ];
485 let gles_bcn_exts = [
486 "GL_EXT_texture_compression_s3tc_srgb",
487 "GL_EXT_texture_compression_rgtc",
488 "GL_EXT_texture_compression_bptc",
489 ];
490 let webgl_bcn_exts = [
491 "WEBGL_compressed_texture_s3tc",
492 "WEBGL_compressed_texture_s3tc_srgb",
493 "EXT_texture_compression_rgtc",
494 "EXT_texture_compression_bptc",
495 ];
496 let bcn_exts = if cfg!(any(webgl, Emscripten)) {
497 &webgl_bcn_exts[..]
498 } else if es_ver.is_some() {
499 &gles_bcn_exts[..]
500 } else {
501 &gl_bcn_exts[..]
502 };
503 features.set(
504 wgt::Features::TEXTURE_COMPRESSION_BC,
505 bcn_exts.iter().all(|&ext| extensions.contains(ext)),
506 );
507 features.set(
508 wgt::Features::TEXTURE_COMPRESSION_BC_SLICED_3D,
509 bcn_exts.iter().all(|&ext| extensions.contains(ext)), );
511 let has_etc = if cfg!(any(webgl, Emscripten)) {
512 extensions.contains("WEBGL_compressed_texture_etc")
513 } else {
514 es_ver.is_some() || extensions.contains("GL_ARB_ES3_compatibility")
515 };
516 features.set(wgt::Features::TEXTURE_COMPRESSION_ETC2, has_etc);
517
518 if extensions.contains("WEBGL_compressed_texture_astc")
520 || extensions.contains("GL_OES_texture_compression_astc")
521 {
522 #[cfg(webgl)]
523 {
524 if context
525 .glow_context
526 .compressed_texture_astc_supports_ldr_profile()
527 {
528 features.insert(wgt::Features::TEXTURE_COMPRESSION_ASTC);
529 }
530 if context
531 .glow_context
532 .compressed_texture_astc_supports_hdr_profile()
533 {
534 features.insert(wgt::Features::TEXTURE_COMPRESSION_ASTC_HDR);
535 }
536 }
537
538 #[cfg(any(native, Emscripten))]
539 {
540 features.insert(wgt::Features::TEXTURE_COMPRESSION_ASTC);
541 features.insert(wgt::Features::TEXTURE_COMPRESSION_ASTC_HDR);
542 }
543 } else {
544 features.set(
545 wgt::Features::TEXTURE_COMPRESSION_ASTC,
546 extensions.contains("GL_KHR_texture_compression_astc_ldr"),
547 );
548 features.set(
549 wgt::Features::TEXTURE_COMPRESSION_ASTC_HDR,
550 extensions.contains("GL_KHR_texture_compression_astc_hdr"),
551 );
552 }
553
554 features.set(
555 wgt::Features::FLOAT32_FILTERABLE,
556 extensions.contains("GL_ARB_color_buffer_float")
557 || extensions.contains("GL_EXT_color_buffer_float")
558 || extensions.contains("OES_texture_float_linear"),
559 );
560
561 if es_ver.is_none() {
562 features |= wgt::Features::POLYGON_MODE_LINE | wgt::Features::POLYGON_MODE_POINT;
563 }
564
565 let mut private_caps = super::PrivateCapabilities::empty();
568 private_caps.set(
569 super::PrivateCapabilities::BUFFER_ALLOCATION,
570 extensions.contains("GL_EXT_buffer_storage")
571 || extensions.contains("GL_ARB_buffer_storage"),
572 );
573 private_caps.set(
574 super::PrivateCapabilities::SHADER_BINDING_LAYOUT,
575 supports_compute,
576 );
577 private_caps.set(
578 super::PrivateCapabilities::SHADER_TEXTURE_SHADOW_LOD,
579 extensions.contains("GL_EXT_texture_shadow_lod"),
580 );
581 private_caps.set(
582 super::PrivateCapabilities::MEMORY_BARRIERS,
583 supported((3, 1), (4, 2)),
584 );
585 private_caps.set(
586 super::PrivateCapabilities::VERTEX_BUFFER_LAYOUT,
587 supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_vertex_attrib_binding"),
588 );
589 private_caps.set(
590 super::PrivateCapabilities::INDEX_BUFFER_ROLE_CHANGE,
591 !cfg!(any(webgl, Emscripten)),
592 );
593 private_caps.set(
594 super::PrivateCapabilities::GET_BUFFER_SUB_DATA,
595 cfg!(any(webgl, Emscripten)) || full_ver.is_some(),
596 );
597 let color_buffer_float = extensions.contains("GL_EXT_color_buffer_float")
598 || extensions.contains("GL_ARB_color_buffer_float")
599 || extensions.contains("EXT_color_buffer_float");
600 let color_buffer_half_float = extensions.contains("GL_EXT_color_buffer_half_float")
601 || extensions.contains("GL_ARB_half_float_pixel");
602 private_caps.set(
603 super::PrivateCapabilities::COLOR_BUFFER_HALF_FLOAT,
604 color_buffer_half_float || color_buffer_float,
605 );
606 private_caps.set(
607 super::PrivateCapabilities::COLOR_BUFFER_FLOAT,
608 color_buffer_float,
609 );
610 private_caps.set(super::PrivateCapabilities::QUERY_BUFFERS, query_buffers);
611 private_caps.set(super::PrivateCapabilities::QUERY_64BIT, full_ver.is_some());
612 private_caps.set(
613 super::PrivateCapabilities::TEXTURE_STORAGE,
614 supported((3, 0), (4, 2)),
615 );
616 private_caps.set(super::PrivateCapabilities::DEBUG_FNS, gl.supports_debug());
617 private_caps.set(
618 super::PrivateCapabilities::INVALIDATE_FRAMEBUFFER,
619 supported((3, 0), (4, 3)),
620 );
621 if let Some(full_ver) = full_ver {
622 let supported =
623 full_ver >= (4, 2) && extensions.contains("GL_ARB_shader_draw_parameters");
624 private_caps.set(
625 super::PrivateCapabilities::FULLY_FEATURED_INSTANCING,
626 supported,
627 );
628 features.set(wgt::Features::INDIRECT_FIRST_INSTANCE, supported);
635 }
636
637 let max_texture_size = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as u32;
638 let max_texture_3d_size = unsafe { gl.get_parameter_i32(glow::MAX_3D_TEXTURE_SIZE) } as u32;
639
640 let min_uniform_buffer_offset_alignment =
641 (unsafe { gl.get_parameter_i32(glow::UNIFORM_BUFFER_OFFSET_ALIGNMENT) } as u32);
642 let min_storage_buffer_offset_alignment = if supports_storage {
643 (unsafe { gl.get_parameter_i32(glow::SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT) } as u32)
644 } else {
645 256
646 };
647 let max_uniform_buffers_per_shader_stage =
648 unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_UNIFORM_BLOCKS) }
649 .min(unsafe { gl.get_parameter_i32(glow::MAX_FRAGMENT_UNIFORM_BLOCKS) })
650 as u32;
651
652 let max_compute_workgroups_per_dimension = if supports_work_group_params {
653 unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_COUNT, 0) }
654 .min(unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_COUNT, 1) })
655 .min(unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_COUNT, 2) })
656 as u32
657 } else {
658 0
659 };
660
661 let max_color_attachments = unsafe {
662 gl.get_parameter_i32(glow::MAX_COLOR_ATTACHMENTS)
663 .min(gl.get_parameter_i32(glow::MAX_DRAW_BUFFERS))
664 .min(crate::MAX_COLOR_ATTACHMENTS as i32) as u32
665 };
666
667 let max_color_attachment_bytes_per_sample =
669 max_color_attachments * wgt::TextureFormat::MAX_TARGET_PIXEL_BYTE_COST;
670
671 let limits = wgt::Limits {
672 max_texture_dimension_1d: max_texture_size,
673 max_texture_dimension_2d: max_texture_size,
674 max_texture_dimension_3d: max_texture_3d_size,
675 max_texture_array_layers: unsafe {
676 gl.get_parameter_i32(glow::MAX_ARRAY_TEXTURE_LAYERS)
677 } as u32,
678 max_bind_groups: crate::MAX_BIND_GROUPS as u32,
679 max_bindings_per_bind_group: 65535,
680 max_dynamic_uniform_buffers_per_pipeline_layout: max_uniform_buffers_per_shader_stage,
681 max_dynamic_storage_buffers_per_pipeline_layout: max_storage_buffers_per_shader_stage,
682 max_sampled_textures_per_shader_stage: super::MAX_TEXTURE_SLOTS as u32,
683 max_samplers_per_shader_stage: super::MAX_SAMPLERS as u32,
684 max_storage_buffers_per_shader_stage,
685 max_storage_textures_per_shader_stage,
686 max_uniform_buffers_per_shader_stage,
687 max_uniform_buffer_binding_size: unsafe {
688 gl.get_parameter_i32(glow::MAX_UNIFORM_BLOCK_SIZE)
689 } as u32,
690 max_storage_buffer_binding_size: if supports_storage {
691 unsafe { gl.get_parameter_i32(glow::MAX_SHADER_STORAGE_BLOCK_SIZE) }
692 } else {
693 0
694 } as u32,
695 max_vertex_buffers: if private_caps
696 .contains(super::PrivateCapabilities::VERTEX_BUFFER_LAYOUT)
697 {
698 (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_BINDINGS) } as u32)
699 } else {
700 16 }
702 .min(crate::MAX_VERTEX_BUFFERS as u32),
703 max_vertex_attributes: (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIBS) }
704 as u32)
705 .min(super::MAX_VERTEX_ATTRIBUTES as u32),
706 max_vertex_buffer_array_stride: if private_caps
707 .contains(super::PrivateCapabilities::VERTEX_BUFFER_LAYOUT)
708 {
709 if let Some(full_ver) = full_ver {
710 if full_ver >= (4, 4) {
711 let value =
713 (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_STRIDE) })
714 as u32;
715
716 if value == 0 {
717 log::warn!("Max vertex attribute stride is 0. Assuming it is 2048");
721 2048
722 } else {
723 value
724 }
725 } else {
726 log::warn!("Max vertex attribute stride unknown. Assuming it is 2048");
727 2048
728 }
729 } else {
730 (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_STRIDE) }) as u32
731 }
732 } else {
733 !0
734 },
735 min_subgroup_size: 0,
736 max_subgroup_size: 0,
737 max_push_constant_size: super::MAX_PUSH_CONSTANTS as u32 * 4,
738 min_uniform_buffer_offset_alignment,
739 min_storage_buffer_offset_alignment,
740 max_inter_stage_shader_components: {
741 let max_varying_components =
745 unsafe { gl.get_parameter_i32(glow::MAX_VARYING_COMPONENTS) } as u32;
746 if max_varying_components == 0 {
747 60
749 } else {
750 max_varying_components
751 }
752 },
753 max_color_attachments,
754 max_color_attachment_bytes_per_sample,
755 max_compute_workgroup_storage_size: if supports_work_group_params {
756 (unsafe { gl.get_parameter_i32(glow::MAX_COMPUTE_SHARED_MEMORY_SIZE) } as u32)
757 } else {
758 0
759 },
760 max_compute_invocations_per_workgroup: if supports_work_group_params {
761 (unsafe { gl.get_parameter_i32(glow::MAX_COMPUTE_WORK_GROUP_INVOCATIONS) } as u32)
762 } else {
763 0
764 },
765 max_compute_workgroup_size_x: if supports_work_group_params {
766 (unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_SIZE, 0) }
767 as u32)
768 } else {
769 0
770 },
771 max_compute_workgroup_size_y: if supports_work_group_params {
772 (unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_SIZE, 1) }
773 as u32)
774 } else {
775 0
776 },
777 max_compute_workgroup_size_z: if supports_work_group_params {
778 (unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_SIZE, 2) }
779 as u32)
780 } else {
781 0
782 },
783 max_compute_workgroups_per_dimension,
784 max_buffer_size: i32::MAX as u64,
785 max_non_sampler_bindings: u32::MAX,
786 };
787
788 let mut workarounds = super::Workarounds::empty();
789
790 workarounds.set(
791 super::Workarounds::EMULATE_BUFFER_MAP,
792 cfg!(any(webgl, Emscripten)),
793 );
794
795 let r = renderer.to_lowercase();
796 if context.is_owned()
799 && r.contains("mesa")
800 && r.contains("intel")
801 && r.split(&[' ', '(', ')'][..])
802 .any(|substr| substr.len() == 3 && substr.chars().nth(2) == Some('l'))
803 {
804 log::warn!(
805 "Detected skylake derivative running on mesa i915. Clears to srgb textures will \
806 use manual shader clears."
807 );
808 workarounds.set(super::Workarounds::MESA_I915_SRGB_SHADER_CLEAR, true);
809 }
810
811 let downlevel_defaults = wgt::DownlevelLimits {};
812 let max_samples = unsafe { gl.get_parameter_i32(glow::MAX_SAMPLES) };
813
814 #[cfg_attr(target_arch = "wasm32", allow(dropping_references))]
818 drop(gl);
819
820 Some(crate::ExposedAdapter {
821 adapter: super::Adapter {
822 shared: Arc::new(super::AdapterShared {
823 context,
824 private_caps,
825 workarounds,
826 features,
827 limits: limits.clone(),
828 shading_language_version,
829 next_shader_id: Default::default(),
830 program_cache: Default::default(),
831 es: es_ver.is_some(),
832 max_msaa_samples: max_samples,
833 }),
834 },
835 info: Self::make_info(vendor, renderer, version),
836 features,
837 capabilities: crate::Capabilities {
838 limits,
839 downlevel: wgt::DownlevelCapabilities {
840 flags: downlevel_flags,
841 limits: downlevel_defaults,
842 shader_model: wgt::ShaderModel::Sm5,
843 },
844 alignments: crate::Alignments {
845 buffer_copy_offset: wgt::BufferSize::new(4).unwrap(),
846 buffer_copy_pitch: wgt::BufferSize::new(4).unwrap(),
847 uniform_bounds_check_alignment: wgt::BufferSize::new(1).unwrap(),
857 raw_tlas_instance_size: 0,
858 ray_tracing_scratch_buffer_alignment: 0,
859 },
860 },
861 })
862 }
863
864 unsafe fn compile_shader(
865 source: &str,
866 gl: &glow::Context,
867 shader_type: u32,
868 es: bool,
869 ) -> Option<glow::Shader> {
870 let source = if es {
871 format!("#version 300 es\nprecision lowp float;\n{source}")
872 } else {
873 let version = gl.version();
874 if version.major == 3 && version.minor == 0 {
875 format!("#version 130\n{source}")
877 } else {
878 format!("#version 140\n{source}")
880 }
881 };
882 let shader = unsafe { gl.create_shader(shader_type) }.expect("Could not create shader");
883 unsafe { gl.shader_source(shader, &source) };
884 unsafe { gl.compile_shader(shader) };
885
886 if !unsafe { gl.get_shader_compile_status(shader) } {
887 let msg = unsafe { gl.get_shader_info_log(shader) };
888 if !msg.is_empty() {
889 log::error!("\tShader compile error: {}", msg);
890 }
891 unsafe { gl.delete_shader(shader) };
892 None
893 } else {
894 Some(shader)
895 }
896 }
897
898 unsafe fn create_shader_clear_program(
899 gl: &glow::Context,
900 es: bool,
901 ) -> Option<ShaderClearProgram> {
902 let program = unsafe { gl.create_program() }.expect("Could not create shader program");
903 let vertex = unsafe {
904 Self::compile_shader(
905 include_str!("./shaders/clear.vert"),
906 gl,
907 glow::VERTEX_SHADER,
908 es,
909 )?
910 };
911 let fragment = unsafe {
912 Self::compile_shader(
913 include_str!("./shaders/clear.frag"),
914 gl,
915 glow::FRAGMENT_SHADER,
916 es,
917 )?
918 };
919 unsafe { gl.attach_shader(program, vertex) };
920 unsafe { gl.attach_shader(program, fragment) };
921 unsafe { gl.link_program(program) };
922
923 let linked_ok = unsafe { gl.get_program_link_status(program) };
924 let msg = unsafe { gl.get_program_info_log(program) };
925 if !msg.is_empty() {
926 log::warn!("Shader link error: {}", msg);
927 }
928 if !linked_ok {
929 return None;
930 }
931
932 let color_uniform_location = unsafe { gl.get_uniform_location(program, "color") }
933 .expect("Could not find color uniform in shader clear shader");
934 unsafe { gl.delete_shader(vertex) };
935 unsafe { gl.delete_shader(fragment) };
936
937 Some(ShaderClearProgram {
938 program,
939 color_uniform_location,
940 })
941 }
942}
943
944impl crate::Adapter for super::Adapter {
945 type A = super::Api;
946
947 unsafe fn open(
948 &self,
949 features: wgt::Features,
950 _limits: &wgt::Limits,
951 _memory_hints: &wgt::MemoryHints,
952 ) -> Result<crate::OpenDevice<super::Api>, crate::DeviceError> {
953 let gl = &self.shared.context.lock();
954 unsafe { gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1) };
955 unsafe { gl.pixel_store_i32(glow::PACK_ALIGNMENT, 1) };
956 let main_vao =
957 unsafe { gl.create_vertex_array() }.map_err(|_| crate::DeviceError::OutOfMemory)?;
958 unsafe { gl.bind_vertex_array(Some(main_vao)) };
959
960 let zero_buffer =
961 unsafe { gl.create_buffer() }.map_err(|_| crate::DeviceError::OutOfMemory)?;
962 unsafe { gl.bind_buffer(glow::COPY_READ_BUFFER, Some(zero_buffer)) };
963 let zeroes = vec![0u8; super::ZERO_BUFFER_SIZE];
964 unsafe { gl.buffer_data_u8_slice(glow::COPY_READ_BUFFER, &zeroes, glow::STATIC_DRAW) };
965
966 let shader_clear_program = if self
970 .shared
971 .workarounds
972 .contains(super::Workarounds::MESA_I915_SRGB_SHADER_CLEAR)
973 {
974 Some(unsafe {
975 Self::create_shader_clear_program(gl, self.shared.es)
976 .ok_or(crate::DeviceError::ResourceCreationFailed)?
977 })
978 } else {
979 None
981 };
982
983 Ok(crate::OpenDevice {
984 device: super::Device {
985 shared: Arc::clone(&self.shared),
986 main_vao,
987 #[cfg(all(native, feature = "renderdoc"))]
988 render_doc: Default::default(),
989 counters: Default::default(),
990 },
991 queue: super::Queue {
992 shared: Arc::clone(&self.shared),
993 features,
994 draw_fbo: unsafe { gl.create_framebuffer() }
995 .map_err(|_| crate::DeviceError::OutOfMemory)?,
996 copy_fbo: unsafe { gl.create_framebuffer() }
997 .map_err(|_| crate::DeviceError::OutOfMemory)?,
998 shader_clear_program,
999 zero_buffer,
1000 temp_query_results: Mutex::new(Vec::new()),
1001 draw_buffer_count: AtomicU8::new(1),
1002 current_index_buffer: Mutex::new(None),
1003 },
1004 })
1005 }
1006
1007 unsafe fn texture_format_capabilities(
1008 &self,
1009 format: wgt::TextureFormat,
1010 ) -> crate::TextureFormatCapabilities {
1011 use crate::TextureFormatCapabilities as Tfc;
1012 use wgt::TextureFormat as Tf;
1013
1014 let sample_count = {
1015 let max_samples = self.shared.max_msaa_samples;
1016 if max_samples >= 16 {
1017 Tfc::MULTISAMPLE_X2
1018 | Tfc::MULTISAMPLE_X4
1019 | Tfc::MULTISAMPLE_X8
1020 | Tfc::MULTISAMPLE_X16
1021 } else if max_samples >= 8 {
1022 Tfc::MULTISAMPLE_X2 | Tfc::MULTISAMPLE_X4 | Tfc::MULTISAMPLE_X8
1023 } else {
1024 Tfc::MULTISAMPLE_X2 | Tfc::MULTISAMPLE_X4
1029 }
1030 };
1031
1032 let empty = Tfc::empty();
1037 let base = Tfc::COPY_SRC | Tfc::COPY_DST;
1038 let unfilterable = base | Tfc::SAMPLED;
1039 let depth = base | Tfc::SAMPLED | sample_count | Tfc::DEPTH_STENCIL_ATTACHMENT;
1040 let filterable = unfilterable | Tfc::SAMPLED_LINEAR;
1041 let renderable =
1042 unfilterable | Tfc::COLOR_ATTACHMENT | sample_count | Tfc::MULTISAMPLE_RESOLVE;
1043 let filterable_renderable = filterable | renderable | Tfc::COLOR_ATTACHMENT_BLEND;
1044 let storage =
1045 base | Tfc::STORAGE_READ_WRITE | Tfc::STORAGE_READ_ONLY | Tfc::STORAGE_WRITE_ONLY;
1046
1047 let feature_fn = |f, caps| {
1048 if self.shared.features.contains(f) {
1049 caps
1050 } else {
1051 empty
1052 }
1053 };
1054
1055 let bcn_features = feature_fn(wgt::Features::TEXTURE_COMPRESSION_BC, filterable);
1056 let etc2_features = feature_fn(wgt::Features::TEXTURE_COMPRESSION_ETC2, filterable);
1057 let astc_features = feature_fn(wgt::Features::TEXTURE_COMPRESSION_ASTC, filterable);
1058 let astc_hdr_features = feature_fn(wgt::Features::TEXTURE_COMPRESSION_ASTC_HDR, filterable);
1059
1060 let private_caps_fn = |f, caps| {
1061 if self.shared.private_caps.contains(f) {
1062 caps
1063 } else {
1064 empty
1065 }
1066 };
1067
1068 let half_float_renderable = private_caps_fn(
1069 super::PrivateCapabilities::COLOR_BUFFER_HALF_FLOAT,
1070 Tfc::COLOR_ATTACHMENT
1071 | Tfc::COLOR_ATTACHMENT_BLEND
1072 | sample_count
1073 | Tfc::MULTISAMPLE_RESOLVE,
1074 );
1075
1076 let float_renderable = private_caps_fn(
1077 super::PrivateCapabilities::COLOR_BUFFER_FLOAT,
1078 Tfc::COLOR_ATTACHMENT
1079 | Tfc::COLOR_ATTACHMENT_BLEND
1080 | sample_count
1081 | Tfc::MULTISAMPLE_RESOLVE,
1082 );
1083
1084 let texture_float_linear = feature_fn(wgt::Features::FLOAT32_FILTERABLE, filterable);
1085
1086 let image_atomic = feature_fn(wgt::Features::TEXTURE_ATOMIC, Tfc::STORAGE_ATOMIC);
1087 let image_64_atomic = feature_fn(wgt::Features::TEXTURE_INT64_ATOMIC, Tfc::STORAGE_ATOMIC);
1088
1089 match format {
1090 Tf::R8Unorm => filterable_renderable,
1091 Tf::R8Snorm => filterable,
1092 Tf::R8Uint => renderable,
1093 Tf::R8Sint => renderable,
1094 Tf::R16Uint => renderable,
1095 Tf::R16Sint => renderable,
1096 Tf::R16Unorm => empty,
1097 Tf::R16Snorm => empty,
1098 Tf::R16Float => filterable | half_float_renderable,
1099 Tf::Rg8Unorm => filterable_renderable,
1100 Tf::Rg8Snorm => filterable,
1101 Tf::Rg8Uint => renderable,
1102 Tf::Rg8Sint => renderable,
1103 Tf::R32Uint => renderable | storage | image_atomic,
1104 Tf::R32Sint => renderable | storage | image_atomic,
1105 Tf::R32Float => unfilterable | storage | float_renderable | texture_float_linear,
1106 Tf::Rg16Uint => renderable,
1107 Tf::Rg16Sint => renderable,
1108 Tf::Rg16Unorm => empty,
1109 Tf::Rg16Snorm => empty,
1110 Tf::Rg16Float => filterable | half_float_renderable,
1111 Tf::Rgba8Unorm => filterable_renderable | storage,
1112 Tf::Rgba8UnormSrgb => filterable_renderable,
1113 Tf::Bgra8Unorm | Tf::Bgra8UnormSrgb => filterable_renderable,
1114 Tf::Rgba8Snorm => filterable | storage,
1115 Tf::Rgba8Uint => renderable | storage,
1116 Tf::Rgba8Sint => renderable | storage,
1117 Tf::Rgb10a2Uint => renderable,
1118 Tf::Rgb10a2Unorm => filterable_renderable,
1119 Tf::Rg11b10Ufloat => filterable | float_renderable,
1120 Tf::R64Uint => image_64_atomic,
1121 Tf::Rg32Uint => renderable,
1122 Tf::Rg32Sint => renderable,
1123 Tf::Rg32Float => unfilterable | float_renderable | texture_float_linear,
1124 Tf::Rgba16Uint => renderable | storage,
1125 Tf::Rgba16Sint => renderable | storage,
1126 Tf::Rgba16Unorm => empty,
1127 Tf::Rgba16Snorm => empty,
1128 Tf::Rgba16Float => filterable | storage | half_float_renderable,
1129 Tf::Rgba32Uint => renderable | storage,
1130 Tf::Rgba32Sint => renderable | storage,
1131 Tf::Rgba32Float => unfilterable | storage | float_renderable | texture_float_linear,
1132 Tf::Stencil8
1133 | Tf::Depth16Unorm
1134 | Tf::Depth32Float
1135 | Tf::Depth32FloatStencil8
1136 | Tf::Depth24Plus
1137 | Tf::Depth24PlusStencil8 => depth,
1138 Tf::NV12 => empty,
1139 Tf::Rgb9e5Ufloat => filterable,
1140 Tf::Bc1RgbaUnorm
1141 | Tf::Bc1RgbaUnormSrgb
1142 | Tf::Bc2RgbaUnorm
1143 | Tf::Bc2RgbaUnormSrgb
1144 | Tf::Bc3RgbaUnorm
1145 | Tf::Bc3RgbaUnormSrgb
1146 | Tf::Bc4RUnorm
1147 | Tf::Bc4RSnorm
1148 | Tf::Bc5RgUnorm
1149 | Tf::Bc5RgSnorm
1150 | Tf::Bc6hRgbFloat
1151 | Tf::Bc6hRgbUfloat
1152 | Tf::Bc7RgbaUnorm
1153 | Tf::Bc7RgbaUnormSrgb => bcn_features,
1154 Tf::Etc2Rgb8Unorm
1155 | Tf::Etc2Rgb8UnormSrgb
1156 | Tf::Etc2Rgb8A1Unorm
1157 | Tf::Etc2Rgb8A1UnormSrgb
1158 | Tf::Etc2Rgba8Unorm
1159 | Tf::Etc2Rgba8UnormSrgb
1160 | Tf::EacR11Unorm
1161 | Tf::EacR11Snorm
1162 | Tf::EacRg11Unorm
1163 | Tf::EacRg11Snorm => etc2_features,
1164 Tf::Astc {
1165 block: _,
1166 channel: AstcChannel::Unorm | AstcChannel::UnormSrgb,
1167 } => astc_features,
1168 Tf::Astc {
1169 block: _,
1170 channel: AstcChannel::Hdr,
1171 } => astc_hdr_features,
1172 }
1173 }
1174
1175 unsafe fn surface_capabilities(
1176 &self,
1177 surface: &super::Surface,
1178 ) -> Option<crate::SurfaceCapabilities> {
1179 #[cfg(webgl)]
1180 if self.shared.context.webgl2_context != surface.webgl2_context {
1181 return None;
1182 }
1183
1184 if surface.presentable {
1185 let mut formats = vec![
1186 wgt::TextureFormat::Rgba8Unorm,
1187 #[cfg(native)]
1188 wgt::TextureFormat::Bgra8Unorm,
1189 ];
1190 if surface.supports_srgb() {
1191 formats.extend([
1192 wgt::TextureFormat::Rgba8UnormSrgb,
1193 #[cfg(native)]
1194 wgt::TextureFormat::Bgra8UnormSrgb,
1195 ])
1196 }
1197 if self
1198 .shared
1199 .private_caps
1200 .contains(super::PrivateCapabilities::COLOR_BUFFER_HALF_FLOAT)
1201 {
1202 formats.push(wgt::TextureFormat::Rgba16Float)
1203 }
1204
1205 Some(crate::SurfaceCapabilities {
1206 formats,
1207 present_modes: if cfg!(windows) {
1208 vec![wgt::PresentMode::Fifo, wgt::PresentMode::Immediate]
1209 } else {
1210 vec![wgt::PresentMode::Fifo] },
1212 composite_alpha_modes: vec![wgt::CompositeAlphaMode::Opaque], maximum_frame_latency: 2..=2, current_extent: None,
1215 usage: crate::TextureUses::COLOR_TARGET,
1216 })
1217 } else {
1218 None
1219 }
1220 }
1221
1222 unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp {
1223 wgt::PresentationTimestamp::INVALID_TIMESTAMP
1224 }
1225}
1226
1227impl super::AdapterShared {
1228 pub(super) unsafe fn get_buffer_sub_data(
1229 &self,
1230 gl: &glow::Context,
1231 target: u32,
1232 offset: i32,
1233 dst_data: &mut [u8],
1234 ) {
1235 if self
1236 .private_caps
1237 .contains(super::PrivateCapabilities::GET_BUFFER_SUB_DATA)
1238 {
1239 unsafe { gl.get_buffer_sub_data(target, offset, dst_data) };
1240 } else {
1241 log::error!("Fake map");
1242 let length = dst_data.len();
1243 let buffer_mapping =
1244 unsafe { gl.map_buffer_range(target, offset, length as _, glow::MAP_READ_BIT) };
1245
1246 unsafe { std::ptr::copy_nonoverlapping(buffer_mapping, dst_data.as_mut_ptr(), length) };
1247
1248 unsafe { gl.unmap_buffer(target) };
1249 }
1250 }
1251}
1252
1253#[cfg(send_sync)]
1254unsafe impl Sync for super::Adapter {}
1255#[cfg(send_sync)]
1256unsafe impl Send for super::Adapter {}
1257
1258#[cfg(test)]
1259mod tests {
1260 use super::super::Adapter;
1261
1262 #[test]
1263 fn test_version_parse() {
1264 Adapter::parse_version("1").unwrap_err();
1265 Adapter::parse_version("1.").unwrap_err();
1266 Adapter::parse_version("1 h3l1o. W0rld").unwrap_err();
1267 Adapter::parse_version("1. h3l1o. W0rld").unwrap_err();
1268 Adapter::parse_version("1.2.3").unwrap_err();
1269
1270 assert_eq!(Adapter::parse_version("OpenGL ES 3.1").unwrap(), (3, 1));
1271 assert_eq!(
1272 Adapter::parse_version("OpenGL ES 2.0 Google Nexus").unwrap(),
1273 (2, 0)
1274 );
1275 assert_eq!(Adapter::parse_version("GLSL ES 1.1").unwrap(), (1, 1));
1276 assert_eq!(
1277 Adapter::parse_version("OpenGL ES GLSL ES 3.20").unwrap(),
1278 (3, 2)
1279 );
1280 assert_eq!(
1281 Adapter::parse_version("WebGL 2.0 (OpenGL ES 3.0 Chromium)").unwrap(),
1283 (3, 0)
1284 );
1285 assert_eq!(
1286 Adapter::parse_version("WebGL GLSL ES 3.00 (OpenGL ES GLSL ES 3.0 Chromium)").unwrap(),
1287 (3, 0)
1288 );
1289 }
1290}