use ash::vk;
use concinnity_core::components;
use concinnity_core::render::backend_init::{self, PostSettings};
use concinnity_core::render::error::RenderResult;
use concinnity_core::render::hdr_output;
use concinnity_core::render::pass_timing;
use std::ffi::{CStr, CString, c_char};
use crate::vulkan::context::{SwapchainState, VkHardware};
use crate::vulkan::device::*;
use crate::vulkan::swapchain::*;
pub(super) struct HardwareRequest<'a> {
pub(super) window: &'a components::Window,
pub(super) validation: bool,
pub(super) frames: usize,
pub(super) vsync: bool,
pub(super) post: &'a PostSettings,
}
impl HardwareRequest<'_> {
fn swapchain_config(&self) -> backend_init::SwapchainConfig {
backend_init::SwapchainConfig {
frames_in_flight: self.frames,
hdr_display: self.post.hdr_display,
hdr_pq: self.post.hdr_pq,
}
}
}
pub(super) fn inherit_hardware(
(mut hw, mut swapchain): (VkHardware, SwapchainState),
req: &HardwareRequest<'_>,
) -> RenderResult<(VkHardware, SwapchainState)> {
swapchain.image_views =
create_swapchain_image_views(&hw.device, &swapchain.images, swapchain.format)?;
hw.vsync = req.vsync;
hw.swapchain_config = req.swapchain_config();
Ok((hw, swapchain))
}
pub(super) fn acquire_hardware(
req: &HardwareRequest<'_>,
) -> RenderResult<(VkHardware, SwapchainState)> {
let (title, width, height, title_bar) = (
req.window.title.as_str(),
req.window.width,
req.window.height,
req.window.title_bar,
);
let HardwareRequest {
validation,
frames,
vsync,
post,
..
} = *req;
let hdr_display = post.hdr_display;
let (temporal_upscaling, upscale_backend) = (post.temporal_upscaling, post.upscale_backend);
let mut window = crate::vulkan::window::PlatformWindow::new(
title,
width,
height,
&components::WindowMode::Windowed,
true,
title_bar,
)?;
let entry = crate::vulkan::loader::load_entry()?;
let upscale_sdk = crate::vulkan::post::UpscaleSdk::prepare(temporal_upscaling, upscale_backend);
let app_name = CString::new(title).unwrap_or_default();
let engine_name = CString::new("Concinnity").unwrap();
let loader_version = unsafe { entry.try_enumerate_instance_version() }
.ok()
.flatten()
.unwrap_or(vk::API_VERSION_1_2);
let api_version = vk::API_VERSION_1_2
.max(upscale_sdk.min_api_version())
.min(loader_version);
let app_info = vk::ApplicationInfo::default()
.application_name(&app_name)
.application_version(vk::make_api_version(0, 0, 1, 0))
.engine_name(&engine_name)
.engine_version(vk::make_api_version(0, 0, 1, 0))
.api_version(api_version);
let instance_ext_cstrings: Vec<CString> = window
.required_instance_extensions()
.iter()
.map(|s| CString::new(s.as_str()).unwrap())
.collect();
let mut ext_names_raw: Vec<*const c_char> =
instance_ext_cstrings.iter().map(|c| c.as_ptr()).collect();
let debug_ext = ash::ext::debug_utils::NAME.as_ptr();
if validation {
ext_names_raw.push(debug_ext);
}
let available_ext_props =
unsafe { entry.enumerate_instance_extension_properties(None) }
.unwrap_or_default();
let optional_exts = crate::vulkan::instance_exts::select(
&crate::vulkan::instance_exts::names_of(&available_ext_props),
hdr_display,
);
let swapchain_colorspace_ext_available = optional_exts.swapchain_colorspace;
if hdr_display && !swapchain_colorspace_ext_available {
tracing::warn!(
"HDR display requested but VK_EXT_swapchain_colorspace is not exposed by the \
Vulkan loader; falling back to SDR (BGRA8 sRGB) output"
);
}
ext_names_raw.extend(optional_exts.names().iter().map(|n| n.as_ptr()));
for ptr in upscale_sdk.instance_extension_ptrs() {
ext_names_raw.push(ptr);
}
let layer_names_raw: Vec<*const c_char> = if validation {
let layer = CString::new("VK_LAYER_KHRONOS_validation").unwrap();
vec![layer.into_raw().cast_const()]
} else {
vec![]
};
let instance_info = vk::InstanceCreateInfo::default()
.application_info(&app_info)
.flags(optional_exts.flags())
.enabled_extension_names(&ext_names_raw)
.enabled_layer_names(&layer_names_raw);
let instance = unsafe { entry.create_instance(&instance_info, None) }
.map_err(|e| crate::vulkan::error::map_vk_result(e, "create instance"))?;
if validation {
tracing::info!("vulkan validation layer: enabled");
}
let debug_filter: Option<Box<std::sync::atomic::AtomicU32>> =
validation.then(|| Box::new(std::sync::atomic::AtomicU32::new(0)));
let (debug_utils, debug_messenger) = if validation {
let du = ash::ext::debug_utils::Instance::new(&entry, &instance);
let user_data = debug_filter
.as_ref()
.map(|b| &**b as *const std::sync::atomic::AtomicU32 as *mut std::ffi::c_void)
.unwrap_or(std::ptr::null_mut());
let info = vk::DebugUtilsMessengerCreateInfoEXT::default()
.message_severity(
vk::DebugUtilsMessageSeverityFlagsEXT::ERROR
| vk::DebugUtilsMessageSeverityFlagsEXT::WARNING,
)
.message_type(
vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
| vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
| vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
)
.pfn_user_callback(Some(debug_callback))
.user_data(user_data);
let messenger = unsafe { du.create_debug_utils_messenger(&info, None) }
.map_err(|e| crate::vulkan::error::map_vk_result(e, "debug messenger"))?;
(Some(du), Some(messenger))
} else {
(None, None)
};
let surface_loader = ash::khr::surface::Instance::new(&entry, &instance);
let surface = window.create_surface(&entry, &instance)?;
let (physical_device, graphics_family, present_family) =
pick_physical_device(&instance, &surface_loader, surface)?;
let crate::vulkan::device::LogicalDevice {
device,
memory_budget: memory_budget_supported,
rt_capable,
caps,
update_after_bind,
} = create_logical_device(
&instance,
physical_device,
graphics_family,
present_family,
&upscale_sdk,
)?;
let device = crate::vulkan::owned::VkDevice::new(
entry.clone(),
instance.clone(),
device,
frames,
crate::vulkan::owned::DebugMessenger {
utils: debug_utils,
messenger: debug_messenger,
filter: debug_filter,
},
caps,
);
let graphics_queue = unsafe { device.get_device_queue(graphics_family, 0) };
let present_queue = unsafe { device.get_device_queue(present_family, 0) };
let device_props =
unsafe { instance.get_physical_device_properties(physical_device) };
crate::vulkan::pipeline_cache::install(&device, &device_props);
let queue_family_props =
unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
let timestamp_period = device_props.limits.timestamp_period;
let timestamp_valid_bits = queue_family_props
.get(graphics_family as usize)
.map(|f| f.timestamp_valid_bits)
.unwrap_or(0);
let timestamps_supported = timestamp_period > 0.0 && timestamp_valid_bits > 0;
let timestamp_query_pool = if timestamps_supported {
let info = vk::QueryPoolCreateInfo::default()
.query_type(vk::QueryType::TIMESTAMP)
.query_count((pass_timing::SLOTS_PER_FRAME * frames) as u32);
match unsafe { device.create_query_pool(&info, None) } {
Ok(p) => Some(p),
Err(e) => {
tracing::warn!("timestamp query pool create failed: {e}");
None
}
}
} else {
None
};
let memory_props =
unsafe { instance.get_physical_device_memory_properties(physical_device) };
let device_local_heaps: Vec<u32> = if memory_budget_supported {
(0..memory_props.memory_heap_count as usize)
.filter(|i| {
memory_props.memory_heaps[*i]
.flags
.contains(vk::MemoryHeapFlags::DEVICE_LOCAL)
})
.map(|i| i as u32)
.collect()
} else {
Vec::new()
};
let hdr_mode = resolve_hdr_mode(
&surface_loader,
physical_device,
surface,
swapchain_colorspace_ext_available,
post,
);
let swapchain_loader = ash::khr::swapchain::Device::new(&instance, &device);
let (swapchain, swapchain_images, swapchain_format, swapchain_extent) = create_swapchain_inner(
&SwapchainSurface {
instance: &instance,
device: &device,
pd: physical_device,
surface_loader: &surface_loader,
surface,
swapchain_loader: &swapchain_loader,
},
SwapchainQueueFamilies {
graphics_family,
present_family,
},
SwapchainConfig {
width,
height,
old_swapchain: vk::SwapchainKHR::null(),
hdr_mode,
vsync,
},
)?;
let swapchain_image_views =
create_swapchain_image_views(&device, &swapchain_images, swapchain_format)?;
let alloc =
crate::vulkan::allocator::DeviceAllocator::new(&instance, physical_device, &device, frames);
Ok((
VkHardware {
instance,
device,
physical_device,
surface,
surface_loader,
graphics_queue,
present_queue,
graphics_family,
alloc,
timestamp_query_pool,
timestamp_period_ns: timestamp_period,
device_local_heaps,
memory_budget_supported,
rt_capable,
update_after_bind,
hdr_mode,
vsync,
swapchain_config: req.swapchain_config(),
window: Some(window),
_entry: entry,
},
SwapchainState {
loader: swapchain_loader,
handle: swapchain,
images: swapchain_images,
image_views: swapchain_image_views,
format: swapchain_format,
extent: swapchain_extent,
last_present_index: None,
},
))
}
fn resolve_hdr_mode(
surface_loader: &ash::khr::surface::Instance,
physical_device: vk::PhysicalDevice,
surface: vk::SurfaceKHR,
swapchain_colorspace_ext_available: bool,
post: &PostSettings,
) -> hdr_output::HdrOutputMode {
let (hdr_display, hdr_pq) = (post.hdr_display, post.hdr_pq);
let surface_formats =
unsafe { surface_loader.get_physical_device_surface_formats(physical_device, surface) }
.unwrap_or_default();
let advertises = |fmt: vk::Format, cs: vk::ColorSpaceKHR| {
surface_formats
.iter()
.any(|f| f.format == fmt && f.color_space == cs)
};
let scrgb_advertises = swapchain_colorspace_ext_available
&& advertises(
vk::Format::R16G16B16A16_SFLOAT,
vk::ColorSpaceKHR::EXTENDED_SRGB_LINEAR_EXT,
);
let pq_advertises = swapchain_colorspace_ext_available
&& (advertises(
vk::Format::R16G16B16A16_SFLOAT,
vk::ColorSpaceKHR::HDR10_ST2084_EXT,
) || advertises(
vk::Format::A2B10G10R10_UNORM_PACK32,
vk::ColorSpaceKHR::HDR10_ST2084_EXT,
));
let pq_capable = hdr_pq && pq_advertises;
if hdr_display && hdr_pq && !pq_advertises {
tracing::warn!(
"HDR display + hdr_pq:true requested but no surface format advertises HDR10 PQ \
(RGBA16F / A2B10G10R10_UNORM_PACK32 + HDR10_ST2084_EXT); falling back to \
scRGB-linear extended-range output"
);
}
let max_edr = if scrgb_advertises || pq_advertises {
2.0
} else {
1.0
};
let hdr_mode = hdr_output::HdrOutputMode::resolve(hdr_display, pq_capable, max_edr);
if hdr_display && !hdr_mode.is_hdr() {
tracing::warn!(
"HDR display requested but no surface format advertises an HDR color space \
(scRGB linear or HDR10 PQ): falling back to SDR (BGRA8 sRGB) output"
);
} else if hdr_mode.pq_flag() > 0.5 {
tracing::info!("HDR display output enabled: HDR10 PQ swapchain (SMPTE ST 2084)");
} else if hdr_mode.is_hdr() {
tracing::info!(
"HDR display output enabled: scRGB-linear swapchain (RGBA16F + \
EXTENDED_SRGB_LINEAR_EXT)"
);
}
hdr_mode
}
pub(in crate::vulkan) const DLSS_FIRST_FRAME_LAYOUT_SUPPRESS: u32 = 2;
fn drop_benign_dlss_layout_error(message_id: &[u8], budget: &std::sync::atomic::AtomicU32) -> bool {
if message_id != b"VUID-vkCmdDraw-None-09600" {
return false;
}
budget
.fetch_update(
std::sync::atomic::Ordering::Relaxed,
std::sync::atomic::Ordering::Relaxed,
|n| (n > 0).then(|| n - 1),
)
.is_ok()
}
unsafe extern "system" fn debug_callback(
severity: vk::DebugUtilsMessageSeverityFlagsEXT,
_msg_type: vk::DebugUtilsMessageTypeFlagsEXT,
data: *const vk::DebugUtilsMessengerCallbackDataEXT,
user: *mut std::ffi::c_void,
) -> vk::Bool32 {
if data.is_null() {
return vk::FALSE;
}
let data = unsafe { &*data };
if !user.is_null() && !data.p_message_id_name.is_null() {
let vuid = unsafe { CStr::from_ptr(data.p_message_id_name) };
let budget = unsafe { &*(user as *const std::sync::atomic::AtomicU32) };
if drop_benign_dlss_layout_error(vuid.to_bytes(), budget) {
return vk::FALSE;
}
}
let msg = unsafe { CStr::from_ptr(data.p_message) }.to_string_lossy();
if severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::ERROR) {
tracing::error!("[Vulkan] {}", msg);
} else {
tracing::warn!("[Vulkan] {}", msg);
}
vk::FALSE
}
#[cfg(test)]
mod tests {
use super::{DLSS_FIRST_FRAME_LAYOUT_SUPPRESS, drop_benign_dlss_layout_error};
use std::sync::atomic::{AtomicU32, Ordering};
const LAYOUT_VUID: &[u8] = b"VUID-vkCmdDraw-None-09600";
#[test]
fn drops_exactly_the_budgeted_layout_errors_then_logs() {
let budget = AtomicU32::new(DLSS_FIRST_FRAME_LAYOUT_SUPPRESS);
for _ in 0..DLSS_FIRST_FRAME_LAYOUT_SUPPRESS {
assert!(drop_benign_dlss_layout_error(LAYOUT_VUID, &budget));
}
assert!(!drop_benign_dlss_layout_error(LAYOUT_VUID, &budget));
assert_eq!(budget.load(Ordering::Relaxed), 0);
}
#[test]
fn never_drops_other_vuids_or_touches_budget() {
let budget = AtomicU32::new(DLSS_FIRST_FRAME_LAYOUT_SUPPRESS);
assert!(!drop_benign_dlss_layout_error(
b"VUID-vkCmdDraw-None-02699",
&budget
));
assert!(!drop_benign_dlss_layout_error(b"", &budget));
assert_eq!(
budget.load(Ordering::Relaxed),
DLSS_FIRST_FRAME_LAYOUT_SUPPRESS
);
}
#[test]
fn drops_nothing_when_budget_is_zero() {
let budget = AtomicU32::new(0);
assert!(!drop_benign_dlss_layout_error(LAYOUT_VUID, &budget));
}
}