refactor(vulkan-ffi): extract capability probe module
This commit is contained in:
@@ -0,0 +1,405 @@
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use ash::vk;
|
||||
use std::ffi::CStr;
|
||||
|
||||
use super::{VulkanInstanceProbe, VulkanSurfaceProbe};
|
||||
use crate::policy::{
|
||||
compare_reports, plan_vulkan_swapchain, 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,
|
||||
}
|
||||
|
||||
/// 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 {}
|
||||
|
||||
pub(super) struct SelectedLiveDevice {
|
||||
pub(super) physical_device: vk::PhysicalDevice,
|
||||
pub(super) runtime: VulkanRuntimeCapabilityProbe,
|
||||
}
|
||||
|
||||
struct LiveDeviceCandidate {
|
||||
physical_device: vk::PhysicalDevice,
|
||||
capability: VulkanCapabilityReport,
|
||||
surface_formats: Vec<VulkanSurfaceFormat>,
|
||||
present_modes: Vec<i32>,
|
||||
surface_capabilities: VulkanSwapchainSurfaceCapabilities,
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
pub(super) 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,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn unique_queue_families(graphics: u32, present: u32) -> Vec<u32> {
|
||||
if graphics == present {
|
||||
vec![graphics]
|
||||
} else {
|
||||
vec![graphics, present]
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub(super) 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())
|
||||
}
|
||||
|
||||
pub(super) 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())
|
||||
}
|
||||
|
||||
pub(super) 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(),
|
||||
})
|
||||
}
|
||||
@@ -1,24 +1,13 @@
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use ash::vk;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::ffi::CString;
|
||||
|
||||
use super::{VulkanInstanceProbe, VulkanSurfaceProbe};
|
||||
use crate::policy::{
|
||||
compare_reports, plan_vulkan_swapchain, validate_device, VulkanCapabilityError,
|
||||
VulkanCapabilityReport, VulkanDeviceType, VulkanPhysicalDeviceRecord, VulkanQueueFamily,
|
||||
VulkanSurfaceFormat, VulkanSwapchainError, VulkanSwapchainPlan, VulkanSwapchainRequest,
|
||||
VulkanSwapchainSurfaceCapabilities,
|
||||
use super::capabilities::{
|
||||
select_live_device_candidate, unique_queue_families, VulkanRuntimeCapabilityError,
|
||||
VulkanRuntimeCapabilityProbe,
|
||||
};
|
||||
|
||||
/// 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,
|
||||
}
|
||||
use super::{VulkanInstanceProbe, VulkanSurfaceProbe};
|
||||
|
||||
/// Created Vulkan logical device probe.
|
||||
pub struct VulkanLogicalDeviceProbe {
|
||||
@@ -86,95 +75,6 @@ pub struct VulkanLogicalDeviceReport {
|
||||
pub enabled_extensions: Vec<String>,
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
@@ -214,21 +114,6 @@ impl std::fmt::Display for VulkanLogicalDeviceError {
|
||||
|
||||
impl std::error::Error for VulkanLogicalDeviceError {}
|
||||
|
||||
/// 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
|
||||
@@ -293,289 +178,9 @@ pub fn create_vulkan_logical_device_probe(
|
||||
})
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) 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)
|
||||
}
|
||||
|
||||
pub(super) 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())
|
||||
}
|
||||
|
||||
pub(super) 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())
|
||||
}
|
||||
|
||||
pub(super) 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(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use ash::{khr::swapchain, vk};
|
||||
|
||||
use super::{
|
||||
runtime::{
|
||||
capabilities::{
|
||||
live_present_modes, live_surface_capabilities, live_surface_formats, unique_queue_families,
|
||||
},
|
||||
VulkanInstanceProbe, VulkanLogicalDeviceProbe, VulkanRuntimeCapabilityError,
|
||||
|
||||
Reference in New Issue
Block a user