feat: probe live Vulkan runtime capabilities
This commit is contained in:
@@ -311,6 +311,104 @@ impl Drop for VulkanSurfaceProbe {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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: String,
|
||||||
|
},
|
||||||
|
/// Device extension enumeration failed.
|
||||||
|
EnumerateDeviceExtensionsFailed {
|
||||||
|
/// Device name or index context.
|
||||||
|
device: String,
|
||||||
|
/// Vulkan result.
|
||||||
|
result: String,
|
||||||
|
},
|
||||||
|
/// Queue-family present support query failed.
|
||||||
|
PresentSupportFailed {
|
||||||
|
/// Device name.
|
||||||
|
device: String,
|
||||||
|
/// Queue-family index.
|
||||||
|
queue_family: u32,
|
||||||
|
/// Vulkan result.
|
||||||
|
result: String,
|
||||||
|
},
|
||||||
|
/// Surface format query failed.
|
||||||
|
SurfaceFormatsFailed {
|
||||||
|
/// Device name.
|
||||||
|
device: String,
|
||||||
|
/// Vulkan result.
|
||||||
|
result: String,
|
||||||
|
},
|
||||||
|
/// Surface capability query failed.
|
||||||
|
SurfaceCapabilitiesFailed {
|
||||||
|
/// Device name.
|
||||||
|
device: String,
|
||||||
|
/// Vulkan result.
|
||||||
|
result: String,
|
||||||
|
},
|
||||||
|
/// Present mode query failed.
|
||||||
|
PresentModesFailed {
|
||||||
|
/// Device name.
|
||||||
|
device: String,
|
||||||
|
/// Vulkan result.
|
||||||
|
result: String,
|
||||||
|
},
|
||||||
|
/// 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 {}
|
||||||
|
|
||||||
/// Builds a deterministic Vulkan surface plan from native window handles.
|
/// Builds a deterministic Vulkan surface plan from native window handles.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
@@ -371,6 +469,264 @@ pub fn create_vulkan_surface_probe(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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 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: format!("{error:?}"),
|
||||||
|
}
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
let mut best: Option<LiveDeviceCandidate> = None;
|
||||||
|
for (index, device) in devices.iter().copied().enumerate() {
|
||||||
|
let candidate = live_device_candidate(instance, surface, device, index)?;
|
||||||
|
match &best {
|
||||||
|
Some(existing)
|
||||||
|
if compare_reports(&candidate.capability, &existing.capability)
|
||||||
|
!= std::cmp::Ordering::Greater => {}
|
||||||
|
_ => best = Some(candidate),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let best = best.ok_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(VulkanRuntimeCapabilityProbe {
|
||||||
|
capability: best.capability,
|
||||||
|
swapchain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LiveDeviceCandidate {
|
||||||
|
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: format!("{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(),
|
||||||
|
};
|
||||||
|
let capability = validate_device(&record).map_err(VulkanRuntimeCapabilityError::Capability)?;
|
||||||
|
Ok(LiveDeviceCandidate {
|
||||||
|
capability,
|
||||||
|
surface_formats,
|
||||||
|
present_modes,
|
||||||
|
surface_capabilities,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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: format!("{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: format!("{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: format!("{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: format!("{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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Renders a deterministic JSON Vulkan surface plan.
|
/// Renders a deterministic JSON Vulkan surface plan.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn render_surface_plan_json(plan: &VulkanSurfacePlan) -> String {
|
pub fn render_surface_plan_json(plan: &VulkanSurfacePlan) -> String {
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ use fparkan_platform::{NativeWindowHandles, WindowPort};
|
|||||||
use fparkan_platform_winit::{probe_smoke_window, WinitWindowPlan};
|
use fparkan_platform_winit::{probe_smoke_window, WinitWindowPlan};
|
||||||
use fparkan_render_vulkan::{
|
use fparkan_render_vulkan::{
|
||||||
create_vulkan_instance_probe, create_vulkan_surface_probe, probe_vulkan_loader,
|
create_vulkan_instance_probe, create_vulkan_surface_probe, probe_vulkan_loader,
|
||||||
triangle_shader_manifest, validate_shader_manifest, VulkanInstanceConfig, VulkanInstanceProbe,
|
probe_vulkan_runtime_capabilities, triangle_shader_manifest, validate_shader_manifest,
|
||||||
|
VulkanInstanceConfig, VulkanInstanceProbe, VulkanRuntimeCapabilityProbe,
|
||||||
};
|
};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
@@ -205,6 +206,14 @@ struct VulkanBootstrapProbe {
|
|||||||
window_error: Option<String>,
|
window_error: Option<String>,
|
||||||
surface_status: VulkanSurfaceStatus,
|
surface_status: VulkanSurfaceStatus,
|
||||||
surface_error: Option<String>,
|
surface_error: Option<String>,
|
||||||
|
device_status: VulkanDeviceStatus,
|
||||||
|
device_name: Option<String>,
|
||||||
|
device_error: Option<String>,
|
||||||
|
swapchain_status: VulkanSwapchainStatus,
|
||||||
|
swapchain_width: Option<u32>,
|
||||||
|
swapchain_height: Option<u32>,
|
||||||
|
swapchain_image_count: Option<u32>,
|
||||||
|
swapchain_error: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VulkanBootstrapProbe {
|
impl VulkanBootstrapProbe {
|
||||||
@@ -234,6 +243,14 @@ impl VulkanBootstrapProbe {
|
|||||||
window_error: None,
|
window_error: None,
|
||||||
surface_status: VulkanSurfaceStatus::Skipped,
|
surface_status: VulkanSurfaceStatus::Skipped,
|
||||||
surface_error: None,
|
surface_error: None,
|
||||||
|
device_status: VulkanDeviceStatus::Skipped,
|
||||||
|
device_name: None,
|
||||||
|
device_error: None,
|
||||||
|
swapchain_status: VulkanSwapchainStatus::Skipped,
|
||||||
|
swapchain_width: None,
|
||||||
|
swapchain_height: None,
|
||||||
|
swapchain_image_count: None,
|
||||||
|
swapchain_error: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,6 +269,14 @@ impl VulkanBootstrapProbe {
|
|||||||
window_error: None,
|
window_error: None,
|
||||||
surface_status: VulkanSurfaceStatus::Skipped,
|
surface_status: VulkanSurfaceStatus::Skipped,
|
||||||
surface_error: None,
|
surface_error: None,
|
||||||
|
device_status: VulkanDeviceStatus::Skipped,
|
||||||
|
device_name: None,
|
||||||
|
device_error: None,
|
||||||
|
swapchain_status: VulkanSwapchainStatus::Skipped,
|
||||||
|
swapchain_width: None,
|
||||||
|
swapchain_height: None,
|
||||||
|
swapchain_image_count: None,
|
||||||
|
swapchain_error: None,
|
||||||
},
|
},
|
||||||
Err(err) => Self {
|
Err(err) => Self {
|
||||||
loader_status: VulkanLoaderStatus::Unavailable,
|
loader_status: VulkanLoaderStatus::Unavailable,
|
||||||
@@ -266,6 +291,14 @@ impl VulkanBootstrapProbe {
|
|||||||
window_error: None,
|
window_error: None,
|
||||||
surface_status: VulkanSurfaceStatus::Skipped,
|
surface_status: VulkanSurfaceStatus::Skipped,
|
||||||
surface_error: None,
|
surface_error: None,
|
||||||
|
device_status: VulkanDeviceStatus::Skipped,
|
||||||
|
device_name: None,
|
||||||
|
device_error: None,
|
||||||
|
swapchain_status: VulkanSwapchainStatus::Skipped,
|
||||||
|
swapchain_width: None,
|
||||||
|
swapchain_height: None,
|
||||||
|
swapchain_image_count: None,
|
||||||
|
swapchain_error: None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,8 +372,9 @@ impl VulkanBootstrapProbe {
|
|||||||
create_vulkan_surface_probe(instance, window_handles)
|
create_vulkan_surface_probe(instance, window_handles)
|
||||||
.map_err(|err| err.to_string())
|
.map_err(|err| err.to_string())
|
||||||
}) {
|
}) {
|
||||||
Ok(_) => {
|
Ok(surface) => {
|
||||||
self.surface_status = VulkanSurfaceStatus::Created;
|
self.surface_status = VulkanSurfaceStatus::Created;
|
||||||
|
self.probe_runtime_capabilities(instance, &surface);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.surface_status = VulkanSurfaceStatus::Failed;
|
self.surface_status = VulkanSurfaceStatus::Failed;
|
||||||
@@ -349,6 +383,44 @@ impl VulkanBootstrapProbe {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn probe_runtime_capabilities(
|
||||||
|
&mut self,
|
||||||
|
instance: Option<&VulkanInstanceProbe>,
|
||||||
|
surface: &fparkan_render_vulkan::VulkanSurfaceProbe,
|
||||||
|
) {
|
||||||
|
let Some(instance) = instance else {
|
||||||
|
self.device_status = VulkanDeviceStatus::Failed;
|
||||||
|
self.device_error = Some("Vulkan instance probe was not retained".to_string());
|
||||||
|
self.swapchain_status = VulkanSwapchainStatus::Skipped;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
match probe_vulkan_runtime_capabilities(
|
||||||
|
instance,
|
||||||
|
surface,
|
||||||
|
(
|
||||||
|
self.window_width.unwrap_or(1).max(1),
|
||||||
|
self.window_height.unwrap_or(1).max(1),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Ok(runtime) => self.record_runtime_capabilities(runtime),
|
||||||
|
Err(err) => {
|
||||||
|
self.device_status = VulkanDeviceStatus::Failed;
|
||||||
|
self.device_error = Some(err.to_string());
|
||||||
|
self.swapchain_status = VulkanSwapchainStatus::Failed;
|
||||||
|
self.swapchain_error = Some(err.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record_runtime_capabilities(&mut self, runtime: VulkanRuntimeCapabilityProbe) {
|
||||||
|
self.device_status = VulkanDeviceStatus::Selected;
|
||||||
|
self.device_name = Some(runtime.capability.device_name);
|
||||||
|
self.swapchain_status = VulkanSwapchainStatus::Planned;
|
||||||
|
self.swapchain_width = Some(runtime.swapchain.extent.0);
|
||||||
|
self.swapchain_height = Some(runtime.swapchain.extent.1);
|
||||||
|
self.swapchain_image_count = Some(runtime.swapchain.image_count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
@@ -421,6 +493,40 @@ impl VulkanSurfaceStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
enum VulkanDeviceStatus {
|
||||||
|
Skipped,
|
||||||
|
Selected,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VulkanDeviceStatus {
|
||||||
|
const fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Skipped => "skipped",
|
||||||
|
Self::Selected => "selected",
|
||||||
|
Self::Failed => "failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
enum VulkanSwapchainStatus {
|
||||||
|
Skipped,
|
||||||
|
Planned,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VulkanSwapchainStatus {
|
||||||
|
const fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Skipped => "skipped",
|
||||||
|
Self::Planned => "planned",
|
||||||
|
Self::Failed => "failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
enum SmokePlatform {
|
enum SmokePlatform {
|
||||||
Windows,
|
Windows,
|
||||||
@@ -524,6 +630,16 @@ fn validate_smoke_options(
|
|||||||
"passed native smoke report requires successful --probe-surface".to_string(),
|
"passed native smoke report requires successful --probe-surface".to_string(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if bootstrap.device_status != VulkanDeviceStatus::Selected {
|
||||||
|
return Err(
|
||||||
|
"passed native smoke report requires selected Vulkan device".to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if bootstrap.swapchain_status != VulkanSwapchainStatus::Planned {
|
||||||
|
return Err(
|
||||||
|
"passed native smoke report requires planned Vulkan swapchain".to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -535,97 +651,131 @@ fn render_smoke_report_json(
|
|||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let shader_manifest = validate_shader_manifest(&triangle_shader_manifest())
|
let shader_manifest = validate_shader_manifest(&triangle_shader_manifest())
|
||||||
.map_err(|err| format!("shader manifest: {err}"))?;
|
.map_err(|err| format!("shader manifest: {err}"))?;
|
||||||
let validation_error_count = options
|
Ok(render_json_object(&[
|
||||||
.validation_error_count
|
("schema_version", json_string(SCHEMA_VERSION)),
|
||||||
.map_or_else(|| "null".to_string(), |value| value.to_string());
|
("commit_sha", json_string(¤t_git_commit_sha())),
|
||||||
let reason = options
|
("rust_toolchain", json_string(RUST_TOOLCHAIN)),
|
||||||
.reason
|
("target_triple", json_string(¤t_rustc_host_triple())),
|
||||||
.as_ref()
|
("platform", json_string(options.platform.as_str())),
|
||||||
.map_or_else(|| "null".to_string(), |value| json_string(value));
|
("status", json_string(options.status.as_str())),
|
||||||
let instance_api = bootstrap
|
("frames", options.frames.to_string()),
|
||||||
.instance_api
|
("resize_count", options.resize_count.to_string()),
|
||||||
.as_ref()
|
(
|
||||||
.map_or_else(|| "null".to_string(), |value| json_string(value));
|
"swapchain_recreate_count",
|
||||||
let loader_error = bootstrap
|
options.swapchain_recreate_count.to_string(),
|
||||||
.loader_error
|
|
||||||
.as_ref()
|
|
||||||
.map_or_else(|| "null".to_string(), |value| json_string(value));
|
|
||||||
let instance_error = bootstrap
|
|
||||||
.instance_error
|
|
||||||
.as_ref()
|
|
||||||
.map_or_else(|| "null".to_string(), |value| json_string(value));
|
|
||||||
let window_width = bootstrap
|
|
||||||
.window_width
|
|
||||||
.map_or_else(|| "null".to_string(), |value| value.to_string());
|
|
||||||
let window_height = bootstrap
|
|
||||||
.window_height
|
|
||||||
.map_or_else(|| "null".to_string(), |value| value.to_string());
|
|
||||||
let window_error = bootstrap
|
|
||||||
.window_error
|
|
||||||
.as_ref()
|
|
||||||
.map_or_else(|| "null".to_string(), |value| json_string(value));
|
|
||||||
let surface_error = bootstrap
|
|
||||||
.surface_error
|
|
||||||
.as_ref()
|
|
||||||
.map_or_else(|| "null".to_string(), |value| json_string(value));
|
|
||||||
Ok(format!(
|
|
||||||
concat!(
|
|
||||||
"{{\n",
|
|
||||||
" \"schema_version\": \"{}\",\n",
|
|
||||||
" \"commit_sha\": \"{}\",\n",
|
|
||||||
" \"rust_toolchain\": \"{}\",\n",
|
|
||||||
" \"target_triple\": \"{}\",\n",
|
|
||||||
" \"platform\": \"{}\",\n",
|
|
||||||
" \"status\": \"{}\",\n",
|
|
||||||
" \"frames\": {},\n",
|
|
||||||
" \"resize_count\": {},\n",
|
|
||||||
" \"swapchain_recreate_count\": {},\n",
|
|
||||||
" \"validation_error_count\": {},\n",
|
|
||||||
" \"shader_manifest_hash\": \"{}\",\n",
|
|
||||||
" \"vulkan_loader_status\": \"{}\",\n",
|
|
||||||
" \"vulkan_instance_api\": {},\n",
|
|
||||||
" \"vulkan_loader_error\": {},\n",
|
|
||||||
" \"vulkan_instance_status\": \"{}\",\n",
|
|
||||||
" \"vulkan_instance_error\": {},\n",
|
|
||||||
" \"vulkan_portability_enumeration\": {},\n",
|
|
||||||
" \"window_status\": \"{}\",\n",
|
|
||||||
" \"window_width\": {},\n",
|
|
||||||
" \"window_height\": {},\n",
|
|
||||||
" \"window_error\": {},\n",
|
|
||||||
" \"vulkan_surface_status\": \"{}\",\n",
|
|
||||||
" \"vulkan_surface_error\": {},\n",
|
|
||||||
" \"reason\": {}\n",
|
|
||||||
"}}\n"
|
|
||||||
),
|
),
|
||||||
SCHEMA_VERSION,
|
(
|
||||||
json_escape(¤t_git_commit_sha()),
|
"validation_error_count",
|
||||||
RUST_TOOLCHAIN,
|
optional_u32(options.validation_error_count),
|
||||||
json_escape(¤t_rustc_host_triple()),
|
),
|
||||||
options.platform.as_str(),
|
(
|
||||||
options.status.as_str(),
|
"shader_manifest_hash",
|
||||||
options.frames,
|
json_string(&shader_manifest.manifest_hash),
|
||||||
options.resize_count,
|
),
|
||||||
options.swapchain_recreate_count,
|
(
|
||||||
validation_error_count,
|
"vulkan_loader_status",
|
||||||
json_escape(&shader_manifest.manifest_hash),
|
json_string(bootstrap.loader_status.as_str()),
|
||||||
bootstrap.loader_status.as_str(),
|
),
|
||||||
instance_api,
|
(
|
||||||
loader_error,
|
"vulkan_instance_api",
|
||||||
bootstrap.instance_status.as_str(),
|
optional_string(bootstrap.instance_api.as_deref()),
|
||||||
instance_error,
|
),
|
||||||
if bootstrap.portability_enumeration {
|
(
|
||||||
"true"
|
"vulkan_loader_error",
|
||||||
} else {
|
optional_string(bootstrap.loader_error.as_deref()),
|
||||||
"false"
|
),
|
||||||
},
|
(
|
||||||
bootstrap.window_status.as_str(),
|
"vulkan_instance_status",
|
||||||
window_width,
|
json_string(bootstrap.instance_status.as_str()),
|
||||||
window_height,
|
),
|
||||||
window_error,
|
(
|
||||||
bootstrap.surface_status.as_str(),
|
"vulkan_instance_error",
|
||||||
surface_error,
|
optional_string(bootstrap.instance_error.as_deref()),
|
||||||
reason
|
),
|
||||||
))
|
(
|
||||||
|
"vulkan_portability_enumeration",
|
||||||
|
bool_json(bootstrap.portability_enumeration),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"window_status",
|
||||||
|
json_string(bootstrap.window_status.as_str()),
|
||||||
|
),
|
||||||
|
("window_width", optional_u32(bootstrap.window_width)),
|
||||||
|
("window_height", optional_u32(bootstrap.window_height)),
|
||||||
|
(
|
||||||
|
"window_error",
|
||||||
|
optional_string(bootstrap.window_error.as_deref()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vulkan_surface_status",
|
||||||
|
json_string(bootstrap.surface_status.as_str()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vulkan_surface_error",
|
||||||
|
optional_string(bootstrap.surface_error.as_deref()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vulkan_device_status",
|
||||||
|
json_string(bootstrap.device_status.as_str()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vulkan_device_name",
|
||||||
|
optional_string(bootstrap.device_name.as_deref()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vulkan_device_error",
|
||||||
|
optional_string(bootstrap.device_error.as_deref()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vulkan_swapchain_status",
|
||||||
|
json_string(bootstrap.swapchain_status.as_str()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vulkan_swapchain_width",
|
||||||
|
optional_u32(bootstrap.swapchain_width),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vulkan_swapchain_height",
|
||||||
|
optional_u32(bootstrap.swapchain_height),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vulkan_swapchain_image_count",
|
||||||
|
optional_u32(bootstrap.swapchain_image_count),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vulkan_swapchain_error",
|
||||||
|
optional_string(bootstrap.swapchain_error.as_deref()),
|
||||||
|
),
|
||||||
|
("reason", optional_string(options.reason.as_deref())),
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_json_object(fields: &[(&str, String)]) -> String {
|
||||||
|
let mut out = String::from("{\n");
|
||||||
|
for (index, (name, value)) in fields.iter().enumerate() {
|
||||||
|
out.push_str(" ");
|
||||||
|
out.push_str(&json_string(name));
|
||||||
|
out.push_str(": ");
|
||||||
|
out.push_str(value);
|
||||||
|
if index + 1 < fields.len() {
|
||||||
|
out.push(',');
|
||||||
|
}
|
||||||
|
out.push('\n');
|
||||||
|
}
|
||||||
|
out.push_str("}\n");
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn optional_string(value: Option<&str>) -> String {
|
||||||
|
value.map_or_else(|| "null".to_string(), json_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn optional_u32(value: Option<u32>) -> String {
|
||||||
|
value.map_or_else(|| "null".to_string(), |value| value.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bool_json(value: bool) -> String {
|
||||||
|
if value { "true" } else { "false" }.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_api_version(version: u32) -> String {
|
fn format_api_version(version: u32) -> String {
|
||||||
@@ -694,6 +844,31 @@ mod tests {
|
|||||||
values.iter().map(|value| (*value).to_string()).collect()
|
values.iter().map(|value| (*value).to_string()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn probe_fixture() -> VulkanBootstrapProbe {
|
||||||
|
VulkanBootstrapProbe {
|
||||||
|
loader_status: VulkanLoaderStatus::Available,
|
||||||
|
instance_api: Some("1.3.0".to_string()),
|
||||||
|
loader_error: None,
|
||||||
|
instance_status: VulkanInstanceStatus::Created,
|
||||||
|
instance_error: None,
|
||||||
|
portability_enumeration: false,
|
||||||
|
window_status: WinitWindowStatus::Created,
|
||||||
|
window_width: Some(1280),
|
||||||
|
window_height: Some(720),
|
||||||
|
window_error: None,
|
||||||
|
surface_status: VulkanSurfaceStatus::Created,
|
||||||
|
surface_error: None,
|
||||||
|
device_status: VulkanDeviceStatus::Selected,
|
||||||
|
device_name: Some("Stage 0 GPU".to_string()),
|
||||||
|
device_error: None,
|
||||||
|
swapchain_status: VulkanSwapchainStatus::Planned,
|
||||||
|
swapchain_width: Some(1280),
|
||||||
|
swapchain_height: Some(720),
|
||||||
|
swapchain_image_count: Some(3),
|
||||||
|
swapchain_error: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_blocked_smoke_args() -> Result<(), String> {
|
fn parses_blocked_smoke_args() -> Result<(), String> {
|
||||||
let options = SmokeOptions::parse(&strings(&[
|
let options = SmokeOptions::parse(&strings(&[
|
||||||
@@ -727,6 +902,7 @@ mod tests {
|
|||||||
window_error: None,
|
window_error: None,
|
||||||
surface_status: VulkanSurfaceStatus::Skipped,
|
surface_status: VulkanSurfaceStatus::Skipped,
|
||||||
surface_error: None,
|
surface_error: None,
|
||||||
|
..probe_fixture()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -767,6 +943,7 @@ mod tests {
|
|||||||
window_error: None,
|
window_error: None,
|
||||||
surface_status: VulkanSurfaceStatus::Created,
|
surface_status: VulkanSurfaceStatus::Created,
|
||||||
surface_error: None,
|
surface_error: None,
|
||||||
|
..probe_fixture()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Err("passed native smoke report requires --frames >= 300".to_string())
|
Err("passed native smoke report requires --frames >= 300".to_string())
|
||||||
@@ -809,6 +986,7 @@ mod tests {
|
|||||||
window_error: None,
|
window_error: None,
|
||||||
surface_status: VulkanSurfaceStatus::Skipped,
|
surface_status: VulkanSurfaceStatus::Skipped,
|
||||||
surface_error: None,
|
surface_error: None,
|
||||||
|
..probe_fixture()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Err("passed native smoke report requires successful --probe-loader".to_string())
|
Err("passed native smoke report requires successful --probe-loader".to_string())
|
||||||
@@ -850,6 +1028,7 @@ mod tests {
|
|||||||
window_error: None,
|
window_error: None,
|
||||||
surface_status: VulkanSurfaceStatus::Created,
|
surface_status: VulkanSurfaceStatus::Created,
|
||||||
surface_error: None,
|
surface_error: None,
|
||||||
|
..probe_fixture()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Err("passed native smoke report requires --swapchain-recreate-count >= 1".to_string())
|
Err("passed native smoke report requires --swapchain-recreate-count >= 1".to_string())
|
||||||
@@ -893,6 +1072,7 @@ mod tests {
|
|||||||
window_error: None,
|
window_error: None,
|
||||||
surface_status: VulkanSurfaceStatus::Skipped,
|
surface_status: VulkanSurfaceStatus::Skipped,
|
||||||
surface_error: None,
|
surface_error: None,
|
||||||
|
..probe_fixture()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Err("passed native smoke report requires successful --probe-instance".to_string())
|
Err("passed native smoke report requires successful --probe-instance".to_string())
|
||||||
@@ -936,6 +1116,7 @@ mod tests {
|
|||||||
window_error: None,
|
window_error: None,
|
||||||
surface_status: VulkanSurfaceStatus::Created,
|
surface_status: VulkanSurfaceStatus::Created,
|
||||||
surface_error: None,
|
surface_error: None,
|
||||||
|
..probe_fixture()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Err("passed native smoke report requires successful --probe-window".to_string())
|
Err("passed native smoke report requires successful --probe-window".to_string())
|
||||||
@@ -980,6 +1161,7 @@ mod tests {
|
|||||||
window_error: None,
|
window_error: None,
|
||||||
surface_status: VulkanSurfaceStatus::Skipped,
|
surface_status: VulkanSurfaceStatus::Skipped,
|
||||||
surface_error: None,
|
surface_error: None,
|
||||||
|
..probe_fixture()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Err("passed native smoke report requires successful --probe-surface".to_string())
|
Err("passed native smoke report requires successful --probe-surface".to_string())
|
||||||
@@ -1023,12 +1205,82 @@ mod tests {
|
|||||||
window_error: None,
|
window_error: None,
|
||||||
surface_status: VulkanSurfaceStatus::Failed,
|
surface_status: VulkanSurfaceStatus::Failed,
|
||||||
surface_error: Some("Vulkan surface creation failed".to_string()),
|
surface_error: Some("Vulkan surface creation failed".to_string()),
|
||||||
|
..probe_fixture()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Err("passed native smoke report requires successful --probe-surface".to_string())
|
Err("passed native smoke report requires successful --probe-surface".to_string())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_passed_without_selected_device() {
|
||||||
|
let options = SmokeOptions::parse(&strings(&[
|
||||||
|
"--platform",
|
||||||
|
"linux",
|
||||||
|
"--out",
|
||||||
|
"target/native.json",
|
||||||
|
"--status",
|
||||||
|
"passed",
|
||||||
|
"--frames",
|
||||||
|
"300",
|
||||||
|
"--resize-count",
|
||||||
|
"1",
|
||||||
|
"--swapchain-recreate-count",
|
||||||
|
"1",
|
||||||
|
"--validation-error-count",
|
||||||
|
"0",
|
||||||
|
"--probe-surface",
|
||||||
|
]))
|
||||||
|
.expect("options");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
validate_smoke_options(
|
||||||
|
&options,
|
||||||
|
&VulkanBootstrapProbe {
|
||||||
|
device_status: VulkanDeviceStatus::Failed,
|
||||||
|
device_name: None,
|
||||||
|
device_error: Some("no Vulkan physical device available".to_string()),
|
||||||
|
..probe_fixture()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Err("passed native smoke report requires selected Vulkan device".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_passed_without_planned_swapchain() {
|
||||||
|
let options = SmokeOptions::parse(&strings(&[
|
||||||
|
"--platform",
|
||||||
|
"linux",
|
||||||
|
"--out",
|
||||||
|
"target/native.json",
|
||||||
|
"--status",
|
||||||
|
"passed",
|
||||||
|
"--frames",
|
||||||
|
"300",
|
||||||
|
"--resize-count",
|
||||||
|
"1",
|
||||||
|
"--swapchain-recreate-count",
|
||||||
|
"1",
|
||||||
|
"--validation-error-count",
|
||||||
|
"0",
|
||||||
|
"--probe-surface",
|
||||||
|
]))
|
||||||
|
.expect("options");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
validate_smoke_options(
|
||||||
|
&options,
|
||||||
|
&VulkanBootstrapProbe {
|
||||||
|
swapchain_status: VulkanSwapchainStatus::Failed,
|
||||||
|
swapchain_error: Some("Vulkan swapchain has no surface format".to_string()),
|
||||||
|
..probe_fixture()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Err("passed native smoke report requires planned Vulkan swapchain".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn blocked_report_includes_shader_manifest_and_bootstrap_status() -> Result<(), String> {
|
fn blocked_report_includes_shader_manifest_and_bootstrap_status() -> Result<(), String> {
|
||||||
let options = SmokeOptions::parse(&strings(&[
|
let options = SmokeOptions::parse(&strings(&[
|
||||||
@@ -1060,6 +1312,14 @@ mod tests {
|
|||||||
"native window/display handles are required for Vulkan surface creation"
|
"native window/display handles are required for Vulkan surface creation"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
),
|
),
|
||||||
|
device_status: VulkanDeviceStatus::Skipped,
|
||||||
|
device_name: None,
|
||||||
|
device_error: None,
|
||||||
|
swapchain_status: VulkanSwapchainStatus::Skipped,
|
||||||
|
swapchain_width: None,
|
||||||
|
swapchain_height: None,
|
||||||
|
swapchain_image_count: None,
|
||||||
|
swapchain_error: None,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -1084,6 +1344,10 @@ mod tests {
|
|||||||
assert!(json.contains(
|
assert!(json.contains(
|
||||||
"\"vulkan_surface_error\": \"native window/display handles are required for Vulkan surface creation\""
|
"\"vulkan_surface_error\": \"native window/display handles are required for Vulkan surface creation\""
|
||||||
));
|
));
|
||||||
|
assert!(json.contains("\"vulkan_device_status\": \"skipped\""));
|
||||||
|
assert!(json.contains("\"vulkan_device_name\": null"));
|
||||||
|
assert!(json.contains("\"vulkan_swapchain_status\": \"skipped\""));
|
||||||
|
assert!(json.contains("\"vulkan_swapchain_width\": null"));
|
||||||
assert!(json.contains("\"reason\": \"runner unavailable\""));
|
assert!(json.contains("\"reason\": \"runner unavailable\""));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ S0-VK-027 covered cargo test -p fparkan-vulkan-smoke --offline rejects_passed_wi
|
|||||||
S0-VK-028 covered cargo test -p fparkan-vulkan-smoke --offline reports_rustc_host_triple blocked_report_includes_shader_manifest_and_bootstrap_status
|
S0-VK-028 covered cargo test -p fparkan-vulkan-smoke --offline reports_rustc_host_triple blocked_report_includes_shader_manifest_and_bootstrap_status
|
||||||
S0-VK-029 covered cargo test -p xtask --offline native_smoke_audit_accepts_complete_three_platform_pass native_smoke_audit_rejects_blocked_or_incomplete_reports
|
S0-VK-029 covered cargo test -p xtask --offline native_smoke_audit_accepts_complete_three_platform_pass native_smoke_audit_rejects_blocked_or_incomplete_reports
|
||||||
S0-VK-030 covered cargo test -p fparkan-vulkan-smoke --offline rejects_passed_with_failed_surface
|
S0-VK-030 covered cargo test -p fparkan-vulkan-smoke --offline rejects_passed_with_failed_surface
|
||||||
|
S0-VK-031 covered cargo test -p fparkan-vulkan-smoke --offline rejects_passed_without_selected_device
|
||||||
|
S0-VK-032 covered cargo test -p fparkan-vulkan-smoke --offline rejects_passed_without_planned_swapchain
|
||||||
S0-LIMIT-001 covered cargo test -p fparkan-binary --offline rejects_count_stride_overflow
|
S0-LIMIT-001 covered cargo test -p fparkan-binary --offline rejects_count_stride_overflow
|
||||||
S0-LIMIT-002 covered cargo test -p fparkan-binary --offline rejects_oversized_declared_allocation_before_read
|
S0-LIMIT-002 covered cargo test -p fparkan-binary --offline rejects_oversized_declared_allocation_before_read
|
||||||
L1-P1-NRES-001 covered cargo test -p fparkan-nres --offline licensed_corpora_nres_roundtrip_gates
|
L1-P1-NRES-001 covered cargo test -p fparkan-nres --offline licensed_corpora_nres_roundtrip_gates
|
||||||
|
|||||||
|
@@ -60,6 +60,8 @@
|
|||||||
`S0-VK-028`
|
`S0-VK-028`
|
||||||
`S0-VK-029`
|
`S0-VK-029`
|
||||||
`S0-VK-030`
|
`S0-VK-030`
|
||||||
|
`S0-VK-031`
|
||||||
|
`S0-VK-032`
|
||||||
`S0-LIMIT-001`
|
`S0-LIMIT-001`
|
||||||
`S0-LIMIT-002`
|
`S0-LIMIT-002`
|
||||||
`L1-P1-NRES-001`
|
`L1-P1-NRES-001`
|
||||||
|
|||||||
+34
-2
@@ -1535,6 +1535,20 @@ fn validate_native_smoke_report(
|
|||||||
"created",
|
"created",
|
||||||
failures,
|
failures,
|
||||||
);
|
);
|
||||||
|
expect_string_field(
|
||||||
|
platform,
|
||||||
|
report,
|
||||||
|
"vulkan_device_status",
|
||||||
|
"selected",
|
||||||
|
failures,
|
||||||
|
);
|
||||||
|
expect_string_field(
|
||||||
|
platform,
|
||||||
|
report,
|
||||||
|
"vulkan_swapchain_status",
|
||||||
|
"planned",
|
||||||
|
failures,
|
||||||
|
);
|
||||||
expect_u64_at_least(platform, report, "frames", 300, failures);
|
expect_u64_at_least(platform, report, "frames", 300, failures);
|
||||||
expect_u64_at_least(platform, report, "resize_count", 1, failures);
|
expect_u64_at_least(platform, report, "resize_count", 1, failures);
|
||||||
expect_u64_at_least(platform, report, "swapchain_recreate_count", 1, failures);
|
expect_u64_at_least(platform, report, "swapchain_recreate_count", 1, failures);
|
||||||
@@ -1543,6 +1557,16 @@ fn validate_native_smoke_report(
|
|||||||
expect_nonempty_string(platform, report, "rust_toolchain", failures);
|
expect_nonempty_string(platform, report, "rust_toolchain", failures);
|
||||||
expect_nonempty_string(platform, report, "target_triple", failures);
|
expect_nonempty_string(platform, report, "target_triple", failures);
|
||||||
expect_nonempty_string(platform, report, "shader_manifest_hash", failures);
|
expect_nonempty_string(platform, report, "shader_manifest_hash", failures);
|
||||||
|
expect_nonempty_string(platform, report, "vulkan_device_name", failures);
|
||||||
|
expect_u64_at_least(platform, report, "vulkan_swapchain_width", 1, failures);
|
||||||
|
expect_u64_at_least(platform, report, "vulkan_swapchain_height", 1, failures);
|
||||||
|
expect_u64_at_least(
|
||||||
|
platform,
|
||||||
|
report,
|
||||||
|
"vulkan_swapchain_image_count",
|
||||||
|
2,
|
||||||
|
failures,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expect_string_field(
|
fn expect_string_field(
|
||||||
@@ -2233,7 +2257,13 @@ mod tests {
|
|||||||
"vulkan_loader_status": "available",
|
"vulkan_loader_status": "available",
|
||||||
"vulkan_instance_status": "created",
|
"vulkan_instance_status": "created",
|
||||||
"window_status": "created",
|
"window_status": "created",
|
||||||
"vulkan_surface_status": "created"
|
"vulkan_surface_status": "created",
|
||||||
|
"vulkan_device_status": "selected",
|
||||||
|
"vulkan_device_name": format!("{platform} GPU"),
|
||||||
|
"vulkan_swapchain_status": "planned",
|
||||||
|
"vulkan_swapchain_width": 1280,
|
||||||
|
"vulkan_swapchain_height": 720,
|
||||||
|
"vulkan_swapchain_image_count": 3
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -2261,7 +2291,9 @@ mod tests {
|
|||||||
"vulkan_loader_status": "unavailable",
|
"vulkan_loader_status": "unavailable",
|
||||||
"vulkan_instance_status": "skipped",
|
"vulkan_instance_status": "skipped",
|
||||||
"window_status": "planned",
|
"window_status": "planned",
|
||||||
"vulkan_surface_status": "skipped"
|
"vulkan_surface_status": "skipped",
|
||||||
|
"vulkan_device_status": "skipped",
|
||||||
|
"vulkan_swapchain_status": "skipped"
|
||||||
}),
|
}),
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
Reference in New Issue
Block a user