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