feat: probe Vulkan logical device creation
This commit is contained in:
@@ -320,6 +320,37 @@ pub struct VulkanRuntimeCapabilityProbe {
|
|||||||
pub swapchain: VulkanSwapchainPlan,
|
pub swapchain: VulkanSwapchainPlan,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Created Vulkan logical device probe.
|
||||||
|
pub struct VulkanLogicalDeviceProbe {
|
||||||
|
device: ash::Device,
|
||||||
|
/// 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) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Live Vulkan device/surface capability probe error.
|
/// Live Vulkan device/surface capability probe error.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum VulkanRuntimeCapabilityError {
|
pub enum VulkanRuntimeCapabilityError {
|
||||||
@@ -409,6 +440,45 @@ impl std::fmt::Display for VulkanRuntimeCapabilityError {
|
|||||||
|
|
||||||
impl std::error::Error for VulkanRuntimeCapabilityError {}
|
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: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {}
|
||||||
|
|
||||||
/// Builds a deterministic Vulkan surface plan from native window handles.
|
/// Builds a deterministic Vulkan surface plan from native window handles.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
@@ -480,6 +550,78 @@ pub fn probe_vulkan_runtime_capabilities(
|
|||||||
surface: &VulkanSurfaceProbe,
|
surface: &VulkanSurfaceProbe,
|
||||||
drawable_extent: (u32, u32),
|
drawable_extent: (u32, u32),
|
||||||
) -> Result<VulkanRuntimeCapabilityProbe, VulkanRuntimeCapabilityError> {
|
) -> 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: format!("{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,
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_live_device_candidate(
|
||||||
|
instance: &VulkanInstanceProbe,
|
||||||
|
surface: &VulkanSurfaceProbe,
|
||||||
|
drawable_extent: (u32, u32),
|
||||||
|
) -> Result<SelectedLiveDevice, VulkanRuntimeCapabilityError> {
|
||||||
let devices = {
|
let devices = {
|
||||||
// SAFETY: The Vulkan instance is live for this query and no handles are retained.
|
// SAFETY: The Vulkan instance is live for this query and no handles are retained.
|
||||||
unsafe { instance.instance.enumerate_physical_devices() }.map_err(|error| {
|
unsafe { instance.instance.enumerate_physical_devices() }.map_err(|error| {
|
||||||
@@ -509,13 +651,22 @@ pub fn probe_vulkan_runtime_capabilities(
|
|||||||
preferred_present_mode: vk::PresentModeKHR::MAILBOX.as_raw(),
|
preferred_present_mode: vk::PresentModeKHR::MAILBOX.as_raw(),
|
||||||
})
|
})
|
||||||
.map_err(VulkanRuntimeCapabilityError::Swapchain)?;
|
.map_err(VulkanRuntimeCapabilityError::Swapchain)?;
|
||||||
Ok(VulkanRuntimeCapabilityProbe {
|
Ok(SelectedLiveDevice {
|
||||||
capability: best.capability,
|
physical_device: best.physical_device,
|
||||||
swapchain,
|
runtime: VulkanRuntimeCapabilityProbe {
|
||||||
|
capability: best.capability,
|
||||||
|
swapchain,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SelectedLiveDevice {
|
||||||
|
physical_device: vk::PhysicalDevice,
|
||||||
|
runtime: VulkanRuntimeCapabilityProbe,
|
||||||
|
}
|
||||||
|
|
||||||
struct LiveDeviceCandidate {
|
struct LiveDeviceCandidate {
|
||||||
|
physical_device: vk::PhysicalDevice,
|
||||||
capability: VulkanCapabilityReport,
|
capability: VulkanCapabilityReport,
|
||||||
surface_formats: Vec<VulkanSurfaceFormat>,
|
surface_formats: Vec<VulkanSurfaceFormat>,
|
||||||
present_modes: Vec<i32>,
|
present_modes: Vec<i32>,
|
||||||
@@ -587,6 +738,7 @@ fn live_device_candidate(
|
|||||||
};
|
};
|
||||||
let capability = validate_device(&record).map_err(VulkanRuntimeCapabilityError::Capability)?;
|
let capability = validate_device(&record).map_err(VulkanRuntimeCapabilityError::Capability)?;
|
||||||
Ok(LiveDeviceCandidate {
|
Ok(LiveDeviceCandidate {
|
||||||
|
physical_device: device,
|
||||||
capability,
|
capability,
|
||||||
surface_formats,
|
surface_formats,
|
||||||
present_modes,
|
present_modes,
|
||||||
@@ -594,6 +746,21 @@ fn live_device_candidate(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
fn physical_device_name(properties: &vk::PhysicalDeviceProperties, index: usize) -> String {
|
||||||
// SAFETY: Vulkan device names are fixed-size NUL-terminated C strings per the spec.
|
// 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()) }
|
let name = unsafe { CStr::from_ptr(properties.device_name.as_ptr()) }
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
use fparkan_platform::{NativeWindowHandles, WindowPort};
|
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_logical_device_probe, create_vulkan_surface_probe,
|
||||||
probe_vulkan_runtime_capabilities, triangle_shader_manifest, validate_shader_manifest,
|
probe_vulkan_loader, triangle_shader_manifest, validate_shader_manifest, VulkanInstanceConfig,
|
||||||
VulkanInstanceConfig, VulkanInstanceProbe, VulkanRuntimeCapabilityProbe,
|
VulkanInstanceProbe, VulkanLogicalDeviceProbe,
|
||||||
};
|
};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
@@ -209,6 +209,11 @@ struct VulkanBootstrapProbe {
|
|||||||
device_status: VulkanDeviceStatus,
|
device_status: VulkanDeviceStatus,
|
||||||
device_name: Option<String>,
|
device_name: Option<String>,
|
||||||
device_error: Option<String>,
|
device_error: Option<String>,
|
||||||
|
logical_device_status: VulkanLogicalDeviceStatus,
|
||||||
|
logical_device_graphics_queue_family: Option<u32>,
|
||||||
|
logical_device_present_queue_family: Option<u32>,
|
||||||
|
logical_device_enabled_extension_count: Option<u32>,
|
||||||
|
logical_device_error: Option<String>,
|
||||||
swapchain_status: VulkanSwapchainStatus,
|
swapchain_status: VulkanSwapchainStatus,
|
||||||
swapchain_width: Option<u32>,
|
swapchain_width: Option<u32>,
|
||||||
swapchain_height: Option<u32>,
|
swapchain_height: Option<u32>,
|
||||||
@@ -246,6 +251,11 @@ impl VulkanBootstrapProbe {
|
|||||||
device_status: VulkanDeviceStatus::Skipped,
|
device_status: VulkanDeviceStatus::Skipped,
|
||||||
device_name: None,
|
device_name: None,
|
||||||
device_error: None,
|
device_error: None,
|
||||||
|
logical_device_status: VulkanLogicalDeviceStatus::Skipped,
|
||||||
|
logical_device_graphics_queue_family: None,
|
||||||
|
logical_device_present_queue_family: None,
|
||||||
|
logical_device_enabled_extension_count: None,
|
||||||
|
logical_device_error: None,
|
||||||
swapchain_status: VulkanSwapchainStatus::Skipped,
|
swapchain_status: VulkanSwapchainStatus::Skipped,
|
||||||
swapchain_width: None,
|
swapchain_width: None,
|
||||||
swapchain_height: None,
|
swapchain_height: None,
|
||||||
@@ -272,6 +282,11 @@ impl VulkanBootstrapProbe {
|
|||||||
device_status: VulkanDeviceStatus::Skipped,
|
device_status: VulkanDeviceStatus::Skipped,
|
||||||
device_name: None,
|
device_name: None,
|
||||||
device_error: None,
|
device_error: None,
|
||||||
|
logical_device_status: VulkanLogicalDeviceStatus::Skipped,
|
||||||
|
logical_device_graphics_queue_family: None,
|
||||||
|
logical_device_present_queue_family: None,
|
||||||
|
logical_device_enabled_extension_count: None,
|
||||||
|
logical_device_error: None,
|
||||||
swapchain_status: VulkanSwapchainStatus::Skipped,
|
swapchain_status: VulkanSwapchainStatus::Skipped,
|
||||||
swapchain_width: None,
|
swapchain_width: None,
|
||||||
swapchain_height: None,
|
swapchain_height: None,
|
||||||
@@ -294,6 +309,11 @@ impl VulkanBootstrapProbe {
|
|||||||
device_status: VulkanDeviceStatus::Skipped,
|
device_status: VulkanDeviceStatus::Skipped,
|
||||||
device_name: None,
|
device_name: None,
|
||||||
device_error: None,
|
device_error: None,
|
||||||
|
logical_device_status: VulkanLogicalDeviceStatus::Skipped,
|
||||||
|
logical_device_graphics_queue_family: None,
|
||||||
|
logical_device_present_queue_family: None,
|
||||||
|
logical_device_enabled_extension_count: None,
|
||||||
|
logical_device_error: None,
|
||||||
swapchain_status: VulkanSwapchainStatus::Skipped,
|
swapchain_status: VulkanSwapchainStatus::Skipped,
|
||||||
swapchain_width: None,
|
swapchain_width: None,
|
||||||
swapchain_height: None,
|
swapchain_height: None,
|
||||||
@@ -392,10 +412,11 @@ impl VulkanBootstrapProbe {
|
|||||||
let Some(instance) = instance else {
|
let Some(instance) = instance else {
|
||||||
self.device_status = VulkanDeviceStatus::Failed;
|
self.device_status = VulkanDeviceStatus::Failed;
|
||||||
self.device_error = Some("Vulkan instance probe was not retained".to_string());
|
self.device_error = Some("Vulkan instance probe was not retained".to_string());
|
||||||
|
self.logical_device_status = VulkanLogicalDeviceStatus::Skipped;
|
||||||
self.swapchain_status = VulkanSwapchainStatus::Skipped;
|
self.swapchain_status = VulkanSwapchainStatus::Skipped;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
match probe_vulkan_runtime_capabilities(
|
match create_vulkan_logical_device_probe(
|
||||||
instance,
|
instance,
|
||||||
surface,
|
surface,
|
||||||
(
|
(
|
||||||
@@ -403,23 +424,36 @@ impl VulkanBootstrapProbe {
|
|||||||
self.window_height.unwrap_or(1).max(1),
|
self.window_height.unwrap_or(1).max(1),
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
Ok(runtime) => self.record_runtime_capabilities(runtime),
|
Ok(device) => self.record_logical_device_probe(&device),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
self.device_status = VulkanDeviceStatus::Failed;
|
self.device_status = VulkanDeviceStatus::Failed;
|
||||||
self.device_error = Some(err.to_string());
|
self.device_error = Some(err.to_string());
|
||||||
|
self.logical_device_status = VulkanLogicalDeviceStatus::Failed;
|
||||||
|
self.logical_device_error = Some(err.to_string());
|
||||||
self.swapchain_status = VulkanSwapchainStatus::Failed;
|
self.swapchain_status = VulkanSwapchainStatus::Failed;
|
||||||
self.swapchain_error = Some(err.to_string());
|
self.swapchain_error = Some(err.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_runtime_capabilities(&mut self, runtime: VulkanRuntimeCapabilityProbe) {
|
fn record_logical_device_probe(&mut self, device: &VulkanLogicalDeviceProbe) {
|
||||||
self.device_status = VulkanDeviceStatus::Selected;
|
self.device_status = VulkanDeviceStatus::Selected;
|
||||||
self.device_name = Some(runtime.capability.device_name);
|
self.device_name = Some(device.runtime.capability.device_name.clone());
|
||||||
|
self.logical_device_status = VulkanLogicalDeviceStatus::Created;
|
||||||
|
self.logical_device_graphics_queue_family = Some(device.report.graphics_queue_family);
|
||||||
|
self.logical_device_present_queue_family = Some(device.report.present_queue_family);
|
||||||
|
self.logical_device_enabled_extension_count = Some(
|
||||||
|
device
|
||||||
|
.report
|
||||||
|
.enabled_extensions
|
||||||
|
.len()
|
||||||
|
.try_into()
|
||||||
|
.unwrap_or(u32::MAX),
|
||||||
|
);
|
||||||
self.swapchain_status = VulkanSwapchainStatus::Planned;
|
self.swapchain_status = VulkanSwapchainStatus::Planned;
|
||||||
self.swapchain_width = Some(runtime.swapchain.extent.0);
|
self.swapchain_width = Some(device.runtime.swapchain.extent.0);
|
||||||
self.swapchain_height = Some(runtime.swapchain.extent.1);
|
self.swapchain_height = Some(device.runtime.swapchain.extent.1);
|
||||||
self.swapchain_image_count = Some(runtime.swapchain.image_count);
|
self.swapchain_image_count = Some(device.runtime.swapchain.image_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,6 +544,23 @@ impl VulkanDeviceStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
enum VulkanLogicalDeviceStatus {
|
||||||
|
Skipped,
|
||||||
|
Created,
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VulkanLogicalDeviceStatus {
|
||||||
|
const fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Skipped => "skipped",
|
||||||
|
Self::Created => "created",
|
||||||
|
Self::Failed => "failed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
enum VulkanSwapchainStatus {
|
enum VulkanSwapchainStatus {
|
||||||
Skipped,
|
Skipped,
|
||||||
@@ -635,6 +686,11 @@ fn validate_smoke_options(
|
|||||||
"passed native smoke report requires selected Vulkan device".to_string()
|
"passed native smoke report requires selected Vulkan device".to_string()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if bootstrap.logical_device_status != VulkanLogicalDeviceStatus::Created {
|
||||||
|
return Err(
|
||||||
|
"passed native smoke report requires created Vulkan logical device".to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
if bootstrap.swapchain_status != VulkanSwapchainStatus::Planned {
|
if bootstrap.swapchain_status != VulkanSwapchainStatus::Planned {
|
||||||
return Err(
|
return Err(
|
||||||
"passed native smoke report requires planned Vulkan swapchain".to_string(),
|
"passed native smoke report requires planned Vulkan swapchain".to_string(),
|
||||||
@@ -651,7 +707,17 @@ 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}"))?;
|
||||||
Ok(render_json_object(&[
|
let mut fields = base_smoke_report_fields(options, &shader_manifest.manifest_hash);
|
||||||
|
fields.extend(vulkan_bootstrap_fields(bootstrap));
|
||||||
|
fields.push(("reason", optional_string(options.reason.as_deref())));
|
||||||
|
Ok(render_json_object(&fields))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base_smoke_report_fields(
|
||||||
|
options: &SmokeOptions,
|
||||||
|
shader_manifest_hash: &str,
|
||||||
|
) -> Vec<(&'static str, String)> {
|
||||||
|
vec![
|
||||||
("schema_version", json_string(SCHEMA_VERSION)),
|
("schema_version", json_string(SCHEMA_VERSION)),
|
||||||
("commit_sha", json_string(¤t_git_commit_sha())),
|
("commit_sha", json_string(¤t_git_commit_sha())),
|
||||||
("rust_toolchain", json_string(RUST_TOOLCHAIN)),
|
("rust_toolchain", json_string(RUST_TOOLCHAIN)),
|
||||||
@@ -668,10 +734,12 @@ fn render_smoke_report_json(
|
|||||||
"validation_error_count",
|
"validation_error_count",
|
||||||
optional_u32(options.validation_error_count),
|
optional_u32(options.validation_error_count),
|
||||||
),
|
),
|
||||||
(
|
("shader_manifest_hash", json_string(shader_manifest_hash)),
|
||||||
"shader_manifest_hash",
|
]
|
||||||
json_string(&shader_manifest.manifest_hash),
|
}
|
||||||
),
|
|
||||||
|
fn vulkan_bootstrap_fields(bootstrap: &VulkanBootstrapProbe) -> Vec<(&'static str, String)> {
|
||||||
|
vec![
|
||||||
(
|
(
|
||||||
"vulkan_loader_status",
|
"vulkan_loader_status",
|
||||||
json_string(bootstrap.loader_status.as_str()),
|
json_string(bootstrap.loader_status.as_str()),
|
||||||
@@ -726,6 +794,26 @@ fn render_smoke_report_json(
|
|||||||
"vulkan_device_error",
|
"vulkan_device_error",
|
||||||
optional_string(bootstrap.device_error.as_deref()),
|
optional_string(bootstrap.device_error.as_deref()),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"vulkan_logical_device_status",
|
||||||
|
json_string(bootstrap.logical_device_status.as_str()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vulkan_logical_device_graphics_queue_family",
|
||||||
|
optional_u32(bootstrap.logical_device_graphics_queue_family),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vulkan_logical_device_present_queue_family",
|
||||||
|
optional_u32(bootstrap.logical_device_present_queue_family),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vulkan_logical_device_enabled_extension_count",
|
||||||
|
optional_u32(bootstrap.logical_device_enabled_extension_count),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"vulkan_logical_device_error",
|
||||||
|
optional_string(bootstrap.logical_device_error.as_deref()),
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"vulkan_swapchain_status",
|
"vulkan_swapchain_status",
|
||||||
json_string(bootstrap.swapchain_status.as_str()),
|
json_string(bootstrap.swapchain_status.as_str()),
|
||||||
@@ -746,8 +834,7 @@ fn render_smoke_report_json(
|
|||||||
"vulkan_swapchain_error",
|
"vulkan_swapchain_error",
|
||||||
optional_string(bootstrap.swapchain_error.as_deref()),
|
optional_string(bootstrap.swapchain_error.as_deref()),
|
||||||
),
|
),
|
||||||
("reason", optional_string(options.reason.as_deref())),
|
]
|
||||||
]))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_json_object(fields: &[(&str, String)]) -> String {
|
fn render_json_object(fields: &[(&str, String)]) -> String {
|
||||||
@@ -861,6 +948,11 @@ mod tests {
|
|||||||
device_status: VulkanDeviceStatus::Selected,
|
device_status: VulkanDeviceStatus::Selected,
|
||||||
device_name: Some("Stage 0 GPU".to_string()),
|
device_name: Some("Stage 0 GPU".to_string()),
|
||||||
device_error: None,
|
device_error: None,
|
||||||
|
logical_device_status: VulkanLogicalDeviceStatus::Created,
|
||||||
|
logical_device_graphics_queue_family: Some(0),
|
||||||
|
logical_device_present_queue_family: Some(0),
|
||||||
|
logical_device_enabled_extension_count: Some(1),
|
||||||
|
logical_device_error: None,
|
||||||
swapchain_status: VulkanSwapchainStatus::Planned,
|
swapchain_status: VulkanSwapchainStatus::Planned,
|
||||||
swapchain_width: Some(1280),
|
swapchain_width: Some(1280),
|
||||||
swapchain_height: Some(720),
|
swapchain_height: Some(720),
|
||||||
@@ -1281,6 +1373,40 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_passed_without_created_logical_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 {
|
||||||
|
logical_device_status: VulkanLogicalDeviceStatus::Failed,
|
||||||
|
logical_device_error: Some("Vulkan logical device creation failed".to_string()),
|
||||||
|
..probe_fixture()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Err("passed native smoke report requires created Vulkan logical device".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(&[
|
||||||
@@ -1315,6 +1441,11 @@ mod tests {
|
|||||||
device_status: VulkanDeviceStatus::Skipped,
|
device_status: VulkanDeviceStatus::Skipped,
|
||||||
device_name: None,
|
device_name: None,
|
||||||
device_error: None,
|
device_error: None,
|
||||||
|
logical_device_status: VulkanLogicalDeviceStatus::Skipped,
|
||||||
|
logical_device_graphics_queue_family: None,
|
||||||
|
logical_device_present_queue_family: None,
|
||||||
|
logical_device_enabled_extension_count: None,
|
||||||
|
logical_device_error: None,
|
||||||
swapchain_status: VulkanSwapchainStatus::Skipped,
|
swapchain_status: VulkanSwapchainStatus::Skipped,
|
||||||
swapchain_width: None,
|
swapchain_width: None,
|
||||||
swapchain_height: None,
|
swapchain_height: None,
|
||||||
@@ -1346,6 +1477,8 @@ mod tests {
|
|||||||
));
|
));
|
||||||
assert!(json.contains("\"vulkan_device_status\": \"skipped\""));
|
assert!(json.contains("\"vulkan_device_status\": \"skipped\""));
|
||||||
assert!(json.contains("\"vulkan_device_name\": null"));
|
assert!(json.contains("\"vulkan_device_name\": null"));
|
||||||
|
assert!(json.contains("\"vulkan_logical_device_status\": \"skipped\""));
|
||||||
|
assert!(json.contains("\"vulkan_logical_device_graphics_queue_family\": null"));
|
||||||
assert!(json.contains("\"vulkan_swapchain_status\": \"skipped\""));
|
assert!(json.contains("\"vulkan_swapchain_status\": \"skipped\""));
|
||||||
assert!(json.contains("\"vulkan_swapchain_width\": null"));
|
assert!(json.contains("\"vulkan_swapchain_width\": null"));
|
||||||
assert!(json.contains("\"reason\": \"runner unavailable\""));
|
assert!(json.contains("\"reason\": \"runner unavailable\""));
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ S0-VK-029 covered cargo test -p xtask --offline native_smoke_audit_accepts_compl
|
|||||||
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-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-VK-032 covered cargo test -p fparkan-vulkan-smoke --offline rejects_passed_without_planned_swapchain
|
||||||
|
S0-VK-033 covered cargo test -p fparkan-vulkan-smoke --offline rejects_passed_without_created_logical_device
|
||||||
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
|
||||||
|
|||||||
|
@@ -62,6 +62,7 @@
|
|||||||
`S0-VK-030`
|
`S0-VK-030`
|
||||||
`S0-VK-031`
|
`S0-VK-031`
|
||||||
`S0-VK-032`
|
`S0-VK-032`
|
||||||
|
`S0-VK-033`
|
||||||
`S0-LIMIT-001`
|
`S0-LIMIT-001`
|
||||||
`S0-LIMIT-002`
|
`S0-LIMIT-002`
|
||||||
`L1-P1-NRES-001`
|
`L1-P1-NRES-001`
|
||||||
|
|||||||
@@ -1542,6 +1542,13 @@ fn validate_native_smoke_report(
|
|||||||
"selected",
|
"selected",
|
||||||
failures,
|
failures,
|
||||||
);
|
);
|
||||||
|
expect_string_field(
|
||||||
|
platform,
|
||||||
|
report,
|
||||||
|
"vulkan_logical_device_status",
|
||||||
|
"created",
|
||||||
|
failures,
|
||||||
|
);
|
||||||
expect_string_field(
|
expect_string_field(
|
||||||
platform,
|
platform,
|
||||||
report,
|
report,
|
||||||
@@ -1558,6 +1565,27 @@ fn validate_native_smoke_report(
|
|||||||
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_nonempty_string(platform, report, "vulkan_device_name", failures);
|
||||||
|
expect_u64_at_least(
|
||||||
|
platform,
|
||||||
|
report,
|
||||||
|
"vulkan_logical_device_enabled_extension_count",
|
||||||
|
1,
|
||||||
|
failures,
|
||||||
|
);
|
||||||
|
expect_u64_at_least(
|
||||||
|
platform,
|
||||||
|
report,
|
||||||
|
"vulkan_logical_device_graphics_queue_family",
|
||||||
|
0,
|
||||||
|
failures,
|
||||||
|
);
|
||||||
|
expect_u64_at_least(
|
||||||
|
platform,
|
||||||
|
report,
|
||||||
|
"vulkan_logical_device_present_queue_family",
|
||||||
|
0,
|
||||||
|
failures,
|
||||||
|
);
|
||||||
expect_u64_at_least(platform, report, "vulkan_swapchain_width", 1, 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_height", 1, failures);
|
||||||
expect_u64_at_least(
|
expect_u64_at_least(
|
||||||
@@ -2260,6 +2288,10 @@ mod tests {
|
|||||||
"vulkan_surface_status": "created",
|
"vulkan_surface_status": "created",
|
||||||
"vulkan_device_status": "selected",
|
"vulkan_device_status": "selected",
|
||||||
"vulkan_device_name": format!("{platform} GPU"),
|
"vulkan_device_name": format!("{platform} GPU"),
|
||||||
|
"vulkan_logical_device_status": "created",
|
||||||
|
"vulkan_logical_device_graphics_queue_family": 0,
|
||||||
|
"vulkan_logical_device_present_queue_family": 0,
|
||||||
|
"vulkan_logical_device_enabled_extension_count": 1,
|
||||||
"vulkan_swapchain_status": "planned",
|
"vulkan_swapchain_status": "planned",
|
||||||
"vulkan_swapchain_width": 1280,
|
"vulkan_swapchain_width": 1280,
|
||||||
"vulkan_swapchain_height": 720,
|
"vulkan_swapchain_height": 720,
|
||||||
|
|||||||
Reference in New Issue
Block a user