Skip to main content

vk_graph/driver/
device.rs

1//! Logical device types
2
3use {
4    super::{
5        DriverError,
6        instance::{Instance, InstanceInfoBuilder},
7        physical_device::{PhysicalDevice, RayTraceProperties},
8    },
9    ash::{khr, vk},
10    derive_builder::{Builder, UninitializedFieldError},
11    gpu_allocator::{
12        AllocatorDebugSettings,
13        vulkan::{Allocator, AllocatorCreateDesc},
14    },
15    log::{error, info, trace, warn},
16    raw_window_handle::HasDisplayHandle,
17    std::{
18        fmt::{Debug, Formatter},
19        mem::{ManuallyDrop, forget},
20        ops::Deref,
21        slice,
22        sync::Arc,
23        thread::panicking,
24        time::Instant,
25    },
26};
27
28#[cfg(feature = "parking_lot")]
29use parking_lot::Mutex;
30
31#[cfg(not(feature = "parking_lot"))]
32use std::sync::Mutex;
33
34fn select_physical_device(
35    instance: &Instance,
36    mut index: usize,
37) -> Result<PhysicalDevice, DriverError> {
38    let mut physical_devices = Instance::physical_devices(instance)?
39        .into_iter()
40        .collect::<Vec<_>>();
41    if physical_devices.is_empty() {
42        warn!("unable to find physical devices");
43
44        return Err(DriverError::Unsupported);
45    }
46
47    if index >= physical_devices.len() {
48        index = 0;
49    }
50
51    let physical_device = physical_devices.remove(index);
52
53    Ok(physical_device)
54}
55
56/// Opaque handle to a device object.
57#[read_only::embed]
58#[derive(Clone)]
59pub struct Device {
60    #[readonly]
61    pub(self) inner: Arc<DeviceInner>,
62
63    /// The physical device, which contains useful data about features, properties, and limits.
64    ///
65    /// _Note:_ This field is read-only.
66    #[readonly]
67    pub physical_device: Box<PhysicalDevice>,
68}
69
70impl Device {
71    #[deprecated = "use create"]
72    #[doc(hidden)]
73    pub fn new(info: impl Into<DeviceInfo>) -> Result<Self, DriverError> {
74        Self::create(info)
75    }
76
77    /// Begins recording a command buffer on this device.
78    ///
79    /// This is a thin wrapper around [`ash::Device::begin_command_buffer`] that maps Vulkan errors
80    /// to [`DriverError`] variants.
81    pub fn begin_command_buffer(
82        this: &Self,
83        cmd: vk::CommandBuffer,
84        begin_info: &vk::CommandBufferBeginInfo,
85    ) -> Result<(), DriverError> {
86        unsafe {
87            this.begin_command_buffer(cmd, begin_info).map_err(|err| {
88                warn!("unable to begin command buffer: {err}");
89
90                match err {
91                    vk::Result::ERROR_OUT_OF_DEVICE_MEMORY
92                    | vk::Result::ERROR_OUT_OF_HOST_MEMORY => DriverError::OutOfMemory,
93                    _ => DriverError::Unsupported,
94                }
95            })
96        }
97    }
98
99    /// Constructs a new device using the given configuration.
100    ///
101    /// This constructor is intended for headless or manually managed setups. It does not infer or
102    /// enable display platform surface extensions. Use [`Self::try_from_display`] when the
103    /// resulting device must be capable of later surface creation.
104    #[profiling::function]
105    pub fn create(info: impl Into<DeviceInfo>) -> Result<Self, DriverError> {
106        let DeviceInfo {
107            debug,
108            physical_device_index,
109        } = info.into();
110        let instance_info = InstanceInfoBuilder::default().debug(debug);
111        let instance = Instance::create(instance_info)?;
112        let physical_device = select_physical_device(&instance, physical_device_index)?;
113
114        Self::try_from_physical_device(physical_device)
115    }
116
117    pub(crate) fn create_fence(this: &Self, signaled: bool) -> Result<vk::Fence, DriverError> {
118        let mut flags = vk::FenceCreateFlags::empty();
119
120        if signaled {
121            flags |= vk::FenceCreateFlags::SIGNALED;
122        }
123
124        let create_info = vk::FenceCreateInfo::default().flags(flags);
125        let allocation_callbacks = None;
126
127        unsafe { this.create_fence(&create_info, allocation_callbacks) }.map_err(|err| {
128            warn!("unable to create fence: {err}");
129
130            DriverError::OutOfMemory
131        })
132    }
133
134    /// Ends recording a command buffer on this device.
135    ///
136    /// This is a thin wrapper around [`ash::Device::end_command_buffer`] that maps Vulkan errors
137    /// to [`DriverError`] variants.
138    pub fn end_command_buffer(this: &Self, cmd: vk::CommandBuffer) -> Result<(), DriverError> {
139        unsafe {
140            this.end_command_buffer(cmd).map_err(|err| {
141                warn!("unable to end command buffer: {err}");
142
143                match err {
144                    vk::Result::ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR => DriverError::InvalidData,
145                    vk::Result::ERROR_OUT_OF_DEVICE_MEMORY
146                    | vk::Result::ERROR_OUT_OF_HOST_MEMORY => DriverError::OutOfMemory,
147                    _ => DriverError::Unsupported,
148                }
149            })
150        }
151    }
152
153    /// Helper for times when you already know that the device supports the acceleration
154    /// structure extension.
155    ///
156    /// # Panics
157    ///
158    /// Panics if [Self.physical_device.accel_struct_properties] is `None`.
159    pub(crate) fn expect_accel_struct_ext(this: &Self) -> &khr::acceleration_structure::Device {
160        this.inner
161            .accel_struct_ext
162            .as_ref()
163            .expect("missing VK_KHR_acceleration_structure")
164    }
165
166    /// Helper for times when you already know that the device supports the ray tracing pipeline
167    /// extension.
168    ///
169    /// # Panics
170    ///
171    /// Panics if [Self.physical_device.ray_trace_properties] is `None`.
172    pub(crate) fn expect_ray_trace_ext(this: &Self) -> &khr::ray_tracing_pipeline::Device {
173        this.inner
174            .ray_trace_ext
175            .as_ref()
176            .expect("missing VK_KHR_ray_tracing_pipeline")
177    }
178
179    /// Helper for times when you already know that the device supports the ray tracing pipeline
180    /// extension.
181    ///
182    /// # Panics
183    ///
184    /// Panics if [Self.physical_device.ray_trace_properties] is `None`.
185    pub(crate) fn expect_ray_trace_properties(this: &Self) -> &RayTraceProperties {
186        this.physical_device
187            .ray_trace_properties
188            .as_ref()
189            .expect("missing VK_KHR_ray_tracing_pipeline")
190    }
191
192    /// Helper for times when you already know that the instance supports the surface extension.
193    ///
194    /// # Panics
195    ///
196    /// Panics if the device was not created for display window access.
197    pub(crate) fn expect_surface_ext(this: &Self) -> &khr::surface::Instance {
198        this.inner
199            .surface_ext
200            .as_ref()
201            .expect("missing VK_KHR_surface")
202    }
203
204    /// Helper for times when you already know that the device supports the swapchain extension.
205    ///
206    /// # Panics
207    ///
208    /// Panics if the device was not created for display window access.
209    pub(crate) fn expect_swapchain_ext(this: &Self) -> &khr::swapchain::Device {
210        this.inner
211            .swapchain_ext
212            .as_ref()
213            .expect("missing VK_KHR_swapchain")
214    }
215
216    pub(crate) fn pipeline_cache(this: &Self) -> vk::PipelineCache {
217        this.inner.pipeline_cache
218    }
219
220    /// Submits command buffers to a queue, optionally signaling a fence.
221    pub fn queue_submit(
222        this: &Self,
223        queue: vk::Queue,
224        submits: &[vk::SubmitInfo],
225        fence: vk::Fence,
226    ) -> Result<(), DriverError> {
227        unsafe {
228            this.queue_submit(queue, submits, fence).map_err(|err| {
229                warn!("unable to queue submits: {err}");
230
231                match err {
232                    vk::Result::ERROR_DEVICE_LOST => DriverError::InvalidData,
233                    vk::Result::ERROR_OUT_OF_DEVICE_MEMORY
234                    | vk::Result::ERROR_OUT_OF_HOST_MEMORY => DriverError::OutOfMemory,
235                    _ => DriverError::Unsupported,
236                }
237            })
238        }
239    }
240
241    /// Resets one or more fences to the unsignaled state.
242    pub fn reset_fences(this: &Self, fences: &[vk::Fence]) -> Result<(), DriverError> {
243        unsafe {
244            this.reset_fences(fences).map_err(|err| {
245                warn!("unable to reset fences: {err}");
246
247                match err {
248                    vk::Result::ERROR_OUT_OF_DEVICE_MEMORY => DriverError::OutOfMemory,
249                    _ => DriverError::Unsupported,
250                }
251            })
252        }
253    }
254
255    /// Loads and existing `ash` Vulkan device that may have been created by other means.
256    #[profiling::function]
257    pub fn try_from_ash_device(
258        device: ash::Device,
259        physical_device: PhysicalDevice,
260    ) -> Result<Self, DriverError> {
261        let debug = physical_device.instance.info.debug;
262        let mut debug_settings = AllocatorDebugSettings::default();
263        debug_settings.log_leaks_on_shutdown = debug;
264        debug_settings.log_memory_information = debug;
265        debug_settings.log_allocations = debug;
266
267        let allocator = Allocator::new(&AllocatorCreateDesc {
268            instance: (*physical_device.instance).clone(),
269            device: device.clone(),
270            physical_device: physical_device.handle,
271            debug_settings,
272            buffer_device_address: true,
273            allocation_sizes: Default::default(),
274        })
275        .map_err(|err| {
276            warn!("unable to create allocator: {err}");
277
278            DriverError::Unsupported
279        })?;
280
281        let mut queues = Vec::with_capacity(physical_device.queue_families.len());
282
283        for (queue_family_index, properties) in physical_device.queue_families.iter().enumerate() {
284            let mut queue_family = Vec::with_capacity(properties.queue_count as _);
285
286            for queue_index in 0..properties.queue_count {
287                queue_family.push(Mutex::new(unsafe {
288                    device.get_device_queue(queue_family_index as _, queue_index)
289                }));
290            }
291
292            queues.push(queue_family.into_boxed_slice());
293        }
294
295        let surface_ext = physical_device.swapchain_ext.then(|| {
296            let entry = Instance::entry(&physical_device.instance);
297            khr::surface::Instance::new(entry, &physical_device.instance)
298        });
299        let swapchain_ext = physical_device
300            .swapchain_ext
301            .then(|| khr::swapchain::Device::new(&physical_device.instance, &device));
302        let accel_struct_ext = physical_device
303            .accel_struct_properties
304            .is_some()
305            .then(|| khr::acceleration_structure::Device::new(&physical_device.instance, &device));
306        let ray_trace_ext = physical_device
307            .ray_trace_features
308            .ray_tracing_pipeline
309            .then(|| khr::ray_tracing_pipeline::Device::new(&physical_device.instance, &device));
310
311        let pipeline_cache =
312            unsafe { device.create_pipeline_cache(&vk::PipelineCacheCreateInfo::default(), None) }
313                .map_err(|err| {
314                    warn!("unable to create pipeline cache: {err}");
315
316                    DriverError::Unsupported
317                })?;
318
319        Ok(Self {
320            read_only: ReadOnlyDevice {
321                inner: Arc::new(DeviceInner {
322                    accel_struct_ext,
323                    allocator: ManuallyDrop::new(Mutex::new(allocator)),
324                    device,
325                    pipeline_cache,
326                    queues: queues.into_boxed_slice(),
327                    ray_trace_ext,
328                    surface_ext,
329                    swapchain_ext,
330                }),
331                physical_device: Box::new(physical_device),
332            },
333        })
334    }
335
336    /// Constructs a new device using the given configuration.
337    #[profiling::function]
338    pub fn try_from_display(
339        display: impl HasDisplayHandle,
340        info: impl Into<DeviceInfo>,
341    ) -> Result<Self, DriverError> {
342        let DeviceInfo {
343            debug,
344            physical_device_index,
345        } = info.into();
346        let instance_info = InstanceInfoBuilder::default().debug(debug);
347        let instance = Instance::try_from_display(display, instance_info)?;
348        let physical_device = select_physical_device(&instance, physical_device_index)?;
349
350        Self::try_from_physical_device(physical_device)
351    }
352
353    /// Constructs a new device using the given physical device.
354    #[profiling::function]
355    pub fn try_from_physical_device(physical_device: PhysicalDevice) -> Result<Self, DriverError> {
356        let device = unsafe {
357            physical_device.create_ash_device(|device_create_info| {
358                physical_device.instance.create_device(
359                    physical_device.handle,
360                    &device_create_info,
361                    None,
362                )
363            })
364        }
365        .map_err(|err| {
366            error!("unable to create device: {err}");
367
368            DriverError::Unsupported
369        })?;
370
371        info!("created {}", physical_device.properties_v1_0.device_name);
372
373        Self::try_from_ash_device(device, physical_device)
374    }
375
376    #[profiling::function]
377    pub(crate) fn wait_for_fence(this: &Self, fence: &vk::Fence) -> Result<(), DriverError> {
378        Device::wait_for_fences(this, slice::from_ref(fence))
379    }
380
381    #[profiling::function]
382    pub(crate) fn wait_for_fences(this: &Self, fences: &[vk::Fence]) -> Result<(), DriverError> {
383        unsafe {
384            match this.wait_for_fences(fences, true, 100) {
385                Ok(_) => return Ok(()),
386                Err(err) if err == vk::Result::ERROR_DEVICE_LOST => {
387                    error!("invalid device state: lost");
388
389                    return Err(DriverError::InvalidData);
390                }
391                Err(err) if err == vk::Result::TIMEOUT => {
392                    trace!("waiting...");
393                }
394                Err(err) => {
395                    warn!("unable to wait for fences during polling phase: {err}");
396
397                    return Err(DriverError::OutOfMemory);
398                }
399            }
400
401            let started = Instant::now();
402
403            match this.wait_for_fences(fences, true, u64::MAX) {
404                Ok(_) => (),
405                Err(err) if err == vk::Result::ERROR_DEVICE_LOST => {
406                    error!("invalid device state: lost");
407
408                    return Err(DriverError::InvalidData);
409                }
410                Err(err) => {
411                    warn!("unable to wait for fences to completion: {err}");
412
413                    return Err(DriverError::OutOfMemory);
414                }
415            }
416
417            let elapsed = Instant::now() - started;
418            let elapsed_millis = elapsed.as_millis();
419
420            if elapsed_millis > 0 {
421                warn!("slow fence wait: {} ms", elapsed_millis);
422            }
423        }
424
425        Ok(())
426    }
427
428    pub(crate) fn with_allocator<R>(this: &Self, f: impl FnOnce(&mut Allocator) -> R) -> R {
429        let allocator = this.inner.allocator.lock();
430
431        #[cfg(not(feature = "parking_lot"))]
432        let allocator = allocator.expect("poisoned allocator lock");
433
434        let mut allocator = allocator;
435
436        f(&mut allocator)
437    }
438
439    /// Provides locked access to a device queue.
440    ///
441    /// Acquires the mutex for the queue at the given family and index, calls `f` with the
442    /// [`vk::Queue`], and releases the mutex after `f` returns.
443    ///
444    /// # Panics
445    ///
446    /// Panics if `queue_family_index` or `queue_index` is out of range for this device.
447    pub fn with_queue<R>(
448        this: &Self,
449        queue_family_index: u32,
450        queue_index: u32,
451        f: impl FnOnce(vk::Queue) -> R,
452    ) -> R {
453        let queue_family = this
454            .inner
455            .queues
456            .get(queue_family_index as usize)
457            .expect("invalid queue family index");
458        let queue = queue_family
459            .get(queue_index as usize)
460            .expect("invalid queue index");
461        #[cfg(not(feature = "parking_lot"))]
462        let guard = queue.lock().expect("poisoned queue lock");
463
464        #[cfg(feature = "parking_lot")]
465        let guard = queue.lock();
466
467        f(*guard)
468    }
469}
470
471impl Debug for Device {
472    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
473        f.write_str("Device")
474    }
475}
476
477#[cfg(doc)]
478impl Deref for Device {
479    type Target = ash::Device;
480
481    fn deref(&self) -> &Self::Target {
482        unreachable!()
483    }
484}
485
486impl Eq for Device {}
487
488impl PartialEq for Device {
489    fn eq(&self, other: &Self) -> bool {
490        Arc::ptr_eq(&self.inner, &other.inner)
491    }
492}
493
494/// Information used to create a [`Device`] instance.
495#[derive(Builder, Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
496#[builder(
497    build_fn(private, name = "fallible_build", error = "UninitializedFieldError"),
498    derive(Clone, Copy, Debug),
499    pattern = "owned"
500)]
501pub struct DeviceInfo {
502    /// Enables Vulkan validation layers.
503    ///
504    /// This requires a Vulkan SDK installation and will cause validation errors to introduce
505    /// panics as they happen.
506    ///
507    /// _NOTE:_ Consider turning OFF debug if you discover an unknown issue. Often the validation
508    /// layers will throw an error before other layers can provide additional context such as the
509    /// API dump info or other messages. You might find the "actual" issue is detailed in those
510    /// subsequent details.
511    ///
512    /// ## Platform-specific
513    ///
514    /// **macOS:** Has no effect unless the `loaded` feature is enabled.
515    #[builder(default)]
516    pub debug: bool,
517
518    /// Index of the [`PhysicalDevice`] from the available devices. See
519    /// [`Instance::physical_devices`].
520    #[builder(default)]
521    pub physical_device_index: usize,
522}
523
524impl DeviceInfo {
525    /// Creates a default `DeviceInfoBuilder`.
526    pub fn builder() -> DeviceInfoBuilder {
527        Default::default()
528    }
529
530    /// Converts a `DeviceInfo` into a `DeviceInfoBuilder`.
531    pub fn into_builder(self) -> DeviceInfoBuilder {
532        DeviceInfoBuilder {
533            debug: Some(self.debug),
534            physical_device_index: Some(self.physical_device_index),
535        }
536    }
537
538    #[deprecated = "use into_builder function"]
539    #[doc(hidden)]
540    pub fn to_builder(self) -> DeviceInfoBuilder {
541        self.into_builder()
542    }
543}
544
545impl From<DeviceInfoBuilder> for DeviceInfo {
546    fn from(info: DeviceInfoBuilder) -> Self {
547        info.build()
548    }
549}
550
551impl DeviceInfoBuilder {
552    /// Builds a new `DeviceInfo`.
553    #[inline(always)]
554    pub fn build(self) -> DeviceInfo {
555        self.fallible_build().expect("invalid device info")
556    }
557}
558
559struct DeviceInner {
560    accel_struct_ext: Option<khr::acceleration_structure::Device>,
561    allocator: ManuallyDrop<Mutex<Allocator>>,
562    device: ash::Device,
563    pipeline_cache: vk::PipelineCache,
564    queues: Box<[Box<[Mutex<vk::Queue>]>]>,
565    ray_trace_ext: Option<khr::ray_tracing_pipeline::Device>,
566    surface_ext: Option<khr::surface::Instance>,
567    swapchain_ext: Option<khr::swapchain::Device>,
568}
569
570impl Drop for DeviceInner {
571    #[profiling::function]
572    fn drop(&mut self) {
573        if panicking() {
574            // When panicking we don't want the GPU allocator to complain about leaks
575            unsafe {
576                forget(ManuallyDrop::take(&mut self.allocator));
577            }
578
579            return;
580        }
581
582        // trace!("drop");
583
584        if let Err(err) = unsafe { self.device.device_wait_idle() } {
585            warn!("device_wait_idle() failed: {err}");
586        }
587
588        unsafe {
589            self.device
590                .destroy_pipeline_cache(self.pipeline_cache, None);
591
592            ManuallyDrop::drop(&mut self.allocator);
593        }
594
595        unsafe {
596            self.device.destroy_device(None);
597        }
598    }
599}
600
601#[doc(hidden)]
602impl Clone for ReadOnlyDevice {
603    fn clone(&self) -> Self {
604        Self {
605            inner: self.inner.clone(),
606            physical_device: self.physical_device.clone(),
607        }
608    }
609}
610
611#[doc(hidden)]
612impl Deref for ReadOnlyDevice {
613    type Target = ash::Device;
614
615    fn deref(&self) -> &Self::Target {
616        &self.inner.device
617    }
618}
619
620#[allow(deprecated)]
621#[allow(unused)]
622pub(crate) mod deprecated {
623    use {
624        crate::driver::{
625            DriverError,
626            device::{Device, DeviceInfo, DeviceInfoBuilder},
627        },
628        ash::vk,
629        log::warn,
630        raw_window_handle::HasDisplayHandle,
631        std::any::Any,
632    };
633
634    impl Device {
635        #[deprecated = "use from_display function"]
636        #[doc(hidden)]
637        pub fn create_display(
638            info: impl Into<DeviceInfo>,
639            display_handle: &impl HasDisplayHandle,
640        ) -> Result<Self, DriverError> {
641            Self::try_from_display(display_handle, info)
642        }
643
644        #[deprecated = "use new function"]
645        #[doc(hidden)]
646        pub fn create_headless(info: impl Into<DeviceInfo>) -> Result<Self, DriverError> {
647            Self::new(info)
648        }
649        #[deprecated = "use format_properties function of physical_device field"]
650        #[doc(hidden)]
651        pub fn format_properties(this: &Self, format: vk::Format) -> vk::FormatProperties {
652            this.physical_device.format_properties(format)
653        }
654
655        #[deprecated = "use image_format_properties function of physical_device field"]
656        #[doc(hidden)]
657        pub fn image_format_properties(
658            this: &Self,
659            format: vk::Format,
660            ty: vk::ImageType,
661            tiling: vk::ImageTiling,
662            usage: vk::ImageUsageFlags,
663            flags: vk::ImageCreateFlags,
664        ) -> Result<Option<vk::ImageFormatProperties>, DriverError> {
665            this.physical_device
666                .image_format_properties(format, ty, tiling, usage, flags)
667        }
668    }
669
670    impl DeviceInfo {
671        #[deprecated = "no effect; use physical_device_index"]
672        #[doc(hidden)]
673        pub fn integrated_gpu() {
674            warn!("invalid deprecated device selection hint: integrated_gpu has no effect");
675        }
676
677        #[deprecated = "no effect; use physical_device_index"]
678        #[doc(hidden)]
679        pub fn discrete_gpu() {
680            warn!("invalid deprecated device selection hint: discrete_gpu has no effect");
681        }
682    }
683
684    impl DeviceInfoBuilder {
685        #[deprecated = "no effect; use physical_device_index"]
686        #[doc(hidden)]
687        pub fn select_physical_device(self, _: Box<dyn Fn()>) -> Self {
688            warn!(
689                "invalid deprecated device selection callback: select_physical_device has no effect"
690            );
691
692            self
693        }
694    }
695}
696
697#[cfg(test)]
698mod test {
699    use super::*;
700
701    type Info = DeviceInfo;
702    type Builder = DeviceInfoBuilder;
703
704    #[test]
705    pub fn device_info() {
706        Info::default().into_builder().build();
707    }
708
709    #[test]
710    pub fn device_info_builder() {
711        Builder::default().build();
712    }
713}