Skip to main content

vk_video/
adapter.rs

1use ash::vk;
2use std::{
3    ffi::CStr,
4    fmt::{self, Debug},
5    sync::Arc,
6};
7use tracing::{debug, debug_span, warn};
8
9#[cfg(feature = "wgpu")]
10use wgpu::hal::{DynAdapter, vulkan::Api as VkApi};
11
12use crate::{
13    VulkanDevice, VulkanInitError, VulkanInstance,
14    capabilities::EncodeCapabilities,
15    device::{
16        DECODE_EXTENSIONS, ENCODE_EXTENSIONS, REQUIRED_EXTENSIONS, VulkanDeviceDescriptor,
17        caps::{DecodeCapabilities, NativeDecodeCapabilities, NativeEncodeCapabilities},
18        queues::{QueueIndex, QueueIndices},
19    },
20};
21
22/// Represents a handle to a physical device.
23/// Can be used to create [`VulkanDevice`].
24pub struct VulkanAdapter<'a> {
25    #[cfg(feature = "wgpu")]
26    pub(crate) wgpu_adapter: wgpu::hal::ExposedAdapter<VkApi>,
27
28    pub(crate) instance: &'a VulkanInstance,
29    pub(crate) physical_device: vk::PhysicalDevice,
30    pub(crate) queue_indices: QueueIndices<'static>,
31    pub(crate) decode_capabilities: Option<NativeDecodeCapabilities>,
32    pub(crate) encode_capabilities: Option<NativeEncodeCapabilities>,
33    pub(crate) info: AdapterInfo,
34}
35
36impl<'a> VulkanAdapter<'a> {
37    fn new(vulkan_instance: &'a VulkanInstance, device: vk::PhysicalDevice) -> Option<Self> {
38        let instance = &vulkan_instance.instance;
39
40        #[cfg(feature = "wgpu")]
41        let wgpu_adapter = expose_wgpu_adapter(vulkan_instance, device)?;
42
43        let properties = unsafe { instance.get_physical_device_properties(device) };
44        let device_name = properties
45            .device_name_as_c_str()
46            .map(CStr::to_string_lossy)
47            .unwrap_or("unknown".into());
48
49        let _span = debug_span!("creating adapter", device_name = %device_name).entered();
50
51        let mut vk_13_features = vk::PhysicalDeviceVulkan13Features::default();
52        let mut features = vk::PhysicalDeviceFeatures2::default().push_next(&mut vk_13_features);
53
54        unsafe { instance.get_physical_device_features2(device, &mut features) };
55        let extensions = match unsafe { instance.enumerate_device_extension_properties(device) } {
56            Ok(ext) => ext,
57            Err(err) => {
58                warn!("Couldn't enumerate device extension properties: {err}");
59                return None;
60            }
61        };
62
63        if vk_13_features.synchronization2 == vk::FALSE {
64            debug!("device does not support the required synchronization2 feature");
65            return None;
66        }
67
68        if let Err(missing) = check_extensions(REQUIRED_EXTENSIONS, &extensions) {
69            debug!(missing_extensions = ?missing, "device is missing some required extensions",);
70            return None;
71        }
72
73        let has_decode_extensions = check_extensions(DECODE_EXTENSIONS, &extensions).is_ok();
74        let has_encode_extensions = check_extensions(ENCODE_EXTENSIONS, &extensions).is_ok();
75        if !has_decode_extensions && !has_encode_extensions {
76            debug!("device does not support encoding or decoding extensions");
77            return None;
78        }
79
80        let queues_len =
81            unsafe { instance.get_physical_device_queue_family_properties2_len(device) };
82        let mut queues = vec![vk::QueueFamilyProperties2::default(); queues_len];
83        let mut video_properties = vec![vk::QueueFamilyVideoPropertiesKHR::default(); queues_len];
84        let mut query_result_status_properties =
85            vec![vk::QueueFamilyQueryResultStatusPropertiesKHR::default(); queues_len];
86
87        for ((queue, video_properties), query_result_properties) in queues
88            .iter_mut()
89            .zip(video_properties.iter_mut())
90            .zip(query_result_status_properties.iter_mut())
91        {
92            *queue = queue
93                .push_next(query_result_properties)
94                .push_next(video_properties);
95        }
96
97        unsafe { instance.get_physical_device_queue_family_properties2(device, &mut queues) };
98
99        let decode_capabilities = match has_decode_extensions {
100            true => Some(NativeDecodeCapabilities::query(instance, device)),
101            false => None,
102        };
103
104        let encode_capabilities = match has_encode_extensions {
105            true => Some(NativeEncodeCapabilities::query(instance, device)),
106            false => None,
107        };
108
109        let queue_counts = queues
110            .iter()
111            .map(|q| q.queue_family_properties.queue_count)
112            .collect::<Vec<_>>();
113
114        let transfer_queue_idx = queues
115            .iter()
116            .enumerate()
117            .find(|(_, q)| {
118                q.queue_family_properties
119                    .queue_flags
120                    .contains(vk::QueueFlags::TRANSFER)
121                    && !q
122                        .queue_family_properties
123                        .queue_flags
124                        .intersects(vk::QueueFlags::GRAPHICS)
125            })
126            .map(|(i, _)| i)?;
127
128        let compute_queue_idx = queues
129            .iter()
130            .enumerate()
131            .find(|(_, q)| {
132                q.queue_family_properties
133                    .queue_flags
134                    .contains(vk::QueueFlags::COMPUTE)
135                    && !q
136                        .queue_family_properties
137                        .queue_flags
138                        .intersects(vk::QueueFlags::GRAPHICS)
139            })
140            .map(|(i, _)| i)?;
141
142        let graphics_transfer_compute_queue_idx = queues
143            .iter()
144            .enumerate()
145            .find(|(_, q)| {
146                q.queue_family_properties.queue_flags.contains(
147                    vk::QueueFlags::GRAPHICS | vk::QueueFlags::TRANSFER | vk::QueueFlags::COMPUTE,
148                )
149            })
150            .map(|(i, _)| i)?;
151
152        let decode_queue_idx = match has_decode_extensions {
153            true => find_video_queue_idx(
154                &queues,
155                vk::QueueFlags::VIDEO_DECODE_KHR,
156                vk::VideoCodecOperationFlagsKHR::DECODE_H264,
157            ),
158            false => None,
159        };
160        let encode_queue_idx = match has_encode_extensions {
161            true => find_video_queue_idx(
162                &queues,
163                vk::QueueFlags::VIDEO_ENCODE_KHR,
164                vk::VideoCodecOperationFlagsKHR::ENCODE_H264,
165            ),
166            false => None,
167        };
168
169        if decode_queue_idx.is_none() && encode_queue_idx.is_none() {
170            debug!("device does not have any queues that support video operations");
171            return None;
172        }
173
174        debug!("decode capabilities: {decode_capabilities:#?}");
175        debug!("encode capabilities: {encode_capabilities:#?}");
176
177        let (driver_name, driver_info) = match properties.api_version >= vk::API_VERSION_1_2 {
178            true => {
179                let mut driver_properties = vk::PhysicalDeviceDriverProperties::default();
180                let mut properties2 =
181                    vk::PhysicalDeviceProperties2::default().push_next(&mut driver_properties);
182                unsafe {
183                    instance.get_physical_device_properties2(device, &mut properties2);
184                }
185
186                let driver_name = driver_properties
187                    .driver_name_as_c_str()
188                    .map(CStr::to_string_lossy)
189                    .unwrap_or("unknown".into())
190                    .into_owned();
191                let driver_info = driver_properties
192                    .driver_info_as_c_str()
193                    .map(CStr::to_string_lossy)
194                    .unwrap_or_default()
195                    .into_owned();
196                (driver_name, driver_info)
197            }
198            false => ("unknown".to_owned(), "".to_owned()),
199        };
200
201        let info = AdapterInfo {
202            name: device_name.into_owned(),
203            driver_name,
204            driver_info,
205            device_type: properties.device_type,
206            device_properties: properties,
207            supports_decoding: decode_queue_idx.is_some(),
208            supports_encoding: encode_queue_idx.is_some(),
209            decode_capabilities: DecodeCapabilities {
210                h264: decode_capabilities
211                    .as_ref()
212                    .map(NativeDecodeCapabilities::user_facing),
213            },
214            encode_capabilities: EncodeCapabilities {
215                h264: encode_capabilities
216                    .as_ref()
217                    .map(NativeEncodeCapabilities::user_facing),
218            },
219        };
220
221        Some(Self {
222            #[cfg(feature = "wgpu")]
223            wgpu_adapter,
224
225            instance: vulkan_instance,
226            physical_device: device,
227            queue_indices: QueueIndices {
228                transfer: QueueIndex {
229                    family_index: transfer_queue_idx,
230                    queue_count: queue_counts[transfer_queue_idx] as usize,
231                    video_properties: video_properties[transfer_queue_idx],
232                    query_result_status_properties: query_result_status_properties
233                        [transfer_queue_idx],
234                },
235                compute: QueueIndex {
236                    family_index: compute_queue_idx,
237                    queue_count: queue_counts[compute_queue_idx] as usize,
238                    video_properties: video_properties[compute_queue_idx],
239                    query_result_status_properties: query_result_status_properties
240                        [compute_queue_idx],
241                },
242                h264_decode: decode_queue_idx.map(|idx| QueueIndex {
243                    family_index: idx,
244                    queue_count: queue_counts[idx] as usize,
245                    video_properties: video_properties[idx],
246                    query_result_status_properties: query_result_status_properties[idx],
247                }),
248                h264_encode: encode_queue_idx.map(|idx| QueueIndex {
249                    family_index: idx,
250                    queue_count: queue_counts[idx] as usize,
251                    video_properties: video_properties[idx],
252                    query_result_status_properties: query_result_status_properties[idx],
253                }),
254                graphics_transfer_compute: QueueIndex {
255                    family_index: graphics_transfer_compute_queue_idx,
256                    queue_count: 1, // Currently we can only handle 1 queue
257                    video_properties: video_properties[graphics_transfer_compute_queue_idx],
258                    query_result_status_properties: query_result_status_properties
259                        [graphics_transfer_compute_queue_idx],
260                },
261            },
262            decode_capabilities,
263            encode_capabilities,
264            info,
265        })
266    }
267
268    pub fn supports_decoding(&self) -> bool {
269        self.info.supports_decoding
270    }
271
272    pub fn supports_encoding(&self) -> bool {
273        self.info.supports_encoding
274    }
275
276    #[cfg(feature = "wgpu")]
277    pub fn supports_surface(&self, surface: &wgpu::Surface<'_>) -> bool {
278        unsafe {
279            surface
280                .as_hal::<VkApi>()
281                .and_then(|surface| {
282                    self.wgpu_adapter
283                        .adapter
284                        .surface_capabilities(&surface as &wgpu::hal::vulkan::Surface)
285                })
286                .is_some()
287        }
288    }
289
290    pub fn create_device(
291        self,
292        descriptor: &VulkanDeviceDescriptor,
293    ) -> Result<Arc<VulkanDevice>, VulkanInitError> {
294        Ok(VulkanDevice::new(self.instance, self, descriptor)?.into())
295    }
296
297    pub fn info(&self) -> &AdapterInfo {
298        &self.info
299    }
300}
301
302// TODO: maybe there should be a way of specifying power preference / device preference (like wgpu)
303/// Describes a [`VulkanAdapter`].
304/// Used by [`VulkanInstance::create_adapter`]
305#[cfg(feature = "wgpu")]
306pub struct VulkanAdapterDescriptor<'a> {
307    pub supports_decoding: bool,
308    pub supports_encoding: bool,
309    pub compatible_surface: Option<&'a wgpu::Surface<'a>>,
310}
311
312#[cfg(not(feature = "wgpu"))]
313pub struct VulkanAdapterDescriptor {
314    pub supports_decoding: bool,
315    pub supports_encoding: bool,
316}
317
318#[cfg(feature = "wgpu")]
319impl Default for VulkanAdapterDescriptor<'_> {
320    fn default() -> Self {
321        Self {
322            supports_decoding: true,
323            supports_encoding: true,
324            compatible_surface: None,
325        }
326    }
327}
328
329#[cfg(not(feature = "wgpu"))]
330impl Default for VulkanAdapterDescriptor {
331    fn default() -> Self {
332        Self {
333            supports_decoding: true,
334            supports_encoding: true,
335        }
336    }
337}
338
339pub struct AdapterInfo {
340    pub name: String,
341    pub driver_name: String,
342    pub driver_info: String,
343    pub device_type: vk::PhysicalDeviceType,
344    pub supports_decoding: bool,
345    pub supports_encoding: bool,
346    pub device_properties: vk::PhysicalDeviceProperties,
347    pub decode_capabilities: DecodeCapabilities,
348    pub encode_capabilities: EncodeCapabilities,
349}
350
351impl Debug for AdapterInfo {
352    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
353        let version = {
354            let version = self.device_properties.api_version;
355            let major = vk::api_version_major(version);
356            let minor = vk::api_version_minor(version);
357            let patch = vk::api_version_patch(version);
358
359            format!("{major}.{minor}.{patch}")
360        };
361        f.debug_struct("AdapterInfo")
362            .field("name", &self.name)
363            .field("device_type", &self.device_type)
364            .field("api_version", &version)
365            .field("driver", &self.driver_name)
366            .field("driver_info", &self.driver_info)
367            .field("vendor", &self.device_properties.vendor_id)
368            .field("device", &self.device_properties.device_id)
369            .field("supports_decoding", &self.supports_decoding)
370            .field("supports_encoding", &self.supports_encoding)
371            .finish()
372    }
373}
374
375/// This macro will iterate over the `p_next` chain of the base struct until it finds a struct,
376/// which matches the given type. After that it will execute the given action on the found struct.
377///
378/// # Example
379/// ```ignore
380/// unsafe {
381///     find_ext!(queue_family_properties, found_extension @ ash::vk::QueueFamilyVideoPropertiesKHR => {
382///         dbg!(found_extension)
383///     });
384/// }
385/// ```
386#[cfg_attr(doctest, macro_export)]
387macro_rules! find_ext {
388    ($base:expr, $var:ident @ $ext:ty => $action:stmt) => {
389        let mut next = $base.p_next.cast::<ash::vk::BaseOutStructure>();
390        while !next.is_null() {
391            ash::match_out_struct!(match next {
392                $var @ $ext => {
393                    $action
394                    break;
395                }
396            });
397
398            next = (*next).p_next;
399        }
400    };
401}
402
403pub(crate) fn iter_adapters<'a>(
404    vulkan_instance: &'a VulkanInstance,
405) -> Result<impl Iterator<Item = VulkanAdapter<'a>> + 'a, VulkanInitError> {
406    let physical_devices = unsafe { vulkan_instance.instance.enumerate_physical_devices()? };
407    Ok(physical_devices
408        .into_iter()
409        .filter_map(move |device| VulkanAdapter::new(vulkan_instance, device)))
410}
411
412/// Returns the list of missing extensions
413fn check_extensions<'a>(
414    required_extensions: &'a [&'a CStr],
415    available_extensions: &'a [vk::ExtensionProperties],
416) -> Result<(), Vec<&'a CStr>> {
417    let missing = required_extensions
418        .iter()
419        .copied()
420        .filter(|&required_name| {
421            !available_extensions.iter().any(|ext| {
422                let Ok(name) = ext.extension_name_as_c_str() else {
423                    return false;
424                };
425
426                name == required_name
427            })
428        })
429        .collect::<Vec<_>>();
430
431    if !missing.is_empty() {
432        return Err(missing);
433    }
434
435    Ok(())
436}
437
438fn find_video_queue_idx(
439    queues: &[vk::QueueFamilyProperties2<'_>],
440    queue_flag: vk::QueueFlags,
441    video_codec_operation: vk::VideoCodecOperationFlagsKHR,
442) -> Option<usize> {
443    for (i, queue) in queues.iter().enumerate() {
444        if !queue
445            .queue_family_properties
446            .queue_flags
447            .contains(queue_flag)
448        {
449            continue;
450        }
451
452        unsafe {
453            find_ext!(queue, video_properties @ vk::QueueFamilyVideoPropertiesKHR =>
454                if video_properties
455                    .video_codec_operations
456                    .contains(video_codec_operation)
457                {
458                    return Some(i);
459                }
460            );
461        }
462    }
463
464    None
465}
466
467#[cfg(feature = "wgpu")]
468fn expose_wgpu_adapter(
469    vulkan_instance: &VulkanInstance,
470    device: vk::PhysicalDevice,
471) -> Option<wgpu::hal::ExposedAdapter<VkApi>> {
472    let wgpu_instance = &vulkan_instance.wgpu_instance;
473    let wgpu_instance = unsafe { wgpu_instance.as_hal::<VkApi>() }.unwrap();
474    wgpu_instance.expose_adapter(device)
475}