| 1 | use crate::Error; |
| 2 | use ash::vk; |
| 3 | use gpu_allocator::vulkan::{Allocator, AllocatorCreateDesc}; |
| 4 | use std::sync::Mutex; |
| 5 | |
| 6 | /// Everything that must outlive every Vulkan object the renderer hands out. |
| 7 | /// Fields drop in declaration order, so the allocator frees its memory before |
| 8 | /// the device is destroyed. |
| 9 | pub(crate) struct Shared { |
| 10 | pub allocator: Mutex<Allocator>, |
| 11 | pub device: Device, |
| 12 | } |
| 13 | |
| 14 | pub(crate) struct Device { |
| 15 | pub _entry: ash::Entry, |
| 16 | pub instance: ash::Instance, |
| 17 | #[expect( |
| 18 | dead_code, |
| 19 | reason = "dmabuf import queries format properties through it" |
| 20 | )] |
| 21 | pub physical: vk::PhysicalDevice, |
| 22 | pub device: ash::Device, |
| 23 | pub queue: Mutex<vk::Queue>, |
| 24 | pub queue_family: u32, |
| 25 | pub push_descriptor: ash::khr::push_descriptor::Device, |
| 26 | pub name: String, |
| 27 | } |
| 28 | |
| 29 | impl Drop for Device { |
| 30 | fn drop(&mut self) { |
| 31 | unsafe { |
| 32 | let _ = self.device.device_wait_idle(); |
| 33 | self.device.destroy_device(None); |
| 34 | self.instance.destroy_instance(None); |
| 35 | } |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | impl Shared { |
| 40 | /// Opens the first suitable device, or the one whose name contains |
| 41 | /// `MONOLITH_VK_DEVICE` when that is set. |
| 42 | pub fn new() -> Result<Self, Error> { |
| 43 | let entry = unsafe { ash::Entry::load()? }; |
| 44 | let app = vk::ApplicationInfo::default() |
| 45 | .application_name(c"monolith") |
| 46 | .engine_name(c"monolith") |
| 47 | .api_version(vk::API_VERSION_1_3); |
| 48 | let instance = unsafe { |
| 49 | entry.create_instance( |
| 50 | &vk::InstanceCreateInfo::default().application_info(&app), |
| 51 | None, |
| 52 | )? |
| 53 | }; |
| 54 | let wanted = std::env::var("MONOLITH_VK_DEVICE").ok(); |
| 55 | let picked = unsafe { pick_physical(&instance, wanted.as_deref()) }; |
| 56 | let Some((physical, queue_family, name)) = picked else { |
| 57 | unsafe { instance.destroy_instance(None) }; |
| 58 | return Err(Error::NoSuitableDevice); |
| 59 | }; |
| 60 | let priorities = [1.0]; |
| 61 | let queues = [vk::DeviceQueueCreateInfo::default() |
| 62 | .queue_family_index(queue_family) |
| 63 | .queue_priorities(&priorities)]; |
| 64 | let extensions = [ash::khr::push_descriptor::NAME.as_ptr()]; |
| 65 | let mut features13 = vk::PhysicalDeviceVulkan13Features::default() |
| 66 | .dynamic_rendering(true) |
| 67 | .synchronization2(true); |
| 68 | let info = vk::DeviceCreateInfo::default() |
| 69 | .queue_create_infos(&queues) |
| 70 | .enabled_extension_names(&extensions) |
| 71 | .push_next(&mut features13); |
| 72 | let device = match unsafe { instance.create_device(physical, &info, None) } { |
| 73 | Ok(d) => d, |
| 74 | Err(e) => { |
| 75 | unsafe { instance.destroy_instance(None) }; |
| 76 | return Err(e.into()); |
| 77 | } |
| 78 | }; |
| 79 | let queue = unsafe { device.get_device_queue(queue_family, 0) }; |
| 80 | let push_descriptor = ash::khr::push_descriptor::Device::new(&instance, &device); |
| 81 | let allocator = Allocator::new(&AllocatorCreateDesc { |
| 82 | instance: instance.clone(), |
| 83 | device: device.clone(), |
| 84 | physical_device: physical, |
| 85 | debug_settings: Default::default(), |
| 86 | buffer_device_address: false, |
| 87 | allocation_sizes: Default::default(), |
| 88 | }); |
| 89 | let device = Device { |
| 90 | _entry: entry, |
| 91 | instance, |
| 92 | physical, |
| 93 | device, |
| 94 | queue: Mutex::new(queue), |
| 95 | queue_family, |
| 96 | push_descriptor, |
| 97 | name, |
| 98 | }; |
| 99 | Ok(Self { |
| 100 | allocator: Mutex::new(allocator?), |
| 101 | device, |
| 102 | }) |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | unsafe fn pick_physical( |
| 107 | instance: &ash::Instance, |
| 108 | wanted: Option<&str>, |
| 109 | ) -> Option<(vk::PhysicalDevice, u32, String)> { |
| 110 | let devices = unsafe { instance.enumerate_physical_devices() }.ok()?; |
| 111 | let mut candidates = Vec::new(); |
| 112 | for physical in devices { |
| 113 | let props = unsafe { instance.get_physical_device_properties(physical) }; |
| 114 | if props.api_version < vk::API_VERSION_1_3 { |
| 115 | continue; |
| 116 | } |
| 117 | let name = props |
| 118 | .device_name_as_c_str() |
| 119 | .unwrap_or(c"unknown") |
| 120 | .to_string_lossy() |
| 121 | .into_owned(); |
| 122 | if wanted.is_some_and(|w| !name.contains(w)) { |
| 123 | continue; |
| 124 | } |
| 125 | let mut f13 = vk::PhysicalDeviceVulkan13Features::default(); |
| 126 | let mut f2 = vk::PhysicalDeviceFeatures2::default().push_next(&mut f13); |
| 127 | unsafe { instance.get_physical_device_features2(physical, &mut f2) }; |
| 128 | if f13.dynamic_rendering == vk::FALSE || f13.synchronization2 == vk::FALSE { |
| 129 | continue; |
| 130 | } |
| 131 | let has_push = unsafe { instance.enumerate_device_extension_properties(physical) } |
| 132 | .unwrap_or_default() |
| 133 | .iter() |
| 134 | .any(|e| e.extension_name_as_c_str().ok() == Some(ash::khr::push_descriptor::NAME)); |
| 135 | if !has_push { |
| 136 | continue; |
| 137 | } |
| 138 | let families = unsafe { instance.get_physical_device_queue_family_properties(physical) }; |
| 139 | let Some(family) = families |
| 140 | .iter() |
| 141 | .position(|f| f.queue_flags.contains(vk::QueueFlags::GRAPHICS)) |
| 142 | else { |
| 143 | continue; |
| 144 | }; |
| 145 | let rank = match props.device_type { |
| 146 | vk::PhysicalDeviceType::DISCRETE_GPU => 0, |
| 147 | vk::PhysicalDeviceType::INTEGRATED_GPU => 1, |
| 148 | vk::PhysicalDeviceType::VIRTUAL_GPU => 2, |
| 149 | _ => 3, |
| 150 | }; |
| 151 | candidates.push((rank, physical, family as u32, name)); |
| 152 | } |
| 153 | candidates.sort_by_key(|c| c.0); |
| 154 | candidates.into_iter().next().map(|(_, p, f, n)| (p, f, n)) |
| 155 | } |