fix(vulkan-instance): verify required extensions before create

This commit is contained in:
2026-06-25 05:31:09 +04:00
parent 72f6c06eca
commit 0d139b1aae
+63
View File
@@ -3095,6 +3095,11 @@ pub enum VulkanInstanceError {
/// Invalid extension name. /// Invalid extension name.
extension: String, extension: String,
}, },
/// A required instance extension is unavailable from the loader.
MissingInstanceExtension {
/// Required extension name.
extension: String,
},
/// Validation layers were requested but unavailable. /// Validation layers were requested but unavailable.
MissingValidationLayer, MissingValidationLayer,
/// Instance creation failed. /// Instance creation failed.
@@ -3117,6 +3122,9 @@ impl std::fmt::Display for VulkanInstanceError {
"Vulkan instance extension name contains an interior NUL byte: {extension:?}" "Vulkan instance extension name contains an interior NUL byte: {extension:?}"
) )
} }
Self::MissingInstanceExtension { extension } => {
write!(f, "Vulkan instance extension {extension} is unavailable")
}
Self::MissingValidationLayer => { Self::MissingValidationLayer => {
write!( write!(
f, f,
@@ -3183,6 +3191,8 @@ pub fn create_vulkan_instance_probe(
.map_err(|_| VulkanInstanceError::InvalidApplicationName)?; .map_err(|_| VulkanInstanceError::InvalidApplicationName)?;
let engine_name = c"fparkan"; let engine_name = c"fparkan";
let plan = plan_vulkan_instance(config); let plan = plan_vulkan_instance(config);
let available_extensions = available_instance_extensions(&entry)?;
ensure_instance_extensions_available(&plan.enabled_extensions, &available_extensions)?;
let extension_names = cstring_vec(&plan.enabled_extensions)?; let extension_names = cstring_vec(&plan.enabled_extensions)?;
let extension_ptrs = cstring_ptrs(&extension_names); let extension_ptrs = cstring_ptrs(&extension_names);
let layer_names = validation_layer_cstrings(&entry, config.enable_validation)?; let layer_names = validation_layer_cstrings(&entry, config.enable_validation)?;
@@ -3208,6 +3218,43 @@ pub fn create_vulkan_instance_probe(
}) })
} }
fn available_instance_extensions(entry: &ash::Entry) -> Result<Vec<String>, VulkanInstanceError> {
let available_extensions =
// SAFETY: Enumerating instance extensions reads loader-owned immutable metadata.
unsafe { entry.enumerate_instance_extension_properties(None) }.map_err(|error| {
VulkanInstanceError::CreateFailed {
result: error,
}
})?;
available_extensions
.into_iter()
.map(|extension| {
// SAFETY: Vulkan extension names are fixed-size NUL-terminated strings from the loader.
Ok(unsafe { CStr::from_ptr(extension.extension_name.as_ptr()) }
.to_string_lossy()
.into_owned())
})
.collect()
}
fn ensure_instance_extensions_available(
required_extensions: &[String],
available_extensions: &[String],
) -> Result<(), VulkanInstanceError> {
let available = available_extensions
.iter()
.map(String::as_str)
.collect::<BTreeSet<_>>();
for extension in required_extensions {
if !available.contains(extension.as_str()) {
return Err(VulkanInstanceError::MissingInstanceExtension {
extension: extension.clone(),
});
}
}
Ok(())
}
fn validation_layer_cstrings( fn validation_layer_cstrings(
entry: &ash::Entry, entry: &ash::Entry,
enable_validation: bool, enable_validation: bool,
@@ -4758,6 +4805,22 @@ mod tests {
); );
} }
#[test]
fn missing_instance_extension_is_reported_before_create_instance() {
assert_eq!(
ensure_instance_extensions_available(
&[
"VK_EXT_debug_utils".to_string(),
"VK_KHR_surface".to_string(),
],
&["VK_KHR_surface".to_string()],
),
Err(VulkanInstanceError::MissingInstanceExtension {
extension: "VK_EXT_debug_utils".to_string(),
})
);
}
#[test] #[test]
fn surface_plan_requires_native_handles() { fn surface_plan_requires_native_handles() {
assert_eq!( assert_eq!(