atagen

monolith

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