Skip to main content

vk/load/
capability.rs

1//! Immutable creation provenance and registry command admission.
2use super::{CallError, Features};
3use crate::core::{self as vk, CapabilityRequirement, CommandMetadata};
4use std::{
5	ffi::{CStr, c_char},
6	sync::Arc,
7};
8
9/// Version/extension admission for one exact command spelling.
10/// This does not validate enabled feature bits, parameters or Vulkan valid usage.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum CommandSupport {
13	Enabled,
14	Disabled,
15	/// An instance-level query depends on support on the selected physical device.
16	RequiresPhysicalDevice,
17}
18
19/// Owned snapshot of the API version and extension names used at creation.
20/// Device snapshots inherit instance extensions and cap the API version by the
21/// selected physical device. Feature records are owned; no input pNext pointers are retained.
22#[derive(Debug)]
23pub struct Capabilities {
24	api_version: u32,
25	instance_api_version: u32,
26	instance_extensions: Arc<Vec<Vec<u8>>>,
27	device_extensions: Option<Vec<Vec<u8>>>,
28	features: Option<Features>,
29}
30
31impl Capabilities {
32	/// Application API ceiling, capped by physical-device API for device snapshots.
33	/// The ignored patch component is removed.
34	pub fn api_version(&self) -> u32 {
35		self.api_version
36	}
37
38	/// Application API ceiling capped by the queried instance implementation.
39	/// Physical-device commands use their own API support, independently.
40	pub fn instance_api_version(&self) -> u32 {
41		self.instance_api_version
42	}
43
44	pub(crate) fn limit_instance_version(&mut self, version: u32) -> Result<(), CallError> {
45		self.instance_api_version = self.api_version.min(normalize_version(version)?);
46		Ok(())
47	}
48
49	/// Exact enabled instance extension names, independently owned from create info.
50	pub fn instance_extensions(&self) -> impl ExactSizeIterator<Item = &CStr> {
51		self.instance_extensions.iter().map(|bytes| {
52			// SAFETY: capture copies one complete valid CStr including its terminator.
53			unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }
54		})
55	}
56
57	/// Enabled device extensions; absent on an instance-only snapshot.
58	pub fn device_extensions(&self) -> Option<impl ExactSizeIterator<Item = &CStr>> {
59		self.device_extensions.as_ref().map(|extensions| {
60			extensions.iter().map(|bytes| {
61				// SAFETY: capture copies one complete valid CStr including its terminator.
62				unsafe { CStr::from_bytes_with_nul_unchecked(bytes) }
63			})
64		})
65	}
66
67	/// Exact enabled feature records; absent on an instance-only snapshot.
68	pub fn features(&self) -> Option<&Features> {
69		self.features.as_ref()
70	}
71
72	/// Capture external instance creation metadata without adopting a native handle.
73	///
74	/// # Errors
75	/// Reports invalid versions/name arrays or host allocation failure.
76	///
77	/// # Safety
78	/// Every reachable create-info pointer must be valid for its declared length.
79	/// When importing, this must describe the actual instance's creation and
80	/// `implementation_version` must be the version of its instance implementation.
81	pub unsafe fn new_from_instance_create_info(
82		info: &vk::InstanceCreateInfo,
83		implementation_version: u32,
84	) -> Result<Self, CallError> {
85		// SAFETY: caller provides the original valid metadata graph.
86		let mut capabilities = unsafe { Self::instance(info) }?;
87		capabilities.limit_instance_version(implementation_version)?;
88		Ok(capabilities)
89	}
90
91	/// Capture external device creation metadata without adopting a native handle.
92	///
93	/// # Errors
94	/// Reports a device snapshot used as parent, invalid metadata/chains or host
95	/// allocation failure. No native allocation or feature query is performed.
96	///
97	/// # Safety
98	/// This snapshot must describe the actual parent instance. The create info
99	/// must describe the actual logical device, and physical_version its selected
100	/// physical device. All reachable records must be readable, aligned and valid
101	/// for their sType; pointers are borrowed only until this constructor returns.
102	pub unsafe fn new_from_device_create_info(
103		&self,
104		info: &vk::DeviceCreateInfo,
105		physical_version: u32,
106	) -> Result<Self, CallError> {
107		if self.device_extensions.is_some() {
108			return Err(CallError::InvalidMetadata(
109				"device capabilities cannot be an instance parent",
110			));
111		}
112		// SAFETY: caller provides the original valid metadata graph.
113		unsafe { self.device(info, physical_version) }
114	}
115
116	pub(crate) fn matches_instance(&self, parent: &Self) -> bool {
117		!parent.is_device()
118			&& self.api_version <= parent.api_version
119			&& self.instance_api_version == parent.instance_api_version
120			&& self
121				.instance_extensions()
122				.all(|name| parent.instance_extensions().any(|other| name == other))
123			&& parent
124				.instance_extensions()
125				.all(|name| self.instance_extensions().any(|other| name == other))
126	}
127
128	pub(crate) fn is_device(&self) -> bool {
129		self.device_extensions.is_some()
130	}
131
132	/// Evaluate registry version/extension requirements for an exact native name.
133	/// Unknown names return None. Alias spellings have their own requirements;
134	/// promotion never silently enables an extension spelling or replaces a PFN.
135	/// Enabled does not guarantee a loaded pointer or feature-bit validity.
136	pub fn command_support(&self, name: &CStr) -> Option<CommandSupport> {
137		vk::COMMANDS
138			.iter()
139			.find(|command| command.name.as_bytes() == name.to_bytes())
140			.map(|command| self.support(command))
141	}
142
143	pub(crate) fn support(&self, command: &CommandMetadata) -> CommandSupport {
144		use CommandSupport::*;
145		let api_version = if command.dispatch == "Instance" && !command.physical_device {
146			self.instance_api_version
147		} else {
148			self.api_version
149		};
150		let mut result = Disabled;
151		for clause in command.requirements {
152			let mut support = Enabled;
153			for atom in *clause {
154				let value = match atom {
155					CapabilityRequirement::Core(version) => {
156						if api_version >= *version {
157							if command.physical_device
158								&& self.device_extensions.is_none()
159								&& *version > vk::API_VERSION_1_0
160							{
161								RequiresPhysicalDevice
162							} else {
163								Enabled
164							}
165						} else {
166							Disabled
167						}
168					}
169					CapabilityRequirement::InstanceExtension(name) => {
170						if self.instance_extensions().any(|enabled| enabled == *name) {
171							Enabled
172						} else {
173							Disabled
174						}
175					}
176					CapabilityRequirement::DeviceExtension(name) => match self.device_extensions() {
177						Some(mut extensions) => {
178							if extensions.any(|enabled| enabled == *name) {
179								Enabled
180							} else {
181								Disabled
182							}
183						}
184						None => RequiresPhysicalDevice,
185					},
186				};
187				if value == Disabled {
188					support = Disabled;
189					break;
190				}
191				if value == RequiresPhysicalDevice {
192					support = RequiresPhysicalDevice;
193				}
194			}
195			if support == Enabled {
196				return Enabled;
197			}
198			if support == RequiresPhysicalDevice {
199				result = RequiresPhysicalDevice;
200			}
201		}
202		result
203	}
204
205	/// # Safety
206	/// All reachable create-info pointers must be valid for their declared lengths.
207	pub(crate) unsafe fn instance(info: &vk::InstanceCreateInfo) -> Result<Self, CallError> {
208		// SAFETY: caller guarantees the optional application-info pointer is valid.
209		let requested = unsafe { info.p_application_info.as_ref() }.map_or(0, |app| app.api_version);
210		let api_version = normalize_version(if requested == 0 {
211			vk::API_VERSION_1_0
212		} else {
213			requested
214		})?;
215		// SAFETY: caller supplies the enabled-name array and valid terminated strings.
216		let extensions = unsafe {
217			capture(
218				info.enabled_extension_count,
219				info.pp_enabled_extension_names,
220			)
221		}?;
222		Ok(Self {
223			api_version,
224			instance_api_version: api_version,
225			instance_extensions: Arc::new(extensions),
226			device_extensions: None,
227			features: None,
228		})
229	}
230
231	/// # Safety
232	/// All reachable create-info pointers must be valid for their declared lengths.
233	pub(crate) unsafe fn device(
234		&self,
235		info: &vk::DeviceCreateInfo,
236		physical_version: u32,
237	) -> Result<Self, CallError> {
238		let api_version = self.api_version.min(normalize_version(physical_version)?);
239		// SAFETY: caller supplies the enabled-name array and valid terminated strings.
240		let extensions = unsafe {
241			capture(
242				info.enabled_extension_count,
243				info.pp_enabled_extension_names,
244			)
245		}?;
246		Ok(Self {
247			api_version,
248			instance_api_version: self.instance_api_version,
249			instance_extensions: Arc::clone(&self.instance_extensions),
250			device_extensions: Some(extensions),
251			// SAFETY: caller guarantees the complete readable feature chain.
252			features: Some(unsafe { Features::capture(info) }?),
253		})
254	}
255}
256
257fn normalize_version(version: u32) -> Result<u32, CallError> {
258	if vk::api_version_variant(version) != 0 || version < vk::API_VERSION_1_0 {
259		return Err(CallError::InvalidOutput("unsupported Vulkan API version"));
260	}
261	Ok(version & !0xfff)
262}
263
264unsafe fn capture(count: u32, names: *const *const c_char) -> Result<Vec<Vec<u8>>, CallError> {
265	if count != 0 && names.is_null() {
266		return Err(CallError::InvalidOutput("null enabled-extension array"));
267	}
268	let mut result = Vec::new();
269	result
270		.try_reserve_exact(count as usize)
271		.map_err(|_| CallError::HostAllocationFailed)?;
272	for index in 0..count as usize {
273		// SAFETY: caller guarantees a readable array of count pointers.
274		let name = unsafe { *names.add(index) };
275		if name.is_null() {
276			return Err(CallError::InvalidOutput("null enabled-extension name"));
277		}
278		// SAFETY: caller guarantees each non-null entry is a valid terminated CStr.
279		let bytes = unsafe { CStr::from_ptr(name) }.to_bytes_with_nul();
280		let mut owned = Vec::new();
281		owned
282			.try_reserve_exact(bytes.len())
283			.map_err(|_| CallError::HostAllocationFailed)?;
284		owned.extend_from_slice(bytes);
285		result.push(owned);
286	}
287	Ok(result)
288}
289
290#[cfg(test)]
291#[path = "../../../test/rs/load/test_capability.rs"]
292mod tests;