screen_13/driver/
device.rs

1//! Logical device resource types
2
3use {
4    super::{DriverError, Instance, physical_device::PhysicalDevice},
5    ash::{ext, khr, vk},
6    ash_window::enumerate_required_extensions,
7    derive_builder::{Builder, UninitializedFieldError},
8    gpu_allocator::{
9        AllocatorDebugSettings,
10        vulkan::{Allocator, AllocatorCreateDesc},
11    },
12    log::{error, trace, warn},
13    raw_window_handle::HasDisplayHandle,
14    std::{
15        cmp::Ordering,
16        ffi::CStr,
17        fmt::{Debug, Formatter},
18        iter::{empty, repeat},
19        mem::{ManuallyDrop, forget},
20        ops::Deref,
21        thread::panicking,
22        time::Instant,
23    },
24};
25
26#[cfg(feature = "parking_lot")]
27use parking_lot::Mutex;
28
29#[cfg(not(feature = "parking_lot"))]
30use std::sync::Mutex;
31
32/// Function type for selection of physical devices.
33pub type SelectPhysicalDeviceFn = dyn FnOnce(&[PhysicalDevice]) -> usize;
34
35/// Opaque handle to a device object.
36pub struct Device {
37    accel_struct_ext: Option<khr::acceleration_structure::Device>,
38
39    pub(super) allocator: ManuallyDrop<Mutex<Allocator>>,
40
41    device: ash::Device,
42
43    /// Vulkan instance pointer, which includes useful functions.
44    instance: Instance,
45
46    pipeline_cache: vk::PipelineCache,
47
48    /// The physical device, which contains useful data about features, properties, and limits.
49    pub physical_device: PhysicalDevice,
50
51    /// The physical execution queues which all work will be submitted to.
52    pub(crate) queues: Vec<Vec<vk::Queue>>,
53
54    pub(crate) ray_trace_ext: Option<khr::ray_tracing_pipeline::Device>,
55
56    pub(super) surface_ext: Option<khr::surface::Instance>,
57    pub(super) swapchain_ext: Option<khr::swapchain::Device>,
58}
59
60impl Device {
61    /// Prepares device creation information and calls the provided callback to allow an application
62    /// to control the device creation process.
63    ///
64    /// # Safety
65    ///
66    /// This is only required for interoperting with other libraries and comes with all the caveats
67    /// of using `ash` builder types, which are inherently dangerous. Use with extreme caution.
68    #[profiling::function]
69    pub unsafe fn create_ash_device<F>(
70        instance: &Instance,
71        physical_device: &PhysicalDevice,
72        display_window: bool,
73        create_fn: F,
74    ) -> ash::prelude::VkResult<ash::Device>
75    where
76        F: FnOnce(vk::DeviceCreateInfo) -> ash::prelude::VkResult<ash::Device>,
77    {
78        let mut enabled_ext_names = Vec::with_capacity(6);
79
80        if display_window {
81            enabled_ext_names.push(khr::swapchain::NAME.as_ptr());
82        }
83
84        if physical_device.accel_struct_properties.is_some() {
85            enabled_ext_names.push(khr::acceleration_structure::NAME.as_ptr());
86            enabled_ext_names.push(khr::deferred_host_operations::NAME.as_ptr());
87        }
88
89        if physical_device.ray_query_features.ray_query {
90            enabled_ext_names.push(khr::ray_query::NAME.as_ptr());
91        }
92
93        if physical_device.ray_trace_features.ray_tracing_pipeline {
94            enabled_ext_names.push(khr::ray_tracing_pipeline::NAME.as_ptr());
95        }
96
97        if physical_device.index_type_uint8_features.index_type_uint8 {
98            enabled_ext_names.push(ext::index_type_uint8::NAME.as_ptr());
99        }
100
101        let priorities = repeat(1.0)
102            .take(
103                physical_device
104                    .queue_families
105                    .iter()
106                    .map(|family| family.queue_count)
107                    .max()
108                    .unwrap_or_default() as _,
109            )
110            .collect::<Box<_>>();
111
112        let queue_infos = physical_device
113            .queue_families
114            .iter()
115            .enumerate()
116            .map(|(idx, family)| {
117                let mut queue_info = vk::DeviceQueueCreateInfo::default()
118                    .queue_family_index(idx as _)
119                    .queue_priorities(&priorities[0..family.queue_count as usize]);
120                queue_info.queue_count = family.queue_count;
121
122                queue_info
123            })
124            .collect::<Box<_>>();
125
126        let ash::InstanceFnV1_1 {
127            get_physical_device_features2,
128            ..
129        } = instance.fp_v1_1();
130        let mut features_v1_1 = vk::PhysicalDeviceVulkan11Features::default();
131        let mut features_v1_2 = vk::PhysicalDeviceVulkan12Features::default();
132        let mut acceleration_structure_features =
133            vk::PhysicalDeviceAccelerationStructureFeaturesKHR::default();
134        let mut index_type_uint8_features = vk::PhysicalDeviceIndexTypeUint8FeaturesEXT::default();
135        let mut ray_query_features = vk::PhysicalDeviceRayQueryFeaturesKHR::default();
136        let mut ray_trace_features = vk::PhysicalDeviceRayTracingPipelineFeaturesKHR::default();
137        let mut features = vk::PhysicalDeviceFeatures2::default()
138            .push_next(&mut features_v1_1)
139            .push_next(&mut features_v1_2);
140
141        if physical_device.accel_struct_properties.is_some() {
142            features = features.push_next(&mut acceleration_structure_features);
143        }
144
145        if physical_device.ray_query_features.ray_query {
146            features = features.push_next(&mut ray_query_features);
147        }
148
149        if physical_device.ray_trace_features.ray_tracing_pipeline {
150            features = features.push_next(&mut ray_trace_features);
151        }
152
153        if physical_device.index_type_uint8_features.index_type_uint8 {
154            features = features.push_next(&mut index_type_uint8_features);
155        }
156
157        unsafe { get_physical_device_features2(**physical_device, &mut features) };
158
159        let device_create_info = vk::DeviceCreateInfo::default()
160            .queue_create_infos(&queue_infos)
161            .enabled_extension_names(&enabled_ext_names)
162            .push_next(&mut features);
163
164        create_fn(device_create_info)
165    }
166
167    #[profiling::function]
168    fn create(
169        instance: Instance,
170        select_physical_device: Box<SelectPhysicalDeviceFn>,
171        display_window: bool,
172    ) -> Result<Self, DriverError> {
173        let mut physical_devices = Instance::physical_devices(&instance)?;
174
175        if physical_devices.is_empty() {
176            error!("no supported devices found");
177
178            return Err(DriverError::Unsupported);
179        }
180
181        let mut phyical_device_idx = select_physical_device(&physical_devices);
182
183        if phyical_device_idx >= physical_devices.len() {
184            warn!("invalid device selected");
185
186            phyical_device_idx = 0;
187        }
188
189        let physical_device = physical_devices.remove(phyical_device_idx);
190
191        let device = unsafe {
192            Self::create_ash_device(
193                &instance,
194                &physical_device,
195                display_window,
196                |device_create_info| {
197                    instance.create_device(*physical_device, &device_create_info, None)
198                },
199            )
200        }
201        .map_err(|err| {
202            error!("unable to create device: {err}");
203
204            DriverError::Unsupported
205        })?;
206
207        Self::load(instance, physical_device, device, display_window)
208    }
209
210    /// Constructs a new device using the given configuration.
211    #[profiling::function]
212    pub fn create_headless(info: impl Into<DeviceInfo>) -> Result<Self, DriverError> {
213        let DeviceInfo {
214            debug,
215            select_physical_device,
216        } = info.into();
217        let instance = Instance::create(debug, empty())?;
218
219        Self::create(instance, select_physical_device, false)
220    }
221
222    /// Constructs a new device using the given configuration.
223    #[profiling::function]
224    pub fn create_display(
225        info: impl Into<DeviceInfo>,
226        display_handle: &impl HasDisplayHandle,
227    ) -> Result<Self, DriverError> {
228        let DeviceInfo {
229            debug,
230            select_physical_device,
231        } = info.into();
232        let display_handle = display_handle.display_handle().map_err(|err| {
233            warn!("{err}");
234
235            DriverError::Unsupported
236        })?;
237        let required_extensions = enumerate_required_extensions(display_handle.as_raw())
238            .map_err(|err| {
239                warn!("{err}");
240
241                DriverError::Unsupported
242            })?
243            .iter()
244            .map(|ext| unsafe { CStr::from_ptr(*ext as *const _) });
245        let instance = Instance::create(debug, required_extensions)?;
246
247        Self::create(instance, select_physical_device, true)
248    }
249
250    pub(crate) fn create_fence(this: &Self, signaled: bool) -> Result<vk::Fence, DriverError> {
251        let mut flags = vk::FenceCreateFlags::empty();
252
253        if signaled {
254            flags |= vk::FenceCreateFlags::SIGNALED;
255        }
256
257        let create_info = vk::FenceCreateInfo::default().flags(flags);
258        let allocation_callbacks = None;
259
260        unsafe { this.create_fence(&create_info, allocation_callbacks) }.map_err(|err| {
261            warn!("{err}");
262
263            DriverError::OutOfMemory
264        })
265    }
266
267    pub(crate) fn create_semaphore(this: &Self) -> Result<vk::Semaphore, DriverError> {
268        let create_info = vk::SemaphoreCreateInfo::default();
269        let allocation_callbacks = None;
270
271        unsafe { this.create_semaphore(&create_info, allocation_callbacks) }.map_err(|err| {
272            warn!("{err}");
273
274            DriverError::OutOfMemory
275        })
276    }
277
278    /// Helper for times when you already know that the device supports the acceleration
279    /// structure extension.
280    ///
281    /// # Panics
282    ///
283    /// Panics if [Self.physical_device.accel_struct_properties] is `None`.
284    pub(crate) fn expect_accel_struct_ext(this: &Self) -> &khr::acceleration_structure::Device {
285        this.accel_struct_ext
286            .as_ref()
287            .expect("VK_KHR_acceleration_structure")
288    }
289
290    /// Helper for times when you already know that the instance supports the surface extension.
291    ///
292    /// # Panics
293    ///
294    /// Panics if the device was not created for display window access.
295    pub(crate) fn expect_surface_ext(this: &Self) -> &khr::surface::Instance {
296        this.surface_ext.as_ref().expect("VK_KHR_surface")
297    }
298
299    /// Helper for times when you already know that the device supports the swapchain extension.
300    ///
301    /// # Panics
302    ///
303    /// Panics if the device was not created for display window access.
304    pub(crate) fn expect_swapchain_ext(this: &Self) -> &khr::swapchain::Device {
305        this.swapchain_ext.as_ref().expect("VK_KHR_swapchain")
306    }
307
308    /// Loads and existing `ash` Vulkan device that may have been created by other means.
309    #[profiling::function]
310    pub fn load(
311        instance: Instance,
312        physical_device: PhysicalDevice,
313        device: ash::Device,
314        display_window: bool,
315    ) -> Result<Self, DriverError> {
316        let debug = Instance::is_debug(&instance);
317        let allocator = Allocator::new(&AllocatorCreateDesc {
318            instance: (*instance).clone(),
319            device: device.clone(),
320            physical_device: *physical_device,
321            debug_settings: AllocatorDebugSettings {
322                log_leaks_on_shutdown: debug,
323                log_memory_information: debug,
324                log_allocations: debug,
325                ..Default::default()
326            },
327            buffer_device_address: true,
328            allocation_sizes: Default::default(),
329        })
330        .map_err(|err| {
331            warn!("{err}");
332
333            DriverError::Unsupported
334        })?;
335
336        let mut queues = Vec::with_capacity(physical_device.queue_families.len());
337
338        for (queue_family_index, properties) in physical_device.queue_families.iter().enumerate() {
339            let mut queue_family = Vec::with_capacity(properties.queue_count as _);
340
341            for queue_index in 0..properties.queue_count {
342                queue_family
343                    .push(unsafe { device.get_device_queue(queue_family_index as _, queue_index) });
344            }
345
346            queues.push(queue_family);
347        }
348
349        let surface_ext = display_window
350            .then(|| khr::surface::Instance::new(Instance::entry(&instance), &instance));
351        let swapchain_ext = display_window.then(|| khr::swapchain::Device::new(&instance, &device));
352        let accel_struct_ext = physical_device
353            .accel_struct_properties
354            .is_some()
355            .then(|| khr::acceleration_structure::Device::new(&instance, &device));
356        let ray_trace_ext = physical_device
357            .ray_trace_features
358            .ray_tracing_pipeline
359            .then(|| khr::ray_tracing_pipeline::Device::new(&instance, &device));
360
361        let pipeline_cache =
362            unsafe { device.create_pipeline_cache(&vk::PipelineCacheCreateInfo::default(), None) }
363                .map_err(|err| {
364                    warn!("{err}");
365
366                    DriverError::Unsupported
367                })?;
368
369        Ok(Self {
370            accel_struct_ext,
371            allocator: ManuallyDrop::new(Mutex::new(allocator)),
372            device,
373            instance,
374            pipeline_cache,
375            physical_device,
376            queues,
377            ray_trace_ext,
378            surface_ext,
379            swapchain_ext,
380        })
381    }
382
383    /// Lists the physical device's format capabilities.
384    #[profiling::function]
385    pub fn format_properties(this: &Self, format: vk::Format) -> vk::FormatProperties {
386        unsafe {
387            this.instance
388                .get_physical_device_format_properties(*this.physical_device, format)
389        }
390    }
391
392    /// Lists the physical device's image format capabilities.
393    ///
394    /// A result of `None` indicates the format is not supported.
395    #[profiling::function]
396    pub fn image_format_properties(
397        this: &Self,
398        format: vk::Format,
399        ty: vk::ImageType,
400        tiling: vk::ImageTiling,
401        usage: vk::ImageUsageFlags,
402        flags: vk::ImageCreateFlags,
403    ) -> Result<Option<vk::ImageFormatProperties>, DriverError> {
404        unsafe {
405            match this.instance.get_physical_device_image_format_properties(
406                *this.physical_device,
407                format,
408                ty,
409                tiling,
410                usage,
411                flags,
412            ) {
413                Ok(properties) => Ok(Some(properties)),
414                Err(err) if err == vk::Result::ERROR_FORMAT_NOT_SUPPORTED => {
415                    // We don't log this condition because it is normal for unsupported
416                    // formats to be checked - we use the result to inform callers they
417                    // cannot use those formats.
418
419                    Ok(None)
420                }
421                _ => Err(DriverError::OutOfMemory),
422            }
423        }
424    }
425
426    /// Provides a reference to the Vulkan instance used by this device.
427    pub fn instance(this: &Self) -> &Instance {
428        &this.instance
429    }
430
431    pub(crate) fn pipeline_cache(this: &Self) -> vk::PipelineCache {
432        this.pipeline_cache
433    }
434
435    #[profiling::function]
436    pub(crate) fn wait_for_fence(this: &Self, fence: &vk::Fence) -> Result<(), DriverError> {
437        use std::slice::from_ref;
438
439        Device::wait_for_fences(this, from_ref(fence))
440    }
441
442    #[profiling::function]
443    pub(crate) fn wait_for_fences(this: &Self, fences: &[vk::Fence]) -> Result<(), DriverError> {
444        unsafe {
445            match this.device.wait_for_fences(fences, true, 100) {
446                Ok(_) => return Ok(()),
447                Err(err) if err == vk::Result::ERROR_DEVICE_LOST => {
448                    error!("Device lost");
449
450                    return Err(DriverError::InvalidData);
451                }
452                Err(err) if err == vk::Result::TIMEOUT => {
453                    trace!("waiting...");
454                }
455                _ => return Err(DriverError::OutOfMemory),
456            }
457
458            let started = Instant::now();
459
460            match this.device.wait_for_fences(fences, true, u64::MAX) {
461                Ok(_) => (),
462                Err(err) if err == vk::Result::ERROR_DEVICE_LOST => {
463                    error!("Device lost");
464
465                    return Err(DriverError::InvalidData);
466                }
467                _ => return Err(DriverError::OutOfMemory),
468            }
469
470            let elapsed = Instant::now() - started;
471            let elapsed_millis = elapsed.as_millis();
472
473            if elapsed_millis > 0 {
474                warn!("waited for {} ms", elapsed_millis);
475            }
476        }
477
478        Ok(())
479    }
480}
481
482impl Debug for Device {
483    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
484        f.write_str("Device")
485    }
486}
487
488impl Deref for Device {
489    type Target = ash::Device;
490
491    fn deref(&self) -> &Self::Target {
492        &self.device
493    }
494}
495
496impl Drop for Device {
497    #[profiling::function]
498    fn drop(&mut self) {
499        if panicking() {
500            // When panicking we don't want the GPU allocator to complain about leaks
501            unsafe {
502                forget(ManuallyDrop::take(&mut self.allocator));
503            }
504
505            return;
506        }
507
508        // trace!("drop");
509
510        if let Err(err) = unsafe { self.device.device_wait_idle() } {
511            warn!("device_wait_idle() failed: {err}");
512        }
513
514        unsafe {
515            self.device
516                .destroy_pipeline_cache(self.pipeline_cache, None);
517
518            ManuallyDrop::drop(&mut self.allocator);
519        }
520
521        unsafe {
522            self.device.destroy_device(None);
523        }
524    }
525}
526
527/// Information used to create a [`Device`] instance.
528#[derive(Builder)]
529#[builder(
530    build_fn(private, name = "fallible_build", error = "DeviceInfoBuilderError"),
531    pattern = "owned"
532)]
533#[non_exhaustive]
534pub struct DeviceInfo {
535    /// Enables Vulkan validation layers.
536    ///
537    /// This requires a Vulkan SDK installation and will cause validation errors to introduce
538    /// panics as they happen.
539    ///
540    /// _NOTE:_ Consider turning OFF debug if you discover an unknown issue. Often the validation
541    /// layers will throw an error before other layers can provide additional context such as the
542    /// API dump info or other messages. You might find the "actual" issue is detailed in those
543    /// subsequent details.
544    ///
545    /// ## Platform-specific
546    ///
547    /// **macOS:** Has no effect.
548    #[builder(default)]
549    pub debug: bool,
550
551    /// Callback function used to select a [`PhysicalDevice`] from the available devices. The
552    /// callback must return the index of the selected device.
553    #[builder(default = "Box::new(DeviceInfo::discrete_gpu)")]
554    pub select_physical_device: Box<SelectPhysicalDeviceFn>,
555}
556
557impl DeviceInfo {
558    /// Specifies default device information.
559    #[allow(clippy::new_ret_no_self)]
560    #[deprecated = "Use DeviceInfo::default()"]
561    #[doc(hidden)]
562    pub fn new() -> DeviceInfoBuilder {
563        Default::default()
564    }
565
566    /// A builtin [`DeviceInfo::select_physical_device`] function which prioritizes selection of
567    /// lower-power integrated GPU devices.
568    #[profiling::function]
569    pub fn integrated_gpu(physical_devices: &[PhysicalDevice]) -> usize {
570        assert!(!physical_devices.is_empty());
571
572        let mut physical_devices = physical_devices.iter().enumerate().collect::<Box<_>>();
573
574        if physical_devices.len() == 1 {
575            return 0;
576        }
577
578        fn device_type(ty: vk::PhysicalDeviceType) -> usize {
579            match ty {
580                vk::PhysicalDeviceType::INTEGRATED_GPU => 0,
581                vk::PhysicalDeviceType::VIRTUAL_GPU => 1,
582                vk::PhysicalDeviceType::CPU => 2,
583                vk::PhysicalDeviceType::DISCRETE_GPU => 3,
584                _ => 4,
585            }
586        }
587
588        physical_devices.sort_unstable_by(|(_, lhs), (_, rhs)| {
589            let lhs_device_ty = device_type(lhs.properties_v1_0.device_type);
590            let rhs_device_ty = device_type(rhs.properties_v1_0.device_type);
591            let device_ty = lhs_device_ty.cmp(&rhs_device_ty);
592
593            if device_ty != Ordering::Equal {
594                return device_ty;
595            }
596
597            // TODO: Select the device with the most memory
598
599            Ordering::Equal
600        });
601
602        let (idx, _) = physical_devices[0];
603
604        idx
605    }
606
607    /// A builtin [`DeviceInfo::select_physical_device`] function which prioritizes selection of
608    /// higher-performance discrete GPU devices.
609    #[profiling::function]
610    pub fn discrete_gpu(physical_devices: &[PhysicalDevice]) -> usize {
611        assert!(!physical_devices.is_empty());
612
613        let mut physical_devices = physical_devices.iter().enumerate().collect::<Box<_>>();
614
615        if physical_devices.len() == 1 {
616            return 0;
617        }
618
619        fn device_type(ty: vk::PhysicalDeviceType) -> usize {
620            match ty {
621                vk::PhysicalDeviceType::DISCRETE_GPU => 0,
622                vk::PhysicalDeviceType::INTEGRATED_GPU => 1,
623                vk::PhysicalDeviceType::VIRTUAL_GPU => 2,
624                vk::PhysicalDeviceType::CPU => 3,
625                _ => 4,
626            }
627        }
628
629        physical_devices.sort_unstable_by(|(_, lhs), (_, rhs)| {
630            let lhs_device_ty = device_type(lhs.properties_v1_0.device_type);
631            let rhs_device_ty = device_type(rhs.properties_v1_0.device_type);
632            let device_ty = lhs_device_ty.cmp(&rhs_device_ty);
633
634            if device_ty != Ordering::Equal {
635                return device_ty;
636            }
637
638            // TODO: Select the device with the most memory
639
640            Ordering::Equal
641        });
642
643        let (idx, _) = physical_devices[0];
644
645        idx
646    }
647
648    /// Converts a `DeviceInfo` into a `DeviceInfoBuilder`.
649    #[inline(always)]
650    pub fn to_builder(self) -> DeviceInfoBuilder {
651        DeviceInfoBuilder {
652            debug: Some(self.debug),
653            select_physical_device: Some(self.select_physical_device),
654        }
655    }
656}
657
658impl Debug for DeviceInfo {
659    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
660        f.debug_struct("DeviceInfo")
661            .field("debug", &self.debug)
662            .field("select_physical_device", &"fn")
663            .finish()
664    }
665}
666
667impl Default for DeviceInfo {
668    fn default() -> Self {
669        Self {
670            debug: false,
671            select_physical_device: Box::new(DeviceInfo::discrete_gpu),
672        }
673    }
674}
675
676impl From<DeviceInfoBuilder> for DeviceInfo {
677    fn from(info: DeviceInfoBuilder) -> Self {
678        info.build()
679    }
680}
681
682impl DeviceInfoBuilder {
683    /// Builds a new `DeviceInfo`.
684    #[inline(always)]
685    pub fn build(self) -> DeviceInfo {
686        let res = self.fallible_build();
687
688        #[cfg(test)]
689        let res = res.unwrap();
690
691        #[cfg(not(test))]
692        let res = unsafe { res.unwrap_unchecked() };
693
694        res
695    }
696}
697
698#[derive(Debug)]
699struct DeviceInfoBuilderError;
700
701impl From<UninitializedFieldError> for DeviceInfoBuilderError {
702    fn from(_: UninitializedFieldError) -> Self {
703        Self
704    }
705}
706
707#[cfg(test)]
708mod tests {
709    use super::*;
710
711    type Info = DeviceInfo;
712    type Builder = DeviceInfoBuilder;
713
714    #[test]
715    pub fn device_info() {
716        Info::default().to_builder().build();
717    }
718
719    #[test]
720    pub fn device_info_builder() {
721        Builder::default().build();
722    }
723}