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