aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-06-25 05:16:12 +0300
committerValentin Popov <valentin@popov.link>2026-06-25 10:45:36 +0300
commitdcd5417af247aa07d7c61e9cbd9074cc97122274 (patch)
treefcf79ebf810f23ded5a9ac5e2586702101ebe910
parentb6b47ae6f612d365e77bbaf421b7c0927df12835 (diff)
downloadfparkan-dcd5417af247aa07d7c61e9cbd9074cc97122274.tar.xz
fparkan-dcd5417af247aa07d7c61e9cbd9074cc97122274.zip
refactor(vulkan-ffi): extract validation messenger module
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi.rs119
-rw-r--r--adapters/fparkan-render-vulkan/src/ffi/validation.rs125
-rw-r--r--xtask/src/main.rs1
3 files changed, 128 insertions, 117 deletions
diff --git a/adapters/fparkan-render-vulkan/src/ffi.rs b/adapters/fparkan-render-vulkan/src/ffi.rs
index 9be61eb..618b53b 100644
--- a/adapters/fparkan-render-vulkan/src/ffi.rs
+++ b/adapters/fparkan-render-vulkan/src/ffi.rs
@@ -29,6 +29,7 @@
mod instance;
mod surface;
+mod validation;
pub use self::instance::{
create_vulkan_instance_probe, plan_vulkan_instance, probe_vulkan_loader,
@@ -44,16 +45,14 @@ pub use self::surface::{
create_vulkan_surface_probe, plan_vulkan_surface, render_surface_plan_json, VulkanSurfaceError,
VulkanSurfacePlan, VulkanSurfaceProbe,
};
+use self::validation::{create_validation_messenger, VulkanValidationMessenger};
use crate::policy::*;
use crate::shader_manifest::{
triangle_shader_manifest, validate_shader_manifest, VulkanShaderManifestError,
};
use ash::{khr::swapchain, vk};
use fparkan_platform::NativeWindowHandles;
-use std::collections::BTreeSet;
use std::ffi::{CStr, CString};
-use std::sync::atomic::{AtomicU32, Ordering};
-use std::sync::Mutex;
/// Minimum Vulkan API version accepted by the Stage 0 backend.
pub const MIN_VULKAN_API_VERSION: u32 = vk::API_VERSION_1_1;
const KHR_PORTABILITY_ENUMERATION_EXTENSION: &str = "VK_KHR_portability_enumeration";
@@ -653,88 +652,6 @@ impl std::fmt::Display for VulkanSmokeRendererError {
impl std::error::Error for VulkanSmokeRendererError {}
-struct VulkanValidationShared {
- warning_count: AtomicU32,
- error_count: AtomicU32,
- vuids: Mutex<BTreeSet<String>>,
-}
-
-impl Default for VulkanValidationShared {
- fn default() -> Self {
- Self {
- warning_count: AtomicU32::new(0),
- error_count: AtomicU32::new(0),
- vuids: Mutex::new(BTreeSet::new()),
- }
- }
-}
-
-struct VulkanValidationMessenger {
- loader: ash::ext::debug_utils::Instance,
- messenger: vk::DebugUtilsMessengerEXT,
- shared: Box<VulkanValidationShared>,
-}
-
-impl VulkanValidationMessenger {
- fn report(&self) -> VulkanValidationReport {
- let vuids = self
- .shared
- .vuids
- .lock()
- .map(|values| values.iter().cloned().collect::<Vec<_>>())
- .unwrap_or_default();
- VulkanValidationReport {
- warning_count: self.shared.warning_count.load(Ordering::Relaxed),
- error_count: self.shared.error_count.load(Ordering::Relaxed),
- vuids,
- }
- }
-}
-
-impl Drop for VulkanValidationMessenger {
- fn drop(&mut self) {
- // SAFETY: The messenger belongs to this instance-level loader and is destroyed once.
- unsafe {
- self.loader
- .destroy_debug_utils_messenger(self.messenger, None);
- };
- }
-}
-
-unsafe extern "system" fn vulkan_validation_callback(
- message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
- _message_types: vk::DebugUtilsMessageTypeFlagsEXT,
- callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>,
- user_data: *mut std::ffi::c_void,
-) -> vk::Bool32 {
- // SAFETY: The debug messenger stores a stable pointer to `VulkanValidationShared` for the messenger lifetime.
- let Some(shared) = (unsafe { (user_data as *const VulkanValidationShared).as_ref() }) else {
- return vk::FALSE;
- };
- if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::ERROR) {
- shared.error_count.fetch_add(1, Ordering::Relaxed);
- } else if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::WARNING) {
- shared.warning_count.fetch_add(1, Ordering::Relaxed);
- }
- // SAFETY: Vulkan invokes the callback with either a null pointer or a valid callback-data payload.
- let Some(callback_data) = (unsafe { callback_data.as_ref() }) else {
- return vk::FALSE;
- };
- if let Some(vuid) = (!callback_data.p_message_id_name.is_null()).then(|| {
- // SAFETY: `p_message_id_name` is a Vulkan-owned NUL-terminated string for the callback duration.
- unsafe { CStr::from_ptr(callback_data.p_message_id_name) }
- .to_string_lossy()
- .into_owned()
- }) {
- if vuid.starts_with("VUID-") {
- if let Ok(mut vuids) = shared.vuids.lock() {
- vuids.insert(vuid);
- }
- }
- }
- vk::FALSE
-}
-
struct VulkanAllocatedBuffer {
buffer: vk::Buffer,
memory: vk::DeviceMemory,
@@ -1379,38 +1296,6 @@ impl Drop for VulkanSmokeRenderer {
}
}
-fn create_validation_messenger(
- instance: &VulkanInstanceProbe,
-) -> Result<VulkanValidationMessenger, VulkanSmokeRendererError> {
- let shared = Box::new(VulkanValidationShared::default());
- let loader = ash::ext::debug_utils::Instance::new(&instance.entry, &instance.instance);
- let create_info = vk::DebugUtilsMessengerCreateInfoEXT::default()
- .message_severity(
- vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
- | vk::DebugUtilsMessageSeverityFlagsEXT::ERROR,
- )
- .message_type(
- vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
- | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
- | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
- )
- .pfn_user_callback(Some(vulkan_validation_callback))
- .user_data((&raw const *shared).cast_mut().cast());
- let messenger =
- // SAFETY: The create info points at a stable boxed user-data allocation for the messenger lifetime.
- unsafe { loader.create_debug_utils_messenger(&create_info, None) }.map_err(|error| {
- VulkanSmokeRendererError::VulkanOperation {
- context: "vkCreateDebugUtilsMessengerEXT",
- result: error,
- }
- })?;
- Ok(VulkanValidationMessenger {
- loader,
- messenger,
- shared,
- })
-}
-
fn create_command_pool(
device: &VulkanLogicalDeviceProbe,
) -> Result<vk::CommandPool, VulkanSmokeRendererError> {
diff --git a/adapters/fparkan-render-vulkan/src/ffi/validation.rs b/adapters/fparkan-render-vulkan/src/ffi/validation.rs
new file mode 100644
index 0000000..2045373
--- /dev/null
+++ b/adapters/fparkan-render-vulkan/src/ffi/validation.rs
@@ -0,0 +1,125 @@
+#![allow(unsafe_code)]
+
+use ash::vk;
+use std::collections::BTreeSet;
+use std::ffi::CStr;
+use std::sync::{
+ atomic::{AtomicU32, Ordering},
+ Mutex,
+};
+
+use super::{VulkanInstanceProbe, VulkanSmokeRendererError, VulkanValidationReport};
+
+struct VulkanValidationShared {
+ warning_count: AtomicU32,
+ error_count: AtomicU32,
+ vuids: Mutex<BTreeSet<String>>,
+}
+
+impl Default for VulkanValidationShared {
+ fn default() -> Self {
+ Self {
+ warning_count: AtomicU32::new(0),
+ error_count: AtomicU32::new(0),
+ vuids: Mutex::new(BTreeSet::new()),
+ }
+ }
+}
+
+pub(super) struct VulkanValidationMessenger {
+ loader: ash::ext::debug_utils::Instance,
+ messenger: vk::DebugUtilsMessengerEXT,
+ shared: Box<VulkanValidationShared>,
+}
+
+impl VulkanValidationMessenger {
+ pub(super) fn report(&self) -> VulkanValidationReport {
+ let vuids = self
+ .shared
+ .vuids
+ .lock()
+ .map(|values| values.iter().cloned().collect::<Vec<_>>())
+ .unwrap_or_default();
+ VulkanValidationReport {
+ warning_count: self.shared.warning_count.load(Ordering::Relaxed),
+ error_count: self.shared.error_count.load(Ordering::Relaxed),
+ vuids,
+ }
+ }
+}
+
+impl Drop for VulkanValidationMessenger {
+ fn drop(&mut self) {
+ // SAFETY: The messenger belongs to this instance-level loader and is destroyed once.
+ unsafe {
+ self.loader
+ .destroy_debug_utils_messenger(self.messenger, None);
+ };
+ }
+}
+
+unsafe extern "system" fn vulkan_validation_callback(
+ message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
+ _message_types: vk::DebugUtilsMessageTypeFlagsEXT,
+ callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>,
+ user_data: *mut std::ffi::c_void,
+) -> vk::Bool32 {
+ // SAFETY: The debug messenger stores a stable pointer to `VulkanValidationShared` for the messenger lifetime.
+ let Some(shared) = (unsafe { (user_data as *const VulkanValidationShared).as_ref() }) else {
+ return vk::FALSE;
+ };
+ if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::ERROR) {
+ shared.error_count.fetch_add(1, Ordering::Relaxed);
+ } else if message_severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::WARNING) {
+ shared.warning_count.fetch_add(1, Ordering::Relaxed);
+ }
+ // SAFETY: Vulkan invokes the callback with either a null pointer or a valid callback-data payload.
+ let Some(callback_data) = (unsafe { callback_data.as_ref() }) else {
+ return vk::FALSE;
+ };
+ if let Some(vuid) = (!callback_data.p_message_id_name.is_null()).then(|| {
+ // SAFETY: `p_message_id_name` is a Vulkan-owned NUL-terminated string for the callback duration.
+ unsafe { CStr::from_ptr(callback_data.p_message_id_name) }
+ .to_string_lossy()
+ .into_owned()
+ }) {
+ if vuid.starts_with("VUID-") {
+ if let Ok(mut vuids) = shared.vuids.lock() {
+ vuids.insert(vuid);
+ }
+ }
+ }
+ vk::FALSE
+}
+
+pub(super) fn create_validation_messenger(
+ instance: &VulkanInstanceProbe,
+) -> Result<VulkanValidationMessenger, VulkanSmokeRendererError> {
+ let shared = Box::new(VulkanValidationShared::default());
+ let loader = ash::ext::debug_utils::Instance::new(&instance.entry, &instance.instance);
+ let create_info = vk::DebugUtilsMessengerCreateInfoEXT::default()
+ .message_severity(
+ vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
+ | vk::DebugUtilsMessageSeverityFlagsEXT::ERROR,
+ )
+ .message_type(
+ vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
+ | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
+ | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
+ )
+ .pfn_user_callback(Some(vulkan_validation_callback))
+ .user_data((&raw const *shared).cast_mut().cast());
+ let messenger =
+ // SAFETY: The create info points at a stable boxed user-data allocation for the messenger lifetime.
+ unsafe { loader.create_debug_utils_messenger(&create_info, None) }.map_err(|error| {
+ VulkanSmokeRendererError::VulkanOperation {
+ context: "vkCreateDebugUtilsMessengerEXT",
+ result: error,
+ }
+ })?;
+ Ok(VulkanValidationMessenger {
+ loader,
+ messenger,
+ shared,
+ })
+}
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index bf79ec7..fd98c82 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -1243,6 +1243,7 @@ const AUDITED_UNSAFE_SOURCE_FILES: &[&str] = &[
"adapters/fparkan-render-vulkan/src/ffi.rs",
"adapters/fparkan-render-vulkan/src/ffi/instance.rs",
"adapters/fparkan-render-vulkan/src/ffi/surface.rs",
+ "adapters/fparkan-render-vulkan/src/ffi/validation.rs",
];
fn is_audited_unsafe_source(path: &Path) -> bool {