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
22pub 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, 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#[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#[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
412fn 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}