diff options
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi.rs | 387 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi/instance.rs | 390 | ||||
| -rw-r--r-- | xtask/src/main.rs | 5 |
3 files changed, 404 insertions, 378 deletions
diff --git a/adapters/fparkan-render-vulkan/src/ffi.rs b/adapters/fparkan-render-vulkan/src/ffi.rs index b897a03..070c2ad 100644 --- a/adapters/fparkan-render-vulkan/src/ffi.rs +++ b/adapters/fparkan-render-vulkan/src/ffi.rs @@ -27,6 +27,16 @@ //! //! This crate is the declared low-level Vulkan boundary. +mod instance; + +pub use self::instance::{ + create_vulkan_instance_probe, plan_vulkan_instance, probe_vulkan_loader, + render_instance_plan_json, render_loader_probe_report_json, vulkan_entry_symbol_name, + VulkanInstanceConfig, VulkanInstanceError, VulkanInstancePlan, VulkanInstanceProbe, + VulkanLoaderError, VulkanLoaderProbeReport, +}; +#[cfg(test)] +use self::instance::{cstring_vec, ensure_instance_extensions_available}; use crate::policy::*; use crate::shader_manifest::{ triangle_shader_manifest, validate_shader_manifest, VulkanShaderManifestError, @@ -432,60 +442,6 @@ pub(crate) const TRIANGLE_FRAGMENT_SHADER_WORDS: &[u32] = &[ 0x0001_0038, ]; -/// Vulkan instance bootstrap configuration. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct VulkanInstanceConfig { - /// Application name reported to the loader. - pub application_name: String, - /// Required instance extensions, usually including surface extensions. - pub required_extensions: Vec<String>, - /// Whether `VK_KHR_portability_enumeration` and its create flag are enabled. - pub enable_portability_enumeration: bool, - /// Whether validation layers are requested. - pub enable_validation: bool, -} - -impl VulkanInstanceConfig { - /// Returns a conservative instance configuration for smoke probes. - #[must_use] - pub fn smoke(application_name: impl Into<String>) -> Self { - Self { - application_name: application_name.into(), - required_extensions: Vec::new(), - enable_portability_enumeration: cfg!(target_os = "macos"), - enable_validation: false, - } - } -} - -/// Deterministic Vulkan instance creation plan. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct VulkanInstancePlan { - /// Report schema version. - pub schema: u32, - /// Instance extensions requested at creation time. - pub enabled_extensions: Vec<String>, - /// Raw Vulkan instance creation flags. - pub create_flags: u32, - /// Whether validation was requested. - pub validation_requested: bool, -} - -/// Created Vulkan instance probe. -pub struct VulkanInstanceProbe { - entry: ash::Entry, - instance: ash::Instance, - /// Deterministic instance creation report. - pub report: VulkanInstancePlan, -} - -impl Drop for VulkanInstanceProbe { - fn drop(&mut self) { - // SAFETY: The `Instance` was created by this probe and is destroyed once during drop. - unsafe { self.instance.destroy_instance(None) }; - } -} - /// Deterministic Vulkan surface creation plan. #[derive(Clone, Debug, Eq, PartialEq)] pub struct VulkanSurfacePlan { @@ -2911,329 +2867,6 @@ fn extension_name(extension: *const c_char) -> Result<String, VulkanSurfaceError .map_err(|_| VulkanSurfaceError::InvalidExtensionName) } -/// Vulkan instance bootstrap error. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum VulkanInstanceError { - /// The Vulkan loader could not be opened. - Loader(VulkanLoaderError), - /// Application name contained an interior NUL byte. - InvalidApplicationName, - /// An extension name contained an interior NUL byte. - InvalidExtensionName { - /// Invalid extension name. - extension: String, - }, - /// A required instance extension is unavailable from the loader. - MissingInstanceExtension { - /// Required extension name. - extension: String, - }, - /// Validation layers were requested but unavailable. - MissingValidationLayer, - /// Instance creation failed. - CreateFailed { - /// Vulkan result. - result: vk::Result, - }, -} - -impl std::fmt::Display for VulkanInstanceError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Loader(error) => write!(f, "{error}"), - Self::InvalidApplicationName => { - write!(f, "Vulkan application name contains an interior NUL byte") - } - Self::InvalidExtensionName { extension } => { - write!( - f, - "Vulkan instance extension name contains an interior NUL byte: {extension:?}" - ) - } - Self::MissingInstanceExtension { extension } => { - write!(f, "Vulkan instance extension {extension} is unavailable") - } - Self::MissingValidationLayer => { - write!( - f, - "Vulkan validation layer VK_LAYER_KHRONOS_validation is unavailable" - ) - } - Self::CreateFailed { result } => { - write!(f, "Vulkan instance creation failed: {result:?}") - } - } - } -} - -impl std::error::Error for VulkanInstanceError {} - -/// Builds the deterministic instance creation plan without touching the loader. -#[must_use] -pub fn plan_vulkan_instance(config: &VulkanInstanceConfig) -> VulkanInstancePlan { - let mut enabled_extensions = config.required_extensions.clone(); - if config.enable_validation - && !enabled_extensions - .iter() - .any(|extension| extension == EXT_DEBUG_UTILS_EXTENSION) - { - enabled_extensions.push(EXT_DEBUG_UTILS_EXTENSION.to_string()); - } - if config.enable_portability_enumeration - && !enabled_extensions - .iter() - .any(|extension| extension == KHR_PORTABILITY_ENUMERATION_EXTENSION) - { - enabled_extensions.push(KHR_PORTABILITY_ENUMERATION_EXTENSION.to_string()); - } - enabled_extensions.sort(); - enabled_extensions.dedup(); - VulkanInstancePlan { - schema: 1, - enabled_extensions, - create_flags: if config.enable_portability_enumeration { - vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR.as_raw() - } else { - 0 - }, - validation_requested: config.enable_validation, - } -} - -/// Creates a Vulkan instance probe from the supplied configuration. -/// -/// # Errors -/// -/// Returns [`VulkanInstanceError`] when the loader is unavailable, names are not -/// valid C strings, or `vkCreateInstance` fails. -pub fn create_vulkan_instance_probe( - config: &VulkanInstanceConfig, -) -> Result<VulkanInstanceProbe, VulkanInstanceError> { - // SAFETY: Loading the entry only resolves loader symbols; no raw Vulkan handles escape. - let entry = unsafe { ash::Entry::load() }.map_err(|error| { - VulkanInstanceError::Loader(VulkanLoaderError::Unavailable { - message: error.to_string(), - }) - })?; - let app_name = CString::new(config.application_name.clone()) - .map_err(|_| VulkanInstanceError::InvalidApplicationName)?; - let engine_name = c"fparkan"; - 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_ptrs = cstring_ptrs(&extension_names); - let layer_names = validation_layer_cstrings(&entry, config.enable_validation)?; - let layer_ptrs = cstring_ptrs(&layer_names); - let app_info = vk::ApplicationInfo::default() - .application_name(&app_name) - .application_version(0) - .engine_name(engine_name) - .engine_version(0) - .api_version(MIN_VULKAN_API_VERSION); - let create_info = vk::InstanceCreateInfo::default() - .application_info(&app_info) - .enabled_extension_names(&extension_ptrs) - .enabled_layer_names(&layer_ptrs) - .flags(vk::InstanceCreateFlags::from_raw(plan.create_flags)); - // SAFETY: `create_info` points to stack-owned Vulkan create data that lives for the call. - let instance = unsafe { entry.create_instance(&create_info, None) } - .map_err(|error| VulkanInstanceError::CreateFailed { result: error })?; - Ok(VulkanInstanceProbe { - entry, - instance, - report: plan, - }) -} - -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( - entry: &ash::Entry, - enable_validation: bool, -) -> Result<Vec<CString>, VulkanInstanceError> { - if !enable_validation { - return Ok(Vec::new()); - } - let available_layers = - // SAFETY: Enumerating instance layers reads loader-owned immutable metadata. - unsafe { entry.enumerate_instance_layer_properties() }.map_err(|error| { - VulkanInstanceError::CreateFailed { - result: error, - } - })?; - let validation_available = available_layers.iter().any(|layer| { - // SAFETY: Vulkan layer names are fixed-size NUL-terminated strings from the loader. - unsafe { CStr::from_ptr(layer.layer_name.as_ptr()) } - .to_string_lossy() - .as_ref() - == VALIDATION_LAYER_NAME - }); - if !validation_available { - return Err(VulkanInstanceError::MissingValidationLayer); - } - Ok(vec![CString::new(VALIDATION_LAYER_NAME).map_err(|_| { - VulkanInstanceError::InvalidApplicationName - })?]) -} - -/// Renders a deterministic JSON Vulkan instance plan. -#[must_use] -pub fn render_instance_plan_json(plan: &VulkanInstancePlan) -> String { - #[derive(Serialize)] - struct InstancePlanJson<'a> { - schema: u32, - create_flags: u32, - validation_requested: bool, - enabled_extensions: &'a [String], - } - - serialize_json_or_fallback( - &InstancePlanJson { - schema: plan.schema, - create_flags: plan.create_flags, - validation_requested: plan.validation_requested, - enabled_extensions: &plan.enabled_extensions, - }, - "{\"schema\":0,\"create_flags\":0,\"validation_requested\":false,\"enabled_extensions\":[]}", - ) -} - -fn cstring_vec(values: &[String]) -> Result<Vec<CString>, VulkanInstanceError> { - values - .iter() - .map(|extension| { - CString::new(extension.as_str()).map_err(|_| { - VulkanInstanceError::InvalidExtensionName { - extension: extension.clone(), - } - }) - }) - .collect() -} - -fn cstring_ptrs(values: &[CString]) -> Vec<*const c_char> { - values.iter().map(|value| value.as_ptr()).collect() -} - -/// Deterministic Vulkan loader probe report. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct VulkanLoaderProbeReport { - /// Report schema version. - pub schema: u32, - /// Whether the Vulkan loader was opened successfully. - pub loader_available: bool, - /// Reported loader instance API version. - pub instance_api_version: u32, -} - -/// Vulkan loader bootstrap error. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum VulkanLoaderError { - /// The Vulkan loader library could not be opened. - Unavailable { - /// Loader error text. - message: String, - }, -} - -impl std::fmt::Display for VulkanLoaderError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Unavailable { message } => { - write!(f, "Vulkan loader is unavailable: {message}") - } - } - } -} - -impl std::error::Error for VulkanLoaderError {} - -/// Opens the Vulkan loader and reports the supported instance API version. -/// -/// # Errors -/// -/// Returns [`VulkanLoaderError`] when no Vulkan loader library can be opened on -/// the host. -pub fn probe_vulkan_loader() -> Result<VulkanLoaderProbeReport, VulkanLoaderError> { - // SAFETY: Loading the entry only resolves loader symbols; no raw Vulkan handles escape. - let entry = unsafe { ash::Entry::load() }.map_err(|error| VulkanLoaderError::Unavailable { - message: error.to_string(), - })?; - // SAFETY: The resolved entry only queries the loader-supported instance API version. - let version = unsafe { entry.try_enumerate_instance_version() } - .map_err(|error| VulkanLoaderError::Unavailable { - message: error.to_string(), - })? - .unwrap_or(vk::API_VERSION_1_0); - Ok(VulkanLoaderProbeReport { - schema: 1, - loader_available: true, - instance_api_version: version, - }) -} - -/// Returns the static Vulkan entry name used by loader probes. -#[must_use] -pub fn vulkan_entry_symbol_name() -> &'static CStr { - c"vkGetInstanceProcAddr" -} - -/// Renders a deterministic JSON Vulkan loader report. -#[must_use] -pub fn render_loader_probe_report_json(report: &VulkanLoaderProbeReport) -> String { - #[derive(Serialize)] - struct LoaderProbeReportJson { - schema: u32, - loader_available: bool, - instance_api: String, - } - - serialize_json_or_fallback( - &LoaderProbeReportJson { - schema: report.schema, - loader_available: report.loader_available, - instance_api: format_api_version(report.instance_api_version), - }, - "{\"schema\":0,\"loader_available\":false,\"instance_api\":\"0.0.0\"}", - ) -} - #[cfg(test)] mod tests { use super::*; diff --git a/adapters/fparkan-render-vulkan/src/ffi/instance.rs b/adapters/fparkan-render-vulkan/src/ffi/instance.rs new file mode 100644 index 0000000..50a056e --- /dev/null +++ b/adapters/fparkan-render-vulkan/src/ffi/instance.rs @@ -0,0 +1,390 @@ +#![allow(unsafe_code)] + +use ash::vk; +use serde::Serialize; +use std::collections::BTreeSet; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; + +use super::{ + EXT_DEBUG_UTILS_EXTENSION, KHR_PORTABILITY_ENUMERATION_EXTENSION, MIN_VULKAN_API_VERSION, + VALIDATION_LAYER_NAME, +}; +use crate::policy::{format_api_version, serialize_json_or_fallback}; + +/// Vulkan instance bootstrap configuration. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct VulkanInstanceConfig { + /// Application name reported to the loader. + pub application_name: String, + /// Required instance extensions, usually including surface extensions. + pub required_extensions: Vec<String>, + /// Whether `VK_KHR_portability_enumeration` and its create flag are enabled. + pub enable_portability_enumeration: bool, + /// Whether validation layers are requested. + pub enable_validation: bool, +} + +impl VulkanInstanceConfig { + /// Returns a conservative instance configuration for smoke probes. + #[must_use] + pub fn smoke(application_name: impl Into<String>) -> Self { + Self { + application_name: application_name.into(), + required_extensions: Vec::new(), + enable_portability_enumeration: cfg!(target_os = "macos"), + enable_validation: false, + } + } +} + +/// Deterministic Vulkan instance creation plan. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct VulkanInstancePlan { + /// Report schema version. + pub schema: u32, + /// Instance extensions requested at creation time. + pub enabled_extensions: Vec<String>, + /// Raw Vulkan instance creation flags. + pub create_flags: u32, + /// Whether validation was requested. + pub validation_requested: bool, +} + +/// Created Vulkan instance probe. +pub struct VulkanInstanceProbe { + pub(super) entry: ash::Entry, + pub(super) instance: ash::Instance, + /// Deterministic instance creation report. + pub report: VulkanInstancePlan, +} + +impl Drop for VulkanInstanceProbe { + fn drop(&mut self) { + // SAFETY: The `Instance` was created by this probe and is destroyed once during drop. + unsafe { self.instance.destroy_instance(None) }; + } +} + +/// Vulkan instance bootstrap error. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum VulkanInstanceError { + /// The Vulkan loader could not be opened. + Loader(VulkanLoaderError), + /// Application name contained an interior NUL byte. + InvalidApplicationName, + /// An extension name contained an interior NUL byte. + InvalidExtensionName { + /// Invalid extension name. + extension: String, + }, + /// A required instance extension is unavailable from the loader. + MissingInstanceExtension { + /// Required extension name. + extension: String, + }, + /// Validation layers were requested but unavailable. + MissingValidationLayer, + /// Instance creation failed. + CreateFailed { + /// Vulkan result. + result: vk::Result, + }, +} + +impl std::fmt::Display for VulkanInstanceError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Loader(error) => write!(f, "{error}"), + Self::InvalidApplicationName => { + write!(f, "Vulkan application name contains an interior NUL byte") + } + Self::InvalidExtensionName { extension } => { + write!( + f, + "Vulkan instance extension name contains an interior NUL byte: {extension:?}" + ) + } + Self::MissingInstanceExtension { extension } => { + write!(f, "Vulkan instance extension {extension} is unavailable") + } + Self::MissingValidationLayer => { + write!( + f, + "Vulkan validation layer VK_LAYER_KHRONOS_validation is unavailable" + ) + } + Self::CreateFailed { result } => { + write!(f, "Vulkan instance creation failed: {result:?}") + } + } + } +} + +impl std::error::Error for VulkanInstanceError {} + +/// Deterministic Vulkan loader probe report. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct VulkanLoaderProbeReport { + /// Report schema version. + pub schema: u32, + /// Whether the Vulkan loader was opened successfully. + pub loader_available: bool, + /// Reported loader instance API version. + pub instance_api_version: u32, +} + +/// Vulkan loader bootstrap error. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum VulkanLoaderError { + /// The Vulkan loader library could not be opened. + Unavailable { + /// Loader error text. + message: String, + }, +} + +impl std::fmt::Display for VulkanLoaderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Unavailable { message } => { + write!(f, "Vulkan loader is unavailable: {message}") + } + } + } +} + +impl std::error::Error for VulkanLoaderError {} + +/// Builds the deterministic instance creation plan without touching the loader. +#[must_use] +pub fn plan_vulkan_instance(config: &VulkanInstanceConfig) -> VulkanInstancePlan { + let mut enabled_extensions = config.required_extensions.clone(); + if config.enable_validation + && !enabled_extensions + .iter() + .any(|extension| extension == EXT_DEBUG_UTILS_EXTENSION) + { + enabled_extensions.push(EXT_DEBUG_UTILS_EXTENSION.to_string()); + } + if config.enable_portability_enumeration + && !enabled_extensions + .iter() + .any(|extension| extension == KHR_PORTABILITY_ENUMERATION_EXTENSION) + { + enabled_extensions.push(KHR_PORTABILITY_ENUMERATION_EXTENSION.to_string()); + } + enabled_extensions.sort(); + enabled_extensions.dedup(); + VulkanInstancePlan { + schema: 1, + enabled_extensions, + create_flags: if config.enable_portability_enumeration { + vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR.as_raw() + } else { + 0 + }, + validation_requested: config.enable_validation, + } +} + +/// Creates a Vulkan instance probe from the supplied configuration. +/// +/// # Errors +/// +/// Returns [`VulkanInstanceError`] when the loader is unavailable, names are not +/// valid C strings, or `vkCreateInstance` fails. +pub fn create_vulkan_instance_probe( + config: &VulkanInstanceConfig, +) -> Result<VulkanInstanceProbe, VulkanInstanceError> { + // SAFETY: Loading the entry only resolves loader symbols; no raw Vulkan handles escape. + let entry = unsafe { ash::Entry::load() }.map_err(|error| { + VulkanInstanceError::Loader(VulkanLoaderError::Unavailable { + message: error.to_string(), + }) + })?; + let app_name = CString::new(config.application_name.clone()) + .map_err(|_| VulkanInstanceError::InvalidApplicationName)?; + let engine_name = c"fparkan"; + 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_ptrs = cstring_ptrs(&extension_names); + let layer_names = validation_layer_cstrings(&entry, config.enable_validation)?; + let layer_ptrs = cstring_ptrs(&layer_names); + let app_info = vk::ApplicationInfo::default() + .application_name(&app_name) + .application_version(0) + .engine_name(engine_name) + .engine_version(0) + .api_version(MIN_VULKAN_API_VERSION); + let create_info = vk::InstanceCreateInfo::default() + .application_info(&app_info) + .enabled_extension_names(&extension_ptrs) + .enabled_layer_names(&layer_ptrs) + .flags(vk::InstanceCreateFlags::from_raw(plan.create_flags)); + // SAFETY: `create_info` points to stack-owned Vulkan create data that lives for the call. + let instance = unsafe { entry.create_instance(&create_info, None) } + .map_err(|error| VulkanInstanceError::CreateFailed { result: error })?; + Ok(VulkanInstanceProbe { + entry, + instance, + report: plan, + }) +} + +/// Renders a deterministic JSON Vulkan instance plan. +#[must_use] +pub fn render_instance_plan_json(plan: &VulkanInstancePlan) -> String { + #[derive(Serialize)] + struct InstancePlanJson<'a> { + schema: u32, + create_flags: u32, + validation_requested: bool, + enabled_extensions: &'a [String], + } + + serialize_json_or_fallback( + &InstancePlanJson { + schema: plan.schema, + create_flags: plan.create_flags, + validation_requested: plan.validation_requested, + enabled_extensions: &plan.enabled_extensions, + }, + "{\"schema\":0,\"create_flags\":0,\"validation_requested\":false,\"enabled_extensions\":[]}", + ) +} + +/// Opens the Vulkan loader and reports the supported instance API version. +/// +/// # Errors +/// +/// Returns [`VulkanLoaderError`] when no Vulkan loader library can be opened on +/// the host. +pub fn probe_vulkan_loader() -> Result<VulkanLoaderProbeReport, VulkanLoaderError> { + // SAFETY: Loading the entry only resolves loader symbols; no raw Vulkan handles escape. + let entry = unsafe { ash::Entry::load() }.map_err(|error| VulkanLoaderError::Unavailable { + message: error.to_string(), + })?; + // SAFETY: The resolved entry only queries the loader-supported instance API version. + let version = unsafe { entry.try_enumerate_instance_version() } + .map_err(|error| VulkanLoaderError::Unavailable { + message: error.to_string(), + })? + .unwrap_or(vk::API_VERSION_1_0); + Ok(VulkanLoaderProbeReport { + schema: 1, + loader_available: true, + instance_api_version: version, + }) +} + +/// Returns the static Vulkan entry name used by loader probes. +#[must_use] +pub fn vulkan_entry_symbol_name() -> &'static CStr { + c"vkGetInstanceProcAddr" +} + +/// Renders a deterministic JSON Vulkan loader report. +#[must_use] +pub fn render_loader_probe_report_json(report: &VulkanLoaderProbeReport) -> String { + #[derive(Serialize)] + struct LoaderProbeReportJson { + schema: u32, + loader_available: bool, + instance_api: String, + } + + serialize_json_or_fallback( + &LoaderProbeReportJson { + schema: report.schema, + loader_available: report.loader_available, + instance_api: format_api_version(report.instance_api_version), + }, + "{\"schema\":0,\"loader_available\":false,\"instance_api\":\"0.0.0\"}", + ) +} + +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() +} + +pub(super) 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( + entry: &ash::Entry, + enable_validation: bool, +) -> Result<Vec<CString>, VulkanInstanceError> { + if !enable_validation { + return Ok(Vec::new()); + } + let available_layers = + // SAFETY: Enumerating instance layers reads loader-owned immutable metadata. + unsafe { entry.enumerate_instance_layer_properties() }.map_err(|error| { + VulkanInstanceError::CreateFailed { + result: error, + } + })?; + let validation_available = available_layers.iter().any(|layer| { + // SAFETY: Vulkan layer names are fixed-size NUL-terminated strings from the loader. + unsafe { CStr::from_ptr(layer.layer_name.as_ptr()) } + .to_string_lossy() + .as_ref() + == VALIDATION_LAYER_NAME + }); + if !validation_available { + return Err(VulkanInstanceError::MissingValidationLayer); + } + Ok(vec![CString::new(VALIDATION_LAYER_NAME).map_err(|_| { + VulkanInstanceError::InvalidApplicationName + })?]) +} + +pub(super) fn cstring_vec(values: &[String]) -> Result<Vec<CString>, VulkanInstanceError> { + values + .iter() + .map(|extension| { + CString::new(extension.as_str()).map_err(|_| { + VulkanInstanceError::InvalidExtensionName { + extension: extension.clone(), + } + }) + }) + .collect() +} + +fn cstring_ptrs(values: &[CString]) -> Vec<*const c_char> { + values.iter().map(|value| value.as_ptr()).collect() +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 13aa656..95866b4 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1239,7 +1239,10 @@ fn has_safety_comment(line: &str) -> bool { line.contains("SAFETY:") } -const AUDITED_UNSAFE_SOURCE_FILES: &[&str] = &["adapters/fparkan-render-vulkan/src/ffi.rs"]; +const AUDITED_UNSAFE_SOURCE_FILES: &[&str] = &[ + "adapters/fparkan-render-vulkan/src/ffi.rs", + "adapters/fparkan-render-vulkan/src/ffi/instance.rs", +]; fn is_audited_unsafe_source(path: &Path) -> bool { let as_path = path.as_os_str().to_string_lossy(); |
