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
376 let mut downlevel_flags = wgt::DownlevelFlags::empty()
377 | wgt::DownlevelFlags::NON_POWER_OF_TWO_MIPMAPPED_TEXTURES
378 | wgt::DownlevelFlags::CUBE_ARRAY_TEXTURES
379 | wgt::DownlevelFlags::COMPARISON_SAMPLERS
380 | wgt::DownlevelFlags::VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW;
381 downlevel_flags.set(wgt::DownlevelFlags::COMPUTE_SHADERS, supports_compute);
382 downlevel_flags.set(
383 wgt::DownlevelFlags::FRAGMENT_WRITABLE_STORAGE,
384 max_storage_block_size != 0,
385 );
386 downlevel_flags.set(
387 wgt::DownlevelFlags::INDIRECT_EXECUTION,
388 supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_multi_draw_indirect"),
389 );
390 downlevel_flags.set(wgt::DownlevelFlags::BASE_VERTEX, supported((3, 2), (3, 2)));
391 downlevel_flags.set(
392 wgt::DownlevelFlags::INDEPENDENT_BLEND,
393 supported((3, 2), (4, 0)) || extensions.contains("GL_EXT_draw_buffers_indexed"),
394 );
395 downlevel_flags.set(
396 wgt::DownlevelFlags::VERTEX_STORAGE,
397 max_storage_block_size != 0
398 && max_storage_buffers_per_shader_stage != 0
399 && (vertex_shader_storage_blocks != 0 || vertex_ssbo_false_zero),
400 );
401 downlevel_flags.set(wgt::DownlevelFlags::FRAGMENT_STORAGE, supports_storage);
402 if extensions.contains("EXT_texture_filter_anisotropic")
403 || extensions.contains("GL_EXT_texture_filter_anisotropic")
404 {
405 let max_aniso =
406 unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_MAX_ANISOTROPY_EXT) } as u32;
407 downlevel_flags.set(wgt::DownlevelFlags::ANISOTROPIC_FILTERING, max_aniso >= 16);
408 }
409 downlevel_flags.set(
410 wgt::DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED,
411 !(cfg!(any(webgl, Emscripten)) || is_angle),
412 );
413 downlevel_flags.set(
415 wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER,
416 !cfg!(any(webgl, Emscripten)),
417 );
418 downlevel_flags.set(
419 wgt::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES,
420 !cfg!(any(webgl, Emscripten)),
421 );
422 downlevel_flags.set(
423 wgt::DownlevelFlags::FULL_DRAW_INDEX_UINT32,
424 max_element_index == u32::MAX,
425 );
426 downlevel_flags.set(
427 wgt::DownlevelFlags::MULTISAMPLED_SHADING,
428 supported((3, 2), (4, 0)) || extensions.contains("OES_sample_variables"),
429 );
430 let query_buffers = extensions.contains("GL_ARB_query_buffer_object")
431 || extensions.contains("GL_AMD_query_buffer_object");
432 if query_buffers {
433 downlevel_flags.set(wgt::DownlevelFlags::NONBLOCKING_QUERY_RESOLVE, true);
434 }
435
436 let mut features = wgt::Features::empty()
437 | wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
438 | wgt::Features::CLEAR_TEXTURE
439 | wgt::Features::PUSH_CONSTANTS
440 | wgt::Features::DEPTH32FLOAT_STENCIL8;
441 features.set(
442 wgt::Features::ADDRESS_MODE_CLAMP_TO_BORDER | wgt::Features::ADDRESS_MODE_CLAMP_TO_ZERO,
443 extensions.contains("GL_EXT_texture_border_clamp")
444 || extensions.contains("GL_ARB_texture_border_clamp"),
445 );
446 features.set(
447 wgt::Features::DEPTH_CLIP_CONTROL,
448 extensions.contains("GL_EXT_depth_clamp") || extensions.contains("GL_ARB_depth_clamp"),
449 );
450 features.set(
451 wgt::Features::VERTEX_WRITABLE_STORAGE,
452 downlevel_flags.contains(wgt::DownlevelFlags::VERTEX_STORAGE)
453 && vertex_shader_storage_textures != 0,
454 );
455 features.set(
456 wgt::Features::MULTIVIEW,
457 extensions.contains("OVR_multiview2") || extensions.contains("GL_OVR_multiview2"),
458 );
459 features.set(
460 wgt::Features::DUAL_SOURCE_BLENDING,
461 extensions.contains("GL_EXT_blend_func_extended")
462 || extensions.contains("GL_ARB_blend_func_extended"),
463 );
464 features.set(
465 wgt::Features::SHADER_PRIMITIVE_INDEX,
466 supported((3, 2), (3, 2))
467 || extensions.contains("OES_geometry_shader")
468 || extensions.contains("GL_ARB_geometry_shader4"),
469 );
470 features.set(
471 wgt::Features::SHADER_EARLY_DEPTH_TEST,
472 supported((3, 1), (4, 2)) || extensions.contains("GL_ARB_shader_image_load_store"),
473 );
474 if extensions.contains("GL_ARB_timer_query") {
475 features.set(wgt::Features::TIMESTAMP_QUERY, true);
476 features.set(wgt::Features::TIMESTAMP_QUERY_INSIDE_ENCODERS, true);
477 features.set(wgt::Features::TIMESTAMP_QUERY_INSIDE_PASSES, true);
478 }
479 let gl_bcn_exts = [
480 "GL_EXT_texture_compression_s3tc",
481 "GL_EXT_texture_compression_rgtc",
482 "GL_ARB_texture_compression_bptc",
483 ];
484 let gles_bcn_exts = [
485 "GL_EXT_texture_compression_s3tc_srgb",
486 "GL_EXT_texture_compression_rgtc",
487 "GL_EXT_texture_compression_bptc",
488 ];
489 let webgl_bcn_exts = [
490 "WEBGL_compressed_texture_s3tc",
491 "WEBGL_compressed_texture_s3tc_srgb",
492 "EXT_texture_compression_rgtc",
493 "EXT_texture_compression_bptc",
494 ];
495 let bcn_exts = if cfg!(any(webgl, Emscripten)) {
496 &webgl_bcn_exts[..]
497 } else if es_ver.is_some() {
498 &gles_bcn_exts[..]
499 } else {
500 &gl_bcn_exts[..]
501 };
502 features.set(
503 wgt::Features::TEXTURE_COMPRESSION_BC,
504 bcn_exts.iter().all(|&ext| extensions.contains(ext)),
505 );
506 features.set(
507 wgt::Features::TEXTURE_COMPRESSION_BC_SLICED_3D,
508 bcn_exts.iter().all(|&ext| extensions.contains(ext)), );
510 let has_etc = if cfg!(any(webgl, Emscripten)) {
511 extensions.contains("WEBGL_compressed_texture_etc")
512 } else {
513 es_ver.is_some() || extensions.contains("GL_ARB_ES3_compatibility")
514 };
515 features.set(wgt::Features::TEXTURE_COMPRESSION_ETC2, has_etc);
516
517 if extensions.contains("WEBGL_compressed_texture_astc")
519 || extensions.contains("GL_OES_texture_compression_astc")
520 {
521 #[cfg(webgl)]
522 {
523 if context
524 .glow_context
525 .compressed_texture_astc_supports_ldr_profile()
526 {
527 features.insert(wgt::Features::TEXTURE_COMPRESSION_ASTC);
528 }
529 if context
530 .glow_context
531 .compressed_texture_astc_supports_hdr_profile()
532 {
533 features.insert(wgt::Features::TEXTURE_COMPRESSION_ASTC_HDR);
534 }
535 }
536
537 #[cfg(any(native, Emscripten))]
538 {
539 features.insert(wgt::Features::TEXTURE_COMPRESSION_ASTC);
540 features.insert(wgt::Features::TEXTURE_COMPRESSION_ASTC_HDR);
541 }
542 } else {
543 features.set(
544 wgt::Features::TEXTURE_COMPRESSION_ASTC,
545 extensions.contains("GL_KHR_texture_compression_astc_ldr"),
546 );
547 features.set(
548 wgt::Features::TEXTURE_COMPRESSION_ASTC_HDR,
549 extensions.contains("GL_KHR_texture_compression_astc_hdr"),
550 );
551 }
552
553 features.set(
554 wgt::Features::FLOAT32_FILTERABLE,
555 extensions.contains("GL_ARB_color_buffer_float")
556 || extensions.contains("GL_EXT_color_buffer_float")
557 || extensions.contains("OES_texture_float_linear"),
558 );
559
560 if es_ver.is_none() {
561 features |= wgt::Features::POLYGON_MODE_LINE | wgt::Features::POLYGON_MODE_POINT;
562 }
563
564 let mut private_caps = super::PrivateCapabilities::empty();
567 private_caps.set(
568 super::PrivateCapabilities::BUFFER_ALLOCATION,
569 extensions.contains("GL_EXT_buffer_storage")
570 || extensions.contains("GL_ARB_buffer_storage"),
571 );
572 private_caps.set(
573 super::PrivateCapabilities::SHADER_BINDING_LAYOUT,
574 supports_compute,
575 );
576 private_caps.set(
577 super::PrivateCapabilities::SHADER_TEXTURE_SHADOW_LOD,
578 extensions.contains("GL_EXT_texture_shadow_lod"),
579 );
580 private_caps.set(
581 super::PrivateCapabilities::MEMORY_BARRIERS,
582 supported((3, 1), (4, 2)),
583 );
584 private_caps.set(
585 super::PrivateCapabilities::VERTEX_BUFFER_LAYOUT,
586 supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_vertex_attrib_binding"),
587 );
588 private_caps.set(
589 super::PrivateCapabilities::INDEX_BUFFER_ROLE_CHANGE,
590 !cfg!(any(webgl, Emscripten)),
591 );
592 private_caps.set(
593 super::PrivateCapabilities::GET_BUFFER_SUB_DATA,
594 cfg!(any(webgl, Emscripten)) || full_ver.is_some(),
595 );
596 let color_buffer_float = extensions.contains("GL_EXT_color_buffer_float")
597 || extensions.contains("GL_ARB_color_buffer_float")
598 || extensions.contains("EXT_color_buffer_float");
599 let color_buffer_half_float = extensions.contains("GL_EXT_color_buffer_half_float")
600 || extensions.contains("GL_ARB_half_float_pixel");
601 private_caps.set(
602 super::PrivateCapabilities::COLOR_BUFFER_HALF_FLOAT,
603 color_buffer_half_float || color_buffer_float,
604 );
605 private_caps.set(
606 super::PrivateCapabilities::COLOR_BUFFER_FLOAT,
607 color_buffer_float,
608 );
609 private_caps.set(super::PrivateCapabilities::QUERY_BUFFERS, query_buffers);
610 private_caps.set(super::PrivateCapabilities::QUERY_64BIT, full_ver.is_some());
611 private_caps.set(
612 super::PrivateCapabilities::TEXTURE_STORAGE,
613 supported((3, 0), (4, 2)),
614 );
615 private_caps.set(super::PrivateCapabilities::DEBUG_FNS, gl.supports_debug());
616 private_caps.set(
617 super::PrivateCapabilities::INVALIDATE_FRAMEBUFFER,
618 supported((3, 0), (4, 3)),
619 );
620 if let Some(full_ver) = full_ver {
621 let supported =
622 full_ver >= (4, 2) && extensions.contains("GL_ARB_shader_draw_parameters");
623 private_caps.set(
624 super::PrivateCapabilities::FULLY_FEATURED_INSTANCING,
625 supported,
626 );
627 features.set(wgt::Features::INDIRECT_FIRST_INSTANCE, supported);
634 }
635
636 let max_texture_size = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as u32;
637 let max_texture_3d_size = unsafe { gl.get_parameter_i32(glow::MAX_3D_TEXTURE_SIZE) } as u32;
638
639 let min_uniform_buffer_offset_alignment =
640 (unsafe { gl.get_parameter_i32(glow::UNIFORM_BUFFER_OFFSET_ALIGNMENT) } as u32);
641 let min_storage_buffer_offset_alignment = if supports_storage {
642 (unsafe { gl.get_parameter_i32(glow::SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT) } as u32)
643 } else {
644 256
645 };
646 let max_uniform_buffers_per_shader_stage =
647 unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_UNIFORM_BLOCKS) }
648 .min(unsafe { gl.get_parameter_i32(glow::MAX_FRAGMENT_UNIFORM_BLOCKS) })
649 as u32;
650
651 let max_compute_workgroups_per_dimension = if supports_work_group_params {
652 unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_COUNT, 0) }
653 .min(unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_COUNT, 1) })
654 .min(unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_COUNT, 2) })
655 as u32
656 } else {
657 0
658 };
659
660 let max_color_attachments = unsafe {
661 gl.get_parameter_i32(glow::MAX_COLOR_ATTACHMENTS)
662 .min(gl.get_parameter_i32(glow::MAX_DRAW_BUFFERS))
663 .min(crate::MAX_COLOR_ATTACHMENTS as i32) as u32
664 };
665
666 let max_color_attachment_bytes_per_sample = 32;
668
669 let limits = wgt::Limits {
670 max_texture_dimension_1d: max_texture_size,
671 max_texture_dimension_2d: max_texture_size,
672 max_texture_dimension_3d: max_texture_3d_size,
673 max_texture_array_layers: unsafe {
674 gl.get_parameter_i32(glow::MAX_ARRAY_TEXTURE_LAYERS)
675 } as u32,
676 max_bind_groups: crate::MAX_BIND_GROUPS as u32,
677 max_bindings_per_bind_group: 65535,
678 max_dynamic_uniform_buffers_per_pipeline_layout: max_uniform_buffers_per_shader_stage,
679 max_dynamic_storage_buffers_per_pipeline_layout: max_storage_buffers_per_shader_stage,
680 max_sampled_textures_per_shader_stage: super::MAX_TEXTURE_SLOTS as u32,
681 max_samplers_per_shader_stage: super::MAX_SAMPLERS as u32,
682 max_storage_buffers_per_shader_stage,
683 max_storage_textures_per_shader_stage,
684 max_uniform_buffers_per_shader_stage,
685 max_uniform_buffer_binding_size: unsafe {
686 gl.get_parameter_i32(glow::MAX_UNIFORM_BLOCK_SIZE)
687 } as u32,
688 max_storage_buffer_binding_size: if supports_storage {
689 unsafe { gl.get_parameter_i32(glow::MAX_SHADER_STORAGE_BLOCK_SIZE) }
690 } else {
691 0
692 } as u32,
693 max_vertex_buffers: if private_caps
694 .contains(super::PrivateCapabilities::VERTEX_BUFFER_LAYOUT)
695 {
696 (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_BINDINGS) } as u32)
697 } else {
698 16 }
700 .min(crate::MAX_VERTEX_BUFFERS as u32),
701 max_vertex_attributes: (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIBS) }
702 as u32)
703 .min(super::MAX_VERTEX_ATTRIBUTES as u32),
704 max_vertex_buffer_array_stride: if private_caps
705 .contains(super::PrivateCapabilities::VERTEX_BUFFER_LAYOUT)
706 {
707 if let Some(full_ver) = full_ver {
708 if full_ver >= (4, 4) {
709 let value =
711 (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_STRIDE) })
712 as u32;
713
714 if value == 0 {
715 log::warn!("Max vertex attribute stride is 0. Assuming it is 2048");
719 2048
720 } else {
721 value
722 }
723 } else {
724 log::warn!("Max vertex attribute stride unknown. Assuming it is 2048");
725 2048
726 }
727 } else {
728 (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_STRIDE) }) as u32
729 }
730 } else {
731 !0
732 },
733 min_subgroup_size: 0,
734 max_subgroup_size: 0,
735 max_push_constant_size: super::MAX_PUSH_CONSTANTS as u32 * 4,
736 min_uniform_buffer_offset_alignment,
737 min_storage_buffer_offset_alignment,
738 max_inter_stage_shader_components: {
739 let max_varying_components =
743 unsafe { gl.get_parameter_i32(glow::MAX_VARYING_COMPONENTS) } as u32;
744 if max_varying_components == 0 {
745 60
747 } else {
748 max_varying_components
749 }
750 },
751 max_color_attachments,
752 max_color_attachment_bytes_per_sample,
753 max_compute_workgroup_storage_size: if supports_work_group_params {
754 (unsafe { gl.get_parameter_i32(glow::MAX_COMPUTE_SHARED_MEMORY_SIZE) } as u32)
755 } else {
756 0
757 },
758 max_compute_invocations_per_workgroup: if supports_work_group_params {
759 (unsafe { gl.get_parameter_i32(glow::MAX_COMPUTE_WORK_GROUP_INVOCATIONS) } as u32)
760 } else {
761 0
762 },
763 max_compute_workgroup_size_x: if supports_work_group_params {
764 (unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_SIZE, 0) }
765 as u32)
766 } else {
767 0
768 },
769 max_compute_workgroup_size_y: if supports_work_group_params {
770 (unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_SIZE, 1) }
771 as u32)
772 } else {
773 0
774 },
775 max_compute_workgroup_size_z: if supports_work_group_params {
776 (unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_SIZE, 2) }
777 as u32)
778 } else {
779 0
780 },
781 max_compute_workgroups_per_dimension,
782 max_buffer_size: i32::MAX as u64,
783 max_non_sampler_bindings: u32::MAX,
784 };
785
786 let mut workarounds = super::Workarounds::empty();
787
788 workarounds.set(
789 super::Workarounds::EMULATE_BUFFER_MAP,
790 cfg!(any(webgl, Emscripten)),
791 );
792
793 let r = renderer.to_lowercase();
794 if context.is_owned()
797 && r.contains("mesa")
798 && r.contains("intel")
799 && r.split(&[' ', '(', ')'][..])
800 .any(|substr| substr.len() == 3 && substr.chars().nth(2) == Some('l'))
801 {
802 log::warn!(
803 "Detected skylake derivative running on mesa i915. Clears to srgb textures will \
804 use manual shader clears."
805 );
806 workarounds.set(super::Workarounds::MESA_I915_SRGB_SHADER_CLEAR, true);
807 }
808
809 let downlevel_defaults = wgt::DownlevelLimits {};
810 let max_samples = unsafe { gl.get_parameter_i32(glow::MAX_SAMPLES) };
811
812 #[cfg_attr(target_arch = "wasm32", allow(dropping_references))]
816 drop(gl);
817
818 Some(crate::ExposedAdapter {
819 adapter: super::Adapter {
820 shared: Arc::new(super::AdapterShared {
821 context,
822 private_caps,
823 workarounds,
824 features,
825 shading_language_version,
826 next_shader_id: Default::default(),
827 program_cache: Default::default(),
828 es: es_ver.is_some(),
829 max_msaa_samples: max_samples,
830 }),
831 },
832 info: Self::make_info(vendor, renderer, version),
833 features,
834 capabilities: crate::Capabilities {
835 limits,
836 downlevel: wgt::DownlevelCapabilities {
837 flags: downlevel_flags,
838 limits: downlevel_defaults,
839 shader_model: wgt::ShaderModel::Sm5,
840 },
841 alignments: crate::Alignments {
842 buffer_copy_offset: wgt::BufferSize::new(4).unwrap(),
843 buffer_copy_pitch: wgt::BufferSize::new(4).unwrap(),
844 uniform_bounds_check_alignment: wgt::BufferSize::new(1).unwrap(),
854 },
855 },
856 })
857 }
858
859 unsafe fn compile_shader(
860 source: &str,
861 gl: &glow::Context,
862 shader_type: u32,
863 es: bool,
864 ) -> Option<glow::Shader> {
865 let source = if es {
866 format!("#version 300 es\nprecision lowp float;\n{source}")
867 } else {
868 let version = gl.version();
869 if version.major == 3 && version.minor == 0 {
870 format!("#version 130\n{source}")
872 } else {
873 format!("#version 140\n{source}")
875 }
876 };
877 let shader = unsafe { gl.create_shader(shader_type) }.expect("Could not create shader");
878 unsafe { gl.shader_source(shader, &source) };
879 unsafe { gl.compile_shader(shader) };
880
881 if !unsafe { gl.get_shader_compile_status(shader) } {
882 let msg = unsafe { gl.get_shader_info_log(shader) };
883 if !msg.is_empty() {
884 log::error!("\tShader compile error: {}", msg);
885 }
886 unsafe { gl.delete_shader(shader) };
887 None
888 } else {
889 Some(shader)
890 }
891 }
892
893 unsafe fn create_shader_clear_program(
894 gl: &glow::Context,
895 es: bool,
896 ) -> Option<ShaderClearProgram> {
897 let program = unsafe { gl.create_program() }.expect("Could not create shader program");
898 let vertex = unsafe {
899 Self::compile_shader(
900 include_str!("./shaders/clear.vert"),
901 gl,
902 glow::VERTEX_SHADER,
903 es,
904 )?
905 };
906 let fragment = unsafe {
907 Self::compile_shader(
908 include_str!("./shaders/clear.frag"),
909 gl,
910 glow::FRAGMENT_SHADER,
911 es,
912 )?
913 };
914 unsafe { gl.attach_shader(program, vertex) };
915 unsafe { gl.attach_shader(program, fragment) };
916 unsafe { gl.link_program(program) };
917
918 let linked_ok = unsafe { gl.get_program_link_status(program) };
919 let msg = unsafe { gl.get_program_info_log(program) };
920 if !msg.is_empty() {
921 log::warn!("Shader link error: {}", msg);
922 }
923 if !linked_ok {
924 return None;
925 }
926
927 let color_uniform_location = unsafe { gl.get_uniform_location(program, "color") }
928 .expect("Could not find color uniform in shader clear shader");
929 unsafe { gl.delete_shader(vertex) };
930 unsafe { gl.delete_shader(fragment) };
931
932 Some(ShaderClearProgram {
933 program,
934 color_uniform_location,
935 })
936 }
937}
938
939impl crate::Adapter for super::Adapter {
940 type A = super::Api;
941
942 unsafe fn open(
943 &self,
944 features: wgt::Features,
945 _limits: &wgt::Limits,
946 _memory_hints: &wgt::MemoryHints,
947 ) -> Result<crate::OpenDevice<super::Api>, crate::DeviceError> {
948 let gl = &self.shared.context.lock();
949 unsafe { gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1) };
950 unsafe { gl.pixel_store_i32(glow::PACK_ALIGNMENT, 1) };
951 let main_vao =
952 unsafe { gl.create_vertex_array() }.map_err(|_| crate::DeviceError::OutOfMemory)?;
953 unsafe { gl.bind_vertex_array(Some(main_vao)) };
954
955 let zero_buffer =
956 unsafe { gl.create_buffer() }.map_err(|_| crate::DeviceError::OutOfMemory)?;
957 unsafe { gl.bind_buffer(glow::COPY_READ_BUFFER, Some(zero_buffer)) };
958 let zeroes = vec![0u8; super::ZERO_BUFFER_SIZE];
959 unsafe { gl.buffer_data_u8_slice(glow::COPY_READ_BUFFER, &zeroes, glow::STATIC_DRAW) };
960
961 let shader_clear_program = if self
965 .shared
966 .workarounds
967 .contains(super::Workarounds::MESA_I915_SRGB_SHADER_CLEAR)
968 {
969 Some(unsafe {
970 Self::create_shader_clear_program(gl, self.shared.es)
971 .ok_or(crate::DeviceError::ResourceCreationFailed)?
972 })
973 } else {
974 None
976 };
977
978 Ok(crate::OpenDevice {
979 device: super::Device {
980 shared: Arc::clone(&self.shared),
981 main_vao,
982 #[cfg(all(native, feature = "renderdoc"))]
983 render_doc: Default::default(),
984 counters: Default::default(),
985 },
986 queue: super::Queue {
987 shared: Arc::clone(&self.shared),
988 features,
989 draw_fbo: unsafe { gl.create_framebuffer() }
990 .map_err(|_| crate::DeviceError::OutOfMemory)?,
991 copy_fbo: unsafe { gl.create_framebuffer() }
992 .map_err(|_| crate::DeviceError::OutOfMemory)?,
993 shader_clear_program,
994 zero_buffer,
995 temp_query_results: Mutex::new(Vec::new()),
996 draw_buffer_count: AtomicU8::new(1),
997 current_index_buffer: Mutex::new(None),
998 },
999 })
1000 }
1001
1002 unsafe fn texture_format_capabilities(
1003 &self,
1004 format: wgt::TextureFormat,
1005 ) -> crate::TextureFormatCapabilities {
1006 use crate::TextureFormatCapabilities as Tfc;
1007 use wgt::TextureFormat as Tf;
1008
1009 let sample_count = {
1010 let max_samples = self.shared.max_msaa_samples;
1011 if max_samples >= 16 {
1012 Tfc::MULTISAMPLE_X2
1013 | Tfc::MULTISAMPLE_X4
1014 | Tfc::MULTISAMPLE_X8
1015 | Tfc::MULTISAMPLE_X16
1016 } else if max_samples >= 8 {
1017 Tfc::MULTISAMPLE_X2 | Tfc::MULTISAMPLE_X4 | Tfc::MULTISAMPLE_X8
1018 } else {
1019 Tfc::MULTISAMPLE_X2 | Tfc::MULTISAMPLE_X4
1024 }
1025 };
1026
1027 let empty = Tfc::empty();
1032 let base = Tfc::COPY_SRC | Tfc::COPY_DST;
1033 let unfilterable = base | Tfc::SAMPLED;
1034 let depth = base | Tfc::SAMPLED | sample_count | Tfc::DEPTH_STENCIL_ATTACHMENT;
1035 let filterable = unfilterable | Tfc::SAMPLED_LINEAR;
1036 let renderable =
1037 unfilterable | Tfc::COLOR_ATTACHMENT | sample_count | Tfc::MULTISAMPLE_RESOLVE;
1038 let filterable_renderable = filterable | renderable | Tfc::COLOR_ATTACHMENT_BLEND;
1039 let storage = base | Tfc::STORAGE | Tfc::STORAGE_READ_WRITE;
1040
1041 let feature_fn = |f, caps| {
1042 if self.shared.features.contains(f) {
1043 caps
1044 } else {
1045 empty
1046 }
1047 };
1048
1049 let bcn_features = feature_fn(wgt::Features::TEXTURE_COMPRESSION_BC, filterable);
1050 let etc2_features = feature_fn(wgt::Features::TEXTURE_COMPRESSION_ETC2, filterable);
1051 let astc_features = feature_fn(wgt::Features::TEXTURE_COMPRESSION_ASTC, filterable);
1052 let astc_hdr_features = feature_fn(wgt::Features::TEXTURE_COMPRESSION_ASTC_HDR, filterable);
1053
1054 let private_caps_fn = |f, caps| {
1055 if self.shared.private_caps.contains(f) {
1056 caps
1057 } else {
1058 empty
1059 }
1060 };
1061
1062 let half_float_renderable = private_caps_fn(
1063 super::PrivateCapabilities::COLOR_BUFFER_HALF_FLOAT,
1064 Tfc::COLOR_ATTACHMENT
1065 | Tfc::COLOR_ATTACHMENT_BLEND
1066 | sample_count
1067 | Tfc::MULTISAMPLE_RESOLVE,
1068 );
1069
1070 let float_renderable = private_caps_fn(
1071 super::PrivateCapabilities::COLOR_BUFFER_FLOAT,
1072 Tfc::COLOR_ATTACHMENT
1073 | Tfc::COLOR_ATTACHMENT_BLEND
1074 | sample_count
1075 | Tfc::MULTISAMPLE_RESOLVE,
1076 );
1077
1078 let texture_float_linear = feature_fn(wgt::Features::FLOAT32_FILTERABLE, filterable);
1079
1080 match format {
1081 Tf::R8Unorm => filterable_renderable,
1082 Tf::R8Snorm => filterable,
1083 Tf::R8Uint => renderable,
1084 Tf::R8Sint => renderable,
1085 Tf::R16Uint => renderable,
1086 Tf::R16Sint => renderable,
1087 Tf::R16Unorm => empty,
1088 Tf::R16Snorm => empty,
1089 Tf::R16Float => filterable | half_float_renderable,
1090 Tf::Rg8Unorm => filterable_renderable,
1091 Tf::Rg8Snorm => filterable,
1092 Tf::Rg8Uint => renderable,
1093 Tf::Rg8Sint => renderable,
1094 Tf::R32Uint => renderable | storage,
1095 Tf::R32Sint => renderable | storage,
1096 Tf::R32Float => unfilterable | storage | float_renderable | texture_float_linear,
1097 Tf::Rg16Uint => renderable,
1098 Tf::Rg16Sint => renderable,
1099 Tf::Rg16Unorm => empty,
1100 Tf::Rg16Snorm => empty,
1101 Tf::Rg16Float => filterable | half_float_renderable,
1102 Tf::Rgba8Unorm => filterable_renderable | storage,
1103 Tf::Rgba8UnormSrgb => filterable_renderable,
1104 Tf::Bgra8Unorm | Tf::Bgra8UnormSrgb => filterable_renderable,
1105 Tf::Rgba8Snorm => filterable | storage,
1106 Tf::Rgba8Uint => renderable | storage,
1107 Tf::Rgba8Sint => renderable | storage,
1108 Tf::Rgb10a2Uint => renderable,
1109 Tf::Rgb10a2Unorm => filterable_renderable,
1110 Tf::Rg11b10Ufloat => filterable | float_renderable,
1111 Tf::Rg32Uint => renderable,
1112 Tf::Rg32Sint => renderable,
1113 Tf::Rg32Float => unfilterable | float_renderable | texture_float_linear,
1114 Tf::Rgba16Uint => renderable | storage,
1115 Tf::Rgba16Sint => renderable | storage,
1116 Tf::Rgba16Unorm => empty,
1117 Tf::Rgba16Snorm => empty,
1118 Tf::Rgba16Float => filterable | storage | half_float_renderable,
1119 Tf::Rgba32Uint => renderable | storage,
1120 Tf::Rgba32Sint => renderable | storage,
1121 Tf::Rgba32Float => unfilterable | storage | float_renderable | texture_float_linear,
1122 Tf::Stencil8
1123 | Tf::Depth16Unorm
1124 | Tf::Depth32Float
1125 | Tf::Depth32FloatStencil8
1126 | Tf::Depth24Plus
1127 | Tf::Depth24PlusStencil8 => depth,
1128 Tf::NV12 => empty,
1129 Tf::Rgb9e5Ufloat => filterable,
1130 Tf::Bc1RgbaUnorm
1131 | Tf::Bc1RgbaUnormSrgb
1132 | Tf::Bc2RgbaUnorm
1133 | Tf::Bc2RgbaUnormSrgb
1134 | Tf::Bc3RgbaUnorm
1135 | Tf::Bc3RgbaUnormSrgb
1136 | Tf::Bc4RUnorm
1137 | Tf::Bc4RSnorm
1138 | Tf::Bc5RgUnorm
1139 | Tf::Bc5RgSnorm
1140 | Tf::Bc6hRgbFloat
1141 | Tf::Bc6hRgbUfloat
1142 | Tf::Bc7RgbaUnorm
1143 | Tf::Bc7RgbaUnormSrgb => bcn_features,
1144 Tf::Etc2Rgb8Unorm
1145 | Tf::Etc2Rgb8UnormSrgb
1146 | Tf::Etc2Rgb8A1Unorm
1147 | Tf::Etc2Rgb8A1UnormSrgb
1148 | Tf::Etc2Rgba8Unorm
1149 | Tf::Etc2Rgba8UnormSrgb
1150 | Tf::EacR11Unorm
1151 | Tf::EacR11Snorm
1152 | Tf::EacRg11Unorm
1153 | Tf::EacRg11Snorm => etc2_features,
1154 Tf::Astc {
1155 block: _,
1156 channel: AstcChannel::Unorm | AstcChannel::UnormSrgb,
1157 } => astc_features,
1158 Tf::Astc {
1159 block: _,
1160 channel: AstcChannel::Hdr,
1161 } => astc_hdr_features,
1162 }
1163 }
1164
1165 unsafe fn surface_capabilities(
1166 &self,
1167 surface: &super::Surface,
1168 ) -> Option<crate::SurfaceCapabilities> {
1169 #[cfg(webgl)]
1170 if self.shared.context.webgl2_context != surface.webgl2_context {
1171 return None;
1172 }
1173
1174 if surface.presentable {
1175 let mut formats = vec![
1176 wgt::TextureFormat::Rgba8Unorm,
1177 #[cfg(native)]
1178 wgt::TextureFormat::Bgra8Unorm,
1179 ];
1180 if surface.supports_srgb() {
1181 formats.extend([
1182 wgt::TextureFormat::Rgba8UnormSrgb,
1183 #[cfg(native)]
1184 wgt::TextureFormat::Bgra8UnormSrgb,
1185 ])
1186 }
1187 if self
1188 .shared
1189 .private_caps
1190 .contains(super::PrivateCapabilities::COLOR_BUFFER_HALF_FLOAT)
1191 {
1192 formats.push(wgt::TextureFormat::Rgba16Float)
1193 }
1194
1195 Some(crate::SurfaceCapabilities {
1196 formats,
1197 present_modes: if cfg!(windows) {
1198 vec![wgt::PresentMode::Fifo, wgt::PresentMode::Immediate]
1199 } else {
1200 vec![wgt::PresentMode::Fifo] },
1202 composite_alpha_modes: vec![wgt::CompositeAlphaMode::Opaque], maximum_frame_latency: 2..=2, current_extent: None,
1205 usage: crate::TextureUses::COLOR_TARGET,
1206 })
1207 } else {
1208 None
1209 }
1210 }
1211
1212 unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp {
1213 wgt::PresentationTimestamp::INVALID_TIMESTAMP
1214 }
1215}
1216
1217impl super::AdapterShared {
1218 pub(super) unsafe fn get_buffer_sub_data(
1219 &self,
1220 gl: &glow::Context,
1221 target: u32,
1222 offset: i32,
1223 dst_data: &mut [u8],
1224 ) {
1225 if self
1226 .private_caps
1227 .contains(super::PrivateCapabilities::GET_BUFFER_SUB_DATA)
1228 {
1229 unsafe { gl.get_buffer_sub_data(target, offset, dst_data) };
1230 } else {
1231 log::error!("Fake map");
1232 let length = dst_data.len();
1233 let buffer_mapping =
1234 unsafe { gl.map_buffer_range(target, offset, length as _, glow::MAP_READ_BIT) };
1235
1236 unsafe { std::ptr::copy_nonoverlapping(buffer_mapping, dst_data.as_mut_ptr(), length) };
1237
1238 unsafe { gl.unmap_buffer(target) };
1239 }
1240 }
1241}
1242
1243#[cfg(send_sync)]
1244unsafe impl Sync for super::Adapter {}
1245#[cfg(send_sync)]
1246unsafe impl Send for super::Adapter {}
1247
1248#[cfg(test)]
1249mod tests {
1250 use super::super::Adapter;
1251
1252 #[test]
1253 fn test_version_parse() {
1254 Adapter::parse_version("1").unwrap_err();
1255 Adapter::parse_version("1.").unwrap_err();
1256 Adapter::parse_version("1 h3l1o. W0rld").unwrap_err();
1257 Adapter::parse_version("1. h3l1o. W0rld").unwrap_err();
1258 Adapter::parse_version("1.2.3").unwrap_err();
1259
1260 assert_eq!(Adapter::parse_version("OpenGL ES 3.1").unwrap(), (3, 1));
1261 assert_eq!(
1262 Adapter::parse_version("OpenGL ES 2.0 Google Nexus").unwrap(),
1263 (2, 0)
1264 );
1265 assert_eq!(Adapter::parse_version("GLSL ES 1.1").unwrap(), (1, 1));
1266 assert_eq!(
1267 Adapter::parse_version("OpenGL ES GLSL ES 3.20").unwrap(),
1268 (3, 2)
1269 );
1270 assert_eq!(
1271 Adapter::parse_version("WebGL 2.0 (OpenGL ES 3.0 Chromium)").unwrap(),
1273 (3, 0)
1274 );
1275 assert_eq!(
1276 Adapter::parse_version("WebGL GLSL ES 3.00 (OpenGL ES GLSL ES 3.0 Chromium)").unwrap(),
1277 (3, 0)
1278 );
1279 }
1280}