atagen

monolith

public
Star0
Watch0
6118d58e7ed3f223f56ca1a7997b0f6594aec766
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git
crates/render-vk/src/device.rsRust342 lines13.2 KB
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
    /// Staging buffers kept for reuse, see `Staging`.
11
    pub staging_pool: Mutex<Vec<crate::image::Pooled>>,
12
    pub allocator: Mutex<Allocator>,
13
    pub device: Device,
14
}
15
16
impl Drop for Shared {
17
    fn drop(&mut self) {
18
        let pool = std::mem::take(
19
            self.staging_pool
20
                .get_mut()
21
                .unwrap_or_else(|e| e.into_inner()),
22
        );
23
        let allocator = self.allocator.get_mut().unwrap_or_else(|e| e.into_inner());
24
        for p in pool {
25
            unsafe { self.device.device.destroy_buffer(p.buffer, None) };
26
            let _ = allocator.free(p.allocation);
27
        }
28
    }
29
}
30
31
/// Loaders for the extensions that dmabuf import, export and fence export
32
/// need. Absent when the device lacks any of them.
33
pub(crate) struct DmabufExt {
34
    pub memory_fd: ash::khr::external_memory_fd::Device,
35
    pub fence_fd: ash::khr::external_fence_fd::Device,
36
    pub modifier: ash::ext::image_drm_format_modifier::Device,
37
}
38
39
const DMABUF_EXTENSIONS: [&std::ffi::CStr; 5] = [
40
    ash::khr::external_memory_fd::NAME,
41
    ash::ext::external_memory_dma_buf::NAME,
42
    ash::ext::image_drm_format_modifier::NAME,
43
    ash::ext::queue_family_foreign::NAME,
44
    ash::khr::external_fence_fd::NAME,
45
];
46
47
pub(crate) struct Device {
48
    pub _entry: ash::Entry,
49
    pub instance: ash::Instance,
50
    pub physical: vk::PhysicalDevice,
51
    pub device: ash::Device,
52
    pub queue: Mutex<vk::Queue>,
53
    pub queue_family: u32,
54
    pub push_descriptor: ash::khr::push_descriptor::Device,
55
    pub dmabuf: Option<DmabufExt>,
56
    pub name: String,
57
    pub drm_node: Option<u64>,
58
    /// Signalled fences kept for reuse, see `FenceInner`.
59
    pub spare_fences: Mutex<Vec<vk::Fence>>,
60
}
61
62
impl Drop for Device {
63
    fn drop(&mut self) {
64
        unsafe {
65
            let _ = self.device.device_wait_idle();
66
            for f in self
67
                .spare_fences
68
                .get_mut()
69
                .unwrap_or_else(|e| e.into_inner())
70
                .drain(..)
71
            {
72
                self.device.destroy_fence(f, None);
73
            }
74
            self.device.destroy_device(None);
75
            self.instance.destroy_instance(None);
76
        }
77
    }
78
}
79
80
impl Shared {
81
    /// Opens the first suitable device, or the one whose name contains
82
    /// `MONOLITH_VK_DEVICE` when that is set. With `node`, only the device
83
    /// behind that DRM device number qualifies.
84
    pub fn new(node: Option<u64>) -> Result<Self, Error> {
85
        let entry = unsafe { ash::Entry::load()? };
86
        let app = vk::ApplicationInfo::default()
87
            .application_name(c"monolith")
88
            .engine_name(c"monolith")
89
            .api_version(vk::API_VERSION_1_3);
90
        let wanted = std::env::var("MONOLITH_VK_DEVICE").ok();
91
        let mut filter = node.and_then(icd_filter);
92
        let (instance, physical, queue_family, name) = loop {
93
            if let Some(glob) = filter {
94
                // SAFETY: the compositor creates its renderer before starting any
95
                // other thread, so nothing reads the environment concurrently.
96
                unsafe { std::env::set_var(DRIVERS_SELECT, glob) };
97
            }
98
            let instance = unsafe {
99
                entry.create_instance(
100
                    &vk::InstanceCreateInfo::default().application_info(&app),
101
                    None,
102
                )
103
            };
104
            if filter.is_some() {
105
                // SAFETY: as above. Removing it keeps clients from inheriting it.
106
                unsafe { std::env::remove_var(DRIVERS_SELECT) };
107
            }
108
            let instance = match instance {
109
                Ok(i) => i,
110
                Err(_) if filter.take().is_some() => continue,
111
                Err(e) => return Err(e.into()),
112
            };
113
            match unsafe { pick_physical(&instance, wanted.as_deref(), node) } {
114
                Some((p, f, n)) => break (instance, p, f, n),
115
                None => {
116
                    unsafe { instance.destroy_instance(None) };
117
                    if filter.take().is_none() {
118
                        return Err(Error::NoSuitableDevice);
119
                    }
120
                }
121
            }
122
        };
123
        let priorities = [1.0];
124
        let available: Vec<_> = unsafe { instance.enumerate_device_extension_properties(physical) }
125
            .unwrap_or_default()
126
            .iter()
127
            .filter_map(|e| e.extension_name_as_c_str().ok().map(ToOwned::to_owned))
128
            .collect();
129
        let drm_node = available
130
            .iter()
131
            .any(|a| a.as_c_str() == ash::ext::physical_device_drm::NAME)
132
            .then(|| unsafe { drm_node(&instance, physical) })
133
            .flatten();
134
        let has_dmabuf = DMABUF_EXTENSIONS
135
            .iter()
136
            .all(|n| available.iter().any(|a| a.as_c_str() == *n));
137
        let mut extensions = vec![ash::khr::push_descriptor::NAME.as_ptr()];
138
        if has_dmabuf {
139
            extensions.extend(DMABUF_EXTENSIONS.iter().map(|n| n.as_ptr()));
140
        }
141
        let global_priority = [
142
            ash::khr::global_priority::NAME,
143
            ash::ext::global_priority::NAME,
144
        ]
145
        .into_iter()
146
        .find(|n| available.iter().any(|a| a.as_c_str() == *n));
147
        let mut priorities_to_try = vec![None];
148
        if let Some(name) = global_priority {
149
            extensions.push(name.as_ptr());
150
            priorities_to_try = vec![
151
                Some(vk::QueueGlobalPriorityKHR::REALTIME),
152
                Some(vk::QueueGlobalPriorityKHR::HIGH),
153
                None,
154
            ];
155
        }
156
        let mut created = Err(vk::Result::ERROR_INITIALIZATION_FAILED);
157
        for priority in priorities_to_try {
158
            let mut features13 = vk::PhysicalDeviceVulkan13Features::default()
159
                .dynamic_rendering(true)
160
                .synchronization2(true);
161
            let mut global = vk::DeviceQueueGlobalPriorityCreateInfoKHR::default()
162
                .global_priority(priority.unwrap_or(vk::QueueGlobalPriorityKHR::MEDIUM));
163
            let mut queue = vk::DeviceQueueCreateInfo::default()
164
                .queue_family_index(queue_family)
165
                .queue_priorities(&priorities);
166
            if priority.is_some() {
167
                queue = queue.push_next(&mut global);
168
            }
169
            let queues = [queue];
170
            let info = vk::DeviceCreateInfo::default()
171
                .queue_create_infos(&queues)
172
                .enabled_extension_names(&extensions)
173
                .push_next(&mut features13);
174
            created = unsafe { instance.create_device(physical, &info, None) };
175
            match &created {
176
                Ok(_) => {
177
                    tracing::info!(?priority, "queue global priority");
178
                    break;
179
                }
180
                Err(e) => tracing::debug!(?priority, "queue global priority refused: {e}"),
181
            }
182
        }
183
        let device = match created {
184
            Ok(d) => d,
185
            Err(e) => {
186
                unsafe { instance.destroy_instance(None) };
187
                return Err(e.into());
188
            }
189
        };
190
        let queue = unsafe { device.get_device_queue(queue_family, 0) };
191
        let push_descriptor = ash::khr::push_descriptor::Device::new(&instance, &device);
192
        let dmabuf = has_dmabuf.then(|| DmabufExt {
193
            memory_fd: ash::khr::external_memory_fd::Device::new(&instance, &device),
194
            fence_fd: ash::khr::external_fence_fd::Device::new(&instance, &device),
195
            modifier: ash::ext::image_drm_format_modifier::Device::new(&instance, &device),
196
        });
197
        let allocator = Allocator::new(&AllocatorCreateDesc {
198
            instance: instance.clone(),
199
            device: device.clone(),
200
            physical_device: physical,
201
            debug_settings: Default::default(),
202
            buffer_device_address: false,
203
            allocation_sizes: Default::default(),
204
        });
205
        let device = Device {
206
            _entry: entry,
207
            instance,
208
            physical,
209
            device,
210
            queue: Mutex::new(queue),
211
            queue_family,
212
            push_descriptor,
213
            dmabuf,
214
            name,
215
            drm_node,
216
            spare_fences: Mutex::new(Vec::new()),
217
        };
218
        Ok(Self {
219
            staging_pool: Mutex::new(Vec::new()),
220
            allocator: Mutex::new(allocator?),
221
            device,
222
        })
223
    }
224
}
225
226
/// Whether `physical` is the device behind DRM device number `node`, as its
227
/// primary or render node.
228
unsafe fn serves_node(instance: &ash::Instance, physical: vk::PhysicalDevice, node: u64) -> bool {
229
    let mut drm = vk::PhysicalDeviceDrmPropertiesEXT::default();
230
    let mut props = vk::PhysicalDeviceProperties2::default().push_next(&mut drm);
231
    unsafe { instance.get_physical_device_properties2(physical, &mut props) };
232
    let (major, minor) = (
233
        rustix::fs::major(node) as i64,
234
        rustix::fs::minor(node) as i64,
235
    );
236
    (drm.has_primary == vk::TRUE && (drm.primary_major, drm.primary_minor) == (major, minor))
237
        || (drm.has_render == vk::TRUE && (drm.render_major, drm.render_minor) == (major, minor))
238
}
239
240
const DRIVERS_SELECT: &str = "VK_LOADER_DRIVERS_SELECT";
241
242
/// A loader glob matching the ICD for the kernel driver behind `node`, unless
243
/// the user already picks drivers themselves. The loader keeps every ICD it
244
/// finds loaded for the instance's lifetime, and on NixOS that includes
245
/// lavapipe and its 26 MB of resident libLLVM.
246
fn icd_filter(node: u64) -> Option<&'static str> {
247
    let chosen = [
248
        DRIVERS_SELECT,
249
        "VK_LOADER_DRIVERS_DISABLE",
250
        "VK_DRIVER_FILES",
251
        "VK_ICD_FILENAMES",
252
        "VK_ADD_DRIVER_FILES",
253
    ];
