1use {
4 super::{DriverError, physical_device::PhysicalDevice},
5 ash::{
6 ext, khr,
7 vk::{self, Handle},
8 },
9 derive_builder::Builder,
10 log::{debug, error, trace, warn},
11 raw_window_handle::{HasDisplayHandle, RawDisplayHandle},
12 std::{
13 collections::HashSet,
14 error::Error,
15 ffi::CStr,
16 fmt::{Debug, Display, Formatter},
17 ops::Deref,
18 sync::Arc,
19 thread::panicking,
20 },
21};
22
23#[cfg(any(not(target_os = "macos"), feature = "loaded"))]
24use {
25 log::{Level, Metadata, info, logger},
26 std::{
27 env::var,
28 ffi::c_void,
29 io::{IsTerminal, stderr},
30 process::{abort, id},
31 thread::{current, park},
32 },
33};
34
35#[cfg(any(not(target_os = "macos"), feature = "loaded"))]
36const SKIP_VALIDATION_PARK_ENV: &str = "VK_GRAPH_SKIP_VALIDATION_PARK";
37
38#[cfg(target_os = "macos")]
39use std::env::set_var;
40
41#[cfg(any(not(target_os = "macos"), feature = "loaded"))]
42unsafe extern "system" fn debug_callback(
43 message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
44 _message_types: vk::DebugUtilsMessageTypeFlagsEXT,
45 callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>,
46 _user_data: *mut c_void,
47) -> vk::Bool32 {
48 if panicking() {
49 return vk::FALSE;
50 }
51
52 assert!(!callback_data.is_null());
53
54 let callback_data = unsafe { &*callback_data };
55 let message = if callback_data.p_message.is_null() {
56 "<missing Vulkan validation message>"
57 } else {
58 unsafe { CStr::from_ptr(callback_data.p_message) }
59 .to_str()
60 .unwrap_or("<invalid Vulkan validation message>")
61 };
62
63 if !callback_data.p_message_id_name.is_null() {
64 let vuid = unsafe { CStr::from_ptr(callback_data.p_message_id_name) }
65 .to_str()
66 .unwrap_or("<invalid Vulkan validation message ID name>");
67 if vuid != "Loader Message" {
68 debug!("{vuid}");
69 }
70 };
71
72 let is_error = message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::ERROR);
73
74 if is_error {
75 error!("{message}");
76 } else if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::WARNING) {
77 warn!("{message}");
78 } else if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::INFO) {
79 info!("{message}");
80 } else if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE) {
81 debug!("{message}");
82 }
83
84 if !is_error {
85 return vk::FALSE;
86 }
87
88 if !logger().enabled(&Metadata::builder().level(Level::Debug).build())
89 || var("RUST_LOG")
90 .map(|rust_log| rust_log.is_empty())
91 .unwrap_or(true)
92 {
93 eprintln!(
94 "note: run with `RUST_LOG=trace` environment variable to display more information"
95 );
96 eprintln!("note: see https://github.com/rust-lang/log#in-executables");
97 abort()
98 }
99
100 if current().name() != Some("main") {
101 warn!("invalid validation callback thread: child thread")
102 }
103
104 if var(SKIP_VALIDATION_PARK_ENV)
105 .map(|value| !matches!(value.as_str(), "" | "0" | "false" | "False" | "FALSE"))
106 .unwrap_or(false)
107 {
108 warn!("validation callback park skipped; execution will continue");
109
110 return vk::FALSE;
111 }
112
113 if !stderr().is_terminal() {
114 warn!("validation callback park skipped; stderr is not an interactive terminal");
115
116 return vk::FALSE;
117 }
118
119 debug!(
120 "parking validation callback thread `{}` for debugger attach to pid {}",
121 current().name().unwrap_or_default(),
122 id()
123 );
124
125 logger().flush();
126 park();
127
128 vk::FALSE
129}
130
131fn debug_extension_names() -> &'static [&'static CStr] {
132 #[cfg(any(not(target_os = "macos"), feature = "loaded"))]
133 return &[ext::debug_utils::NAME];
134
135 #[cfg(all(target_os = "macos", not(feature = "loaded")))]
136 return &[];
137}
138
139fn debug_layer_names() -> &'static [&'static CStr] {
140 #[cfg(any(not(target_os = "macos"), feature = "loaded"))]
141 return &[c"VK_LAYER_KHRONOS_validation"];
142
143 #[cfg(all(target_os = "macos", not(feature = "loaded")))]
144 return &[];
145}
146
147fn display_extension_names(
149 display_handle: RawDisplayHandle,
150) -> Result<&'static [&'static CStr], DriverError> {
151 let extensions = match display_handle {
152 RawDisplayHandle::Windows(_) => &[khr::surface::NAME, khr::win32_surface::NAME],
153 RawDisplayHandle::Wayland(_) => &[khr::surface::NAME, khr::wayland_surface::NAME],
154 RawDisplayHandle::Xlib(_) => &[khr::surface::NAME, khr::xlib_surface::NAME],
155 RawDisplayHandle::Xcb(_) => &[khr::surface::NAME, khr::xcb_surface::NAME],
156 RawDisplayHandle::Android(_) => &[khr::surface::NAME, khr::android_surface::NAME],
157 RawDisplayHandle::AppKit(_) | RawDisplayHandle::UiKit(_) => {
158 &[khr::surface::NAME, ext::metal_surface::NAME]
159 }
160 _ => {
161 warn!("unsupported display handle type: {display_handle:?}");
162
163 return Err(DriverError::Unsupported);
164 }
165 };
166
167 Ok(extensions)
168}
169
170fn has_vk_khr_surface(entry: &ash::Entry, instance: vk::Instance) -> bool {
177 [
178 c"vkGetPhysicalDeviceSurfaceCapabilitiesKHR",
179 c"vkGetPhysicalDeviceSurfaceFormatsKHR",
180 c"vkGetPhysicalDeviceSurfacePresentModesKHR",
181 c"vkGetPhysicalDeviceSurfaceSupportKHR",
182 c"vkDestroySurfaceKHR",
183 ]
184 .into_iter()
185 .all(|name| unsafe {
186 entry
187 .get_instance_proc_addr(instance, name.as_ptr())
188 .is_some()
189 })
190}
191
192#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, PartialOrd, Ord)]
196pub enum ApiVersion {
197 Vulkan12,
199
200 #[default]
204 Vulkan13,
205}
206
207impl ApiVersion {
208 pub fn try_parse_vk_api_version(version: u32) -> Result<Self, ParseApiVersionError> {
210 Self::try_from(version)
211 }
212
213 pub fn major(self) -> u32 {
217 1
218 }
219
220 pub fn minor(self) -> u32 {
222 match self {
223 Self::Vulkan12 => 2,
224 Self::Vulkan13 => 3,
225 }
226 }
227
228 pub fn patch(self) -> u32 {
232 0
233 }
234
235 pub fn to_vk_api_version(self) -> u32 {
237 self.into()
238 }
239
240 pub fn variant(self) -> u32 {
244 0
245 }
246}
247
248impl From<ApiVersion> for u32 {
249 fn from(val: ApiVersion) -> Self {
250 vk::make_api_version(val.variant(), val.major(), val.minor(), val.patch())
251 }
252}
253
254impl TryFrom<u32> for ApiVersion {
255 type Error = ParseApiVersionError;
256
257 fn try_from(val: u32) -> Result<Self, Self::Error> {
258 let major = vk::api_version_major(val);
259 let minor = vk::api_version_minor(val);
260 let patch = vk::api_version_patch(val);
261 let variant = vk::api_version_variant(val);
262
263 if variant != 0 || major != 1 || minor < 2 {
264 return Err(ParseApiVersionError {
265 major,
266 minor,
267 patch,
268 variant,
269 });
270 }
271
272 Ok(match minor {
273 2 => ApiVersion::Vulkan12,
274 _ => ApiVersion::Vulkan13,
275 })
276 }
277}
278
279#[read_only::embed]
287#[allow(private_interfaces)]
288pub struct Instance {
289 #[readonly]
293 pub info: InstanceInfo,
294
295 #[readonly]
296 pub(self) inner: Arc<InstanceInner>,
297
298 #[readonly]
302 pub khr_surface: bool,
303}
304
305impl Clone for Instance {
306 fn clone(&self) -> Self {
307 Self {
308 read_only: ReadOnlyInstance {
309 info: self.info,
310 inner: self.inner.clone(),
311 khr_surface: self.khr_surface,
312 },
313 }
314 }
315}
316
317impl Instance {
318 pub const DEFAULT_API_VERSION: ApiVersion = ApiVersion::Vulkan13;
320
321 #[profiling::function]
329 pub fn create(info: impl Into<InstanceInfo>) -> Result<Self, DriverError> {
330 Self::create_with_extension_names(info.into(), &[])
331 }
332
333 fn create_with_extension_names(
334 info: InstanceInfo,
335 extra_extension_names: &[&CStr],
336 ) -> Result<Self, DriverError> {
337 if info.debug && debug_extension_names().is_empty() {
338 error!("debug mode requires VK_EXT_debug_utils support");
339
340 return Err(DriverError::Unsupported);
341 }
342
343 #[cfg(target_os = "macos")]
345 unsafe {
346 set_var("MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", "1");
347 }
348
349 #[cfg(feature = "loaded")]
351 let entry = unsafe {
352 ash::Entry::load().map_err(|err| {
353 error!("unable to load Vulkan driver: {err}");
354
355 DriverError::Unsupported
356 })?
357 };
358
359 #[cfg(not(feature = "loaded"))]
361 let entry = {
362 #[cfg(not(target_os = "macos"))]
363 let entry = ash::Entry::linked();
364
365 #[cfg(target_os = "macos")]
367 let entry = ash_molten::load();
368 };
369
370 let mut extension_names = info
371 .extension_names
372 .iter()
373 .chain(extra_extension_names)
374 .copied()
375 .collect::<HashSet<_>>();
376
377 if info.debug {
378 extension_names.extend(debug_extension_names());
379 }
380
381 #[cfg(all(target_os = "macos", feature = "loaded"))]
387 {
388 extension_names.extend(&[
389 ash::khr::get_physical_device_properties2::NAME,
390 ash::khr::portability_enumeration::NAME,
391 ]);
392 }
393
394 let khr_surface = extension_names.contains(&khr::surface::NAME);
395
396 let extension_name_ptrs = extension_names
397 .iter()
398 .copied()
399 .map(CStr::as_ptr)
400 .collect::<Box<_>>();
401
402 let mut layer_names = Vec::with_capacity(info.debug as _);
403
404 if info.debug {
405 layer_names.extend(debug_layer_names());
406 }
407
408 let layer_name_ptrs = layer_names
409 .iter()
410 .copied()
411 .map(CStr::as_ptr)
412 .collect::<Box<_>>();
413
414 let app_desc =
415 vk::ApplicationInfo::default().api_version(info.api_version.to_vk_api_version());
416 let instance_desc = vk::InstanceCreateInfo::default()
417 .application_info(&app_desc)
418 .enabled_layer_names(&layer_name_ptrs)
419 .enabled_extension_names(&extension_name_ptrs);
420
421 #[cfg(all(target_os = "macos", feature = "loaded"))]
426 let instance_desc = instance_desc.flags(vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR);
427
428 #[cfg(any(not(target_os = "macos"), feature = "loaded"))]
429 let mut debug_create_info = vk::DebugUtilsMessengerCreateInfoEXT::default()
430 .message_severity(
431 vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE
432 | vk::DebugUtilsMessageSeverityFlagsEXT::INFO
433 | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
434 | vk::DebugUtilsMessageSeverityFlagsEXT::ERROR,
435 )
436 .message_type(
437 vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
438 | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
439 | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
440 )
441 .pfn_user_callback(Some(debug_callback));
442
443 #[cfg(any(not(target_os = "macos"), feature = "loaded"))]
444 let instance_desc = if info.debug {
445 instance_desc.push_next(&mut debug_create_info)
446 } else {
447 instance_desc
448 };
449
450 let instance = unsafe {
451 entry.create_instance(&instance_desc, None).map_err(|_| {
452 if info.debug {
453 warn!("debug may only be enabled with a valid Vulkan SDK installation");
454 }
455
456 error!(
457 "Vulkan driver does not support API v{}",
458 match info.api_version {
459 ApiVersion::Vulkan12 => "1.2",
460 ApiVersion::Vulkan13 => "1.3",
461 }
462 );
463
464 for layer_name in &layer_names {
465 debug!("Layer: {:?}", layer_name);
466 }
467
468 for extension_name in extension_names {
469 debug!("Extension: {:?}", extension_name);
470 }
471
472 DriverError::Unsupported
473 })?
474 };
475
476 trace!("created a Vulkan instance");
477
478 #[cfg(all(target_os = "macos", not(feature = "loaded")))]
479 let debug_utils = None;
480
481 #[cfg(any(not(target_os = "macos"), feature = "loaded"))]
482 let debug_utils = if info.debug {
483 let debug_utils = ext::debug_utils::Instance::new(&entry, &instance);
484 let debug_messenger =
485 unsafe { debug_utils.create_debug_utils_messenger(&debug_create_info, None) }
486 .map_err(|err| {
487 unsafe {
488 instance.destroy_instance(None);
489 }
490
491 error!("unable to create debug utils messenger: {err}");
492
493 DriverError::Unsupported
494 })?;
495
496 Some((debug_utils, debug_messenger))
497 } else {
498 None
499 };
500
501 Ok(Self {
502 read_only: ReadOnlyInstance {
503 info,
504 inner: Arc::new(InstanceInner {
505 debug_utils,
506 entry,
507 instance,
508 instance_created: true,
509 }),
510 khr_surface,
511 },
512 })
513 }
514
515 pub fn entry(this: &Self) -> &ash::Entry {
517 &this.inner.entry
518 }
519
520 pub(crate) fn supports_debug_utils(this: &Self) -> bool {
521 this.inner.debug_utils.is_some()
522 }
523
524 #[profiling::function]
528 pub fn physical_devices(
529 this: &Self,
530 ) -> Result<impl IntoIterator<Item = PhysicalDevice>, DriverError> {
531 let physical_devices = unsafe { this.enumerate_physical_devices() }.map_err(|err| {
532 error!("unable to enumerate physical devices: {err}");
533
534 match err {
535 vk::Result::ERROR_INITIALIZATION_FAILED => DriverError::Unsupported,
536 vk::Result::ERROR_OUT_OF_DEVICE_MEMORY | vk::Result::ERROR_OUT_OF_HOST_MEMORY => {
537 DriverError::OutOfMemory
538 }
539 vk::Result::ERROR_VALIDATION_FAILED_EXT => DriverError::InvalidData,
540 _ => {
541 warn!("unexpected enumerate_physical_devices error: {err}");
542
543 DriverError::Unsupported
544 }
545 }
546 })?;
547
548 Ok(physical_devices
549 .into_iter()
550 .enumerate()
551 .filter_map(|(idx, physical_device)| {
552 let physical_device = unsafe {
553 PhysicalDevice::try_from_ash(this, physical_device)
554 .inspect_err(|err| warn!("unsupported physical device #{idx}: {err}"))
555 .ok()?
556 };
557
558 let api_version = ApiVersion::try_parse_vk_api_version(
559 physical_device.properties_v1_0.api_version,
560 )
561 .inspect_err(|err| {
562 warn!(
563 "unsupported physical device #{idx} {}: {err}",
564 physical_device.properties_v1_0.device_name
565 );
566 })
567 .ok()?;
568
569 if api_version < this.info.api_version {
570 return None;
571 }
572
573 if this.info.debug && !physical_device.vk_ext_private_data {
574 warn!(
575 "unsupported physical device #{idx} {}: missing VK_EXT_private_data",
576 physical_device.properties_v1_0.device_name
577 );
578
579 return None;
580 }
581
582 Some(physical_device)
583 }))
584 }
585
586 #[profiling::function]
591 pub fn try_from_display(
592 display: impl HasDisplayHandle,
593 info: impl Into<InstanceInfo>,
594 ) -> Result<Self, DriverError> {
595 let display_handle = display.display_handle().map_err(|err| {
596 warn!("unable to get display handle: {err}");
597
598 DriverError::Unsupported
599 })?;
600 let display_extension_names =
601 display_extension_names(display_handle.as_raw()).map_err(|err| {
602 warn!("unable to enumerate display extensions: {err}");
603
604 DriverError::Unsupported
605 })?;
606
607 Self::create_with_extension_names(info.into(), display_extension_names)
608 }
609
610 #[profiling::function]
617 pub fn try_from_entry(entry: ash::Entry, instance: vk::Instance) -> Result<Self, DriverError> {
618 if instance == vk::Instance::null() {
619 warn!("invalid VkInstance handle: null");
620
621 return Err(DriverError::InvalidData);
622 }
623
624 let api_version = unsafe { entry.try_enumerate_instance_version() }
625 .map_err(|err| match err {
626 vk::Result::ERROR_OUT_OF_HOST_MEMORY => DriverError::OutOfMemory,
627 vk::Result::ERROR_VALIDATION_FAILED_EXT => DriverError::InvalidData,
628 err => {
629 error!("unable to enumerate instance version: {err}");
630
631 DriverError::Unsupported
632 }
633 })?
634 .unwrap_or_else(|| {
635 Self::DEFAULT_API_VERSION.to_vk_api_version()
639 })
640 .try_into()
641 .map_err(|err| {
642 warn!("unsupported instance: {err}");
643
644 DriverError::Unsupported
645 })?;
646 let khr_surface = has_vk_khr_surface(&entry, instance);
647
648 let instance = unsafe { ash::Instance::load(entry.static_fn(), instance) };
649
650 Ok(Self {
651 read_only: ReadOnlyInstance {
652 info: InstanceInfo {
653 api_version,
654 ..Default::default()
655 },
656 inner: Arc::new(InstanceInner {
657 debug_utils: None,
658 entry,
659 instance,
660 instance_created: false,
661 }),
662 khr_surface,
663 },
664 })
665 }
666}
667
668impl Debug for Instance {
669 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
670 f.debug_struct(stringify!(Instance))
671 .field("handle", &self.inner.instance.handle())
672 .field("info", &self.info)
673 .field("khr_surface", &self.khr_surface)
674 .field("debug_utils", &self.inner.debug_utils.is_some())
675 .finish_non_exhaustive()
676 }
677}
678
679#[derive(Builder, Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
681#[builder(
682 build_fn(private, name = "fallible_build"),
683 derive(Clone, Copy, Debug),
684 pattern = "owned"
685)]
686pub struct InstanceInfo {
687 #[builder(default = "Instance::DEFAULT_API_VERSION")]
691 pub api_version: ApiVersion,
692
693 #[builder(default)]
713 pub debug: bool,
714
715 #[builder(default)]
717 pub extension_names: &'static [&'static CStr],
718}
719
720impl InstanceInfo {
721 pub fn builder() -> InstanceInfoBuilder {
723 Default::default()
724 }
725
726 pub fn into_builder(self) -> InstanceInfoBuilder {
728 InstanceInfoBuilder {
729 api_version: Some(self.api_version),
730 debug: Some(self.debug),
731 extension_names: Some(self.extension_names),
732 }
733 }
734}
735
736impl InstanceInfoBuilder {
737 #[inline(always)]
739 pub fn build(self) -> InstanceInfo {
740 self.fallible_build().expect("invalid instance info")
741 }
742}
743
744impl From<InstanceInfoBuilder> for InstanceInfo {
745 fn from(info: InstanceInfoBuilder) -> Self {
746 info.build()
747 }
748}
749
750struct InstanceInner {
751 debug_utils: Option<(ext::debug_utils::Instance, vk::DebugUtilsMessengerEXT)>,
752 entry: ash::Entry,
753 instance: ash::Instance,
754 instance_created: bool,
755}
756
757impl Drop for InstanceInner {
758 #[profiling::function]
759 fn drop(&mut self) {
760 if panicking() {
761 return;
762 }
763
764 unsafe {
765 if let Some((debug_utils, debug_messenger)) = self.debug_utils.take() {
766 trace!("destroy debug_utils_messenger {}", debug_messenger.as_raw());
767 debug_utils.destroy_debug_utils_messenger(debug_messenger, None);
768 trace!(
769 "destroy debug_utils_messenger {} DONE",
770 debug_messenger.as_raw()
771 );
772 }
773
774 if self.instance_created {
775 trace!("destroy instance {}", self.instance.handle().as_raw());
776 self.instance.destroy_instance(None);
777 self.instance_created = false;
778 }
779 }
780 }
781}
782
783#[derive(Clone, Copy, Debug)]
785pub struct ParseApiVersionError {
786 pub major: u32,
789
790 pub minor: u32,
793
794 pub patch: u32,
797
798 pub variant: u32,
801}
802
803impl Display for ParseApiVersionError {
804 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
805 f.write_fmt(format_args!(
806 "v{}.{}.{}-{}",
807 self.major, self.minor, self.patch, self.variant
808 ))
809 }
810}
811
812impl Error for ParseApiVersionError {}
813
814#[doc(hidden)]
815impl Deref for ReadOnlyInstance {
816 type Target = ash::Instance;
817
818 fn deref(&self) -> &Self::Target {
819 &self.inner.instance
820 }
821}
822
823#[cfg(test)]
824mod test {
825 use super::*;
826
827 #[test]
828 pub fn api_versions_match() {
829 assert_eq!(
830 ApiVersion::Vulkan12.to_vk_api_version(),
831 vk::API_VERSION_1_2
832 );
833 assert_eq!(
834 ApiVersion::Vulkan13.to_vk_api_version(),
835 vk::API_VERSION_1_3
836 );
837 }
838
839 #[test]
840 pub fn api_versions_from() {
841 assert_eq!(
842 ApiVersion::try_parse_vk_api_version(vk::API_VERSION_1_2).unwrap(),
843 ApiVersion::Vulkan12
844 );
845 assert_eq!(
846 ApiVersion::try_parse_vk_api_version(vk::API_VERSION_1_3).unwrap(),
847 ApiVersion::Vulkan13
848 );
849 }
850
851 #[test]
852 pub fn default_api_version_matches_instance_info_default() {
853 assert_eq!(
854 Instance::DEFAULT_API_VERSION,
855 InstanceInfo::default().api_version
856 );
857 }
858
859 #[test]
860 pub fn default_api_version_matches_api_version_default() {
861 assert_eq!(Instance::DEFAULT_API_VERSION, ApiVersion::default());
862 }
863
864 #[test]
865 pub fn invalid_api_versions_are_rejected() {
866 assert!(ApiVersion::try_parse_vk_api_version(vk::API_VERSION_1_1).is_err());
867 assert!(ApiVersion::try_parse_vk_api_version(vk::make_api_version(0, 2, 0, 0)).is_err());
868 assert!(ApiVersion::try_parse_vk_api_version(vk::make_api_version(1, 1, 9, 0)).is_err());
869 assert!(ApiVersion::try_parse_vk_api_version(vk::make_api_version(1, 4, 2, 0)).is_err());
870 assert!(ApiVersion::try_parse_vk_api_version(vk::make_api_version(1, 2, 0, 1)).is_err());
871 assert!(ApiVersion::try_parse_vk_api_version(vk::make_api_version(1, 3, 0, 1)).is_err());
872 }
873}