vidya/crates/renderer/src/instance.rs
2026-04-04 00:28:43 +02:00

513 lines
17 KiB
Rust

use std::{
cmp::Ordering,
ffi::{CStr, CString},
mem::MaybeUninit,
ops::Deref,
sync::Arc,
};
use ash::{Entry, ext, khr, vk};
use raw_window_handle::RawDisplayHandle;
use crate::{
Error, PhysicalDeviceFeatures, PhysicalDeviceInfo, SurfaceCapabilities,
device::{Extension, get_extensions, get_layers},
get_physical_device_features, get_physical_device_properties, make_extension,
swapchain::Surface,
};
pub struct DebugUtilsCreateInfo {
pub severity: vk::DebugUtilsMessageSeverityFlagsEXT,
pub message_type: vk::DebugUtilsMessageTypeFlagsEXT,
}
pub(crate) struct InstanceExtensions {
pub(crate) get_surface_capabilities2: Option<khr::get_surface_capabilities2::Instance>,
pub(crate) surface_maintenance1: Option<()>,
}
pub struct InstanceInner {
pub raw: ash::Instance,
pub(crate) extensions: InstanceExtensions,
pub entry: Entry,
pub(crate) _debug_utils: DebugUtils,
}
#[derive(Clone)]
pub struct Instance {
pub(crate) inner: Arc<InstanceInner>,
}
impl Deref for Instance {
type Target = InstanceInner;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl core::fmt::Debug for Instance {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Instance")
.field("raw", &self.inner.raw.handle())
.finish_non_exhaustive()
}
}
#[derive(Debug)]
pub struct InstanceDesc<'a> {
pub app_name: Option<&'a str>,
pub app_version: u32,
pub instance_extensions: &'a [Extension<'a>],
pub layer_settings: &'a [vk::LayerSettingEXT<'a>],
pub layers: &'a [&'a CStr],
pub display_handle: Option<RawDisplayHandle>,
}
pub const VALIDATION_LAYER: &CStr = c"VK_LAYER_KHRONOS_validation";
#[cfg(not(debug_assertions))]
const DEBUG_LAYER_SETTINGS: &[vk::LayerSettingEXT] = &[];
#[cfg(debug_assertions)]
const DEBUG_LAYER_SETTINGS: &[vk::LayerSettingEXT] = &[
vk::LayerSettingEXT {
p_layer_name: VALIDATION_LAYER.as_ptr(),
p_setting_name: c"validate_best_practices".as_ptr(),
value_count: 1,
p_values: &[1u8; 1] as *const u8 as _,
ty: vk::LayerSettingTypeEXT::BOOL32,
_marker: core::marker::PhantomData,
},
vk::LayerSettingEXT {
p_layer_name: VALIDATION_LAYER.as_ptr(),
p_setting_name: c"validate_best_practices_amd".as_ptr(),
value_count: 1,
p_values: &[1u8; 1] as *const u8 as _,
ty: vk::LayerSettingTypeEXT::BOOL32,
_marker: core::marker::PhantomData,
},
vk::LayerSettingEXT {
p_layer_name: VALIDATION_LAYER.as_ptr(),
p_setting_name: c"validate_sync".as_ptr(),
value_count: 1,
p_values: &[1u8; 1] as *const u8 as _,
ty: vk::LayerSettingTypeEXT::BOOL32,
_marker: core::marker::PhantomData,
},
];
impl Default for InstanceDesc<'_> {
fn default() -> Self {
Self {
app_name: None,
app_version: vk::make_api_version(0, 1, 0, 0),
instance_extensions: &[make_extension!(khr::surface)],
layer_settings: DEBUG_LAYER_SETTINGS,
layers: &[VALIDATION_LAYER],
display_handle: None,
}
}
}
impl Instance {
pub fn new<'a>(desc: &InstanceDesc<'a>) -> crate::Result<Self> {
let entry = unsafe { ash::Entry::load()? };
let app_name = desc
.app_name
.and_then(|name| CString::new(name).ok())
.unwrap_or(c"ShooterGame".to_owned());
let version =
unsafe { entry.try_enumerate_instance_version()? }.unwrap_or(vk::API_VERSION_1_0);
let app_info = vk::ApplicationInfo::default()
.api_version(if version < vk::API_VERSION_1_1 {
vk::API_VERSION_1_0
} else {
// ash doesn't support 1.4 yet
vk::API_VERSION_1_3
})
.application_name(&app_name)
.application_version(desc.app_version)
.engine_name(c"Bevy Engine")
.engine_version(vk::make_api_version(0, 0, 1, 0));
let mut validation_info =
vk::LayerSettingsCreateInfoEXT::default().settings(desc.layer_settings);
let layers = match get_layers(&entry, desc.layers.to_vec()) {
Ok(layers) => layers,
Err((supported, unsupported)) => {
tracing::error!(
"Some requested layers were not supported by instance:\nSupported: {:?}\nUnsupported: {:?}",
supported,
unsupported
);
supported
}
};
let mut requested_extensions = desc.instance_extensions.to_vec();
requested_extensions.push(make_extension!(ext::debug_utils as 1));
#[cfg(debug_assertions)]
requested_extensions.push(make_extension!(ext::layer_settings));
// These are wanted for surface capabilities querying, but not strictly
// required, and are only supported on newer devices, especially on
// NVIDIA cards.
requested_extensions.push(make_extension!(khr::get_surface_capabilities2));
requested_extensions.push(make_extension!(ext::surface_maintenance1));
let (extensions, unsupported_extensions) =
get_extensions(&entry, &layers, requested_extensions, desc.display_handle)?;
if !unsupported_extensions.is_empty() {
tracing::error!(
"extensions were requested but not supported by instance: {:?}",
unsupported_extensions
);
}
let layers = layers
.iter()
.map(|layer| layer.as_ptr())
.collect::<Vec<_>>();
let extension_names = extensions
.iter()
.map(|ext| ext.name.as_ptr())
.collect::<Vec<_>>();
let create_info = vk::InstanceCreateInfo::default()
.application_info(&app_info)
.enabled_extension_names(&extension_names)
.enabled_layer_names(&layers)
.push_next(&mut validation_info);
tracing::debug!(
"Creating instance:\napp_info: {app_info:#?}\ncreate_info: {create_info:#?}"
);
let instance = unsafe { entry.create_instance(&create_info, None)? };
let debug_utils = {
let debug_info = vk::DebugUtilsMessengerCreateInfoEXT::default()
.message_severity(
vk::DebugUtilsMessageSeverityFlagsEXT::ERROR
| vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
| vk::DebugUtilsMessageSeverityFlagsEXT::INFO,
)
.message_type(
vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
| vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
| vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
)
.pfn_user_callback(Some(crate::debug::debug_callback));
let instance = ext::debug_utils::Instance::new(&entry, &instance);
let messenger = unsafe { instance.create_debug_utils_messenger(&debug_info, None)? };
crate::DebugUtils {
instance,
messenger,
}
};
let extensions = InstanceExtensions {
get_surface_capabilities2: extensions
.contains(&make_extension!(khr::get_surface_capabilities2))
.then(|| khr::get_surface_capabilities2::Instance::new(&entry, &instance)),
surface_maintenance1: extensions
.contains(&make_extension!(ext::surface_maintenance1))
.then_some(()),
};
let instance = Arc::new(InstanceInner {
raw: instance,
extensions,
_debug_utils: debug_utils,
entry,
});
Ok(Self { inner: instance })
}
fn choose_adapter(
&self,
mut discriminator: impl FnMut(&PhysicalDeviceInfo, &PhysicalDeviceInfo) -> Ordering,
) -> crate::Result<PhysicalDeviceInfo> {
let pdevs = unsafe { self.inner.raw.enumerate_physical_devices()? };
let mut pdevs = pdevs
.into_iter()
.map(|pdev| -> crate::Result<PhysicalDeviceInfo> { self.expose_adapter(pdev) })
.filter_map(Result::ok)
.collect::<Vec<_>>();
pdevs.sort_unstable_by(&mut discriminator);
pdevs.pop().ok_or(Error::NoAdapter)
}
/// Queries the surface capabilities of the given physical device and
/// surface, including supported formats and present modes.
/// If a present mode is provided, and the
/// `VK_KHR_get_surface_capabilities2` and `VK_EXT_surface_maintenance1`
/// extensions are available, it will also query present mode compatibility
/// information to filter the present modes based on the provided initial
/// mode.
pub(crate) fn get_adapter_surface_capabilities(
&self,
pdev: vk::PhysicalDevice,
surface: &Surface,
present_mode: Option<vk::PresentModeKHR>,
) -> crate::Result<SurfaceCapabilities> {
let formats = unsafe {
surface
.functor
.get_physical_device_surface_formats(pdev, surface.raw)?
};
let present_modes = unsafe {
surface
.functor
.get_physical_device_surface_present_modes(pdev, surface.raw)?
};
let generic_caps = unsafe {
surface
.functor
.get_physical_device_surface_capabilities(pdev, surface.raw)?
};
let (capabilities, present_modes) = if let Some(ref functor) =
self.extensions.get_surface_capabilities2
&& self.extensions.surface_maintenance1.is_some()
{
let mut capabilities = vk::SurfaceCapabilities2KHR::default();
let mut surface_info =
vk::PhysicalDeviceSurfaceInfo2KHR::default().surface(surface.raw());
let present_mode = present_mode.unwrap_or(present_modes[0]);
let mut surface_present_mode =
vk::SurfacePresentModeEXT::default().present_mode(present_mode);
surface_info = surface_info.push_next(&mut surface_present_mode);
let mut present_modes = MaybeUninit::<[vk::PresentModeKHR; 8]>::uninit();
let mut present_mode_info = vk::SurfacePresentModeCompatibilityEXT::default()
.present_modes(unsafe { present_modes.assume_init_mut() });
capabilities = capabilities.push_next(&mut present_mode_info);
unsafe {
functor.get_physical_device_surface_capabilities2(
pdev,
&surface_info,
&mut capabilities,
)?;
}
(capabilities.surface_capabilities, unsafe {
let num_present_modes = present_mode_info.present_mode_count as usize;
present_modes.assume_init()[..num_present_modes].to_vec()
})
} else {
(generic_caps, present_modes)
};
// surface.functor
// .get_physical_device_surface_capabilities2(pdev, surface.raw, present_mode)?;
Ok(SurfaceCapabilities {
capabilities,
min_image_count: generic_caps.min_image_count,
formats,
present_modes,
})
}
pub(crate) fn expose_adapter(
&self,
pdev: vk::PhysicalDevice,
) -> crate::Result<PhysicalDeviceInfo> {
let properties = get_physical_device_properties(&self.inner, pdev)?;
let features = get_physical_device_features(&self.inner, pdev, &properties);
Ok(PhysicalDeviceInfo {
pdev,
properties,
features,
})
}
pub(crate) fn choose_adapter_default(
&self,
surface: Option<&Surface>,
required_extensions: &[Extension],
required_features: Option<&PhysicalDeviceFeatures>,
) -> crate::Result<PhysicalDeviceInfo> {
self.choose_adapter(|a, b| {
// Extensions: we definitely need swapchain.
match a
.properties
.supports_extension(make_extension!(khr::swapchain))
.cmp(
&b.properties
.supports_extension(make_extension!(khr::swapchain)),
) {
Ordering::Equal => {}
other => return other,
}
for ext in required_extensions {
match a.properties.supports_extension(*ext) {
true => {}
false => return Ordering::Less,
}
}
// Check surface compatibility
// TODO
_ = surface;
if let Some(ref required_features) = required_features {
if b.features.superset_of(&required_features)
&& !a.features.superset_of(&required_features)
{
return Ordering::Less;
}
}
// check specific features that we need
match a
.features
.core13
.synchronization2
.cmp(&b.features.core13.synchronization2)
{
Ordering::Equal => {}
other => return other,
}
match a
.features
.core13
.dynamic_rendering
.cmp(&b.features.core13.dynamic_rendering)
{
Ordering::Equal => {}
other => return other,
}
match a
.features
.core13
.maintenance4
.cmp(&b.features.core13.maintenance4)
{
Ordering::Equal => {}
other => return other,
}
// Prefer discrete GPUs
let a_discrete = a.properties.core.device_type == vk::PhysicalDeviceType::DISCRETE_GPU;
let b_discrete = b.properties.core.device_type == vk::PhysicalDeviceType::DISCRETE_GPU;
match (a_discrete, b_discrete) {
(true, false) => return Ordering::Greater,
(false, true) => return Ordering::Less,
_ => {}
}
// Then prefer newer APIs
match a
.properties
.core
.api_version
.cmp(&b.properties.core.api_version)
{
Ordering::Equal => {}
other => return other,
}
// Prefer larger memory
match a
.properties
.get_device_local_memory_count()
.cmp(&b.properties.get_device_local_memory_count())
{
Ordering::Equal => {}
other => return other,
}
// Prefer larger texture size
match a
.properties
.core
.limits
.max_image_dimension2_d
.cmp(&b.properties.core.limits.max_image_dimension2_d)
{
Ordering::Equal => {}
other => return other,
}
Ordering::Equal
})
}
/// So... basically this probably doesn't matter because the graphics queue will always be a present queue as well...
pub(crate) fn query_presentation_support(
&self,
pdev: vk::PhysicalDevice,
queue_family: u32,
display_handle: RawDisplayHandle,
) -> bool {
unsafe {
match display_handle {
RawDisplayHandle::Xlib(display) => {
let surface =
ash::khr::xlib_surface::Instance::new(&self.inner.entry, &self.inner.raw);
surface.get_physical_device_xlib_presentation_support(
pdev,
queue_family,
display.display.unwrap().as_ptr() as _,
display.screen as _,
)
}
RawDisplayHandle::Xcb(_xcb_display_handle) => todo!("xcb"),
RawDisplayHandle::Wayland(wayland_display_handle) => {
let surface = ash::khr::wayland_surface::Instance::new(
&self.inner.entry,
&self.inner.raw,
);
surface.get_physical_device_wayland_presentation_support(
pdev,
queue_family,
wayland_display_handle.display.cast().as_mut(),
)
}
RawDisplayHandle::Drm(_) => {
// idk ?
true
}
RawDisplayHandle::Windows(_) => {
ash::khr::win32_surface::Instance::new(&self.inner.entry, &self.inner.raw)
.get_physical_device_win32_presentation_support(pdev, queue_family)
}
_ => panic!("unsupported platform"),
}
}
}
}
pub(crate) struct DebugUtils {
pub instance: ext::debug_utils::Instance,
pub messenger: vk::DebugUtilsMessengerEXT,
}
impl Drop for DebugUtils {
fn drop(&mut self) {
unsafe {
self.instance
.destroy_debug_utils_messenger(self.messenger, None);
}
}
}