feat(vulkan-policy): report rejected device diagnostics
This commit is contained in:
@@ -140,6 +140,14 @@ fn device_selection_skips_rejected_candidates_before_accepting_valid_gpu() {
|
|||||||
assert_eq!(report.device_name, "Accepted");
|
assert_eq!(report.device_name, "Accepted");
|
||||||
assert_eq!(report.graphics_queue_family, 2);
|
assert_eq!(report.graphics_queue_family, 2);
|
||||||
assert_eq!(report.present_queue_family, 2);
|
assert_eq!(report.present_queue_family, 2);
|
||||||
|
assert_eq!(
|
||||||
|
report.rejected_devices,
|
||||||
|
vec![VulkanRejectedDeviceReport {
|
||||||
|
device_name: "Rejected".to_string(),
|
||||||
|
reason_code: "no_present_queue",
|
||||||
|
reason: "Vulkan device Rejected has no present queue".to_string(),
|
||||||
|
}]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -281,18 +289,17 @@ fn rejects_missing_graphics_present_swapchain_and_format() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn capability_report_json_is_stable() {
|
fn capability_report_json_is_stable() {
|
||||||
let report = select_physical_device(&[device(
|
let mut rejected = device("Rejected", VulkanDeviceType::IntegratedGpu, 0, true, false);
|
||||||
"GPU \"A\"",
|
rejected.present_modes.clear();
|
||||||
VulkanDeviceType::DiscreteGpu,
|
let report = select_physical_device(&[
|
||||||
3,
|
rejected,
|
||||||
true,
|
device("GPU \"A\"", VulkanDeviceType::DiscreteGpu, 3, true, false),
|
||||||
false,
|
])
|
||||||
)])
|
|
||||||
.expect("selected device");
|
.expect("selected device");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
render_capability_report_json(&report),
|
render_capability_report_json(&report),
|
||||||
"{\"schema\":1,\"vulkan_api\":\"1.1.0\",\"device_name\":\"GPU \\\"A\\\"\",\"score\":1101,\"graphics_queue_family\":3,\"present_queue_family\":3,\"portability_subset\":false,\"enabled_extensions\":[\"VK_KHR_swapchain\"]}"
|
"{\"schema\":1,\"vulkan_api\":\"1.1.0\",\"device_name\":\"GPU \\\"A\\\"\",\"score\":1101,\"graphics_queue_family\":3,\"present_queue_family\":3,\"portability_subset\":false,\"enabled_extensions\":[\"VK_KHR_swapchain\"],\"rejected_devices\":[{\"device_name\":\"Rejected\",\"reason_code\":\"missing_present_mode\",\"reason\":\"Vulkan device Rejected has no supported present mode\"}]}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -213,6 +213,19 @@ pub struct VulkanCapabilityReport {
|
|||||||
pub portability_subset: bool,
|
pub portability_subset: bool,
|
||||||
/// Enabled device extensions.
|
/// Enabled device extensions.
|
||||||
pub enabled_extensions: Vec<String>,
|
pub enabled_extensions: Vec<String>,
|
||||||
|
/// Devices rejected by deterministic Stage 0 capability validation.
|
||||||
|
pub rejected_devices: Vec<VulkanRejectedDeviceReport>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deterministic rejection reason for an unsuitable physical device.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||||
|
pub struct VulkanRejectedDeviceReport {
|
||||||
|
/// Human-readable device name.
|
||||||
|
pub device_name: String,
|
||||||
|
/// Stable machine-readable rejection code.
|
||||||
|
pub reason_code: &'static str,
|
||||||
|
/// Actionable rejection summary.
|
||||||
|
pub reason: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Vulkan capability selection error.
|
/// Vulkan capability selection error.
|
||||||
@@ -308,11 +321,13 @@ pub fn select_physical_device(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut best = None;
|
let mut best = None;
|
||||||
|
let mut rejected_devices = Vec::new();
|
||||||
let mut last_error = None;
|
let mut last_error = None;
|
||||||
for device in devices {
|
for device in devices {
|
||||||
let report = match validate_device(device) {
|
let report = match validate_device(device) {
|
||||||
Ok(report) => report,
|
Ok(report) => report,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
rejected_devices.push(rejected_device_report(device, &err));
|
||||||
last_error = Some(err);
|
last_error = Some(err);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -323,7 +338,10 @@ pub fn select_physical_device(
|
|||||||
_ => best = Some(report),
|
_ => best = Some(report),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
best.ok_or_else(|| last_error.unwrap_or(VulkanCapabilityError::NoPhysicalDevice))
|
let mut best =
|
||||||
|
best.ok_or_else(|| last_error.unwrap_or(VulkanCapabilityError::NoPhysicalDevice))?;
|
||||||
|
best.rejected_devices = rejected_devices;
|
||||||
|
Ok(best)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a deterministic swapchain plan from surface capabilities.
|
/// Builds a deterministic swapchain plan from surface capabilities.
|
||||||
@@ -411,6 +429,7 @@ pub fn render_capability_report_json(report: &VulkanCapabilityReport) -> String
|
|||||||
present_queue_family: u32,
|
present_queue_family: u32,
|
||||||
portability_subset: bool,
|
portability_subset: bool,
|
||||||
enabled_extensions: &'a [String],
|
enabled_extensions: &'a [String],
|
||||||
|
rejected_devices: &'a [VulkanRejectedDeviceReport],
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize_json_or_fallback(
|
serialize_json_or_fallback(
|
||||||
@@ -423,8 +442,9 @@ pub fn render_capability_report_json(report: &VulkanCapabilityReport) -> String
|
|||||||
present_queue_family: report.present_queue_family,
|
present_queue_family: report.present_queue_family,
|
||||||
portability_subset: report.portability_subset,
|
portability_subset: report.portability_subset,
|
||||||
enabled_extensions: &report.enabled_extensions,
|
enabled_extensions: &report.enabled_extensions,
|
||||||
|
rejected_devices: &report.rejected_devices,
|
||||||
},
|
},
|
||||||
"{\"schema\":0,\"vulkan_api\":\"0.0.0\",\"device_name\":\"unknown\",\"score\":0,\"graphics_queue_family\":0,\"present_queue_family\":0,\"portability_subset\":false,\"enabled_extensions\":[]}",
|
"{\"schema\":0,\"vulkan_api\":\"0.0.0\",\"device_name\":\"unknown\",\"score\":0,\"graphics_queue_family\":0,\"present_queue_family\":0,\"portability_subset\":false,\"enabled_extensions\":[],\"rejected_devices\":[]}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -637,9 +657,36 @@ pub(crate) fn validate_device(
|
|||||||
present_queue_family,
|
present_queue_family,
|
||||||
portability_subset,
|
portability_subset,
|
||||||
enabled_extensions,
|
enabled_extensions,
|
||||||
|
rejected_devices: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rejected_device_report(
|
||||||
|
device: &VulkanPhysicalDeviceRecord,
|
||||||
|
error: &VulkanCapabilityError,
|
||||||
|
) -> VulkanRejectedDeviceReport {
|
||||||
|
VulkanRejectedDeviceReport {
|
||||||
|
device_name: device.name.clone(),
|
||||||
|
reason_code: capability_error_code(error),
|
||||||
|
reason: error.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn capability_error_code(error: &VulkanCapabilityError) -> &'static str {
|
||||||
|
match error {
|
||||||
|
VulkanCapabilityError::NoPhysicalDevice => "no_physical_device",
|
||||||
|
VulkanCapabilityError::ApiVersionTooLow { .. } => "api_version_too_low",
|
||||||
|
VulkanCapabilityError::NoGraphicsQueue { .. } => "no_graphics_queue",
|
||||||
|
VulkanCapabilityError::NoPresentQueue { .. } => "no_present_queue",
|
||||||
|
VulkanCapabilityError::MissingSwapchainExtension { .. } => "missing_swapchain_extension",
|
||||||
|
VulkanCapabilityError::MissingSurfaceFormat { .. } => "missing_surface_format",
|
||||||
|
VulkanCapabilityError::MissingPresentMode { .. } => "missing_present_mode",
|
||||||
|
VulkanCapabilityError::MissingColorAttachmentUsage { .. } => {
|
||||||
|
"missing_color_attachment_usage"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn select_queue_families(
|
fn select_queue_families(
|
||||||
device: &VulkanPhysicalDeviceRecord,
|
device: &VulkanPhysicalDeviceRecord,
|
||||||
) -> Result<(u32, u32), VulkanCapabilityError> {
|
) -> Result<(u32, u32), VulkanCapabilityError> {
|
||||||
|
|||||||
Reference in New Issue
Block a user