screen_13/driver/
instance.rs

1use {
2    super::{DriverError, physical_device::PhysicalDevice},
3    ash::{Entry, ext, vk},
4    log::{debug, error, trace, warn},
5    std::{
6        ffi::{CStr, CString},
7        fmt::{Debug, Formatter},
8        ops::Deref,
9        os::raw::c_char,
10        thread::panicking,
11    },
12};
13
14#[cfg(not(target_os = "macos"))]
15use {
16    log::{Level, Metadata, info, logger},
17    std::{
18        env::var,
19        ffi::c_void,
20        process::{abort, id},
21        thread::{current, park},
22    },
23};
24
25#[cfg(target_os = "macos")]
26use std::env::set_var;
27
28#[cfg(not(target_os = "macos"))]
29unsafe extern "system" fn vulkan_debug_callback(
30    _flags: vk::DebugReportFlagsEXT,
31    _obj_type: vk::DebugReportObjectTypeEXT,
32    _src_obj: u64,
33    _location: usize,
34    _msg_code: i32,
35    _layer_prefix: *const c_char,
36    message: *const c_char,
37    _user_data: *mut c_void,
38) -> u32 {
39    if panicking() {
40        return vk::FALSE;
41    }
42
43    assert!(!message.is_null());
44
45    let mut found_null = false;
46    for i in 0..u16::MAX as _ {
47        if unsafe { *message.add(i) } == 0 {
48            found_null = true;
49            break;
50        }
51    }
52
53    assert!(found_null);
54
55    let message = unsafe { CStr::from_ptr(message) }.to_str().unwrap();
56
57    if message.starts_with("Validation Warning: [ UNASSIGNED-BestPractices-pipeline-stage-flags ]")
58    {
59        // vk_sync uses vk::PipelineStageFlags::ALL_COMMANDS with AccessType::NOTHING and others
60        warn!("{}", message);
61    } else {
62        let prefix = "Validation Error: [ ";
63
64        let (vuid, message) = if message.starts_with(prefix) {
65            let (vuid, message) = message
66                .trim_start_matches(prefix)
67                .split_once(" ]")
68                .unwrap_or_default();
69            let message = message.split(" | ").nth(2).unwrap_or(message);
70
71            (Some(vuid.trim()), message)
72        } else {
73            (None, message)
74        };
75
76        if let Some(vuid) = vuid {
77            info!("{vuid}");
78        }
79
80        error!("🆘 {message}");
81
82        if !logger().enabled(&Metadata::builder().level(Level::Debug).build())
83            || var("RUST_LOG")
84                .map(|rust_log| rust_log.is_empty())
85                .unwrap_or(true)
86        {
87            eprintln!(
88                "note: run with `RUST_LOG=trace` environment variable to display more information"
89            );
90            eprintln!("note: see https://github.com/rust-lang/log#in-executables");
91            abort()
92        }
93
94        if current().name() != Some("main") {
95            warn!("executing on a child thread!")
96        }
97
98        debug!(
99            "🛑 PARKING THREAD `{}` -> attach debugger to pid {}!",
100            current().name().unwrap_or_default(),
101            id()
102        );
103
104        logger().flush();
105
106        park();
107    }
108
109    vk::FALSE
110}
111
112/// There is no global state in Vulkan and all per-application state is stored in a VkInstance
113/// object.
114///
115/// Creating an Instance initializes the Vulkan library and allows the application to pass
116/// information about itself to the implementation.
117pub struct Instance {
118    _debug_callback: Option<vk::DebugReportCallbackEXT>,
119    #[allow(deprecated)] // TODO: Remove? Look into this....
120    _debug_loader: Option<ext::debug_report::Instance>,
121    debug_utils: Option<ext::debug_utils::Instance>,
122    entry: Entry,
123    instance: ash::Instance,
124}
125
126impl Instance {
127    /// Creates a new Vulkan instance.
128    #[profiling::function]
129    pub fn create<'a>(
130        debug: bool,
131        required_extensions: impl Iterator<Item = &'a CStr>,
132    ) -> Result<Self, DriverError> {
133        // Required to enable non-uniform descriptor indexing (bindless)
134        #[cfg(target_os = "macos")]
135        unsafe {
136            set_var("MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", "1");
137        }
138
139        #[cfg(not(target_os = "macos"))]
140        let entry = unsafe {
141            Entry::load().map_err(|err| {
142                error!("Vulkan driver not found: {err}");
143
144                DriverError::Unsupported
145            })?
146        };
147
148        #[cfg(target_os = "macos")]
149        let entry = ash_molten::load();
150
151        let required_extensions = required_extensions.collect::<Vec<_>>();
152        let instance_extensions = required_extensions
153            .iter()
154            .map(|ext| ext.as_ptr())
155            .chain(unsafe { Self::extension_names(debug).into_iter() })
156            .collect::<Box<[_]>>();
157        let layer_names = Self::layer_names(debug);
158        let layer_names: Vec<*const c_char> = layer_names
159            .iter()
160            .map(|raw_name| raw_name.as_ptr())
161            .collect();
162        let app_desc = vk::ApplicationInfo::default().api_version(vk::API_VERSION_1_2);
163        let instance_desc = vk::InstanceCreateInfo::default()
164            .application_info(&app_desc)
165            .enabled_layer_names(&layer_names)
166            .enabled_extension_names(&instance_extensions);
167
168        let instance = unsafe {
169            entry.create_instance(&instance_desc, None).map_err(|_| {
170                if debug {
171                    warn!("debug may only be enabled with a valid Vulkan SDK installation");
172                }
173
174                error!("Vulkan driver does not support API v1.2");
175
176                for layer_name in Self::layer_names(debug) {
177                    debug!("Layer: {:?}", layer_name);
178                }
179
180                for extension_name in required_extensions {
181                    debug!("Extension: {:?}", extension_name);
182                }
183
184                DriverError::Unsupported
185            })?
186        };
187
188        trace!("created a Vulkan instance");
189
190        #[cfg(target_os = "macos")]
191        let (debug_loader, debug_callback, debug_utils) = (None, None, None);
192
193        #[cfg(not(target_os = "macos"))]
194        let (debug_loader, debug_callback, debug_utils) = if debug {
195            let debug_info = vk::DebugReportCallbackCreateInfoEXT {
196                flags: vk::DebugReportFlagsEXT::ERROR
197                    | vk::DebugReportFlagsEXT::WARNING
198                    | vk::DebugReportFlagsEXT::PERFORMANCE_WARNING,
199                pfn_callback: Some(vulkan_debug_callback),
200                ..Default::default()
201            };
202
203            #[allow(deprecated)]
204            let debug_loader = ext::debug_report::Instance::new(&entry, &instance);
205
206            let debug_callback = unsafe {
207                #[allow(deprecated)]
208                debug_loader
209                    .create_debug_report_callback(&debug_info, None)
210                    .unwrap()
211            };
212
213            let debug_utils = ext::debug_utils::Instance::new(&entry, &instance);
214
215            (Some(debug_loader), Some(debug_callback), Some(debug_utils))
216        } else {
217            (None, None, None)
218        };
219
220        Ok(Self {
221            _debug_callback: debug_callback,
222            _debug_loader: debug_loader,
223            debug_utils,
224            entry,
225            instance,
226        })
227    }
228
229    /// Loads an existing Vulkan instance that may have been created by other means.
230    ///
231    /// This is useful when you want to use a Vulkan instance created by some other library, such
232    /// as OpenXR.
233    #[profiling::function]
234    pub fn load(entry: Entry, instance: vk::Instance) -> Result<Self, DriverError> {
235        if instance == vk::Instance::null() {
236            return Err(DriverError::InvalidData);
237        }
238
239        let instance = unsafe { ash::Instance::load(entry.static_fn(), instance) };
240
241        Ok(Self {
242            _debug_callback: None,
243            _debug_loader: None,
244            debug_utils: None,
245            entry,
246            instance,
247        })
248    }
249
250    /// Returns the `ash` entrypoint for Vulkan functions.
251    pub fn entry(this: &Self) -> &Entry {
252        &this.entry
253    }
254
255    unsafe fn extension_names(
256        #[cfg_attr(target_os = "macos", allow(unused_variables))] debug: bool,
257    ) -> Vec<*const c_char> {
258        #[cfg_attr(target_os = "macos", allow(unused_mut))]
259        let mut res = vec![];
260
261        #[cfg(not(target_os = "macos"))]
262        if debug {
263            #[allow(deprecated)]
264            res.push(ext::debug_report::NAME.as_ptr());
265            res.push(ext::debug_utils::NAME.as_ptr());
266        }
267
268        res
269    }
270
271    /// Returns `true` if this instance was created with debug layers enabled.
272    pub fn is_debug(this: &Self) -> bool {
273        this.debug_utils.is_some()
274    }
275
276    fn layer_names(
277        #[cfg_attr(target_os = "macos", allow(unused_variables))] debug: bool,
278    ) -> Vec<CString> {
279        #[cfg_attr(target_os = "macos", allow(unused_mut))]
280        let mut res = vec![];
281
282        #[cfg(not(target_os = "macos"))]
283        if debug {
284            res.push(CString::new("VK_LAYER_KHRONOS_validation").unwrap());
285        }
286
287        res
288    }
289
290    /// Returns the available physical devices of this instance.
291    #[profiling::function]
292    pub fn physical_devices(this: &Self) -> Result<Vec<PhysicalDevice>, DriverError> {
293        let physical_devices = unsafe { this.enumerate_physical_devices() };
294
295        Ok(physical_devices
296            .map_err(|err| {
297                error!("unable to enumerate physical devices: {err}");
298
299                DriverError::Unsupported
300            })?
301            .into_iter()
302            .enumerate()
303            .filter_map(|(idx, physical_device)| {
304                let res = PhysicalDevice::new(this, physical_device);
305
306                if let Err(err) = &res {
307                    warn!("unable to create physical device at index {idx}: {err}");
308                }
309
310                res.ok().filter(|physical_device| {
311                    let major = vk::api_version_major(physical_device.properties_v1_0.api_version);
312                    let minor = vk::api_version_minor(physical_device.properties_v1_0.api_version);
313                    let supports_vulkan_1_2 = major > 1 || (major == 1 && minor >= 2);
314
315                    if !supports_vulkan_1_2 {
316                        warn!(
317                            "physical device `{}` does not support Vulkan v1.2",
318                            physical_device.properties_v1_0.device_name
319                        );
320                    }
321
322                    supports_vulkan_1_2
323                })
324            })
325            .collect())
326    }
327}
328
329impl Debug for Instance {
330    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
331        f.write_str("Instance")
332    }
333}
334
335impl Deref for Instance {
336    type Target = ash::Instance;
337
338    fn deref(&self) -> &Self::Target {
339        &self.instance
340    }
341}
342
343impl Drop for Instance {
344    #[profiling::function]
345    fn drop(&mut self) {
346        if panicking() {
347            return;
348        }
349
350        unsafe {
351            #[allow(deprecated)]
352            if let Some(debug_loader) = &self._debug_loader {
353                let debug_callback = self._debug_callback.unwrap();
354                debug_loader.destroy_debug_report_callback(debug_callback, None);
355            }
356
357            self.instance.destroy_instance(None);
358        }
359    }
360}