li_wgpu_hal/gles/
adapter.rs

1use glow::HasContext;
2use std::sync::Arc;
3use wgt::AstcChannel;
4
5use crate::auxil::db;
6
7// https://webgl2fundamentals.org/webgl/lessons/webgl-data-textures.html
8
9const GL_UNMASKED_VENDOR_WEBGL: u32 = 0x9245;
10const GL_UNMASKED_RENDERER_WEBGL: u32 = 0x9246;
11
12impl super::Adapter {
13    /// Note that this function is intentionally lenient in regards to parsing,
14    /// and will try to recover at least the first two version numbers without
15    /// resulting in an `Err`.
16    /// # Notes
17    /// `WebGL 2` version returned as `OpenGL ES 3.0`
18    fn parse_version(mut src: &str) -> Result<(u8, u8), crate::InstanceError> {
19        let webgl_sig = "WebGL ";
20        // According to the WebGL specification
21        // VERSION  WebGL<space>1.0<space><vendor-specific information>
22        // SHADING_LANGUAGE_VERSION WebGL<space>GLSL<space>ES<space>1.0<space><vendor-specific information>
23        let is_webgl = src.starts_with(webgl_sig);
24        if is_webgl {
25            let pos = src.rfind(webgl_sig).unwrap_or(0);
26            src = &src[pos + webgl_sig.len()..];
27        } else {
28            let es_sig = " ES ";
29            match src.rfind(es_sig) {
30                Some(pos) => {
31                    src = &src[pos + es_sig.len()..];
32                }
33                None => {
34                    return Err(crate::InstanceError::new(format!(
35                        "OpenGL version {src:?} does not contain 'ES'"
36                    )));
37                }
38            }
39        };
40
41        let glsl_es_sig = "GLSL ES ";
42        let is_glsl = match src.find(glsl_es_sig) {
43            Some(pos) => {
44                src = &src[pos + glsl_es_sig.len()..];
45                true
46            }
47            None => false,
48        };
49
50        Self::parse_full_version(src).map(|(major, minor)| {
51            (
52                // Return WebGL 2.0 version as OpenGL ES 3.0
53                if is_webgl && !is_glsl {
54                    major + 1
55                } else {
56                    major
57                },
58                minor,
59            )
60        })
61    }
62
63    /// According to the OpenGL specification, the version information is
64    /// expected to follow the following syntax:
65    ///
66    /// ~~~bnf
67    /// <major>       ::= <number>
68    /// <minor>       ::= <number>
69    /// <revision>    ::= <number>
70    /// <vendor-info> ::= <string>
71    /// <release>     ::= <major> "." <minor> ["." <release>]
72    /// <version>     ::= <release> [" " <vendor-info>]
73    /// ~~~
74    ///
75    /// Note that this function is intentionally lenient in regards to parsing,
76    /// and will try to recover at least the first two version numbers without
77    /// resulting in an `Err`.
78    pub(super) fn parse_full_version(src: &str) -> Result<(u8, u8), crate::InstanceError> {
79        let (version, _vendor_info) = match src.find(' ') {
80            Some(i) => (&src[..i], src[i + 1..].to_string()),
81            None => (src, String::new()),
82        };
83
84        // TODO: make this even more lenient so that we can also accept
85        // `<major> "." <minor> [<???>]`
86        let mut it = version.split('.');
87        let major = it.next().and_then(|s| s.parse().ok());
88        let minor = it.next().and_then(|s| {
89            let trimmed = if s.starts_with('0') {
90                "0"
91            } else {
92                s.trim_end_matches('0')
93            };
94            trimmed.parse().ok()
95        });
96
97        match (major, minor) {
98            (Some(major), Some(minor)) => Ok((major, minor)),
99            _ => Err(crate::InstanceError::new(format!(
100                "unable to extract OpenGL version from {version:?}"
101            ))),
102        }
103    }
104
105    fn make_info(vendor_orig: String, renderer_orig: String) -> wgt::AdapterInfo {
106        let vendor = vendor_orig.to_lowercase();
107        let renderer = renderer_orig.to_lowercase();
108
109        // opengl has no way to discern device_type, so we can try to infer it from the renderer string
110        let strings_that_imply_integrated = [
111            " xpress", // space here is on purpose so we don't match express
112            "amd renoir",
113            "radeon hd 4200",
114            "radeon hd 4250",
115            "radeon hd 4290",
116            "radeon hd 4270",
117            "radeon hd 4225",
118            "radeon hd 3100",
119            "radeon hd 3200",
120            "radeon hd 3000",
121            "radeon hd 3300",
122            "radeon(tm) r4 graphics",
123            "radeon(tm) r5 graphics",
124            "radeon(tm) r6 graphics",
125            "radeon(tm) r7 graphics",
126            "radeon r7 graphics",
127            "nforce", // all nvidia nforce are integrated
128            "tegra",  // all nvidia tegra are integrated
129            "shield", // all nvidia shield are integrated
130            "igp",
131            "mali",
132            "intel",
133            "v3d",
134            "apple m", // all apple m are integrated
135        ];
136        let strings_that_imply_cpu = ["mesa offscreen", "swiftshader", "llvmpipe"];
137
138        //TODO: handle Intel Iris XE as discreet
139        let inferred_device_type = if vendor.contains("qualcomm")
140            || vendor.contains("intel")
141            || strings_that_imply_integrated
142                .iter()
143                .any(|&s| renderer.contains(s))
144        {
145            wgt::DeviceType::IntegratedGpu
146        } else if strings_that_imply_cpu.iter().any(|&s| renderer.contains(s)) {
147            wgt::DeviceType::Cpu
148        } else {
149            // At this point the Device type is Unknown.
150            // It's most likely DiscreteGpu, but we do not know for sure.
151            // Use "Other" to avoid possibly making incorrect assumptions.
152            // Note that if this same device is available under some other API (ex: Vulkan),
153            // It will mostly likely get a different device type (probably DiscreteGpu).
154            wgt::DeviceType::Other
155        };
156
157        // source: Sascha Willems at Vulkan
158        let vendor_id = if vendor.contains("amd") {
159            db::amd::VENDOR
160        } else if vendor.contains("imgtec") {
161            db::imgtec::VENDOR
162        } else if vendor.contains("nvidia") {
163            db::nvidia::VENDOR
164        } else if vendor.contains("arm") {
165            db::arm::VENDOR
166        } else if vendor.contains("qualcomm") {
167            db::qualcomm::VENDOR
168        } else if vendor.contains("intel") {
169            db::intel::VENDOR
170        } else if vendor.contains("broadcom") {
171            db::broadcom::VENDOR
172        } else if vendor.contains("mesa") {
173            db::mesa::VENDOR
174        } else if vendor.contains("apple") {
175            db::apple::VENDOR
176        } else {
177            0
178        };
179
180        wgt::AdapterInfo {
181            name: renderer_orig,
182            vendor: vendor_id,
183            device: 0,
184            device_type: inferred_device_type,
185            driver: String::new(),
186            driver_info: String::new(),
187            backend: wgt::Backend::Gl,
188        }
189    }
190
191    pub(super) unsafe fn expose(
192        context: super::AdapterContext,
193    ) -> Option<crate::ExposedAdapter<super::Api>> {
194        let gl = context.lock();
195        let extensions = gl.supported_extensions();
196
197        let (vendor_const, renderer_const) = if extensions.contains("WEBGL_debug_renderer_info") {
198            // emscripten doesn't enable "WEBGL_debug_renderer_info" extension by default. so, we do it manually.
199            // See https://github.com/gfx-rs/wgpu/issues/3245 for context
200            #[cfg(target_os = "emscripten")]
201            if unsafe { super::emscripten::enable_extension("WEBGL_debug_renderer_info\0") } {
202                (GL_UNMASKED_VENDOR_WEBGL, GL_UNMASKED_RENDERER_WEBGL)
203            } else {
204                (glow::VENDOR, glow::RENDERER)
205            }
206            // glow already enables WEBGL_debug_renderer_info on wasm32-unknown-unknown target by default.
207            #[cfg(not(target_os = "emscripten"))]
208            (GL_UNMASKED_VENDOR_WEBGL, GL_UNMASKED_RENDERER_WEBGL)
209        } else {
210            (glow::VENDOR, glow::RENDERER)
211        };
212
213        let vendor = unsafe { gl.get_parameter_string(vendor_const) };
214        let renderer = unsafe { gl.get_parameter_string(renderer_const) };
215        let version = unsafe { gl.get_parameter_string(glow::VERSION) };
216        log::trace!("Vendor: {}", vendor);
217        log::trace!("Renderer: {}", renderer);
218        log::trace!("Version: {}", version);
219
220        let full_ver = Self::parse_full_version(&version).ok();
221        let es_ver = full_ver
222            .is_none()
223            .then_some(())
224            .and_then(|_| Self::parse_version(&version).ok());
225
226        if es_ver.is_none() && full_ver.is_none() {
227            log::warn!("Unable to parse OpenGL version");
228            return None;
229        }
230
231        if let Some(es_ver) = es_ver {
232            if es_ver < (3, 0) {
233                log::warn!(
234                    "Returned GLES context is {}.{}, when 3.0+ was requested",
235                    es_ver.0,
236                    es_ver.1
237                );
238                return None;
239            }
240        }
241
242        if let Some(full_ver) = full_ver {
243            if full_ver < (3, 3) {
244                log::warn!(
245                    "Returned GL context is {}.{}, when 3.3+ is needed",
246                    full_ver.0,
247                    full_ver.1
248                );
249                return None;
250            }
251        }
252
253        let shading_language_version = {
254            let sl_version = unsafe { gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) };
255            log::trace!("SL version: {}", &sl_version);
256            if full_ver.is_some() {
257                let (sl_major, sl_minor) = Self::parse_full_version(&sl_version).ok()?;
258                let mut value = sl_major as u16 * 100 + sl_minor as u16 * 10;
259                // Naga doesn't think it supports GL 460+, so we cap it at 450
260                if value > 450 {
261                    value = 450;
262                }
263                naga::back::glsl::Version::Desktop(value)
264            } else {
265                let (sl_major, sl_minor) = Self::parse_version(&sl_version).ok()?;
266                let value = sl_major as u16 * 100 + sl_minor as u16 * 10;
267                naga::back::glsl::Version::Embedded {
268                    version: value,
269                    is_webgl: cfg!(target_arch = "wasm32"),
270                }
271            }
272        };
273
274        log::trace!("Supported GL Extensions: {:#?}", extensions);
275
276        let supported = |(req_es_major, req_es_minor), (req_full_major, req_full_minor)| {
277            let es_supported = es_ver
278                .map(|es_ver| es_ver >= (req_es_major, req_es_minor))
279                .unwrap_or_default();
280
281            let full_supported = full_ver
282                .map(|full_ver| full_ver >= (req_full_major, req_full_minor))
283                .unwrap_or_default();
284
285            es_supported || full_supported
286        };
287
288        let supports_storage =
289            supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_shader_storage_buffer_object");
290        let supports_compute =
291            supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_compute_shader");
292        let supports_work_group_params = supports_compute;
293
294        // ANGLE provides renderer strings like: "ANGLE (Apple, Apple M1 Pro, OpenGL 4.1)"
295        let is_angle = renderer.contains("ANGLE");
296
297        let vertex_shader_storage_blocks = if supports_storage {
298            let value =
299                (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_SHADER_STORAGE_BLOCKS) } as u32);
300
301            if value == 0 && extensions.contains("GL_ARB_shader_storage_buffer_object") {
302                // The driver for AMD Radeon HD 5870 returns zero here, so assume the value matches the compute shader storage block count.
303                // Windows doesn't recognize `GL_MAX_VERTEX_ATTRIB_STRIDE`.
304                let new = (unsafe { gl.get_parameter_i32(glow::MAX_COMPUTE_SHADER_STORAGE_BLOCKS) }
305                    as u32);
306                log::warn!("Max vertex shader storage blocks is zero, but GL_ARB_shader_storage_buffer_object is specified. Assuming the compute value {new}");
307                new
308            } else {
309                value
310            }
311        } else {
312            0
313        };
314        let fragment_shader_storage_blocks = if supports_storage {
315            (unsafe { gl.get_parameter_i32(glow::MAX_FRAGMENT_SHADER_STORAGE_BLOCKS) } as u32)
316        } else {
317            0
318        };
319        let vertex_shader_storage_textures = if supports_storage {
320            (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_IMAGE_UNIFORMS) } as u32)
321        } else {
322            0
323        };
324        let fragment_shader_storage_textures = if supports_storage {
325            (unsafe { gl.get_parameter_i32(glow::MAX_FRAGMENT_IMAGE_UNIFORMS) } as u32)
326        } else {
327            0
328        };
329        let max_storage_block_size = if supports_storage {
330            (unsafe { gl.get_parameter_i32(glow::MAX_SHADER_STORAGE_BLOCK_SIZE) } as u32)
331        } else {
332            0
333        };
334        let max_element_index = unsafe { gl.get_parameter_i32(glow::MAX_ELEMENT_INDEX) } as u32;
335
336        // WORKAROUND: In order to work around an issue with GL on RPI4 and similar, we ignore a
337        // zero vertex ssbo count if there are vertex sstos. (more info:
338        // https://github.com/gfx-rs/wgpu/pull/1607#issuecomment-874938961) The hardware does not
339        // want us to write to these SSBOs, but GLES cannot express that. We detect this case and
340        // disable writing to SSBOs.
341        let vertex_ssbo_false_zero =
342            vertex_shader_storage_blocks == 0 && vertex_shader_storage_textures != 0;
343        if vertex_ssbo_false_zero {
344            // We only care about fragment here as the 0 is a lie.
345            log::warn!("Max vertex shader SSBO == 0 and SSTO != 0. Interpreting as false zero.");
346        }
347
348        let max_storage_buffers_per_shader_stage = if vertex_shader_storage_blocks == 0 {
349            fragment_shader_storage_blocks
350        } else {
351            vertex_shader_storage_blocks.min(fragment_shader_storage_blocks)
352        };
353        let max_storage_textures_per_shader_stage = if vertex_shader_storage_textures == 0 {
354            fragment_shader_storage_textures
355        } else {
356            vertex_shader_storage_textures.min(fragment_shader_storage_textures)
357        };
358
359        let mut downlevel_flags = wgt::DownlevelFlags::empty()
360            | wgt::DownlevelFlags::NON_POWER_OF_TWO_MIPMAPPED_TEXTURES
361            | wgt::DownlevelFlags::CUBE_ARRAY_TEXTURES
362            | wgt::DownlevelFlags::COMPARISON_SAMPLERS;
363        downlevel_flags.set(wgt::DownlevelFlags::COMPUTE_SHADERS, supports_compute);
364        downlevel_flags.set(
365            wgt::DownlevelFlags::FRAGMENT_WRITABLE_STORAGE,
366            max_storage_block_size != 0,
367        );
368        downlevel_flags.set(
369            wgt::DownlevelFlags::INDIRECT_EXECUTION,
370            supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_multi_draw_indirect"),
371        );
372        //TODO: we can actually support positive `base_vertex` in the same way
373        // as we emulate the `start_instance`. But we can't deal with negatives...
374        downlevel_flags.set(wgt::DownlevelFlags::BASE_VERTEX, supported((3, 2), (3, 2)));
375        downlevel_flags.set(
376            wgt::DownlevelFlags::INDEPENDENT_BLEND,
377            supported((3, 2), (4, 0)) || extensions.contains("GL_EXT_draw_buffers_indexed"),
378        );
379        downlevel_flags.set(
380            wgt::DownlevelFlags::VERTEX_STORAGE,
381            max_storage_block_size != 0
382                && max_storage_buffers_per_shader_stage != 0
383                && (vertex_shader_storage_blocks != 0 || vertex_ssbo_false_zero),
384        );
385        downlevel_flags.set(wgt::DownlevelFlags::FRAGMENT_STORAGE, supports_storage);
386        if extensions.contains("EXT_texture_filter_anisotropic")
387            || extensions.contains("GL_EXT_texture_filter_anisotropic")
388        {
389            let max_aniso =
390                unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_MAX_ANISOTROPY_EXT) } as u32;
391            downlevel_flags.set(wgt::DownlevelFlags::ANISOTROPIC_FILTERING, max_aniso >= 16);
392        }
393        downlevel_flags.set(
394            wgt::DownlevelFlags::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED,
395            !(cfg!(target_arch = "wasm32") || is_angle),
396        );
397        // see https://registry.khronos.org/webgl/specs/latest/2.0/#BUFFER_OBJECT_BINDING
398        downlevel_flags.set(
399            wgt::DownlevelFlags::UNRESTRICTED_INDEX_BUFFER,
400            !cfg!(target_arch = "wasm32"),
401        );
402        downlevel_flags.set(
403            wgt::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES,
404            !cfg!(target_arch = "wasm32"),
405        );
406        downlevel_flags.set(
407            wgt::DownlevelFlags::FULL_DRAW_INDEX_UINT32,
408            max_element_index == u32::MAX,
409        );
410        downlevel_flags.set(
411            wgt::DownlevelFlags::MULTISAMPLED_SHADING,
412            supported((3, 2), (4, 0)) || extensions.contains("OES_sample_variables"),
413        );
414        let query_buffers = extensions.contains("GL_ARB_query_buffer_object")
415            || extensions.contains("GL_AMD_query_buffer_object");
416        if query_buffers {
417            downlevel_flags.set(wgt::DownlevelFlags::NONBLOCKING_QUERY_RESOLVE, true);
418        }
419
420        let mut features = wgt::Features::empty()
421            | wgt::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
422            | wgt::Features::CLEAR_TEXTURE
423            | wgt::Features::PUSH_CONSTANTS;
424        features.set(
425            wgt::Features::ADDRESS_MODE_CLAMP_TO_BORDER | wgt::Features::ADDRESS_MODE_CLAMP_TO_ZERO,
426            extensions.contains("GL_EXT_texture_border_clamp")
427                || extensions.contains("GL_ARB_texture_border_clamp"),
428        );
429        features.set(
430            wgt::Features::DEPTH_CLIP_CONTROL,
431            extensions.contains("GL_EXT_depth_clamp") || extensions.contains("GL_ARB_depth_clamp"),
432        );
433        features.set(
434            wgt::Features::VERTEX_WRITABLE_STORAGE,
435            downlevel_flags.contains(wgt::DownlevelFlags::VERTEX_STORAGE)
436                && vertex_shader_storage_textures != 0,
437        );
438        features.set(
439            wgt::Features::MULTIVIEW,
440            extensions.contains("OVR_multiview2") || extensions.contains("GL_OVR_multiview2"),
441        );
442        features.set(
443            wgt::Features::DUAL_SOURCE_BLENDING,
444            extensions.contains("GL_EXT_blend_func_extended")
445                || extensions.contains("GL_ARB_blend_func_extended"),
446        );
447        features.set(
448            wgt::Features::SHADER_PRIMITIVE_INDEX,
449            supported((3, 2), (3, 2))
450                || extensions.contains("OES_geometry_shader")
451                || extensions.contains("GL_ARB_geometry_shader4"),
452        );
453        features.set(
454            wgt::Features::SHADER_EARLY_DEPTH_TEST,
455            supported((3, 1), (4, 2)) || extensions.contains("GL_ARB_shader_image_load_store"),
456        );
457        features.set(wgt::Features::SHADER_UNUSED_VERTEX_OUTPUT, true);
458        if extensions.contains("GL_ARB_timer_query") {
459            features.set(wgt::Features::TIMESTAMP_QUERY, true);
460            features.set(wgt::Features::TIMESTAMP_QUERY_INSIDE_PASSES, true);
461        }
462        let gl_bcn_exts = [
463            "GL_EXT_texture_compression_s3tc",
464            "GL_EXT_texture_compression_rgtc",
465            "GL_ARB_texture_compression_bptc",
466        ];
467        let gles_bcn_exts = [
468            "GL_EXT_texture_compression_s3tc_srgb",
469            "GL_EXT_texture_compression_rgtc",
470            "GL_EXT_texture_compression_bptc",
471        ];
472        let webgl_bcn_exts = [
473            "WEBGL_compressed_texture_s3tc",
474            "WEBGL_compressed_texture_s3tc_srgb",
475            "EXT_texture_compression_rgtc",
476            "EXT_texture_compression_bptc",
477        ];
478        let bcn_exts = if cfg!(target_arch = "wasm32") {
479            &webgl_bcn_exts[..]
480        } else if es_ver.is_some() {
481            &gles_bcn_exts[..]
482        } else {
483            &gl_bcn_exts[..]
484        };
485        features.set(
486            wgt::Features::TEXTURE_COMPRESSION_BC,
487            bcn_exts.iter().all(|&ext| extensions.contains(ext)),
488        );
489        let has_etc = if cfg!(target_arch = "wasm32") {
490            extensions.contains("WEBGL_compressed_texture_etc")
491        } else {
492            // This is a required part of GLES3, but not part of Desktop GL at all.
493            es_ver.is_some()
494        };
495        features.set(wgt::Features::TEXTURE_COMPRESSION_ETC2, has_etc);
496
497        // `OES_texture_compression_astc` provides 2D + 3D, LDR + HDR support
498        if extensions.contains("WEBGL_compressed_texture_astc")
499            || extensions.contains("GL_OES_texture_compression_astc")
500        {
501            #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
502            {
503                if context
504                    .glow_context
505                    .compressed_texture_astc_supports_ldr_profile()
506                {
507                    features.insert(wgt::Features::TEXTURE_COMPRESSION_ASTC);
508                }
509                if context
510                    .glow_context
511                    .compressed_texture_astc_supports_hdr_profile()
512                {
513                    features.insert(wgt::Features::TEXTURE_COMPRESSION_ASTC_HDR);
514                }
515            }
516
517            #[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))]
518            {
519                features.insert(wgt::Features::TEXTURE_COMPRESSION_ASTC);
520                features.insert(wgt::Features::TEXTURE_COMPRESSION_ASTC_HDR);
521            }
522        } else {
523            features.set(
524                wgt::Features::TEXTURE_COMPRESSION_ASTC,
525                extensions.contains("GL_KHR_texture_compression_astc_ldr"),
526            );
527            features.set(
528                wgt::Features::TEXTURE_COMPRESSION_ASTC_HDR,
529                extensions.contains("GL_KHR_texture_compression_astc_hdr"),
530            );
531        }
532
533        // We *might* be able to emulate bgra8unorm-storage but currently don't attempt to.
534
535        let mut private_caps = super::PrivateCapabilities::empty();
536        private_caps.set(
537            super::PrivateCapabilities::BUFFER_ALLOCATION,
538            extensions.contains("GL_EXT_buffer_storage")
539                || extensions.contains("GL_ARB_buffer_storage"),
540        );
541        private_caps.set(
542            super::PrivateCapabilities::SHADER_BINDING_LAYOUT,
543            supports_compute,
544        );
545        private_caps.set(
546            super::PrivateCapabilities::SHADER_TEXTURE_SHADOW_LOD,
547            extensions.contains("GL_EXT_texture_shadow_lod"),
548        );
549        private_caps.set(
550            super::PrivateCapabilities::MEMORY_BARRIERS,
551            supported((3, 1), (4, 2)),
552        );
553        private_caps.set(
554            super::PrivateCapabilities::VERTEX_BUFFER_LAYOUT,
555            supported((3, 1), (4, 3)) || extensions.contains("GL_ARB_vertex_attrib_binding"),
556        );
557        private_caps.set(
558            super::PrivateCapabilities::INDEX_BUFFER_ROLE_CHANGE,
559            !cfg!(target_arch = "wasm32"),
560        );
561        private_caps.set(
562            super::PrivateCapabilities::GET_BUFFER_SUB_DATA,
563            cfg!(target_arch = "wasm32") || full_ver.is_some(),
564        );
565        let color_buffer_float = extensions.contains("GL_EXT_color_buffer_float")
566            || extensions.contains("GL_ARB_color_buffer_float")
567            || extensions.contains("EXT_color_buffer_float");
568        let color_buffer_half_float = extensions.contains("GL_EXT_color_buffer_half_float")
569            || extensions.contains("GL_ARB_half_float_pixel");
570        private_caps.set(
571            super::PrivateCapabilities::COLOR_BUFFER_HALF_FLOAT,
572            color_buffer_half_float || color_buffer_float,
573        );
574        private_caps.set(
575            super::PrivateCapabilities::COLOR_BUFFER_FLOAT,
576            color_buffer_float,
577        );
578        private_caps.set(
579            super::PrivateCapabilities::TEXTURE_FLOAT_LINEAR,
580            if full_ver.is_some() {
581                color_buffer_float
582            } else {
583                extensions.contains("OES_texture_float_linear")
584            },
585        );
586        private_caps.set(super::PrivateCapabilities::QUERY_BUFFERS, query_buffers);
587
588        let max_texture_size = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as u32;
589        let max_texture_3d_size = unsafe { gl.get_parameter_i32(glow::MAX_3D_TEXTURE_SIZE) } as u32;
590
591        let min_uniform_buffer_offset_alignment =
592            (unsafe { gl.get_parameter_i32(glow::UNIFORM_BUFFER_OFFSET_ALIGNMENT) } as u32);
593        let min_storage_buffer_offset_alignment = if supports_storage {
594            (unsafe { gl.get_parameter_i32(glow::SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT) } as u32)
595        } else {
596            256
597        };
598        let max_uniform_buffers_per_shader_stage =
599            unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_UNIFORM_BLOCKS) }
600                .min(unsafe { gl.get_parameter_i32(glow::MAX_FRAGMENT_UNIFORM_BLOCKS) })
601                as u32;
602
603        let max_compute_workgroups_per_dimension = if supports_work_group_params {
604            unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_COUNT, 0) }
605                .min(unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_COUNT, 1) })
606                .min(unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_COUNT, 2) })
607                as u32
608        } else {
609            0
610        };
611
612        let limits = wgt::Limits {
613            max_texture_dimension_1d: max_texture_size,
614            max_texture_dimension_2d: max_texture_size,
615            max_texture_dimension_3d: max_texture_3d_size,
616            max_texture_array_layers: unsafe {
617                gl.get_parameter_i32(glow::MAX_ARRAY_TEXTURE_LAYERS)
618            } as u32,
619            max_bind_groups: crate::MAX_BIND_GROUPS as u32,
620            max_bindings_per_bind_group: 65535,
621            max_dynamic_uniform_buffers_per_pipeline_layout: max_uniform_buffers_per_shader_stage,
622            max_dynamic_storage_buffers_per_pipeline_layout: max_storage_buffers_per_shader_stage,
623            max_sampled_textures_per_shader_stage: super::MAX_TEXTURE_SLOTS as u32,
624            max_samplers_per_shader_stage: super::MAX_SAMPLERS as u32,
625            max_storage_buffers_per_shader_stage,
626            max_storage_textures_per_shader_stage,
627            max_uniform_buffers_per_shader_stage,
628            max_uniform_buffer_binding_size: unsafe {
629                gl.get_parameter_i32(glow::MAX_UNIFORM_BLOCK_SIZE)
630            } as u32,
631            max_storage_buffer_binding_size: if supports_storage {
632                unsafe { gl.get_parameter_i32(glow::MAX_SHADER_STORAGE_BLOCK_SIZE) }
633            } else {
634                0
635            } as u32,
636            max_vertex_buffers: if private_caps
637                .contains(super::PrivateCapabilities::VERTEX_BUFFER_LAYOUT)
638            {
639                (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_BINDINGS) } as u32)
640            } else {
641                16 // should this be different?
642            },
643            max_vertex_attributes: (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIBS) }
644                as u32)
645                .min(super::MAX_VERTEX_ATTRIBUTES as u32),
646            max_vertex_buffer_array_stride: if private_caps
647                .contains(super::PrivateCapabilities::VERTEX_BUFFER_LAYOUT)
648            {
649                if let Some(full_ver) = full_ver {
650                    if full_ver >= (4, 4) {
651                        // We can query `GL_MAX_VERTEX_ATTRIB_STRIDE` in OpenGL 4.4+
652                        let value =
653                            (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_STRIDE) })
654                                as u32;
655
656                        if value == 0 {
657                            // This should be at least 2048, but the driver for AMD Radeon HD 5870 on
658                            // Windows doesn't recognize `GL_MAX_VERTEX_ATTRIB_STRIDE`.
659
660                            log::warn!("Max vertex attribute stride is 0. Assuming it is 2048");
661                            2048
662                        } else {
663                            value
664                        }
665                    } else {
666                        log::warn!("Max vertex attribute stride unknown. Assuming it is 2048");
667                        2048
668                    }
669                } else {
670                    (unsafe { gl.get_parameter_i32(glow::MAX_VERTEX_ATTRIB_STRIDE) }) as u32
671                }
672            } else {
673                !0
674            },
675            max_push_constant_size: super::MAX_PUSH_CONSTANTS as u32 * 4,
676            min_uniform_buffer_offset_alignment,
677            min_storage_buffer_offset_alignment,
678            max_inter_stage_shader_components: unsafe {
679                gl.get_parameter_i32(glow::MAX_VARYING_COMPONENTS)
680            } as u32,
681            max_compute_workgroup_storage_size: if supports_work_group_params {
682                (unsafe { gl.get_parameter_i32(glow::MAX_COMPUTE_SHARED_MEMORY_SIZE) } as u32)
683            } else {
684                0
685            },
686            max_compute_invocations_per_workgroup: if supports_work_group_params {
687                (unsafe { gl.get_parameter_i32(glow::MAX_COMPUTE_WORK_GROUP_INVOCATIONS) } as u32)
688            } else {
689                0
690            },
691            max_compute_workgroup_size_x: if supports_work_group_params {
692                (unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_SIZE, 0) }
693                    as u32)
694            } else {
695                0
696            },
697            max_compute_workgroup_size_y: if supports_work_group_params {
698                (unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_SIZE, 1) }
699                    as u32)
700            } else {
701                0
702            },
703            max_compute_workgroup_size_z: if supports_work_group_params {
704                (unsafe { gl.get_parameter_indexed_i32(glow::MAX_COMPUTE_WORK_GROUP_SIZE, 2) }
705                    as u32)
706            } else {
707                0
708            },
709            max_compute_workgroups_per_dimension,
710            max_buffer_size: i32::MAX as u64,
711            max_non_sampler_bindings: std::u32::MAX,
712        };
713
714        let mut workarounds = super::Workarounds::empty();
715
716        workarounds.set(
717            super::Workarounds::EMULATE_BUFFER_MAP,
718            cfg!(target_arch = "wasm32"),
719        );
720
721        let r = renderer.to_lowercase();
722        // Check for Mesa sRGB clear bug. See
723        // [`super::PrivateCapabilities::MESA_I915_SRGB_SHADER_CLEAR`].
724        if context.is_owned()
725            && r.contains("mesa")
726            && r.contains("intel")
727            && r.split(&[' ', '(', ')'][..])
728                .any(|substr| substr.len() == 3 && substr.chars().nth(2) == Some('l'))
729        {
730            log::warn!(
731                "Detected skylake derivative running on mesa i915. Clears to srgb textures will \
732                use manual shader clears."
733            );
734            workarounds.set(super::Workarounds::MESA_I915_SRGB_SHADER_CLEAR, true);
735        }
736
737        let downlevel_defaults = wgt::DownlevelLimits {};
738
739        // Drop the GL guard so we can move the context into AdapterShared
740        // ( on Wasm the gl handle is just a ref so we tell clippy to allow
741        // dropping the ref )
742        #[cfg_attr(target_arch = "wasm32", allow(clippy::drop_ref))]
743        drop(gl);
744
745        Some(crate::ExposedAdapter {
746            adapter: super::Adapter {
747                shared: Arc::new(super::AdapterShared {
748                    context,
749                    private_caps,
750                    workarounds,
751                    features,
752                    shading_language_version,
753                    max_texture_size,
754                    next_shader_id: Default::default(),
755                    program_cache: Default::default(),
756                    es: es_ver.is_some(),
757                }),
758            },
759            info: Self::make_info(vendor, renderer),
760            features,
761            capabilities: crate::Capabilities {
762                limits,
763                downlevel: wgt::DownlevelCapabilities {
764                    flags: downlevel_flags,
765                    limits: downlevel_defaults,
766                    shader_model: wgt::ShaderModel::Sm5,
767                },
768                alignments: crate::Alignments {
769                    buffer_copy_offset: wgt::BufferSize::new(4).unwrap(),
770                    buffer_copy_pitch: wgt::BufferSize::new(4).unwrap(),
771                },
772            },
773        })
774    }
775
776    unsafe fn compile_shader(
777        source: &str,
778        gl: &glow::Context,
779        shader_type: u32,
780        es: bool,
781    ) -> Option<glow::Shader> {
782        let source = if es {
783            format!("#version 300 es\nprecision lowp float;\n{source}")
784        } else {
785            format!("#version 130\n{source}")
786        };
787        let shader = unsafe { gl.create_shader(shader_type) }.expect("Could not create shader");
788        unsafe { gl.shader_source(shader, &source) };
789        unsafe { gl.compile_shader(shader) };
790
791        if !unsafe { gl.get_shader_compile_status(shader) } {
792            let msg = unsafe { gl.get_shader_info_log(shader) };
793            if !msg.is_empty() {
794                log::error!("\tShader compile error: {}", msg);
795            }
796            unsafe { gl.delete_shader(shader) };
797            None
798        } else {
799            Some(shader)
800        }
801    }
802
803    unsafe fn create_shader_clear_program(
804        gl: &glow::Context,
805        es: bool,
806    ) -> Option<(glow::Program, glow::UniformLocation)> {
807        let program = unsafe { gl.create_program() }.expect("Could not create shader program");
808        let vertex = unsafe {
809            Self::compile_shader(
810                include_str!("./shaders/clear.vert"),
811                gl,
812                glow::VERTEX_SHADER,
813                es,
814            )?
815        };
816        let fragment = unsafe {
817            Self::compile_shader(
818                include_str!("./shaders/clear.frag"),
819                gl,
820                glow::FRAGMENT_SHADER,
821                es,
822            )?
823        };
824        unsafe { gl.attach_shader(program, vertex) };
825        unsafe { gl.attach_shader(program, fragment) };
826        unsafe { gl.link_program(program) };
827
828        let linked_ok = unsafe { gl.get_program_link_status(program) };
829        let msg = unsafe { gl.get_program_info_log(program) };
830        if !msg.is_empty() {
831            log::warn!("Shader link error: {}", msg);
832        }
833        if !linked_ok {
834            return None;
835        }
836
837        let color_uniform_location = unsafe { gl.get_uniform_location(program, "color") }
838            .expect("Could not find color uniform in shader clear shader");
839        unsafe { gl.delete_shader(vertex) };
840        unsafe { gl.delete_shader(fragment) };
841
842        Some((program, color_uniform_location))
843    }
844}
845
846impl crate::Adapter<super::Api> for super::Adapter {
847    unsafe fn open(
848        &self,
849        features: wgt::Features,
850        _limits: &wgt::Limits,
851    ) -> Result<crate::OpenDevice<super::Api>, crate::DeviceError> {
852        let gl = &self.shared.context.lock();
853        unsafe { gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1) };
854        unsafe { gl.pixel_store_i32(glow::PACK_ALIGNMENT, 1) };
855        let main_vao =
856            unsafe { gl.create_vertex_array() }.map_err(|_| crate::DeviceError::OutOfMemory)?;
857        unsafe { gl.bind_vertex_array(Some(main_vao)) };
858
859        let zero_buffer =
860            unsafe { gl.create_buffer() }.map_err(|_| crate::DeviceError::OutOfMemory)?;
861        unsafe { gl.bind_buffer(glow::COPY_READ_BUFFER, Some(zero_buffer)) };
862        let zeroes = vec![0u8; super::ZERO_BUFFER_SIZE];
863        unsafe { gl.buffer_data_u8_slice(glow::COPY_READ_BUFFER, &zeroes, glow::STATIC_DRAW) };
864
865        // Compile the shader program we use for doing manual clears to work around Mesa fastclear
866        // bug.
867
868        let (shader_clear_program, shader_clear_program_color_uniform_location) = unsafe {
869            Self::create_shader_clear_program(gl, self.shared.es)
870                .ok_or(crate::DeviceError::ResourceCreationFailed)?
871        };
872
873        Ok(crate::OpenDevice {
874            device: super::Device {
875                shared: Arc::clone(&self.shared),
876                main_vao,
877                #[cfg(all(not(target_arch = "wasm32"), feature = "renderdoc"))]
878                render_doc: Default::default(),
879            },
880            queue: super::Queue {
881                shared: Arc::clone(&self.shared),
882                features,
883                draw_fbo: unsafe { gl.create_framebuffer() }
884                    .map_err(|_| crate::DeviceError::OutOfMemory)?,
885                copy_fbo: unsafe { gl.create_framebuffer() }
886                    .map_err(|_| crate::DeviceError::OutOfMemory)?,
887                shader_clear_program,
888                shader_clear_program_color_uniform_location,
889                zero_buffer,
890                temp_query_results: Vec::new(),
891                draw_buffer_count: 1,
892                current_index_buffer: None,
893            },
894        })
895    }
896
897    unsafe fn texture_format_capabilities(
898        &self,
899        format: wgt::TextureFormat,
900    ) -> crate::TextureFormatCapabilities {
901        use crate::TextureFormatCapabilities as Tfc;
902        use wgt::TextureFormat as Tf;
903
904        let sample_count = {
905            let max_samples = unsafe {
906                self.shared
907                    .context
908                    .lock()
909                    .get_parameter_i32(glow::MAX_SAMPLES)
910            };
911            if max_samples >= 16 {
912                Tfc::MULTISAMPLE_X2
913                    | Tfc::MULTISAMPLE_X4
914                    | Tfc::MULTISAMPLE_X8
915                    | Tfc::MULTISAMPLE_X16
916            } else if max_samples >= 8 {
917                Tfc::MULTISAMPLE_X2 | Tfc::MULTISAMPLE_X4 | Tfc::MULTISAMPLE_X8
918            } else {
919                // The lowest supported level in GLE3.0/WebGL2 is 4X
920                // (see GL_MAX_SAMPLES in https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml).
921                // On some platforms, like iOS Safari, `get_parameter_i32(MAX_SAMPLES)` returns 0,
922                // so we always fall back to supporting 4x here.
923                Tfc::MULTISAMPLE_X2 | Tfc::MULTISAMPLE_X4
924            }
925        };
926
927        // Base types are pulled from the table in the OpenGLES 3.0 spec in section 3.8.
928        //
929        // The storage types are based on table 8.26, in section
930        // "TEXTURE IMAGE LOADS AND STORES" of OpenGLES-3.2 spec.
931        let empty = Tfc::empty();
932        let base = Tfc::COPY_SRC | Tfc::COPY_DST;
933        let unfilterable = base | Tfc::SAMPLED;
934        let depth = base | Tfc::SAMPLED | sample_count | Tfc::DEPTH_STENCIL_ATTACHMENT;
935        let filterable = unfilterable | Tfc::SAMPLED_LINEAR;
936        let renderable =
937            unfilterable | Tfc::COLOR_ATTACHMENT | sample_count | Tfc::MULTISAMPLE_RESOLVE;
938        let filterable_renderable = filterable | renderable | Tfc::COLOR_ATTACHMENT_BLEND;
939        let storage = base | Tfc::STORAGE | Tfc::STORAGE_READ_WRITE;
940
941        let feature_fn = |f, caps| {
942            if self.shared.features.contains(f) {
943                caps
944            } else {
945                empty
946            }
947        };
948
949        let bcn_features = feature_fn(wgt::Features::TEXTURE_COMPRESSION_BC, filterable);
950        let etc2_features = feature_fn(wgt::Features::TEXTURE_COMPRESSION_ETC2, filterable);
951        let astc_features = feature_fn(wgt::Features::TEXTURE_COMPRESSION_ASTC, filterable);
952        let astc_hdr_features = feature_fn(wgt::Features::TEXTURE_COMPRESSION_ASTC_HDR, filterable);
953
954        let private_caps_fn = |f, caps| {
955            if self.shared.private_caps.contains(f) {
956                caps
957            } else {
958                empty
959            }
960        };
961
962        let half_float_renderable = private_caps_fn(
963            super::PrivateCapabilities::COLOR_BUFFER_HALF_FLOAT,
964            Tfc::COLOR_ATTACHMENT
965                | Tfc::COLOR_ATTACHMENT_BLEND
966                | sample_count
967                | Tfc::MULTISAMPLE_RESOLVE,
968        );
969
970        let float_renderable = private_caps_fn(
971            super::PrivateCapabilities::COLOR_BUFFER_FLOAT,
972            Tfc::COLOR_ATTACHMENT
973                | Tfc::COLOR_ATTACHMENT_BLEND
974                | sample_count
975                | Tfc::MULTISAMPLE_RESOLVE,
976        );
977
978        let texture_float_linear =
979            private_caps_fn(super::PrivateCapabilities::TEXTURE_FLOAT_LINEAR, filterable);
980
981        match format {
982            Tf::R8Unorm => filterable_renderable,
983            Tf::R8Snorm => filterable,
984            Tf::R8Uint => renderable,
985            Tf::R8Sint => renderable,
986            Tf::R16Uint => renderable,
987            Tf::R16Sint => renderable,
988            Tf::R16Unorm => empty,
989            Tf::R16Snorm => empty,
990            Tf::R16Float => filterable | half_float_renderable,
991            Tf::Rg8Unorm => filterable_renderable,
992            Tf::Rg8Snorm => filterable,
993            Tf::Rg8Uint => renderable,
994            Tf::Rg8Sint => renderable,
995            Tf::R32Uint => renderable | storage,
996            Tf::R32Sint => renderable | storage,
997            Tf::R32Float => unfilterable | storage | float_renderable | texture_float_linear,
998            Tf::Rg16Uint => renderable,
999            Tf::Rg16Sint => renderable,
1000            Tf::Rg16Unorm => empty,
1001            Tf::Rg16Snorm => empty,
1002            Tf::Rg16Float => filterable | half_float_renderable,
1003            Tf::Rgba8Unorm | Tf::Rgba8UnormSrgb => filterable_renderable | storage,
1004            Tf::Bgra8Unorm | Tf::Bgra8UnormSrgb => filterable_renderable,
1005            Tf::Rgba8Snorm => filterable,
1006            Tf::Rgba8Uint => renderable | storage,
1007            Tf::Rgba8Sint => renderable | storage,
1008            Tf::Rgb10a2Uint => renderable,
1009            Tf::Rgb10a2Unorm => filterable_renderable,
1010            Tf::Rg11b10Float => filterable | float_renderable,
1011            Tf::Rg32Uint => renderable,
1012            Tf::Rg32Sint => renderable,
1013            Tf::Rg32Float => unfilterable | float_renderable | texture_float_linear,
1014            Tf::Rgba16Uint => renderable | storage,
1015            Tf::Rgba16Sint => renderable | storage,
1016            Tf::Rgba16Unorm => empty,
1017            Tf::Rgba16Snorm => empty,
1018            Tf::Rgba16Float => filterable | storage | half_float_renderable,
1019            Tf::Rgba32Uint => renderable | storage,
1020            Tf::Rgba32Sint => renderable | storage,
1021            Tf::Rgba32Float => unfilterable | storage | float_renderable | texture_float_linear,
1022            Tf::Stencil8
1023            | Tf::Depth16Unorm
1024            | Tf::Depth32Float
1025            | Tf::Depth32FloatStencil8
1026            | Tf::Depth24Plus
1027            | Tf::Depth24PlusStencil8 => depth,
1028            Tf::Rgb9e5Ufloat => filterable,
1029            Tf::Bc1RgbaUnorm
1030            | Tf::Bc1RgbaUnormSrgb
1031            | Tf::Bc2RgbaUnorm
1032            | Tf::Bc2RgbaUnormSrgb
1033            | Tf::Bc3RgbaUnorm
1034            | Tf::Bc3RgbaUnormSrgb
1035            | Tf::Bc4RUnorm
1036            | Tf::Bc4RSnorm
1037            | Tf::Bc5RgUnorm
1038            | Tf::Bc5RgSnorm
1039            | Tf::Bc6hRgbFloat
1040            | Tf::Bc6hRgbUfloat
1041            | Tf::Bc7RgbaUnorm
1042            | Tf::Bc7RgbaUnormSrgb => bcn_features,
1043            Tf::Etc2Rgb8Unorm
1044            | Tf::Etc2Rgb8UnormSrgb
1045            | Tf::Etc2Rgb8A1Unorm
1046            | Tf::Etc2Rgb8A1UnormSrgb
1047            | Tf::Etc2Rgba8Unorm
1048            | Tf::Etc2Rgba8UnormSrgb
1049            | Tf::EacR11Unorm
1050            | Tf::EacR11Snorm
1051            | Tf::EacRg11Unorm
1052            | Tf::EacRg11Snorm => etc2_features,
1053            Tf::Astc {
1054                block: _,
1055                channel: AstcChannel::Unorm | AstcChannel::UnormSrgb,
1056            } => astc_features,
1057            Tf::Astc {
1058                block: _,
1059                channel: AstcChannel::Hdr,
1060            } => astc_hdr_features,
1061        }
1062    }
1063
1064    unsafe fn surface_capabilities(
1065        &self,
1066        surface: &super::Surface,
1067    ) -> Option<crate::SurfaceCapabilities> {
1068        if surface.presentable {
1069            let mut formats = vec![
1070                wgt::TextureFormat::Rgba8Unorm,
1071                #[cfg(not(target_arch = "wasm32"))]
1072                wgt::TextureFormat::Bgra8Unorm,
1073            ];
1074            if surface.supports_srgb() {
1075                formats.extend([
1076                    wgt::TextureFormat::Rgba8UnormSrgb,
1077                    #[cfg(not(target_arch = "wasm32"))]
1078                    wgt::TextureFormat::Bgra8UnormSrgb,
1079                ])
1080            }
1081            if self
1082                .shared
1083                .private_caps
1084                .contains(super::PrivateCapabilities::COLOR_BUFFER_HALF_FLOAT)
1085            {
1086                formats.push(wgt::TextureFormat::Rgba16Float)
1087            }
1088
1089            Some(crate::SurfaceCapabilities {
1090                formats,
1091                present_modes: if cfg!(windows) {
1092                    vec![wgt::PresentMode::Fifo, wgt::PresentMode::Mailbox]
1093                } else {
1094                    vec![wgt::PresentMode::Fifo] //TODO
1095                },
1096                composite_alpha_modes: vec![wgt::CompositeAlphaMode::Opaque], //TODO
1097                swap_chain_sizes: 2..=2,
1098                current_extent: None,
1099                extents: wgt::Extent3d {
1100                    width: 4,
1101                    height: 4,
1102                    depth_or_array_layers: 1,
1103                }..=wgt::Extent3d {
1104                    width: self.shared.max_texture_size,
1105                    height: self.shared.max_texture_size,
1106                    depth_or_array_layers: 1,
1107                },
1108                usage: crate::TextureUses::COLOR_TARGET,
1109            })
1110        } else {
1111            None
1112        }
1113    }
1114
1115    unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp {
1116        wgt::PresentationTimestamp::INVALID_TIMESTAMP
1117    }
1118}
1119
1120impl super::AdapterShared {
1121    pub(super) unsafe fn get_buffer_sub_data(
1122        &self,
1123        gl: &glow::Context,
1124        target: u32,
1125        offset: i32,
1126        dst_data: &mut [u8],
1127    ) {
1128        if self
1129            .private_caps
1130            .contains(super::PrivateCapabilities::GET_BUFFER_SUB_DATA)
1131        {
1132            unsafe { gl.get_buffer_sub_data(target, offset, dst_data) };
1133        } else {
1134            log::error!("Fake map");
1135            let length = dst_data.len();
1136            let buffer_mapping =
1137                unsafe { gl.map_buffer_range(target, offset, length as _, glow::MAP_READ_BIT) };
1138
1139            unsafe { std::ptr::copy_nonoverlapping(buffer_mapping, dst_data.as_mut_ptr(), length) };
1140
1141            unsafe { gl.unmap_buffer(target) };
1142        }
1143    }
1144}
1145
1146#[cfg(all(
1147    target_arch = "wasm32",
1148    feature = "fragile-send-sync-non-atomic-wasm",
1149    not(target_feature = "atomics")
1150))]
1151unsafe impl Sync for super::Adapter {}
1152#[cfg(all(
1153    target_arch = "wasm32",
1154    feature = "fragile-send-sync-non-atomic-wasm",
1155    not(target_feature = "atomics")
1156))]
1157unsafe impl Send for super::Adapter {}
1158
1159#[cfg(test)]
1160mod tests {
1161    use super::super::Adapter;
1162
1163    #[test]
1164    fn test_version_parse() {
1165        Adapter::parse_version("1").unwrap_err();
1166        Adapter::parse_version("1.").unwrap_err();
1167        Adapter::parse_version("1 h3l1o. W0rld").unwrap_err();
1168        Adapter::parse_version("1. h3l1o. W0rld").unwrap_err();
1169        Adapter::parse_version("1.2.3").unwrap_err();
1170
1171        assert_eq!(Adapter::parse_version("OpenGL ES 3.1").unwrap(), (3, 1));
1172        assert_eq!(
1173            Adapter::parse_version("OpenGL ES 2.0 Google Nexus").unwrap(),
1174            (2, 0)
1175        );
1176        assert_eq!(Adapter::parse_version("GLSL ES 1.1").unwrap(), (1, 1));
1177        assert_eq!(
1178            Adapter::parse_version("OpenGL ES GLSL ES 3.20").unwrap(),
1179            (3, 2)
1180        );
1181        assert_eq!(
1182            // WebGL 2.0 should parse as OpenGL ES 3.0
1183            Adapter::parse_version("WebGL 2.0 (OpenGL ES 3.0 Chromium)").unwrap(),
1184            (3, 0)
1185        );
1186        assert_eq!(
1187            Adapter::parse_version("WebGL GLSL ES 3.00 (OpenGL ES GLSL ES 3.0 Chromium)").unwrap(),
1188            (3, 0)
1189        );
1190    }
1191}