refactor(vulkan-ffi): extract instance bootstrap module

This commit is contained in:
2026-06-25 06:08:23 +04:00
parent ce3e5ad813
commit 1eead8d597
3 changed files with 404 additions and 378 deletions
+10 -377
View File
@@ -27,6 +27,16 @@
//! //!
//! This crate is the declared low-level Vulkan boundary. //! 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::policy::*;
use crate::shader_manifest::{ use crate::shader_manifest::{
triangle_shader_manifest, validate_shader_manifest, VulkanShaderManifestError, triangle_shader_manifest, validate_shader_manifest, VulkanShaderManifestError,
@@ -432,60 +442,6 @@ pub(crate) const TRIANGLE_FRAGMENT_SHADER_WORDS: &[u32] = &[
0x0001_0038, 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. /// Deterministic Vulkan surface creation plan.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct VulkanSurfacePlan { pub struct VulkanSurfacePlan {
@@ -2911,329 +2867,6 @@ fn extension_name(extension: *const c_char) -> Result<String, VulkanSurfaceError
.map_err(|_| VulkanSurfaceError::InvalidExtensionName) .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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -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()
}
+4 -1
View File
@@ -1239,7 +1239,10 @@ fn has_safety_comment(line: &str) -> bool {
line.contains("SAFETY:") 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 { fn is_audited_unsafe_source(path: &Path) -> bool {
let as_path = path.as_os_str().to_string_lossy(); let as_path = path.as_os_str().to_string_lossy();