From 5a60671bd6e437a728f2805dc94d14128723de90 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Thu, 25 Jun 2026 08:23:41 +0400 Subject: fix(vulkan-policy): report sampled formats and limits --- .../fparkan-render-vulkan/src/ffi/capabilities.rs | 44 +++++++++++++-- adapters/fparkan-render-vulkan/src/ffi/tests.rs | 42 ++++++++++++++- adapters/fparkan-render-vulkan/src/policy.rs | 63 +++++++++++++++++++++- 3 files changed, 144 insertions(+), 5 deletions(-) diff --git a/adapters/fparkan-render-vulkan/src/ffi/capabilities.rs b/adapters/fparkan-render-vulkan/src/ffi/capabilities.rs index c0de225..40cd232 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/capabilities.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/capabilities.rs @@ -7,9 +7,9 @@ use std::ffi::CStr; use super::{VulkanInstanceProbe, VulkanSurfaceProbe}; use crate::policy::{ compare_reports, plan_vulkan_swapchain, validate_device_for_request, VulkanCapabilityError, - VulkanCapabilityReport, VulkanDeviceType, VulkanPhysicalDeviceRecord, VulkanQueueFamily, - VulkanSurfaceFormat, VulkanSwapchainError, VulkanSwapchainPlan, VulkanSwapchainRequest, - VulkanSwapchainSurfaceCapabilities, + VulkanCapabilityReport, VulkanDeviceLimits, VulkanDeviceType, VulkanPhysicalDeviceRecord, + VulkanQueueFamily, VulkanSurfaceFormat, VulkanSwapchainError, VulkanSwapchainPlan, + VulkanSwapchainRequest, VulkanSwapchainSurfaceCapabilities, }; /// Live Vulkan device/surface capability probe. @@ -242,6 +242,7 @@ fn live_device_candidate( let present_modes = live_present_modes(surface, device, &name)?; let surface_capabilities = live_surface_capabilities(surface, device, &name)?; let supported_depth_stencil_formats = live_depth_stencil_formats(instance, device); + let sampled_image_formats = live_sampled_image_formats(instance, device); let queue_families = queue_properties .iter() .enumerate() @@ -284,6 +285,13 @@ fn live_device_candidate( present_modes: present_modes.clone(), surface_capabilities, supported_depth_stencil_formats, + sampled_image_formats, + limits: VulkanDeviceLimits { + max_image_dimension_2d: properties.limits.max_image_dimension2_d, + max_sampler_allocation_count: properties.limits.max_sampler_allocation_count, + max_per_stage_descriptor_samplers: properties.limits.max_per_stage_descriptor_samplers, + max_bound_descriptor_sets: properties.limits.max_bound_descriptor_sets, + }, }; let capability = validate_device_for_request(&record, render_request) .map_err(VulkanRuntimeCapabilityError::Capability)?; @@ -468,3 +476,33 @@ fn live_depth_stencil_formats( .map(vk::Format::as_raw) .collect() } + +fn live_sampled_image_formats( + instance: &VulkanInstanceProbe, + device: vk::PhysicalDevice, +) -> Vec { + [ + vk::Format::R8G8B8A8_SRGB, + vk::Format::B8G8R8A8_SRGB, + vk::Format::D16_UNORM, + vk::Format::D32_SFLOAT, + vk::Format::D24_UNORM_S8_UINT, + vk::Format::D32_SFLOAT_S8_UINT, + ] + .into_iter() + .filter(|format| { + let properties = { + // SAFETY: `device` belongs to `instance`; format-property queries copy data by value. + unsafe { + instance + .instance + .get_physical_device_format_properties(device, *format) + } + }; + properties + .optimal_tiling_features + .contains(vk::FormatFeatureFlags::SAMPLED_IMAGE) + }) + .map(vk::Format::as_raw) + .collect() +} diff --git a/adapters/fparkan-render-vulkan/src/ffi/tests.rs b/adapters/fparkan-render-vulkan/src/ffi/tests.rs index 891789d..d472a28 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/tests.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/tests.rs @@ -317,6 +317,36 @@ fn capability_gate_respects_request_specific_depth_profiles() { assert!(report.rejected_devices.is_empty()); } +#[test] +fn capability_report_preserves_informational_sampled_formats_and_limits() { + let report = select_physical_device(&[device( + "Telemetry GPU", + VulkanDeviceType::DiscreteGpu, + 0, + true, + false, + )]) + .expect("selected device"); + + assert_eq!( + report.informational_capabilities.sampled_color_formats, + vec![vk::Format::B8G8R8A8_SRGB.as_raw()] + ); + assert_eq!( + report.informational_capabilities.sampled_depth_formats, + vec![vk::Format::D32_SFLOAT.as_raw()] + ); + assert_eq!( + report.informational_capabilities.limits, + VulkanDeviceLimits { + max_image_dimension_2d: 4096, + max_sampler_allocation_count: 4096, + max_per_stage_descriptor_samplers: 16, + max_bound_descriptor_sets: 4, + } + ); +} + #[test] fn capability_report_json_is_stable() { let mut rejected = device("Rejected", VulkanDeviceType::IntegratedGpu, 0, true, false); @@ -329,7 +359,7 @@ fn capability_report_json_is_stable() { assert_eq!( 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\"],\"rejected_devices\":[{\"device_name\":\"Rejected\",\"reason_code\":\"missing_present_mode\",\"reason\":\"Vulkan device Rejected has no supported present mode\"}]}" + "{\"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\"],\"informational_capabilities\":{\"sampled_color_formats\":[50],\"sampled_depth_formats\":[126],\"limits\":{\"max_image_dimension_2d\":4096,\"max_sampler_allocation_count\":4096,\"max_per_stage_descriptor_samplers\":16,\"max_bound_descriptor_sets\":4}},\"rejected_devices\":[{\"device_name\":\"Rejected\",\"reason_code\":\"missing_present_mode\",\"reason\":\"Vulkan device Rejected has no supported present mode\"}]}" ); } @@ -695,6 +725,16 @@ fn device( vk::Format::D32_SFLOAT_S8_UINT.as_raw(), vk::Format::D32_SFLOAT.as_raw(), ], + sampled_image_formats: vec![ + vk::Format::B8G8R8A8_SRGB.as_raw(), + vk::Format::D32_SFLOAT.as_raw(), + ], + limits: VulkanDeviceLimits { + max_image_dimension_2d: 4096, + max_sampler_allocation_count: 4096, + max_per_stage_descriptor_samplers: 16, + max_bound_descriptor_sets: 4, + }, } } diff --git a/adapters/fparkan-render-vulkan/src/policy.rs b/adapters/fparkan-render-vulkan/src/policy.rs index 040cefb..3615aae 100644 --- a/adapters/fparkan-render-vulkan/src/policy.rs +++ b/adapters/fparkan-render-vulkan/src/policy.rs @@ -185,6 +185,10 @@ pub struct VulkanPhysicalDeviceRecord { pub surface_capabilities: VulkanSwapchainSurfaceCapabilities, /// Depth/stencil attachment formats supported by the device. pub supported_depth_stencil_formats: Vec, + /// Formats that can be used as sampled images. + pub sampled_image_formats: Vec, + /// Informational device limits relevant to the future Stage 0 baseline. + pub limits: VulkanDeviceLimits, } impl VulkanPhysicalDeviceRecord { @@ -197,6 +201,30 @@ impl VulkanPhysicalDeviceRecord { } } +/// Informational device limits relevant to future Stage 0 capability growth. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)] +pub struct VulkanDeviceLimits { + /// Maximum 2D image dimension supported by the device. + pub max_image_dimension_2d: u32, + /// Maximum number of live samplers supported by the device. + pub max_sampler_allocation_count: u32, + /// Maximum number of sampler descriptors per stage. + pub max_per_stage_descriptor_samplers: u32, + /// Maximum number of bound descriptor sets. + pub max_bound_descriptor_sets: u32, +} + +/// Informational capabilities preserved in deterministic Stage 0 reports. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct VulkanInformationalCapabilities { + /// Color formats that support sampled-image usage. + pub sampled_color_formats: Vec, + /// Depth/stencil formats that support sampled-image usage. + pub sampled_depth_formats: Vec, + /// Future-baseline device limits. + pub limits: VulkanDeviceLimits, +} + /// Selected device and queue capability report. #[derive(Clone, Debug, Eq, PartialEq)] pub struct VulkanCapabilityReport { @@ -216,6 +244,8 @@ pub struct VulkanCapabilityReport { pub portability_subset: bool, /// Enabled device extensions. pub enabled_extensions: Vec, + /// Informational capabilities retained for future baseline planning. + pub informational_capabilities: VulkanInformationalCapabilities, /// Devices rejected by deterministic Stage 0 capability validation. pub rejected_devices: Vec, } @@ -459,6 +489,7 @@ pub fn render_capability_report_json(report: &VulkanCapabilityReport) -> String present_queue_family: u32, portability_subset: bool, enabled_extensions: &'a [String], + informational_capabilities: &'a VulkanInformationalCapabilities, rejected_devices: &'a [VulkanRejectedDeviceReport], } @@ -472,9 +503,10 @@ pub fn render_capability_report_json(report: &VulkanCapabilityReport) -> String present_queue_family: report.present_queue_family, portability_subset: report.portability_subset, enabled_extensions: &report.enabled_extensions, + informational_capabilities: &report.informational_capabilities, 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\":[],\"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\":[],\"informational_capabilities\":{\"sampled_color_formats\":[],\"sampled_depth_formats\":[],\"limits\":{\"max_image_dimension_2d\":0,\"max_sampler_allocation_count\":0,\"max_per_stage_descriptor_samplers\":0,\"max_bound_descriptor_sets\":0}},\"rejected_devices\":[]}", ) } @@ -694,6 +726,7 @@ pub(crate) fn validate_device_for_request( present_queue_family, portability_subset, enabled_extensions, + informational_capabilities: informational_capabilities(device), rejected_devices: Vec::new(), }) } @@ -780,6 +813,21 @@ fn supports_depth_stencil_request( }) } +fn informational_capabilities( + device: &VulkanPhysicalDeviceRecord, +) -> VulkanInformationalCapabilities { + let (sampled_depth_formats, sampled_color_formats): (Vec<_>, Vec<_>) = device + .sampled_image_formats + .iter() + .copied() + .partition(|format| is_depth_stencil_format(*format)); + VulkanInformationalCapabilities { + sampled_color_formats, + sampled_depth_formats, + limits: device.limits, + } +} + fn required_depth_stencil_formats(depth: DepthStencilSupport) -> &'static [vk::Format] { match (depth.depth_bits, depth.stencil_bits) { (0, 0) => &[], @@ -796,6 +844,19 @@ fn required_depth_stencil_formats(depth: DepthStencilSupport) -> &'static [vk::F } } +fn is_depth_stencil_format(format: i32) -> bool { + matches!( + vk::Format::from_raw(format), + vk::Format::D16_UNORM + | vk::Format::X8_D24_UNORM_PACK32 + | vk::Format::D32_SFLOAT + | vk::Format::S8_UINT + | vk::Format::D16_UNORM_S8_UINT + | vk::Format::D24_UNORM_S8_UINT + | vk::Format::D32_SFLOAT_S8_UINT + ) +} + fn score_device( device: &VulkanPhysicalDeviceRecord, graphics_queue_family: u32, -- cgit v1.2.3