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, info, trace, warn},
13    raw_window_handle::HasDisplayHandle,
14    std::{
15        cmp::Ordering,
16        ffi::CStr,
17        fmt::{Debug, Formatter},
18        iter::{empty, repeat_n},
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_n(
102            1.0,
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        info!("created {}", physical_device.properties_v1_0.device_name);
208
209        Self::load(instance, physical_device, device, display_window)
210    }
211
212    /// Constructs a new device using the given configuration.
213    #[profiling::function]
214    pub fn create_headless(info: impl Into<DeviceInfo>) -> Result<Self, DriverError> {
215        let DeviceInfo {
216            debug,
217            select_physical_device,
218        } = info.into();
219        let instance = Instance::create(debug, empty())?;
220
221        Self::create(instance, select_physical_device, false)
222    }
223
224    /// Constructs a new device using the given configuration.
225    #[profiling::function]
226    pub fn create_display(
227        info: impl Into<DeviceInfo>,
228        display_handle: &impl HasDisplayHandle,
229    ) -> Result<Self, DriverError> {
230        let DeviceInfo {
231            debug,
232            select_physical_device,
233        } = info.into();
234        let display_handle = display_handle.display_handle().map_err(|err| {
235            warn!("{err}");
236
237            DriverError::Unsupported
238        })?;
239        let required_extensions = enumerate_required_extensions(display_handle.as_raw())
240            .map_err(|err| {
241                warn!("{err}");
242
243                DriverError::Unsupported
244            })?
245            .iter()
246            .map(|ext| unsafe { CStr::from_ptr(*ext as *const _) });
247        let instance = Instance::create(debug, required_extensions)?;
248
249        Self::create(instance, select_physical_device, true)
250    }
251
252    pub(crate) fn create_fence(this: &Self, signaled: bool) -> Result<vk::Fence, DriverError> {
253        let mut flags = vk::FenceCreateFlags::empty();
254
255        if signaled {
256            flags |= vk::FenceCreateFlags::SIGNALED;
257        }
258
259        let create_info = vk::FenceCreateInfo::default().flags(flags);
260        let allocation_callbacks = None;
261
262        unsafe { this.create_fence(&create_info, allocation_callbacks) }.map_err(|err| {
263            warn!("{err}");
264
265            DriverError::OutOfMemory
266        })
267    }
268
269    pub(crate) fn create_semaphore(this: &Self) -> Result<vk::Semaphore, DriverError> {
270        let create_info = vk::SemaphoreCreateInfo::default();
271        let allocation_callbacks = None;
272
273        unsafe { this.create_semaphore(&create_info, allocation_callbacks) }.map_err(|err| {
274            warn!("{err}");
275
276            DriverError::OutOfMemory
277        })
278    }
279
280    /// Helper for times when you already know that the device supports the acceleration
281    /// structure extension.
282    ///
283    /// # Panics
284    ///
285    /// Panics if [Self.physical_device.accel_struct_properties] is `None`.
286    pub(crate) fn expect_accel_struct_ext(this: &Self) -> &khr::acceleration_structure::Device {
287        this.accel_struct_ext
288            .as_ref()
289            .expect("VK_KHR_acceleration_structure")
290    }
291
292    /// Helper for times when you already know that the instance supports the surface extension.
293    ///
294    /// # Panics
295    ///
296    /// Panics if the device was not created for display window access.
297    pub(crate) fn expect_surface_ext(this: &Self) -> &khr::surface::Instance {
298        this.surface_ext.as_ref().expect("VK_KHR_surface")
299    }
300
301    /// Helper for times when you already know that the device supports the swapchain extension.
302    ///
303    /// # Panics
304    ///
305    /// Panics if the device was not created for display window access.
306    pub(crate) fn expect_swapchain_ext(this: &Self) -> &khr::swapchain::Device {
307        this.swapchain_ext.as_ref().expect("VK_KHR_swapchain")
308    }
309
310    /// Loads and existing `ash` Vulkan device that may have been created by other means.
311    #[profiling::function]
312    pub fn load(
313        instance: Instance,
314        physical_device: PhysicalDevice,
315        device: ash::Device,
316        display_window: bool,
317    ) -> Result<Self, DriverError> {
318        let debug = Instance::is_debug(&instance);
319        let mut debug_settings = AllocatorDebugSettings::default();
320        debug_settings.log_leaks_on_shutdown = debug;
321        debug_settings.log_memory_information = debug;
322        debug_settings.log_allocations = debug;
323
324        let allocator = Allocator::new(&AllocatorCreateDesc {
325            instance: (*instance).clone(),
326            device: device.clone(),
327            physical_device: *physical_device,
328            debug_settings,
329            buffer_device_address: true,
330            allocation_sizes: Default::default(),
331        })
332        .map_err(|err| {
333            warn!("{err}");
334
335            DriverError::Unsupported
336        })?;
337
338        let mut queues = Vec::with_capacity(physical_device.queue_families.len());
339
340        for (queue_family_index, properties) in physical_device.queue_families.iter().enumerate() {
341            let mut queue_family = Vec::with_capacity(properties.queue_count as _);
342
343            for queue_index in 0..properties.queue_count {
344                queue_family
345                    .push(unsafe { device.get_device_queue(queue_family_index as _, queue_index) });
346            }
347
348            queues.push(queue_family);
349        }
350
351        let surface_ext = display_window
352            .then(|| khr::surface::Instance::new(Instance::entry(&instance), &instance));
353        let swapchain_ext = display_window.then(|| khr::swapchain::Device::new(&instance, &device));
354        let accel_struct_ext = physical_device
355            .accel_struct_properties
356            .is_some()
357            .then(|| khr::acceleration_structure::Device::new(&instance, &device));
358        let ray_trace_ext = physical_device
359            .ray_trace_features
360            .ray_tracing_pipeline
361            .then(|| khr::ray_tracing_pipeline::Device::new(&instance, &device));
362
363        let pipeline_cache =
364            unsafe { device.create_pipeline_cache(&vk::PipelineCacheCreateInfo::default(), None) }
365                .map_err(|err| {
366                    warn!("{err}");
367
368                    DriverError::Unsupported
369                })?;
370
371        Ok(Self {
372            accel_struct_ext,
373            allocator: ManuallyDrop::new(Mutex::new(allocator)),
374            device,
375            instance,
376            pipeline_cache,
377            physical_device,
378            queues,
379            ray_trace_ext,
380            surface_ext,
381            swapchain_ext,
382        })
383    }
384
385    /// Lists the physical device's format capabilities.
386    #[profiling::function]
387    pub fn format_properties(this: &Self, format: vk::Format) -> vk::FormatProperties {
388        unsafe {
389            this.instance
390                .get_physical_device_format_properties(*this.physical_device, format)
391        }
392    }
393
394    /// Lists the physical device's image format capabilities.
395    ///
396    /// A result of `None` indicates the format is not supported.
397    #[profiling::function]
398    pub fn image_format_properties(
399        this: &Self,
400        format: vk::Format,
401        ty: vk::ImageType,
402        tiling: vk::ImageTiling,
403        usage: vk::ImageUsageFlags,
404        flags: vk::ImageCreateFlags,
405    ) -> Result<Option<vk::ImageFormatProperties>, DriverError> {
406        unsafe {
407            match this.instance.get_physical_device_image_format_properties(
408                *this.physical_device,
409                format,
410                ty,
411                tiling,
412                usage,
413                flags,
414            ) {
415                Ok(properties) => Ok(Some(properties)),
416                Err(err) if err == vk::Result::ERROR_FORMAT_NOT_SUPPORTED => {
417                    // We don't log this condition because it is normal for unsupported
418                    // formats to be checked - we use the result to inform callers they
419                    // cannot use those formats.
420
421                    Ok(None)
422                }
423                _ => Err(DriverError::OutOfMemory),
424            }
425        }
426    }
427
428    /// Provides a reference to the Vulkan instance used by this device.
429    pub fn instance(this: &Self) -> &Instance {
430        &this.instance
431    }
432
433    pub(crate) fn pipeline_cache(this: &Self) -> vk::PipelineCache {
434        this.pipeline_cache
435    }
436
437    #[profiling::function]
438    pub(crate) fn wait_for_fence(this: &Self, fence: &vk::Fence) -> Result<(), DriverError> {
439        use std::slice::from_ref;
440
441        Device::wait_for_fences(this, from_ref(fence))
442    }
443
444    #[profiling::function]
445    pub(crate) fn wait_for_fences(this: &Self, fences: &[vk::Fence]) -> Result<(), DriverError> {
446        unsafe {
447            match this.device.wait_for_fences(fences, true, 100) {
448                Ok(_) => return Ok(()),
449                Err(err) if err == vk::Result::ERROR_DEVICE_LOST => {
450                    error!("Device lost");
451
452                    return Err(DriverError::InvalidData);
453                }
454                Err(err) if err == vk::Result::TIMEOUT => {
455                    trace!("waiting...");
456                }
457                _ => return Err(DriverError::OutOfMemory),
458            }
459
460            let started = Instant::now();
461
462            match this.device.wait_for_fences(fences, true, u64::MAX) {
463                Ok(_) => (),
464                Err(err) if err == vk::Result::ERROR_DEVICE_LOST => {
465                    error!("Device lost");
466
467                    return Err(DriverError::InvalidData);
468                }
469                _ => return Err(DriverError::OutOfMemory),
470            }
471
472            let elapsed = Instant::now() - started;
473            let elapsed_millis = elapsed.as_millis();
474
475            if elapsed_millis > 0 {
476                warn!("waited for {} ms", elapsed_millis);
477            }
478        }
479
480        Ok(())
481    }
482}
483
484impl Debug for Device {
485    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
486        f.write_str("Device")
487    }
488}
489
490impl Deref for Device {
491    type Target = ash::Device;
492
493    fn deref(&self) -> &Self::Target {
494        &self.device
495    }
496}
497
498impl Drop for Device {
499    #[profiling::function]
500    fn drop(&mut self) {
501        if panicking() {
502            // When panicking we don't want the GPU allocator to complain about leaks
503            unsafe {
504                forget(ManuallyDrop::take(&mut self.allocator));
505            }
506
507            return;
508        }
509
510        // trace!("drop");
511
512        if let Err(err) = unsafe { self.device.device_wait_idle() } {
513            warn!("device_wait_idle() failed: {err}");
514        }
515
516        unsafe {
517            self.device
518                .destroy_pipeline_cache(self.pipeline_cache, None);
519
520            ManuallyDrop::drop(&mut self.allocator);
521        }
522
523        unsafe {
524            self.device.destroy_device(None);
525        }
526    }
527}
528
529/// Information used to create a [`Device`] instance.
530#[derive(Builder)]
531#[builder(
532    build_fn(private, name = "fallible_build", error = "DeviceInfoBuilderError"),
533    pattern = "owned"
534)]
535#[non_exhaustive]
536pub struct DeviceInfo {
537    /// Enables Vulkan validation layers.
538    ///
539    /// This requires a Vulkan SDK installation and will cause validation errors to introduce
540    /// panics as they happen.
541    ///
542    /// _NOTE:_ Consider turning OFF debug if you discover an unknown issue. Often the validation
543    /// layers will throw an error before other layers can provide additional context such as the
544    /// API dump info or other messages. You might find the "actual" issue is detailed in those
545    /// subsequent details.
546    ///
547    /// ## Platform-specific
548    ///
549    /// **macOS:** Has no effect.
550    #[builder(default)]
551    pub debug: bool,
552
553    /// Callback function used to select a [`PhysicalDevice`] from the available devices. The
554    /// callback must return the index of the selected device.
555    #[builder(default = "Box::new(DeviceInfo::discrete_gpu)")]
556    pub select_physical_device: Box<SelectPhysicalDeviceFn>,
557}
558
559impl DeviceInfo {
560    /// Specifies default device information.
561    #[allow(clippy::new_ret_no_self)]
562    #[deprecated = "Use DeviceInfo::default()"]
563    #[doc(hidden)]
564    pub fn new() -> DeviceInfoBuilder {
565        Default::default()
566    }
567
568    /// A builtin [`DeviceInfo::select_physical_device`] function which prioritizes selection of
569    /// lower-power integrated GPU devices.
570    #[profiling::function]
571    pub fn integrated_gpu(physical_devices: &[PhysicalDevice]) -> usize {
572        assert!(!physical_devices.is_empty());
573
574        let mut physical_devices = physical_devices.iter().enumerate().collect::<Box<_>>();
575
576        if physical_devices.len() == 1 {
577            return 0;
578        }
579
580        fn device_type(ty: vk::PhysicalDeviceType) -> usize {
581            match ty {
582                vk::PhysicalDeviceType::INTEGRATED_GPU => 0,
583                vk::PhysicalDeviceType::VIRTUAL_GPU => 1,
584                vk::PhysicalDeviceType::CPU => 2,
585                vk::PhysicalDeviceType::DISCRETE_GPU => 3,
586                _ => 4,
587            }
588        }
589
590        physical_devices.sort_unstable_by(|(_, lhs), (_, rhs)| {
591            let lhs_device_ty = device_type(lhs.properties_v1_0.device_type);
592            let rhs_device_ty = device_type(rhs.properties_v1_0.device_type);
593            let device_ty = lhs_device_ty.cmp(&rhs_device_ty);
594
595            if device_ty != Ordering::Equal {
596                return device_ty;
597            }
598
599            // TODO: Select the device with the most memory
600
601            Ordering::Equal
602        });
603
604        let (idx, _) = physical_devices[0];
605
606        idx
607    }
608
609    /// A builtin [`DeviceInfo::select_physical_device`] function which prioritizes selection of
610    /// higher-performance discrete GPU devices.
611    #[profiling::function]
612    pub fn discrete_gpu(physical_devices: &[PhysicalDevice]) -> usize {
613        assert!(!physical_devices.is_empty());
614
615        let mut physical_devices = physical_devices.iter().enumerate().collect::<Box<_>>();
616
617        if physical_devices.len() == 1 {
618            return 0;
619        }
620
621        fn device_type(ty: vk::PhysicalDeviceType) -> usize {
622            match ty {
623                vk::PhysicalDeviceType::DISCRETE_GPU => 0,
624                vk::PhysicalDeviceType::INTEGRATED_GPU => 1,
625                vk::PhysicalDeviceType::VIRTUAL_GPU => 2,
626                vk::PhysicalDeviceType::CPU => 3,
627                _ => 4,
628            }
629        }
630
631        physical_devices.sort_unstable_by(|(_, lhs), (_, rhs)| {
632            let lhs_device_ty = device_type(lhs.properties_v1_0.device_type);
633            let rhs_device_ty = device_type(rhs.properties_v1_0.device_type);
634            let device_ty = lhs_device_ty.cmp(&rhs_device_ty);
635
636            if device_ty != Ordering::Equal {
637                return device_ty;
638            }
639
640            // TODO: Select the device with the most memory
641
642            Ordering::Equal
643        });
644
645        let (idx, _) = physical_devices[0];
646
647        idx
648    }
649
650    /// Converts a `DeviceInfo` into a `DeviceInfoBuilder`.
651    #[inline(always)]
652    pub fn to_builder(self) -> DeviceInfoBuilder {
653        DeviceInfoBuilder {
654            debug: Some(self.debug),
655            select_physical_device: Some(self.select_physical_device),
656        }
657    }
658}
659
660impl Debug for DeviceInfo {
661    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
662        f.debug_struct("DeviceInfo")
663            .field("debug", &self.debug)
664            .field("select_physical_device", &"fn")
665            .finish()
666    }
667}
668
669impl Default for DeviceInfo {
670    fn default() -> Self {
671        Self {
672            debug: false,
673            select_physical_device: Box::new(DeviceInfo::discrete_gpu),
674        }
675    }
676}
677
678impl From<DeviceInfoBuilder> for DeviceInfo {
679    fn from(info: DeviceInfoBuilder) -> Self {
680        info.build()
681    }
682}
683
684impl DeviceInfoBuilder {
685    /// Builds a new `DeviceInfo`.
686    #[inline(always)]
687    pub fn build(self) -> DeviceInfo {
688        let res = self.fallible_build();
689
690        #[cfg(test)]
691        let res = res.unwrap();
692
693        #[cfg(not(test))]
694        let res = unsafe { res.unwrap_unchecked() };
695
696        res
697    }
698}
699
700#[derive(Debug)]
701struct DeviceInfoBuilderError;
702
703impl From<UninitializedFieldError> for DeviceInfoBuilderError {
704    fn from(_: UninitializedFieldError) -> Self {
705        Self
706    }
707}
708
709#[cfg(test)]
710mod tests {
711    use super::*;
712
713    type Info = DeviceInfo;
714    type Builder = DeviceInfoBuilder;
715
716    #[test]
717    pub fn device_info() {
718        Info::default().to_builder().build();
719    }
720
721    #[test]
722    pub fn device_info_builder() {
723        Builder::default().build();
724    }
725}