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