use crate::sugarloaf::{Colorspace, SugarloafWindow, SugarloafWindowSize};
use ash::khr;
use ash::vk;
use ash::{Device, Entry, Instance};
use raw_window_handle::{
HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle,
};
use std::ffi::{c_char, CStr};
use std::sync::Arc;
pub struct VkShared {
pub raw: Device,
pub instance: Instance,
pub physical_device: vk::PhysicalDevice,
_entry: Entry,
}
unsafe impl Send for VkShared {}
unsafe impl Sync for VkShared {}
impl Drop for VkShared {
fn drop(&mut self) {
unsafe {
let _ = self.raw.device_wait_idle();
self.raw.destroy_device(None);
self.instance.destroy_instance(None);
}
}
}
impl std::ops::Deref for VkShared {
type Target = Device;
#[inline]
fn deref(&self) -> &Device {
&self.raw
}
}
pub const FRAMES_IN_FLIGHT: usize = 3;
struct FrameSync {
image_available: vk::Semaphore,
render_finished: vk::Semaphore,
in_flight: vk::Fence,
cmd_pool: vk::CommandPool,
cmd_buffer: vk::CommandBuffer,
}
pub struct VulkanContext {
pub size: SugarloafWindowSize,
pub scale: f32,
pub supports_f16: bool,
pub colorspace: Colorspace,
pub needs_recreate: bool,
frame_index: usize,
frames: [FrameSync; FRAMES_IN_FLIGHT],
swapchain_extent: vk::Extent2D,
swapchain_color_space: vk::ColorSpaceKHR,
swapchain_format: vk::Format,
swapchain_images: Vec<vk::Image>,
swapchain_views: Vec<vk::ImageView>,
swapchain: vk::SwapchainKHR,
swapchain_loader: khr::swapchain::Device,
queue: vk::Queue,
#[allow(dead_code)]
queue_family_index: u32,
shared: Arc<VkShared>,
pipeline_cache: vk::PipelineCache,
surface: vk::SurfaceKHR,
surface_loader: khr::surface::Instance,
_debug_messenger: Option<DebugMessenger>,
}
struct DebugMessenger {
loader: ash::ext::debug_utils::Instance,
handle: vk::DebugUtilsMessengerEXT,
}
impl Drop for DebugMessenger {
fn drop(&mut self) {
unsafe {
self.loader.destroy_debug_utils_messenger(self.handle, None);
}
}
}
pub struct VulkanFrame {
pub image_index: u32,
pub image: vk::Image,
pub image_view: vk::ImageView,
pub cmd_buffer: vk::CommandBuffer,
pub extent: vk::Extent2D,
pub format: vk::Format,
pub slot: usize,
}
impl VulkanContext {
pub fn new(sugarloaf_window: SugarloafWindow) -> Self {
let size = sugarloaf_window.size;
let scale = sugarloaf_window.scale;
let entry =
unsafe { Entry::load() }.expect("failed to load Vulkan loader (libvulkan)");
let validation_requested = validation_requested();
let instance = create_instance(&entry, &sugarloaf_window, validation_requested);
let _debug_messenger = if validation_requested {
create_debug_messenger(&entry, &instance)
} else {
None
};
let surface_loader = khr::surface::Instance::new(&entry, &instance);
let surface = create_surface(&entry, &instance, &sugarloaf_window);
let (physical_device, queue_family_index) =
pick_physical_device(&instance, &surface_loader, surface);
let device = create_device(&instance, physical_device, queue_family_index);
let queue = unsafe { device.get_device_queue(queue_family_index, 0) };
let pipeline_cache = create_pipeline_cache(&device);
let swapchain_loader = khr::swapchain::Device::new(&instance, &device);
let (
swapchain,
swapchain_format,
swapchain_color_space,
swapchain_extent,
swapchain_images,
swapchain_views,
) = create_swapchain(
&device,
&surface_loader,
&swapchain_loader,
physical_device,
surface,
size.width as u32,
size.height as u32,
vk::SwapchainKHR::null(),
);
let frames = create_frames(&device, queue_family_index);
let supports_f16 = false;
tracing::info!(
"Vulkan device created: {}",
physical_device_name(&instance, physical_device)
);
tracing::info!(
"Swapchain: {:?} {}x{} ({} images)",
swapchain_format,
swapchain_extent.width,
swapchain_extent.height,
swapchain_images.len()
);
log_memory_heap_choice(&instance, physical_device);
let shared = Arc::new(VkShared {
raw: device,
instance,
physical_device,
_entry: entry,
});
VulkanContext {
size,
scale,
supports_f16,
colorspace: Colorspace::Srgb,
needs_recreate: false,
frame_index: 0,
frames,
swapchain_extent,
swapchain_color_space,
swapchain_format,
swapchain_images,
swapchain_views,
swapchain,
swapchain_loader,
queue,
queue_family_index,
shared,
pipeline_cache,
surface,
surface_loader,
_debug_messenger,
}
}
#[inline]
pub fn set_scale(&mut self, scale: f32) {
self.scale = scale;
}
#[inline]
pub fn supports_f16(&self) -> bool {
self.supports_f16
}
pub fn resize(&mut self, width: u32, height: u32) {
if width == 0 || height == 0 {
return;
}
self.size.width = width as f32;
self.size.height = height as f32;
self.recreate_swapchain(width, height);
}
fn recreate_swapchain(&mut self, width: u32, height: u32) {
unsafe {
let _ = self.shared.device_wait_idle();
}
for &view in &self.swapchain_views {
unsafe { self.shared.destroy_image_view(view, None) };
}
self.swapchain_views.clear();
self.swapchain_images.clear();
let old = self.swapchain;
let (swapchain, format, color_space, extent, images, views) = create_swapchain(
&self.shared.raw,
&self.surface_loader,
&self.swapchain_loader,
self.shared.physical_device,
self.surface,
width,
height,
old,
);
unsafe { self.swapchain_loader.destroy_swapchain(old, None) };
self.swapchain = swapchain;
self.swapchain_format = format;
self.swapchain_color_space = color_space;
self.swapchain_extent = extent;
self.swapchain_images = images;
self.swapchain_views = views;
self.needs_recreate = false;
}
pub fn acquire_frame(&mut self) -> Option<VulkanFrame> {
if self.needs_recreate {
self.recreate_swapchain(self.size.width as u32, self.size.height as u32);
}
let slot = self.frame_index;
let sync = &self.frames[slot];
unsafe {
self.shared
.wait_for_fences(&[sync.in_flight], true, u64::MAX)
.expect("wait_for_fences");
}
let (image_index, suboptimal) = unsafe {
match self.swapchain_loader.acquire_next_image(
self.swapchain,
u64::MAX,
sync.image_available,
vk::Fence::null(),
) {
Ok(pair) => pair,
Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => {
self.recreate_swapchain(
self.size.width as u32,
self.size.height as u32,
);
return None;
}
Err(e) => panic!("acquire_next_image failed: {e:?}"),
}
};
if suboptimal {
self.needs_recreate = true;
}
unsafe {
self.shared
.reset_fences(&[sync.in_flight])
.expect("reset_fences");
self.shared
.reset_command_pool(sync.cmd_pool, vk::CommandPoolResetFlags::empty())
.expect("reset_command_pool");
self.shared
.begin_command_buffer(
sync.cmd_buffer,
&vk::CommandBufferBeginInfo::default()
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
)
.expect("begin_command_buffer");
}
Some(VulkanFrame {
image_index,
image: self.swapchain_images[image_index as usize],
image_view: self.swapchain_views[image_index as usize],
cmd_buffer: sync.cmd_buffer,
extent: self.swapchain_extent,
format: self.swapchain_format,
slot,
})
}
pub fn present_frame(&mut self, frame: VulkanFrame) {
let sync = &self.frames[frame.slot];
unsafe {
self.shared
.end_command_buffer(sync.cmd_buffer)
.expect("end_command_buffer");
let wait_semaphores = [sync.image_available];
let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
let signal_semaphores = [sync.render_finished];
let cmd_buffers = [sync.cmd_buffer];
let submit = vk::SubmitInfo::default()
.wait_semaphores(&wait_semaphores)
.wait_dst_stage_mask(&wait_stages)
.command_buffers(&cmd_buffers)
.signal_semaphores(&signal_semaphores);
self.shared
.queue_submit(self.queue, &[submit], sync.in_flight)
.expect("queue_submit");
let swapchains = [self.swapchain];
let image_indices = [frame.image_index];
let present_info = vk::PresentInfoKHR::default()
.wait_semaphores(&signal_semaphores)
.swapchains(&swapchains)
.image_indices(&image_indices);
match self
.swapchain_loader
.queue_present(self.queue, &present_info)
{
Ok(suboptimal) => {
if suboptimal {
self.needs_recreate = true;
}
}
Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => {
self.needs_recreate = true;
}
Err(e) => panic!("queue_present failed: {e:?}"),
}
}
self.frame_index = (self.frame_index + 1) % FRAMES_IN_FLIGHT;
}
#[inline]
pub fn device(&self) -> &Device {
&self.shared.raw
}
#[inline]
pub fn shared(&self) -> &Arc<VkShared> {
&self.shared
}
#[inline]
pub fn swapchain_format(&self) -> vk::Format {
self.swapchain_format
}
#[inline]
pub fn instance(&self) -> &Instance {
&self.shared.instance
}
#[inline]
pub fn physical_device(&self) -> vk::PhysicalDevice {
self.shared.physical_device
}
#[inline]
pub fn pipeline_cache(&self) -> vk::PipelineCache {
self.pipeline_cache
}
pub fn submit_oneshot<F: FnOnce(vk::CommandBuffer)>(&self, record: F) {
unsafe {
let pool_info = vk::CommandPoolCreateInfo::default()
.queue_family_index(self.queue_family_index)
.flags(vk::CommandPoolCreateFlags::TRANSIENT);
let pool = self
.shared
.create_command_pool(&pool_info, None)
.expect("create_command_pool(oneshot)");
let alloc = vk::CommandBufferAllocateInfo::default()
.command_pool(pool)
.level(vk::CommandBufferLevel::PRIMARY)
.command_buffer_count(1);
let cmd = self
.shared
.allocate_command_buffers(&alloc)
.expect("allocate_command_buffers(oneshot)")[0];
let begin = vk::CommandBufferBeginInfo::default()
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
self.shared
.begin_command_buffer(cmd, &begin)
.expect("begin_command_buffer(oneshot)");
record(cmd);
self.shared
.end_command_buffer(cmd)
.expect("end_command_buffer(oneshot)");
let fence = self
.shared
.create_fence(&vk::FenceCreateInfo::default(), None)
.expect("create_fence(oneshot)");
let cmds = [cmd];
let submit = vk::SubmitInfo::default().command_buffers(&cmds);
self.shared
.queue_submit(self.queue, &[submit], fence)
.expect("queue_submit(oneshot)");
self.shared
.wait_for_fences(&[fence], true, u64::MAX)
.expect("wait_for_fences(oneshot)");
self.shared.destroy_fence(fence, None);
self.shared.destroy_command_pool(pool, None);
}
}
#[inline]
pub fn current_frame_slot(&self) -> usize {
self.frame_index
}
pub fn allocate_host_visible_buffer(
&self,
size: u64,
usage: vk::BufferUsageFlags,
) -> VulkanBuffer {
let size = size.max(1);
let buffer_info = vk::BufferCreateInfo::default()
.size(size)
.usage(usage)
.sharing_mode(vk::SharingMode::EXCLUSIVE);
let buffer = unsafe {
self.shared
.create_buffer(&buffer_info, None)
.expect("create_buffer")
};
let req = unsafe { self.shared.get_buffer_memory_requirements(buffer) };
let mem_type = find_memory_type(
&self.shared.instance,
self.shared.physical_device,
req.memory_type_bits,
vk::MemoryPropertyFlags::HOST_VISIBLE
| vk::MemoryPropertyFlags::HOST_COHERENT,
)
.expect("no HOST_VISIBLE | HOST_COHERENT memory type — driver bug?");
let alloc_info = vk::MemoryAllocateInfo::default()
.allocation_size(req.size)
.memory_type_index(mem_type);
let memory = unsafe {
self.shared
.allocate_memory(&alloc_info, None)
.expect("allocate_memory")
};
unsafe {
self.shared
.bind_buffer_memory(buffer, memory, 0)
.expect("bind_buffer_memory");
}
let mapped = unsafe {
self.shared
.map_memory(memory, 0, vk::WHOLE_SIZE, vk::MemoryMapFlags::empty())
.expect("map_memory") as *mut u8
};
VulkanBuffer {
shared: self.shared.clone(),
buffer,
memory,
mapped,
size,
}
}
}
pub struct VulkanBuffer {
shared: Arc<VkShared>,
buffer: vk::Buffer,
memory: vk::DeviceMemory,
mapped: *mut u8,
size: u64,
}
unsafe impl Send for VulkanBuffer {}
unsafe impl Sync for VulkanBuffer {}
impl VulkanBuffer {
#[inline]
pub fn handle(&self) -> vk::Buffer {
self.buffer
}
#[inline]
pub fn size(&self) -> u64 {
self.size
}
#[inline]
pub fn as_mut_ptr(&self) -> *mut u8 {
self.mapped
}
}
impl Drop for VulkanBuffer {
fn drop(&mut self) {
unsafe {
self.shared.unmap_memory(self.memory);
self.shared.destroy_buffer(self.buffer, None);
self.shared.free_memory(self.memory, None);
}
}
}
pub fn allocate_host_visible_buffer_raw(
shared: &Arc<VkShared>,
size: u64,
usage: vk::BufferUsageFlags,
) -> VulkanBuffer {
let size = size.max(1);
let buffer_info = vk::BufferCreateInfo::default()
.size(size)
.usage(usage)
.sharing_mode(vk::SharingMode::EXCLUSIVE);
let buffer = unsafe {
shared
.create_buffer(&buffer_info, None)
.expect("create_buffer")
};
let req = unsafe { shared.get_buffer_memory_requirements(buffer) };
let mem_type = find_memory_type(
&shared.instance,
shared.physical_device,
req.memory_type_bits,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
)
.expect("no HOST_VISIBLE | HOST_COHERENT memory type");
let alloc_info = vk::MemoryAllocateInfo::default()
.allocation_size(req.size)
.memory_type_index(mem_type);
let memory = unsafe {
shared
.allocate_memory(&alloc_info, None)
.expect("allocate_memory")
};
unsafe {
shared
.bind_buffer_memory(buffer, memory, 0)
.expect("bind_buffer_memory");
}
let mapped = unsafe {
shared
.map_memory(memory, 0, vk::WHOLE_SIZE, vk::MemoryMapFlags::empty())
.expect("map_memory") as *mut u8
};
VulkanBuffer {
shared: shared.clone(),
buffer,
memory,
mapped,
size,
}
}
fn find_memory_type(
instance: &Instance,
physical_device: vk::PhysicalDevice,
type_filter: u32,
flags: vk::MemoryPropertyFlags,
) -> Option<u32> {
let props =
unsafe { instance.get_physical_device_memory_properties(physical_device) };
for i in 0..props.memory_type_count {
let supported = (type_filter & (1 << i)) != 0;
let matches_flags = props.memory_types[i as usize]
.property_flags
.contains(flags);
if supported && matches_flags {
return Some(i);
}
}
None
}
fn log_memory_heap_choice(instance: &Instance, physical_device: vk::PhysicalDevice) {
let host_visible = find_memory_type(
instance,
physical_device,
!0,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
);
let device_local = find_memory_type(
instance,
physical_device,
!0,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
);
let props =
unsafe { instance.get_physical_device_memory_properties(physical_device) };
if let Some(idx) = host_visible {
let flags = props.memory_types[idx as usize].property_flags;
let bar = flags.contains(vk::MemoryPropertyFlags::DEVICE_LOCAL);
tracing::info!(
"Vulkan host-visible memory: type {} flags={:?} ({})",
idx,
flags,
if bar {
"BAR / unified — fast GPU reads"
} else {
"system RAM — slower GPU reads, consider staging for hot data"
}
);
}
if let Some(idx) = device_local {
tracing::info!(
"Vulkan device-local memory: type {} flags={:?}",
idx,
props.memory_types[idx as usize].property_flags
);
}
}
impl VulkanContext {
pub fn allocate_sampled_image(
&self,
width: u32,
height: u32,
format: vk::Format,
usage: vk::ImageUsageFlags,
) -> VulkanImage {
let image_info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.format(format)
.extent(vk::Extent3D {
width,
height,
depth: 1,
})
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::OPTIMAL)
.usage(usage)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.initial_layout(vk::ImageLayout::UNDEFINED);
let image = unsafe {
self.shared
.create_image(&image_info, None)
.expect("create_image")
};
let req = unsafe { self.shared.get_image_memory_requirements(image) };
let mem_type = find_memory_type(
&self.shared.instance,
self.shared.physical_device,
req.memory_type_bits,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)
.expect("no DEVICE_LOCAL memory type — driver bug?");
let alloc_info = vk::MemoryAllocateInfo::default()
.allocation_size(req.size)
.memory_type_index(mem_type);
let memory = unsafe {
self.shared
.allocate_memory(&alloc_info, None)
.expect("allocate_memory(image)")
};
unsafe {
self.shared
.bind_image_memory(image, memory, 0)
.expect("bind_image_memory");
}
let view_info = vk::ImageViewCreateInfo::default()
.image(image)
.view_type(vk::ImageViewType::TYPE_2D)
.format(format)
.components(vk::ComponentMapping::default())
.subresource_range(
vk::ImageSubresourceRange::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.base_mip_level(0)
.level_count(1)
.base_array_layer(0)
.layer_count(1),
);
let view = unsafe {
self.shared
.create_image_view(&view_info, None)
.expect("create_image_view")
};
VulkanImage {
shared: self.shared.clone(),
image,
view,
memory,
width,
height,
format,
}
}
}
pub struct VulkanImage {
shared: Arc<VkShared>,
image: vk::Image,
view: vk::ImageView,
memory: vk::DeviceMemory,
pub width: u32,
pub height: u32,
pub format: vk::Format,
}
unsafe impl Send for VulkanImage {}
unsafe impl Sync for VulkanImage {}
impl VulkanImage {
#[inline]
pub fn handle(&self) -> vk::Image {
self.image
}
#[inline]
pub fn view(&self) -> vk::ImageView {
self.view
}
}
impl Drop for VulkanImage {
fn drop(&mut self) {
unsafe {
self.shared.destroy_image_view(self.view, None);
self.shared.destroy_image(self.image, None);
self.shared.free_memory(self.memory, None);
}
}
}
impl Drop for VulkanContext {
fn drop(&mut self) {
unsafe {
let _ = self.shared.device_wait_idle();
save_pipeline_cache(&self.shared.raw, self.pipeline_cache);
self.shared
.destroy_pipeline_cache(self.pipeline_cache, None);
for frame in &self.frames {
self.shared.destroy_semaphore(frame.image_available, None);
self.shared.destroy_semaphore(frame.render_finished, None);
self.shared.destroy_fence(frame.in_flight, None);
self.shared.destroy_command_pool(frame.cmd_pool, None);
}
for &view in &self.swapchain_views {
self.shared.destroy_image_view(view, None);
}
self.swapchain_loader
.destroy_swapchain(self.swapchain, None);
self.surface_loader.destroy_surface(self.surface, None);
}
}
}
fn pipeline_cache_path() -> Option<std::path::PathBuf> {
let dir = if let Some(xdg) = std::env::var_os("XDG_CACHE_HOME") {
std::path::PathBuf::from(xdg)
} else if let Some(home) = std::env::var_os("HOME") {
let mut p = std::path::PathBuf::from(home);
p.push(".cache");
p
} else {
return None;
};
Some(dir.join("rio").join("sugarloaf-vulkan.cache"))
}
fn create_pipeline_cache(device: &Device) -> vk::PipelineCache {
let initial_data: Vec<u8> = pipeline_cache_path()
.and_then(|p| std::fs::read(&p).ok())
.unwrap_or_default();
if !initial_data.is_empty() {
tracing::info!("loaded Vulkan pipeline cache: {} bytes", initial_data.len());
}
let info = vk::PipelineCacheCreateInfo::default().initial_data(&initial_data);
unsafe {
device
.create_pipeline_cache(&info, None)
.expect("create_pipeline_cache")
}
}
fn save_pipeline_cache(device: &Device, cache: vk::PipelineCache) {
let Some(path) = pipeline_cache_path() else {
return;
};
let data = match unsafe { device.get_pipeline_cache_data(cache) } {
Ok(d) => d,
Err(e) => {
tracing::warn!("get_pipeline_cache_data failed: {:?}", e);
return;
}
};
if data.is_empty() {
return;
}
if let Some(parent) = path.parent() {
if let Err(e) = std::fs::create_dir_all(parent) {
tracing::warn!("pipeline cache mkdir {:?} failed: {}", parent, e);
return;
}
}
if let Err(e) = std::fs::write(&path, &data) {
tracing::warn!("pipeline cache write {:?} failed: {}", path, e);
} else {
tracing::info!(
"saved Vulkan pipeline cache: {} bytes → {:?}",
data.len(),
path
);
}
}
fn create_instance(
entry: &Entry,
window: &SugarloafWindow,
enable_validation: bool,
) -> Instance {
let app_name = c"sugarloaf";
let app_info = vk::ApplicationInfo::default()
.application_name(app_name)
.application_version(0)
.engine_name(app_name)
.engine_version(0)
.api_version(vk::API_VERSION_1_3);
let mut extensions: Vec<*const c_char> = vec![khr::surface::NAME.as_ptr()];
match window.display_handle().unwrap().as_raw() {
RawDisplayHandle::Xlib(_) => extensions.push(khr::xlib_surface::NAME.as_ptr()),
RawDisplayHandle::Xcb(_) => extensions.push(khr::xcb_surface::NAME.as_ptr()),
RawDisplayHandle::Wayland(_) => {
extensions.push(khr::wayland_surface::NAME.as_ptr())
}
other => panic!("Vulkan backend: unsupported display handle {:?}", other),
}
let validation_layer_name = c"VK_LAYER_KHRONOS_validation";
let layer_ptrs: Vec<*const c_char> = if enable_validation {
if validation_layer_available(entry, validation_layer_name) {
extensions.push(ash::ext::debug_utils::NAME.as_ptr());
vec![validation_layer_name.as_ptr()]
} else {
tracing::warn!(
"RIO_VULKAN_VALIDATION set but VK_LAYER_KHRONOS_validation \
not available — install `vulkan-validationlayers` (Debian) \
/ `vulkan-validation-layers` (Arch) to enable it"
);
Vec::new()
}
} else {
Vec::new()
};
let create_info = vk::InstanceCreateInfo::default()
.application_info(&app_info)
.enabled_extension_names(&extensions)
.enabled_layer_names(&layer_ptrs);
unsafe { entry.create_instance(&create_info, None) }
.expect("vkCreateInstance failed — is a Vulkan 1.3 driver installed?")
}
fn validation_requested() -> bool {
std::env::var_os("RIO_VULKAN_VALIDATION")
.map(|v| v != "0" && !v.is_empty())
.unwrap_or(false)
}
fn validation_layer_available(entry: &Entry, target: &CStr) -> bool {
match unsafe { entry.enumerate_instance_layer_properties() } {
Ok(layers) => layers.iter().any(|l| {
let name = unsafe { CStr::from_ptr(l.layer_name.as_ptr()) };
name == target
}),
Err(_) => false,
}
}
fn create_debug_messenger(entry: &Entry, instance: &Instance) -> Option<DebugMessenger> {
let loader = ash::ext::debug_utils::Instance::new(entry, instance);
let 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(debug_callback));
let handle = unsafe { loader.create_debug_utils_messenger(&info, None) }
.expect("create_debug_utils_messenger");
tracing::info!("Vulkan validation layers active");
Some(DebugMessenger { loader, handle })
}
unsafe extern "system" fn debug_callback(
severity: vk::DebugUtilsMessageSeverityFlagsEXT,
msg_type: vk::DebugUtilsMessageTypeFlagsEXT,
callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>,
_user_data: *mut std::ffi::c_void,
) -> vk::Bool32 {
let data = unsafe { &*callback_data };
let message = if data.p_message.is_null() {
std::borrow::Cow::Borrowed("<null>")
} else {
unsafe { CStr::from_ptr(data.p_message) }.to_string_lossy()
};
let kind = if msg_type.contains(vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION) {
"validation"
} else if msg_type.contains(vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE) {
"perf"
} else {
"general"
};
if severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::ERROR) {
tracing::error!("vk[{}] {}", kind, message);
} else if severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::WARNING) {
tracing::warn!("vk[{}] {}", kind, message);
} else if severity.contains(vk::DebugUtilsMessageSeverityFlagsEXT::INFO) {
tracing::info!("vk[{}] {}", kind, message);
} else {
tracing::debug!("vk[{}] {}", kind, message);
}
vk::FALSE
}
fn create_surface(
entry: &Entry,
instance: &Instance,
window: &SugarloafWindow,
) -> vk::SurfaceKHR {
let display = window.display_handle().unwrap().as_raw();
let window_handle = window.window_handle().unwrap().as_raw();
unsafe {
match (display, window_handle) {
(RawDisplayHandle::Xlib(d), RawWindowHandle::Xlib(w)) => {
let loader = khr::xlib_surface::Instance::new(entry, instance);
let info = vk::XlibSurfaceCreateInfoKHR::default()
.dpy(
d.display
.expect("Xlib display pointer missing")
.as_ptr()
.cast(),
)
.window(w.window);
loader
.create_xlib_surface(&info, None)
.expect("create_xlib_surface")
}
(RawDisplayHandle::Xcb(d), RawWindowHandle::Xcb(w)) => {
let loader = khr::xcb_surface::Instance::new(entry, instance);
let info = vk::XcbSurfaceCreateInfoKHR::default()
.connection(
d.connection
.expect("Xcb connection pointer missing")
.as_ptr()
.cast(),
)
.window(w.window.get());
loader
.create_xcb_surface(&info, None)
.expect("create_xcb_surface")
}
(RawDisplayHandle::Wayland(d), RawWindowHandle::Wayland(w)) => {
let loader = khr::wayland_surface::Instance::new(entry, instance);
let info = vk::WaylandSurfaceCreateInfoKHR::default()
.display(d.display.as_ptr().cast())
.surface(w.surface.as_ptr().cast());
loader
.create_wayland_surface(&info, None)
.expect("create_wayland_surface")
}
(d, w) => panic!(
"Vulkan backend: mismatched or unsupported handles: display={d:?} window={w:?}"
),
}
}
}
fn pick_physical_device(
instance: &Instance,
surface_loader: &khr::surface::Instance,
surface: vk::SurfaceKHR,
) -> (vk::PhysicalDevice, u32) {
let devices = unsafe { instance.enumerate_physical_devices() }
.expect("enumerate_physical_devices");
let mut best: Option<(vk::PhysicalDevice, u32, i32)> = None;
for device in devices {
let props = unsafe { instance.get_physical_device_properties(device) };
let qf_props =
unsafe { instance.get_physical_device_queue_family_properties(device) };
for (index, qf) in qf_props.iter().enumerate() {
let index = index as u32;
if !qf.queue_flags.contains(vk::QueueFlags::GRAPHICS) {
continue;
}
let present_ok = unsafe {
surface_loader
.get_physical_device_surface_support(device, index, surface)
.unwrap_or(false)
};
if !present_ok {
continue;
}
let score = match props.device_type {
vk::PhysicalDeviceType::DISCRETE_GPU => 1000,
vk::PhysicalDeviceType::INTEGRATED_GPU => 500,
vk::PhysicalDeviceType::VIRTUAL_GPU => 100,
vk::PhysicalDeviceType::CPU => 10,
_ => 1,
};
if best.map(|(_, _, s)| score > s).unwrap_or(true) {
best = Some((device, index, score));
}
}
}
let (device, queue_family, _) =
best.expect("no Vulkan device with graphics + present support on this surface");
(device, queue_family)
}
fn physical_device_name(instance: &Instance, device: vk::PhysicalDevice) -> String {
let props = unsafe { instance.get_physical_device_properties(device) };
let raw = props.device_name.as_ptr();
unsafe { CStr::from_ptr(raw) }
.to_string_lossy()
.into_owned()
}
fn create_device(
instance: &Instance,
physical_device: vk::PhysicalDevice,
queue_family_index: u32,
) -> Device {
let queue_priorities = [1.0f32];
let queue_info = vk::DeviceQueueCreateInfo::default()
.queue_family_index(queue_family_index)
.queue_priorities(&queue_priorities);
let device_extensions = [khr::swapchain::NAME.as_ptr()];
let mut vk13_features = vk::PhysicalDeviceVulkan13Features::default()
.dynamic_rendering(true)
.synchronization2(true);
let queue_infos = [queue_info];
let create_info = vk::DeviceCreateInfo::default()
.queue_create_infos(&queue_infos)
.enabled_extension_names(&device_extensions)
.push_next(&mut vk13_features);
unsafe { instance.create_device(physical_device, &create_info, None) }
.expect("vkCreateDevice")
}
#[allow(clippy::too_many_arguments)]
fn create_swapchain(
device: &Device,
surface_loader: &khr::surface::Instance,
swapchain_loader: &khr::swapchain::Device,
physical_device: vk::PhysicalDevice,
surface: vk::SurfaceKHR,
requested_width: u32,
requested_height: u32,
old: vk::SwapchainKHR,
) -> (
vk::SwapchainKHR,
vk::Format,
vk::ColorSpaceKHR,
vk::Extent2D,
Vec<vk::Image>,
Vec<vk::ImageView>,
) {
let caps = unsafe {
surface_loader
.get_physical_device_surface_capabilities(physical_device, surface)
.expect("get_physical_device_surface_capabilities")
};
let formats = unsafe {
surface_loader
.get_physical_device_surface_formats(physical_device, surface)
.expect("get_physical_device_surface_formats")
};
let chosen_format = formats
.iter()
.find(|f| {
f.format == vk::Format::B8G8R8A8_UNORM
&& f.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR
})
.copied()
.unwrap_or(formats[0]);
let present_mode = vk::PresentModeKHR::FIFO;
let extent = if caps.current_extent.width != u32::MAX {
caps.current_extent
} else {
vk::Extent2D {
width: requested_width
.clamp(caps.min_image_extent.width, caps.max_image_extent.width),
height: requested_height
.clamp(caps.min_image_extent.height, caps.max_image_extent.height),
}
};
let mut image_count = caps.min_image_count.max(3);
if caps.max_image_count != 0 && image_count > caps.max_image_count {
image_count = caps.max_image_count;
}
let create_info = vk::SwapchainCreateInfoKHR::default()
.surface(surface)
.min_image_count(image_count)
.image_format(chosen_format.format)
.image_color_space(chosen_format.color_space)
.image_extent(extent)
.image_array_layers(1)
.image_usage(
vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSFER_DST,
)
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
.pre_transform(caps.current_transform)
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
.present_mode(present_mode)
.clipped(true)
.old_swapchain(old);
let swapchain = unsafe { swapchain_loader.create_swapchain(&create_info, None) }
.expect("create_swapchain");
let images = unsafe { swapchain_loader.get_swapchain_images(swapchain) }
.expect("get_swapchain_images");
let views = images
.iter()
.map(|&image| {
let info = vk::ImageViewCreateInfo::default()
.image(image)
.view_type(vk::ImageViewType::TYPE_2D)
.format(chosen_format.format)
.components(vk::ComponentMapping::default())
.subresource_range(
vk::ImageSubresourceRange::default()
.aspect_mask(vk::ImageAspectFlags::COLOR)
.base_mip_level(0)
.level_count(1)
.base_array_layer(0)
.layer_count(1),
);
unsafe { device.create_image_view(&info, None) }.expect("create_image_view")
})
.collect();
(
swapchain,
chosen_format.format,
chosen_format.color_space,
extent,
images,
views,
)
}
fn create_frames(
device: &Device,
queue_family_index: u32,
) -> [FrameSync; FRAMES_IN_FLIGHT] {
std::array::from_fn(|_| {
let semaphore_info = vk::SemaphoreCreateInfo::default();
let fence_info =
vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED);
let pool_info = vk::CommandPoolCreateInfo::default()
.queue_family_index(queue_family_index)
.flags(vk::CommandPoolCreateFlags::TRANSIENT);
unsafe {
let image_available = device
.create_semaphore(&semaphore_info, None)
.expect("create_semaphore");
let render_finished = device
.create_semaphore(&semaphore_info, None)
.expect("create_semaphore");
let in_flight = device
.create_fence(&fence_info, None)
.expect("create_fence");
let cmd_pool = device
.create_command_pool(&pool_info, None)
.expect("create_command_pool");
let alloc_info = vk::CommandBufferAllocateInfo::default()
.command_pool(cmd_pool)
.level(vk::CommandBufferLevel::PRIMARY)
.command_buffer_count(1);
let cmd_buffer = device
.allocate_command_buffers(&alloc_info)
.expect("allocate_command_buffers")[0];
FrameSync {
image_available,
render_finished,
in_flight,
cmd_pool,
cmd_buffer,
}
}
})
}