Skip to main content

wgpu_hal/vulkan/
adapter.rs

1use alloc::{borrow::ToOwned as _, boxed::Box, collections::BTreeMap, sync::Arc, vec::Vec};
2use core::{ffi::CStr, marker::PhantomData};
3
4use ash::{ext, google, khr, vk};
5use parking_lot::Mutex;
6
7use crate::{vulkan::semaphore_list::SemaphoreList, AllocationSizes};
8
9use super::semaphore_list::SemaphoreListMode;
10
11fn depth_stencil_required_flags() -> vk::FormatFeatureFlags {
12    vk::FormatFeatureFlags::SAMPLED_IMAGE | vk::FormatFeatureFlags::DEPTH_STENCIL_ATTACHMENT
13}
14
15const INDEXING_FEATURES: wgt::Features = wgt::Features::TEXTURE_BINDING_ARRAY
16    .union(wgt::Features::BUFFER_BINDING_ARRAY)
17    .union(wgt::Features::STORAGE_RESOURCE_BINDING_ARRAY)
18    .union(wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING)
19    .union(wgt::Features::STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING)
20    .union(wgt::Features::UNIFORM_BUFFER_BINDING_ARRAYS)
21    .union(wgt::Features::PARTIALLY_BOUND_BINDING_ARRAY);
22#[expect(rustdoc::private_intra_doc_links)]
23/// Features supported by a [`vk::PhysicalDevice`] and its extensions.
24///
25/// This is used in two phases:
26///
27/// - When enumerating adapters, this represents the features offered by the
28///   adapter. [`Instance::expose_adapter`] calls `vkGetPhysicalDeviceFeatures2`
29///   (or `vkGetPhysicalDeviceFeatures` if that is not available) to collect
30///   this information about the `VkPhysicalDevice` represented by the
31///   `wgpu_hal::ExposedAdapter`.
32///
33/// - When opening a device, this represents the features we would like to
34///   enable. At `wgpu_hal::Device` construction time,
35///   [`PhysicalDeviceFeatures::from_extensions_and_requested_features`]
36///   constructs an value of this type indicating which Vulkan features to
37///   enable, based on the `wgpu_types::Features` requested.
38///
39/// [`Instance::expose_adapter`]: super::Instance::expose_adapter
40#[derive(Debug, Default)]
41pub struct PhysicalDeviceFeatures {
42    /// Basic Vulkan 1.0 features.
43    core: vk::PhysicalDeviceFeatures,
44
45    /// Features provided by `VK_EXT_descriptor_indexing`, promoted to Vulkan 1.2.
46    pub(super) descriptor_indexing:
47        Option<vk::PhysicalDeviceDescriptorIndexingFeaturesEXT<'static>>,
48
49    /// Features provided by `VK_KHR_timeline_semaphore`, promoted to Vulkan 1.2
50    timeline_semaphore: Option<vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR<'static>>,
51
52    /// Features provided by `VK_EXT_image_robustness`, promoted to Vulkan 1.3
53    image_robustness: Option<vk::PhysicalDeviceImageRobustnessFeaturesEXT<'static>>,
54
55    /// Features provided by `VK_EXT_robustness2`.
56    robustness2: Option<vk::PhysicalDeviceRobustness2FeaturesEXT<'static>>,
57
58    /// Features provided by `VK_KHR_multiview`, promoted to Vulkan 1.1.
59    multiview: Option<vk::PhysicalDeviceMultiviewFeaturesKHR<'static>>,
60
61    /// Features provided by `VK_KHR_sampler_ycbcr_conversion`, promoted to Vulkan 1.1.
62    sampler_ycbcr_conversion: Option<vk::PhysicalDeviceSamplerYcbcrConversionFeatures<'static>>,
63
64    /// Features provided by `VK_EXT_texture_compression_astc_hdr`, promoted to Vulkan 1.3.
65    astc_hdr: Option<vk::PhysicalDeviceTextureCompressionASTCHDRFeaturesEXT<'static>>,
66
67    /// Features provided by `VK_KHR_shader_float16_int8`, promoted to Vulkan 1.2
68    shader_float16_int8: Option<vk::PhysicalDeviceShaderFloat16Int8Features<'static>>,
69
70    /// Features provided by `VK_KHR_16bit_storage`, promoted to Vulkan 1.1
71    _16bit_storage: Option<vk::PhysicalDevice16BitStorageFeatures<'static>>,
72
73    /// Features provided by `VK_KHR_acceleration_structure`.
74    acceleration_structure: Option<vk::PhysicalDeviceAccelerationStructureFeaturesKHR<'static>>,
75
76    /// Features provided by `VK_KHR_buffer_device_address`, promoted to Vulkan 1.2.
77    ///
78    /// We only use this feature for
79    /// [`Features::EXPERIMENTAL_RAY_QUERY`], which requires
80    /// `VK_KHR_acceleration_structure`, which depends on
81    /// `VK_KHR_buffer_device_address`, so [`Instance::expose_adapter`] only
82    /// bothers to check if `VK_KHR_acceleration_structure` is available,
83    /// leaving this `None`.
84    ///
85    /// However, we do populate this when creating a device if
86    /// [`Features::EXPERIMENTAL_RAY_QUERY`] is requested.
87    ///
88    /// [`Instance::expose_adapter`]: super::Instance::expose_adapter
89    /// [`Features::EXPERIMENTAL_RAY_QUERY`]: wgt::Features::EXPERIMENTAL_RAY_QUERY
90    buffer_device_address: Option<vk::PhysicalDeviceBufferDeviceAddressFeaturesKHR<'static>>,
91
92    /// Features provided by `VK_KHR_ray_query`,
93    ///
94    /// Vulkan requires that the feature be present if the `VK_KHR_ray_query`
95    /// extension is present, so [`Instance::expose_adapter`] doesn't bother retrieving
96    /// this from `vkGetPhysicalDeviceFeatures2`.
97    ///
98    /// However, we do populate this when creating a device if ray tracing is requested.
99    ///
100    /// [`Instance::expose_adapter`]: super::Instance::expose_adapter
101    ray_query: Option<vk::PhysicalDeviceRayQueryFeaturesKHR<'static>>,
102
103    /// Features provided by `VK_KHR_zero_initialize_workgroup_memory`, promoted
104    /// to Vulkan 1.3.
105    zero_initialize_workgroup_memory:
106        Option<vk::PhysicalDeviceZeroInitializeWorkgroupMemoryFeatures<'static>>,
107    position_fetch: Option<vk::PhysicalDeviceRayTracingPositionFetchFeaturesKHR<'static>>,
108
109    /// Features provided by `VK_KHR_shader_atomic_int64`, promoted to Vulkan 1.2.
110    shader_atomic_int64: Option<vk::PhysicalDeviceShaderAtomicInt64Features<'static>>,
111
112    /// Features provided by `VK_EXT_shader_image_atomic_int64`
113    shader_image_atomic_int64: Option<vk::PhysicalDeviceShaderImageAtomicInt64FeaturesEXT<'static>>,
114
115    /// Features provided by `VK_EXT_shader_atomic_float`.
116    shader_atomic_float: Option<vk::PhysicalDeviceShaderAtomicFloatFeaturesEXT<'static>>,
117
118    /// Features provided by `VK_EXT_subgroup_size_control`, promoted to Vulkan 1.3.
119    subgroup_size_control: Option<vk::PhysicalDeviceSubgroupSizeControlFeatures<'static>>,
120
121    /// Features provided by `VK_KHR_maintenance4`, promoted to Vulkan 1.3.
122    maintenance4: Option<vk::PhysicalDeviceMaintenance4FeaturesKHR<'static>>,
123
124    /// Features proved by `VK_EXT_mesh_shader`
125    mesh_shader: Option<vk::PhysicalDeviceMeshShaderFeaturesEXT<'static>>,
126
127    /// Features provided by `VK_KHR_shader_integer_dot_product`, promoted to Vulkan 1.3.
128    shader_integer_dot_product:
129        Option<vk::PhysicalDeviceShaderIntegerDotProductFeaturesKHR<'static>>,
130
131    /// Features provided by `VK_KHR_fragment_shader_barycentric`
132    shader_barycentrics: Option<vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR<'static>>,
133
134    /// Features provided by `VK_KHR_portability_subset`.
135    ///
136    /// Strictly speaking this tells us what features we *don't* have compared to core.
137    portability_subset: Option<vk::PhysicalDevicePortabilitySubsetFeaturesKHR<'static>>,
138
139    /// Features provided by `VK_KHR_cooperative_matrix`
140    cooperative_matrix: Option<vk::PhysicalDeviceCooperativeMatrixFeaturesKHR<'static>>,
141
142    /// Features provided by `VK_KHR_vulkan_memory_model`, promoted to Vulkan 1.2
143    vulkan_memory_model: Option<vk::PhysicalDeviceVulkanMemoryModelFeaturesKHR<'static>>,
144
145    shader_draw_parameters: Option<vk::PhysicalDeviceShaderDrawParametersFeatures<'static>>,
146}
147
148impl PhysicalDeviceFeatures {
149    pub fn get_core(&self) -> vk::PhysicalDeviceFeatures {
150        self.core
151    }
152
153    /// Add the members of `self` into `info.enabled_features` and its `p_next` chain.
154    pub fn add_to_device_create<'a>(
155        &'a mut self,
156        mut info: vk::DeviceCreateInfo<'a>,
157    ) -> vk::DeviceCreateInfo<'a> {
158        info = info.enabled_features(&self.core);
159        if let Some(ref mut feature) = self.descriptor_indexing {
160            info = info.push_next(feature);
161        }
162        if let Some(ref mut feature) = self.timeline_semaphore {
163            info = info.push_next(feature);
164        }
165        if let Some(ref mut feature) = self.image_robustness {
166            info = info.push_next(feature);
167        }
168        if let Some(ref mut feature) = self.robustness2 {
169            info = info.push_next(feature);
170        }
171        if let Some(ref mut feature) = self.multiview {
172            info = info.push_next(feature);
173        }
174        if let Some(ref mut feature) = self.astc_hdr {
175            info = info.push_next(feature);
176        }
177        if let Some(ref mut feature) = self.shader_float16_int8 {
178            info = info.push_next(feature);
179        }
180        if let Some(ref mut feature) = self._16bit_storage {
181            info = info.push_next(feature);
182        }
183        if let Some(ref mut feature) = self.zero_initialize_workgroup_memory {
184            info = info.push_next(feature);
185        }
186        if let Some(ref mut feature) = self.acceleration_structure {
187            info = info.push_next(feature);
188        }
189        if let Some(ref mut feature) = self.buffer_device_address {
190            info = info.push_next(feature);
191        }
192        if let Some(ref mut feature) = self.ray_query {
193            info = info.push_next(feature);
194        }
195        if let Some(ref mut feature) = self.shader_atomic_int64 {
196            info = info.push_next(feature);
197        }
198        if let Some(ref mut feature) = self.position_fetch {
199            info = info.push_next(feature);
200        }
201        if let Some(ref mut feature) = self.shader_image_atomic_int64 {
202            info = info.push_next(feature);
203        }
204        if let Some(ref mut feature) = self.shader_atomic_float {
205            info = info.push_next(feature);
206        }
207        if let Some(ref mut feature) = self.subgroup_size_control {
208            info = info.push_next(feature);
209        }
210        if let Some(ref mut feature) = self.maintenance4 {
211            info = info.push_next(feature);
212        }
213        if let Some(ref mut feature) = self.mesh_shader {
214            info = info.push_next(feature);
215        }
216        if let Some(ref mut feature) = self.shader_integer_dot_product {
217            info = info.push_next(feature);
218        }
219        if let Some(ref mut feature) = self.shader_barycentrics {
220            info = info.push_next(feature);
221        }
222        if let Some(ref mut feature) = self.portability_subset {
223            info = info.push_next(feature);
224        }
225        if let Some(ref mut feature) = self.cooperative_matrix {
226            info = info.push_next(feature);
227        }
228        if let Some(ref mut feature) = self.vulkan_memory_model {
229            info = info.push_next(feature);
230        }
231        if let Some(ref mut feature) = self.shader_draw_parameters {
232            info = info.push_next(feature);
233        }
234        info
235    }
236
237    fn supports_storage_input_output_16(&self) -> bool {
238        self._16bit_storage
239            .as_ref()
240            .map(|features| features.storage_input_output16 != 0)
241            .unwrap_or(false)
242    }
243
244    /// Create a `PhysicalDeviceFeatures` that can be used to create a logical
245    /// device.
246    ///
247    /// Return a `PhysicalDeviceFeatures` value capturing all the Vulkan
248    /// features needed for the given [`Features`], [`DownlevelFlags`], and
249    /// [`PrivateCapabilities`]. You can use the returned value's
250    /// [`add_to_device_create`] method to configure a
251    /// [`vk::DeviceCreateInfo`] to build a logical device providing those
252    /// features.
253    ///
254    /// To ensure that the returned value is able to select all the Vulkan
255    /// features needed to express `requested_features`, `downlevel_flags`, and
256    /// `private_caps`:
257    ///
258    /// - The given `enabled_extensions` set must include all the extensions
259    ///   selected by [`Adapter::required_device_extensions`] when passed
260    ///   `features`.
261    ///
262    /// - The given `device_api_version` must be the Vulkan API version of the
263    ///   physical device we will use to create the logical device.
264    ///
265    /// [`Features`]: wgt::Features
266    /// [`DownlevelFlags`]: wgt::DownlevelFlags
267    /// [`PrivateCapabilities`]: super::PrivateCapabilities
268    /// [`add_to_device_create`]: PhysicalDeviceFeatures::add_to_device_create
269    /// [`Adapter::required_device_extensions`]: super::Adapter::required_device_extensions
270    fn from_extensions_and_requested_features(
271        phd_capabilities: &PhysicalDeviceProperties,
272        phd_features: &PhysicalDeviceFeatures,
273        enabled_extensions: &[&'static CStr],
274        requested_features: wgt::Features,
275        downlevel_flags: wgt::DownlevelFlags,
276        private_caps: &super::PrivateCapabilities,
277    ) -> Self {
278        let device_api_version = phd_capabilities.device_api_version;
279        let needs_bindless = requested_features.intersects(
280            wgt::Features::TEXTURE_BINDING_ARRAY
281                | wgt::Features::BUFFER_BINDING_ARRAY
282                | wgt::Features::STORAGE_RESOURCE_BINDING_ARRAY
283                | wgt::Features::STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING
284                | wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
285        );
286        let needs_partially_bound =
287            requested_features.intersects(wgt::Features::PARTIALLY_BOUND_BINDING_ARRAY);
288
289        Self {
290            // vk::PhysicalDeviceFeatures is a struct composed of Bool32's while
291            // Features is a bitfield so we need to map everything manually
292            core: vk::PhysicalDeviceFeatures::default()
293                .robust_buffer_access(private_caps.robust_buffer_access)
294                .independent_blend(downlevel_flags.contains(wgt::DownlevelFlags::INDEPENDENT_BLEND))
295                .sample_rate_shading(
296                    downlevel_flags.contains(wgt::DownlevelFlags::MULTISAMPLED_SHADING),
297                )
298                .image_cube_array(
299                    downlevel_flags.contains(wgt::DownlevelFlags::CUBE_ARRAY_TEXTURES),
300                )
301                .draw_indirect_first_instance(
302                    requested_features.contains(wgt::Features::INDIRECT_FIRST_INSTANCE),
303                )
304                //.dual_src_blend(requested_features.contains(wgt::Features::DUAL_SRC_BLENDING))
305                .multi_draw_indirect(phd_features.core.multi_draw_indirect != 0)
306                .fill_mode_non_solid(requested_features.intersects(
307                    wgt::Features::POLYGON_MODE_LINE | wgt::Features::POLYGON_MODE_POINT,
308                ))
309                //.depth_bounds(requested_features.contains(wgt::Features::DEPTH_BOUNDS))
310                //.alpha_to_one(requested_features.contains(wgt::Features::ALPHA_TO_ONE))
311                //.multi_viewport(requested_features.contains(wgt::Features::MULTI_VIEWPORTS))
312                .sampler_anisotropy(
313                    downlevel_flags.contains(wgt::DownlevelFlags::ANISOTROPIC_FILTERING),
314                )
315                .texture_compression_etc2(
316                    requested_features.contains(wgt::Features::TEXTURE_COMPRESSION_ETC2),
317                )
318                .texture_compression_astc_ldr(
319                    requested_features.contains(wgt::Features::TEXTURE_COMPRESSION_ASTC),
320                )
321                .texture_compression_bc(
322                    requested_features.contains(wgt::Features::TEXTURE_COMPRESSION_BC),
323                    // BC provides formats for Sliced 3D
324                )
325                //.occlusion_query_precise(requested_features.contains(wgt::Features::PRECISE_OCCLUSION_QUERY))
326                .pipeline_statistics_query(
327                    requested_features.contains(wgt::Features::PIPELINE_STATISTICS_QUERY),
328                )
329                .vertex_pipeline_stores_and_atomics(
330                    requested_features.contains(wgt::Features::VERTEX_WRITABLE_STORAGE),
331                )
332                .fragment_stores_and_atomics(
333                    downlevel_flags.contains(wgt::DownlevelFlags::FRAGMENT_WRITABLE_STORAGE),
334                )
335                //.shader_image_gather_extended(
336                //.shader_storage_image_extended_formats(
337                .shader_uniform_buffer_array_dynamic_indexing(
338                    requested_features.contains(wgt::Features::BUFFER_BINDING_ARRAY),
339                )
340                .shader_storage_buffer_array_dynamic_indexing(requested_features.contains(
341                    wgt::Features::BUFFER_BINDING_ARRAY
342                        | wgt::Features::STORAGE_RESOURCE_BINDING_ARRAY,
343                ))
344                .shader_sampled_image_array_dynamic_indexing(
345                    requested_features.contains(wgt::Features::TEXTURE_BINDING_ARRAY),
346                )
347                .shader_storage_buffer_array_dynamic_indexing(requested_features.contains(
348                    wgt::Features::TEXTURE_BINDING_ARRAY
349                        | wgt::Features::STORAGE_RESOURCE_BINDING_ARRAY,
350                ))
351                //.shader_storage_image_array_dynamic_indexing(
352                .shader_clip_distance(requested_features.contains(wgt::Features::CLIP_DISTANCES))
353                //.shader_cull_distance(requested_features.contains(wgt::Features::SHADER_CULL_DISTANCE))
354                .shader_float64(requested_features.contains(wgt::Features::SHADER_F64))
355                .shader_int64(requested_features.contains(wgt::Features::SHADER_INT64))
356                .shader_int16(requested_features.contains(wgt::Features::SHADER_I16))
357                //.shader_resource_residency(requested_features.contains(wgt::Features::SHADER_RESOURCE_RESIDENCY))
358                .geometry_shader(requested_features.contains(wgt::Features::PRIMITIVE_INDEX))
359                .depth_clamp(requested_features.contains(wgt::Features::DEPTH_CLIP_CONTROL))
360                .dual_src_blend(requested_features.contains(wgt::Features::DUAL_SOURCE_BLENDING)),
361            descriptor_indexing: if requested_features.intersects(INDEXING_FEATURES) {
362                Some(
363                    vk::PhysicalDeviceDescriptorIndexingFeaturesEXT::default()
364                        .shader_sampled_image_array_non_uniform_indexing(needs_bindless)
365                        .shader_storage_image_array_non_uniform_indexing(needs_bindless)
366                        .shader_storage_buffer_array_non_uniform_indexing(needs_bindless)
367                        .descriptor_binding_sampled_image_update_after_bind(needs_bindless)
368                        .descriptor_binding_storage_image_update_after_bind(needs_bindless)
369                        .descriptor_binding_storage_buffer_update_after_bind(needs_bindless)
370                        .descriptor_binding_partially_bound(needs_partially_bound),
371                )
372            } else {
373                None
374            },
375            timeline_semaphore: if device_api_version >= vk::API_VERSION_1_2
376                || enabled_extensions.contains(&khr::timeline_semaphore::NAME)
377            {
378                Some(
379                    vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR::default()
380                        .timeline_semaphore(private_caps.timeline_semaphores),
381                )
382            } else {
383                None
384            },
385            image_robustness: if device_api_version >= vk::API_VERSION_1_3
386                || enabled_extensions.contains(&ext::image_robustness::NAME)
387            {
388                Some(
389                    vk::PhysicalDeviceImageRobustnessFeaturesEXT::default()
390                        .robust_image_access(private_caps.robust_image_access),
391                )
392            } else {
393                None
394            },
395            robustness2: if enabled_extensions.contains(&ext::robustness2::NAME) {
396                Some(
397                    vk::PhysicalDeviceRobustness2FeaturesEXT::default()
398                        .robust_buffer_access2(private_caps.robust_buffer_access2)
399                        .robust_image_access2(private_caps.robust_image_access2),
400                )
401            } else {
402                None
403            },
404            multiview: if device_api_version >= vk::API_VERSION_1_1
405                || enabled_extensions.contains(&khr::multiview::NAME)
406            {
407                Some(
408                    vk::PhysicalDeviceMultiviewFeatures::default()
409                        .multiview(requested_features.contains(wgt::Features::MULTIVIEW)),
410                )
411            } else {
412                None
413            },
414            sampler_ycbcr_conversion: if device_api_version >= vk::API_VERSION_1_1
415                || enabled_extensions.contains(&khr::sampler_ycbcr_conversion::NAME)
416            {
417                Some(
418                    vk::PhysicalDeviceSamplerYcbcrConversionFeatures::default(), // .sampler_ycbcr_conversion(requested_features.contains(wgt::Features::TEXTURE_FORMAT_NV12))
419                )
420            } else {
421                None
422            },
423            astc_hdr: if enabled_extensions.contains(&ext::texture_compression_astc_hdr::NAME) {
424                Some(
425                    vk::PhysicalDeviceTextureCompressionASTCHDRFeaturesEXT::default()
426                        .texture_compression_astc_hdr(true),
427                )
428            } else {
429                None
430            },
431            shader_float16_int8: match requested_features.contains(wgt::Features::SHADER_F16) {
432                shader_float16 if shader_float16 || private_caps.shader_int8 => Some(
433                    vk::PhysicalDeviceShaderFloat16Int8Features::default()
434                        .shader_float16(shader_float16)
435                        .shader_int8(private_caps.shader_int8),
436                ),
437                _ => None,
438            },
439            _16bit_storage: if requested_features.contains(wgt::Features::SHADER_F16) {
440                Some(
441                    vk::PhysicalDevice16BitStorageFeatures::default()
442                        .storage_buffer16_bit_access(true)
443                        .storage_input_output16(phd_features.supports_storage_input_output_16())
444                        .uniform_and_storage_buffer16_bit_access(true),
445                )
446            } else {
447                None
448            },
449            acceleration_structure: if enabled_extensions
450                .contains(&khr::acceleration_structure::NAME)
451            {
452                Some(
453                    vk::PhysicalDeviceAccelerationStructureFeaturesKHR::default()
454                        .acceleration_structure(true)
455                        .descriptor_binding_acceleration_structure_update_after_bind(
456                            requested_features
457                                .contains(wgt::Features::ACCELERATION_STRUCTURE_BINDING_ARRAY),
458                        ),
459                )
460            } else {
461                None
462            },
463            buffer_device_address: if enabled_extensions.contains(&khr::buffer_device_address::NAME)
464            {
465                Some(
466                    vk::PhysicalDeviceBufferDeviceAddressFeaturesKHR::default()
467                        .buffer_device_address(true),
468                )
469            } else {
470                None
471            },
472            ray_query: if enabled_extensions.contains(&khr::ray_query::NAME) {
473                Some(vk::PhysicalDeviceRayQueryFeaturesKHR::default().ray_query(true))
474            } else {
475                None
476            },
477            zero_initialize_workgroup_memory: if device_api_version >= vk::API_VERSION_1_3
478                || enabled_extensions.contains(&khr::zero_initialize_workgroup_memory::NAME)
479            {
480                Some(
481                    vk::PhysicalDeviceZeroInitializeWorkgroupMemoryFeatures::default()
482                        .shader_zero_initialize_workgroup_memory(
483                            private_caps.zero_initialize_workgroup_memory,
484                        ),
485                )
486            } else {
487                None
488            },
489            shader_atomic_int64: if device_api_version >= vk::API_VERSION_1_2
490                || enabled_extensions.contains(&khr::shader_atomic_int64::NAME)
491            {
492                let needed = requested_features.intersects(
493                    wgt::Features::SHADER_INT64_ATOMIC_ALL_OPS
494                        | wgt::Features::SHADER_INT64_ATOMIC_MIN_MAX,
495                );
496                Some(
497                    vk::PhysicalDeviceShaderAtomicInt64Features::default()
498                        .shader_buffer_int64_atomics(needed)
499                        .shader_shared_int64_atomics(needed),
500                )
501            } else {
502                None
503            },
504            shader_image_atomic_int64: if enabled_extensions
505                .contains(&ext::shader_image_atomic_int64::NAME)
506            {
507                let needed = requested_features.intersects(wgt::Features::TEXTURE_INT64_ATOMIC);
508                Some(
509                    vk::PhysicalDeviceShaderImageAtomicInt64FeaturesEXT::default()
510                        .shader_image_int64_atomics(needed),
511                )
512            } else {
513                None
514            },
515            shader_atomic_float: if enabled_extensions.contains(&ext::shader_atomic_float::NAME) {
516                let needed = requested_features.contains(wgt::Features::SHADER_FLOAT32_ATOMIC);
517                Some(
518                    vk::PhysicalDeviceShaderAtomicFloatFeaturesEXT::default()
519                        .shader_buffer_float32_atomics(needed)
520                        .shader_buffer_float32_atomic_add(needed),
521                )
522            } else {
523                None
524            },
525            subgroup_size_control: if device_api_version >= vk::API_VERSION_1_3
526                || enabled_extensions.contains(&ext::subgroup_size_control::NAME)
527            {
528                Some(
529                    vk::PhysicalDeviceSubgroupSizeControlFeatures::default()
530                        .subgroup_size_control(true),
531                )
532            } else {
533                None
534            },
535            position_fetch: if enabled_extensions.contains(&khr::ray_tracing_position_fetch::NAME) {
536                Some(
537                    vk::PhysicalDeviceRayTracingPositionFetchFeaturesKHR::default()
538                        .ray_tracing_position_fetch(true),
539                )
540            } else {
541                None
542            },
543            mesh_shader: if enabled_extensions.contains(&ext::mesh_shader::NAME) {
544                let needed = requested_features.contains(wgt::Features::EXPERIMENTAL_MESH_SHADER);
545                let multiview_needed =
546                    requested_features.contains(wgt::Features::EXPERIMENTAL_MESH_SHADER_MULTIVIEW);
547                Some(
548                    vk::PhysicalDeviceMeshShaderFeaturesEXT::default()
549                        .mesh_shader(needed)
550                        .task_shader(needed)
551                        .multiview_mesh_shader(multiview_needed),
552                )
553            } else {
554                None
555            },
556            maintenance4: if device_api_version >= vk::API_VERSION_1_3
557                || enabled_extensions.contains(&khr::maintenance4::NAME)
558            {
559                let needed = requested_features.contains(wgt::Features::EXPERIMENTAL_MESH_SHADER);
560                Some(vk::PhysicalDeviceMaintenance4Features::default().maintenance4(needed))
561            } else {
562                None
563            },
564            shader_integer_dot_product: if device_api_version >= vk::API_VERSION_1_3
565                || enabled_extensions.contains(&khr::shader_integer_dot_product::NAME)
566            {
567                Some(
568                    vk::PhysicalDeviceShaderIntegerDotProductFeaturesKHR::default()
569                        .shader_integer_dot_product(private_caps.shader_integer_dot_product),
570                )
571            } else {
572                None
573            },
574            shader_barycentrics: if enabled_extensions
575                .contains(&khr::fragment_shader_barycentric::NAME)
576            {
577                let needed = requested_features.intersects(
578                    wgt::Features::SHADER_BARYCENTRICS | wgt::Features::SHADER_PER_VERTEX,
579                );
580                Some(
581                    vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR::default()
582                        .fragment_shader_barycentric(needed),
583                )
584            } else {
585                None
586            },
587            portability_subset: if enabled_extensions.contains(&khr::portability_subset::NAME) {
588                let multisample_array_needed =
589                    requested_features.intersects(wgt::Features::MULTISAMPLE_ARRAY);
590
591                Some(
592                    vk::PhysicalDevicePortabilitySubsetFeaturesKHR::default()
593                        .multisample_array_image(multisample_array_needed),
594                )
595            } else {
596                None
597            },
598            cooperative_matrix: if enabled_extensions.contains(&khr::cooperative_matrix::NAME) {
599                let needed =
600                    requested_features.contains(wgt::Features::EXPERIMENTAL_COOPERATIVE_MATRIX);
601                Some(
602                    vk::PhysicalDeviceCooperativeMatrixFeaturesKHR::default()
603                        .cooperative_matrix(needed),
604                )
605            } else {
606                None
607            },
608            vulkan_memory_model: if device_api_version >= vk::API_VERSION_1_2
609                || enabled_extensions.contains(&khr::vulkan_memory_model::NAME)
610            {
611                let needed =
612                    requested_features.contains(wgt::Features::EXPERIMENTAL_COOPERATIVE_MATRIX);
613                Some(
614                    vk::PhysicalDeviceVulkanMemoryModelFeaturesKHR::default()
615                        .vulkan_memory_model(needed),
616                )
617            } else {
618                None
619            },
620            shader_draw_parameters: if device_api_version >= vk::API_VERSION_1_1 {
621                let needed = requested_features.contains(wgt::Features::SHADER_DRAW_INDEX);
622                Some(
623                    vk::PhysicalDeviceShaderDrawParametersFeatures::default()
624                        .shader_draw_parameters(needed),
625                )
626            } else {
627                None
628            },
629        }
630    }
631
632    /// Compute the wgpu [`Features`] and [`DownlevelFlags`] supported by a physical device.
633    ///
634    /// Given `self`, together with the instance and physical device it was
635    /// built from, and a `caps` also built from those, determine which wgpu
636    /// features and downlevel flags the device can support.
637    ///
638    /// [`Features`]: wgt::Features
639    /// [`DownlevelFlags`]: wgt::DownlevelFlags
640    fn to_wgpu(
641        &self,
642        instance: &ash::Instance,
643        phd: vk::PhysicalDevice,
644        caps: &PhysicalDeviceProperties,
645        queue_props: &vk::QueueFamilyProperties,
646    ) -> (wgt::Features, wgt::DownlevelFlags) {
647        use wgt::{DownlevelFlags as Df, Features as F};
648        let mut features = F::empty()
649            | F::MAPPABLE_PRIMARY_BUFFERS
650            | F::IMMEDIATES
651            | F::ADDRESS_MODE_CLAMP_TO_BORDER
652            | F::ADDRESS_MODE_CLAMP_TO_ZERO
653            | F::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES
654            | F::CLEAR_TEXTURE
655            | F::PIPELINE_CACHE
656            | F::SHADER_EARLY_DEPTH_TEST
657            | F::TEXTURE_ATOMIC
658            | F::PASSTHROUGH_SHADERS
659            | F::MEMORY_DECORATION_COHERENT
660            | F::MEMORY_DECORATION_VOLATILE;
661
662        let mut dl_flags = Df::COMPUTE_SHADERS
663            | Df::BASE_VERTEX
664            | Df::READ_ONLY_DEPTH_STENCIL
665            | Df::NON_POWER_OF_TWO_MIPMAPPED_TEXTURES
666            | Df::COMPARISON_SAMPLERS
667            | Df::VERTEX_STORAGE
668            | Df::FRAGMENT_STORAGE
669            | Df::DEPTH_TEXTURE_AND_BUFFER_COPIES
670            | Df::BUFFER_BINDINGS_NOT_16_BYTE_ALIGNED
671            | Df::UNRESTRICTED_INDEX_BUFFER
672            | Df::INDIRECT_EXECUTION
673            | Df::VIEW_FORMATS
674            | Df::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES
675            | Df::NONBLOCKING_QUERY_RESOLVE
676            | Df::SHADER_F16_IN_F32;
677
678        dl_flags.set(
679            Df::SURFACE_VIEW_FORMATS,
680            caps.supports_extension(khr::swapchain_mutable_format::NAME),
681        );
682        dl_flags.set(Df::CUBE_ARRAY_TEXTURES, self.core.image_cube_array != 0);
683        dl_flags.set(Df::ANISOTROPIC_FILTERING, self.core.sampler_anisotropy != 0);
684        dl_flags.set(
685            Df::FRAGMENT_WRITABLE_STORAGE,
686            self.core.fragment_stores_and_atomics != 0,
687        );
688        dl_flags.set(Df::MULTISAMPLED_SHADING, self.core.sample_rate_shading != 0);
689        dl_flags.set(Df::INDEPENDENT_BLEND, self.core.independent_blend != 0);
690        dl_flags.set(
691            Df::FULL_DRAW_INDEX_UINT32,
692            self.core.full_draw_index_uint32 != 0,
693        );
694        dl_flags.set(Df::DEPTH_BIAS_CLAMP, self.core.depth_bias_clamp != 0);
695
696        features.set(
697            F::TIMESTAMP_QUERY
698                | F::TIMESTAMP_QUERY_INSIDE_ENCODERS
699                | F::TIMESTAMP_QUERY_INSIDE_PASSES,
700            // Vulkan strictly defines this as either 36-64, or zero.
701            queue_props.timestamp_valid_bits >= 36,
702        );
703        features.set(
704            F::INDIRECT_FIRST_INSTANCE,
705            self.core.draw_indirect_first_instance != 0,
706        );
707        //if self.core.dual_src_blend != 0
708        features.set(F::POLYGON_MODE_LINE, self.core.fill_mode_non_solid != 0);
709        features.set(F::POLYGON_MODE_POINT, self.core.fill_mode_non_solid != 0);
710        //if self.core.depth_bounds != 0 {
711        //if self.core.alpha_to_one != 0 {
712        //if self.core.multi_viewport != 0 {
713        features.set(
714            F::TEXTURE_COMPRESSION_ETC2,
715            self.core.texture_compression_etc2 != 0,
716        );
717        features.set(
718            F::TEXTURE_COMPRESSION_ASTC,
719            self.core.texture_compression_astc_ldr != 0,
720        );
721        features.set(
722            F::TEXTURE_COMPRESSION_BC,
723            self.core.texture_compression_bc != 0,
724        );
725        features.set(
726            F::TEXTURE_COMPRESSION_BC_SLICED_3D,
727            self.core.texture_compression_bc != 0, // BC guarantees Sliced 3D
728        );
729        features.set(
730            F::PIPELINE_STATISTICS_QUERY,
731            self.core.pipeline_statistics_query != 0,
732        );
733        features.set(
734            F::VERTEX_WRITABLE_STORAGE,
735            self.core.vertex_pipeline_stores_and_atomics != 0,
736        );
737
738        features.set(F::SHADER_F64, self.core.shader_float64 != 0);
739        features.set(F::SHADER_INT64, self.core.shader_int64 != 0);
740        features.set(F::SHADER_I16, self.core.shader_int16 != 0);
741
742        features.set(F::PRIMITIVE_INDEX, self.core.geometry_shader != 0);
743
744        if let Some(ref shader_atomic_int64) = self.shader_atomic_int64 {
745            features.set(
746                F::SHADER_INT64_ATOMIC_ALL_OPS | F::SHADER_INT64_ATOMIC_MIN_MAX,
747                shader_atomic_int64.shader_buffer_int64_atomics != 0
748                    && shader_atomic_int64.shader_shared_int64_atomics != 0,
749            );
750        }
751
752        if let Some(ref shader_image_atomic_int64) = self.shader_image_atomic_int64 {
753            features.set(
754                F::TEXTURE_INT64_ATOMIC,
755                shader_image_atomic_int64
756                    .shader_image_int64_atomics(true)
757                    .shader_image_int64_atomics
758                    != 0,
759            );
760        }
761
762        if let Some(ref shader_atomic_float) = self.shader_atomic_float {
763            features.set(
764                F::SHADER_FLOAT32_ATOMIC,
765                shader_atomic_float.shader_buffer_float32_atomics != 0
766                    && shader_atomic_float.shader_buffer_float32_atomic_add != 0,
767            );
768        }
769
770        if let Some(ref shader_barycentrics) = self.shader_barycentrics {
771            features.set(
772                F::SHADER_BARYCENTRICS | F::SHADER_PER_VERTEX,
773                shader_barycentrics.fragment_shader_barycentric != 0,
774            );
775        }
776
777        //if caps.supports_extension(khr::sampler_mirror_clamp_to_edge::NAME) {
778        //if caps.supports_extension(ext::sampler_filter_minmax::NAME) {
779        features.set(
780            F::MULTI_DRAW_INDIRECT_COUNT,
781            caps.supports_extension(khr::draw_indirect_count::NAME),
782        );
783        features.set(
784            F::CONSERVATIVE_RASTERIZATION,
785            caps.supports_extension(ext::conservative_rasterization::NAME),
786        );
787        features.set(
788            F::EXPERIMENTAL_RAY_HIT_VERTEX_RETURN,
789            caps.supports_extension(khr::ray_tracing_position_fetch::NAME),
790        );
791
792        if let Some(ref descriptor_indexing) = self.descriptor_indexing {
793            // We use update-after-bind descriptors for all bind groups containing binding arrays.
794            //
795            // In those bind groups, we allow all binding types except uniform buffers to be present.
796            //
797            // As we can only switch between update-after-bind and not on a per bind group basis,
798            // all supported binding types need to be able to be marked update after bind.
799            //
800            // As such, we enable all features as a whole, rather individually.
801            let supports_descriptor_indexing =
802                // Sampled Images
803                descriptor_indexing.shader_sampled_image_array_non_uniform_indexing != 0
804                    && descriptor_indexing.descriptor_binding_sampled_image_update_after_bind != 0
805                    // Storage Images
806                    && descriptor_indexing.shader_storage_image_array_non_uniform_indexing != 0
807                    && descriptor_indexing.descriptor_binding_storage_image_update_after_bind != 0
808                    // Storage Buffers
809                    && descriptor_indexing.shader_storage_buffer_array_non_uniform_indexing != 0
810                    && descriptor_indexing.descriptor_binding_storage_buffer_update_after_bind != 0;
811
812            let descriptor_indexing_features = F::BUFFER_BINDING_ARRAY
813                | F::TEXTURE_BINDING_ARRAY
814                | F::STORAGE_RESOURCE_BINDING_ARRAY
815                | F::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING
816                | F::STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING;
817
818            features.set(descriptor_indexing_features, supports_descriptor_indexing);
819
820            let supports_partially_bound =
821                descriptor_indexing.descriptor_binding_partially_bound != 0;
822
823            features.set(F::PARTIALLY_BOUND_BINDING_ARRAY, supports_partially_bound);
824        }
825
826        features.set(F::DEPTH_CLIP_CONTROL, self.core.depth_clamp != 0);
827        features.set(F::DUAL_SOURCE_BLENDING, self.core.dual_src_blend != 0);
828        features.set(F::CLIP_DISTANCES, self.core.shader_clip_distance != 0);
829
830        if let Some(ref multiview) = self.multiview {
831            features.set(F::MULTIVIEW, multiview.multiview != 0);
832            features.set(F::SELECTIVE_MULTIVIEW, multiview.multiview != 0);
833        }
834
835        features.set(
836            F::TEXTURE_FORMAT_16BIT_NORM,
837            is_format_16bit_norm_supported(instance, phd),
838        );
839
840        if let Some(ref astc_hdr) = self.astc_hdr {
841            features.set(
842                F::TEXTURE_COMPRESSION_ASTC_HDR,
843                astc_hdr.texture_compression_astc_hdr != 0,
844            );
845        }
846
847        if self.core.texture_compression_astc_ldr != 0 {
848            features.set(
849                F::TEXTURE_COMPRESSION_ASTC_SLICED_3D,
850                supports_astc_3d(instance, phd),
851            );
852        }
853
854        if let (Some(ref f16_i8), Some(ref bit16)) = (self.shader_float16_int8, self._16bit_storage)
855        {
856            // Note `storage_input_output16` is not required, we polyfill `f16` I/O using `f32`
857            // types when this capability is not available
858            features.set(
859                F::SHADER_F16,
860                f16_i8.shader_float16 != 0
861                    && bit16.storage_buffer16_bit_access != 0
862                    && bit16.uniform_and_storage_buffer16_bit_access != 0,
863            );
864        }
865
866        if let Some(ref subgroup) = caps.subgroup {
867            if (caps.device_api_version >= vk::API_VERSION_1_3
868                || caps.supports_extension(ext::subgroup_size_control::NAME))
869                && subgroup.supported_operations.contains(
870                    vk::SubgroupFeatureFlags::BASIC
871                        | vk::SubgroupFeatureFlags::VOTE
872                        | vk::SubgroupFeatureFlags::ARITHMETIC
873                        | vk::SubgroupFeatureFlags::BALLOT
874                        | vk::SubgroupFeatureFlags::SHUFFLE
875                        | vk::SubgroupFeatureFlags::SHUFFLE_RELATIVE
876                        | vk::SubgroupFeatureFlags::QUAD,
877                )
878            {
879                features.set(
880                    F::SUBGROUP,
881                    subgroup
882                        .supported_stages
883                        .contains(vk::ShaderStageFlags::COMPUTE | vk::ShaderStageFlags::FRAGMENT),
884                );
885                features.set(
886                    F::SUBGROUP_VERTEX,
887                    subgroup
888                        .supported_stages
889                        .contains(vk::ShaderStageFlags::VERTEX),
890                );
891                features.insert(F::SUBGROUP_BARRIER);
892            }
893        }
894
895        let supports_depth_format = |format| {
896            supports_format(
897                instance,
898                phd,
899                format,
900                vk::ImageTiling::OPTIMAL,
901                depth_stencil_required_flags(),
902            )
903        };
904
905        let texture_s8 = supports_depth_format(vk::Format::S8_UINT);
906        let texture_d32 = supports_depth_format(vk::Format::D32_SFLOAT);
907        let texture_d24_s8 = supports_depth_format(vk::Format::D24_UNORM_S8_UINT);
908        let texture_d32_s8 = supports_depth_format(vk::Format::D32_SFLOAT_S8_UINT);
909
910        let stencil8 = texture_s8 || texture_d24_s8;
911        let depth24_plus_stencil8 = texture_d24_s8 || texture_d32_s8;
912
913        dl_flags.set(
914            Df::WEBGPU_TEXTURE_FORMAT_SUPPORT,
915            stencil8 && depth24_plus_stencil8 && texture_d32,
916        );
917
918        features.set(F::DEPTH32FLOAT_STENCIL8, texture_d32_s8);
919
920        let supports_acceleration_structures = caps
921            .supports_extension(khr::deferred_host_operations::NAME)
922            && caps.supports_extension(khr::acceleration_structure::NAME)
923            && caps.supports_extension(khr::buffer_device_address::NAME);
924
925        let supports_ray_query =
926            supports_acceleration_structures && caps.supports_extension(khr::ray_query::NAME);
927        let supports_acceleration_structure_binding_array = supports_ray_query
928            && self
929                .acceleration_structure
930                .as_ref()
931                .is_some_and(|features| {
932                    features.descriptor_binding_acceleration_structure_update_after_bind != 0
933                });
934
935        features.set(
936            F::EXPERIMENTAL_RAY_QUERY
937            // Although this doesn't really require ray queries, it does not make sense to be enabled if acceleration structures
938            // aren't enabled.
939                | F::EXTENDED_ACCELERATION_STRUCTURE_VERTEX_FORMATS,
940            supports_ray_query,
941        );
942
943        // Binding arrays of TLAS are supported on Vulkan when ray queries are supported.
944        //
945        // Note: this flag is used for shader-side `binding_array<acceleration_structure>` as well as
946        // allowing `BindGroupLayoutEntry::count = Some(...)` for `BindingType::AccelerationStructure`.
947        features.set(
948            F::ACCELERATION_STRUCTURE_BINDING_ARRAY,
949            supports_acceleration_structure_binding_array,
950        );
951
952        let rg11b10ufloat_renderable = supports_format(
953            instance,
954            phd,
955            vk::Format::B10G11R11_UFLOAT_PACK32,
956            vk::ImageTiling::OPTIMAL,
957            vk::FormatFeatureFlags::COLOR_ATTACHMENT
958                | vk::FormatFeatureFlags::COLOR_ATTACHMENT_BLEND,
959        );
960        features.set(F::RG11B10UFLOAT_RENDERABLE, rg11b10ufloat_renderable);
961
962        features.set(
963            F::BGRA8UNORM_STORAGE,
964            supports_bgra8unorm_storage(instance, phd, caps.device_api_version),
965        );
966
967        features.set(
968            F::FLOAT32_FILTERABLE,
969            is_float32_filterable_supported(instance, phd),
970        );
971
972        features.set(
973            F::FLOAT32_BLENDABLE,
974            is_float32_blendable_supported(instance, phd),
975        );
976
977        if let Some(ref _sampler_ycbcr_conversion) = self.sampler_ycbcr_conversion {
978            features.set(
979                F::TEXTURE_FORMAT_NV12,
980                supports_format(
981                    instance,
982                    phd,
983                    vk::Format::G8_B8R8_2PLANE_420_UNORM,
984                    vk::ImageTiling::OPTIMAL,
985                    vk::FormatFeatureFlags::SAMPLED_IMAGE
986                        | vk::FormatFeatureFlags::TRANSFER_SRC
987                        | vk::FormatFeatureFlags::TRANSFER_DST,
988                ) && !caps
989                    .driver
990                    .map(|driver| driver.driver_id == vk::DriverId::MOLTENVK)
991                    .unwrap_or_default(),
992            );
993        }
994
995        if let Some(ref _sampler_ycbcr_conversion) = self.sampler_ycbcr_conversion {
996            features.set(
997                F::TEXTURE_FORMAT_P010,
998                supports_format(
999                    instance,
1000                    phd,
1001                    vk::Format::G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16,
1002                    vk::ImageTiling::OPTIMAL,
1003                    vk::FormatFeatureFlags::SAMPLED_IMAGE
1004                        | vk::FormatFeatureFlags::TRANSFER_SRC
1005                        | vk::FormatFeatureFlags::TRANSFER_DST,
1006                ) && !caps
1007                    .driver
1008                    .map(|driver| driver.driver_id == vk::DriverId::MOLTENVK)
1009                    .unwrap_or_default(),
1010            );
1011        }
1012
1013        features.set(
1014            F::VULKAN_GOOGLE_DISPLAY_TIMING,
1015            caps.supports_extension(google::display_timing::NAME),
1016        );
1017
1018        features.set(
1019            F::VULKAN_EXTERNAL_MEMORY_WIN32,
1020            caps.supports_extension(khr::external_memory_win32::NAME),
1021        );
1022        features.set(
1023            F::EXPERIMENTAL_MESH_SHADER,
1024            caps.supports_extension(ext::mesh_shader::NAME),
1025        );
1026        features.set(
1027            F::EXPERIMENTAL_MESH_SHADER_POINTS,
1028            caps.supports_extension(ext::mesh_shader::NAME),
1029        );
1030        if let Some(ref mesh_shader) = self.mesh_shader {
1031            features.set(
1032                F::EXPERIMENTAL_MESH_SHADER_MULTIVIEW,
1033                mesh_shader.multiview_mesh_shader != 0,
1034            );
1035        }
1036
1037        // Not supported by default by `VK_KHR_portability_subset`, which we use on apple platforms.
1038        features.set(
1039            F::MULTISAMPLE_ARRAY,
1040            self.portability_subset
1041                .map(|p| p.multisample_array_image == vk::TRUE)
1042                .unwrap_or(true),
1043        );
1044        // Enable cooperative matrix if any configuration is supported
1045        features.set(
1046            F::EXPERIMENTAL_COOPERATIVE_MATRIX,
1047            !caps.cooperative_matrix_properties.is_empty(),
1048        );
1049
1050        features.set(
1051            F::SHADER_DRAW_INDEX,
1052            self.shader_draw_parameters
1053                .is_some_and(|a| a.shader_draw_parameters != 0)
1054                || caps.supports_extension(c"VK_KHR_shader_draw_parameters"),
1055        );
1056
1057        (features, dl_flags)
1058    }
1059}
1060
1061/// Vulkan "properties" structures gathered about a physical device.
1062///
1063/// This structure holds the properties of a [`vk::PhysicalDevice`]:
1064/// - the standard Vulkan device properties
1065/// - the `VkExtensionProperties` structs for all available extensions, and
1066/// - the per-extension properties structures for the available extensions that
1067///   `wgpu` cares about.
1068///
1069/// Generally, if you get it from any of these functions, it's stored
1070/// here:
1071/// - `vkEnumerateDeviceExtensionProperties`
1072/// - `vkGetPhysicalDeviceProperties`
1073/// - `vkGetPhysicalDeviceProperties2`
1074///
1075/// This also includes a copy of the device API version, since we can
1076/// use that as a shortcut for searching for an extension, if the
1077/// extension has been promoted to core in the current version.
1078///
1079/// This does not include device features; for those, see
1080/// [`PhysicalDeviceFeatures`].
1081#[derive(Default, Debug)]
1082pub struct PhysicalDeviceProperties {
1083    /// Extensions supported by the `vk::PhysicalDevice`,
1084    /// as returned by `vkEnumerateDeviceExtensionProperties`.
1085    supported_extensions: Vec<vk::ExtensionProperties>,
1086
1087    /// Properties of the `vk::PhysicalDevice`, as returned by
1088    /// `vkGetPhysicalDeviceProperties`.
1089    properties: vk::PhysicalDeviceProperties,
1090
1091    /// Additional `vk::PhysicalDevice` properties from the
1092    /// `VK_KHR_maintenance3` extension, promoted to Vulkan 1.1.
1093    maintenance_3: Option<vk::PhysicalDeviceMaintenance3Properties<'static>>,
1094
1095    /// Additional `vk::PhysicalDevice` properties from the
1096    /// `VK_KHR_maintenance4` extension, promoted to Vulkan 1.3.
1097    maintenance_4: Option<vk::PhysicalDeviceMaintenance4Properties<'static>>,
1098
1099    /// Additional `vk::PhysicalDevice` properties from the
1100    /// `VK_EXT_descriptor_indexing` extension, promoted to Vulkan 1.2.
1101    descriptor_indexing: Option<vk::PhysicalDeviceDescriptorIndexingPropertiesEXT<'static>>,
1102
1103    /// Additional `vk::PhysicalDevice` properties from the
1104    /// `VK_KHR_acceleration_structure` extension.
1105    acceleration_structure: Option<vk::PhysicalDeviceAccelerationStructurePropertiesKHR<'static>>,
1106
1107    /// Additional `vk::PhysicalDevice` properties from the
1108    /// `VK_KHR_driver_properties` extension, promoted to Vulkan 1.2.
1109    driver: Option<vk::PhysicalDeviceDriverPropertiesKHR<'static>>,
1110
1111    /// Additional `vk::PhysicalDevice` properties from Vulkan 1.1.
1112    subgroup: Option<vk::PhysicalDeviceSubgroupProperties<'static>>,
1113
1114    /// Additional `vk::PhysicalDevice` properties from the
1115    /// `VK_EXT_subgroup_size_control` extension, promoted to Vulkan 1.3.
1116    subgroup_size_control: Option<vk::PhysicalDeviceSubgroupSizeControlProperties<'static>>,
1117
1118    /// Additional `vk::PhysicalDevice` properties from the
1119    /// `VK_EXT_robustness2` extension.
1120    robustness2: Option<vk::PhysicalDeviceRobustness2PropertiesEXT<'static>>,
1121
1122    /// Additional `vk::PhysicalDevice` properties from the
1123    /// `VK_EXT_mesh_shader` extension.
1124    mesh_shader: Option<vk::PhysicalDeviceMeshShaderPropertiesEXT<'static>>,
1125
1126    /// Additional `vk::PhysicalDevice` properties from the
1127    /// `VK_KHR_multiview` extension.
1128    multiview: Option<vk::PhysicalDeviceMultiviewPropertiesKHR<'static>>,
1129
1130    /// `VK_EXT_pci_bus_info` extension.
1131    pci_bus_info: Option<vk::PhysicalDevicePCIBusInfoPropertiesEXT<'static>>,
1132
1133    /// The device API version.
1134    ///
1135    /// Which is the version of Vulkan supported for device-level functionality.
1136    ///
1137    /// It is associated with a `VkPhysicalDevice` and its children.
1138    device_api_version: u32,
1139
1140    /// Supported cooperative matrix configurations.
1141    ///
1142    /// This is determined by querying `vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR`.
1143    cooperative_matrix_properties: Vec<wgt::CooperativeMatrixProperties>,
1144}
1145
1146impl PhysicalDeviceProperties {
1147    pub fn properties(&self) -> vk::PhysicalDeviceProperties {
1148        self.properties
1149    }
1150
1151    pub fn supports_extension(&self, extension: &CStr) -> bool {
1152        self.supported_extensions
1153            .iter()
1154            .any(|ep| ep.extension_name_as_c_str() == Ok(extension))
1155    }
1156
1157    /// Map `requested_features` to the list of Vulkan extension strings required to create the logical device.
1158    fn get_required_extensions(&self, requested_features: wgt::Features) -> Vec<&'static CStr> {
1159        let mut extensions = Vec::new();
1160
1161        // Note that quite a few extensions depend on the `VK_KHR_get_physical_device_properties2` instance extension.
1162        // We enable `VK_KHR_get_physical_device_properties2` unconditionally (if available).
1163
1164        // Require `VK_KHR_swapchain`
1165        extensions.push(khr::swapchain::NAME);
1166
1167        if self.device_api_version < vk::API_VERSION_1_1 {
1168            // Require `VK_KHR_maintenance1`
1169            extensions.push(khr::maintenance1::NAME);
1170
1171            // Optional `VK_KHR_maintenance2`
1172            if self.supports_extension(khr::maintenance2::NAME) {
1173                extensions.push(khr::maintenance2::NAME);
1174            }
1175
1176            // Optional `VK_KHR_maintenance3`
1177            if self.supports_extension(khr::maintenance3::NAME) {
1178                extensions.push(khr::maintenance3::NAME);
1179            }
1180
1181            // Require `VK_KHR_storage_buffer_storage_class`
1182            extensions.push(khr::storage_buffer_storage_class::NAME);
1183
1184            // Require `VK_KHR_multiview` if the associated feature was requested
1185            if requested_features.contains(wgt::Features::MULTIVIEW) {
1186                extensions.push(khr::multiview::NAME);
1187            }
1188
1189            // Require `VK_KHR_sampler_ycbcr_conversion` if the associated feature was requested
1190            if requested_features.contains(wgt::Features::TEXTURE_FORMAT_NV12) {
1191                extensions.push(khr::sampler_ycbcr_conversion::NAME);
1192            }
1193
1194            // Require `VK_KHR_16bit_storage` if the feature `SHADER_F16` was requested
1195            if requested_features.contains(wgt::Features::SHADER_F16) {
1196                // - Feature `SHADER_F16` also requires `VK_KHR_shader_float16_int8`, but we always
1197                //   require that anyway (if it is available) below.
1198                // - `VK_KHR_16bit_storage` requires `VK_KHR_storage_buffer_storage_class`, however
1199                //   we require that one already.
1200                extensions.push(khr::_16bit_storage::NAME);
1201            }
1202
1203            if requested_features.contains(wgt::Features::SHADER_DRAW_INDEX) {
1204                extensions.push(khr::shader_draw_parameters::NAME);
1205            }
1206        }
1207
1208        if self.device_api_version < vk::API_VERSION_1_2 {
1209            // Optional `VK_KHR_image_format_list`
1210            if self.supports_extension(khr::image_format_list::NAME) {
1211                extensions.push(khr::image_format_list::NAME);
1212            }
1213
1214            // Optional `VK_KHR_driver_properties`
1215            if self.supports_extension(khr::driver_properties::NAME) {
1216                extensions.push(khr::driver_properties::NAME);
1217            }
1218
1219            // Optional `VK_KHR_timeline_semaphore`
1220            if self.supports_extension(khr::timeline_semaphore::NAME) {
1221                extensions.push(khr::timeline_semaphore::NAME);
1222            }
1223
1224            // Require `VK_EXT_descriptor_indexing` if one of the associated features was requested
1225            if requested_features.intersects(INDEXING_FEATURES) {
1226                extensions.push(ext::descriptor_indexing::NAME);
1227            }
1228
1229            // Always require `VK_KHR_shader_float16_int8` if available as it enables
1230            // Int8 optimizations. Also require it even if it's not available but
1231            // requested so that we get a corresponding error message.
1232            if requested_features.contains(wgt::Features::SHADER_F16)
1233                || self.supports_extension(khr::shader_float16_int8::NAME)
1234            {
1235                extensions.push(khr::shader_float16_int8::NAME);
1236            }
1237
1238            if requested_features.intersects(wgt::Features::EXPERIMENTAL_MESH_SHADER) {
1239                extensions.push(khr::spirv_1_4::NAME);
1240            }
1241
1242            //extensions.push(khr::sampler_mirror_clamp_to_edge::NAME);
1243            //extensions.push(ext::sampler_filter_minmax::NAME);
1244        }
1245
1246        if self.device_api_version < vk::API_VERSION_1_3 {
1247            // Optional `VK_KHR_maintenance4`
1248            if self.supports_extension(khr::maintenance4::NAME) {
1249                extensions.push(khr::maintenance4::NAME);
1250            }
1251
1252            // Optional `VK_EXT_image_robustness`
1253            if self.supports_extension(ext::image_robustness::NAME) {
1254                extensions.push(ext::image_robustness::NAME);
1255            }
1256
1257            // Require `VK_EXT_subgroup_size_control` if the associated feature was requested
1258            if requested_features.contains(wgt::Features::SUBGROUP) {
1259                extensions.push(ext::subgroup_size_control::NAME);
1260            }
1261
1262            // Optional `VK_KHR_shader_integer_dot_product`
1263            if self.supports_extension(khr::shader_integer_dot_product::NAME) {
1264                extensions.push(khr::shader_integer_dot_product::NAME);
1265            }
1266        }
1267
1268        // Optional `VK_KHR_swapchain_mutable_format`
1269        if self.supports_extension(khr::swapchain_mutable_format::NAME) {
1270            extensions.push(khr::swapchain_mutable_format::NAME);
1271        }
1272
1273        // Optional `VK_EXT_robustness2`
1274        if self.supports_extension(ext::robustness2::NAME) {
1275            extensions.push(ext::robustness2::NAME);
1276        }
1277
1278        // Optional `VK_KHR_external_memory_win32`
1279        if self.supports_extension(khr::external_memory_win32::NAME) {
1280            extensions.push(khr::external_memory_win32::NAME);
1281        }
1282
1283        // Optional `VK_KHR_external_memory_fd`
1284        if self.supports_extension(khr::external_memory_fd::NAME) {
1285            extensions.push(khr::external_memory_fd::NAME);
1286        }
1287
1288        // Optional `VK_EXT_external_memory_dma`
1289        if self.supports_extension(ext::external_memory_dma_buf::NAME) {
1290            extensions.push(ext::external_memory_dma_buf::NAME);
1291        }
1292
1293        // Optional `VK_EXT_memory_budget`
1294        if self.supports_extension(ext::memory_budget::NAME) {
1295            extensions.push(ext::memory_budget::NAME);
1296        } else {
1297            log::debug!("VK_EXT_memory_budget is not available.")
1298        }
1299
1300        // Require `VK_KHR_draw_indirect_count` if the associated feature was requested
1301        // Even though Vulkan 1.2 has promoted the extension to core, we must require the extension to avoid
1302        // large amounts of spaghetti involved with using PhysicalDeviceVulkan12Features.
1303        if requested_features.contains(wgt::Features::MULTI_DRAW_INDIRECT_COUNT) {
1304            extensions.push(khr::draw_indirect_count::NAME);
1305        }
1306
1307        // Require `VK_KHR_deferred_host_operations`, `VK_KHR_acceleration_structure` `VK_KHR_buffer_device_address` (for acceleration structures) and`VK_KHR_ray_query` if `EXPERIMENTAL_RAY_QUERY` was requested
1308        if requested_features.contains(wgt::Features::EXPERIMENTAL_RAY_QUERY) {
1309            extensions.push(khr::deferred_host_operations::NAME);
1310            extensions.push(khr::acceleration_structure::NAME);
1311            extensions.push(khr::buffer_device_address::NAME);
1312            extensions.push(khr::ray_query::NAME);
1313        }
1314
1315        if requested_features.contains(wgt::Features::EXPERIMENTAL_RAY_HIT_VERTEX_RETURN) {
1316            extensions.push(khr::ray_tracing_position_fetch::NAME)
1317        }
1318
1319        // Require `VK_EXT_conservative_rasterization` if the associated feature was requested
1320        if requested_features.contains(wgt::Features::CONSERVATIVE_RASTERIZATION) {
1321            extensions.push(ext::conservative_rasterization::NAME);
1322        }
1323
1324        // Require `VK_KHR_portability_subset` on macOS/iOS
1325        #[cfg(target_vendor = "apple")]
1326        extensions.push(khr::portability_subset::NAME);
1327
1328        // Require `VK_EXT_texture_compression_astc_hdr` if the associated feature was requested
1329        if requested_features.contains(wgt::Features::TEXTURE_COMPRESSION_ASTC_HDR) {
1330            extensions.push(ext::texture_compression_astc_hdr::NAME);
1331        }
1332
1333        // Require `VK_KHR_shader_atomic_int64` if the associated feature was requested
1334        if requested_features.intersects(
1335            wgt::Features::SHADER_INT64_ATOMIC_ALL_OPS | wgt::Features::SHADER_INT64_ATOMIC_MIN_MAX,
1336        ) {
1337            extensions.push(khr::shader_atomic_int64::NAME);
1338        }
1339
1340        // Require `VK_EXT_shader_image_atomic_int64` if the associated feature was requested
1341        if requested_features.intersects(wgt::Features::TEXTURE_INT64_ATOMIC) {
1342            extensions.push(ext::shader_image_atomic_int64::NAME);
1343        }
1344
1345        // Require `VK_EXT_shader_atomic_float` if the associated feature was requested
1346        if requested_features.contains(wgt::Features::SHADER_FLOAT32_ATOMIC) {
1347            extensions.push(ext::shader_atomic_float::NAME);
1348        }
1349
1350        // Require VK_GOOGLE_display_timing if the associated feature was requested
1351        if requested_features.contains(wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING) {
1352            extensions.push(google::display_timing::NAME);
1353        }
1354
1355        if requested_features.contains(wgt::Features::EXPERIMENTAL_MESH_SHADER) {
1356            extensions.push(ext::mesh_shader::NAME);
1357        }
1358
1359        // Require `VK_KHR_fragment_shader_barycentric` if an associated feature was requested
1360        // Vulkan bundles both barycentrics and per-vertex attributes under the same feature.
1361        if requested_features
1362            .intersects(wgt::Features::SHADER_BARYCENTRICS | wgt::Features::SHADER_PER_VERTEX)
1363        {
1364            extensions.push(khr::fragment_shader_barycentric::NAME);
1365        }
1366
1367        // Require `VK_KHR_cooperative_matrix` if the associated feature was requested
1368        if requested_features.contains(wgt::Features::EXPERIMENTAL_COOPERATIVE_MATRIX) {
1369            extensions.push(khr::cooperative_matrix::NAME);
1370        }
1371
1372        extensions
1373    }
1374
1375    fn to_wgpu_limits(&self) -> wgt::Limits {
1376        let limits = &self.properties.limits;
1377
1378        let (
1379            mut max_task_mesh_workgroup_total_count,
1380            mut max_task_mesh_workgroups_per_dimension,
1381            mut max_task_invocations_per_workgroup,
1382            mut max_task_invocations_per_dimension,
1383            mut max_mesh_invocations_per_workgroup,
1384            mut max_mesh_invocations_per_dimension,
1385            mut max_task_payload_size,
1386            mut max_mesh_output_vertices,
1387            mut max_mesh_output_primitives,
1388            mut max_mesh_output_layers,
1389            mut max_mesh_multiview_view_count,
1390        ) = Default::default();
1391        if let Some(m) = self.mesh_shader {
1392            max_task_mesh_workgroup_total_count = m
1393                .max_task_work_group_total_count
1394                .min(m.max_mesh_work_group_total_count);
1395            max_task_mesh_workgroups_per_dimension = m
1396                .max_task_work_group_count
1397                .into_iter()
1398                .chain(m.max_mesh_work_group_count)
1399                .min()
1400                .unwrap();
1401            max_task_invocations_per_workgroup = m.max_task_work_group_invocations;
1402            max_task_invocations_per_dimension =
1403                m.max_task_work_group_size.into_iter().min().unwrap();
1404            max_mesh_invocations_per_workgroup = m.max_mesh_work_group_invocations;
1405            max_mesh_invocations_per_dimension =
1406                m.max_mesh_work_group_size.into_iter().min().unwrap();
1407            max_task_payload_size = m.max_task_payload_size;
1408            max_mesh_output_vertices = m.max_mesh_output_vertices;
1409            max_mesh_output_primitives = m.max_mesh_output_primitives;
1410            max_mesh_output_layers = m.max_mesh_output_layers;
1411            max_mesh_multiview_view_count = m.max_mesh_multiview_view_count;
1412        }
1413
1414        let max_memory_allocation_size = self
1415            .maintenance_3
1416            .map(|maintenance_3| maintenance_3.max_memory_allocation_size)
1417            .unwrap_or(u64::MAX);
1418        let max_buffer_size = self
1419            .maintenance_4
1420            .map(|maintenance_4| maintenance_4.max_buffer_size)
1421            .unwrap_or(u64::MAX);
1422        let max_buffer_size = max_buffer_size.min(max_memory_allocation_size);
1423
1424        // Prevent very large buffers on mesa and most android devices, and in all cases
1425        // don't risk confusing JS by exceeding the range of a double.
1426        let is_nvidia = self.properties.vendor_id == crate::auxil::db::nvidia::VENDOR;
1427        let max_buffer_size_cap =
1428            if (cfg!(target_os = "linux") || cfg!(target_os = "android")) && !is_nvidia {
1429                i32::MAX as u64
1430            } else {
1431                1u64 << 52
1432            };
1433
1434        let max_buffer_size = max_buffer_size.min(max_buffer_size_cap);
1435
1436        let mut max_binding_array_elements = 0;
1437        let mut max_sampler_binding_array_elements = 0;
1438        if let Some(ref descriptor_indexing) = self.descriptor_indexing {
1439            max_binding_array_elements = descriptor_indexing
1440                .max_descriptor_set_update_after_bind_sampled_images
1441                .min(descriptor_indexing.max_descriptor_set_update_after_bind_storage_images)
1442                .min(descriptor_indexing.max_descriptor_set_update_after_bind_storage_buffers)
1443                .min(descriptor_indexing.max_per_stage_descriptor_update_after_bind_sampled_images)
1444                .min(descriptor_indexing.max_per_stage_descriptor_update_after_bind_storage_images)
1445                .min(
1446                    descriptor_indexing.max_per_stage_descriptor_update_after_bind_storage_buffers,
1447                );
1448
1449            max_sampler_binding_array_elements = descriptor_indexing
1450                .max_descriptor_set_update_after_bind_samplers
1451                .min(descriptor_indexing.max_per_stage_descriptor_update_after_bind_samplers);
1452        }
1453
1454        const MAX_SHADER_STAGES_PER_PIPELINE: u32 = 2;
1455
1456        // When summed, the 3 limits below must be under Vulkan's maxFragmentCombinedOutputResources.
1457        // https://gpuweb.github.io/gpuweb/correspondence/#vulkan-maxFragmentCombinedOutputResources
1458        //
1459        // - maxStorageTexturesPerShaderStage, WebGPU default: 4
1460        // - maxStorageBuffersPerShaderStage, WebGPU default: 8
1461        // - maxColorAttachments, WebGPU default: 8
1462        //
1463        // However, maxFragmentCombinedOutputResources should be ignored on
1464        // intel/nvidia/amd/imgtec since it's not reported correctly.
1465        //
1466        // https://github.com/gpuweb/gpuweb/issues/3631#issuecomment-1498747606
1467        // https://github.com/gpuweb/gpuweb/issues/4018
1468        let mut max_storage_textures_per_shader_stage = limits
1469            .max_per_stage_descriptor_storage_images
1470            .min(limits.max_descriptor_set_storage_images / MAX_SHADER_STAGES_PER_PIPELINE);
1471        let mut max_storage_buffers_per_shader_stage = limits
1472            .max_per_stage_descriptor_storage_buffers
1473            .min(limits.max_descriptor_set_storage_buffers / MAX_SHADER_STAGES_PER_PIPELINE);
1474        let mut max_color_attachments = limits
1475            .max_color_attachments
1476            .min(limits.max_fragment_output_attachments);
1477
1478        let ignore_max_fragment_combined_output_resources = [
1479            crate::auxil::db::intel::VENDOR,
1480            crate::auxil::db::nvidia::VENDOR,
1481            crate::auxil::db::amd::VENDOR,
1482            crate::auxil::db::imgtec::VENDOR,
1483        ]
1484        .contains(&self.properties.vendor_id);
1485
1486        if !ignore_max_fragment_combined_output_resources {
1487            crate::auxil::cap_limits_to_be_under_the_sum_limit(
1488                [
1489                    &mut max_storage_textures_per_shader_stage,
1490                    &mut max_storage_buffers_per_shader_stage,
1491                    &mut max_color_attachments,
1492                ],
1493                limits.max_fragment_combined_output_resources,
1494            );
1495        }
1496
1497        // When summed, the 5 limits below must be under Vulkan's maxPerStageResources.
1498        //
1499        // - maxUniformBuffersPerShaderStage, WebGPU default: 12
1500        // - maxSampledTexturesPerShaderStage, WebGPU default: 16
1501        // - maxStorageTexturesPerShaderStage, WebGPU default: 4
1502        // - maxStorageBuffersPerShaderStage, WebGPU default: 8
1503        // - maxColorAttachments, WebGPU default: 8
1504        //
1505        // Note: Vulkan's texel buffers and input attachments also count towards
1506        // maxPerStageResources but we don't make use of them.
1507        let mut max_sampled_textures_per_shader_stage = limits
1508            .max_per_stage_descriptor_sampled_images
1509            .min(limits.max_descriptor_set_sampled_images / MAX_SHADER_STAGES_PER_PIPELINE);
1510        let mut max_uniform_buffers_per_shader_stage = limits
1511            .max_per_stage_descriptor_uniform_buffers
1512            .min(limits.max_descriptor_set_uniform_buffers / MAX_SHADER_STAGES_PER_PIPELINE);
1513
1514        crate::auxil::cap_limits_to_be_under_the_sum_limit(
1515            [
1516                &mut max_sampled_textures_per_shader_stage,
1517                &mut max_uniform_buffers_per_shader_stage,
1518                &mut max_storage_textures_per_shader_stage,
1519                &mut max_storage_buffers_per_shader_stage,
1520                &mut max_color_attachments,
1521            ],
1522            limits.max_per_stage_resources,
1523        );
1524
1525        // Acceleration structure limits
1526        let mut max_blas_geometry_count = 0;
1527        let mut max_blas_primitive_count = 0;
1528        let mut max_tlas_instance_count = 0;
1529        let mut max_acceleration_structures_per_shader_stage = 0;
1530        if let Some(properties) = self.acceleration_structure {
1531            max_blas_geometry_count = properties.max_geometry_count as u32;
1532            max_blas_primitive_count = properties.max_primitive_count as u32;
1533            max_tlas_instance_count = properties.max_instance_count as u32;
1534            max_acceleration_structures_per_shader_stage = properties
1535                .max_per_stage_descriptor_acceleration_structures
1536                .min(
1537                    properties.max_descriptor_set_acceleration_structures
1538                        / MAX_SHADER_STAGES_PER_PIPELINE,
1539                );
1540        }
1541
1542        // When summed, the 6 limits below must be under Vulkan's
1543        // maxPerSetDescriptors / MAX_SHADER_STAGES_PER_PIPELINE.
1544        //
1545        // - maxUniformBuffersPerShaderStage, WebGPU default: 12
1546        // - maxSampledTexturesPerShaderStage, WebGPU default: 16
1547        // - maxStorageTexturesPerShaderStage, WebGPU default: 4
1548        // - maxStorageBuffersPerShaderStage, WebGPU default: 8
1549        // - maxSamplersPerShaderStage, WebGPU default: 16
1550        // - maxAccelerationStructuresPerShaderStage, Native only
1551        //
1552        // Note: All Vulkan's descriptor types count towards maxPerSetDescriptors but
1553        // we don't use all of them.
1554        // See https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#interfaces-resources-limits
1555        let max_per_set_descriptors = self
1556            .maintenance_3
1557            .map(|maintenance_3| maintenance_3.max_per_set_descriptors)
1558            // The lowest value seen in reports is 312, use 256 as a safe default.
1559            // https://vulkan.gpuinfo.org/displayextensionproperty.php?extensionname=VK_KHR_maintenance3&extensionproperty=maxPerSetDescriptors&platform=all
1560            // https://vulkan.gpuinfo.org/displaycoreproperty.php?core=1.1&name=maxPerSetDescriptors&platform=all
1561            .unwrap_or(256);
1562
1563        let mut max_samplers_per_shader_stage = limits
1564            .max_per_stage_descriptor_samplers
1565            .min(limits.max_descriptor_set_samplers / MAX_SHADER_STAGES_PER_PIPELINE);
1566
1567        crate::auxil::cap_limits_to_be_under_the_sum_limit(
1568            [
1569                &mut max_sampled_textures_per_shader_stage,
1570                &mut max_uniform_buffers_per_shader_stage,
1571                &mut max_storage_textures_per_shader_stage,
1572                &mut max_storage_buffers_per_shader_stage,
1573                &mut max_samplers_per_shader_stage,
1574                &mut max_acceleration_structures_per_shader_stage,
1575            ],
1576            max_per_set_descriptors / MAX_SHADER_STAGES_PER_PIPELINE,
1577        );
1578
1579        // Use max(default, maxPerSetDescriptors) since the spec requires this
1580        // limit to be at least 1000. This is ok because we already lowered
1581        // all the other relevant per stage limits so their sum is lower
1582        // than maxPerSetDescriptors.
1583        let max_bindings_per_bind_group = 1000.max(max_per_set_descriptors);
1584
1585        // TODO: programmatically determine this, if possible. It's unclear whether we can
1586        // as of https://github.com/gpuweb/gpuweb/issues/2965#issuecomment-1361315447.
1587        //
1588        // In theory some tilers may not support this much. We can't tell however, and
1589        // the driver will throw a DEVICE_REMOVED if it goes too high in usage. This is fine.
1590        let max_color_attachment_bytes_per_sample =
1591            max_color_attachments * wgt::TextureFormat::MAX_TARGET_PIXEL_BYTE_COST;
1592
1593        let max_multiview_view_count = self
1594            .multiview
1595            .map(|a| a.max_multiview_view_count.min(32))
1596            .unwrap_or(0);
1597
1598        crate::auxil::adjust_raw_limits(wgt::Limits {
1599            //
1600            // WebGPU LIMITS:
1601            // Based on https://gpuweb.github.io/gpuweb/correspondence/#limits
1602            //
1603            max_texture_dimension_1d: limits.max_image_dimension1_d,
1604            max_texture_dimension_2d: limits
1605                .max_image_dimension2_d
1606                .min(limits.max_image_dimension_cube)
1607                .min(limits.max_framebuffer_width)
1608                .min(limits.max_framebuffer_height),
1609            max_texture_dimension_3d: limits.max_image_dimension3_d,
1610            max_texture_array_layers: limits.max_image_array_layers,
1611            max_bind_groups: limits.max_bound_descriptor_sets,
1612            max_bindings_per_bind_group,
1613            max_dynamic_uniform_buffers_per_pipeline_layout: limits
1614                .max_descriptor_set_uniform_buffers_dynamic,
1615            max_dynamic_storage_buffers_per_pipeline_layout: limits
1616                .max_descriptor_set_storage_buffers_dynamic,
1617            max_samplers_per_shader_stage,
1618            max_sampled_textures_per_shader_stage,
1619            max_storage_textures_per_shader_stage,
1620            max_storage_buffers_per_shader_stage,
1621            max_uniform_buffers_per_shader_stage,
1622            max_vertex_buffers: limits.max_vertex_input_bindings,
1623            max_buffer_size,
1624            max_uniform_buffer_binding_size: limits
1625                .max_uniform_buffer_range
1626                .min(crate::auxil::MAX_I32_BINDING_SIZE)
1627                .into(),
1628            max_storage_buffer_binding_size: limits
1629                .max_storage_buffer_range
1630                .min(crate::auxil::MAX_I32_BINDING_SIZE)
1631                .into(),
1632            min_uniform_buffer_offset_alignment: limits.min_uniform_buffer_offset_alignment as u32,
1633            min_storage_buffer_offset_alignment: limits.min_storage_buffer_offset_alignment as u32,
1634            max_vertex_attributes: limits.max_vertex_input_attributes,
1635            max_vertex_buffer_array_stride: limits.max_vertex_input_binding_stride,
1636            max_inter_stage_shader_variables: limits
1637                .max_vertex_output_components
1638                .min(limits.max_fragment_input_components)
1639                / 4
1640                - 1, // -1 for position
1641            max_color_attachments,
1642            max_color_attachment_bytes_per_sample,
1643            max_compute_workgroup_storage_size: limits.max_compute_shared_memory_size,
1644            max_compute_invocations_per_workgroup: limits.max_compute_work_group_invocations,
1645            max_compute_workgroup_size_x: limits.max_compute_work_group_size[0],
1646            max_compute_workgroup_size_y: limits.max_compute_work_group_size[1],
1647            max_compute_workgroup_size_z: limits.max_compute_work_group_size[2],
1648            max_compute_workgroups_per_dimension: limits.max_compute_work_group_count[0]
1649                .min(limits.max_compute_work_group_count[1])
1650                .min(limits.max_compute_work_group_count[2]),
1651            max_immediate_size: limits.max_push_constants_size,
1652            //
1653            // NATIVE (Non-WebGPU) LIMITS:
1654            //
1655            max_non_sampler_bindings: u32::MAX,
1656
1657            max_binding_array_elements_per_shader_stage: max_binding_array_elements,
1658            max_binding_array_sampler_elements_per_shader_stage: max_sampler_binding_array_elements,
1659            max_binding_array_acceleration_structure_elements_per_shader_stage: if self
1660                .descriptor_indexing
1661                .is_some()
1662            {
1663                max_acceleration_structures_per_shader_stage
1664            } else {
1665                0
1666            },
1667
1668            max_task_mesh_workgroup_total_count,
1669            max_task_mesh_workgroups_per_dimension,
1670            max_task_invocations_per_workgroup,
1671            max_task_invocations_per_dimension,
1672
1673            max_mesh_invocations_per_workgroup,
1674            max_mesh_invocations_per_dimension,
1675
1676            max_task_payload_size,
1677            max_mesh_output_vertices,
1678            max_mesh_output_primitives,
1679            max_mesh_output_layers,
1680            max_mesh_multiview_view_count,
1681
1682            max_blas_primitive_count,
1683            max_blas_geometry_count,
1684            max_tlas_instance_count,
1685            max_acceleration_structures_per_shader_stage,
1686
1687            max_multiview_view_count,
1688        })
1689    }
1690
1691    /// Return a `wgpu_hal::Alignments` structure describing this adapter.
1692    ///
1693    /// The `using_robustness2` argument says how this adapter will implement
1694    /// `wgpu_hal`'s guarantee that shaders can only read the [accessible
1695    /// region][ar] of bindgroup's buffer bindings:
1696    ///
1697    /// - If this adapter will depend on `VK_EXT_robustness2`'s
1698    ///   `robustBufferAccess2` feature to apply bounds checks to shader buffer
1699    ///   access, `using_robustness2` must be `true`.
1700    ///
1701    /// - Otherwise, this adapter must use Naga to inject bounds checks on
1702    ///   buffer accesses, and `using_robustness2` must be `false`.
1703    ///
1704    /// [ar]: ../../struct.BufferBinding.html#accessible-region
1705    fn to_hal_alignments(&self, using_robustness2: bool) -> crate::Alignments {
1706        let limits = &self.properties.limits;
1707        crate::Alignments {
1708            buffer_copy_offset: wgt::BufferSize::new(limits.optimal_buffer_copy_offset_alignment)
1709                .unwrap(),
1710            buffer_copy_pitch: wgt::BufferSize::new(limits.optimal_buffer_copy_row_pitch_alignment)
1711                .unwrap(),
1712            uniform_bounds_check_alignment: {
1713                let alignment = if using_robustness2 {
1714                    self.robustness2
1715                        .unwrap() // if we're using it, we should have its properties
1716                        .robust_uniform_buffer_access_size_alignment
1717                } else {
1718                    // If the `robustness2` properties are unavailable, then `robustness2` is not available either Naga-injected bounds checks are precise.
1719                    1
1720                };
1721                wgt::BufferSize::new(alignment).unwrap()
1722            },
1723            raw_tlas_instance_size: 64,
1724            ray_tracing_scratch_buffer_alignment: self.acceleration_structure.map_or(
1725                0,
1726                |acceleration_structure| {
1727                    acceleration_structure.min_acceleration_structure_scratch_offset_alignment
1728                },
1729            ),
1730        }
1731    }
1732}
1733
1734impl super::InstanceShared {
1735    fn inspect(
1736        &self,
1737        phd: vk::PhysicalDevice,
1738    ) -> (PhysicalDeviceProperties, PhysicalDeviceFeatures) {
1739        let capabilities = {
1740            let mut capabilities = PhysicalDeviceProperties::default();
1741            capabilities.supported_extensions =
1742                unsafe { self.raw.enumerate_device_extension_properties(phd).unwrap() };
1743            capabilities.properties = unsafe { self.raw.get_physical_device_properties(phd) };
1744            capabilities.device_api_version = capabilities.properties.api_version;
1745
1746            let supports_multiview = capabilities.device_api_version >= vk::API_VERSION_1_1
1747                || capabilities.supports_extension(khr::multiview::NAME);
1748
1749            if let Some(ref get_device_properties) = self.get_physical_device_properties {
1750                // Get these now to avoid borrowing conflicts later
1751                let supports_maintenance3 = capabilities.device_api_version >= vk::API_VERSION_1_1
1752                    || capabilities.supports_extension(khr::maintenance3::NAME);
1753                let supports_maintenance4 = capabilities.device_api_version >= vk::API_VERSION_1_3
1754                    || capabilities.supports_extension(khr::maintenance4::NAME);
1755                let supports_descriptor_indexing = capabilities.device_api_version
1756                    >= vk::API_VERSION_1_2
1757                    || capabilities.supports_extension(ext::descriptor_indexing::NAME);
1758                let supports_driver_properties = capabilities.device_api_version
1759                    >= vk::API_VERSION_1_2
1760                    || capabilities.supports_extension(khr::driver_properties::NAME);
1761                let supports_subgroup_size_control = capabilities.device_api_version
1762                    >= vk::API_VERSION_1_3
1763                    || capabilities.supports_extension(ext::subgroup_size_control::NAME);
1764                let supports_robustness2 = capabilities.supports_extension(ext::robustness2::NAME);
1765                let supports_pci_bus_info =
1766                    capabilities.supports_extension(ext::pci_bus_info::NAME);
1767
1768                let supports_acceleration_structure =
1769                    capabilities.supports_extension(khr::acceleration_structure::NAME);
1770
1771                let supports_mesh_shader = capabilities.supports_extension(ext::mesh_shader::NAME);
1772
1773                let mut properties2 = vk::PhysicalDeviceProperties2KHR::default();
1774                if supports_maintenance3 {
1775                    let next = capabilities
1776                        .maintenance_3
1777                        .insert(vk::PhysicalDeviceMaintenance3Properties::default());
1778                    properties2 = properties2.push_next(next);
1779                }
1780
1781                if supports_maintenance4 {
1782                    let next = capabilities
1783                        .maintenance_4
1784                        .insert(vk::PhysicalDeviceMaintenance4Properties::default());
1785                    properties2 = properties2.push_next(next);
1786                }
1787
1788                if supports_descriptor_indexing {
1789                    let next = capabilities
1790                        .descriptor_indexing
1791                        .insert(vk::PhysicalDeviceDescriptorIndexingPropertiesEXT::default());
1792                    properties2 = properties2.push_next(next);
1793                }
1794
1795                if supports_acceleration_structure {
1796                    let next = capabilities
1797                        .acceleration_structure
1798                        .insert(vk::PhysicalDeviceAccelerationStructurePropertiesKHR::default());
1799                    properties2 = properties2.push_next(next);
1800                }
1801
1802                if supports_driver_properties {
1803                    let next = capabilities
1804                        .driver
1805                        .insert(vk::PhysicalDeviceDriverPropertiesKHR::default());
1806                    properties2 = properties2.push_next(next);
1807                }
1808
1809                if capabilities.device_api_version >= vk::API_VERSION_1_1 {
1810                    let next = capabilities
1811                        .subgroup
1812                        .insert(vk::PhysicalDeviceSubgroupProperties::default());
1813                    properties2 = properties2.push_next(next);
1814                }
1815
1816                if supports_subgroup_size_control {
1817                    let next = capabilities
1818                        .subgroup_size_control
1819                        .insert(vk::PhysicalDeviceSubgroupSizeControlProperties::default());
1820                    properties2 = properties2.push_next(next);
1821                }
1822
1823                if supports_robustness2 {
1824                    let next = capabilities
1825                        .robustness2
1826                        .insert(vk::PhysicalDeviceRobustness2PropertiesEXT::default());
1827                    properties2 = properties2.push_next(next);
1828                }
1829
1830                if supports_pci_bus_info {
1831                    let next = capabilities
1832                        .pci_bus_info
1833                        .insert(vk::PhysicalDevicePCIBusInfoPropertiesEXT::default());
1834                    properties2 = properties2.push_next(next);
1835                }
1836
1837                if supports_mesh_shader {
1838                    let next = capabilities
1839                        .mesh_shader
1840                        .insert(vk::PhysicalDeviceMeshShaderPropertiesEXT::default());
1841                    properties2 = properties2.push_next(next);
1842                }
1843
1844                if supports_multiview {
1845                    let next = capabilities
1846                        .multiview
1847                        .insert(vk::PhysicalDeviceMultiviewProperties::default());
1848                    properties2 = properties2.push_next(next);
1849                }
1850
1851                unsafe {
1852                    get_device_properties.get_physical_device_properties2(phd, &mut properties2)
1853                };
1854
1855                // Query cooperative matrix properties
1856                if capabilities.supports_extension(khr::cooperative_matrix::NAME) {
1857                    let coop_matrix =
1858                        khr::cooperative_matrix::Instance::new(&self.entry, &self.raw);
1859                    capabilities.cooperative_matrix_properties =
1860                        query_cooperative_matrix_properties(&coop_matrix, phd);
1861                }
1862
1863                if is_intel_igpu_outdated_for_robustness2(
1864                    capabilities.properties,
1865                    capabilities.driver,
1866                ) {
1867                    capabilities
1868                        .supported_extensions
1869                        .retain(|&x| x.extension_name_as_c_str() != Ok(ext::robustness2::NAME));
1870                    capabilities.robustness2 = None;
1871                }
1872            };
1873            capabilities
1874        };
1875
1876        let mut features = PhysicalDeviceFeatures::default();
1877        features.core = if let Some(ref get_device_properties) = self.get_physical_device_properties
1878        {
1879            let core = vk::PhysicalDeviceFeatures::default();
1880            let mut features2 = vk::PhysicalDeviceFeatures2KHR::default().features(core);
1881
1882            // `VK_KHR_multiview` is promoted to 1.1
1883            if capabilities.device_api_version >= vk::API_VERSION_1_1
1884                || capabilities.supports_extension(khr::multiview::NAME)
1885            {
1886                let next = features
1887                    .multiview
1888                    .insert(vk::PhysicalDeviceMultiviewFeatures::default());
1889                features2 = features2.push_next(next);
1890            }
1891
1892            // `VK_KHR_sampler_ycbcr_conversion` is promoted to 1.1
1893            if capabilities.device_api_version >= vk::API_VERSION_1_1
1894                || capabilities.supports_extension(khr::sampler_ycbcr_conversion::NAME)
1895            {
1896                let next = features
1897                    .sampler_ycbcr_conversion
1898                    .insert(vk::PhysicalDeviceSamplerYcbcrConversionFeatures::default());
1899                features2 = features2.push_next(next);
1900            }
1901
1902            if capabilities.supports_extension(ext::descriptor_indexing::NAME) {
1903                let next = features
1904                    .descriptor_indexing
1905                    .insert(vk::PhysicalDeviceDescriptorIndexingFeaturesEXT::default());
1906                features2 = features2.push_next(next);
1907            }
1908
1909            // `VK_KHR_timeline_semaphore` is promoted to 1.2, but has no
1910            // changes, so we can keep using the extension unconditionally.
1911            if capabilities.supports_extension(khr::timeline_semaphore::NAME) {
1912                let next = features
1913                    .timeline_semaphore
1914                    .insert(vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR::default());
1915                features2 = features2.push_next(next);
1916            }
1917
1918            // `VK_KHR_shader_atomic_int64` is promoted to 1.2, but has no
1919            // changes, so we can keep using the extension unconditionally.
1920            if capabilities.device_api_version >= vk::API_VERSION_1_2
1921                || capabilities.supports_extension(khr::shader_atomic_int64::NAME)
1922            {
1923                let next = features
1924                    .shader_atomic_int64
1925                    .insert(vk::PhysicalDeviceShaderAtomicInt64Features::default());
1926                features2 = features2.push_next(next);
1927            }
1928
1929            if capabilities.supports_extension(ext::shader_image_atomic_int64::NAME) {
1930                let next = features
1931                    .shader_image_atomic_int64
1932                    .insert(vk::PhysicalDeviceShaderImageAtomicInt64FeaturesEXT::default());
1933                features2 = features2.push_next(next);
1934            }
1935            if capabilities.supports_extension(ext::shader_atomic_float::NAME) {
1936                let next = features
1937                    .shader_atomic_float
1938                    .insert(vk::PhysicalDeviceShaderAtomicFloatFeaturesEXT::default());
1939                features2 = features2.push_next(next);
1940            }
1941            if capabilities.supports_extension(ext::image_robustness::NAME) {
1942                let next = features
1943                    .image_robustness
1944                    .insert(vk::PhysicalDeviceImageRobustnessFeaturesEXT::default());
1945                features2 = features2.push_next(next);
1946            }
1947            if capabilities.supports_extension(ext::robustness2::NAME) {
1948                let next = features
1949                    .robustness2
1950                    .insert(vk::PhysicalDeviceRobustness2FeaturesEXT::default());
1951                features2 = features2.push_next(next);
1952            }
1953            if capabilities.supports_extension(ext::texture_compression_astc_hdr::NAME) {
1954                let next = features
1955                    .astc_hdr
1956                    .insert(vk::PhysicalDeviceTextureCompressionASTCHDRFeaturesEXT::default());
1957                features2 = features2.push_next(next);
1958            }
1959
1960            // `VK_KHR_shader_float16_int8` is promoted to 1.2
1961            if capabilities.device_api_version >= vk::API_VERSION_1_2
1962                || capabilities.supports_extension(khr::shader_float16_int8::NAME)
1963            {
1964                let next = features
1965                    .shader_float16_int8
1966                    .insert(vk::PhysicalDeviceShaderFloat16Int8FeaturesKHR::default());
1967                features2 = features2.push_next(next);
1968            }
1969
1970            if capabilities.supports_extension(khr::_16bit_storage::NAME) {
1971                let next = features
1972                    ._16bit_storage
1973                    .insert(vk::PhysicalDevice16BitStorageFeaturesKHR::default());
1974                features2 = features2.push_next(next);
1975            }
1976            if capabilities.supports_extension(khr::acceleration_structure::NAME) {
1977                let next = features
1978                    .acceleration_structure
1979                    .insert(vk::PhysicalDeviceAccelerationStructureFeaturesKHR::default());
1980                features2 = features2.push_next(next);
1981            }
1982
1983            if capabilities.supports_extension(khr::ray_tracing_position_fetch::NAME) {
1984                let next = features
1985                    .position_fetch
1986                    .insert(vk::PhysicalDeviceRayTracingPositionFetchFeaturesKHR::default());
1987                features2 = features2.push_next(next);
1988            }
1989
1990            // `VK_KHR_maintenance4` is promoted to 1.3
1991            if capabilities.device_api_version >= vk::API_VERSION_1_3
1992                || capabilities.supports_extension(khr::maintenance4::NAME)
1993            {
1994                let next = features
1995                    .maintenance4
1996                    .insert(vk::PhysicalDeviceMaintenance4Features::default());
1997                features2 = features2.push_next(next);
1998            }
1999
2000            // `VK_KHR_zero_initialize_workgroup_memory` is promoted to 1.3
2001            if capabilities.device_api_version >= vk::API_VERSION_1_3
2002                || capabilities.supports_extension(khr::zero_initialize_workgroup_memory::NAME)
2003            {
2004                let next = features
2005                    .zero_initialize_workgroup_memory
2006                    .insert(vk::PhysicalDeviceZeroInitializeWorkgroupMemoryFeatures::default());
2007                features2 = features2.push_next(next);
2008            }
2009
2010            // `VK_EXT_subgroup_size_control` is promoted to 1.3
2011            if capabilities.device_api_version >= vk::API_VERSION_1_3
2012                || capabilities.supports_extension(ext::subgroup_size_control::NAME)
2013            {
2014                let next = features
2015                    .subgroup_size_control
2016                    .insert(vk::PhysicalDeviceSubgroupSizeControlFeatures::default());
2017                features2 = features2.push_next(next);
2018            }
2019
2020            if capabilities.supports_extension(ext::mesh_shader::NAME) {
2021                let next = features
2022                    .mesh_shader
2023                    .insert(vk::PhysicalDeviceMeshShaderFeaturesEXT::default());
2024                features2 = features2.push_next(next);
2025            }
2026
2027            // `VK_KHR_shader_integer_dot_product` is promoted to 1.3
2028            if capabilities.device_api_version >= vk::API_VERSION_1_3
2029                || capabilities.supports_extension(khr::shader_integer_dot_product::NAME)
2030            {
2031                let next = features
2032                    .shader_integer_dot_product
2033                    .insert(vk::PhysicalDeviceShaderIntegerDotProductFeatures::default());
2034                features2 = features2.push_next(next);
2035            }
2036
2037            if capabilities.supports_extension(khr::fragment_shader_barycentric::NAME) {
2038                let next = features
2039                    .shader_barycentrics
2040                    .insert(vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR::default());
2041                features2 = features2.push_next(next);
2042            }
2043
2044            if capabilities.supports_extension(khr::portability_subset::NAME) {
2045                let next = features
2046                    .portability_subset
2047                    .insert(vk::PhysicalDevicePortabilitySubsetFeaturesKHR::default());
2048                features2 = features2.push_next(next);
2049            }
2050
2051            if capabilities.supports_extension(khr::cooperative_matrix::NAME) {
2052                let next = features
2053                    .cooperative_matrix
2054                    .insert(vk::PhysicalDeviceCooperativeMatrixFeaturesKHR::default());
2055                features2 = features2.push_next(next);
2056            }
2057
2058            if capabilities.device_api_version >= vk::API_VERSION_1_1 {
2059                let next = features
2060                    .shader_draw_parameters
2061                    .insert(vk::PhysicalDeviceShaderDrawParametersFeatures::default());
2062                features2 = features2.push_next(next);
2063            }
2064
2065            unsafe { get_device_properties.get_physical_device_features2(phd, &mut features2) };
2066            features2.features
2067        } else {
2068            unsafe { self.raw.get_physical_device_features(phd) }
2069        };
2070
2071        (capabilities, features)
2072    }
2073}
2074
2075impl super::Instance {
2076    pub fn expose_adapter(
2077        &self,
2078        phd: vk::PhysicalDevice,
2079    ) -> Option<crate::ExposedAdapter<super::Api>> {
2080        use crate::auxil::db;
2081
2082        let (phd_capabilities, phd_features) = self.shared.inspect(phd);
2083
2084        let mem_properties = {
2085            profiling::scope!("vkGetPhysicalDeviceMemoryProperties");
2086            unsafe { self.shared.raw.get_physical_device_memory_properties(phd) }
2087        };
2088        let memory_types = &mem_properties.memory_types_as_slice();
2089        let supports_lazily_allocated = memory_types.iter().any(|mem| {
2090            mem.property_flags
2091                .contains(vk::MemoryPropertyFlags::LAZILY_ALLOCATED)
2092        });
2093
2094        let info = wgt::AdapterInfo {
2095            name: {
2096                phd_capabilities
2097                    .properties
2098                    .device_name_as_c_str()
2099                    .ok()
2100                    .and_then(|name| name.to_str().ok())
2101                    .unwrap_or("?")
2102                    .to_owned()
2103            },
2104            vendor: phd_capabilities.properties.vendor_id,
2105            device: phd_capabilities.properties.device_id,
2106            device_type: match phd_capabilities.properties.device_type {
2107                vk::PhysicalDeviceType::OTHER => wgt::DeviceType::Other,
2108                vk::PhysicalDeviceType::INTEGRATED_GPU => wgt::DeviceType::IntegratedGpu,
2109                vk::PhysicalDeviceType::DISCRETE_GPU => wgt::DeviceType::DiscreteGpu,
2110                vk::PhysicalDeviceType::VIRTUAL_GPU => wgt::DeviceType::VirtualGpu,
2111                vk::PhysicalDeviceType::CPU => wgt::DeviceType::Cpu,
2112                _ => wgt::DeviceType::Other,
2113            },
2114            device_pci_bus_id: phd_capabilities
2115                .pci_bus_info
2116                .filter(|info| info.pci_bus != 0 || info.pci_device != 0)
2117                .map(|info| {
2118                    format!(
2119                        "{:04x}:{:02x}:{:02x}.{}",
2120                        info.pci_domain, info.pci_bus, info.pci_device, info.pci_function
2121                    )
2122                })
2123                .unwrap_or_default(),
2124            driver: {
2125                phd_capabilities
2126                    .driver
2127                    .as_ref()
2128                    .and_then(|driver| driver.driver_name_as_c_str().ok())
2129                    .and_then(|name| name.to_str().ok())
2130                    .unwrap_or("?")
2131                    .to_owned()
2132            },
2133            driver_info: {
2134                phd_capabilities
2135                    .driver
2136                    .as_ref()
2137                    .and_then(|driver| driver.driver_info_as_c_str().ok())
2138                    .and_then(|name| name.to_str().ok())
2139                    .unwrap_or("?")
2140                    .to_owned()
2141            },
2142            backend: wgt::Backend::Vulkan,
2143            subgroup_min_size: phd_capabilities
2144                .subgroup_size_control
2145                .map(|subgroup_size| subgroup_size.min_subgroup_size)
2146                .unwrap_or(wgt::MINIMUM_SUBGROUP_MIN_SIZE),
2147            subgroup_max_size: phd_capabilities
2148                .subgroup_size_control
2149                .map(|subgroup_size| subgroup_size.max_subgroup_size)
2150                .unwrap_or(wgt::MAXIMUM_SUBGROUP_MAX_SIZE),
2151            transient_saves_memory: supports_lazily_allocated,
2152        };
2153        let mut workarounds = super::Workarounds::empty();
2154        {
2155            // TODO: only enable for particular devices
2156            workarounds |= super::Workarounds::SEPARATE_ENTRY_POINTS;
2157            workarounds.set(
2158                super::Workarounds::EMPTY_RESOLVE_ATTACHMENT_LISTS,
2159                phd_capabilities.properties.vendor_id == db::qualcomm::VENDOR,
2160            );
2161            workarounds.set(
2162                super::Workarounds::FORCE_FILL_BUFFER_WITH_SIZE_GREATER_4096_ALIGNED_OFFSET_16,
2163                phd_capabilities.properties.vendor_id == db::nvidia::VENDOR,
2164            );
2165        };
2166
2167        if let Some(driver) = phd_capabilities.driver {
2168            if driver.conformance_version.major == 0 {
2169                if driver.driver_id == vk::DriverId::MOLTENVK {
2170                    log::debug!("Adapter is not Vulkan compliant, but is MoltenVK, continuing");
2171                } else if self
2172                    .shared
2173                    .flags
2174                    .contains(wgt::InstanceFlags::ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER)
2175                {
2176                    log::debug!("Adapter is not Vulkan compliant: {}", info.name);
2177                } else {
2178                    log::debug!(
2179                        "Adapter is not Vulkan compliant, hiding adapter: {}",
2180                        info.name
2181                    );
2182                    return None;
2183                }
2184            }
2185        }
2186        if phd_capabilities.device_api_version == vk::API_VERSION_1_0
2187            && !phd_capabilities.supports_extension(khr::storage_buffer_storage_class::NAME)
2188        {
2189            log::debug!(
2190                "SPIR-V storage buffer class is not supported, hiding adapter: {}",
2191                info.name
2192            );
2193            return None;
2194        }
2195        if !phd_capabilities.supports_extension(khr::maintenance1::NAME)
2196            && phd_capabilities.device_api_version < vk::API_VERSION_1_1
2197        {
2198            log::debug!(
2199                "VK_KHR_maintenance1 is not supported, hiding adapter: {}",
2200                info.name
2201            );
2202            return None;
2203        }
2204
2205        let queue_families = unsafe {
2206            self.shared
2207                .raw
2208                .get_physical_device_queue_family_properties(phd)
2209        };
2210        let queue_family_properties = queue_families.first()?;
2211        let queue_flags = queue_family_properties.queue_flags;
2212        if !queue_flags.contains(vk::QueueFlags::GRAPHICS) {
2213            log::debug!("The first queue only exposes {queue_flags:?}");
2214            return None;
2215        }
2216
2217        let (available_features, mut downlevel_flags) = phd_features.to_wgpu(
2218            &self.shared.raw,
2219            phd,
2220            &phd_capabilities,
2221            queue_family_properties,
2222        );
2223
2224        if info.driver == "llvmpipe" {
2225            // The `F16_IN_F32` instructions do not normally require native `F16` support, but on
2226            // llvmpipe, they do.
2227            downlevel_flags.set(
2228                wgt::DownlevelFlags::SHADER_F16_IN_F32,
2229                available_features.contains(wgt::Features::SHADER_F16),
2230            );
2231        }
2232
2233        let has_robust_buffer_access2 = phd_features
2234            .robustness2
2235            .as_ref()
2236            .map(|r| r.robust_buffer_access2 == 1)
2237            .unwrap_or_default();
2238
2239        let alignments = phd_capabilities.to_hal_alignments(has_robust_buffer_access2);
2240
2241        let private_caps = super::PrivateCapabilities {
2242            image_view_usage: phd_capabilities.device_api_version >= vk::API_VERSION_1_1
2243                || phd_capabilities.supports_extension(khr::maintenance2::NAME),
2244            timeline_semaphores: match phd_features.timeline_semaphore {
2245                Some(features) => features.timeline_semaphore == vk::TRUE,
2246                None => phd_features
2247                    .timeline_semaphore
2248                    .is_some_and(|ext| ext.timeline_semaphore != 0),
2249            },
2250            texture_d24: supports_format(
2251                &self.shared.raw,
2252                phd,
2253                vk::Format::X8_D24_UNORM_PACK32,
2254                vk::ImageTiling::OPTIMAL,
2255                depth_stencil_required_flags(),
2256            ),
2257            texture_d24_s8: supports_format(
2258                &self.shared.raw,
2259                phd,
2260                vk::Format::D24_UNORM_S8_UINT,
2261                vk::ImageTiling::OPTIMAL,
2262                depth_stencil_required_flags(),
2263            ),
2264            texture_s8: supports_format(
2265                &self.shared.raw,
2266                phd,
2267                vk::Format::S8_UINT,
2268                vk::ImageTiling::OPTIMAL,
2269                depth_stencil_required_flags(),
2270            ),
2271            multi_draw_indirect: phd_features.core.multi_draw_indirect != 0,
2272            max_draw_indirect_count: phd_capabilities.properties.limits.max_draw_indirect_count,
2273            non_coherent_map_mask: phd_capabilities.properties.limits.non_coherent_atom_size - 1,
2274            can_present: true,
2275            //TODO: make configurable
2276            robust_buffer_access: phd_features.core.robust_buffer_access != 0,
2277            robust_image_access: match phd_features.robustness2 {
2278                Some(ref f) => f.robust_image_access2 != 0,
2279                None => phd_features
2280                    .image_robustness
2281                    .is_some_and(|ext| ext.robust_image_access != 0),
2282            },
2283            robust_buffer_access2: has_robust_buffer_access2,
2284            robust_image_access2: phd_features
2285                .robustness2
2286                .as_ref()
2287                .map(|r| r.robust_image_access2 == 1)
2288                .unwrap_or_default(),
2289            zero_initialize_workgroup_memory: phd_features
2290                .zero_initialize_workgroup_memory
2291                .is_some_and(|ext| ext.shader_zero_initialize_workgroup_memory == vk::TRUE),
2292            image_format_list: phd_capabilities.device_api_version >= vk::API_VERSION_1_2
2293                || phd_capabilities.supports_extension(khr::image_format_list::NAME),
2294            maximum_samplers: phd_capabilities
2295                .properties
2296                .limits
2297                .max_sampler_allocation_count,
2298            shader_integer_dot_product: phd_features
2299                .shader_integer_dot_product
2300                .is_some_and(|ext| ext.shader_integer_dot_product != 0),
2301            shader_int8: phd_features
2302                .shader_float16_int8
2303                .is_some_and(|features| features.shader_int8 != 0),
2304            multiview_instance_index_limit: phd_capabilities
2305                .multiview
2306                .map(|a| a.max_multiview_instance_index)
2307                .unwrap_or(0),
2308            scratch_buffer_alignment: alignments.ray_tracing_scratch_buffer_alignment,
2309        };
2310        let capabilities = crate::Capabilities {
2311            limits: phd_capabilities.to_wgpu_limits(),
2312            alignments,
2313            downlevel: wgt::DownlevelCapabilities {
2314                flags: downlevel_flags,
2315                limits: wgt::DownlevelLimits {},
2316                shader_model: wgt::ShaderModel::Sm5, //TODO?
2317            },
2318            cooperative_matrix_properties: phd_capabilities.cooperative_matrix_properties.clone(),
2319        };
2320
2321        let adapter = super::Adapter {
2322            raw: phd,
2323            instance: Arc::clone(&self.shared),
2324            //queue_families,
2325            known_memory_flags: vk::MemoryPropertyFlags::DEVICE_LOCAL
2326                | vk::MemoryPropertyFlags::HOST_VISIBLE
2327                | vk::MemoryPropertyFlags::HOST_COHERENT
2328                | vk::MemoryPropertyFlags::HOST_CACHED
2329                | vk::MemoryPropertyFlags::LAZILY_ALLOCATED,
2330            phd_capabilities,
2331            phd_features,
2332            downlevel_flags,
2333            private_caps,
2334            workarounds,
2335        };
2336
2337        Some(crate::ExposedAdapter {
2338            adapter,
2339            info,
2340            features: available_features,
2341            capabilities,
2342        })
2343    }
2344}
2345
2346impl super::Adapter {
2347    pub fn raw_physical_device(&self) -> vk::PhysicalDevice {
2348        self.raw
2349    }
2350
2351    pub fn get_physical_device_features(&self) -> &PhysicalDeviceFeatures {
2352        &self.phd_features
2353    }
2354
2355    pub fn physical_device_capabilities(&self) -> &PhysicalDeviceProperties {
2356        &self.phd_capabilities
2357    }
2358
2359    pub fn shared_instance(&self) -> &super::InstanceShared {
2360        &self.instance
2361    }
2362
2363    pub fn required_device_extensions(&self, features: wgt::Features) -> Vec<&'static CStr> {
2364        let (supported_extensions, unsupported_extensions) = self
2365            .phd_capabilities
2366            .get_required_extensions(features)
2367            .iter()
2368            .partition::<Vec<&CStr>, _>(|&&extension| {
2369                self.phd_capabilities.supports_extension(extension)
2370            });
2371
2372        if !unsupported_extensions.is_empty() {
2373            log::debug!("Missing extensions: {unsupported_extensions:?}");
2374        }
2375
2376        log::debug!("Supported extensions: {supported_extensions:?}");
2377        supported_extensions
2378    }
2379
2380    /// Create a `PhysicalDeviceFeatures` for opening a logical device with
2381    /// `features` from this adapter.
2382    ///
2383    /// The given `enabled_extensions` set must include all the extensions
2384    /// selected by [`required_device_extensions`] when passed `features`.
2385    /// Otherwise, the `PhysicalDeviceFeatures` value may not be able to select
2386    /// all the Vulkan features needed to represent `features` and this
2387    /// adapter's characteristics.
2388    ///
2389    /// Typically, you'd simply call `required_device_extensions`, and then pass
2390    /// its return value and the feature set you gave it directly to this
2391    /// function. But it's fine to add more extensions to the list.
2392    ///
2393    /// [`required_device_extensions`]: Self::required_device_extensions
2394    pub fn physical_device_features(
2395        &self,
2396        enabled_extensions: &[&'static CStr],
2397        features: wgt::Features,
2398    ) -> PhysicalDeviceFeatures {
2399        PhysicalDeviceFeatures::from_extensions_and_requested_features(
2400            &self.phd_capabilities,
2401            &self.phd_features,
2402            enabled_extensions,
2403            features,
2404            self.downlevel_flags,
2405            &self.private_caps,
2406        )
2407    }
2408
2409    /// # Safety
2410    ///
2411    /// - `raw_device` must be created from this adapter.
2412    /// - `raw_device` must be created using `family_index`, `enabled_extensions` and `physical_device_features()`
2413    /// - `enabled_extensions` must be a superset of `required_device_extensions()`.
2414    /// - If `drop_callback` is [`None`], wgpu-hal will take ownership of `raw_device`. If
2415    ///   `drop_callback` is [`Some`], `raw_device` must be valid until the callback is called.
2416    #[allow(clippy::too_many_arguments)]
2417    pub unsafe fn device_from_raw(
2418        &self,
2419        raw_device: ash::Device,
2420        drop_callback: Option<crate::DropCallback>,
2421        enabled_extensions: &[&'static CStr],
2422        features: wgt::Features,
2423        limits: &wgt::Limits,
2424        memory_hints: &wgt::MemoryHints,
2425        family_index: u32,
2426        queue_index: u32,
2427    ) -> Result<crate::OpenDevice<super::Api>, crate::DeviceError> {
2428        let mem_properties = {
2429            profiling::scope!("vkGetPhysicalDeviceMemoryProperties");
2430            unsafe {
2431                self.instance
2432                    .raw
2433                    .get_physical_device_memory_properties(self.raw)
2434            }
2435        };
2436        let memory_types = &mem_properties.memory_types_as_slice();
2437        let valid_ash_memory_types = memory_types.iter().enumerate().fold(0, |u, (i, mem)| {
2438            if self.known_memory_flags.contains(mem.property_flags) {
2439                u | (1 << i)
2440            } else {
2441                u
2442            }
2443        });
2444
2445        // Note that VK_EXT_debug_utils is an instance extension (enabled at the instance
2446        // level) but contains a few functions that can be loaded directly on the Device for a
2447        // dispatch-table-less pointer.
2448        let debug_utils_fn = if self.instance.extensions.contains(&ext::debug_utils::NAME) {
2449            Some(ext::debug_utils::Device::new(
2450                &self.instance.raw,
2451                &raw_device,
2452            ))
2453        } else {
2454            None
2455        };
2456        let indirect_count_fn = if enabled_extensions.contains(&khr::draw_indirect_count::NAME) {
2457            Some(khr::draw_indirect_count::Device::new(
2458                &self.instance.raw,
2459                &raw_device,
2460            ))
2461        } else {
2462            None
2463        };
2464        let timeline_semaphore_fn = if enabled_extensions.contains(&khr::timeline_semaphore::NAME) {
2465            Some(super::ExtensionFn::Extension(
2466                khr::timeline_semaphore::Device::new(&self.instance.raw, &raw_device),
2467            ))
2468        } else if self.phd_capabilities.device_api_version >= vk::API_VERSION_1_2 {
2469            Some(super::ExtensionFn::Promoted)
2470        } else {
2471            None
2472        };
2473        let ray_tracing_fns = if enabled_extensions.contains(&khr::acceleration_structure::NAME)
2474            && enabled_extensions.contains(&khr::buffer_device_address::NAME)
2475        {
2476            Some(super::RayTracingDeviceExtensionFunctions {
2477                acceleration_structure: khr::acceleration_structure::Device::new(
2478                    &self.instance.raw,
2479                    &raw_device,
2480                ),
2481                buffer_device_address: khr::buffer_device_address::Device::new(
2482                    &self.instance.raw,
2483                    &raw_device,
2484                ),
2485            })
2486        } else {
2487            None
2488        };
2489        let mesh_shading_fns = if enabled_extensions.contains(&ext::mesh_shader::NAME) {
2490            Some(ext::mesh_shader::Device::new(
2491                &self.instance.raw,
2492                &raw_device,
2493            ))
2494        } else {
2495            None
2496        };
2497
2498        let naga_options = {
2499            use naga::back::spv;
2500
2501            // The following capabilities are always available
2502            // see https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap52.html#spirvenv-capabilities
2503            let mut capabilities = vec![
2504                spv::Capability::Shader,
2505                spv::Capability::Matrix,
2506                spv::Capability::Sampled1D,
2507                spv::Capability::Image1D,
2508                spv::Capability::ImageQuery,
2509                spv::Capability::DerivativeControl,
2510                spv::Capability::StorageImageExtendedFormats,
2511            ];
2512
2513            if self
2514                .downlevel_flags
2515                .contains(wgt::DownlevelFlags::CUBE_ARRAY_TEXTURES)
2516            {
2517                capabilities.push(spv::Capability::SampledCubeArray);
2518            }
2519
2520            if self
2521                .downlevel_flags
2522                .contains(wgt::DownlevelFlags::MULTISAMPLED_SHADING)
2523            {
2524                capabilities.push(spv::Capability::SampleRateShading);
2525            }
2526
2527            if features.contains(wgt::Features::MULTIVIEW) {
2528                capabilities.push(spv::Capability::MultiView);
2529            }
2530
2531            if features.contains(wgt::Features::PRIMITIVE_INDEX) {
2532                capabilities.push(spv::Capability::Geometry);
2533            }
2534
2535            if features.intersects(wgt::Features::SUBGROUP | wgt::Features::SUBGROUP_VERTEX) {
2536                capabilities.push(spv::Capability::GroupNonUniform);
2537                capabilities.push(spv::Capability::GroupNonUniformVote);
2538                capabilities.push(spv::Capability::GroupNonUniformArithmetic);
2539                capabilities.push(spv::Capability::GroupNonUniformBallot);
2540                capabilities.push(spv::Capability::GroupNonUniformShuffle);
2541                capabilities.push(spv::Capability::GroupNonUniformShuffleRelative);
2542                capabilities.push(spv::Capability::GroupNonUniformQuad);
2543            }
2544
2545            if features.intersects(
2546                wgt::Features::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING
2547                    | wgt::Features::STORAGE_TEXTURE_ARRAY_NON_UNIFORM_INDEXING
2548                    | wgt::Features::UNIFORM_BUFFER_BINDING_ARRAYS,
2549            ) {
2550                capabilities.push(spv::Capability::ShaderNonUniform);
2551            }
2552            if features.contains(wgt::Features::BGRA8UNORM_STORAGE) {
2553                capabilities.push(spv::Capability::StorageImageWriteWithoutFormat);
2554            }
2555
2556            if features.contains(wgt::Features::EXPERIMENTAL_RAY_QUERY) {
2557                capabilities.push(spv::Capability::RayQueryKHR);
2558            }
2559
2560            if features.contains(wgt::Features::SHADER_INT64) {
2561                capabilities.push(spv::Capability::Int64);
2562            }
2563
2564            if features.contains(wgt::Features::SHADER_F16) {
2565                capabilities.push(spv::Capability::Float16);
2566            }
2567
2568            if features.intersects(
2569                wgt::Features::SHADER_INT64_ATOMIC_ALL_OPS
2570                    | wgt::Features::SHADER_INT64_ATOMIC_MIN_MAX
2571                    | wgt::Features::TEXTURE_INT64_ATOMIC,
2572            ) {
2573                capabilities.push(spv::Capability::Int64Atomics);
2574            }
2575
2576            if features.intersects(wgt::Features::TEXTURE_INT64_ATOMIC) {
2577                capabilities.push(spv::Capability::Int64ImageEXT);
2578            }
2579
2580            if features.contains(wgt::Features::SHADER_FLOAT32_ATOMIC) {
2581                capabilities.push(spv::Capability::AtomicFloat32AddEXT);
2582            }
2583
2584            if features.contains(wgt::Features::CLIP_DISTANCES) {
2585                capabilities.push(spv::Capability::ClipDistance);
2586            }
2587
2588            // Vulkan bundles both barycentrics and per-vertex attributes under the same feature.
2589            if features
2590                .intersects(wgt::Features::SHADER_BARYCENTRICS | wgt::Features::SHADER_PER_VERTEX)
2591            {
2592                capabilities.push(spv::Capability::FragmentBarycentricKHR);
2593            }
2594
2595            if features.contains(wgt::Features::SHADER_DRAW_INDEX) {
2596                capabilities.push(spv::Capability::DrawParameters);
2597            }
2598
2599            let mut flags = spv::WriterFlags::empty();
2600            flags.set(
2601                spv::WriterFlags::DEBUG,
2602                self.instance.flags.contains(wgt::InstanceFlags::DEBUG),
2603            );
2604            flags.set(
2605                spv::WriterFlags::LABEL_VARYINGS,
2606                self.phd_capabilities.properties.vendor_id != crate::auxil::db::qualcomm::VENDOR,
2607            );
2608            flags.set(
2609                spv::WriterFlags::FORCE_POINT_SIZE,
2610                //Note: we could technically disable this when we are compiling separate entry points,
2611                // and we know exactly that the primitive topology is not `PointList`.
2612                // But this requires cloning the `spv::Options` struct, which has heap allocations.
2613                true, // could check `super::Workarounds::SEPARATE_ENTRY_POINTS`
2614            );
2615            flags.set(
2616                spv::WriterFlags::PRINT_ON_RAY_QUERY_INITIALIZATION_FAIL,
2617                self.instance.flags.contains(wgt::InstanceFlags::DEBUG)
2618                    && (self.instance.instance_api_version >= vk::API_VERSION_1_3
2619                        || enabled_extensions.contains(&khr::shader_non_semantic_info::NAME)),
2620            );
2621            if features.contains(wgt::Features::EXPERIMENTAL_RAY_QUERY) {
2622                capabilities.push(spv::Capability::RayQueryKHR);
2623            }
2624            if features.contains(wgt::Features::EXPERIMENTAL_RAY_HIT_VERTEX_RETURN) {
2625                capabilities.push(spv::Capability::RayQueryPositionFetchKHR)
2626            }
2627            if features.contains(wgt::Features::EXPERIMENTAL_MESH_SHADER) {
2628                capabilities.push(spv::Capability::MeshShadingEXT);
2629            }
2630            if features.contains(wgt::Features::EXPERIMENTAL_COOPERATIVE_MATRIX) {
2631                capabilities.push(spv::Capability::CooperativeMatrixKHR);
2632                // TODO: expose this more generally
2633                capabilities.push(spv::Capability::VulkanMemoryModel);
2634            }
2635            if self.private_caps.shader_integer_dot_product {
2636                // See <https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_shader_integer_dot_product.html#_new_spir_v_capabilities>.
2637                capabilities.extend(&[
2638                    spv::Capability::DotProductInputAllKHR,
2639                    spv::Capability::DotProductInput4x8BitKHR,
2640                    spv::Capability::DotProductInput4x8BitPackedKHR,
2641                    spv::Capability::DotProductKHR,
2642                ]);
2643            }
2644            if self.private_caps.shader_int8 {
2645                // See <https://registry.khronos.org/vulkan/specs/latest/man/html/VkPhysicalDeviceShaderFloat16Int8Features.html#extension-features-shaderInt8>.
2646                capabilities.extend(&[spv::Capability::Int8]);
2647            }
2648            spv::Options {
2649                lang_version: match self.phd_capabilities.device_api_version {
2650                    // Use maximum supported SPIR-V version according to
2651                    // <https://github.com/KhronosGroup/Vulkan-Docs/blob/19b7651/appendices/spirvenv.adoc?plain=1#L21-L40>.
2652                    vk::API_VERSION_1_0..vk::API_VERSION_1_1 => (1, 0),
2653                    vk::API_VERSION_1_1..vk::API_VERSION_1_2 => (1, 3),
2654                    vk::API_VERSION_1_2..vk::API_VERSION_1_3 => (1, 5),
2655                    vk::API_VERSION_1_3.. => (1, 6),
2656                    _ => unreachable!(),
2657                },
2658                flags,
2659                capabilities: Some(capabilities.iter().cloned().collect()),
2660                bounds_check_policies: naga::proc::BoundsCheckPolicies {
2661                    index: naga::proc::BoundsCheckPolicy::Restrict,
2662                    buffer: if self.private_caps.robust_buffer_access2 {
2663                        naga::proc::BoundsCheckPolicy::Unchecked
2664                    } else {
2665                        naga::proc::BoundsCheckPolicy::Restrict
2666                    },
2667                    image_load: if self.private_caps.robust_image_access {
2668                        naga::proc::BoundsCheckPolicy::Unchecked
2669                    } else {
2670                        naga::proc::BoundsCheckPolicy::Restrict
2671                    },
2672                    // TODO: support bounds checks on binding arrays
2673                    binding_array: naga::proc::BoundsCheckPolicy::Unchecked,
2674                },
2675                zero_initialize_workgroup_memory: if self
2676                    .private_caps
2677                    .zero_initialize_workgroup_memory
2678                {
2679                    spv::ZeroInitializeWorkgroupMemoryMode::Native
2680                } else {
2681                    spv::ZeroInitializeWorkgroupMemoryMode::Polyfill
2682                },
2683                force_loop_bounding: true,
2684                ray_query_initialization_tracking: true,
2685                use_storage_input_output_16: features.contains(wgt::Features::SHADER_F16)
2686                    && self.phd_features.supports_storage_input_output_16(),
2687                fake_missing_bindings: false,
2688                // We need to build this separately for each invocation, so just default it out here
2689                binding_map: BTreeMap::default(),
2690                debug_info: None,
2691                task_dispatch_limits: Some(naga::back::TaskDispatchLimits {
2692                    max_mesh_workgroups_per_dim: limits.max_task_mesh_workgroups_per_dimension,
2693                    max_mesh_workgroups_total: limits.max_task_mesh_workgroup_total_count,
2694                }),
2695                mesh_shader_primitive_indices_clamp: true,
2696            }
2697        };
2698
2699        let raw_queue = {
2700            profiling::scope!("vkGetDeviceQueue");
2701            unsafe { raw_device.get_device_queue(family_index, queue_index) }
2702        };
2703
2704        let driver_version = self
2705            .phd_capabilities
2706            .properties
2707            .driver_version
2708            .to_be_bytes();
2709        #[rustfmt::skip]
2710        let pipeline_cache_validation_key = [
2711            driver_version[0], driver_version[1], driver_version[2], driver_version[3],
2712            0, 0, 0, 0,
2713            0, 0, 0, 0,
2714            0, 0, 0, 0,
2715        ];
2716
2717        let drop_guard = crate::DropGuard::from_option(drop_callback);
2718
2719        let empty_descriptor_set_layout = unsafe {
2720            raw_device
2721                .create_descriptor_set_layout(&vk::DescriptorSetLayoutCreateInfo::default(), None)
2722                .map_err(super::map_host_device_oom_err)?
2723        };
2724
2725        let shared = Arc::new(super::DeviceShared {
2726            raw: raw_device,
2727            family_index,
2728            queue_index,
2729            raw_queue,
2730            drop_guard,
2731            instance: Arc::clone(&self.instance),
2732            physical_device: self.raw,
2733            enabled_extensions: enabled_extensions.into(),
2734            extension_fns: super::DeviceExtensionFunctions {
2735                debug_utils: debug_utils_fn,
2736                draw_indirect_count: indirect_count_fn,
2737                timeline_semaphore: timeline_semaphore_fn,
2738                ray_tracing: ray_tracing_fns,
2739                mesh_shading: mesh_shading_fns,
2740            },
2741            pipeline_cache_validation_key,
2742            vendor_id: self.phd_capabilities.properties.vendor_id,
2743            timestamp_period: self.phd_capabilities.properties.limits.timestamp_period,
2744            private_caps: self.private_caps.clone(),
2745            features,
2746            workarounds: self.workarounds,
2747            render_passes: Mutex::new(Default::default()),
2748            sampler_cache: Mutex::new(super::sampler::SamplerCache::new(
2749                self.private_caps.maximum_samplers,
2750            )),
2751            memory_allocations_counter: Default::default(),
2752
2753            texture_identity_factory: super::ResourceIdentityFactory::new(),
2754            texture_view_identity_factory: super::ResourceIdentityFactory::new(),
2755            empty_descriptor_set_layout,
2756        });
2757
2758        let relay_semaphores = super::RelaySemaphores::new(&shared)?;
2759
2760        let queue = super::Queue {
2761            raw: raw_queue,
2762            device: Arc::clone(&shared),
2763            family_index,
2764            relay_semaphores: Mutex::new(relay_semaphores),
2765            signal_semaphores: Mutex::new(SemaphoreList::new(SemaphoreListMode::Signal)),
2766        };
2767
2768        let allocation_sizes = AllocationSizes::from_memory_hints(memory_hints).into();
2769
2770        let buffer_device_address = enabled_extensions.contains(&khr::buffer_device_address::NAME);
2771
2772        let mem_allocator =
2773            gpu_allocator::vulkan::Allocator::new(&gpu_allocator::vulkan::AllocatorCreateDesc {
2774                instance: self.instance.raw.clone(),
2775                device: shared.raw.clone(),
2776                physical_device: self.raw,
2777                debug_settings: Default::default(),
2778                buffer_device_address,
2779                allocation_sizes,
2780            })?;
2781
2782        let desc_allocator = gpu_descriptor::DescriptorAllocator::new(
2783            if let Some(di) = self.phd_capabilities.descriptor_indexing {
2784                di.max_update_after_bind_descriptors_in_all_pools
2785            } else {
2786                0
2787            },
2788        );
2789
2790        let device = super::Device {
2791            shared,
2792            mem_allocator: Mutex::new(mem_allocator),
2793            desc_allocator: Mutex::new(desc_allocator),
2794            valid_ash_memory_types,
2795            naga_options,
2796            #[cfg(feature = "renderdoc")]
2797            render_doc: Default::default(),
2798            counters: Default::default(),
2799        };
2800
2801        Ok(crate::OpenDevice { device, queue })
2802    }
2803
2804    pub fn texture_format_as_raw(&self, texture_format: wgt::TextureFormat) -> vk::Format {
2805        self.private_caps.map_texture_format(texture_format)
2806    }
2807
2808    /// # Safety:
2809    /// - Same as `open` plus
2810    /// - The callback may not change anything that the device does not support.
2811    /// - The callback may not remove features.
2812    pub unsafe fn open_with_callback<'a>(
2813        &self,
2814        features: wgt::Features,
2815        limits: &wgt::Limits,
2816        memory_hints: &wgt::MemoryHints,
2817        callback: Option<Box<super::CreateDeviceCallback<'a>>>,
2818    ) -> Result<crate::OpenDevice<super::Api>, crate::DeviceError> {
2819        let mut enabled_extensions = self.required_device_extensions(features);
2820        let mut enabled_phd_features = self.physical_device_features(&enabled_extensions, features);
2821
2822        let family_index = 0; //TODO
2823        let family_info = vk::DeviceQueueCreateInfo::default()
2824            .queue_family_index(family_index)
2825            .queue_priorities(&[1.0]);
2826        let mut family_infos = Vec::from([family_info]);
2827
2828        let mut pre_info = vk::DeviceCreateInfo::default();
2829
2830        if let Some(callback) = callback {
2831            callback(super::CreateDeviceCallbackArgs {
2832                extensions: &mut enabled_extensions,
2833                device_features: &mut enabled_phd_features,
2834                queue_create_infos: &mut family_infos,
2835                create_info: &mut pre_info,
2836                _phantom: PhantomData,
2837            })
2838        }
2839
2840        let str_pointers = enabled_extensions
2841            .iter()
2842            .map(|&s| {
2843                // Safe because `enabled_extensions` entries have static lifetime.
2844                s.as_ptr()
2845            })
2846            .collect::<Vec<_>>();
2847
2848        let pre_info = pre_info
2849            .queue_create_infos(&family_infos)
2850            .enabled_extension_names(&str_pointers);
2851        let info = enabled_phd_features.add_to_device_create(pre_info);
2852        let raw_device = {
2853            profiling::scope!("vkCreateDevice");
2854            unsafe {
2855                self.instance
2856                    .raw
2857                    .create_device(self.raw, &info, None)
2858                    .map_err(map_err)?
2859            }
2860        };
2861        fn map_err(err: vk::Result) -> crate::DeviceError {
2862            match err {
2863                vk::Result::ERROR_TOO_MANY_OBJECTS => crate::DeviceError::OutOfMemory,
2864                vk::Result::ERROR_INITIALIZATION_FAILED => crate::DeviceError::Lost,
2865                vk::Result::ERROR_EXTENSION_NOT_PRESENT | vk::Result::ERROR_FEATURE_NOT_PRESENT => {
2866                    crate::hal_usage_error(err)
2867                }
2868                other => super::map_host_device_oom_and_lost_err(other),
2869            }
2870        }
2871
2872        unsafe {
2873            self.device_from_raw(
2874                raw_device,
2875                None,
2876                &enabled_extensions,
2877                features,
2878                limits,
2879                memory_hints,
2880                family_info.queue_family_index,
2881                0,
2882            )
2883        }
2884    }
2885}
2886
2887impl crate::Adapter for super::Adapter {
2888    type A = super::Api;
2889
2890    unsafe fn open(
2891        &self,
2892        features: wgt::Features,
2893        limits: &wgt::Limits,
2894        memory_hints: &wgt::MemoryHints,
2895    ) -> Result<crate::OpenDevice<super::Api>, crate::DeviceError> {
2896        unsafe { self.open_with_callback(features, limits, memory_hints, None) }
2897    }
2898
2899    unsafe fn texture_format_capabilities(
2900        &self,
2901        format: wgt::TextureFormat,
2902    ) -> crate::TextureFormatCapabilities {
2903        use crate::TextureFormatCapabilities as Tfc;
2904
2905        let vk_format = self.private_caps.map_texture_format(format);
2906        let properties = unsafe {
2907            self.instance
2908                .raw
2909                .get_physical_device_format_properties(self.raw, vk_format)
2910        };
2911        let features = properties.optimal_tiling_features;
2912
2913        let mut flags = Tfc::empty();
2914        flags.set(
2915            Tfc::SAMPLED,
2916            features.contains(vk::FormatFeatureFlags::SAMPLED_IMAGE),
2917        );
2918        flags.set(
2919            Tfc::SAMPLED_LINEAR,
2920            features.contains(vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR),
2921        );
2922        // flags.set(
2923        //     Tfc::SAMPLED_MINMAX,
2924        //     features.contains(vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_MINMAX),
2925        // );
2926        flags.set(
2927            Tfc::STORAGE_READ_WRITE
2928                | Tfc::STORAGE_WRITE_ONLY
2929                | Tfc::STORAGE_READ_ONLY
2930                | Tfc::STORAGE_ATOMIC,
2931            features.contains(vk::FormatFeatureFlags::STORAGE_IMAGE),
2932        );
2933        flags.set(
2934            Tfc::STORAGE_ATOMIC,
2935            features.contains(vk::FormatFeatureFlags::STORAGE_IMAGE_ATOMIC),
2936        );
2937        flags.set(
2938            Tfc::COLOR_ATTACHMENT,
2939            features.contains(vk::FormatFeatureFlags::COLOR_ATTACHMENT),
2940        );
2941        flags.set(
2942            Tfc::COLOR_ATTACHMENT_BLEND,
2943            features.contains(vk::FormatFeatureFlags::COLOR_ATTACHMENT_BLEND),
2944        );
2945        flags.set(
2946            Tfc::DEPTH_STENCIL_ATTACHMENT,
2947            features.contains(vk::FormatFeatureFlags::DEPTH_STENCIL_ATTACHMENT),
2948        );
2949        flags.set(
2950            Tfc::COPY_SRC,
2951            features.intersects(vk::FormatFeatureFlags::TRANSFER_SRC),
2952        );
2953        flags.set(
2954            Tfc::COPY_DST,
2955            features.intersects(vk::FormatFeatureFlags::TRANSFER_DST),
2956        );
2957        flags.set(
2958            Tfc::STORAGE_ATOMIC,
2959            features.intersects(vk::FormatFeatureFlags::STORAGE_IMAGE_ATOMIC),
2960        );
2961        // Vulkan is very permissive about MSAA
2962        flags.set(Tfc::MULTISAMPLE_RESOLVE, !format.is_compressed());
2963
2964        // get the supported sample counts
2965        let format_aspect = crate::FormatAspects::from(format);
2966        let limits = self.phd_capabilities.properties.limits;
2967
2968        let sample_flags = if format_aspect.contains(crate::FormatAspects::DEPTH) {
2969            limits
2970                .framebuffer_depth_sample_counts
2971                .min(limits.sampled_image_depth_sample_counts)
2972        } else if format_aspect.contains(crate::FormatAspects::STENCIL) {
2973            limits
2974                .framebuffer_stencil_sample_counts
2975                .min(limits.sampled_image_stencil_sample_counts)
2976        } else {
2977            let first_aspect = format_aspect
2978                .iter()
2979                .next()
2980                .expect("All texture should at least one aspect")
2981                .map();
2982
2983            // We should never get depth or stencil out of this, due to the above.
2984            assert_ne!(first_aspect, wgt::TextureAspect::DepthOnly);
2985            assert_ne!(first_aspect, wgt::TextureAspect::StencilOnly);
2986
2987            match format.sample_type(Some(first_aspect), None).unwrap() {
2988                wgt::TextureSampleType::Float { .. } => limits
2989                    .framebuffer_color_sample_counts
2990                    .min(limits.sampled_image_color_sample_counts),
2991                wgt::TextureSampleType::Sint | wgt::TextureSampleType::Uint => {
2992                    limits.sampled_image_integer_sample_counts
2993                }
2994                _ => unreachable!(),
2995            }
2996        };
2997
2998        flags.set(
2999            Tfc::MULTISAMPLE_X2,
3000            sample_flags.contains(vk::SampleCountFlags::TYPE_2),
3001        );
3002        flags.set(
3003            Tfc::MULTISAMPLE_X4,
3004            sample_flags.contains(vk::SampleCountFlags::TYPE_4),
3005        );
3006        flags.set(
3007            Tfc::MULTISAMPLE_X8,
3008            sample_flags.contains(vk::SampleCountFlags::TYPE_8),
3009        );
3010        flags.set(
3011            Tfc::MULTISAMPLE_X16,
3012            sample_flags.contains(vk::SampleCountFlags::TYPE_16),
3013        );
3014
3015        flags
3016    }
3017
3018    unsafe fn surface_capabilities(
3019        &self,
3020        surface: &super::Surface,
3021    ) -> Option<crate::SurfaceCapabilities> {
3022        surface.inner.surface_capabilities(self)
3023    }
3024
3025    unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp {
3026        // VK_GOOGLE_display_timing is the only way to get presentation
3027        // timestamps on vulkan right now and it is only ever available
3028        // on android and linux. This includes mac, but there's no alternative
3029        // on mac, so this is fine.
3030        #[cfg(unix)]
3031        {
3032            let mut timespec = libc::timespec {
3033                tv_sec: 0,
3034                tv_nsec: 0,
3035            };
3036            unsafe {
3037                libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut timespec);
3038            }
3039
3040            wgt::PresentationTimestamp(
3041                timespec.tv_sec as u128 * 1_000_000_000 + timespec.tv_nsec as u128,
3042            )
3043        }
3044        #[cfg(not(unix))]
3045        {
3046            wgt::PresentationTimestamp::INVALID_TIMESTAMP
3047        }
3048    }
3049
3050    fn get_ordered_buffer_usages(&self) -> wgt::BufferUses {
3051        wgt::BufferUses::INCLUSIVE | wgt::BufferUses::MAP_WRITE
3052    }
3053
3054    // Vulkan makes very few execution ordering guarantees
3055    // see https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#synchronization-implicit
3056    // We just don't want to insert barriers between inclusive uses
3057    // See https://github.com/gfx-rs/wgpu/issues/8853
3058    fn get_ordered_texture_usages(&self) -> wgt::TextureUses {
3059        wgt::TextureUses::INCLUSIVE
3060    }
3061}
3062
3063fn is_format_16bit_norm_supported(instance: &ash::Instance, phd: vk::PhysicalDevice) -> bool {
3064    [
3065        vk::Format::R16_UNORM,
3066        vk::Format::R16_SNORM,
3067        vk::Format::R16G16_UNORM,
3068        vk::Format::R16G16_SNORM,
3069        vk::Format::R16G16B16A16_UNORM,
3070        vk::Format::R16G16B16A16_SNORM,
3071    ]
3072    .into_iter()
3073    .all(|format| {
3074        supports_format(
3075            instance,
3076            phd,
3077            format,
3078            vk::ImageTiling::OPTIMAL,
3079            vk::FormatFeatureFlags::SAMPLED_IMAGE
3080                | vk::FormatFeatureFlags::STORAGE_IMAGE
3081                | vk::FormatFeatureFlags::TRANSFER_SRC
3082                | vk::FormatFeatureFlags::TRANSFER_DST,
3083        )
3084    })
3085}
3086
3087fn is_float32_filterable_supported(instance: &ash::Instance, phd: vk::PhysicalDevice) -> bool {
3088    [
3089        vk::Format::R32_SFLOAT,
3090        vk::Format::R32G32_SFLOAT,
3091        vk::Format::R32G32B32A32_SFLOAT,
3092    ]
3093    .into_iter()
3094    .all(|format| {
3095        supports_format(
3096            instance,
3097            phd,
3098            format,
3099            vk::ImageTiling::OPTIMAL,
3100            vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR,
3101        )
3102    })
3103}
3104
3105fn is_float32_blendable_supported(instance: &ash::Instance, phd: vk::PhysicalDevice) -> bool {
3106    [
3107        vk::Format::R32_SFLOAT,
3108        vk::Format::R32G32_SFLOAT,
3109        vk::Format::R32G32B32A32_SFLOAT,
3110    ]
3111    .into_iter()
3112    .all(|format| {
3113        supports_format(
3114            instance,
3115            phd,
3116            format,
3117            vk::ImageTiling::OPTIMAL,
3118            vk::FormatFeatureFlags::COLOR_ATTACHMENT_BLEND,
3119        )
3120    })
3121}
3122
3123fn supports_format(
3124    instance: &ash::Instance,
3125    phd: vk::PhysicalDevice,
3126    format: vk::Format,
3127    tiling: vk::ImageTiling,
3128    features: vk::FormatFeatureFlags,
3129) -> bool {
3130    let properties = unsafe { instance.get_physical_device_format_properties(phd, format) };
3131    match tiling {
3132        vk::ImageTiling::LINEAR => properties.linear_tiling_features.contains(features),
3133        vk::ImageTiling::OPTIMAL => properties.optimal_tiling_features.contains(features),
3134        _ => false,
3135    }
3136}
3137
3138fn supports_astc_3d(instance: &ash::Instance, phd: vk::PhysicalDevice) -> bool {
3139    [
3140        vk::Format::ASTC_4X4_UNORM_BLOCK,
3141        vk::Format::ASTC_4X4_SRGB_BLOCK,
3142        vk::Format::ASTC_5X4_UNORM_BLOCK,
3143        vk::Format::ASTC_5X4_SRGB_BLOCK,
3144        vk::Format::ASTC_5X5_UNORM_BLOCK,
3145        vk::Format::ASTC_5X5_SRGB_BLOCK,
3146        vk::Format::ASTC_6X5_UNORM_BLOCK,
3147        vk::Format::ASTC_6X5_SRGB_BLOCK,
3148        vk::Format::ASTC_6X6_UNORM_BLOCK,
3149        vk::Format::ASTC_6X6_SRGB_BLOCK,
3150        vk::Format::ASTC_8X5_UNORM_BLOCK,
3151        vk::Format::ASTC_8X5_SRGB_BLOCK,
3152        vk::Format::ASTC_8X6_UNORM_BLOCK,
3153        vk::Format::ASTC_8X6_SRGB_BLOCK,
3154        vk::Format::ASTC_8X8_UNORM_BLOCK,
3155        vk::Format::ASTC_8X8_SRGB_BLOCK,
3156        vk::Format::ASTC_10X5_UNORM_BLOCK,
3157        vk::Format::ASTC_10X5_SRGB_BLOCK,
3158        vk::Format::ASTC_10X6_UNORM_BLOCK,
3159        vk::Format::ASTC_10X6_SRGB_BLOCK,
3160        vk::Format::ASTC_10X8_UNORM_BLOCK,
3161        vk::Format::ASTC_10X8_SRGB_BLOCK,
3162        vk::Format::ASTC_10X10_UNORM_BLOCK,
3163        vk::Format::ASTC_10X10_SRGB_BLOCK,
3164        vk::Format::ASTC_12X10_UNORM_BLOCK,
3165        vk::Format::ASTC_12X10_SRGB_BLOCK,
3166        vk::Format::ASTC_12X12_UNORM_BLOCK,
3167        vk::Format::ASTC_12X12_SRGB_BLOCK,
3168    ]
3169    .into_iter()
3170    .all(|format| {
3171        unsafe {
3172            instance.get_physical_device_image_format_properties(
3173                phd,
3174                format,
3175                vk::ImageType::TYPE_3D,
3176                vk::ImageTiling::OPTIMAL,
3177                vk::ImageUsageFlags::SAMPLED,
3178                vk::ImageCreateFlags::empty(),
3179            )
3180        }
3181        .is_ok()
3182    })
3183}
3184
3185fn supports_bgra8unorm_storage(
3186    instance: &ash::Instance,
3187    phd: vk::PhysicalDevice,
3188    device_api_version: u32,
3189) -> bool {
3190    // See https://github.com/KhronosGroup/Vulkan-Docs/issues/2027#issuecomment-1380608011
3191
3192    // This check gates the function call and structures used below.
3193    // TODO: check for (`VK_KHR_get_physical_device_properties2` or VK1.1) and (`VK_KHR_format_feature_flags2` or VK1.3).
3194    // Right now we only check for VK1.3.
3195    if device_api_version < vk::API_VERSION_1_3 {
3196        return false;
3197    }
3198
3199    unsafe {
3200        let mut properties3 = vk::FormatProperties3::default();
3201        let mut properties2 = vk::FormatProperties2::default().push_next(&mut properties3);
3202
3203        instance.get_physical_device_format_properties2(
3204            phd,
3205            vk::Format::B8G8R8A8_UNORM,
3206            &mut properties2,
3207        );
3208
3209        let features2 = properties2.format_properties.optimal_tiling_features;
3210        let features3 = properties3.optimal_tiling_features;
3211
3212        features2.contains(vk::FormatFeatureFlags::STORAGE_IMAGE)
3213            && features3.contains(vk::FormatFeatureFlags2::STORAGE_WRITE_WITHOUT_FORMAT)
3214    }
3215}
3216
3217// For https://github.com/gfx-rs/wgpu/issues/4599
3218// Intel iGPUs with outdated drivers can break rendering if `VK_EXT_robustness2` is used.
3219// Driver version 31.0.101.2115 works, but there's probably an earlier functional version.
3220fn is_intel_igpu_outdated_for_robustness2(
3221    props: vk::PhysicalDeviceProperties,
3222    driver: Option<vk::PhysicalDeviceDriverPropertiesKHR>,
3223) -> bool {
3224    const DRIVER_VERSION_WORKING: u32 = (101 << 14) | 2115; // X.X.101.2115
3225
3226    let is_outdated = props.vendor_id == crate::auxil::db::intel::VENDOR
3227        && props.device_type == vk::PhysicalDeviceType::INTEGRATED_GPU
3228        && props.driver_version < DRIVER_VERSION_WORKING
3229        && driver
3230            .map(|driver| driver.driver_id == vk::DriverId::INTEL_PROPRIETARY_WINDOWS)
3231            .unwrap_or_default();
3232
3233    if is_outdated {
3234        log::debug!(
3235            "Disabling robustBufferAccess2 and robustImageAccess2: IntegratedGpu Intel Driver is outdated. Found with version 0x{:X}, less than the known good version 0x{:X} (31.0.101.2115)",
3236            props.driver_version,
3237            DRIVER_VERSION_WORKING
3238        );
3239    }
3240    is_outdated
3241}
3242
3243/// Convert Vulkan component type to wgt::CooperativeScalarType.
3244fn map_vk_component_type(ty: vk::ComponentTypeKHR) -> Option<wgt::CooperativeScalarType> {
3245    match ty {
3246        vk::ComponentTypeKHR::FLOAT16 => Some(wgt::CooperativeScalarType::F16),
3247        vk::ComponentTypeKHR::FLOAT32 => Some(wgt::CooperativeScalarType::F32),
3248        vk::ComponentTypeKHR::SINT32 => Some(wgt::CooperativeScalarType::I32),
3249        vk::ComponentTypeKHR::UINT32 => Some(wgt::CooperativeScalarType::U32),
3250        _ => None,
3251    }
3252}
3253
3254/// Convert Vulkan matrix size.
3255fn map_vk_cooperative_size(size: u32) -> Option<u32> {
3256    match size {
3257        8 | 16 => Some(size),
3258        _ => None,
3259    }
3260}
3261
3262/// Query all supported cooperative matrix configurations from Vulkan.
3263fn query_cooperative_matrix_properties(
3264    coop_matrix: &khr::cooperative_matrix::Instance,
3265    phd: vk::PhysicalDevice,
3266) -> Vec<wgt::CooperativeMatrixProperties> {
3267    let vk_properties =
3268        match unsafe { coop_matrix.get_physical_device_cooperative_matrix_properties(phd) } {
3269            Ok(props) => props,
3270            Err(e) => {
3271                log::warn!("Failed to query cooperative matrix properties: {e:?}");
3272                return Vec::new();
3273            }
3274        };
3275
3276    log::debug!(
3277        "Vulkan reports {} cooperative matrix configurations",
3278        vk_properties.len()
3279    );
3280
3281    let mut result = Vec::new();
3282    for prop in &vk_properties {
3283        log::debug!(
3284            "  Vulkan coop matrix: M={} N={} K={} A={:?} B={:?} C={:?} Result={:?} scope={:?} saturating={}",
3285            prop.m_size,
3286            prop.n_size,
3287            prop.k_size,
3288            prop.a_type,
3289            prop.b_type,
3290            prop.c_type,
3291            prop.result_type,
3292            prop.scope,
3293            prop.saturating_accumulation
3294        );
3295
3296        // Only include subgroup-scoped operations (the only scope we support)
3297        if prop.scope != vk::ScopeKHR::SUBGROUP {
3298            log::debug!("    Skipped: scope is not SUBGROUP");
3299            continue;
3300        }
3301
3302        // Map sizes - skip configurations with sizes we don't support
3303        let m_size = match map_vk_cooperative_size(prop.m_size) {
3304            Some(s) => s,
3305            None => {
3306                log::debug!("    Skipped: M size {} not supported", prop.m_size);
3307                continue;
3308            }
3309        };
3310        let n_size = match map_vk_cooperative_size(prop.n_size) {
3311            Some(s) => s,
3312            None => {
3313                log::debug!("    Skipped: N size {} not supported", prop.n_size);
3314                continue;
3315            }
3316        };
3317        let k_size = match map_vk_cooperative_size(prop.k_size) {
3318            Some(s) => s,
3319            None => {
3320                log::debug!("    Skipped: K size {} not supported", prop.k_size);
3321                continue;
3322            }
3323        };
3324
3325        // Map the component types - A and B must match, C and Result must match
3326        let ab_type = match map_vk_component_type(prop.a_type) {
3327            Some(t) if Some(t) == map_vk_component_type(prop.b_type) => t,
3328            _ => {
3329                log::debug!(
3330                    "    Skipped: A/B types {:?}/{:?} not supported or don't match",
3331                    prop.a_type,
3332                    prop.b_type
3333                );
3334                continue;
3335            }
3336        };
3337        let cr_type = match map_vk_component_type(prop.c_type) {
3338            Some(t) if Some(t) == map_vk_component_type(prop.result_type) => t,
3339            _ => {
3340                log::debug!(
3341                    "    Skipped: C/Result types {:?}/{:?} not supported or don't match",
3342                    prop.c_type,
3343                    prop.result_type
3344                );
3345                continue;
3346            }
3347        };
3348
3349        log::debug!("    Accepted!");
3350        result.push(wgt::CooperativeMatrixProperties {
3351            m_size,
3352            n_size,
3353            k_size,
3354            ab_type,
3355            cr_type,
3356            saturating_accumulation: prop.saturating_accumulation != 0,
3357        });
3358    }
3359
3360    log::info!(
3361        "Found {} cooperative matrix configurations supported by wgpu",
3362        result.len()
3363    );
3364    result
3365}