Skip to main content

gpu/
vk_inst.rs

1// vk_inst -- Vulkan instance and adapters
2//
3//   Instance::new() / with(&InstanceDesc) -> Result<Instance>
4//        jfgui_GPU=0 (or "off"): Err(Disabled); the loader from jfgui_GPU_DLL, vulkan-1.dll or
5//        libvulkan.so.1 (Err(NoLoader) without it); a loader below Vulkan 1.1: Err(Unsupported); no driver:
6//        Err(NoAdapter).  Instance extensions: VK_KHR_surface + VK_KHR_win32_surface
7//        (Android: VK_KHR_android_surface) when present, VK_EXT_debug_utils for jfgui_GPU_VALIDATE
8//        / jfgui_GPU_DEBUG.
9//   instance.adapters() -> Result<Vec<Adapter>>   every physical device: discrete, integrated,
10//        virtual, CPU, other (stable within a kind); AdapterInfo::missing says why one is
11//        unusable (Vulkan 1.1, a graphics + compute queue, timeline semaphores, dynamic
12//        rendering)
13//   instance.adapter() -> Result<Adapter>     jfgui_GPU_ADAPTER: an index into adapters() or a
14//        part of the name (case-insensitive); otherwise the first usable adapter
15//   instance.adapters_low_power(display) -> Result<Vec<Adapter>>   the adapters for showing
16//        windows (the UI's output): no CPU, virtual or unusable ones; the one that drives the
17//        display (vendor and device id, plat::win_display_adapter) first, integrated before
18//        discrete (low_power_rank); jfgui_GPU_ADAPTER as for adapter()
19//   adapter.can_present(&surface)            its queue can present to the surface
20//   adapter.info() / features() / limits() / level()   (level: the highest it supports)
21//   validation_messages() -> (errors, warnings)  counted by the debug messenger of every
22//        instance of the process (tests fail on errors)
23//
24// Validation (jfgui_GPU_VALIDATE=1 or InstanceDesc::validate): VK_LAYER_KHRONOS_validation when
25// it is installed (the Vulkan SDK; never required), with synchronization validation
26// (VK_EXT_layer_settings "validate_sync", else VK_EXT_validation_features); warnings and errors
27// go to stderr as "gpu: validation error: ...".  Without the layer a note is printed once and
28// everything runs as without the variable.  jfgui_GPU_DEBUG=1: the messenger alone (driver
29// messages, info level and up).
30
31use alloc::string::String;
32use alloc::sync::Arc;
33use alloc::vec;
34use alloc::vec::Vec;
35use core::ffi::c_void;
36use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
37
38use crate::vk_ffi as vk;
39use crate::vk_load::Lib;
40use crate::{AdapterInfo, AdapterKind, Features, GpuError, InstanceDesc, Level, Limits, Result};
41
42/// a VkResult as an error
43pub(crate) fn err(r: vk::VkResult) -> GpuError {
44    match r {
45        vk::ERROR_OUT_OF_HOST_MEMORY | vk::ERROR_OUT_OF_DEVICE_MEMORY | vk::ERROR_OUT_OF_POOL_MEMORY => {
46            GpuError::OutOfMemory
47        }
48        vk::ERROR_DEVICE_LOST => GpuError::DeviceLost,
49        vk::ERROR_SURFACE_LOST_KHR => GpuError::SurfaceLost,
50        vk::TIMEOUT => GpuError::Timeout,
51        vk::NOT_READY => GpuError::NotReady,
52        vk::ERROR_FEATURE_NOT_PRESENT => GpuError::Unsupported("a device feature"),
53        vk::ERROR_EXTENSION_NOT_PRESENT => GpuError::Unsupported("an extension"),
54        vk::ERROR_FORMAT_NOT_SUPPORTED => GpuError::Unsupported("the format"),
55        vk::ERROR_INCOMPATIBLE_DRIVER => GpuError::NoAdapter,
56        _ => GpuError::Failed(r),
57    }
58}
59
60pub(crate) fn env_on(name: &str) -> bool {
61    plat::env(name).is_some_and(|v| v == "1" || v.eq_ignore_ascii_case("on") || v.eq_ignore_ascii_case("true"))
62}
63
64static VAL_ERRORS: AtomicU32 = AtomicU32::new(0);
65static VAL_WARNINGS: AtomicU32 = AtomicU32::new(0);
66static NO_LAYER_NOTE: AtomicBool = AtomicBool::new(false);
67
68/// validation errors and warnings reported by the debug messenger so far (every instance of
69/// the process; 0, 0 without jfgui_GPU_VALIDATE / jfgui_GPU_DEBUG)
70pub fn validation_messages() -> (u32, u32) {
71    (VAL_ERRORS.load(Ordering::Relaxed), VAL_WARNINGS.load(Ordering::Relaxed))
72}
73
74unsafe extern "system" fn debug_message(sev: u32, types: u32, data: *const vk::DebugUtilsMessengerCallbackDataEXT,
75                                        _user: *mut c_void) -> vk::Bool32 {
76    let what = if sev & vk::DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT != 0 {
77        if types & vk::DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT != 0 {
78            VAL_ERRORS.fetch_add(1, Ordering::Relaxed);
79        }
80        "error"
81    } else if sev & vk::DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT != 0 {
82        VAL_WARNINGS.fetch_add(1, Ordering::Relaxed);
83        "warning"
84    } else {
85        "info"
86    };
87    let kind = if types & vk::DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT != 0 {
88        "validation"
89    } else if types & vk::DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT != 0 {
90        "performance"
91    } else {
92        "driver"
93    };
94    let mut s = String::with_capacity(256);
95    s.push_str("gpu: ");
96    s.push_str(kind);
97    s.push(' ');
98    s.push_str(what);
99    s.push_str(": ");
100    // SAFETY: the messenger passes valid callback data whose message is a C string.
101    unsafe {
102        if !data.is_null() && !(*data).p_message.is_null() {
103            let p = (*data).p_message;
104            let mut n = 0;
105            while *p.add(n) != 0 {
106                n += 1;
107            }
108            s.push_str(core::str::from_utf8(core::slice::from_raw_parts(p, n)).unwrap_or("?"));
109        }
110    }
111    s.push('\n');
112    plat::write_stderr(s.as_bytes());
113    0
114}
115
116pub(crate) struct InstShared {
117    pub inst: vk::Instance,
118    pub f: vk::InstanceFns,
119    /// VK_KHR_surface and the platform's surface extension are enabled
120    pub surface: bool,
121    pub validate: bool,
122    messenger: vk::Handle,
123    _lib: Lib, // last: unloaded after the instance is destroyed
124}
125
126impl Drop for InstShared {
127    fn drop(&mut self) {
128        // SAFETY: every child object holds an Arc of this instance, so none is left.
129        unsafe {
130            if self.messenger != 0 {
131                if let Some(d) = self.f.destroy_debug_utils_messenger_ext {
132                    d(self.inst, self.messenger, core::ptr::null());
133                }
134            }
135            (self.f.destroy_instance)(self.inst, core::ptr::null());
136        }
137    }
138}
139
140/// the GPU API of the system (Vulkan); create adapters and surfaces from it
141#[derive(Clone)]
142pub struct Instance {
143    pub(crate) sh: Arc<InstShared>,
144}
145
146/// the names of the extensions (of a layer: name) the loader offers
147fn instance_extensions(lf: &vk::LoaderFns, layer: *const u8) -> Vec<vk::ExtensionProperties> {
148    let mut n = 0u32;
149    // SAFETY: the two-call enumeration with a correctly sized buffer.
150    unsafe {
151        if (lf.enumerate_instance_extension_properties)(layer, &mut n, core::ptr::null_mut()) != vk::SUCCESS {
152            return Vec::new();
153        }
154        let mut v = vec![vk::Zero::zero(); n as usize];
155        let r = (lf.enumerate_instance_extension_properties)(layer, &mut n, v.as_mut_ptr());
156        if r != vk::SUCCESS && r != vk::INCOMPLETE {
157            return Vec::new();
158        }
159        v.truncate(n as usize);
160        v
161    }
162}
163
164fn has_ext(list: &[vk::ExtensionProperties], name: &[u8]) -> bool {
165    let name = &name[..name.len() - 1];
166    list.iter().any(|e| vk::cstr(&e.extension_name).as_bytes() == name)
167}
168
169const LAYER_VALIDATION: &[u8] = b"VK_LAYER_KHRONOS_validation\0";
170
171impl Instance {
172    /// the system's GPU API with the default settings
173    pub fn new() -> Result<Instance> {
174        Instance::with(&InstanceDesc::DEFAULT)
175    }
176
177    pub fn with(desc: &InstanceDesc) -> Result<Instance> {
178        if plat::env("jfgui_GPU").is_some_and(|v| v == "0" || v.eq_ignore_ascii_case("off")) {
179            return Err(GpuError::Disabled);
180        }
181        let dll = plat::env("jfgui_GPU_DLL").filter(|s| !s.is_empty());
182        let lib = Lib::open(dll.as_deref().unwrap_or(crate::vk_load::LOADER))?;
183        let lf = vk::LoaderFns::load(&|n| lib.gipa(0, n)).map_err(|_| GpuError::NoLoader)?;
184        let mut ver = vk::make_api_version(1, 0, 0);
185        if let Some(e) = lf.enumerate_instance_version {
186            // SAFETY: plain out-parameter.
187            unsafe { e(&mut ver) };
188        }
189        if ver < vk::API_VERSION_1_1 {
190            return Err(GpuError::Unsupported("a Vulkan 1.1 loader"));
191        }
192        let exts = instance_extensions(&lf, core::ptr::null());
193        let mut want = desc.validate || env_on("jfgui_GPU_VALIDATE");
194        let debug = env_on("jfgui_GPU_DEBUG");
195        if want && !layer_present(&lf) {
196            if !NO_LAYER_NOTE.swap(true, Ordering::Relaxed) {
197                plat::write_stderr(b"gpu: jfgui_GPU_VALIDATE=1 but VK_LAYER_KHRONOS_validation is not installed: no validation\n");
198            }
199            want = false;
200        }
201        let layer_exts = if want { instance_extensions(&lf, LAYER_VALIDATION.as_ptr()) } else { Vec::new() };
202        let mut names: Vec<*const u8> = Vec::new();
203        // the target's surfaces (Linux: none, vk_dmabuf.rs)
204        let surface_ext: Option<&[u8]> = if cfg!(windows) {
205            Some(b"VK_KHR_win32_surface\0")
206        } else if cfg!(target_os = "android") {
207            Some(b"VK_KHR_android_surface\0")
208        } else {
209            None
210        };
211        let surface = surface_ext.is_some_and(|e| has_ext(&exts, b"VK_KHR_surface\0") && has_ext(&exts, e));
212        if surface {
213            names.push(b"VK_KHR_surface\0".as_ptr());
214            names.extend(surface_ext.map(<[u8]>::as_ptr));
215        }
216        let du = (want || debug) && (has_ext(&exts, b"VK_EXT_debug_utils\0") || has_ext(&layer_exts, b"VK_EXT_debug_utils\0"));
217        if du {
218            names.push(b"VK_EXT_debug_utils\0".as_ptr());
219        }
220        let settings_ext = want && has_ext(&layer_exts, b"VK_EXT_layer_settings\0");
221        let features_ext = want && !settings_ext && has_ext(&layer_exts, b"VK_EXT_validation_features\0");
222        if settings_ext {
223            names.push(b"VK_EXT_layer_settings\0".as_ptr());
224        }
225        if features_ext {
226            names.push(b"VK_EXT_validation_features\0".as_ptr());
227        }
228        // synchronization validation: "validate_sync" of the layer (or the older feature list)
229        let on: vk::Bool32 = 1;
230        let setting = vk::LayerSettingEXT {
231            p_layer_name: LAYER_VALIDATION.as_ptr(),
232            p_setting_name: b"validate_sync\0".as_ptr(),
233            type_: vk::LAYER_SETTING_TYPE_BOOL32_EXT,
234            value_count: 1,
235            p_values: &on as *const u32 as *const c_void,
236        };
237        let settings = vk::LayerSettingsCreateInfoEXT {
238            s_type: vk::STRUCTURE_TYPE_LAYER_SETTINGS_CREATE_INFO_EXT,
239            setting_count: 1,
240            p_settings: &setting,
241            ..vk::Zero::zero()
242        };
243        let sync = vk::VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT;
244        let vfeatures = vk::ValidationFeaturesEXT {
245            s_type: vk::STRUCTURE_TYPE_VALIDATION_FEATURES_EXT,
246            enabled_validation_feature_count: 1,
247            p_enabled_validation_features: &sync,
248            ..vk::Zero::zero()
249        };
250        let mut app_name: Vec<u8> = desc.app_name.bytes().filter(|&b| b != 0).collect();
251        app_name.push(0);
252        let app = vk::ApplicationInfo {
253            s_type: vk::STRUCTURE_TYPE_APPLICATION_INFO,
254            p_application_name: app_name.as_ptr(),
255            p_engine_name: b"JFLib gpu\0".as_ptr(),
256            engine_version: vk::make_api_version(0, rt::VERSION_MAJOR as u32, rt::VERSION_MINOR as u32),
257            api_version: vk::API_VERSION_1_3,
258            ..vk::Zero::zero()
259        };
260        let layers = [LAYER_VALIDATION.as_ptr()];
261        let info = vk::InstanceCreateInfo {
262            s_type: vk::STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
263            p_next: if settings_ext {
264                &settings as *const _ as *const c_void
265            } else if features_ext {
266                &vfeatures as *const _ as *const c_void
267            } else {
268                core::ptr::null()
269            },
270            p_application_info: &app,
271            enabled_layer_count: want as u32,
272            pp_enabled_layer_names: layers.as_ptr(),
273            enabled_extension_count: names.len() as u32,
274            pp_enabled_extension_names: names.as_ptr(),
275            ..vk::Zero::zero()
276        };
277        let mut inst: vk::Instance = 0;
278        // SAFETY: every pointer of info lives until the call returns.
279        let r = unsafe { (lf.create_instance)(&info, core::ptr::null(), &mut inst) };
280        if r != vk::SUCCESS {
281            return Err(err(r));
282        }
283        let f = match vk::InstanceFns::load(&|n| lib.gipa(inst, n)) {
284            Ok(f) => f,
285            Err(_) => {
286                let d = lib.gipa(inst, b"vkDestroyInstance\0");
287                if d != 0 {
288                    // SAFETY: vkDestroyInstance of the instance just created.
289                    unsafe {
290                        core::mem::transmute::<usize, unsafe extern "system" fn(vk::Instance, *const c_void)>(d)(
291                            inst,
292                            core::ptr::null(),
293                        )
294                    };
295                }
296                return Err(GpuError::Unsupported("a Vulkan 1.1 instance"));
297            }
298        };
299        let mut messenger = 0;
300        if let (true, Some(create)) = (du, f.create_debug_utils_messenger_ext) {
301            let mut sev = vk::DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | vk::DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
302            if debug {
303                sev |= vk::DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT;
304            }
305            let mi = vk::DebugUtilsMessengerCreateInfoEXT {
306                s_type: vk::STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
307                message_severity: sev,
308                message_type: vk::DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
309                    | vk::DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
310                    | vk::DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
311                pfn_user_callback: Some(debug_message),
312                ..vk::Zero::zero()
313            };
314            // SAFETY: a valid create info; the callback is a static function.
315            if unsafe { create(inst, &mi, core::ptr::null(), &mut messenger) } != vk::SUCCESS {
316                messenger = 0;
317            }
318        }
319        Ok(Instance { sh: Arc::new(InstShared { inst, f, surface, validate: want, messenger, _lib: lib }) })
320    }
321
322    /// the validation layer is active on this instance
323    pub fn validating(&self) -> bool {
324        self.sh.validate
325    }
326
327    /// every GPU of the system, discrete ones first (AdapterInfo::missing: unusable ones)
328    pub fn adapters(&self) -> Result<Vec<Adapter>> {
329        let sh = &self.sh;
330        let mut n = 0u32;
331        // SAFETY: the two-call enumeration.
332        let r = unsafe { (sh.f.enumerate_physical_devices)(sh.inst, &mut n, core::ptr::null_mut()) };
333        if r != vk::SUCCESS {
334            return Err(err(r));
335        }
336        let mut pds = vec![0usize; n as usize];
337        // SAFETY: pds holds n handles.
338        let r = unsafe { (sh.f.enumerate_physical_devices)(sh.inst, &mut n, pds.as_mut_ptr()) };
339        if r != vk::SUCCESS && r != vk::INCOMPLETE {
340            return Err(err(r));
341        }
342        pds.truncate(n as usize);
343        let rank = |k: AdapterKind| match k {
344            AdapterKind::Discrete => 0,
345            AdapterKind::Integrated => 1,
346            AdapterKind::Virtual => 2,
347            AdapterKind::Cpu => 3,
348            AdapterKind::Other => 4,
349        };
350        Ok(by_rank(pds.into_iter().map(|pd| probe(sh, pd)).map(|a| (rank(a.info.kind), a)).collect()))
351    }
352
353    /// (the browser: adapter and device come asynchronously; here they are there)
354    pub fn prepare(&self) -> Result<bool> {
355        Ok(true)
356    }
357
358    /// the adapter to use: jfgui_GPU_ADAPTER (index or name part), else the first usable one
359    pub fn adapter(&self) -> Result<Adapter> {
360        let list = self.adapters()?;
361        if let Some(sel) = plat::env("jfgui_GPU_ADAPTER").filter(|s| !s.is_empty()) {
362            let pick = match sel.parse::<usize>() {
363                Ok(i) => list.into_iter().nth(i),
364                Err(_) => {
365                    let want = sel.to_ascii_lowercase();
366                    list.into_iter().find(|a| a.info.name.to_ascii_lowercase().contains(&want))
367                }
368            };
369            let a = pick.ok_or(GpuError::NoAdapter)?;
370            return match a.info.missing {
371                Some(why) => Err(GpuError::Unsupported(why)),
372                None => Ok(a),
373            };
374        }
375        list.into_iter().find(|a| a.info.missing.is_none()).ok_or(GpuError::NoAdapter)
376    }
377
378    /// the adapters for showing windows without waking more hardware than needed, the most
379    /// frugal first: only integrated and discrete GPUs (a CPU or virtual adapter is no gain over
380    /// the CPU path), the one that drives the display (vendor id, device id) before the others,
381    /// integrated before discrete.  jfgui_GPU_ADAPTER picks one as in adapter().  Programs that
382    /// want a GPU for themselves keep adapter().
383    pub fn adapters_low_power(&self, display: Option<(u32, u32)>) -> Result<Vec<Adapter>> {
384        if plat::env("jfgui_GPU_ADAPTER").is_some_and(|s| !s.is_empty()) {
385            return self.adapter().map(|a| alloc::vec![a]);
386        }
387        let list: Vec<(u32, Adapter)> = self
388            .adapters()?
389            .into_iter()
390            .filter(|a| a.info.missing.is_none())
391            .filter_map(|a| {
392                let drives = display.is_some_and(|(v, d)| a.info.vendor_id == v && a.info.device_id == d);
393                low_power_rank(a.info.kind, drives).map(|r| (r, a))
394            })
395            .collect();
396        if list.is_empty() {
397            return Err(GpuError::NoAdapter);
398        }
399        Ok(by_rank(list))
400    }
401}
402
403/// the adapters by rank, stable within a rank (a handful: every program with a UI links this,
404/// a general sort of these large records takes 14 KiB of code)
405fn by_rank(mut list: Vec<(u32, Adapter)>) -> Vec<Adapter> {
406    let mut out = Vec::with_capacity(list.len());
407    while !list.is_empty() {
408        let mut best = 0;
409        for i in 1..list.len() {
410            if list[i].0 < list[best].0 {
411                best = i;
412            }
413        }
414        out.push(list.remove(best).1);
415    }
416    out
417}
418
419/// the order of adapters for showing windows, lower first (None: not for this): the adapter that
420/// drives the display needs no copy across GPUs and is awake anyway; an integrated one before a
421/// discrete one that would have to wake up
422pub fn low_power_rank(kind: AdapterKind, drives_display: bool) -> Option<u32> {
423    let k = match kind {
424        AdapterKind::Integrated => 0,
425        AdapterKind::Discrete => 1,
426        _ => return None,
427    };
428    Some(if drives_display { k } else { 2 + k })
429}
430
431fn layer_present(lf: &vk::LoaderFns) -> bool {
432    let mut n = 0u32;
433    // SAFETY: the two-call enumeration.
434    unsafe {
435        if (lf.enumerate_instance_layer_properties)(&mut n, core::ptr::null_mut()) != vk::SUCCESS {
436            return false;
437        }
438        let mut v: Vec<vk::LayerProperties> = vec![vk::Zero::zero(); n as usize];
439        let r = (lf.enumerate_instance_layer_properties)(&mut n, v.as_mut_ptr());
440        if r != vk::SUCCESS && r != vk::INCOMPLETE {
441            return false;
442        }
443        v.truncate(n as usize);
444        let name = &LAYER_VALIDATION[..LAYER_VALIDATION.len() - 1];
445        v.iter().any(|l| vk::cstr(&l.layer_name).as_bytes() == name)
446    }
447}
448
449/// device extensions the backend uses
450#[derive(Clone, Copy, Default)]
451pub(crate) struct Exts {
452    pub swapchain: bool,
453    pub timeline_khr: bool,
454    pub dynamic_rendering_khr: bool,
455    pub depth_stencil_resolve_khr: bool,
456    pub create_renderpass2_khr: bool,
457    pub driver_properties_khr: bool,
458    /// images exported as dma-bufs with a DRM format modifier (Linux, vk_dmabuf.rs)
459    pub dmabuf: bool,
460    pub queue_foreign: bool,
461    #[cfg_attr(not(jfgui_glibc), allow(dead_code))]
462    pub physical_drm: bool,
463}
464
465/// a GPU of the system
466#[derive(Clone)]
467pub struct Adapter {
468    pub(crate) inst: Arc<InstShared>,
469    pub(crate) pd: vk::PhysicalDevice,
470    pub(crate) info: AdapterInfo,
471    pub(crate) features: Features,
472    pub(crate) limits: Limits,
473    pub(crate) voll: bool,
474    pub(crate) api: u32,
475    pub(crate) family: u32,
476    pub(crate) ts_bits: u32,
477    pub(crate) exts: Exts,
478    pub(crate) f10: [u32; vk::F10_COUNT],
479    pub(crate) f11: [u32; vk::F11_COUNT],
480    pub(crate) f12: [u32; vk::F12_COUNT],
481    pub(crate) f13: [u32; vk::F13_COUNT],
482    pub(crate) mem: vk::PhysicalDeviceMemoryProperties,
483    pub(crate) raw: vk::PhysicalDeviceLimits,
484}
485
486impl Adapter {
487    pub fn info(&self) -> &AdapterInfo {
488        &self.info
489    }
490    /// what a device of the highest level would have
491    pub fn features(&self) -> &Features {
492        &self.features
493    }
494    pub fn limits(&self) -> &Limits {
495        &self.limits
496    }
497    /// the highest level a device of this adapter supports
498    pub fn level(&self) -> Level {
499        if self.voll { Level::Voll } else { Level::Basis }
500    }
501    /// a device of this adapter can show images on the surface
502    #[cfg(jfgui_glibc)]
503    pub fn can_present(&self, surface: &crate::Surface) -> bool {
504        crate::vk_swap::can_present(self, surface)
505    }
506
507    /// a device of this adapter can show images on the surface
508    #[cfg(not(jfgui_glibc))]
509    pub fn can_present(&self, surface: &crate::Surface) -> bool {
510        let Some(support) = self.inst.f.get_physical_device_surface_support_khr else { return false };
511        if !self.exts.swapchain {
512            return false;
513        }
514        let mut ok = 0;
515        // SAFETY: plain query with a queue family of this adapter and a live surface.
516        let r = unsafe { support(self.pd, self.family, surface.0.h, &mut ok) };
517        r == vk::SUCCESS && ok != 0
518    }
519}
520
521fn vendor_name(id: u32) -> &'static str {
522    match id {
523        0x10DE => "NVIDIA",
524        0x1002 | 0x1022 => "AMD",
525        0x8086 => "Intel",
526        0x13B5 => "ARM",
527        0x5143 => "Qualcomm",
528        0x1010 => "Imagination",
529        0x106B => "Apple",
530        0x1414 => "Microsoft",
531        0x10005 => "Mesa",
532        _ => "unknown",
533    }
534}
535
536fn driver_version(vendor: u32, v: u32) -> String {
537    match vendor {
538        0x10DE => alloc::format!("{}.{:02}", v >> 22, (v >> 14) & 0xFF),
539        0x8086 if cfg!(windows) => alloc::format!("{}.{}", v >> 14, v & 0x3FFF),
540        _ => alloc::format!("{}.{}.{}", vk::api_major(v), vk::api_minor(v), vk::api_patch(v)),
541    }
542}
543
544fn device_extensions(sh: &InstShared, pd: vk::PhysicalDevice) -> Vec<vk::ExtensionProperties> {
545    let mut n = 0u32;
546    // SAFETY: the two-call enumeration.
547    unsafe {
548        if (sh.f.enumerate_device_extension_properties)(pd, core::ptr::null(), &mut n, core::ptr::null_mut()) != vk::SUCCESS {
549            return Vec::new();
550        }
551        let mut v = vec![vk::Zero::zero(); n as usize];
552        let r = (sh.f.enumerate_device_extension_properties)(pd, core::ptr::null(), &mut n, v.as_mut_ptr());
553        if r != vk::SUCCESS && r != vk::INCOMPLETE {
554            return Vec::new();
555        }
556        v.truncate(n as usize);
557        v
558    }
559}
560
561/// everything about one physical device
562fn probe(sh: &Arc<InstShared>, pd: vk::PhysicalDevice) -> Adapter {
563    let f = &sh.f;
564    let dexts = device_extensions(sh, pd);
565    let ext = |n: &[u8]| has_ext(&dexts, n);
566    let mut exts = Exts {
567        swapchain: sh.surface && ext(b"VK_KHR_swapchain\0"),
568        timeline_khr: ext(b"VK_KHR_timeline_semaphore\0"),
569        dynamic_rendering_khr: ext(b"VK_KHR_dynamic_rendering\0"),
570        depth_stencil_resolve_khr: ext(b"VK_KHR_depth_stencil_resolve\0"),
571        create_renderpass2_khr: ext(b"VK_KHR_create_renderpass2\0"),
572        driver_properties_khr: ext(b"VK_KHR_driver_properties\0"),
573        dmabuf: cfg!(jfgui_glibc)
574            && ext(b"VK_KHR_external_memory_fd\0")
575            && ext(b"VK_EXT_external_memory_dma_buf\0")
576            && ext(b"VK_EXT_image_drm_format_modifier\0"),
577        queue_foreign: ext(b"VK_EXT_queue_family_foreign\0"),
578        physical_drm: ext(b"VK_EXT_physical_device_drm\0"),
579    };
580    // properties: base, driver, subgroups, descriptor indexing
581    let mut dip: vk::PhysicalDeviceDescriptorIndexingProperties = vk::Zero::zero();
582    dip.s_type = vk::STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES;
583    let mut sub: vk::PhysicalDeviceSubgroupProperties = vk::Zero::zero();
584    sub.s_type = vk::STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES;
585    let mut size_ctl: vk::PhysicalDeviceSubgroupSizeControlProperties = vk::Zero::zero();
586    size_ctl.s_type = vk::STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_PROPERTIES;
587    let mut drv: vk::PhysicalDeviceDriverProperties = vk::Zero::zero();
588    drv.s_type = vk::STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;
589    let mut p2: vk::PhysicalDeviceProperties2 = vk::Zero::zero();
590    p2.s_type = vk::STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
591    // the API version decides the chain: read the base properties first
592    // SAFETY: a valid structure without chain.
593    unsafe { (f.get_physical_device_properties2)(pd, &mut p2) };
594    let api = p2.properties.api_version;
595    let v12 = api >= vk::API_VERSION_1_2;
596    // (VK_EXT_image_drm_format_modifier needs it, core since 1.2)
597    exts.dmabuf = exts.dmabuf && (v12 || ext(b"VK_KHR_image_format_list\0"));
598    let has_drv = v12 || exts.driver_properties_khr;
599    if api >= vk::API_VERSION_1_3 {
600        dip.p_next = &mut size_ctl as *mut _ as *mut c_void;
601    }
602    sub.p_next = if v12 { &mut dip as *mut _ as *mut c_void } else { core::ptr::null_mut() };
603    drv.p_next = &mut sub as *mut _ as *mut c_void;
604    p2.p_next = if has_drv { &mut drv as *mut _ as *mut c_void } else { &mut sub as *mut _ as *mut c_void };
605    // SAFETY: the chain's structures live on this stack frame.
606    unsafe { (f.get_physical_device_properties2)(pd, &mut p2) };
607    let props = p2.properties;
608    let lim = props.limits;
609
610    // features: Vulkan 1.0 + 1.1/1.2/1.3 structures, or the extension structures on 1.1
611    let mut f13: vk::PhysicalDeviceVulkan13Features = vk::Zero::zero();
612    f13.s_type = vk::STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
613    let mut f12: vk::PhysicalDeviceVulkan12Features = vk::Zero::zero();
614    f12.s_type = vk::STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
615    let mut f11: vk::PhysicalDeviceVulkan11Features = vk::Zero::zero();
616    f11.s_type = vk::STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES;
617    let mut dynr: vk::PhysicalDeviceOneFeature = vk::Zero::zero();
618    dynr.s_type = vk::STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES;
619    let mut tl: vk::PhysicalDeviceOneFeature = vk::Zero::zero();
620    tl.s_type = vk::STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES;
621    let mut f2: vk::PhysicalDeviceFeatures2 = vk::Zero::zero();
622    f2.s_type = vk::STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
623    let v13 = api >= vk::API_VERSION_1_3;
624    // linked from the tail: no structure is written after a pointer to it exists
625    if v12 {
626        if v13 {
627            f12.p_next = &mut f13 as *mut _ as *mut c_void;
628        } else if exts.dynamic_rendering_khr {
629            f12.p_next = &mut dynr as *mut _ as *mut c_void;
630        }
631        f11.p_next = &mut f12 as *mut _ as *mut c_void;
632        f2.p_next = &mut f11 as *mut _ as *mut c_void;
633    } else {
634        let mut head: *mut c_void = core::ptr::null_mut();
635        if exts.dynamic_rendering_khr {
636            head = &mut dynr as *mut _ as *mut c_void;
637        }
638        if exts.timeline_khr {
639            tl.p_next = head;
640            head = &mut tl as *mut _ as *mut c_void;
641        }
642        f2.p_next = head;
643    }
644    // SAFETY: the chain's structures live on this stack frame.
645    unsafe { (f.get_physical_device_features2)(pd, &mut f2) };
646    if !v12 {
647        f12.f[vk::F12_TIMELINE_SEMAPHORE] = tl.f;
648    }
649    if !v13 {
650        f13.f[vk::F13_DYNAMIC_RENDERING] = dynr.f;
651    }
652    if v13 {
653        exts.dynamic_rendering_khr = false;
654    }
655    if v12 {
656        exts.timeline_khr = false;
657        exts.depth_stencil_resolve_khr = false;
658        exts.create_renderpass2_khr = false;
659    }
660
661    // the queue: the first family with graphics and compute
662    let mut nq = 0u32;
663    // SAFETY: the two-call enumeration.
664    unsafe { (f.get_physical_device_queue_family_properties)(pd, &mut nq, core::ptr::null_mut()) };
665    let mut qf: Vec<vk::QueueFamilyProperties> = vec![vk::Zero::zero(); nq as usize];
666    // SAFETY: qf holds nq entries.
667    unsafe { (f.get_physical_device_queue_family_properties)(pd, &mut nq, qf.as_mut_ptr()) };
668    qf.truncate(nq as usize);
669    let gc = vk::QUEUE_GRAPHICS_BIT | vk::QUEUE_COMPUTE_BIT;
670    let family = qf.iter().position(|q| q.queue_flags & gc == gc && q.queue_count > 0);
671    let ts_bits = family.map_or(0, |i| qf[i].timestamp_valid_bits);
672
673    let mut mem: vk::PhysicalDeviceMemoryProperties = vk::Zero::zero();
674    // SAFETY: plain out-parameter.
675    unsafe { (f.get_physical_device_memory_properties)(pd, &mut mem) };
676
677    let b10 = |i: usize| f2.f[i] != 0;
678    let b11 = |i: usize| v12 && f11.f[i] != 0;
679    let b12 = |i: usize| f12.f[i] != 0;
680    // on 1.1 the extension also needs VK_KHR_create_renderpass2 and VK_KHR_depth_stencil_resolve
681    let dynamic_rendering = f13.f[vk::F13_DYNAMIC_RENDERING] != 0
682        && (v12 || exts.create_renderpass2_khr && exts.depth_stencil_resolve_khr);
683    let timeline = b12(vk::F12_TIMELINE_SEMAPHORE);
684    let missing = if api < vk::API_VERSION_1_1 {
685        Some("Vulkan 1.1")
686    } else if family.is_none() {
687        Some("a graphics + compute queue")
688    } else if !timeline {
689        Some("timeline semaphores")
690    } else if !dynamic_rendering {
691        Some("dynamic rendering")
692    } else {
693        None
694    };
695    let need_ops = vk::SUBGROUP_FEATURE_BASIC_BIT | vk::SUBGROUP_FEATURE_VOTE_BIT | vk::SUBGROUP_FEATURE_ARITHMETIC_BIT
696        | vk::SUBGROUP_FEATURE_BALLOT_BIT | vk::SUBGROUP_FEATURE_SHUFFLE_BIT;
697    let subgroups = sub.supported_stages & vk::SHADER_STAGE_COMPUTE_BIT != 0 && sub.supported_operations & need_ops == need_ops;
698    let webgpu_ops = need_ops | vk::SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT | vk::SUBGROUP_FEATURE_QUAD_BIT;
699    let webgpu_stages = vk::SHADER_STAGE_COMPUTE_BIT | vk::SHADER_STAGE_FRAGMENT_BIT;
700    let subgroups_webgpu = sub.supported_stages & webgpu_stages == webgpu_stages && sub.supported_operations & webgpu_ops == webgpu_ops;
701    let mut subgroup_stages = 0;
702    for (bit, stage) in [(vk::SHADER_STAGE_COMPUTE_BIT, crate::subgroup_stage::COMPUTE), (vk::SHADER_STAGE_FRAGMENT_BIT, crate::subgroup_stage::FRAGMENT), (vk::SHADER_STAGE_VERTEX_BIT, crate::subgroup_stage::VERTEX)] {
703        if sub.supported_stages & bit != 0 {
704            subgroup_stages |= stage;
705        }
706    }
707    let subgroup_size_control = v13 && f13.f[vk::F13_SUBGROUP_SIZE_CONTROL] != 0 && size_ctl.required_subgroup_size_stages & vk::SHADER_STAGE_COMPUTE_BIT != 0;
708    let bindless = v12
709        && [
710            vk::F12_DESCRIPTOR_INDEXING,
711            vk::F12_RUNTIME_DESCRIPTOR_ARRAY,
712            vk::F12_DESCRIPTOR_BINDING_PARTIALLY_BOUND,
713            vk::F12_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING,
714            vk::F12_DESCRIPTOR_BINDING_SAMPLED_IMAGE_UPDATE_AFTER_BIND,
715            vk::F12_DESCRIPTOR_BINDING_STORAGE_IMAGE_UPDATE_AFTER_BIND,
716            vk::F12_DESCRIPTOR_BINDING_STORAGE_BUFFER_UPDATE_AFTER_BIND,
717            vk::F12_SHADER_SAMPLED_IMAGE_ARRAY_NON_UNIFORM_INDEXING,
718            vk::F12_SHADER_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING,
719        ]
720        .iter()
721        .all(|&i| b12(i))
722        && b10(vk::F10_SHADER_SAMPLED_IMAGE_ARRAY_DYNAMIC_INDEXING)
723        && b10(vk::F10_SHADER_STORAGE_BUFFER_ARRAY_DYNAMIC_INDEXING);
724    let features = Features {
725        timestamps: ts_bits > 0 && lim.timestamp_compute_and_graphics != 0,
726        multi_draw_indirect: b10(vk::F10_MULTI_DRAW_INDIRECT),
727        draw_indirect_first_instance: b10(vk::F10_DRAW_INDIRECT_FIRST_INSTANCE),
728        draw_indirect_count: b12(vk::F12_DRAW_INDIRECT_COUNT) && v12,
729        bindless,
730        subgroups,
731        subgroups_webgpu,
732        subgroup_stages,
733        subgroup_ops: sub.supported_operations,
734        subgroup_size_control,
735        subgroup_extended_types: v12 && b12(vk::F12_SHADER_SUBGROUP_EXTENDED_TYPES),
736        draw_parameters: b11(vk::F11_SHADER_DRAW_PARAMETERS),
737        shader_f16: v12 && b12(vk::F12_SHADER_FLOAT16),
738        shader_f64: b10(vk::F10_SHADER_FLOAT64),
739        shader_i16: b10(vk::F10_SHADER_INT16),
740        shader_i64: b10(vk::F10_SHADER_INT64),
741        shader_i8: v12 && b12(vk::F12_SHADER_INT8),
742        storage_16bit: b11(vk::F11_STORAGE_BUFFER_16BIT_ACCESS),
743        uniform_16bit: b11(vk::F11_UNIFORM_AND_STORAGE_BUFFER_16BIT_ACCESS),
744        push_constant_16bit: b11(vk::F11_STORAGE_PUSH_CONSTANT16),
745        uniform_standard_layout: v12 && b12(vk::F12_UNIFORM_BUFFER_STANDARD_LAYOUT),
746        demote: v13 && f13.f[vk::F13_SHADER_DEMOTE_TO_HELPER_INVOCATION] != 0,
747        clip_distances: b10(vk::F10_SHADER_CLIP_DISTANCE),
748        primitive_index: b10(vk::F10_GEOMETRY_SHADER),
749        sample_shading: b10(vk::F10_SAMPLE_RATE_SHADING),
750        storage_8bit: v12 && b12(vk::F12_STORAGE_BUFFER_8BIT_ACCESS),
751        texture_bc: b10(vk::F10_TEXTURE_COMPRESSION_BC),
752        anisotropy: b10(vk::F10_SAMPLER_ANISOTROPY),
753        depth_clamp: b10(vk::F10_DEPTH_CLAMP),
754        vertex_stores: b10(vk::F10_VERTEX_PIPELINE_STORES_AND_ATOMICS),
755        fragment_stores: b10(vk::F10_FRAGMENT_STORES_AND_ATOMICS),
756        storage_write_without_format: b10(vk::F10_SHADER_STORAGE_IMAGE_WRITE_WITHOUT_FORMAT),
757        buffer_address: v12 && b12(vk::F12_BUFFER_DEVICE_ADDRESS),
758        swapchain: exts.swapchain || (exts.dmabuf && props.device_type != vk::PHYSICAL_DEVICE_TYPE_CPU),
759        mesh_shader: ext(b"VK_EXT_mesh_shader\0"),
760        ray_query: ext(b"VK_KHR_ray_query\0") && ext(b"VK_KHR_acceleration_structure\0"),
761        ray_pipeline: ext(b"VK_KHR_ray_tracing_pipeline\0") && ext(b"VK_KHR_acceleration_structure\0"),
762    };
763    let voll = missing.is_none()
764        && v13
765        && bindless
766        && features.draw_indirect_count
767        && features.multi_draw_indirect
768        && features.draw_indirect_first_instance
769        && features.draw_parameters
770        && subgroups;
771    let uab = |set: usize, stage: usize| if bindless { dip.v[set].min(dip.v[stage]) } else { 0 };
772    let device_memory = (0..mem.memory_heap_count.min(16) as usize)
773        .filter(|&i| mem.memory_heaps[i].flags & 1 != 0)
774        .map(|i| mem.memory_heaps[i].size)
775        .sum();
776    let limits = Limits {
777        max_texture_2d: lim.max_image_dimension2_d,
778        max_texture_3d: lim.max_image_dimension3_d,
779        max_texture_layers: lim.max_image_array_layers,
780        max_storage_binding: lim.max_storage_buffer_range,
781        max_uniform_binding: lim.max_uniform_buffer_range,
782        max_push_constants: lim.max_push_constants_size,
783        max_bind_groups: lim.max_bound_descriptor_sets.min(crate::BIND_GROUPS_MAX),
784        max_compute_workgroup_size: lim.max_compute_work_group_size,
785        max_compute_invocations: lim.max_compute_work_group_invocations,
786        max_compute_workgroups: lim.max_compute_work_group_count,
787        max_compute_shared_memory: lim.max_compute_shared_memory_size,
788        min_uniform_offset_alignment: lim.min_uniform_buffer_offset_alignment as u32,
789        min_storage_offset_alignment: lim.min_storage_buffer_offset_alignment as u32,
790        max_color_targets: lim.max_color_attachments.min(crate::COLOR_TARGETS_MAX as u32),
791        max_vertex_buffers: lim.max_vertex_input_bindings,
792        max_vertex_attributes: lim.max_vertex_input_attributes,
793        max_draw_indirect_count: lim.max_draw_indirect_count,
794        max_anisotropy: lim.max_sampler_anisotropy,
795        timestamp_period_ns: lim.timestamp_period,
796        subgroup_size: sub.subgroup_size,
797        subgroup_min_size: if subgroup_size_control { size_ctl.min_subgroup_size } else { sub.subgroup_size },
798        subgroup_max_size: if subgroup_size_control { size_ctl.max_subgroup_size } else { sub.subgroup_size },
799        max_bindless_textures: uab(vk::DIP_MAX_SET_UAB_SAMPLED_IMAGES, vk::DIP_MAX_PER_STAGE_UAB_SAMPLED_IMAGES),
800        max_bindless_samplers: uab(vk::DIP_MAX_SET_UAB_SAMPLERS, vk::DIP_MAX_PER_STAGE_UAB_SAMPLERS),
801        max_bindless_buffers: uab(vk::DIP_MAX_SET_UAB_STORAGE_BUFFERS, vk::DIP_MAX_PER_STAGE_UAB_STORAGE_BUFFERS),
802        max_bindless_storage_textures: uab(vk::DIP_MAX_SET_UAB_STORAGE_IMAGES, vk::DIP_MAX_PER_STAGE_UAB_STORAGE_IMAGES),
803        device_memory,
804    };
805    let kind = match props.device_type {
806        vk::PHYSICAL_DEVICE_TYPE_DISCRETE_GPU => AdapterKind::Discrete,
807        vk::PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU => AdapterKind::Integrated,
808        vk::PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU => AdapterKind::Virtual,
809        vk::PHYSICAL_DEVICE_TYPE_CPU => AdapterKind::Cpu,
810        _ => AdapterKind::Other,
811    };
812    let info = AdapterInfo {
813        name: String::from(vk::cstr(&props.device_name)),
814        vendor_id: props.vendor_id,
815        device_id: props.device_id,
816        vendor: vendor_name(props.vendor_id),
817        kind,
818        driver: if has_drv { String::from(vk::cstr(&drv.driver_name)) } else { String::new() },
819        driver_info: if has_drv { String::from(vk::cstr(&drv.driver_info)) } else { String::new() },
820        driver_version: driver_version(props.vendor_id, props.driver_version),
821        api_version: (vk::api_major(api), vk::api_minor(api), vk::api_patch(api)),
822        backend: "Vulkan",
823        missing,
824    };
825    Adapter {
826        inst: sh.clone(),
827        pd,
828        info,
829        features,
830        limits,
831        voll,
832        api,
833        family: family.unwrap_or(0) as u32,
834        ts_bits,
835        exts,
836        f10: f2.f,
837        f11: f11.f,
838        f12: f12.f,
839        f13: f13.f,
840        mem,
841        raw: lim,
842    }
843}