atagen

monolith

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