254
    if chosen.iter().any(|v| std::env::var_os(v).is_some()) {
255
        return None;
256
    }
257
    let (major, minor) = (rustix::fs::major(node), rustix::fs::minor(node));
258
    let driver = std::fs::read_link(format!("/sys/dev/char/{major}:{minor}/device/driver")).ok()?;
259
    match driver.file_name()?.to_str()? {
260
        "nvidia" => Some("*nvidia*"),
261
        "amdgpu" | "radeon" => Some("*radeon*"),
262
        "i915" | "xe" => Some("*intel*"),
263
        "nouveau" => Some("*nouveau*"),
264
        _ => None,
265
    }
266
}
267
268
/// The render node of `physical`, or its primary node when it has none.
269
unsafe fn drm_node(instance: &ash::Instance, physical: vk::PhysicalDevice) -> Option<u64> {
270
    let mut drm = vk::PhysicalDeviceDrmPropertiesEXT::default();
271
    let mut props = vk::PhysicalDeviceProperties2::default().push_next(&mut drm);
272
    unsafe { instance.get_physical_device_properties2(physical, &mut props) };
273
    let node = |major: i64, minor: i64| rustix::fs::makedev(major as u32, minor as u32);
274
    if drm.has_render == vk::TRUE {
275
        Some(node(drm.render_major, drm.render_minor))
276
    } else if drm.has_primary == vk::TRUE {
277
        Some(node(drm.primary_major, drm.primary_minor))
278
    } else {
279
        None
280
    }
281
}
282
283
unsafe fn pick_physical(
284
    instance: &ash::Instance,
285
    wanted: Option<&str>,
286
    node: Option<u64>,
287
) -> Option<(vk::PhysicalDevice, u32, String)> {
288
    let devices = unsafe { instance.enumerate_physical_devices() }.ok()?;
289
    let mut candidates = Vec::new();
290
    for physical in devices {
291
        let props = unsafe { instance.get_physical_device_properties(physical) };
292
        if props.api_version < vk::API_VERSION_1_3 {
293
            continue;
294
        }
295
        let name = props
296
            .device_name_as_c_str()
297
            .unwrap_or(c"unknown")
298
            .to_string_lossy()
299
            .into_owned();
300
        if wanted.is_some_and(|w| !name.contains(w)) {
301
            continue;
302
        }
303
        let mut f13 = vk::PhysicalDeviceVulkan13Features::default();
304
        let mut f2 = vk::PhysicalDeviceFeatures2::default().push_next(&mut f13);
305
        unsafe { instance.get_physical_device_features2(physical, &mut f2) };
306
        if f13.dynamic_rendering == vk::FALSE || f13.synchronization2 == vk::FALSE {
307
            continue;
308
        }
309
        let extensions =
310
            unsafe { instance.enumerate_device_extension_properties(physical) }.unwrap_or_default();
311
        let has = |n: &std::ffi::CStr| {
312
            extensions
313
                .iter()
314
                .any(|e| e.extension_name_as_c_str().ok() == Some(n))
315
        };
316
        if !has(ash::khr::push_descriptor::NAME) {
317
            continue;
318
        }
319
        if let Some(node) = node
320
            && !(has(ash::ext::physical_device_drm::NAME)
321
                && unsafe { serves_node(instance, physical, node) })
322
        {
323
            continue;
324
        }
325
        let families = unsafe { instance.get_physical_device_queue_family_properties(physical) };
326
        let Some(family) = families
327
            .iter()
328
            .position(|f| f.queue_flags.contains(vk::QueueFlags::GRAPHICS))
329
        else {
330
            continue;
331
        };
332
        let rank = match props.device_type {
333
            vk::PhysicalDeviceType::DISCRETE_GPU => 0,
334
            vk::PhysicalDeviceType::INTEGRATED_GPU => 1,
335
            vk::PhysicalDeviceType::VIRTUAL_GPU => 2,
336
            _ => 3,
337
        };
338
        candidates.push((rank, physical, family as u32, name));
339
    }
340
    candidates.sort_by_key(|c| c.0);
341
    candidates.into_iter().next().map(|(_, p, f, n)| (p, f, n))
342
}