atagen

monolith

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