aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi.rs7
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/capabilities.rs87
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/runtime.rs32
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/smoke.rs24
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs4
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/tests.rs37
-rw-r--r--adapters/fparkan-render-vulkan/src/policy.rs72
-rw-r--r--apps/fparkan-vulkan-smoke/src/main.rs2
8 files changed, 234 insertions, 31 deletions
diff --git a/adapters/fparkan-render-vulkan/src/ffi.rs b/adapters/fparkan-render-vulkan/src/ffi.rs
index ece45e0..386cb68 100644
--- a/adapters/fparkan-render-vulkan/src/ffi.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi.rs
@@ -38,7 +38,8 @@ mod swapchain_resources;
mod validation;
pub use self::capabilities::{
- probe_vulkan_runtime_capabilities, VulkanRuntimeCapabilityError, VulkanRuntimeCapabilityProbe,
+ probe_vulkan_runtime_capabilities, probe_vulkan_runtime_capabilities_for_request,
+ VulkanRuntimeCapabilityError, VulkanRuntimeCapabilityProbe,
};
pub use self::instance::{
create_vulkan_instance_probe, plan_vulkan_instance, probe_vulkan_loader,
@@ -54,8 +55,8 @@ use self::resources::{
VulkanFrameSync,
};
pub use self::runtime::{
- create_vulkan_logical_device_probe, VulkanLogicalDeviceError, VulkanLogicalDeviceProbe,
- VulkanLogicalDeviceReport,
+ create_vulkan_logical_device_probe, create_vulkan_logical_device_probe_for_request,
+ VulkanLogicalDeviceError, VulkanLogicalDeviceProbe, VulkanLogicalDeviceReport,
};
pub use self::smoke_types::{
VulkanSmokeBootstrapProgress, VulkanSmokeBootstrapSnapshot, VulkanSmokeFrameOutcome,
diff --git a/adapters/fparkan-render-vulkan/src/ffi/capabilities.rs b/adapters/fparkan-render-vulkan/src/ffi/capabilities.rs
index 94fb3a6..c0de225 100644
--- a/adapters/fparkan-render-vulkan/src/ffi/capabilities.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi/capabilities.rs
@@ -1,11 +1,12 @@
#![allow(unsafe_code)]
use ash::vk;
+use fparkan_platform::RenderRequest;
use std::ffi::CStr;
use super::{VulkanInstanceProbe, VulkanSurfaceProbe};
use crate::policy::{
- compare_reports, plan_vulkan_swapchain, validate_device, VulkanCapabilityError,
+ compare_reports, plan_vulkan_swapchain, validate_device_for_request, VulkanCapabilityError,
VulkanCapabilityReport, VulkanDeviceType, VulkanPhysicalDeviceRecord, VulkanQueueFamily,
VulkanSurfaceFormat, VulkanSwapchainError, VulkanSwapchainPlan, VulkanSwapchainRequest,
VulkanSwapchainSurfaceCapabilities,
@@ -133,14 +134,42 @@ pub fn probe_vulkan_runtime_capabilities(
surface: &VulkanSurfaceProbe,
drawable_extent: (u32, u32),
) -> Result<VulkanRuntimeCapabilityProbe, VulkanRuntimeCapabilityError> {
- let selected = select_live_device_candidate(instance, surface, drawable_extent)?;
+ let selected = select_live_device_candidate_for_request(
+ instance,
+ surface,
+ drawable_extent,
+ &RenderRequest::conservative(),
+ )?;
+ Ok(selected.runtime)
+}
+
+/// Probes live Vulkan device, queue, surface and swapchain capabilities for a
+/// specific Stage 0 render request.
+///
+/// # Errors
+///
+/// Returns [`VulkanRuntimeCapabilityError`] when device enumeration, surface
+/// capability queries, Stage 0 device selection, or swapchain planning fails.
+pub fn probe_vulkan_runtime_capabilities_for_request(
+ instance: &VulkanInstanceProbe,
+ surface: &VulkanSurfaceProbe,
+ drawable_extent: (u32, u32),
+ render_request: &RenderRequest,
+) -> Result<VulkanRuntimeCapabilityProbe, VulkanRuntimeCapabilityError> {
+ let selected = select_live_device_candidate_for_request(
+ instance,
+ surface,
+ drawable_extent,
+ render_request,
+ )?;
Ok(selected.runtime)
}
-pub(super) fn select_live_device_candidate(
+pub(super) fn select_live_device_candidate_for_request(
instance: &VulkanInstanceProbe,
surface: &VulkanSurfaceProbe,
drawable_extent: (u32, u32),
+ render_request: &RenderRequest,
) -> Result<SelectedLiveDevice, VulkanRuntimeCapabilityError> {
let devices = {
// SAFETY: The Vulkan instance is live for this query and no handles are retained.
@@ -151,13 +180,14 @@ pub(super) fn select_live_device_candidate(
let mut best: Option<LiveDeviceCandidate> = None;
let mut last_error = None;
for (index, device) in devices.iter().copied().enumerate() {
- let candidate = match live_device_candidate(instance, surface, device, index) {
- Ok(candidate) => candidate,
- Err(err) => {
- last_error = Some(err);
- continue;
- }
- };
+ let candidate =
+ match live_device_candidate(instance, surface, device, index, render_request) {
+ Ok(candidate) => candidate,
+ Err(err) => {
+ last_error = Some(err);
+ continue;
+ }
+ };
match &best {
Some(existing)
if compare_reports(&candidate.capability, &existing.capability)
@@ -192,6 +222,7 @@ fn live_device_candidate(
surface: &VulkanSurfaceProbe,
device: vk::PhysicalDevice,
index: usize,
+ render_request: &RenderRequest,
) -> Result<LiveDeviceCandidate, VulkanRuntimeCapabilityError> {
let properties = {
// SAFETY: `device` was returned by this live instance and the result is copied by value.
@@ -210,6 +241,7 @@ fn live_device_candidate(
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 supported_depth_stencil_formats = live_depth_stencil_formats(instance, device);
let queue_families = queue_properties
.iter()
.enumerate()
@@ -251,8 +283,10 @@ fn live_device_candidate(
surface_formats: surface_formats.clone(),
present_modes: present_modes.clone(),
surface_capabilities,
+ supported_depth_stencil_formats,
};
- let capability = validate_device(&record).map_err(VulkanRuntimeCapabilityError::Capability)?;
+ let capability = validate_device_for_request(&record, render_request)
+ .map_err(VulkanRuntimeCapabilityError::Capability)?;
Ok(LiveDeviceCandidate {
physical_device: device,
capability,
@@ -403,3 +437,34 @@ pub(super) fn live_surface_capabilities(
supported_usage_flags: capabilities.supported_usage_flags.as_raw(),
})
}
+
+fn live_depth_stencil_formats(
+ instance: &VulkanInstanceProbe,
+ device: vk::PhysicalDevice,
+) -> Vec<i32> {
+ [
+ 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,
+ ]
+ .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::DEPTH_STENCIL_ATTACHMENT)
+ })
+ .map(vk::Format::as_raw)
+ .collect()
+}
diff --git a/adapters/fparkan-render-vulkan/src/ffi/runtime.rs b/adapters/fparkan-render-vulkan/src/ffi/runtime.rs
index 134bf3e..555c08c 100644
--- a/adapters/fparkan-render-vulkan/src/ffi/runtime.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi/runtime.rs
@@ -1,10 +1,11 @@
#![allow(unsafe_code)]
use ash::vk;
+use fparkan_platform::RenderRequest;
use std::ffi::CString;
use super::capabilities::{
- select_live_device_candidate, unique_queue_families, VulkanRuntimeCapabilityError,
+ select_live_device_candidate_for_request, unique_queue_families, VulkanRuntimeCapabilityError,
VulkanRuntimeCapabilityProbe,
};
use super::{VulkanInstanceProbe, VulkanSurfaceProbe};
@@ -125,8 +126,33 @@ pub fn create_vulkan_logical_device_probe(
surface: &VulkanSurfaceProbe,
drawable_extent: (u32, u32),
) -> Result<VulkanLogicalDeviceProbe, VulkanLogicalDeviceError> {
- let selected = select_live_device_candidate(instance, surface, drawable_extent)
- .map_err(VulkanLogicalDeviceError::Runtime)?;
+ create_vulkan_logical_device_probe_for_request(
+ instance,
+ surface,
+ drawable_extent,
+ &RenderRequest::conservative(),
+ )
+}
+
+/// Creates a Vulkan logical device for a specific Stage 0 render request.
+///
+/// # Errors
+///
+/// Returns [`VulkanLogicalDeviceError`] when runtime capability probing fails,
+/// device extension names are invalid, or `vkCreateDevice` fails.
+pub fn create_vulkan_logical_device_probe_for_request(
+ instance: &VulkanInstanceProbe,
+ surface: &VulkanSurfaceProbe,
+ drawable_extent: (u32, u32),
+ render_request: &RenderRequest,
+) -> Result<VulkanLogicalDeviceProbe, VulkanLogicalDeviceError> {
+ let selected = select_live_device_candidate_for_request(
+ instance,
+ surface,
+ drawable_extent,
+ render_request,
+ )
+ .map_err(VulkanLogicalDeviceError::Runtime)?;
let capability = &selected.runtime.capability;
let queue_priorities = [1.0_f32];
let queue_families = unique_queue_families(
diff --git a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs
index 02ea0d6..0274daf 100644
--- a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs
@@ -5,13 +5,13 @@ use ash::vk;
use super::{
create_command_pool, create_frame_sync, create_swapchain_resources,
create_triangle_index_buffer, create_triangle_vertex_buffer, create_validation_messenger,
- create_vulkan_instance_probe, create_vulkan_logical_device_probe, create_vulkan_surface_probe,
- create_vulkan_swapchain_probe_for_extent, destroy_allocated_buffer,
- destroy_swapchain_resources, plan_vulkan_surface, VulkanAllocatedBuffer, VulkanInstanceConfig,
- VulkanInstanceProbe, VulkanLogicalDeviceProbe, VulkanSmokeFrameOutcome, VulkanSmokeRenderer,
- VulkanSmokeRendererCreateInfo, VulkanSmokeRendererError, VulkanSmokeRendererReport,
- VulkanSurfaceProbe, VulkanSwapchainProbe, VulkanSwapchainResources, VulkanValidationMessenger,
- VulkanValidationReport,
+ create_vulkan_instance_probe, create_vulkan_logical_device_probe_for_request,
+ create_vulkan_surface_probe, create_vulkan_swapchain_probe_for_extent,
+ destroy_allocated_buffer, destroy_swapchain_resources, plan_vulkan_surface,
+ VulkanAllocatedBuffer, VulkanInstanceConfig, VulkanInstanceProbe, VulkanLogicalDeviceProbe,
+ VulkanSmokeFrameOutcome, VulkanSmokeRenderer, VulkanSmokeRendererCreateInfo,
+ VulkanSmokeRendererError, VulkanSmokeRendererReport, VulkanSurfaceProbe, VulkanSwapchainProbe,
+ VulkanSwapchainResources, VulkanValidationMessenger, VulkanValidationReport,
};
use crate::policy::KHR_PORTABILITY_SUBSET_EXTENSION;
use crate::shader_manifest::{triangle_shader_manifest, validate_shader_manifest};
@@ -106,9 +106,13 @@ impl VulkanSmokeRenderer {
if let Some(progress) = bootstrap_progress {
progress.mark_surface_created();
}
- let device =
- create_vulkan_logical_device_probe(&instance, &surface, create_info.drawable_extent)
- .map_err(VulkanSmokeRendererError::LogicalDevice)?;
+ let device = create_vulkan_logical_device_probe_for_request(
+ &instance,
+ &surface,
+ create_info.drawable_extent,
+ &create_info.render_request,
+ )
+ .map_err(VulkanSmokeRendererError::LogicalDevice)?;
if let Some(progress) = bootstrap_progress {
progress.mark_logical_device_created();
}
diff --git a/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs b/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs
index 836f82d..ea4a471 100644
--- a/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs
@@ -1,5 +1,5 @@
use ash::vk;
-use fparkan_platform::NativeWindowHandles;
+use fparkan_platform::{NativeWindowHandles, RenderRequest};
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Arc;
@@ -20,6 +20,8 @@ pub struct VulkanSmokeRendererCreateInfo {
pub native_handles: NativeWindowHandles,
/// Initial drawable extent.
pub drawable_extent: (u32, u32),
+ /// Stage 0 render request used for capability gating.
+ pub render_request: RenderRequest,
/// Whether validation layers must be enabled.
pub enable_validation: bool,
/// Optional shared bootstrap progress tracker for failure evidence.
diff --git a/adapters/fparkan-render-vulkan/src/ffi/tests.rs b/adapters/fparkan-render-vulkan/src/ffi/tests.rs
index c242d97..891789d 100644
--- a/adapters/fparkan-render-vulkan/src/ffi/tests.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi/tests.rs
@@ -8,7 +8,7 @@ use crate::shader_manifest::{
TRIANGLE_VERTEX_SPIRV_PATH, TRIANGLE_VERTEX_VALIDATE_COMMAND,
};
use crate::*;
-use fparkan_platform::RenderRequest;
+use fparkan_platform::{DepthStencilSupport, RenderRequest};
use fparkan_render::{
DrawCommand, DrawId, GpuMaterialId, GpuMeshId, IndexRange, RenderCommand, RenderPhase,
};
@@ -288,6 +288,36 @@ fn rejects_missing_graphics_present_swapchain_and_format() {
}
#[test]
+fn capability_gate_rejects_devices_without_requested_depth_stencil_support() {
+ let mut no_depth = device("No depth", VulkanDeviceType::DiscreteGpu, 0, true, false);
+ no_depth.supported_depth_stencil_formats = vec![vk::Format::D32_SFLOAT.as_raw()];
+
+ assert!(matches!(
+ select_physical_device(&[no_depth]),
+ Err(VulkanCapabilityError::MissingDepthStencilFormat { .. })
+ ));
+}
+
+#[test]
+fn capability_gate_respects_request_specific_depth_profiles() {
+ let mut no_stencil = device("No stencil", VulkanDeviceType::DiscreteGpu, 0, true, false);
+ no_stencil.supported_depth_stencil_formats = vec![vk::Format::D32_SFLOAT.as_raw()];
+ let relaxed_request = RenderRequest {
+ depth: DepthStencilSupport {
+ depth_bits: 32,
+ stencil_bits: 0,
+ },
+ ..RenderRequest::conservative()
+ };
+
+ let report = select_physical_device_for_request(&[no_stencil], &relaxed_request)
+ .expect("selected device for depth-only request");
+
+ assert_eq!(report.device_name, "No stencil");
+ assert!(report.rejected_devices.is_empty());
+}
+
+#[test]
fn capability_report_json_is_stable() {
let mut rejected = device("Rejected", VulkanDeviceType::IntegratedGpu, 0, true, false);
rejected.present_modes.clear();
@@ -660,6 +690,11 @@ fn device(
vk::PresentModeKHR::MAILBOX.as_raw(),
],
surface_capabilities: default_surface_capabilities(),
+ supported_depth_stencil_formats: vec![
+ vk::Format::D24_UNORM_S8_UINT.as_raw(),
+ vk::Format::D32_SFLOAT_S8_UINT.as_raw(),
+ vk::Format::D32_SFLOAT.as_raw(),
+ ],
}
}
diff --git a/adapters/fparkan-render-vulkan/src/policy.rs b/adapters/fparkan-render-vulkan/src/policy.rs
index ef9f0c4..040cefb 100644
--- a/adapters/fparkan-render-vulkan/src/policy.rs
+++ b/adapters/fparkan-render-vulkan/src/policy.rs
@@ -1,4 +1,5 @@
use ash::vk;
+use fparkan_platform::{DepthStencilSupport, RenderRequest};
use fparkan_render::{validate_command_list, RenderCommand, RenderCommandList, RenderError};
use serde::Serialize;
@@ -182,6 +183,8 @@ pub struct VulkanPhysicalDeviceRecord {
pub present_modes: Vec<i32>,
/// Surface capabilities accepted by the target surface.
pub surface_capabilities: VulkanSwapchainSurfaceCapabilities,
+ /// Depth/stencil attachment formats supported by the device.
+ pub supported_depth_stencil_formats: Vec<i32>,
}
impl VulkanPhysicalDeviceRecord {
@@ -270,6 +273,13 @@ pub enum VulkanCapabilityError {
/// Device name that failed validation.
device: String,
},
+ /// No compatible depth/stencil attachment format exists for the render request.
+ MissingDepthStencilFormat {
+ /// Device name that failed validation.
+ device: String,
+ /// Requested depth/stencil profile.
+ requested: DepthStencilSupport,
+ },
}
impl std::fmt::Display for VulkanCapabilityError {
@@ -301,6 +311,12 @@ impl std::fmt::Display for VulkanCapabilityError {
f,
"Vulkan device {device} surface does not support COLOR_ATTACHMENT usage"
),
+ Self::MissingDepthStencilFormat { device, requested } => write!(
+ f,
+ "Vulkan device {device} lacks a depth/stencil attachment format for {}-bit depth and {}-bit stencil",
+ requested.depth_bits,
+ requested.stencil_bits
+ ),
}
}
}
@@ -316,6 +332,20 @@ impl std::error::Error for VulkanCapabilityError {}
pub fn select_physical_device(
devices: &[VulkanPhysicalDeviceRecord],
) -> Result<VulkanCapabilityReport, VulkanCapabilityError> {
+ select_physical_device_for_request(devices, &RenderRequest::conservative())
+}
+
+/// Selects a Vulkan physical device for a specific Stage 0 render request.
+///
+/// # Errors
+///
+/// Returns [`VulkanCapabilityError`] when no candidate satisfies the minimum
+/// API version, queue, swapchain-extension, surface-format or depth/stencil
+/// requirements for the requested profile.
+pub fn select_physical_device_for_request(
+ devices: &[VulkanPhysicalDeviceRecord],
+ render_request: &RenderRequest,
+) -> Result<VulkanCapabilityReport, VulkanCapabilityError> {
if devices.is_empty() {
return Err(VulkanCapabilityError::NoPhysicalDevice);
}
@@ -324,7 +354,7 @@ pub fn select_physical_device(
let mut rejected_devices = Vec::new();
let mut last_error = None;
for device in devices {
- let report = match validate_device(device) {
+ let report = match validate_device_for_request(device, render_request) {
Ok(report) => report,
Err(err) => {
rejected_devices.push(rejected_device_report(device, &err));
@@ -611,8 +641,9 @@ fn select_image_count(capabilities: VulkanSwapchainSurfaceCapabilities) -> u32 {
}
}
-pub(crate) fn validate_device(
+pub(crate) fn validate_device_for_request(
device: &VulkanPhysicalDeviceRecord,
+ render_request: &RenderRequest,
) -> Result<VulkanCapabilityReport, VulkanCapabilityError> {
if device.api_version < MIN_VULKAN_API_VERSION {
return Err(VulkanCapabilityError::ApiVersionTooLow {
@@ -640,6 +671,12 @@ pub(crate) fn validate_device(
device: device.name.clone(),
});
}
+ if !supports_depth_stencil_request(device, render_request.depth) {
+ return Err(VulkanCapabilityError::MissingDepthStencilFormat {
+ device: device.name.clone(),
+ requested: render_request.depth,
+ });
+ }
let (graphics_queue_family, present_queue_family) = select_queue_families(device)?;
let portability_subset = device.supports_extension(KHR_PORTABILITY_SUBSET_EXTENSION);
@@ -684,6 +721,7 @@ const fn capability_error_code(error: &VulkanCapabilityError) -> &'static str {
VulkanCapabilityError::MissingColorAttachmentUsage { .. } => {
"missing_color_attachment_usage"
}
+ VulkanCapabilityError::MissingDepthStencilFormat { .. } => "missing_depth_stencil_format",
}
}
@@ -728,6 +766,36 @@ fn supports_color_attachment_usage(capabilities: VulkanSwapchainSurfaceCapabilit
capabilities.supported_usage_flags & vk::ImageUsageFlags::COLOR_ATTACHMENT.as_raw() != 0
}
+fn supports_depth_stencil_request(
+ device: &VulkanPhysicalDeviceRecord,
+ depth: DepthStencilSupport,
+) -> bool {
+ if depth.depth_bits == 0 && depth.stencil_bits == 0 {
+ return true;
+ }
+ required_depth_stencil_formats(depth).iter().any(|format| {
+ device
+ .supported_depth_stencil_formats
+ .contains(&format.as_raw())
+ })
+}
+
+fn required_depth_stencil_formats(depth: DepthStencilSupport) -> &'static [vk::Format] {
+ match (depth.depth_bits, depth.stencil_bits) {
+ (0, 0) => &[],
+ (16, 0) => &[vk::Format::D16_UNORM, vk::Format::D32_SFLOAT],
+ (24, 0) => &[vk::Format::X8_D24_UNORM_PACK32, vk::Format::D32_SFLOAT],
+ (32, 0) => &[vk::Format::D32_SFLOAT],
+ (16, 8) => &[vk::Format::D16_UNORM_S8_UINT, vk::Format::D24_UNORM_S8_UINT],
+ (24, 8) => &[
+ vk::Format::D24_UNORM_S8_UINT,
+ vk::Format::D32_SFLOAT_S8_UINT,
+ ],
+ (32, 8) => &[vk::Format::D32_SFLOAT_S8_UINT],
+ _ => &[],
+ }
+}
+
fn score_device(
device: &VulkanPhysicalDeviceRecord,
graphics_queue_family: u32,
diff --git a/apps/fparkan-vulkan-smoke/src/main.rs b/apps/fparkan-vulkan-smoke/src/main.rs
index f9c144c..320eb59 100644
--- a/apps/fparkan-vulkan-smoke/src/main.rs
+++ b/apps/fparkan-vulkan-smoke/src/main.rs
@@ -11,6 +11,7 @@
#![allow(clippy::print_stderr, clippy::print_stdout)]
//! Native Vulkan smoke runner entrypoint.
+use fparkan_platform::RenderRequest;
use fparkan_platform_winit::{window_native_handles, WinitWindowPlan};
use fparkan_render_vulkan::{
VulkanSmokeBootstrapProgress, VulkanSmokeFrameOutcome, VulkanSmokeRenderer,
@@ -524,6 +525,7 @@ impl ApplicationHandler for SmokeApp {
application_name: "fparkan-vulkan-smoke".to_string(),
native_handles,
drawable_extent: (size.width.max(1), size.height.max(1)),
+ render_request: RenderRequest::conservative(),
enable_validation: true,
bootstrap_progress: Some(Arc::clone(&self.progress.bootstrap)),
}) {