1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// src/vulkan/instance_exts.rs
//
// Which optional instance extensions to enable, resolved against the names the
// Vulkan loader advertises. Separated from the instance creation in `init.rs` so
// the decision is a pure function over an extension-name list and can be tested
// without a loader.
use std::ffi::CStr;
use ash::vk;
// The optional instance extensions an instance should enable.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(super) struct OptionalInstanceExts {
// VK_EXT_swapchain_colorspace, which widens the surface-format query to the
// extended-range colour spaces the HDR output path looks for. Only ever set
// when the world asked for HDR.
pub swapchain_colorspace: bool,
// VK_KHR_portability_enumeration. The loader hides portability drivers
// (MoltenVK on macOS) from `enumerate_physical_devices` unless this is
// enabled and `ENUMERATE_PORTABILITY_KHR` is set on the create info, so
// without it a macOS Vulkan build finds no physical device at all.
pub portability_enumeration: bool,
}
impl OptionalInstanceExts {
// The names to append to `VkInstanceCreateInfo::ppEnabledExtensionNames`.
pub(crate) fn names(self) -> Vec<&'static CStr> {
let mut names = Vec::new();
if self.swapchain_colorspace {
names.push(ash::ext::swapchain_colorspace::NAME);
}
if self.portability_enumeration {
names.push(ash::khr::portability_enumeration::NAME);
}
names
}
// The create flags these extensions require.
pub(crate) fn flags(self) -> vk::InstanceCreateFlags {
if self.portability_enumeration {
vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR
} else {
vk::InstanceCreateFlags::empty()
}
}
}
// Resolve the optional extensions against `available`, the loader's advertised
// instance-extension names. A missing extension degrades (SDR output, no
// portability drivers) rather than failing instance creation.
pub(super) fn select(available: &[&CStr], hdr_display: bool) -> OptionalInstanceExts {
let has = |name: &CStr| available.contains(&name);
OptionalInstanceExts {
swapchain_colorspace: hdr_display && has(ash::ext::swapchain_colorspace::NAME),
portability_enumeration: has(ash::khr::portability_enumeration::NAME),
}
}
// The loader's advertised instance-extension names, as borrowed C strings over
// `props`. Split from `select` so the caller owns the properties buffer the
// names point into.
pub(super) fn names_of(props: &[vk::ExtensionProperties]) -> Vec<&CStr> {
props
.iter()
// SAFETY: Vulkan fills `extension_name` with a NUL-terminated string, and the borrow does
// not outlive the properties entry it points into.
.map(|p| unsafe { CStr::from_ptr(p.extension_name.as_ptr()) })
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
const COLORSPACE: &CStr = ash::ext::swapchain_colorspace::NAME;
const PORTABILITY: &CStr = ash::khr::portability_enumeration::NAME;
const SURFACE: &CStr = ash::khr::surface::NAME;
#[test]
fn colorspace_needs_both_the_request_and_the_extension() {
assert!(select(&[COLORSPACE, SURFACE], true).swapchain_colorspace);
// Requested but unavailable: degrade to SDR.
assert!(!select(&[SURFACE], true).swapchain_colorspace);
// Available but not requested: an SDR world never pays for it.
assert!(!select(&[COLORSPACE], false).swapchain_colorspace);
}
#[test]
fn portability_follows_the_loader_alone() {
// Unlike the colour space, nothing requests portability: enable it
// wherever the loader has it so MoltenVK is enumerable.
assert!(select(&[PORTABILITY], false).portability_enumeration);
assert!(select(&[PORTABILITY], true).portability_enumeration);
assert!(!select(&[SURFACE], false).portability_enumeration);
}
#[test]
fn names_and_flags_follow_the_selection() {
let none = OptionalInstanceExts::default();
assert!(none.names().is_empty());
assert_eq!(none.flags(), vk::InstanceCreateFlags::empty());
let both = select(&[COLORSPACE, PORTABILITY], true);
assert_eq!(both.names(), vec![COLORSPACE, PORTABILITY]);
assert_eq!(
both.flags(),
vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR
);
// The colour space carries no create flag of its own.
let colorspace_only = select(&[COLORSPACE], true);
assert_eq!(colorspace_only.names(), vec![COLORSPACE]);
assert_eq!(colorspace_only.flags(), vk::InstanceCreateFlags::empty());
}
#[test]
fn names_read_back_from_extension_properties() {
let mut props = vk::ExtensionProperties::default();
let bytes = PORTABILITY.to_bytes_with_nul();
for (dst, src) in props.extension_name.iter_mut().zip(bytes) {
*dst = *src as std::os::raw::c_char;
}
let props = [props];
assert_eq!(names_of(&props), vec![PORTABILITY]);
}
}