refactor(vulkan-ffi): extract runtime capability module
This commit is contained in:
@@ -28,6 +28,7 @@
|
|||||||
//! This crate is the declared low-level Vulkan boundary.
|
//! This crate is the declared low-level Vulkan boundary.
|
||||||
|
|
||||||
mod instance;
|
mod instance;
|
||||||
|
mod runtime;
|
||||||
mod surface;
|
mod surface;
|
||||||
mod validation;
|
mod validation;
|
||||||
|
|
||||||
@@ -39,6 +40,13 @@ pub use self::instance::{
|
|||||||
};
|
};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use self::instance::{cstring_vec, ensure_instance_extensions_available};
|
use self::instance::{cstring_vec, ensure_instance_extensions_available};
|
||||||
|
pub use self::runtime::{
|
||||||
|
create_vulkan_logical_device_probe, create_vulkan_swapchain_probe,
|
||||||
|
create_vulkan_swapchain_probe_for_extent, probe_vulkan_runtime_capabilities,
|
||||||
|
VulkanLogicalDeviceError, VulkanLogicalDeviceProbe, VulkanLogicalDeviceReport,
|
||||||
|
VulkanRuntimeCapabilityError, VulkanRuntimeCapabilityProbe, VulkanSwapchainProbe,
|
||||||
|
VulkanSwapchainProbeError, VulkanSwapchainReport,
|
||||||
|
};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use self::surface::extension_name;
|
use self::surface::extension_name;
|
||||||
pub use self::surface::{
|
pub use self::surface::{
|
||||||
@@ -46,13 +54,11 @@ pub use self::surface::{
|
|||||||
VulkanSurfacePlan, VulkanSurfaceProbe,
|
VulkanSurfacePlan, VulkanSurfaceProbe,
|
||||||
};
|
};
|
||||||
use self::validation::{create_validation_messenger, VulkanValidationMessenger};
|
use self::validation::{create_validation_messenger, VulkanValidationMessenger};
|
||||||
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,
|
||||||
};
|
};
|
||||||
use ash::{khr::swapchain, vk};
|
use ash::vk;
|
||||||
use fparkan_platform::NativeWindowHandles;
|
use fparkan_platform::NativeWindowHandles;
|
||||||
use std::ffi::{CStr, CString};
|
|
||||||
/// Minimum Vulkan API version accepted by the Stage 0 backend.
|
/// Minimum Vulkan API version accepted by the Stage 0 backend.
|
||||||
pub const MIN_VULKAN_API_VERSION: u32 = vk::API_VERSION_1_1;
|
pub const MIN_VULKAN_API_VERSION: u32 = vk::API_VERSION_1_1;
|
||||||
const KHR_PORTABILITY_ENUMERATION_EXTENSION: &str = "VK_KHR_portability_enumeration";
|
const KHR_PORTABILITY_ENUMERATION_EXTENSION: &str = "VK_KHR_portability_enumeration";
|
||||||
@@ -443,104 +449,6 @@ pub(crate) const TRIANGLE_FRAGMENT_SHADER_WORDS: &[u32] = &[
|
|||||||
0x0001_0038,
|
0x0001_0038,
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Live Vulkan device/surface capability probe.
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub struct VulkanRuntimeCapabilityProbe {
|
|
||||||
/// Selected device/queue capability report.
|
|
||||||
pub capability: VulkanCapabilityReport,
|
|
||||||
/// Swapchain plan built from the selected device and live surface capabilities.
|
|
||||||
pub swapchain: VulkanSwapchainPlan,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Created Vulkan logical device probe.
|
|
||||||
pub struct VulkanLogicalDeviceProbe {
|
|
||||||
device: ash::Device,
|
|
||||||
physical_device: vk::PhysicalDevice,
|
|
||||||
/// Runtime capability report used for device selection.
|
|
||||||
pub runtime: VulkanRuntimeCapabilityProbe,
|
|
||||||
/// Deterministic logical device creation report.
|
|
||||||
pub report: VulkanLogicalDeviceReport,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for VulkanLogicalDeviceProbe {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// SAFETY: The logical device was created by this probe and is destroyed once during drop.
|
|
||||||
unsafe { self.device.destroy_device(None) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VulkanLogicalDeviceProbe {
|
|
||||||
/// Returns the graphics queue selected by the Stage 0 policy.
|
|
||||||
#[must_use]
|
|
||||||
pub fn graphics_queue(&self) -> vk::Queue {
|
|
||||||
// SAFETY: The queue-family index belongs to this live logical device.
|
|
||||||
unsafe {
|
|
||||||
self.device
|
|
||||||
.get_device_queue(self.report.graphics_queue_family, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the presentation queue selected by the Stage 0 policy.
|
|
||||||
#[must_use]
|
|
||||||
pub fn present_queue(&self) -> vk::Queue {
|
|
||||||
// SAFETY: The queue-family index belongs to this live logical device.
|
|
||||||
unsafe {
|
|
||||||
self.device
|
|
||||||
.get_device_queue(self.report.present_queue_family, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a shared reference to the live logical device.
|
|
||||||
#[must_use]
|
|
||||||
pub fn device(&self) -> &ash::Device {
|
|
||||||
&self.device
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Logical device creation report.
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub struct VulkanLogicalDeviceReport {
|
|
||||||
/// Report schema version.
|
|
||||||
pub schema: u32,
|
|
||||||
/// Selected physical device name.
|
|
||||||
pub device_name: String,
|
|
||||||
/// Graphics queue-family index used by the logical device.
|
|
||||||
pub graphics_queue_family: u32,
|
|
||||||
/// Present queue-family index used by the logical device.
|
|
||||||
pub present_queue_family: u32,
|
|
||||||
/// Enabled device extensions.
|
|
||||||
pub enabled_extensions: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Created Vulkan swapchain probe.
|
|
||||||
pub struct VulkanSwapchainProbe {
|
|
||||||
loader: swapchain::Device,
|
|
||||||
swapchain: vk::SwapchainKHR,
|
|
||||||
/// Deterministic swapchain creation report.
|
|
||||||
pub report: VulkanSwapchainReport,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for VulkanSwapchainProbe {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// SAFETY: The swapchain was created by this probe and is destroyed once during drop.
|
|
||||||
unsafe { self.loader.destroy_swapchain(self.swapchain, None) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VulkanSwapchainProbe {
|
|
||||||
/// Returns the live swapchain handle.
|
|
||||||
#[must_use]
|
|
||||||
pub fn swapchain(&self) -> vk::SwapchainKHR {
|
|
||||||
self.swapchain
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the swapchain extension loader for this live swapchain.
|
|
||||||
#[must_use]
|
|
||||||
pub fn loader(&self) -> &swapchain::Device {
|
|
||||||
&self.loader
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a live native Vulkan renderer for the Stage 0 smoke loop.
|
/// Creates a live native Vulkan renderer for the Stage 0 smoke loop.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct VulkanSmokeRendererCreateInfo {
|
pub struct VulkanSmokeRendererCreateInfo {
|
||||||
@@ -1375,7 +1283,7 @@ fn create_host_visible_buffer(
|
|||||||
let requirements = unsafe { device.device().get_buffer_memory_requirements(buffer) };
|
let requirements = unsafe { device.device().get_buffer_memory_requirements(buffer) };
|
||||||
let Some(memory_type_index) = find_memory_type(
|
let Some(memory_type_index) = find_memory_type(
|
||||||
instance,
|
instance,
|
||||||
device.physical_device,
|
device.physical_device(),
|
||||||
requirements.memory_type_bits,
|
requirements.memory_type_bits,
|
||||||
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
|
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
|
||||||
) else {
|
) else {
|
||||||
@@ -1944,667 +1852,6 @@ fn color_subresource_range() -> vk::ImageSubresourceRange {
|
|||||||
.layer_count(1)
|
.layer_count(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runtime swapchain creation report.
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub struct VulkanSwapchainReport {
|
|
||||||
/// Report schema version.
|
|
||||||
pub schema: u32,
|
|
||||||
/// Deterministic swapchain policy used for creation.
|
|
||||||
pub plan: VulkanSwapchainPlan,
|
|
||||||
/// Number of images returned by `vkGetSwapchainImagesKHR`.
|
|
||||||
pub image_count: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Live Vulkan device/surface capability probe error.
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub enum VulkanRuntimeCapabilityError {
|
|
||||||
/// Physical device enumeration failed.
|
|
||||||
EnumerateDevicesFailed {
|
|
||||||
/// Vulkan result.
|
|
||||||
result: vk::Result,
|
|
||||||
},
|
|
||||||
/// Device extension enumeration failed.
|
|
||||||
EnumerateDeviceExtensionsFailed {
|
|
||||||
/// Device name or index context.
|
|
||||||
device: String,
|
|
||||||
/// Vulkan result.
|
|
||||||
result: vk::Result,
|
|
||||||
},
|
|
||||||
/// Queue-family present support query failed.
|
|
||||||
PresentSupportFailed {
|
|
||||||
/// Device name.
|
|
||||||
device: String,
|
|
||||||
/// Queue-family index.
|
|
||||||
queue_family: u32,
|
|
||||||
/// Vulkan result.
|
|
||||||
result: vk::Result,
|
|
||||||
},
|
|
||||||
/// Surface format query failed.
|
|
||||||
SurfaceFormatsFailed {
|
|
||||||
/// Device name.
|
|
||||||
device: String,
|
|
||||||
/// Vulkan result.
|
|
||||||
result: vk::Result,
|
|
||||||
},
|
|
||||||
/// Surface capability query failed.
|
|
||||||
SurfaceCapabilitiesFailed {
|
|
||||||
/// Device name.
|
|
||||||
device: String,
|
|
||||||
/// Vulkan result.
|
|
||||||
result: vk::Result,
|
|
||||||
},
|
|
||||||
/// Present mode query failed.
|
|
||||||
PresentModesFailed {
|
|
||||||
/// Device name.
|
|
||||||
device: String,
|
|
||||||
/// Vulkan result.
|
|
||||||
result: vk::Result,
|
|
||||||
},
|
|
||||||
/// No device satisfied Stage 0 capability policy.
|
|
||||||
Capability(VulkanCapabilityError),
|
|
||||||
/// Live surface capabilities could not produce a swapchain plan.
|
|
||||||
Swapchain(VulkanSwapchainError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for VulkanRuntimeCapabilityError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::EnumerateDevicesFailed { result } => {
|
|
||||||
write!(f, "Vulkan physical device enumeration failed: {result:?}")
|
|
||||||
}
|
|
||||||
Self::EnumerateDeviceExtensionsFailed { device, result } => write!(
|
|
||||||
f,
|
|
||||||
"Vulkan device {device} extension enumeration failed: {result:?}"
|
|
||||||
),
|
|
||||||
Self::PresentSupportFailed {
|
|
||||||
device,
|
|
||||||
queue_family,
|
|
||||||
result,
|
|
||||||
} => write!(
|
|
||||||
f,
|
|
||||||
"Vulkan device {device} queue family {queue_family} present support query failed: {result:?}"
|
|
||||||
),
|
|
||||||
Self::SurfaceFormatsFailed { device, result } => write!(
|
|
||||||
f,
|
|
||||||
"Vulkan device {device} surface format query failed: {result:?}"
|
|
||||||
),
|
|
||||||
Self::SurfaceCapabilitiesFailed { device, result } => write!(
|
|
||||||
f,
|
|
||||||
"Vulkan device {device} surface capabilities query failed: {result:?}"
|
|
||||||
),
|
|
||||||
Self::PresentModesFailed { device, result } => write!(
|
|
||||||
f,
|
|
||||||
"Vulkan device {device} present mode query failed: {result:?}"
|
|
||||||
),
|
|
||||||
Self::Capability(error) => write!(f, "{error}"),
|
|
||||||
Self::Swapchain(error) => write!(f, "{error}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for VulkanRuntimeCapabilityError {}
|
|
||||||
|
|
||||||
/// Vulkan logical device creation error.
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub enum VulkanLogicalDeviceError {
|
|
||||||
/// Runtime capability probing failed.
|
|
||||||
Runtime(VulkanRuntimeCapabilityError),
|
|
||||||
/// Device extension name contained an interior NUL byte.
|
|
||||||
InvalidExtensionName {
|
|
||||||
/// Invalid extension name.
|
|
||||||
extension: String,
|
|
||||||
},
|
|
||||||
/// Logical device creation failed.
|
|
||||||
CreateFailed {
|
|
||||||
/// Selected device name.
|
|
||||||
device: String,
|
|
||||||
/// Vulkan result.
|
|
||||||
result: vk::Result,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for VulkanLogicalDeviceError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Runtime(error) => write!(f, "{error}"),
|
|
||||||
Self::InvalidExtensionName { extension } => write!(
|
|
||||||
f,
|
|
||||||
"Vulkan device extension name contains an interior NUL byte: {extension:?}"
|
|
||||||
),
|
|
||||||
Self::CreateFailed { device, result } => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Vulkan logical device creation failed for {device}: {result:?}"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for VulkanLogicalDeviceError {}
|
|
||||||
|
|
||||||
/// Vulkan swapchain creation error.
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub enum VulkanSwapchainProbeError {
|
|
||||||
/// Live runtime capability probing failed before swapchain creation.
|
|
||||||
Runtime(VulkanRuntimeCapabilityError),
|
|
||||||
/// Deterministic swapchain planning failed before create.
|
|
||||||
Plan(VulkanSwapchainError),
|
|
||||||
/// Surface capability query failed.
|
|
||||||
SurfaceCapabilitiesFailed {
|
|
||||||
/// Vulkan result.
|
|
||||||
result: vk::Result,
|
|
||||||
},
|
|
||||||
/// Swapchain creation failed.
|
|
||||||
CreateFailed {
|
|
||||||
/// Vulkan result.
|
|
||||||
result: vk::Result,
|
|
||||||
},
|
|
||||||
/// Swapchain image query failed.
|
|
||||||
ImagesFailed {
|
|
||||||
/// Vulkan result.
|
|
||||||
result: vk::Result,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for VulkanSwapchainProbeError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Runtime(error) => write!(f, "{error}"),
|
|
||||||
Self::Plan(error) => write!(f, "{error}"),
|
|
||||||
Self::SurfaceCapabilitiesFailed { result } => {
|
|
||||||
write!(f, "Vulkan surface capabilities query failed: {result:?}")
|
|
||||||
}
|
|
||||||
Self::CreateFailed { result } => {
|
|
||||||
write!(f, "Vulkan swapchain creation failed: {result:?}")
|
|
||||||
}
|
|
||||||
Self::ImagesFailed { result } => {
|
|
||||||
write!(f, "Vulkan swapchain image query failed: {result:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for VulkanSwapchainProbeError {}
|
|
||||||
|
|
||||||
/// Probes live Vulkan device, queue, surface and swapchain capabilities.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// Returns [`VulkanRuntimeCapabilityError`] when device enumeration, surface
|
|
||||||
/// capability queries, Stage 0 device selection, or swapchain planning fails.
|
|
||||||
pub fn probe_vulkan_runtime_capabilities(
|
|
||||||
instance: &VulkanInstanceProbe,
|
|
||||||
surface: &VulkanSurfaceProbe,
|
|
||||||
drawable_extent: (u32, u32),
|
|
||||||
) -> Result<VulkanRuntimeCapabilityProbe, VulkanRuntimeCapabilityError> {
|
|
||||||
let selected = select_live_device_candidate(instance, surface, drawable_extent)?;
|
|
||||||
Ok(selected.runtime)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a Vulkan logical device for the selected live surface-capable device.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// Returns [`VulkanLogicalDeviceError`] when runtime capability probing fails,
|
|
||||||
/// device extension names are invalid, or `vkCreateDevice` fails.
|
|
||||||
pub fn create_vulkan_logical_device_probe(
|
|
||||||
instance: &VulkanInstanceProbe,
|
|
||||||
surface: &VulkanSurfaceProbe,
|
|
||||||
drawable_extent: (u32, u32),
|
|
||||||
) -> Result<VulkanLogicalDeviceProbe, VulkanLogicalDeviceError> {
|
|
||||||
let selected = select_live_device_candidate(instance, surface, drawable_extent)
|
|
||||||
.map_err(VulkanLogicalDeviceError::Runtime)?;
|
|
||||||
let capability = &selected.runtime.capability;
|
|
||||||
let queue_priorities = [1.0_f32];
|
|
||||||
let queue_families = unique_queue_families(
|
|
||||||
capability.graphics_queue_family,
|
|
||||||
capability.present_queue_family,
|
|
||||||
);
|
|
||||||
let queue_infos = queue_families
|
|
||||||
.iter()
|
|
||||||
.map(|queue_family| {
|
|
||||||
vk::DeviceQueueCreateInfo::default()
|
|
||||||
.queue_family_index(*queue_family)
|
|
||||||
.queue_priorities(&queue_priorities)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let extension_names = device_extension_cstrings(&capability.enabled_extensions)
|
|
||||||
.map_err(|extension| VulkanLogicalDeviceError::InvalidExtensionName { extension })?;
|
|
||||||
let extension_ptrs = extension_names
|
|
||||||
.iter()
|
|
||||||
.map(|extension| extension.as_ptr())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let create_info = vk::DeviceCreateInfo::default()
|
|
||||||
.queue_create_infos(&queue_infos)
|
|
||||||
.enabled_extension_names(&extension_ptrs);
|
|
||||||
// SAFETY: `selected.physical_device` belongs to `instance`; create data lives for the call.
|
|
||||||
let device = unsafe {
|
|
||||||
instance
|
|
||||||
.instance
|
|
||||||
.create_device(selected.physical_device, &create_info, None)
|
|
||||||
}
|
|
||||||
.map_err(|error| VulkanLogicalDeviceError::CreateFailed {
|
|
||||||
device: capability.device_name.clone(),
|
|
||||||
result: error,
|
|
||||||
})?;
|
|
||||||
// SAFETY: Queue family indices came from validated live queue families requested above.
|
|
||||||
let _graphics_queue = unsafe { device.get_device_queue(capability.graphics_queue_family, 0) };
|
|
||||||
// SAFETY: Queue family indices came from validated live queue families requested above.
|
|
||||||
let _present_queue = unsafe { device.get_device_queue(capability.present_queue_family, 0) };
|
|
||||||
Ok(VulkanLogicalDeviceProbe {
|
|
||||||
device,
|
|
||||||
physical_device: selected.physical_device,
|
|
||||||
report: VulkanLogicalDeviceReport {
|
|
||||||
schema: 1,
|
|
||||||
device_name: capability.device_name.clone(),
|
|
||||||
graphics_queue_family: capability.graphics_queue_family,
|
|
||||||
present_queue_family: capability.present_queue_family,
|
|
||||||
enabled_extensions: capability.enabled_extensions.clone(),
|
|
||||||
},
|
|
||||||
runtime: selected.runtime,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a Vulkan swapchain for the live logical device and surface.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// Returns [`VulkanSwapchainProbeError`] when live surface capability queries,
|
|
||||||
/// swapchain creation, or swapchain image enumeration fails.
|
|
||||||
pub fn create_vulkan_swapchain_probe(
|
|
||||||
instance: &VulkanInstanceProbe,
|
|
||||||
surface: &VulkanSurfaceProbe,
|
|
||||||
device: &VulkanLogicalDeviceProbe,
|
|
||||||
) -> Result<VulkanSwapchainProbe, VulkanSwapchainProbeError> {
|
|
||||||
create_vulkan_swapchain_probe_for_extent(
|
|
||||||
instance,
|
|
||||||
surface,
|
|
||||||
device,
|
|
||||||
device.runtime.swapchain.extent,
|
|
||||||
vk::SwapchainKHR::null(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a Vulkan swapchain for the live logical device and surface at a specific extent.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// Returns [`VulkanSwapchainProbeError`] when live surface capability queries,
|
|
||||||
/// swapchain creation, or swapchain image enumeration fails.
|
|
||||||
pub fn create_vulkan_swapchain_probe_for_extent(
|
|
||||||
instance: &VulkanInstanceProbe,
|
|
||||||
surface: &VulkanSurfaceProbe,
|
|
||||||
device: &VulkanLogicalDeviceProbe,
|
|
||||||
drawable_extent: (u32, u32),
|
|
||||||
old_swapchain: vk::SwapchainKHR,
|
|
||||||
) -> Result<VulkanSwapchainProbe, VulkanSwapchainProbeError> {
|
|
||||||
let raw_capabilities = {
|
|
||||||
// SAFETY: The physical device and surface are live query inputs and no handles are retained.
|
|
||||||
unsafe {
|
|
||||||
surface
|
|
||||||
.loader
|
|
||||||
.get_physical_device_surface_capabilities(device.physical_device, surface.surface)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map_err(|error| VulkanSwapchainProbeError::SurfaceCapabilitiesFailed { result: error })?;
|
|
||||||
let surface_formats =
|
|
||||||
live_surface_formats(surface, device.physical_device, &device.report.device_name)
|
|
||||||
.map_err(VulkanSwapchainProbeError::Runtime)?;
|
|
||||||
let present_modes =
|
|
||||||
live_present_modes(surface, device.physical_device, &device.report.device_name)
|
|
||||||
.map_err(VulkanSwapchainProbeError::Runtime)?;
|
|
||||||
let capabilities =
|
|
||||||
live_surface_capabilities(surface, device.physical_device, &device.report.device_name)
|
|
||||||
.map_err(VulkanSwapchainProbeError::Runtime)?;
|
|
||||||
let plan = plan_vulkan_swapchain(&VulkanSwapchainRequest {
|
|
||||||
drawable_extent,
|
|
||||||
formats: surface_formats,
|
|
||||||
present_modes,
|
|
||||||
capabilities,
|
|
||||||
preferred_present_mode: vk::PresentModeKHR::MAILBOX.as_raw(),
|
|
||||||
})
|
|
||||||
.map_err(VulkanSwapchainProbeError::Plan)?;
|
|
||||||
let queue_family_indices = unique_queue_families(
|
|
||||||
device.runtime.capability.graphics_queue_family,
|
|
||||||
device.runtime.capability.present_queue_family,
|
|
||||||
);
|
|
||||||
let sharing_mode = if queue_family_indices.len() > 1 {
|
|
||||||
vk::SharingMode::CONCURRENT
|
|
||||||
} else {
|
|
||||||
vk::SharingMode::EXCLUSIVE
|
|
||||||
};
|
|
||||||
let create_info = vk::SwapchainCreateInfoKHR::default()
|
|
||||||
.surface(surface.surface)
|
|
||||||
.min_image_count(plan.image_count)
|
|
||||||
.image_format(vk::Format::from_raw(plan.format.format))
|
|
||||||
.image_color_space(vk::ColorSpaceKHR::from_raw(plan.format.color_space))
|
|
||||||
.image_extent(vk::Extent2D {
|
|
||||||
width: plan.extent.0,
|
|
||||||
height: plan.extent.1,
|
|
||||||
})
|
|
||||||
.image_array_layers(1)
|
|
||||||
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
|
|
||||||
.image_sharing_mode(sharing_mode)
|
|
||||||
.queue_family_indices(&queue_family_indices)
|
|
||||||
.pre_transform(raw_capabilities.current_transform)
|
|
||||||
.composite_alpha(select_composite_alpha(
|
|
||||||
raw_capabilities.supported_composite_alpha,
|
|
||||||
))
|
|
||||||
.present_mode(vk::PresentModeKHR::from_raw(plan.present_mode))
|
|
||||||
.old_swapchain(old_swapchain)
|
|
||||||
.clipped(true);
|
|
||||||
let loader = swapchain::Device::new(&instance.instance, &device.device);
|
|
||||||
// SAFETY: The create info references live instance/device/surface handles for this call.
|
|
||||||
let swapchain = unsafe { loader.create_swapchain(&create_info, None) }
|
|
||||||
.map_err(|error| VulkanSwapchainProbeError::CreateFailed { result: error })?;
|
|
||||||
// SAFETY: The swapchain was created above and the returned image handles are owned by it.
|
|
||||||
let images = match unsafe { loader.get_swapchain_images(swapchain) } {
|
|
||||||
Ok(images) => images,
|
|
||||||
Err(error) => {
|
|
||||||
// SAFETY: The swapchain was created above on this loader/device pair and is destroyed on setup failure.
|
|
||||||
unsafe { loader.destroy_swapchain(swapchain, None) };
|
|
||||||
return Err(VulkanSwapchainProbeError::ImagesFailed { result: error });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(VulkanSwapchainProbe {
|
|
||||||
loader,
|
|
||||||
swapchain,
|
|
||||||
report: VulkanSwapchainReport {
|
|
||||||
schema: 1,
|
|
||||||
plan,
|
|
||||||
image_count: images.len().try_into().unwrap_or(u32::MAX),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select_live_device_candidate(
|
|
||||||
instance: &VulkanInstanceProbe,
|
|
||||||
surface: &VulkanSurfaceProbe,
|
|
||||||
drawable_extent: (u32, u32),
|
|
||||||
) -> Result<SelectedLiveDevice, VulkanRuntimeCapabilityError> {
|
|
||||||
let devices = {
|
|
||||||
// SAFETY: The Vulkan instance is live for this query and no handles are retained.
|
|
||||||
unsafe { instance.instance.enumerate_physical_devices() }.map_err(|error| {
|
|
||||||
VulkanRuntimeCapabilityError::EnumerateDevicesFailed { result: error }
|
|
||||||
})?
|
|
||||||
};
|
|
||||||
let mut best: Option<LiveDeviceCandidate> = None;
|
|
||||||
let mut last_error = None;
|
|
||||||
for (index, device) in devices.iter().copied().enumerate() {
|
|
||||||
let candidate = match live_device_candidate(instance, surface, device, index) {
|
|
||||||
Ok(candidate) => candidate,
|
|
||||||
Err(err) => {
|
|
||||||
last_error = Some(err);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match &best {
|
|
||||||
Some(existing)
|
|
||||||
if compare_reports(&candidate.capability, &existing.capability)
|
|
||||||
!= std::cmp::Ordering::Greater => {}
|
|
||||||
_ => best = Some(candidate),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let best = best.ok_or_else(|| {
|
|
||||||
last_error.unwrap_or(VulkanRuntimeCapabilityError::Capability(
|
|
||||||
VulkanCapabilityError::NoPhysicalDevice,
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
let swapchain = plan_vulkan_swapchain(&VulkanSwapchainRequest {
|
|
||||||
drawable_extent,
|
|
||||||
formats: best.surface_formats,
|
|
||||||
present_modes: best.present_modes,
|
|
||||||
capabilities: best.surface_capabilities,
|
|
||||||
preferred_present_mode: vk::PresentModeKHR::MAILBOX.as_raw(),
|
|
||||||
})
|
|
||||||
.map_err(VulkanRuntimeCapabilityError::Swapchain)?;
|
|
||||||
Ok(SelectedLiveDevice {
|
|
||||||
physical_device: best.physical_device,
|
|
||||||
runtime: VulkanRuntimeCapabilityProbe {
|
|
||||||
capability: best.capability,
|
|
||||||
swapchain,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SelectedLiveDevice {
|
|
||||||
physical_device: vk::PhysicalDevice,
|
|
||||||
runtime: VulkanRuntimeCapabilityProbe,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LiveDeviceCandidate {
|
|
||||||
physical_device: vk::PhysicalDevice,
|
|
||||||
capability: VulkanCapabilityReport,
|
|
||||||
surface_formats: Vec<VulkanSurfaceFormat>,
|
|
||||||
present_modes: Vec<i32>,
|
|
||||||
surface_capabilities: VulkanSwapchainSurfaceCapabilities,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn live_device_candidate(
|
|
||||||
instance: &VulkanInstanceProbe,
|
|
||||||
surface: &VulkanSurfaceProbe,
|
|
||||||
device: vk::PhysicalDevice,
|
|
||||||
index: usize,
|
|
||||||
) -> Result<LiveDeviceCandidate, VulkanRuntimeCapabilityError> {
|
|
||||||
let properties = {
|
|
||||||
// SAFETY: `device` was returned by this live instance and the result is copied by value.
|
|
||||||
unsafe { instance.instance.get_physical_device_properties(device) }
|
|
||||||
};
|
|
||||||
let name = physical_device_name(&properties, index);
|
|
||||||
let queue_properties = {
|
|
||||||
// SAFETY: `device` was returned by this live instance and the result is owned by Rust.
|
|
||||||
unsafe {
|
|
||||||
instance
|
|
||||||
.instance
|
|
||||||
.get_physical_device_queue_family_properties(device)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let extensions = live_device_extensions(instance, device, &name)?;
|
|
||||||
let surface_formats = live_surface_formats(surface, device, &name)?;
|
|
||||||
let present_modes = live_present_modes(surface, device, &name)?;
|
|
||||||
let surface_capabilities = live_surface_capabilities(surface, device, &name)?;
|
|
||||||
let queue_families = queue_properties
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(queue_index, properties)| {
|
|
||||||
let index = u32::try_from(queue_index).unwrap_or(u32::MAX);
|
|
||||||
let present = {
|
|
||||||
// SAFETY: The physical device, surface and queue-family index are live query inputs.
|
|
||||||
unsafe {
|
|
||||||
surface.loader.get_physical_device_surface_support(
|
|
||||||
device,
|
|
||||||
index,
|
|
||||||
surface.surface,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map_err(|error| VulkanRuntimeCapabilityError::PresentSupportFailed {
|
|
||||||
device: name.clone(),
|
|
||||||
queue_family: index,
|
|
||||||
result: error,
|
|
||||||
})?;
|
|
||||||
Ok(VulkanQueueFamily {
|
|
||||||
index,
|
|
||||||
graphics: properties.queue_flags.contains(vk::QueueFlags::GRAPHICS),
|
|
||||||
present,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>, VulkanRuntimeCapabilityError>>()?;
|
|
||||||
let record = VulkanPhysicalDeviceRecord {
|
|
||||||
name,
|
|
||||||
api_version: properties.api_version,
|
|
||||||
device_type: match properties.device_type {
|
|
||||||
vk::PhysicalDeviceType::DISCRETE_GPU => VulkanDeviceType::DiscreteGpu,
|
|
||||||
vk::PhysicalDeviceType::INTEGRATED_GPU => VulkanDeviceType::IntegratedGpu,
|
|
||||||
vk::PhysicalDeviceType::CPU => VulkanDeviceType::Cpu,
|
|
||||||
_ => VulkanDeviceType::Other,
|
|
||||||
},
|
|
||||||
extensions,
|
|
||||||
queue_families,
|
|
||||||
surface_formats: surface_formats.clone(),
|
|
||||||
present_modes: present_modes.clone(),
|
|
||||||
surface_capabilities,
|
|
||||||
};
|
|
||||||
let capability = validate_device(&record).map_err(VulkanRuntimeCapabilityError::Capability)?;
|
|
||||||
Ok(LiveDeviceCandidate {
|
|
||||||
physical_device: device,
|
|
||||||
capability,
|
|
||||||
surface_formats,
|
|
||||||
present_modes,
|
|
||||||
surface_capabilities,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unique_queue_families(graphics: u32, present: u32) -> Vec<u32> {
|
|
||||||
if graphics == present {
|
|
||||||
vec![graphics]
|
|
||||||
} else {
|
|
||||||
vec![graphics, present]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn device_extension_cstrings(values: &[String]) -> Result<Vec<CString>, String> {
|
|
||||||
values
|
|
||||||
.iter()
|
|
||||||
.map(|extension| CString::new(extension.as_str()).map_err(|_| extension.clone()))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn physical_device_name(properties: &vk::PhysicalDeviceProperties, index: usize) -> String {
|
|
||||||
// SAFETY: Vulkan device names are fixed-size NUL-terminated C strings per the spec.
|
|
||||||
let name = unsafe { CStr::from_ptr(properties.device_name.as_ptr()) }
|
|
||||||
.to_string_lossy()
|
|
||||||
.trim()
|
|
||||||
.to_string();
|
|
||||||
if name.is_empty() {
|
|
||||||
format!("physical-device-{index}")
|
|
||||||
} else {
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn live_device_extensions(
|
|
||||||
instance: &VulkanInstanceProbe,
|
|
||||||
device: vk::PhysicalDevice,
|
|
||||||
name: &str,
|
|
||||||
) -> Result<Vec<String>, VulkanRuntimeCapabilityError> {
|
|
||||||
let properties = {
|
|
||||||
// SAFETY: `device` was returned by this live instance and no borrowed data escapes.
|
|
||||||
unsafe {
|
|
||||||
instance
|
|
||||||
.instance
|
|
||||||
.enumerate_device_extension_properties(device)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map_err(
|
|
||||||
|error| VulkanRuntimeCapabilityError::EnumerateDeviceExtensionsFailed {
|
|
||||||
device: name.to_string(),
|
|
||||||
result: error,
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
let mut extensions = properties
|
|
||||||
.iter()
|
|
||||||
.map(|property| {
|
|
||||||
// SAFETY: Vulkan extension names are fixed-size NUL-terminated C strings per the spec.
|
|
||||||
unsafe { CStr::from_ptr(property.extension_name.as_ptr()) }
|
|
||||||
.to_string_lossy()
|
|
||||||
.into_owned()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
extensions.sort();
|
|
||||||
extensions.dedup();
|
|
||||||
Ok(extensions)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn live_surface_formats(
|
|
||||||
surface: &VulkanSurfaceProbe,
|
|
||||||
device: vk::PhysicalDevice,
|
|
||||||
name: &str,
|
|
||||||
) -> Result<Vec<VulkanSurfaceFormat>, VulkanRuntimeCapabilityError> {
|
|
||||||
let formats = {
|
|
||||||
// SAFETY: The physical device and surface are live query inputs and no handles are retained.
|
|
||||||
unsafe {
|
|
||||||
surface
|
|
||||||
.loader
|
|
||||||
.get_physical_device_surface_formats(device, surface.surface)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map_err(|error| VulkanRuntimeCapabilityError::SurfaceFormatsFailed {
|
|
||||||
device: name.to_string(),
|
|
||||||
result: error,
|
|
||||||
})?;
|
|
||||||
Ok(formats
|
|
||||||
.into_iter()
|
|
||||||
.map(|format| VulkanSurfaceFormat {
|
|
||||||
format: format.format.as_raw(),
|
|
||||||
color_space: format.color_space.as_raw(),
|
|
||||||
})
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn live_present_modes(
|
|
||||||
surface: &VulkanSurfaceProbe,
|
|
||||||
device: vk::PhysicalDevice,
|
|
||||||
name: &str,
|
|
||||||
) -> Result<Vec<i32>, VulkanRuntimeCapabilityError> {
|
|
||||||
let modes = {
|
|
||||||
// SAFETY: The physical device and surface are live query inputs and no handles are retained.
|
|
||||||
unsafe {
|
|
||||||
surface
|
|
||||||
.loader
|
|
||||||
.get_physical_device_surface_present_modes(device, surface.surface)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map_err(|error| VulkanRuntimeCapabilityError::PresentModesFailed {
|
|
||||||
device: name.to_string(),
|
|
||||||
result: error,
|
|
||||||
})?;
|
|
||||||
Ok(modes.into_iter().map(vk::PresentModeKHR::as_raw).collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn live_surface_capabilities(
|
|
||||||
surface: &VulkanSurfaceProbe,
|
|
||||||
device: vk::PhysicalDevice,
|
|
||||||
name: &str,
|
|
||||||
) -> Result<VulkanSwapchainSurfaceCapabilities, VulkanRuntimeCapabilityError> {
|
|
||||||
let capabilities = {
|
|
||||||
// SAFETY: The physical device and surface are live query inputs and no handles are retained.
|
|
||||||
unsafe {
|
|
||||||
surface
|
|
||||||
.loader
|
|
||||||
.get_physical_device_surface_capabilities(device, surface.surface)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.map_err(
|
|
||||||
|error| VulkanRuntimeCapabilityError::SurfaceCapabilitiesFailed {
|
|
||||||
device: name.to_string(),
|
|
||||||
result: error,
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
Ok(VulkanSwapchainSurfaceCapabilities {
|
|
||||||
current_extent: if capabilities.current_extent.width == u32::MAX {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some((
|
|
||||||
capabilities.current_extent.width,
|
|
||||||
capabilities.current_extent.height,
|
|
||||||
))
|
|
||||||
},
|
|
||||||
min_extent: (
|
|
||||||
capabilities.min_image_extent.width,
|
|
||||||
capabilities.min_image_extent.height,
|
|
||||||
),
|
|
||||||
max_extent: (
|
|
||||||
capabilities.max_image_extent.width,
|
|
||||||
capabilities.max_image_extent.height,
|
|
||||||
),
|
|
||||||
min_image_count: capabilities.min_image_count,
|
|
||||||
max_image_count: capabilities.max_image_count,
|
|
||||||
supported_usage_flags: capabilities.supported_usage_flags.as_raw(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -0,0 +1,786 @@
|
|||||||
|
#![allow(unsafe_code)]
|
||||||
|
|
||||||
|
use ash::{khr::swapchain, vk};
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
|
||||||
|
use super::{VulkanInstanceProbe, VulkanSurfaceProbe};
|
||||||
|
use crate::policy::{
|
||||||
|
compare_reports, plan_vulkan_swapchain, select_composite_alpha, validate_device,
|
||||||
|
VulkanCapabilityError, VulkanCapabilityReport, VulkanDeviceType, VulkanPhysicalDeviceRecord,
|
||||||
|
VulkanQueueFamily, VulkanSurfaceFormat, VulkanSwapchainError, VulkanSwapchainPlan,
|
||||||
|
VulkanSwapchainRequest, VulkanSwapchainSurfaceCapabilities,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Live Vulkan device/surface capability probe.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct VulkanRuntimeCapabilityProbe {
|
||||||
|
/// Selected device/queue capability report.
|
||||||
|
pub capability: VulkanCapabilityReport,
|
||||||
|
/// Swapchain plan built from the selected device and live surface capabilities.
|
||||||
|
pub swapchain: VulkanSwapchainPlan,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Created Vulkan logical device probe.
|
||||||
|
pub struct VulkanLogicalDeviceProbe {
|
||||||
|
device: ash::Device,
|
||||||
|
physical_device: vk::PhysicalDevice,
|
||||||
|
/// Runtime capability report used for device selection.
|
||||||
|
pub runtime: VulkanRuntimeCapabilityProbe,
|
||||||
|
/// Deterministic logical device creation report.
|
||||||
|
pub report: VulkanLogicalDeviceReport,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VulkanLogicalDeviceProbe {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// SAFETY: The logical device was created by this probe and is destroyed once during drop.
|
||||||
|
unsafe { self.device.destroy_device(None) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VulkanLogicalDeviceProbe {
|
||||||
|
/// Returns the graphics queue selected by the Stage 0 policy.
|
||||||
|
#[must_use]
|
||||||
|
pub fn graphics_queue(&self) -> vk::Queue {
|
||||||
|
// SAFETY: The queue-family index belongs to this live logical device.
|
||||||
|
unsafe {
|
||||||
|
self.device
|
||||||
|
.get_device_queue(self.report.graphics_queue_family, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the presentation queue selected by the Stage 0 policy.
|
||||||
|
#[must_use]
|
||||||
|
pub fn present_queue(&self) -> vk::Queue {
|
||||||
|
// SAFETY: The queue-family index belongs to this live logical device.
|
||||||
|
unsafe {
|
||||||
|
self.device
|
||||||
|
.get_device_queue(self.report.present_queue_family, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a shared reference to the live logical device.
|
||||||
|
#[must_use]
|
||||||
|
pub fn device(&self) -> &ash::Device {
|
||||||
|
&self.device
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the selected physical device handle.
|
||||||
|
#[must_use]
|
||||||
|
pub fn physical_device(&self) -> vk::PhysicalDevice {
|
||||||
|
self.physical_device
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logical device creation report.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct VulkanLogicalDeviceReport {
|
||||||
|
/// Report schema version.
|
||||||
|
pub schema: u32,
|
||||||
|
/// Selected physical device name.
|
||||||
|
pub device_name: String,
|
||||||
|
/// Graphics queue-family index used by the logical device.
|
||||||
|
pub graphics_queue_family: u32,
|
||||||
|
/// Present queue-family index used by the logical device.
|
||||||
|
pub present_queue_family: u32,
|
||||||
|
/// Enabled device extensions.
|
||||||
|
pub enabled_extensions: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Created Vulkan swapchain probe.
|
||||||
|
pub struct VulkanSwapchainProbe {
|
||||||
|
loader: swapchain::Device,
|
||||||
|
swapchain: vk::SwapchainKHR,
|
||||||
|
/// Deterministic swapchain creation report.
|
||||||
|
pub report: VulkanSwapchainReport,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for VulkanSwapchainProbe {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// SAFETY: The swapchain was created by this probe and is destroyed once during drop.
|
||||||
|
unsafe { self.loader.destroy_swapchain(self.swapchain, None) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VulkanSwapchainProbe {
|
||||||
|
/// Returns the live swapchain handle.
|
||||||
|
#[must_use]
|
||||||
|
pub fn swapchain(&self) -> vk::SwapchainKHR {
|
||||||
|
self.swapchain
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the swapchain extension loader for this live swapchain.
|
||||||
|
#[must_use]
|
||||||
|
pub fn loader(&self) -> &swapchain::Device {
|
||||||
|
&self.loader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runtime swapchain creation report.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct VulkanSwapchainReport {
|
||||||
|
/// Report schema version.
|
||||||
|
pub schema: u32,
|
||||||
|
/// Deterministic swapchain policy used for creation.
|
||||||
|
pub plan: VulkanSwapchainPlan,
|
||||||
|
/// Number of images returned by `vkGetSwapchainImagesKHR`.
|
||||||
|
pub image_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Live Vulkan device/surface capability probe error.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum VulkanRuntimeCapabilityError {
|
||||||
|
/// Physical device enumeration failed.
|
||||||
|
EnumerateDevicesFailed {
|
||||||
|
/// Vulkan result.
|
||||||
|
result: vk::Result,
|
||||||
|
},
|
||||||
|
/// Device extension enumeration failed.
|
||||||
|
EnumerateDeviceExtensionsFailed {
|
||||||
|
/// Device name or index context.
|
||||||
|
device: String,
|
||||||
|
/// Vulkan result.
|
||||||
|
result: vk::Result,
|
||||||
|
},
|
||||||
|
/// Queue-family present support query failed.
|
||||||
|
PresentSupportFailed {
|
||||||
|
/// Device name.
|
||||||
|
device: String,
|
||||||
|
/// Queue-family index.
|
||||||
|
queue_family: u32,
|
||||||
|
/// Vulkan result.
|
||||||
|
result: vk::Result,
|
||||||
|
},
|
||||||
|
/// Surface format query failed.
|
||||||
|
SurfaceFormatsFailed {
|
||||||
|
/// Device name.
|
||||||
|
device: String,
|
||||||
|
/// Vulkan result.
|
||||||
|
result: vk::Result,
|
||||||
|
},
|
||||||
|
/// Surface capability query failed.
|
||||||
|
SurfaceCapabilitiesFailed {
|
||||||
|
/// Device name.
|
||||||
|
device: String,
|
||||||
|
/// Vulkan result.
|
||||||
|
result: vk::Result,
|
||||||
|
},
|
||||||
|
/// Present mode query failed.
|
||||||
|
PresentModesFailed {
|
||||||
|
/// Device name.
|
||||||
|
device: String,
|
||||||
|
/// Vulkan result.
|
||||||
|
result: vk::Result,
|
||||||
|
},
|
||||||
|
/// No device satisfied Stage 0 capability policy.
|
||||||
|
Capability(VulkanCapabilityError),
|
||||||
|
/// Live surface capabilities could not produce a swapchain plan.
|
||||||
|
Swapchain(VulkanSwapchainError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for VulkanRuntimeCapabilityError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::EnumerateDevicesFailed { result } => {
|
||||||
|
write!(f, "Vulkan physical device enumeration failed: {result:?}")
|
||||||
|
}
|
||||||
|
Self::EnumerateDeviceExtensionsFailed { device, result } => write!(
|
||||||
|
f,
|
||||||
|
"Vulkan device {device} extension enumeration failed: {result:?}"
|
||||||
|
),
|
||||||
|
Self::PresentSupportFailed {
|
||||||
|
device,
|
||||||
|
queue_family,
|
||||||
|
result,
|
||||||
|
} => write!(
|
||||||
|
f,
|
||||||
|
"Vulkan device {device} queue family {queue_family} present support query failed: {result:?}"
|
||||||
|
),
|
||||||
|
Self::SurfaceFormatsFailed { device, result } => write!(
|
||||||
|
f,
|
||||||
|
"Vulkan device {device} surface format query failed: {result:?}"
|
||||||
|
),
|
||||||
|
Self::SurfaceCapabilitiesFailed { device, result } => write!(
|
||||||
|
f,
|
||||||
|
"Vulkan device {device} surface capabilities query failed: {result:?}"
|
||||||
|
),
|
||||||
|
Self::PresentModesFailed { device, result } => write!(
|
||||||
|
f,
|
||||||
|
"Vulkan device {device} present mode query failed: {result:?}"
|
||||||
|
),
|
||||||
|
Self::Capability(error) => write!(f, "{error}"),
|
||||||
|
Self::Swapchain(error) => write!(f, "{error}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for VulkanRuntimeCapabilityError {}
|
||||||
|
|
||||||
|
/// Vulkan logical device creation error.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum VulkanLogicalDeviceError {
|
||||||
|
/// Runtime capability probing failed.
|
||||||
|
Runtime(VulkanRuntimeCapabilityError),
|
||||||
|
/// Device extension name contained an interior NUL byte.
|
||||||
|
InvalidExtensionName {
|
||||||
|
/// Invalid extension name.
|
||||||
|
extension: String,
|
||||||
|
},
|
||||||
|
/// Logical device creation failed.
|
||||||
|
CreateFailed {
|
||||||
|
/// Selected device name.
|
||||||
|
device: String,
|
||||||
|
/// Vulkan result.
|
||||||
|
result: vk::Result,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for VulkanLogicalDeviceError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Runtime(error) => write!(f, "{error}"),
|
||||||
|
Self::InvalidExtensionName { extension } => write!(
|
||||||
|
f,
|
||||||
|
"Vulkan device extension name contains an interior NUL byte: {extension:?}"
|
||||||
|
),
|
||||||
|
Self::CreateFailed { device, result } => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Vulkan logical device creation failed for {device}: {result:?}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for VulkanLogicalDeviceError {}
|
||||||
|
|
||||||
|
/// Vulkan swapchain creation error.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum VulkanSwapchainProbeError {
|
||||||
|
/// Live runtime capability probing failed before swapchain creation.
|
||||||
|
Runtime(VulkanRuntimeCapabilityError),
|
||||||
|
/// Deterministic swapchain planning failed before create.
|
||||||
|
Plan(VulkanSwapchainError),
|
||||||
|
/// Surface capability query failed.
|
||||||
|
SurfaceCapabilitiesFailed {
|
||||||
|
/// Vulkan result.
|
||||||
|
result: vk::Result,
|
||||||
|
},
|
||||||
|
/// Swapchain creation failed.
|
||||||
|
CreateFailed {
|
||||||
|
/// Vulkan result.
|
||||||
|
result: vk::Result,
|
||||||
|
},
|
||||||
|
/// Swapchain image query failed.
|
||||||
|
ImagesFailed {
|
||||||
|
/// Vulkan result.
|
||||||
|
result: vk::Result,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for VulkanSwapchainProbeError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Runtime(error) => write!(f, "{error}"),
|
||||||
|
Self::Plan(error) => write!(f, "{error}"),
|
||||||
|
Self::SurfaceCapabilitiesFailed { result } => {
|
||||||
|
write!(f, "Vulkan surface capabilities query failed: {result:?}")
|
||||||
|
}
|
||||||
|
Self::CreateFailed { result } => {
|
||||||
|
write!(f, "Vulkan swapchain creation failed: {result:?}")
|
||||||
|
}
|
||||||
|
Self::ImagesFailed { result } => {
|
||||||
|
write!(f, "Vulkan swapchain image query failed: {result:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for VulkanSwapchainProbeError {}
|
||||||
|
|
||||||
|
/// Probes live Vulkan device, queue, surface and swapchain capabilities.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`VulkanRuntimeCapabilityError`] when device enumeration, surface
|
||||||
|
/// capability queries, Stage 0 device selection, or swapchain planning fails.
|
||||||
|
pub fn probe_vulkan_runtime_capabilities(
|
||||||
|
instance: &VulkanInstanceProbe,
|
||||||
|
surface: &VulkanSurfaceProbe,
|
||||||
|
drawable_extent: (u32, u32),
|
||||||
|
) -> Result<VulkanRuntimeCapabilityProbe, VulkanRuntimeCapabilityError> {
|
||||||
|
let selected = select_live_device_candidate(instance, surface, drawable_extent)?;
|
||||||
|
Ok(selected.runtime)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a Vulkan logical device for the selected live surface-capable device.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`VulkanLogicalDeviceError`] when runtime capability probing fails,
|
||||||
|
/// device extension names are invalid, or `vkCreateDevice` fails.
|
||||||
|
pub fn create_vulkan_logical_device_probe(
|
||||||
|
instance: &VulkanInstanceProbe,
|
||||||
|
surface: &VulkanSurfaceProbe,
|
||||||
|
drawable_extent: (u32, u32),
|
||||||
|
) -> Result<VulkanLogicalDeviceProbe, VulkanLogicalDeviceError> {
|
||||||
|
let selected = select_live_device_candidate(instance, surface, drawable_extent)
|
||||||
|
.map_err(VulkanLogicalDeviceError::Runtime)?;
|
||||||
|
let capability = &selected.runtime.capability;
|
||||||
|
let queue_priorities = [1.0_f32];
|
||||||
|
let queue_families = unique_queue_families(
|
||||||
|
capability.graphics_queue_family,
|
||||||
|
capability.present_queue_family,
|
||||||
|
);
|
||||||
|
let queue_infos = queue_families
|
||||||
|
.iter()
|
||||||
|
.map(|queue_family| {
|
||||||
|
vk::DeviceQueueCreateInfo::default()
|
||||||
|
.queue_family_index(*queue_family)
|
||||||
|
.queue_priorities(&queue_priorities)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let extension_names = device_extension_cstrings(&capability.enabled_extensions)
|
||||||
|
.map_err(|extension| VulkanLogicalDeviceError::InvalidExtensionName { extension })?;
|
||||||
|
let extension_ptrs = extension_names
|
||||||
|
.iter()
|
||||||
|
.map(|extension| extension.as_ptr())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let create_info = vk::DeviceCreateInfo::default()
|
||||||
|
.queue_create_infos(&queue_infos)
|
||||||
|
.enabled_extension_names(&extension_ptrs);
|
||||||
|
// SAFETY: `selected.physical_device` belongs to `instance`; create data lives for the call.
|
||||||
|
let device = unsafe {
|
||||||
|
instance
|
||||||
|
.instance
|
||||||
|
.create_device(selected.physical_device, &create_info, None)
|
||||||
|
}
|
||||||
|
.map_err(|error| VulkanLogicalDeviceError::CreateFailed {
|
||||||
|
device: capability.device_name.clone(),
|
||||||
|
result: error,
|
||||||
|
})?;
|
||||||
|
// SAFETY: Queue family indices came from validated live queue families requested above.
|
||||||
|
let _graphics_queue = unsafe { device.get_device_queue(capability.graphics_queue_family, 0) };
|
||||||
|
// SAFETY: Queue family indices came from validated live queue families requested above.
|
||||||
|
let _present_queue = unsafe { device.get_device_queue(capability.present_queue_family, 0) };
|
||||||
|
Ok(VulkanLogicalDeviceProbe {
|
||||||
|
device,
|
||||||
|
physical_device: selected.physical_device,
|
||||||
|
report: VulkanLogicalDeviceReport {
|
||||||
|
schema: 1,
|
||||||
|
device_name: capability.device_name.clone(),
|
||||||
|
graphics_queue_family: capability.graphics_queue_family,
|
||||||
|
present_queue_family: capability.present_queue_family,
|
||||||
|
enabled_extensions: capability.enabled_extensions.clone(),
|
||||||
|
},
|
||||||
|
runtime: selected.runtime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a Vulkan swapchain for the live logical device and surface.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`VulkanSwapchainProbeError`] when live surface capability queries,
|
||||||
|
/// swapchain creation, or swapchain image enumeration fails.
|
||||||
|
pub fn create_vulkan_swapchain_probe(
|
||||||
|
instance: &VulkanInstanceProbe,
|
||||||
|
surface: &VulkanSurfaceProbe,
|
||||||
|
device: &VulkanLogicalDeviceProbe,
|
||||||
|
) -> Result<VulkanSwapchainProbe, VulkanSwapchainProbeError> {
|
||||||
|
create_vulkan_swapchain_probe_for_extent(
|
||||||
|
instance,
|
||||||
|
surface,
|
||||||
|
device,
|
||||||
|
device.runtime.swapchain.extent,
|
||||||
|
vk::SwapchainKHR::null(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a Vulkan swapchain for the live logical device and surface at a specific extent.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`VulkanSwapchainProbeError`] when live surface capability queries,
|
||||||
|
/// swapchain creation, or swapchain image enumeration fails.
|
||||||
|
pub fn create_vulkan_swapchain_probe_for_extent(
|
||||||
|
instance: &VulkanInstanceProbe,
|
||||||
|
surface: &VulkanSurfaceProbe,
|
||||||
|
device: &VulkanLogicalDeviceProbe,
|
||||||
|
drawable_extent: (u32, u32),
|
||||||
|
old_swapchain: vk::SwapchainKHR,
|
||||||
|
) -> Result<VulkanSwapchainProbe, VulkanSwapchainProbeError> {
|
||||||
|
let raw_capabilities = {
|
||||||
|
// SAFETY: The physical device and surface are live query inputs and no handles are retained.
|
||||||
|
unsafe {
|
||||||
|
surface
|
||||||
|
.loader
|
||||||
|
.get_physical_device_surface_capabilities(device.physical_device(), surface.surface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map_err(|error| VulkanSwapchainProbeError::SurfaceCapabilitiesFailed { result: error })?;
|
||||||
|
let surface_formats = live_surface_formats(
|
||||||
|
surface,
|
||||||
|
device.physical_device(),
|
||||||
|
&device.report.device_name,
|
||||||
|
)
|
||||||
|
.map_err(VulkanSwapchainProbeError::Runtime)?;
|
||||||
|
let present_modes = live_present_modes(
|
||||||
|
surface,
|
||||||
|
device.physical_device(),
|
||||||
|
&device.report.device_name,
|
||||||
|
)
|
||||||
|
.map_err(VulkanSwapchainProbeError::Runtime)?;
|
||||||
|
let capabilities = live_surface_capabilities(
|
||||||
|
surface,
|
||||||
|
device.physical_device(),
|
||||||
|
&device.report.device_name,
|
||||||
|
)
|
||||||
|
.map_err(VulkanSwapchainProbeError::Runtime)?;
|
||||||
|
let plan = plan_vulkan_swapchain(&VulkanSwapchainRequest {
|
||||||
|
drawable_extent,
|
||||||
|
formats: surface_formats,
|
||||||
|
present_modes,
|
||||||
|
capabilities,
|
||||||
|
preferred_present_mode: vk::PresentModeKHR::MAILBOX.as_raw(),
|
||||||
|
})
|
||||||
|
.map_err(VulkanSwapchainProbeError::Plan)?;
|
||||||
|
let queue_family_indices = unique_queue_families(
|
||||||
|
device.runtime.capability.graphics_queue_family,
|
||||||
|
device.runtime.capability.present_queue_family,
|
||||||
|
);
|
||||||
|
let sharing_mode = if queue_family_indices.len() > 1 {
|
||||||
|
vk::SharingMode::CONCURRENT
|
||||||
|
} else {
|
||||||
|
vk::SharingMode::EXCLUSIVE
|
||||||
|
};
|
||||||
|
let create_info = vk::SwapchainCreateInfoKHR::default()
|
||||||
|
.surface(surface.surface)
|
||||||
|
.min_image_count(plan.image_count)
|
||||||
|
.image_format(vk::Format::from_raw(plan.format.format))
|
||||||
|
.image_color_space(vk::ColorSpaceKHR::from_raw(plan.format.color_space))
|
||||||
|
.image_extent(vk::Extent2D {
|
||||||
|
width: plan.extent.0,
|
||||||
|
height: plan.extent.1,
|
||||||
|
})
|
||||||
|
.image_array_layers(1)
|
||||||
|
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
|
||||||
|
.image_sharing_mode(sharing_mode)
|
||||||
|
.queue_family_indices(&queue_family_indices)
|
||||||
|
.pre_transform(raw_capabilities.current_transform)
|
||||||
|
.composite_alpha(select_composite_alpha(
|
||||||
|
raw_capabilities.supported_composite_alpha,
|
||||||
|
))
|
||||||
|
.present_mode(vk::PresentModeKHR::from_raw(plan.present_mode))
|
||||||
|
.old_swapchain(old_swapchain)
|
||||||
|
.clipped(true);
|
||||||
|
let loader = swapchain::Device::new(&instance.instance, device.device());
|
||||||
|
// SAFETY: The create info references live instance/device/surface handles for this call.
|
||||||
|
let swapchain = unsafe { loader.create_swapchain(&create_info, None) }
|
||||||
|
.map_err(|error| VulkanSwapchainProbeError::CreateFailed { result: error })?;
|
||||||
|
// SAFETY: The swapchain was created above and the returned image handles are owned by it.
|
||||||
|
let images = match unsafe { loader.get_swapchain_images(swapchain) } {
|
||||||
|
Ok(images) => images,
|
||||||
|
Err(error) => {
|
||||||
|
// SAFETY: The swapchain was created above on this loader/device pair and is destroyed on setup failure.
|
||||||
|
unsafe { loader.destroy_swapchain(swapchain, None) };
|
||||||
|
return Err(VulkanSwapchainProbeError::ImagesFailed { result: error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(VulkanSwapchainProbe {
|
||||||
|
loader,
|
||||||
|
swapchain,
|
||||||
|
report: VulkanSwapchainReport {
|
||||||
|
schema: 1,
|
||||||
|
plan,
|
||||||
|
image_count: images.len().try_into().unwrap_or(u32::MAX),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SelectedLiveDevice {
|
||||||
|
physical_device: vk::PhysicalDevice,
|
||||||
|
runtime: VulkanRuntimeCapabilityProbe,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LiveDeviceCandidate {
|
||||||
|
physical_device: vk::PhysicalDevice,
|
||||||
|
capability: VulkanCapabilityReport,
|
||||||
|
surface_formats: Vec<VulkanSurfaceFormat>,
|
||||||
|
present_modes: Vec<i32>,
|
||||||
|
surface_capabilities: VulkanSwapchainSurfaceCapabilities,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_live_device_candidate(
|
||||||
|
instance: &VulkanInstanceProbe,
|
||||||
|
surface: &VulkanSurfaceProbe,
|
||||||
|
drawable_extent: (u32, u32),
|
||||||
|
) -> Result<SelectedLiveDevice, VulkanRuntimeCapabilityError> {
|
||||||
|
let devices = {
|
||||||
|
// SAFETY: The Vulkan instance is live for this query and no handles are retained.
|
||||||
|
unsafe { instance.instance.enumerate_physical_devices() }.map_err(|error| {
|
||||||
|
VulkanRuntimeCapabilityError::EnumerateDevicesFailed { result: error }
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
let mut best: Option<LiveDeviceCandidate> = None;
|
||||||
|
let mut last_error = None;
|
||||||
|
for (index, device) in devices.iter().copied().enumerate() {
|
||||||
|
let candidate = match live_device_candidate(instance, surface, device, index) {
|
||||||
|
Ok(candidate) => candidate,
|
||||||
|
Err(err) => {
|
||||||
|
last_error = Some(err);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match &best {
|
||||||
|
Some(existing)
|
||||||
|
if compare_reports(&candidate.capability, &existing.capability)
|
||||||
|
!= std::cmp::Ordering::Greater => {}
|
||||||
|
_ => best = Some(candidate),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let best = best.ok_or_else(|| {
|
||||||
|
last_error.unwrap_or(VulkanRuntimeCapabilityError::Capability(
|
||||||
|
VulkanCapabilityError::NoPhysicalDevice,
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
let swapchain = plan_vulkan_swapchain(&VulkanSwapchainRequest {
|
||||||
|
drawable_extent,
|
||||||
|
formats: best.surface_formats,
|
||||||
|
present_modes: best.present_modes,
|
||||||
|
capabilities: best.surface_capabilities,
|
||||||
|
preferred_present_mode: vk::PresentModeKHR::MAILBOX.as_raw(),
|
||||||
|
})
|
||||||
|
.map_err(VulkanRuntimeCapabilityError::Swapchain)?;
|
||||||
|
Ok(SelectedLiveDevice {
|
||||||
|
physical_device: best.physical_device,
|
||||||
|
runtime: VulkanRuntimeCapabilityProbe {
|
||||||
|
capability: best.capability,
|
||||||
|
swapchain,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn live_device_candidate(
|
||||||
|
instance: &VulkanInstanceProbe,
|
||||||
|
surface: &VulkanSurfaceProbe,
|
||||||
|
device: vk::PhysicalDevice,
|
||||||
|
index: usize,
|
||||||
|
) -> Result<LiveDeviceCandidate, VulkanRuntimeCapabilityError> {
|
||||||
|
let properties = {
|
||||||
|
// SAFETY: `device` was returned by this live instance and the result is copied by value.
|
||||||
|
unsafe { instance.instance.get_physical_device_properties(device) }
|
||||||
|
};
|
||||||
|
let name = physical_device_name(&properties, index);
|
||||||
|
let queue_properties = {
|
||||||
|
// SAFETY: `device` was returned by this live instance and the result is owned by Rust.
|
||||||
|
unsafe {
|
||||||
|
instance
|
||||||
|
.instance
|
||||||
|
.get_physical_device_queue_family_properties(device)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let extensions = live_device_extensions(instance, device, &name)?;
|
||||||
|
let surface_formats = live_surface_formats(surface, device, &name)?;
|
||||||
|
let present_modes = live_present_modes(surface, device, &name)?;
|
||||||
|
let surface_capabilities = live_surface_capabilities(surface, device, &name)?;
|
||||||
|
let queue_families = queue_properties
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(queue_index, properties)| {
|
||||||
|
let index = u32::try_from(queue_index).unwrap_or(u32::MAX);
|
||||||
|
let present = {
|
||||||
|
// SAFETY: The physical device, surface and queue-family index are live query inputs.
|
||||||
|
unsafe {
|
||||||
|
surface.loader.get_physical_device_surface_support(
|
||||||
|
device,
|
||||||
|
index,
|
||||||
|
surface.surface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map_err(|error| VulkanRuntimeCapabilityError::PresentSupportFailed {
|
||||||
|
device: name.clone(),
|
||||||
|
queue_family: index,
|
||||||
|
result: error,
|
||||||
|
})?;
|
||||||
|
Ok(VulkanQueueFamily {
|
||||||
|
index,
|
||||||
|
graphics: properties.queue_flags.contains(vk::QueueFlags::GRAPHICS),
|
||||||
|
present,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, VulkanRuntimeCapabilityError>>()?;
|
||||||
|
let record = VulkanPhysicalDeviceRecord {
|
||||||
|
name,
|
||||||
|
api_version: properties.api_version,
|
||||||
|
device_type: match properties.device_type {
|
||||||
|
vk::PhysicalDeviceType::DISCRETE_GPU => VulkanDeviceType::DiscreteGpu,
|
||||||
|
vk::PhysicalDeviceType::INTEGRATED_GPU => VulkanDeviceType::IntegratedGpu,
|
||||||
|
vk::PhysicalDeviceType::CPU => VulkanDeviceType::Cpu,
|
||||||
|
_ => VulkanDeviceType::Other,
|
||||||
|
},
|
||||||
|
extensions,
|
||||||
|
queue_families,
|
||||||
|
surface_formats: surface_formats.clone(),
|
||||||
|
present_modes: present_modes.clone(),
|
||||||
|
surface_capabilities,
|
||||||
|
};
|
||||||
|
let capability = validate_device(&record).map_err(VulkanRuntimeCapabilityError::Capability)?;
|
||||||
|
Ok(LiveDeviceCandidate {
|
||||||
|
physical_device: device,
|
||||||
|
capability,
|
||||||
|
surface_formats,
|
||||||
|
present_modes,
|
||||||
|
surface_capabilities,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unique_queue_families(graphics: u32, present: u32) -> Vec<u32> {
|
||||||
|
if graphics == present {
|
||||||
|
vec![graphics]
|
||||||
|
} else {
|
||||||
|
vec![graphics, present]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn device_extension_cstrings(values: &[String]) -> Result<Vec<CString>, String> {
|
||||||
|
values
|
||||||
|
.iter()
|
||||||
|
.map(|extension| CString::new(extension.as_str()).map_err(|_| extension.clone()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn physical_device_name(properties: &vk::PhysicalDeviceProperties, index: usize) -> String {
|
||||||
|
// SAFETY: Vulkan device names are fixed-size NUL-terminated C strings per the spec.
|
||||||
|
let name = unsafe { CStr::from_ptr(properties.device_name.as_ptr()) }
|
||||||
|
.to_string_lossy()
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
if name.is_empty() {
|
||||||
|
format!("physical-device-{index}")
|
||||||
|
} else {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn live_device_extensions(
|
||||||
|
instance: &VulkanInstanceProbe,
|
||||||
|
device: vk::PhysicalDevice,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<Vec<String>, VulkanRuntimeCapabilityError> {
|
||||||
|
let properties = {
|
||||||
|
// SAFETY: `device` was returned by this live instance and no borrowed data escapes.
|
||||||
|
unsafe {
|
||||||
|
instance
|
||||||
|
.instance
|
||||||
|
.enumerate_device_extension_properties(device)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map_err(
|
||||||
|
|error| VulkanRuntimeCapabilityError::EnumerateDeviceExtensionsFailed {
|
||||||
|
device: name.to_string(),
|
||||||
|
result: error,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
let mut extensions = properties
|
||||||
|
.iter()
|
||||||
|
.map(|property| {
|
||||||
|
// SAFETY: Vulkan extension names are fixed-size NUL-terminated C strings per the spec.
|
||||||
|
unsafe { CStr::from_ptr(property.extension_name.as_ptr()) }
|
||||||
|
.to_string_lossy()
|
||||||
|
.into_owned()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
extensions.sort();
|
||||||
|
extensions.dedup();
|
||||||
|
Ok(extensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn live_surface_formats(
|
||||||
|
surface: &VulkanSurfaceProbe,
|
||||||
|
device: vk::PhysicalDevice,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<Vec<VulkanSurfaceFormat>, VulkanRuntimeCapabilityError> {
|
||||||
|
let formats = {
|
||||||
|
// SAFETY: The physical device and surface are live query inputs and no handles are retained.
|
||||||
|
unsafe {
|
||||||
|
surface
|
||||||
|
.loader
|
||||||
|
.get_physical_device_surface_formats(device, surface.surface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map_err(|error| VulkanRuntimeCapabilityError::SurfaceFormatsFailed {
|
||||||
|
device: name.to_string(),
|
||||||
|
result: error,
|
||||||
|
})?;
|
||||||
|
Ok(formats
|
||||||
|
.into_iter()
|
||||||
|
.map(|format| VulkanSurfaceFormat {
|
||||||
|
format: format.format.as_raw(),
|
||||||
|
color_space: format.color_space.as_raw(),
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn live_present_modes(
|
||||||
|
surface: &VulkanSurfaceProbe,
|
||||||
|
device: vk::PhysicalDevice,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<Vec<i32>, VulkanRuntimeCapabilityError> {
|
||||||
|
let modes = {
|
||||||
|
// SAFETY: The physical device and surface are live query inputs and no handles are retained.
|
||||||
|
unsafe {
|
||||||
|
surface
|
||||||
|
.loader
|
||||||
|
.get_physical_device_surface_present_modes(device, surface.surface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map_err(|error| VulkanRuntimeCapabilityError::PresentModesFailed {
|
||||||
|
device: name.to_string(),
|
||||||
|
result: error,
|
||||||
|
})?;
|
||||||
|
Ok(modes.into_iter().map(vk::PresentModeKHR::as_raw).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn live_surface_capabilities(
|
||||||
|
surface: &VulkanSurfaceProbe,
|
||||||
|
device: vk::PhysicalDevice,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<VulkanSwapchainSurfaceCapabilities, VulkanRuntimeCapabilityError> {
|
||||||
|
let capabilities = {
|
||||||
|
// SAFETY: The physical device and surface are live query inputs and no handles are retained.
|
||||||
|
unsafe {
|
||||||
|
surface
|
||||||
|
.loader
|
||||||
|
.get_physical_device_surface_capabilities(device, surface.surface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map_err(
|
||||||
|
|error| VulkanRuntimeCapabilityError::SurfaceCapabilitiesFailed {
|
||||||
|
device: name.to_string(),
|
||||||
|
result: error,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
Ok(VulkanSwapchainSurfaceCapabilities {
|
||||||
|
current_extent: if capabilities.current_extent.width == u32::MAX {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some((
|
||||||
|
capabilities.current_extent.width,
|
||||||
|
capabilities.current_extent.height,
|
||||||
|
))
|
||||||
|
},
|
||||||
|
min_extent: (
|
||||||
|
capabilities.min_image_extent.width,
|
||||||
|
capabilities.min_image_extent.height,
|
||||||
|
),
|
||||||
|
max_extent: (
|
||||||
|
capabilities.max_image_extent.width,
|
||||||
|
capabilities.max_image_extent.height,
|
||||||
|
),
|
||||||
|
min_image_count: capabilities.min_image_count,
|
||||||
|
max_image_count: capabilities.max_image_count,
|
||||||
|
supported_usage_flags: capabilities.supported_usage_flags.as_raw(),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1242,6 +1242,7 @@ fn has_safety_comment(line: &str) -> bool {
|
|||||||
const AUDITED_UNSAFE_SOURCE_FILES: &[&str] = &[
|
const AUDITED_UNSAFE_SOURCE_FILES: &[&str] = &[
|
||||||
"adapters/fparkan-render-vulkan/src/ffi.rs",
|
"adapters/fparkan-render-vulkan/src/ffi.rs",
|
||||||
"adapters/fparkan-render-vulkan/src/ffi/instance.rs",
|
"adapters/fparkan-render-vulkan/src/ffi/instance.rs",
|
||||||
|
"adapters/fparkan-render-vulkan/src/ffi/runtime.rs",
|
||||||
"adapters/fparkan-render-vulkan/src/ffi/surface.rs",
|
"adapters/fparkan-render-vulkan/src/ffi/surface.rs",
|
||||||
"adapters/fparkan-render-vulkan/src/ffi/validation.rs",
|
"adapters/fparkan-render-vulkan/src/ffi/validation.rs",
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user