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        set_var("MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", "1");
137
138        #[cfg(not(target_os = "macos"))]
139        let entry = unsafe {
140            Entry::load().map_err(|err| {
141                error!("Vulkan driver not found: {err}");
142
143                DriverError::Unsupported
144            })?
145        };
146
147        #[cfg(target_os = "macos")]
148        let entry = ash_molten::load();
149
150        let required_extensions = required_extensions.collect::<Vec<_>>();
151        let instance_extensions = required_extensions
152            .iter()
153            .map(|ext| ext.as_ptr())
154            .chain(unsafe { Self::extension_names(debug).into_iter() })
155            .collect::<Box<[_]>>();
156        let layer_names = Self::layer_names(debug);
157        let layer_names: Vec<*const i8> = layer_names
158            .iter()
159            .map(|raw_name| raw_name.as_ptr())
160            .collect();
161        let app_desc = vk::ApplicationInfo::default().api_version(vk::API_VERSION_1_2);
162        let instance_desc = vk::InstanceCreateInfo::default()
163            .application_info(&app_desc)
164            .enabled_layer_names(&layer_names)
165            .enabled_extension_names(&instance_extensions);
166
167        let instance = unsafe {
168            entry.create_instance(&instance_desc, None).map_err(|_| {
169                if debug {
170                    warn!("debug may only be enabled with a valid Vulkan SDK installation");
171                }
172
173                error!("Vulkan driver does not support API v1.2");
174
175                for layer_name in Self::layer_names(debug) {
176                    debug!("Layer: {:?}", layer_name);
177                }
178
179                for extension_name in required_extensions {
180                    debug!("Extension: {:?}", extension_name);
181                }
182
183                DriverError::Unsupported
184            })?
185        };
186
187        trace!("created a Vulkan instance");
188
189        #[cfg(target_os = "macos")]
190        let (debug_loader, debug_callback, debug_utils) = (None, None, None);
191
192        #[cfg(not(target_os = "macos"))]
193        let (debug_loader, debug_callback, debug_utils) = if debug {
194            let debug_info = vk::DebugReportCallbackCreateInfoEXT {
195                flags: vk::DebugReportFlagsEXT::ERROR
196                    | vk::DebugReportFlagsEXT::WARNING
197                    | vk::DebugReportFlagsEXT::PERFORMANCE_WARNING,
198                pfn_callback: Some(vulkan_debug_callback),
199                ..Default::default()
200            };
201
202            #[allow(deprecated)]
203            let debug_loader = ext::debug_report::Instance::new(&entry, &instance);
204
205            let debug_callback = unsafe {
206                #[allow(deprecated)]
207                debug_loader
208                    .create_debug_report_callback(&debug_info, None)
209                    .unwrap()
210            };
211
212            let debug_utils = ext::debug_utils::Instance::new(&entry, &instance);
213
214            (Some(debug_loader), Some(debug_callback), Some(debug_utils))
215        } else {
216            (None, None, None)
217        };
218
219        Ok(Self {
220            _debug_callback: debug_callback,
221            _debug_loader: debug_loader,
222            debug_utils,
223            entry,
224            instance,
225        })
226    }
227
228    /// Loads an existing Vulkan instance that may have been created by other means.
229    ///
230    /// This is useful when you want to use a Vulkan instance created by some other library, such
231    /// as OpenXR.
232    #[profiling::function]
233    pub fn load(entry: Entry, instance: vk::Instance) -> Result<Self, DriverError> {
234        if instance == vk::Instance::null() {
235            return Err(DriverError::InvalidData);
236        }
237
238        let instance = unsafe { ash::Instance::load(entry.static_fn(), instance) };
239
240        Ok(Self {
241            _debug_callback: None,
242            _debug_loader: None,
243            debug_utils: None,
244            entry,
245            instance,
246        })
247    }
248
249    /// Returns the `ash` entrypoint for Vulkan functions.
250    pub fn entry(this: &Self) -> &Entry {
251        &this.entry
252    }
253
254    unsafe fn extension_names(
255        #[cfg_attr(target_os = "macos", allow(unused_variables))] debug: bool,
256    ) -> Vec<*const c_char> {
257        #[cfg_attr(target_os = "macos", allow(unused_mut))]
258        let mut res = vec![];
259
260        #[cfg(not(target_os = "macos"))]
261        if debug {
262            #[allow(deprecated)]
263            res.push(ext::debug_report::NAME.as_ptr());
264            res.push(ext::debug_utils::NAME.as_ptr());
265        }
266
267        res
268    }
269
270    /// Returns `true` if this instance was created with debug layers enabled.
271    pub fn is_debug(this: &Self) -> bool {
272        this.debug_utils.is_some()
273    }
274
275    fn layer_names(
276        #[cfg_attr(target_os = "macos", allow(unused_variables))] debug: bool,
277    ) -> Vec<CString> {
278        #[cfg_attr(target_os = "macos", allow(unused_mut))]
279        let mut res = vec![];
280
281        #[cfg(not(target_os = "macos"))]
282        if debug {
283            res.push(CString::new("VK_LAYER_KHRONOS_validation").unwrap());
284        }
285
286        res
287    }
288
289    /// Returns the available physical devices of this instance.
290    #[profiling::function]
291    pub fn physical_devices(this: &Self) -> Result<Vec<PhysicalDevice>, DriverError> {
292        let physical_devices = unsafe { this.enumerate_physical_devices() };
293
294        Ok(physical_devices
295            .map_err(|err| {
296                error!("unable to enumerate physical devices: {err}");
297
298                DriverError::Unsupported
299            })?
300            .into_iter()
301            .enumerate()
302            .filter_map(|(idx, physical_device)| {
303                let res = PhysicalDevice::new(this, physical_device);
304
305                if let Err(err) = &res {
306                    warn!("unable to create physical device at index {idx}: {err}");
307                }
308
309                res.ok().filter(|physical_device| {
310                    let major = vk::api_version_major(physical_device.properties_v1_0.api_version);
311                    let minor = vk::api_version_minor(physical_device.properties_v1_0.api_version);
312                    let supports_vulkan_1_2 = major > 1 || (major == 1 && minor >= 2);
313
314                    if !supports_vulkan_1_2 {
315                        warn!(
316                            "physical device `{}` does not support Vulkan v1.2",
317                            physical_device.properties_v1_0.device_name
318                        );
319                    }
320
321                    supports_vulkan_1_2
322                })
323            })
324            .collect())
325    }
326}
327
328impl Debug for Instance {
329    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
330        f.write_str("Instance")
331    }
332}
333
334impl Deref for Instance {
335    type Target = ash::Instance;
336
337    fn deref(&self) -> &Self::Target {
338        &self.instance
339    }
340}
341
342impl Drop for Instance {
343    #[profiling::function]
344    fn drop(&mut self) {
345        if panicking() {
346            return;
347        }
348
349        unsafe {
350            #[allow(deprecated)]
351            if let Some(debug_loader) = &self._debug_loader {
352                let debug_callback = self._debug_callback.unwrap();
353                debug_loader.destroy_debug_report_callback(debug_callback, None);
354            }
355
356            self.instance.destroy_instance(None);
357        }
358    }
359}