Skip to main content

vk_graph/driver/
device.rs

1//! Logical device types
2
3use {
4    super::{
5        DriverError,
6        instance::{ApiVersion, Instance, InstanceInfoBuilder},
7        physical_device::PhysicalDevice,
8    },
9    ash::{ext, khr, vk},
10    derive_builder::Builder,
11    gpu_allocator::{
12        AllocatorDebugSettings,
13        vulkan::{Allocator, AllocatorCreateDesc},
14    },
15    log::{error, info, trace, warn},
16    raw_window_handle::HasDisplayHandle,
17    std::{
18        collections::HashMap,
19        ffi::CString,
20        fmt::{Debug, Formatter},
21        mem::{ManuallyDrop, forget},
22        ops::Deref,
23        slice,
24        sync::Arc,
25        sync::atomic::{AtomicU64, Ordering},
26        thread::panicking,
27        time::Instant,
28    },
29};
30
31#[cfg(feature = "parking_lot")]
32use parking_lot::Mutex;
33
34#[cfg(not(feature = "parking_lot"))]
35use std::sync::Mutex;
36
37fn select_physical_device(
38    instance: &Instance,
39    mut index: usize,
40) -> Result<PhysicalDevice, DriverError> {
41    let mut physical_devices = Instance::physical_devices(instance)?
42        .into_iter()
43        .collect::<Vec<_>>();
44    if physical_devices.is_empty() {
45        warn!("unable to find physical devices");
46
47        return Err(DriverError::Unsupported);
48    }
49
50    if index >= physical_devices.len() {
51        index = 0;
52    }
53
54    let physical_device = physical_devices.remove(index);
55
56    Ok(physical_device)
57}
58
59/// Opaque handle to a device object.
60#[read_only::embed]
61#[derive(Clone)]
62pub struct Device {
63    #[readonly]
64    pub(self) inner: Arc<DeviceInner>,
65
66    /// The selected physical device.
67    ///
68    /// This contains the physical device's advertised features, properties, limits, memory
69    /// properties, queue families, and Vulkan extension support. Extension support is exposed with
70    /// fields named after the Vulkan extension, for example
71    /// `device.physical.vk_khr_acceleration_structure.is_some()`.
72    ///
73    /// _Note:_ This field is read-only.
74    #[readonly]
75    pub physical: Box<PhysicalDevice>,
76}
77
78impl Device {
79    /// Begins recording a command buffer on this device.
80    ///
81    /// This is a thin wrapper around [`ash::Device::begin_command_buffer`] that maps Vulkan errors
82    /// to [`DriverError`] variants.
83    pub fn begin_command_buffer(
84        this: &Self,
85        cmd: vk::CommandBuffer,
86        begin_info: &vk::CommandBufferBeginInfo,
87    ) -> Result<(), DriverError> {
88        unsafe {
89            this.begin_command_buffer(cmd, begin_info).map_err(|err| {
90                warn!("unable to begin command buffer: {err}");
91
92                match err {
93                    vk::Result::ERROR_OUT_OF_DEVICE_MEMORY
94                    | vk::Result::ERROR_OUT_OF_HOST_MEMORY => DriverError::OutOfMemory,
95                    _ => DriverError::Unsupported,
96                }
97            })
98        }
99    }
100
101    /// Begins a Vulkan debug label region on `command_buffer` when debug labeling is enabled.
102    ///
103    /// Returns without doing any work if debug mode is `false`.
104    pub fn begin_debug_utils_label(
105        this: &Self,
106        command_buffer: vk::CommandBuffer,
107        label_name: impl AsRef<str>,
108    ) -> Result<(), DriverError> {
109        if !this.physical.instance.info.debug {
110            return Ok(());
111        }
112
113        let Ok(label_name) = CString::new(label_name.as_ref()) else {
114            warn!("invalid label name");
115
116            return Err(DriverError::InvalidData);
117        };
118
119        let ext = Self::try_vk_ext_debug_utils(this)?;
120
121        unsafe {
122            ext.cmd_begin_debug_utils_label(
123                command_buffer,
124                &vk::DebugUtilsLabelEXT::default().label_name(label_name.as_c_str()),
125            );
126        }
127
128        Ok(())
129    }
130
131    /// Clears Vulkan private-data metadata associated with `object_type` and `object_handle`.
132    pub(crate) fn clear_private_data_object_name<T>(
133        this: &Self,
134        object_type: vk::ObjectType,
135        object_handle: T,
136    ) -> Result<(), DriverError>
137    where
138        T: vk::Handle + Copy,
139    {
140        if this.inner.private_data_slot.is_none() {
141            return Ok(());
142        }
143
144        if object_handle.is_null() {
145            warn!("invalid object handle");
146
147            return Err(DriverError::InvalidData);
148        }
149
150        let object_key = (object_type, object_handle.as_raw());
151        let previous_metadata_id = Self::with_object_metadata_ids(this, |object_to_metadata_id| {
152            object_to_metadata_id.remove(&object_key)
153        });
154
155        if previous_metadata_id.is_none() {
156            return Ok(());
157        }
158
159        let ext = Self::try_vk_ext_private_data(this)?;
160        let private_data_slot = this
161            .inner
162            .private_data_slot
163            .expect("missing private data slot");
164
165        if let Err(err) = unsafe { ext.set_private_data(object_handle, private_data_slot, 0) } {
166            Self::with_object_metadata_ids(this, |object_metadata_ids| {
167                if let Some(metadata_id) = previous_metadata_id {
168                    object_metadata_ids.insert(object_key, metadata_id);
169                }
170            });
171
172            warn!("unable to clear private data object name: {err}");
173
174            return Err(match err {
175                vk::Result::ERROR_OUT_OF_DEVICE_MEMORY | vk::Result::ERROR_OUT_OF_HOST_MEMORY => {
176                    DriverError::OutOfMemory
177                }
178                _ => DriverError::Unsupported,
179            });
180        }
181
182        Self::with_private_data_metadata(this, |metadata| {
183            metadata
184                .names
185                .remove(&previous_metadata_id.expect("metadata id removed"));
186        });
187
188        Ok(())
189    }
190
191    /// Records a pipeline barrier using the `VK_KHR_synchronization2` extension or the Vulkan 1.3
192    /// core `vkCmdPipelineBarrier2` path.
193    ///
194    /// See [`vkCmdPipelineBarrier2`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkCmdPipelineBarrier2.html)
195    /// and [`VK_KHR_synchronization2`](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_synchronization2.html).
196    pub fn cmd_pipeline_barrier2(
197        this: &Self,
198        command_buffer: vk::CommandBuffer,
199        dependency_info: &vk::DependencyInfo,
200    ) {
201        #[cfg(feature = "checked")]
202        assert!(
203            this.physical.vk_khr_synchronization2,
204            "missing synchronization2 feature"
205        );
206
207        unsafe {
208            if this.physical.instance.info.api_version >= ApiVersion::Vulkan13 {
209                this.cmd_pipeline_barrier2(command_buffer, dependency_info);
210            } else {
211                let khr_synchronization2 = Device::expect_vk_khr_synchronization2(this);
212
213                khr_synchronization2.cmd_pipeline_barrier2(command_buffer, dependency_info);
214            }
215        }
216    }
217
218    /// Constructs a new device using the given configuration.
219    ///
220    /// This constructor is intended for headless or manually managed setups. It does not infer or
221    /// enable display platform surface extensions. Use [`Self::try_from_display`] when the
222    /// resulting device must be capable of later surface creation.
223    #[profiling::function]
224    pub fn create(info: impl Into<DeviceInfo>) -> Result<Self, DriverError> {
225        let DeviceInfo {
226            debug,
227            physical_device_index,
228        } = info.into();
229        let instance_info = InstanceInfoBuilder::default().debug(debug);
230        let instance = Instance::create(instance_info)?;
231        let physical_device = select_physical_device(&instance, physical_device_index)?;
232
233        Self::try_from_physical_device(physical_device)
234    }
235
236    /// Creates a Vulkan fence on this device.
237    ///
238    /// Pass `true` for `signaled` when the fence should begin in the signaled state.
239    ///
240    /// See [`vkCreateFence`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkCreateFence.html).
241    pub fn create_fence(this: &Self, signaled: bool) -> Result<vk::Fence, DriverError> {
242        let mut flags = vk::FenceCreateFlags::empty();
243
244        if signaled {
245            flags |= vk::FenceCreateFlags::SIGNALED;
246        }
247
248        let create_info = vk::FenceCreateInfo::default().flags(flags);
249        let allocation_callbacks = None;
250
251        unsafe {
252            this.create_fence(&create_info, allocation_callbacks)
253                .map_err(|err| {
254                    warn!("unable to create fence: {err}");
255
256                    DriverError::OutOfMemory
257                })
258        }
259    }
260
261    /// Creates a Vulkan binary semaphore on this device.
262    ///
263    /// See [`vkCreateSemaphore`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkCreateSemaphore.html).
264    pub fn create_semaphore(this: &Self) -> Result<vk::Semaphore, DriverError> {
265        let create_info = vk::SemaphoreCreateInfo::default();
266        let allocation_callbacks = None;
267
268        unsafe {
269            this.create_semaphore(&create_info, allocation_callbacks)
270                .map_err(|err| {
271                    warn!("unable to create semaphore: {err}");
272
273                    DriverError::OutOfMemory
274                })
275        }
276    }
277
278    /// Ends recording a command buffer on this device.
279    ///
280    /// This is a thin wrapper around [`ash::Device::end_command_buffer`] that maps Vulkan errors
281    /// to [`DriverError`] variants.
282    ///
283    /// See [`vkEndCommandBuffer`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkEndCommandBuffer.html).
284    pub fn end_command_buffer(this: &Self, cmd: vk::CommandBuffer) -> Result<(), DriverError> {
285        unsafe {
286            this.end_command_buffer(cmd).map_err(|err| {
287                warn!("unable to end command buffer: {err}");
288
289                match err {
290                    vk::Result::ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR => DriverError::InvalidData,
291                    vk::Result::ERROR_OUT_OF_DEVICE_MEMORY
292                    | vk::Result::ERROR_OUT_OF_HOST_MEMORY => DriverError::OutOfMemory,
293                    _ => DriverError::Unsupported,
294                }
295            })
296        }
297    }
298
299    /// Ends a Vulkan debug label region on `command_buffer` when debug labeling is enabled.
300    ///
301    /// Returns without doing any work if debug mode is `false`.
302    pub fn end_debug_utils_label(
303        this: &Self,
304        command_buffer: vk::CommandBuffer,
305    ) -> Result<(), DriverError> {
306        if !this.physical.instance.info.debug {
307            return Ok(());
308        }
309
310        let ext = Self::try_vk_ext_debug_utils(this)?;
311
312        unsafe {
313            ext.cmd_end_debug_utils_label(command_buffer);
314        }
315
316        Ok(())
317    }
318
319    /// Helper for times when you already know that the device supports the acceleration
320    /// structure extension.
321    ///
322    /// # Panics
323    ///
324    /// Panics if acceleration structure support was not enabled for this device.
325    pub(crate) fn expect_vk_khr_acceleration_structure(
326        this: &Self,
327    ) -> &khr::acceleration_structure::Device {
328        this.inner
329            .vk_khr_acceleration_structure
330            .as_ref()
331            .expect("missing VK_KHR_acceleration_structure")
332    }
333
334    /// Helper for times when you already know that present wait support is enabled.
335    ///
336    /// # Panics
337    ///
338    /// Panics if `VK_KHR_present_wait` support was not enabled for this device.
339    pub(crate) fn expect_vk_khr_present_wait(this: &Self) -> &khr::present_wait::Device {
340        this.inner
341            .vk_khr_present_wait
342            .as_ref()
343            .expect("missing VK_KHR_present_wait")
344    }
345
346    /// Helper for times when you already know that the device supports the ray tracing pipeline
347    /// extension.
348    ///
349    /// # Panics
350    ///
351    /// Panics if ray tracing pipeline support was not enabled for this device.
352    pub(crate) fn expect_vk_khr_ray_tracing_pipeline(
353        this: &Self,
354    ) -> &khr::ray_tracing_pipeline::Device {
355        this.inner
356            .vk_khr_ray_tracing_pipeline
357            .as_ref()
358            .expect("missing VK_KHR_ray_tracing_pipeline")
359    }
360
361    /// Helper for times when you already know that the instance supports the surface extension.
362    ///
363    /// # Panics
364    ///
365    /// Panics if the device was not created for display window access.
366    pub(crate) fn expect_vk_khr_surface(this: &Self) -> &khr::surface::Instance {
367        this.inner
368            .vk_khr_surface
369            .as_ref()
370            .expect("missing VK_KHR_surface")
371    }
372
373    /// Helper for times when you already know that the device supports the synchronization2
374    /// extension.
375    ///
376    /// # Panics
377    ///
378    /// Panics if `VK_KHR_synchronization2` is not available.
379    pub(crate) fn expect_vk_khr_synchronization2(this: &Self) -> &khr::synchronization2::Device {
380        this.inner
381            .vk_khr_synchronization2
382            .as_ref()
383            .expect("missing VK_KHR_synchronization2")
384    }
385
386    /// Helper for times when you already know that the device supports the swapchain extension.
387    ///
388    /// # Panics
389    ///
390    /// Panics if the device was not created for display window access.
391    pub(crate) fn expect_vk_khr_swapchain(this: &Self) -> &khr::swapchain::Device {
392        this.inner
393            .vk_khr_swapchain
394            .as_ref()
395            .expect("missing VK_KHR_swapchain")
396    }
397
398    /// Removes local Vulkan private-data metadata without touching the Vulkan object.
399    pub(crate) fn forget_private_data_object_name<T>(
400        this: &Self,
401        object_type: vk::ObjectType,
402        object_handle: T,
403    ) where
404        T: vk::Handle + Copy,
405    {
406        if this.inner.private_data_slot.is_none() || object_handle.is_null() {
407            return;
408        }
409
410        let object_key = (object_type, object_handle.as_raw());
411        let Some(metadata_id) = Self::with_object_metadata_ids(this, |object_metadata_ids| {
412            object_metadata_ids.remove(&object_key)
413        }) else {
414            return;
415        };
416
417        Self::with_private_data_metadata(this, |metadata| {
418            metadata.names.remove(&metadata_id);
419        });
420    }
421
422    /// Returns `true` if both handles refer to the same logical device allocation.
423    pub(crate) fn is_same(lhs: &Self, rhs: &Self) -> bool {
424        Arc::ptr_eq(&lhs.inner, &rhs.inner)
425    }
426
427    pub(crate) fn identity(this: &Self) -> usize {
428        Arc::as_ptr(&this.inner) as usize
429    }
430
431    /// Returns the device-owned pipeline cache handle.
432    pub(crate) fn pipeline_cache(this: &Self) -> vk::PipelineCache {
433        this.inner.pipeline_cache
434    }
435
436    /// Merges serialized Vulkan pipeline-cache data into the device-owned cache.
437    pub fn merge_pipeline_cache_data(this: &Self, data: &[u8]) -> Result<(), DriverError> {
438        let info = vk::PipelineCacheCreateInfo::default().initial_data(data);
439
440        let source =
441            unsafe { this.create_pipeline_cache(&info, None) }.map_err(|err| match err {
442                vk::Result::ERROR_OUT_OF_DEVICE_MEMORY | vk::Result::ERROR_OUT_OF_HOST_MEMORY => {
443                    DriverError::OutOfMemory
444                }
445                _ => DriverError::Unsupported,
446            })?;
447
448        // Safety: DO NOT BREAK THE 1-2-3 CHAIN
449        // 1. Observe result but do not break control flow
450        let result = unsafe { this.merge_pipeline_caches(this.inner.pipeline_cache, &[source]) }
451            .map_err(|err| match err {
452                vk::Result::ERROR_OUT_OF_DEVICE_MEMORY | vk::Result::ERROR_OUT_OF_HOST_MEMORY => {
453                    DriverError::OutOfMemory
454                }
455                _ => DriverError::Unsupported,
456            });
457
458        // 2. Always destroy obj
459        unsafe { this.destroy_pipeline_cache(source, None) };
460
461        // 3. Return
462        result
463    }
464
465    /// Returns serialized data from the device-owned Vulkan pipeline cache.
466    pub fn pipeline_cache_data(this: &Self) -> Result<Box<[u8]>, DriverError> {
467        unsafe { this.get_pipeline_cache_data(this.inner.pipeline_cache) }
468            .map_err(|err| match err {
469                vk::Result::ERROR_OUT_OF_DEVICE_MEMORY | vk::Result::ERROR_OUT_OF_HOST_MEMORY => {
470                    DriverError::OutOfMemory
471                }
472                _ => DriverError::Unsupported,
473            })
474            .map(Vec::into_boxed_slice)
475    }
476
477    /// Retrieves a Vulkan private-data name associated with `handle`.
478    ///
479    /// Returns `None` when metadata is not available or when the `VK_EXT_private_data` extension is
480    /// not available.
481    pub(crate) fn private_data_object_name<T>(
482        this: &Self,
483        object_type: vk::ObjectType,
484        object_handle: T,
485    ) -> Option<String>
486    where
487        T: vk::Handle + Copy,
488    {
489        this.inner.private_data_slot?;
490
491        if object_handle.is_null() {
492            return None;
493        }
494
495        let object_key = (object_type, object_handle.as_raw());
496        Self::with_private_data_metadata(this, |metadata| {
497            let metadata_id = metadata.object_metadata_ids.get(&object_key)?;
498
499            metadata.names.get(metadata_id).cloned()
500        })
501    }
502
503    /// Submits command buffers to a queue, optionally signaling a fence.
504    ///
505    /// See [`vkQueueSubmit`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkQueueSubmit.html).
506    pub fn queue_submit(
507        this: &Self,
508        queue: vk::Queue,
509        submits: &[vk::SubmitInfo],
510        fence: vk::Fence,
511    ) -> Result<(), DriverError> {
512        unsafe {
513            this.queue_submit(queue, submits, fence).map_err(|err| {
514                warn!("unable to queue submits: {err}");
515
516                match err {
517                    vk::Result::ERROR_DEVICE_LOST => DriverError::InvalidData,
518                    vk::Result::ERROR_OUT_OF_DEVICE_MEMORY
519                    | vk::Result::ERROR_OUT_OF_HOST_MEMORY => DriverError::OutOfMemory,
520                    _ => DriverError::Unsupported,
521                }
522            })
523        }
524    }
525
526    /// Submits command buffers to a queue using the `VK_KHR_synchronization2` extension or the
527    /// Vulkan 1.3 core `vkQueueSubmit2` path.
528    ///
529    /// See [`vkQueueSubmit2`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkQueueSubmit2.html)
530    /// and [`VK_KHR_synchronization2`](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_synchronization2.html).
531    pub fn queue_submit2(
532        this: &Self,
533        queue: vk::Queue,
534        submits: &[vk::SubmitInfo2],
535        fence: vk::Fence,
536    ) -> Result<(), DriverError> {
537        #[cfg(feature = "checked")]
538        assert!(this.physical.vk_khr_synchronization2);
539
540        unsafe {
541            if this.physical.instance.info.api_version >= ApiVersion::Vulkan13 {
542                // Support derived from Vulkan v1.3 implementation
543                this.queue_submit2(queue, submits, fence)
544            } else {
545                let khr_synchronization2 = Device::expect_vk_khr_synchronization2(this);
546
547                // Support derived from Vulkan v1.2 implementation + extension
548                khr_synchronization2.queue_submit2(queue, submits, fence)
549            }
550            .map_err(|err| {
551                warn!("unable to queue submit2 submissions: {err}");
552
553                match err {
554                    vk::Result::ERROR_DEVICE_LOST => DriverError::InvalidData,
555                    vk::Result::ERROR_OUT_OF_DEVICE_MEMORY
556                    | vk::Result::ERROR_OUT_OF_HOST_MEMORY => DriverError::OutOfMemory,
557                    _ => DriverError::Unsupported,
558                }
559            })
560        }
561    }
562
563    /// Waits for queue idle.
564    pub fn queue_wait_idle(this: &Self, queue: vk::Queue) -> Result<(), DriverError> {
565        unsafe {
566            this.queue_wait_idle(queue).map_err(|err| {
567                warn!("unable to wait for queue idle: {err}");
568
569                match err {
570                    vk::Result::ERROR_OUT_OF_DEVICE_MEMORY
571                    | vk::Result::ERROR_OUT_OF_HOST_MEMORY => DriverError::OutOfMemory,
572                    vk::Result::ERROR_DEVICE_LOST | vk::Result::ERROR_VALIDATION_FAILED_EXT => {
573                        DriverError::InvalidData
574                    }
575                    _ => DriverError::Unsupported,
576                }
577            })
578        }
579    }
580
581    /// Resets one or more fences to the unsignaled state.
582    ///
583    /// See [`vkResetFences`](https://registry.khronos.org/vulkan/specs/latest/man/html/vkResetFences.html).
584    pub fn reset_fences(this: &Self, fences: &[vk::Fence]) -> Result<(), DriverError> {
585        unsafe {
586            this.reset_fences(fences).map_err(|err| {
587                warn!("unable to reset fences: {err}");
588
589                match err {
590                    vk::Result::ERROR_OUT_OF_DEVICE_MEMORY => DriverError::OutOfMemory,
591                    _ => DriverError::Unsupported,
592                }
593            })
594        }
595    }
596
597    /// Assigns a Vulkan debug-utils name to `handle` when debug labeling is enabled.
598    ///
599    /// Returns without doing any work if debug mode is `false`.
600    pub fn set_debug_utils_object_name<T>(
601        this: &Self,
602        object_handle: T,
603        object_name: impl AsRef<str>,
604    ) -> Result<(), DriverError>
605    where
606        T: vk::Handle + Copy,
607    {
608        if !this.physical.instance.info.debug {
609            return Ok(());
610        }
611
612        if object_handle.is_null() {
613            warn!("invalid object handle");
614
615            return Err(DriverError::InvalidData);
616        }
617
618        let Ok(object_name) = CString::new(object_name.as_ref()) else {
619            warn!("invalid object name");
620
621            return Err(DriverError::InvalidData);
622        };
623
624        let ext = Self::try_vk_ext_debug_utils(this)?;
625
626        unsafe {
627            match ext.set_debug_utils_object_name(
628                &vk::DebugUtilsObjectNameInfoEXT::default()
629                    .object_handle(object_handle)
630                    .object_name(object_name.as_c_str()),
631            ) {
632                Err(
633                    vk::Result::ERROR_OUT_OF_DEVICE_MEMORY | vk::Result::ERROR_OUT_OF_HOST_MEMORY,
634                ) => Err(DriverError::OutOfMemory),
635                Err(vk::Result::ERROR_VALIDATION_FAILED_EXT) => Err(DriverError::InvalidData),
636                Err(err) => {
637                    warn!("unable to set debug utils object name: {err}");
638
639                    Err(DriverError::Unsupported)
640                }
641                Ok(_) => Ok(()),
642            }
643        }
644    }
645
646    /// Stores a Vulkan name in private-data metadata.
647    ///
648    /// Returns without doing any work when `VK_EXT_private_data` is not available.
649    pub(crate) fn set_private_data_object_name<T>(
650        this: &Self,
651        object_type: vk::ObjectType,
652        object_handle: T,
653        object_name: impl AsRef<str>,
654    ) -> Result<(), DriverError>
655    where
656        T: vk::Handle + Copy,
657    {
658        if this.inner.private_data_slot.is_none() {
659            return Ok(());
660        }
661
662        if object_handle.is_null() {
663            warn!("invalid object handle");
664
665            return Err(DriverError::InvalidData);
666        }
667
668        let object_key = (object_type, object_handle.as_raw());
669        let metadata_id = this
670            .inner
671            .private_data_name_id
672            .fetch_add(1, Ordering::Relaxed)
673            + 1;
674
675        let (previous_metadata_id, previous_name) =
676            Self::with_private_data_metadata(this, |metadata| {
677                let previous_metadata_id =
678                    metadata.object_metadata_ids.insert(object_key, metadata_id);
679                let previous_name = previous_metadata_id.and_then(|id| metadata.names.remove(&id));
680
681                metadata
682                    .names
683                    .insert(metadata_id, object_name.as_ref().to_owned());
684
685                (previous_metadata_id, previous_name)
686            });
687
688        let ext = Self::try_vk_ext_private_data(this)?;
689        let private_data_slot = this
690            .inner
691            .private_data_slot
692            .expect("missing private data slot");
693
694        if let Err(err) =
695            unsafe { ext.set_private_data(object_handle, private_data_slot, metadata_id) }
696        {
697            Self::with_private_data_metadata(this, |metadata| {
698                let _ = metadata.names.remove(&metadata_id);
699                match previous_metadata_id {
700                    Some(id) => {
701                        metadata.object_metadata_ids.insert(object_key, id);
702                        if let Some(name) = previous_name {
703                            metadata.names.insert(id, name);
704                        }
705                    }
706                    None => {
707                        metadata.object_metadata_ids.remove(&object_key);
708                    }
709                }
710            });
711
712            warn!("unable to set private data object name: {err}");
713
714            return Err(match err {
715                vk::Result::ERROR_OUT_OF_DEVICE_MEMORY | vk::Result::ERROR_OUT_OF_HOST_MEMORY => {
716                    DriverError::OutOfMemory
717                }
718                _ => DriverError::Unsupported,
719            });
720        }
721
722        Ok(())
723    }
724
725    /// Loads an existing `ash` Vulkan device that may have been created by other means.
726    ///
727    /// # Safety
728    ///
729    /// `device` must have been created from `physical_device` and must have all queues,
730    /// extensions, and features expected by `vk-graph`. The device must not be destroyed outside
731    /// this wrapper while any cloned [`Device`] or resources created from it remain alive.
732    #[profiling::function]
733    pub unsafe fn try_from_ash(
734        device: ash::Device,
735        physical_device: PhysicalDevice,
736    ) -> Result<Self, DriverError> {
737        let debug = physical_device.instance.info.debug;
738
739        if debug && !Instance::supports_debug_utils(&physical_device.instance) {
740            error!("unsupported VK_EXT_debug_utils");
741
742            return Err(DriverError::Unsupported);
743        }
744
745        if debug && !physical_device.vk_ext_private_data {
746            error!("unsupported VK_EXT_private_data");
747
748            return Err(DriverError::Unsupported);
749        }
750
751        let mut debug_settings = AllocatorDebugSettings::default();
752        debug_settings.log_leaks_on_shutdown = debug;
753        debug_settings.log_memory_information = debug;
754        debug_settings.log_allocations = debug;
755
756        let allocator = Allocator::new(&AllocatorCreateDesc {
757            instance: (*physical_device.instance).clone(),
758            device: device.clone(),
759            physical_device: physical_device.handle,
760            debug_settings,
761            buffer_device_address: true,
762            allocation_sizes: Default::default(),
763        })
764        .map_err(|err| {
765            warn!("unable to create allocator: {err}");
766
767            DriverError::Unsupported
768        })?;
769
770        let mut queues = Vec::with_capacity(physical_device.queue_families.len());
771
772        for (queue_family_index, properties) in physical_device.queue_families.iter().enumerate() {
773            let mut queue_family = Vec::with_capacity(properties.queue_count as _);
774
775            for queue_index in 0..properties.queue_count {
776                queue_family.push(Mutex::new(unsafe {
777                    device.get_device_queue(queue_family_index as _, queue_index)
778                }));
779            }
780
781            queues.push(queue_family.into_boxed_slice());
782        }
783
784        let vk_ext_debug_utils = Some(ext::debug_utils::Device::new(
785            &physical_device.instance,
786            &device,
787        ));
788        let vk_ext_private_data = physical_device
789            .vk_ext_private_data
790            .then(|| ext::private_data::Device::new(&physical_device.instance, &device));
791        let vk_ext_private_data_slot = vk_ext_private_data
792            .as_ref()
793            .map(|vk_ext_private_data| unsafe {
794                vk_ext_private_data
795                    .create_private_data_slot(
796                        &vk::PrivateDataSlotCreateInfoEXT::default()
797                            .flags(vk::PrivateDataSlotCreateFlagsEXT::empty()),
798                        None,
799                    )
800                    .map_err(|err| {
801                        warn!("unable to create private data slot: {err}");
802
803                        DriverError::Unsupported
804                    })
805            })
806            .transpose()?;
807        let vk_khr_present_wait = physical_device
808            .vk_khr_present_wait
809            .is_some()
810            .then(|| khr::present_wait::Device::new(&physical_device.instance, &device));
811        let vk_khr_surface = physical_device.vk_khr_swapchain.then(|| {
812            let entry = Instance::entry(&physical_device.instance);
813            khr::surface::Instance::new(entry, &physical_device.instance)
814        });
815        let vk_khr_swapchain = physical_device
816            .vk_khr_swapchain
817            .then(|| khr::swapchain::Device::new(&physical_device.instance, &device));
818        let vk_khr_acceleration_structure = physical_device
819            .vk_khr_acceleration_structure
820            .is_some()
821            .then(|| khr::acceleration_structure::Device::new(&physical_device.instance, &device));
822        let vk_khr_ray_tracing_pipeline = physical_device
823            .vk_khr_ray_tracing_pipeline
824            .as_ref()
825            .is_some_and(|ext| ext.features.ray_tracing_pipeline)
826            .then(|| khr::ray_tracing_pipeline::Device::new(&physical_device.instance, &device));
827        let vk_khr_synchronization2 = physical_device
828            .vk_khr_synchronization2
829            .then(|| khr::synchronization2::Device::new(&physical_device.instance, &device));
830
831        let pipeline_cache =
832            unsafe { device.create_pipeline_cache(&vk::PipelineCacheCreateInfo::default(), None) }
833                .map_err(|err| {
834                    warn!("unable to create pipeline cache: {err}");
835
836                    DriverError::Unsupported
837                })?;
838
839        Ok(Self {
840            read_only: ReadOnlyDevice {
841                inner: Arc::new(DeviceInner {
842                    allocator: ManuallyDrop::new(Mutex::new(allocator)),
843                    device,
844                    pipeline_cache,
845                    queues: queues.into_boxed_slice(),
846                    vk_ext_debug_utils,
847                    vk_ext_private_data,
848                    vk_khr_acceleration_structure,
849                    vk_khr_present_wait,
850                    vk_khr_ray_tracing_pipeline,
851                    vk_khr_surface,
852                    vk_khr_swapchain,
853                    vk_khr_synchronization2,
854                    private_data_slot: vk_ext_private_data_slot,
855                    private_data_name_id: AtomicU64::new(0),
856                    private_data_metadata: Mutex::new(Default::default()),
857                }),
858                physical: Box::new(physical_device),
859            },
860        })
861    }
862
863    /// Constructs a new device using the given configuration.
864    #[profiling::function]
865    pub fn try_from_display(
866        display: impl HasDisplayHandle,
867        info: impl Into<DeviceInfo>,
868    ) -> Result<Self, DriverError> {
869        let DeviceInfo {
870            debug,
871            physical_device_index,
872        } = info.into();
873        let instance_info = InstanceInfoBuilder::default().debug(debug);
874        let instance = Instance::try_from_display(display, instance_info)?;
875        let physical_device = select_physical_device(&instance, physical_device_index)?;
876
877        Self::try_from_physical_device(physical_device)
878    }
879
880    /// Constructs a new device using the given physical device.
881    #[profiling::function]
882    pub fn try_from_physical_device(physical_device: PhysicalDevice) -> Result<Self, DriverError> {
883        let device = unsafe {
884            physical_device.create_ash_device(|device_create_info| {
885                physical_device.instance.create_device(
886                    physical_device.handle,
887                    &device_create_info,
888                    None,
889                )
890            })
891        }
892        .map_err(|err| {
893            error!("unable to create device: {err}");
894
895            DriverError::Unsupported
896        })?;
897
898        info!("created {}", physical_device.properties_v1_0.device_name);
899
900        unsafe { Self::try_from_ash(device, physical_device) }
901    }
902
903    pub(crate) fn try_clear_private_data_object_name<T>(
904        this: &Self,
905        object_type: vk::ObjectType,
906        object_handle: T,
907    ) where
908        T: vk::Handle + Copy,
909    {
910        let _ = Self::clear_private_data_object_name(this, object_type, object_handle);
911    }
912
913    /// Assigns a Vulkan debug-utils name to `handle` when debug labeling is enabled.
914    ///
915    /// Returns without doing any work if debug mode is `false`.
916    pub fn try_set_debug_utils_object_name<T>(
917        this: &Self,
918        object_handle: T,
919        object_name: impl AsRef<str>,
920    ) where
921        T: vk::Handle + Copy,
922    {
923        let _ = Self::set_debug_utils_object_name(this, object_handle, object_name);
924    }
925
926    /// Stores a Vulkan name in private-data metadata.
927    ///
928    /// Returns without doing any work when `VK_EXT_private_data` is not available.
929    pub(crate) fn try_set_private_data_object_name<T>(
930        this: &Self,
931        object_type: vk::ObjectType,
932        object_handle: T,
933        object_name: impl AsRef<str>,
934    ) where
935        T: vk::Handle + Copy,
936    {
937        let _ = Self::set_private_data_object_name(this, object_type, object_handle, object_name);
938    }
939
940    fn try_vk_ext_debug_utils(this: &Self) -> Result<&ext::debug_utils::Device, DriverError> {
941        this.inner
942            .vk_ext_debug_utils
943            .as_ref()
944            .ok_or(DriverError::Unsupported)
945    }
946
947    fn try_vk_ext_private_data(this: &Self) -> Result<&ext::private_data::Device, DriverError> {
948        this.inner
949            .vk_ext_private_data
950            .as_ref()
951            .ok_or(DriverError::Unsupported)
952    }
953
954    /// Waits for a single fence to signal.
955    #[profiling::function]
956    pub(crate) fn wait_for_fence(this: &Self, fence: &vk::Fence) -> Result<(), DriverError> {
957        Device::wait_for_fences(this, slice::from_ref(fence))
958    }
959
960    /// Waits for all fences in `fences` to signal.
961    ///
962    /// This first performs a short poll so uncontended waits return quickly, then falls back to an
963    /// indefinite wait while logging unusually slow completions.
964    #[profiling::function]
965    pub(crate) fn wait_for_fences(this: &Self, fences: &[vk::Fence]) -> Result<(), DriverError> {
966        unsafe {
967            match this.wait_for_fences(fences, true, 100) {
968                Ok(_) => return Ok(()),
969                Err(err) if err == vk::Result::ERROR_DEVICE_LOST => {
970                    error!("invalid device state: lost");
971
972                    return Err(DriverError::InvalidData);
973                }
974                Err(err) if err == vk::Result::TIMEOUT => {
975                    trace!("waiting...");
976                }
977                Err(err) => {
978                    warn!("unable to wait for fences during polling phase: {err}");
979
980                    return Err(DriverError::OutOfMemory);
981                }
982            }
983
984            let started = cfg!(debug_assertions).then(Instant::now);
985
986            match this.wait_for_fences(fences, true, u64::MAX) {
987                Ok(_) => (),
988                Err(err) if err == vk::Result::ERROR_DEVICE_LOST => {
989                    error!("invalid device state: lost");
990
991                    return Err(DriverError::InvalidData);
992                }
993                Err(err) => {
994                    warn!("unable to wait for fences to completion: {err}");
995
996                    return Err(DriverError::OutOfMemory);
997                }
998            }
999
1000            if let Some(started) = started {
1001                let elapsed = Instant::now() - started;
1002                let elapsed_millis = elapsed.as_millis();
1003
1004                if elapsed_millis > 0 {
1005                    warn!("slow fence wait: {} ms", elapsed_millis);
1006                }
1007            }
1008        }
1009
1010        Ok(())
1011    }
1012
1013    /// Waits for device idle.
1014    pub fn wait_idle(this: &Self) -> Result<(), DriverError> {
1015        unsafe {
1016            this.device_wait_idle().map_err(|err| {
1017                warn!("unable to wait for device idle: {err}");
1018
1019                match err {
1020                    vk::Result::ERROR_OUT_OF_DEVICE_MEMORY
1021                    | vk::Result::ERROR_OUT_OF_HOST_MEMORY => DriverError::OutOfMemory,
1022                    vk::Result::ERROR_DEVICE_LOST | vk::Result::ERROR_VALIDATION_FAILED_EXT => {
1023                        DriverError::InvalidData
1024                    }
1025                    _ => DriverError::Unsupported,
1026                }
1027            })
1028        }
1029    }
1030
1031    /// Provides mutable access to the device allocator under its internal lock.
1032    pub(crate) fn with_allocator<R>(this: &Self, f: impl FnOnce(&mut Allocator) -> R) -> R {
1033        let allocator = this.inner.allocator.lock();
1034
1035        #[cfg(not(feature = "parking_lot"))]
1036        let allocator = allocator.expect("poisoned allocator lock");
1037
1038        let mut allocator = allocator;
1039
1040        f(&mut allocator)
1041    }
1042
1043    fn with_object_metadata_ids<R>(
1044        this: &Self,
1045        f: impl FnOnce(&mut HashMap<(vk::ObjectType, u64), u64>) -> R,
1046    ) -> R {
1047        Self::with_private_data_metadata(this, |metadata| f(&mut metadata.object_metadata_ids))
1048    }
1049
1050    fn with_private_data_metadata<R>(
1051        this: &Self,
1052        f: impl FnOnce(&mut PrivateDataMetadata) -> R,
1053    ) -> R {
1054        let mut metadata = this.inner.private_data_metadata.lock();
1055
1056        #[cfg(not(feature = "parking_lot"))]
1057        let mut metadata = metadata.expect("poisoned private data metadata");
1058
1059        f(&mut metadata)
1060    }
1061
1062    /// Provides locked access to a device queue.
1063    ///
1064    /// Acquires the mutex for the queue at the given family and index, calls `f` with the
1065    /// [`vk::Queue`], and releases the mutex after `f` returns.
1066    ///
1067    /// # Panics
1068    ///
1069    /// Panics if `queue_family_index` or `queue_index` is out of range for this device.
1070    pub fn with_queue<R>(
1071        this: &Self,
1072        queue_family_index: u32,
1073        queue_index: u32,
1074        f: impl FnOnce(vk::Queue) -> R,
1075    ) -> R {
1076        let queue_family = this
1077            .inner
1078            .queues
1079            .get(queue_family_index as usize)
1080            .expect("invalid queue family index");
1081        let queue = queue_family
1082            .get(queue_index as usize)
1083            .expect("invalid queue index");
1084        #[cfg(not(feature = "parking_lot"))]
1085        let guard = queue.lock().expect("poisoned queue lock");
1086
1087        #[cfg(feature = "parking_lot")]
1088        let guard = queue.lock();
1089
1090        f(*guard)
1091    }
1092}
1093
1094impl Debug for Device {
1095    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1096        f.debug_struct(stringify!(Device))
1097            .field("handle", &self.inner.device.handle())
1098            .field("physical", &self.physical)
1099            .finish_non_exhaustive()
1100    }
1101}
1102
1103#[cfg(doc)]
1104impl Deref for Device {
1105    type Target = ash::Device;
1106
1107    fn deref(&self) -> &Self::Target {
1108        unreachable!()
1109    }
1110}
1111
1112impl Eq for Device {}
1113
1114impl PartialEq for Device {
1115    fn eq(&self, other: &Self) -> bool {
1116        Arc::ptr_eq(&self.inner, &other.inner)
1117    }
1118}
1119
1120/// Information used to create a [`Device`] instance.
1121#[derive(Builder, Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
1122#[builder(
1123    build_fn(private, name = "fallible_build"),
1124    derive(Clone, Copy, Debug),
1125    pattern = "owned"
1126)]
1127pub struct DeviceInfo {
1128    /// Enables the Vulkan validation layers.
1129    ///
1130    /// This requires a Vulkan SDK installation and will panic when validation errors happen. See
1131    /// the LunarG [Vulkan Validation Layers] documentation for setup and behavior details.
1132    ///
1133    /// When `stderr` is attached to an interactive terminal, validation errors will park the
1134    /// callback thread for debugger attach.
1135    ///
1136    /// _NOTE:_ Consider turning OFF debug if you discover an unknown issue. Often the validation
1137    /// layers will report an error before other layers can provide additional context such as the
1138    /// API dump info or other messages. You might find the "actual" issue is detailed in those
1139    /// subsequent details.
1140    ///
1141    /// ## Platform-specific
1142    ///
1143    /// **macOS:** Has no effect unless the `loaded` feature is enabled.
1144    ///
1145    /// [Vulkan Validation Layers]: https://vulkan.lunarg.com/doc/sdk/latest/windows/validation_layers.html
1146    #[builder(default)]
1147    pub debug: bool,
1148
1149    /// Index of the [`PhysicalDevice`] from the available devices. See
1150    /// [`Instance::physical_devices`].
1151    #[builder(default)]
1152    pub physical_device_index: usize,
1153}
1154
1155impl DeviceInfo {
1156    /// Creates a default `DeviceInfoBuilder`.
1157    pub fn builder() -> DeviceInfoBuilder {
1158        Default::default()
1159    }
1160
1161    /// Converts a `DeviceInfo` into a `DeviceInfoBuilder`.
1162    pub fn into_builder(self) -> DeviceInfoBuilder {
1163        DeviceInfoBuilder {
1164            debug: Some(self.debug),
1165            physical_device_index: Some(self.physical_device_index),
1166        }
1167    }
1168}
1169
1170impl From<DeviceInfoBuilder> for DeviceInfo {
1171    fn from(info: DeviceInfoBuilder) -> Self {
1172        info.build()
1173    }
1174}
1175
1176impl DeviceInfoBuilder {
1177    /// Builds a new `DeviceInfo`.
1178    #[inline(always)]
1179    pub fn build(self) -> DeviceInfo {
1180        self.fallible_build().expect("invalid device info")
1181    }
1182}
1183
1184struct DeviceInner {
1185    allocator: ManuallyDrop<Mutex<Allocator>>,
1186    device: ash::Device,
1187    pipeline_cache: vk::PipelineCache,
1188    queues: Box<[Box<[Mutex<vk::Queue>]>]>,
1189    vk_ext_debug_utils: Option<ext::debug_utils::Device>,
1190    vk_ext_private_data: Option<ext::private_data::Device>,
1191    vk_khr_acceleration_structure: Option<khr::acceleration_structure::Device>,
1192    vk_khr_present_wait: Option<khr::present_wait::Device>,
1193    vk_khr_ray_tracing_pipeline: Option<khr::ray_tracing_pipeline::Device>,
1194    vk_khr_surface: Option<khr::surface::Instance>,
1195    vk_khr_swapchain: Option<khr::swapchain::Device>,
1196    vk_khr_synchronization2: Option<khr::synchronization2::Device>,
1197    private_data_slot: Option<vk::PrivateDataSlot>,
1198    private_data_name_id: AtomicU64,
1199    private_data_metadata: Mutex<PrivateDataMetadata>,
1200}
1201
1202#[derive(Default)]
1203struct PrivateDataMetadata {
1204    object_metadata_ids: HashMap<(vk::ObjectType, u64), u64>,
1205    names: HashMap<u64, String>,
1206}
1207
1208impl Drop for DeviceInner {
1209    #[profiling::function]
1210    fn drop(&mut self) {
1211        if panicking() {
1212            // When panicking we don't want the GPU allocator to complain about leaks
1213            unsafe {
1214                forget(ManuallyDrop::take(&mut self.allocator));
1215            }
1216
1217            return;
1218        }
1219
1220        // trace!("drop");
1221
1222        if let Err(err) = unsafe { self.device.device_wait_idle() } {
1223            warn!("device_wait_idle() failed: {err}");
1224        }
1225
1226        unsafe {
1227            self.device
1228                .destroy_pipeline_cache(self.pipeline_cache, None);
1229
1230            if let (Some(vk_ext_private_data), Some(private_data_slot)) = (
1231                self.vk_ext_private_data.as_ref(),
1232                self.private_data_slot.take(),
1233            ) {
1234                vk_ext_private_data.destroy_private_data_slot(private_data_slot, None);
1235            }
1236
1237            ManuallyDrop::drop(&mut self.allocator);
1238        }
1239
1240        unsafe {
1241            self.device.destroy_device(None);
1242        }
1243    }
1244}
1245
1246#[doc(hidden)]
1247impl Clone for ReadOnlyDevice {
1248    fn clone(&self) -> Self {
1249        Self {
1250            inner: self.inner.clone(),
1251            physical: self.physical.clone(),
1252        }
1253    }
1254}
1255
1256#[doc(hidden)]
1257impl Deref for ReadOnlyDevice {
1258    type Target = ash::Device;
1259
1260    fn deref(&self) -> &Self::Target {
1261        &self.inner.device
1262    }
1263}
1264
1265#[cfg(test)]
1266mod test {
1267    use super::*;
1268
1269    type Info = DeviceInfo;
1270    type Builder = DeviceInfoBuilder;
1271
1272    #[test]
1273    pub fn device_info() {
1274        Info::default().into_builder().build();
1275    }
1276
1277    #[test]
1278    pub fn device_info_builder() {
1279        Builder::default().build();
1280    }
1281}