refactor: split VulkanContext's impl block across linux_vulkan/ submodules

linux_vulkan.rs's single impl VulkanContext spanned ~840 lines. Split
into three sibling submodules under linux_vulkan/, same file+dir mod
pattern used for mpv_render/: init.rs (new() bootstrap + Drop teardown),
swapchain.rs (semaphore/command-buffer/swapchain creation + resize),
render.rs (frame acquire/submit/present loop + accessors).

create_semaphores, create_command_buffer, create_swapchain needed
pub(super) since new() (in init.rs) and recover_after_render_error()
(in render.rs) call them across the new file boundary.

No logic changed — verified with cargo check.
This commit is contained in:
KhooLy 2026-07-30 03:35:14 +03:00
parent b5085abee2
commit f5aedac0b5
4 changed files with 880 additions and 868 deletions

View file

@ -515,872 +515,7 @@ pub struct VulkanContext {
unsafe impl Send for VulkanContext {}
impl VulkanContext {
pub fn new(native_surface: NativeSurface, width: i32, height: i32) -> Result<Self, String> {
let module = dlopen_first(&["libvulkan.so.1", "libvulkan.so"])
.ok_or("libvulkan.so.1 not found (no Vulkan-capable driver installed?)")?;
let get_instance_proc_addr: PfnGetInstanceProcAddr =
unsafe { std::mem::transmute(dlsym_typed(module, "vkGetInstanceProcAddr")?) };
let create_instance: PfnCreateInstance = unsafe {
std::mem::transmute(get_instance_proc(
get_instance_proc_addr,
ptr::null_mut(),
"vkCreateInstance",
)?)
};
let app_name = CString::new("fluxa-desktop").unwrap();
let app_info = VkApplicationInfo {
s_type: VK_STRUCTURE_TYPE_APPLICATION_INFO,
p_next: ptr::null(),
p_application_name: app_name.as_ptr(),
application_version: 0,
p_engine_name: app_name.as_ptr(),
engine_version: 0,
api_version: VK_API_VERSION_1_3,
};
let surface_ext_name = match native_surface {
NativeSurface::Xlib { .. } => "VK_KHR_xlib_surface",
NativeSurface::Wayland { .. } => "VK_KHR_wayland_surface",
};
let extensions = [
CString::new("VK_KHR_surface").unwrap(),
CString::new(surface_ext_name).unwrap(),
CString::new("VK_EXT_swapchain_colorspace").unwrap(),
];
let extension_ptrs: Vec<*const i8> = extensions.iter().map(|e| e.as_ptr()).collect();
let validation_layer = CString::new("VK_LAYER_KHRONOS_validation").unwrap();
let want_validation = std::env::var_os("FLUXA_VULKAN_VALIDATION").is_some();
let layer_ptrs: Vec<*const i8> = if want_validation {
vec![validation_layer.as_ptr()]
} else {
vec![]
};
let instance_create_info = VkInstanceCreateInfo {
s_type: VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
p_next: ptr::null(),
flags: 0,
p_application_info: &app_info,
enabled_layer_count: layer_ptrs.len() as u32,
pp_enabled_layer_names: layer_ptrs.as_ptr(),
enabled_extension_count: extension_ptrs.len() as u32,
pp_enabled_extension_names: extension_ptrs.as_ptr(),
};
let mut instance: VkInstance = ptr::null_mut();
let result = unsafe { create_instance(&instance_create_info, ptr::null(), &mut instance) };
if result != VK_SUCCESS {
return Err(format!("vkCreateInstance failed: VkResult {result}"));
}
macro_rules! iproc {
($name:expr_2021) => {
unsafe {
std::mem::transmute(get_instance_proc(get_instance_proc_addr, instance, $name)?)
}
};
}
let destroy_instance: PfnDestroyInstance = iproc!("vkDestroyInstance");
let enumerate_physical_devices: PfnEnumeratePhysicalDevices =
iproc!("vkEnumeratePhysicalDevices");
let get_physical_device_properties: PfnGetPhysicalDeviceProperties =
iproc!("vkGetPhysicalDeviceProperties");
let get_queue_family_properties: PfnGetPhysicalDeviceQueueFamilyProperties =
iproc!("vkGetPhysicalDeviceQueueFamilyProperties");
let get_surface_support_khr: PfnGetPhysicalDeviceSurfaceSupportKHR =
iproc!("vkGetPhysicalDeviceSurfaceSupportKHR");
let get_surface_capabilities_khr: PfnGetPhysicalDeviceSurfaceCapabilitiesKHR =
iproc!("vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
let get_surface_formats_khr: PfnGetPhysicalDeviceSurfaceFormatsKHR =
iproc!("vkGetPhysicalDeviceSurfaceFormatsKHR");
let get_surface_present_modes_khr: PfnGetPhysicalDeviceSurfacePresentModesKHR =
iproc!("vkGetPhysicalDeviceSurfacePresentModesKHR");
let destroy_surface_khr: PfnDestroySurfaceKHR = iproc!("vkDestroySurfaceKHR");
let create_device: PfnCreateDevice = iproc!("vkCreateDevice");
let enumerate_device_extension_properties: PfnEnumerateDeviceExtensionProperties =
iproc!("vkEnumerateDeviceExtensionProperties");
let mut surface: VkSurfaceKHR = 0;
let mut owned_xlib_display: *mut c_void = ptr::null_mut();
let result = match native_surface {
NativeSurface::Xlib { display, window } => {
let create_xlib_surface_khr: PfnCreateXlibSurfaceKHR =
iproc!("vkCreateXlibSurfaceKHR");
// The render loop runs on its own thread, but GTK's Display
// connection is not thread-safe (XInitThreads is never called).
// A private connection keeps Mesa's WSI traffic off GTK's.
owned_xlib_display = unsafe { x11::xlib::XOpenDisplay(ptr::null()) as *mut c_void };
let dpy = if owned_xlib_display.is_null() {
display
} else {
owned_xlib_display
};
let create_info = VkXlibSurfaceCreateInfoKHR {
s_type: VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
p_next: ptr::null(),
flags: 0,
dpy,
window,
};
unsafe {
create_xlib_surface_khr(instance, &create_info, ptr::null(), &mut surface)
}
}
NativeSurface::Wayland {
display,
surface: wl_surface,
} => {
let create_wayland_surface_khr: PfnCreateWaylandSurfaceKHR =
iproc!("vkCreateWaylandSurfaceKHR");
let create_info = VkWaylandSurfaceCreateInfoKHR {
s_type: VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR,
p_next: ptr::null(),
flags: 0,
display,
surface: wl_surface,
};
unsafe {
create_wayland_surface_khr(instance, &create_info, ptr::null(), &mut surface)
}
}
};
if result != VK_SUCCESS {
unsafe { destroy_instance(instance, ptr::null()) };
return Err(format!("vkCreate*SurfaceKHR failed: VkResult {result}"));
}
let mut device_count: u32 = 0;
unsafe { enumerate_physical_devices(instance, &mut device_count, ptr::null_mut()) };
if device_count == 0 {
unsafe {
destroy_surface_khr(instance, surface, ptr::null());
destroy_instance(instance, ptr::null());
}
return Err("vkEnumeratePhysicalDevices found no GPUs".into());
}
let mut physical_devices = vec![ptr::null_mut(); device_count as usize];
unsafe {
enumerate_physical_devices(instance, &mut device_count, physical_devices.as_mut_ptr())
};
const VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: u32 = 2;
const VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: u32 = 1;
const VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: u32 = 3;
fn device_type_rank(device_type: u32) -> u32 {
match device_type {
VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU => 0,
VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU => 1,
VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU => 2,
_ => 3,
}
}
let mut candidates: Vec<(VkPhysicalDevice, u32, u32)> = Vec::new();
for &phys_device in &physical_devices {
let mut family_count: u32 = 0;
unsafe { get_queue_family_properties(phys_device, &mut family_count, ptr::null_mut()) };
let mut families: Vec<VkQueueFamilyProperties> = (0..family_count)
.map(|_| VkQueueFamilyProperties {
queue_flags: 0,
queue_count: 0,
timestamp_valid_bits: 0,
min_image_transfer_granularity: [0; 3],
})
.collect();
unsafe {
get_queue_family_properties(phys_device, &mut family_count, families.as_mut_ptr())
};
for (index, family) in families.iter().enumerate() {
if family.queue_flags & VK_QUEUE_GRAPHICS_BIT == 0 {
continue;
}
let mut present_supported: u32 = 0;
unsafe {
get_surface_support_khr(
phys_device,
index as u32,
surface,
&mut present_supported,
)
};
if present_supported != 0 {
let mut props = [0u8; 1024];
unsafe {
get_physical_device_properties(
phys_device,
props.as_mut_ptr() as *mut c_void,
)
};
let device_type = u32::from_ne_bytes(props[16..20].try_into().unwrap());
candidates.push((phys_device, index as u32, device_type));
break;
}
}
}
candidates.sort_by_key(|&(_, _, device_type)| device_type_rank(device_type));
let chosen = candidates
.first()
.map(|&(phys_device, queue_family_index, _)| (phys_device, queue_family_index));
let Some((phys_device, queue_family_index)) = chosen else {
unsafe {
destroy_surface_khr(instance, surface, ptr::null());
destroy_instance(instance, ptr::null());
}
return Err(
"no Vulkan queue family supports both graphics and presenting to this window"
.to_string(),
);
};
let queue_priority: f32 = 1.0;
let queue_create_info = VkDeviceQueueCreateInfo {
s_type: VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
p_next: ptr::null(),
flags: 0,
queue_family_index,
queue_count: 1,
p_queue_priorities: &queue_priority,
};
let mut ext_count: u32 = 0;
unsafe {
enumerate_device_extension_properties(
phys_device,
ptr::null(),
&mut ext_count,
ptr::null_mut(),
)
};
let mut supported_extensions = vec![
VkExtensionProperties {
extension_name: [0; 256],
spec_version: 0,
};
ext_count as usize
];
unsafe {
enumerate_device_extension_properties(
phys_device,
ptr::null(),
&mut ext_count,
supported_extensions.as_mut_ptr(),
)
};
let device_extensions: Vec<CString> = supported_extensions
.iter()
.filter_map(|ext| {
let nul = ext.extension_name.iter().position(|&b| b == 0)?;
CString::new(&ext.extension_name[..nul]).ok()
})
.collect();
let device_extension_ptrs: Vec<*const i8> =
device_extensions.iter().map(|e| e.as_ptr()).collect();
let mut synchronization2_features = VkPhysicalDeviceSynchronization2Features {
s_type: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES,
p_next: ptr::null_mut(),
synchronization2: 1,
};
let mut timeline_semaphore_features = VkPhysicalDeviceTimelineSemaphoreFeatures {
s_type: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES,
p_next: &mut synchronization2_features as *mut _ as *mut c_void,
timeline_semaphore: 1,
};
let device_create_info = VkDeviceCreateInfo {
s_type: VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
p_next: &mut timeline_semaphore_features as *mut _ as *const c_void,
flags: 0,
queue_create_info_count: 1,
p_queue_create_infos: &queue_create_info,
enabled_layer_count: 0,
pp_enabled_layer_names: ptr::null(),
enabled_extension_count: device_extension_ptrs.len() as u32,
pp_enabled_extension_names: device_extension_ptrs.as_ptr(),
p_enabled_features: ptr::null(),
};
let mut device: VkDevice = ptr::null_mut();
let result =
unsafe { create_device(phys_device, &device_create_info, ptr::null(), &mut device) };
if result != VK_SUCCESS {
unsafe {
destroy_surface_khr(instance, surface, ptr::null());
destroy_instance(instance, ptr::null());
}
return Err(format!("vkCreateDevice failed: VkResult {result}"));
}
macro_rules! dproc {
($name:expr_2021) => {
unsafe {
std::mem::transmute(get_instance_proc(get_instance_proc_addr, instance, $name)?)
}
};
}
let destroy_device: PfnDestroyDevice = dproc!("vkDestroyDevice");
let get_device_queue: PfnGetDeviceQueue = dproc!("vkGetDeviceQueue");
let create_swapchain_khr: PfnCreateSwapchainKHR = dproc!("vkCreateSwapchainKHR");
let destroy_swapchain_khr: PfnDestroySwapchainKHR = dproc!("vkDestroySwapchainKHR");
let get_swapchain_images_khr: PfnGetSwapchainImagesKHR = dproc!("vkGetSwapchainImagesKHR");
let acquire_next_image_khr: PfnAcquireNextImageKHR = dproc!("vkAcquireNextImageKHR");
let queue_present_khr: PfnQueuePresentKHR = dproc!("vkQueuePresentKHR");
let create_semaphore: PfnCreateSemaphore = dproc!("vkCreateSemaphore");
let destroy_semaphore: PfnDestroySemaphore = dproc!("vkDestroySemaphore");
let create_fence: PfnCreateFence = dproc!("vkCreateFence");
let destroy_fence: PfnDestroyFence = dproc!("vkDestroyFence");
let wait_for_fences: PfnWaitForFences = dproc!("vkWaitForFences");
let reset_fences: PfnResetFences = dproc!("vkResetFences");
let device_wait_idle: PfnDeviceWaitIdle = dproc!("vkDeviceWaitIdle");
let create_command_pool: PfnCreateCommandPool = dproc!("vkCreateCommandPool");
let destroy_command_pool: PfnDestroyCommandPool = dproc!("vkDestroyCommandPool");
let allocate_command_buffers: PfnAllocateCommandBuffers =
dproc!("vkAllocateCommandBuffers");
let reset_command_buffer: PfnResetCommandBuffer = dproc!("vkResetCommandBuffer");
let begin_command_buffer: PfnBeginCommandBuffer = dproc!("vkBeginCommandBuffer");
let end_command_buffer: PfnEndCommandBuffer = dproc!("vkEndCommandBuffer");
let cmd_pipeline_barrier: PfnCmdPipelineBarrier = dproc!("vkCmdPipelineBarrier");
let queue_submit: PfnQueueSubmit = dproc!("vkQueueSubmit");
let mut queue: VkQueue = ptr::null_mut();
unsafe { get_device_queue(device, queue_family_index, 0, &mut queue) };
let fns = VkFns {
get_instance_proc_addr,
destroy_instance,
get_physical_device_surface_capabilities_khr: get_surface_capabilities_khr,
get_physical_device_surface_formats_khr: get_surface_formats_khr,
get_physical_device_surface_present_modes_khr: get_surface_present_modes_khr,
destroy_surface_khr,
destroy_device,
create_swapchain_khr,
destroy_swapchain_khr,
get_swapchain_images_khr,
acquire_next_image_khr,
queue_present_khr,
create_semaphore,
destroy_semaphore,
create_fence,
destroy_fence,
wait_for_fences,
reset_fences,
device_wait_idle,
create_command_pool,
destroy_command_pool,
allocate_command_buffers,
reset_command_buffer,
begin_command_buffer,
end_command_buffer,
cmd_pipeline_barrier,
queue_submit,
};
let mut ctx = Self {
fns,
instance,
phys_device,
device,
queue,
queue_family_index,
surface,
swapchain: 0,
images: Vec::new(),
image_format: VK_FORMAT_B8G8R8A8_UNORM,
image_usage: VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
extent: VkExtent2D {
width: width.max(2) as u32,
height: height.max(2) as u32,
},
acquire_semaphore: 0,
render_done_semaphore: 0,
transition_semaphore: 0,
in_flight_fence: 0,
command_pool: ptr::null_mut(),
command_buffer: ptr::null_mut(),
hdr: AtomicBool::new(false),
enabled_device_extensions: device_extensions,
owned_xlib_display,
};
ctx.create_swapchain(width.max(2) as u32, height.max(2) as u32)?;
ctx.create_semaphores()?;
ctx.create_command_buffer()?;
Ok(ctx)
}
fn create_semaphores(&mut self) -> Result<(), String> {
let info = VkSemaphoreCreateInfo {
s_type: VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
p_next: ptr::null(),
flags: 0,
};
let mut acquire: VkSemaphore = 0;
let mut render_done: VkSemaphore = 0;
let mut transition: VkSemaphore = 0;
let r1 =
unsafe { (self.fns.create_semaphore)(self.device, &info, ptr::null(), &mut acquire) };
let r2 = unsafe {
(self.fns.create_semaphore)(self.device, &info, ptr::null(), &mut render_done)
};
let r3 = unsafe {
(self.fns.create_semaphore)(self.device, &info, ptr::null(), &mut transition)
};
if r1 != VK_SUCCESS || r2 != VK_SUCCESS || r3 != VK_SUCCESS {
return Err(format!("vkCreateSemaphore failed: {r1}/{r2}/{r3}"));
}
self.acquire_semaphore = acquire;
self.render_done_semaphore = render_done;
self.transition_semaphore = transition;
let fence_info = VkFenceCreateInfo {
s_type: VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
p_next: ptr::null(),
flags: VK_FENCE_CREATE_SIGNALED_BIT,
};
let mut fence: VkFence = 0;
let r4 =
unsafe { (self.fns.create_fence)(self.device, &fence_info, ptr::null(), &mut fence) };
if r4 != VK_SUCCESS {
return Err(format!("vkCreateFence failed: {r4}"));
}
self.in_flight_fence = fence;
Ok(())
}
fn create_command_buffer(&mut self) -> Result<(), String> {
let pool_info = VkCommandPoolCreateInfo {
s_type: VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
p_next: ptr::null(),
flags: VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
queue_family_index: self.queue_family_index,
};
let mut pool: VkCommandPool = ptr::null_mut();
let result = unsafe {
(self.fns.create_command_pool)(self.device, &pool_info, ptr::null(), &mut pool)
};
if result != VK_SUCCESS {
return Err(format!("vkCreateCommandPool failed: VkResult {result}"));
}
let alloc_info = VkCommandBufferAllocateInfo {
s_type: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
p_next: ptr::null(),
command_pool: pool,
level: VK_COMMAND_BUFFER_LEVEL_PRIMARY,
command_buffer_count: 1,
};
let mut command_buffer: VkCommandBuffer = ptr::null_mut();
let result = unsafe {
(self.fns.allocate_command_buffers)(self.device, &alloc_info, &mut command_buffer)
};
if result != VK_SUCCESS {
unsafe { (self.fns.destroy_command_pool)(self.device, pool, ptr::null()) };
return Err(format!(
"vkAllocateCommandBuffers failed: VkResult {result}"
));
}
self.command_pool = pool;
self.command_buffer = command_buffer;
Ok(())
}
fn create_swapchain(&mut self, width: u32, height: u32) -> Result<(), String> {
let mut caps = VkSurfaceCapabilitiesKHR {
min_image_count: 0,
max_image_count: 0,
current_extent: VkExtent2D::default(),
min_image_extent: VkExtent2D::default(),
max_image_extent: VkExtent2D::default(),
max_image_array_layers: 0,
supported_transforms: 0,
current_transform: 0,
supported_composite_alpha: 0,
supported_usage_flags: 0,
};
let result = unsafe {
(self.fns.get_physical_device_surface_capabilities_khr)(
self.phys_device,
self.surface,
&mut caps,
)
};
if result != VK_SUCCESS {
return Err(format!(
"vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: {result}"
));
}
let mut format_count: u32 = 0;
unsafe {
(self.fns.get_physical_device_surface_formats_khr)(
self.phys_device,
self.surface,
&mut format_count,
ptr::null_mut(),
)
};
let mut formats = vec![
VkSurfaceFormatKHR {
format: 0,
color_space: 0
};
format_count as usize
];
unsafe {
(self.fns.get_physical_device_surface_formats_khr)(
self.phys_device,
self.surface,
&mut format_count,
formats.as_mut_ptr(),
)
};
let hdr_format: Option<&VkSurfaceFormatKHR> = None;
let (chosen_format, hdr) = if let Some(f) = hdr_format {
(*f, true)
} else {
let f = formats
.iter()
.find(|f| {
f.format == VK_FORMAT_B8G8R8A8_UNORM
&& f.color_space == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
})
.or_else(|| {
formats
.iter()
.find(|f| f.format == VK_FORMAT_B8G8R8A8_UNORM)
})
.or_else(|| formats.first())
.copied()
.unwrap_or(VkSurfaceFormatKHR {
format: VK_FORMAT_B8G8R8A8_UNORM,
color_space: VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
});
(f, false)
};
self.hdr.store(hdr, Ordering::Release);
let mut image_count = caps.min_image_count + 1;
if caps.max_image_count > 0 && image_count > caps.max_image_count {
image_count = caps.max_image_count;
}
let extent = if caps.current_extent.width != u32::MAX {
caps.current_extent
} else {
VkExtent2D { width, height }
};
let image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
| (caps.supported_usage_flags & VK_IMAGE_USAGE_TRANSFER_DST_BIT);
self.image_usage = image_usage;
let mut mode_count: u32 = 0;
unsafe {
(self.fns.get_physical_device_surface_present_modes_khr)(
self.phys_device,
self.surface,
&mut mode_count,
ptr::null_mut(),
)
};
let mut modes = vec![0i32; mode_count as usize];
if mode_count > 0 {
unsafe {
(self.fns.get_physical_device_surface_present_modes_khr)(
self.phys_device,
self.surface,
&mut mode_count,
modes.as_mut_ptr(),
)
};
}
let present_mode = if modes.contains(&VK_PRESENT_MODE_MAILBOX_KHR) {
VK_PRESENT_MODE_MAILBOX_KHR
} else {
VK_PRESENT_MODE_FIFO_KHR
};
let old_swapchain = self.swapchain;
let create_info = VkSwapchainCreateInfoKHR {
s_type: VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
p_next: ptr::null(),
flags: 0,
surface: self.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,
image_sharing_mode: VK_SHARING_MODE_EXCLUSIVE,
queue_family_index_count: 0,
p_queue_family_indices: ptr::null(),
pre_transform: caps.current_transform,
composite_alpha: VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
present_mode,
clipped: 1,
old_swapchain,
};
let mut swapchain: VkSwapchainKHR = 0;
let result = unsafe {
(self.fns.create_swapchain_khr)(self.device, &create_info, ptr::null(), &mut swapchain)
};
if old_swapchain != 0 {
unsafe { (self.fns.destroy_swapchain_khr)(self.device, old_swapchain, ptr::null()) };
}
if result != VK_SUCCESS {
return Err(format!("vkCreateSwapchainKHR failed: VkResult {result}"));
}
let mut image_count_out: u32 = 0;
unsafe {
(self.fns.get_swapchain_images_khr)(
self.device,
swapchain,
&mut image_count_out,
ptr::null_mut(),
)
};
let mut images = vec![0u64; image_count_out as usize];
unsafe {
(self.fns.get_swapchain_images_khr)(
self.device,
swapchain,
&mut image_count_out,
images.as_mut_ptr(),
)
};
self.swapchain = swapchain;
self.images = images;
self.image_format = chosen_format.format;
self.extent = extent;
Ok(())
}
pub fn device_handles(&self) -> (*mut c_void, *mut c_void, *mut c_void, u32, u32, *mut c_void) {
(
self.instance,
self.phys_device,
self.device,
self.queue_family_index,
1,
self.fns.get_instance_proc_addr as *mut c_void,
)
}
pub fn image_usage(&self) -> u32 {
self.image_usage
}
pub fn is_hdr(&self) -> bool {
self.hdr.load(Ordering::Acquire)
}
pub fn enabled_device_extension_ptrs(&self) -> Vec<*const i8> {
self.enabled_device_extensions
.iter()
.map(|e| e.as_ptr())
.collect()
}
pub fn resize(&mut self, width: i32, height: i32) -> Result<(), String> {
let width = width.max(2) as u32;
let height = height.max(2) as u32;
if self.extent.width == width && self.extent.height == height {
return Ok(());
}
unsafe { (self.fns.device_wait_idle)(self.device) };
self.create_swapchain(width, height)
}
pub fn render_and_present<F>(&mut self, render: F) -> Result<(), String>
where
F: FnMut(u64, i32, u32, u32, u64, u64) -> Result<i32, String>,
{
let result = self.render_and_present_inner(render);
if result.is_err() {
self.recover_after_render_error();
}
result
}
fn recover_after_render_error(&mut self) {
unsafe {
(self.fns.device_wait_idle)(self.device);
for sem in [
self.acquire_semaphore,
self.render_done_semaphore,
self.transition_semaphore,
] {
if sem != 0 {
(self.fns.destroy_semaphore)(self.device, sem, ptr::null());
}
}
if self.in_flight_fence != 0 {
(self.fns.destroy_fence)(self.device, self.in_flight_fence, ptr::null());
}
}
self.acquire_semaphore = 0;
self.render_done_semaphore = 0;
self.transition_semaphore = 0;
self.in_flight_fence = 0;
let extent = self.extent;
let _ = self.create_semaphores();
let _ = self.create_swapchain(extent.width, extent.height);
}
fn render_and_present_inner<F>(&mut self, mut render: F) -> Result<(), String>
where
F: FnMut(u64, i32, u32, u32, u64, u64) -> Result<i32, String>,
{
unsafe {
let wait_result =
(self.fns.wait_for_fences)(self.device, 1, &self.in_flight_fence, 1, u64::MAX);
if wait_result != VK_SUCCESS {
return Err(format!("vkWaitForFences failed: {wait_result}"));
}
let reset_result = (self.fns.reset_fences)(self.device, 1, &self.in_flight_fence);
if reset_result != VK_SUCCESS {
return Err(format!("vkResetFences failed: {reset_result}"));
}
}
let mut image_index: u32 = 0;
let result = unsafe {
(self.fns.acquire_next_image_khr)(
self.device,
self.swapchain,
100_000_000,
self.acquire_semaphore,
ptr::null_mut(),
&mut image_index,
)
};
if result == VK_TIMEOUT {
return Ok(());
}
if result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR {
return Err(format!("vkAcquireNextImageKHR failed: VkResult {result}"));
}
let image = self.images[image_index as usize];
let out_layout = render(
image,
self.image_format,
self.extent.width,
self.extent.height,
self.acquire_semaphore,
self.render_done_semaphore,
)?;
unsafe {
(self.fns.reset_command_buffer)(self.command_buffer, 0);
let begin_info = VkCommandBufferBeginInfo {
s_type: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
p_next: ptr::null(),
flags: VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
p_inheritance_info: ptr::null(),
};
let result = (self.fns.begin_command_buffer)(self.command_buffer, &begin_info);
if result != VK_SUCCESS {
return Err(format!("vkBeginCommandBuffer failed: VkResult {result}"));
}
let barrier = VkImageMemoryBarrier {
s_type: VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
p_next: ptr::null(),
src_access_mask: VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
dst_access_mask: 0,
old_layout: out_layout,
new_layout: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
src_queue_family_index: VK_QUEUE_FAMILY_IGNORED,
dst_queue_family_index: VK_QUEUE_FAMILY_IGNORED,
image,
subresource_range: VkImageSubresourceRange {
aspect_mask: VK_IMAGE_ASPECT_COLOR_BIT,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
},
};
(self.fns.cmd_pipeline_barrier)(
self.command_buffer,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0,
0,
ptr::null(),
0,
ptr::null(),
1,
&barrier,
);
let result = (self.fns.end_command_buffer)(self.command_buffer);
if result != VK_SUCCESS {
return Err(format!("vkEndCommandBuffer failed: VkResult {result}"));
}
let wait_stage: u32 = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
let submit_info = VkSubmitInfo {
s_type: VK_STRUCTURE_TYPE_SUBMIT_INFO,
p_next: ptr::null(),
wait_semaphore_count: 1,
p_wait_semaphores: &self.render_done_semaphore,
p_wait_dst_stage_mask: &wait_stage,
command_buffer_count: 1,
p_command_buffers: &self.command_buffer,
signal_semaphore_count: 1,
p_signal_semaphores: &self.transition_semaphore,
};
let result = (self.fns.queue_submit)(self.queue, 1, &submit_info, self.in_flight_fence);
if result != VK_SUCCESS {
return Err(format!("vkQueueSubmit failed: VkResult {result}"));
}
}
let present_info = VkPresentInfoKHR {
s_type: VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
p_next: ptr::null(),
wait_semaphore_count: 1,
p_wait_semaphores: &self.transition_semaphore,
swapchain_count: 1,
p_swapchains: &self.swapchain,
p_image_indices: &image_index,
p_results: ptr::null_mut(),
};
let result = unsafe { (self.fns.queue_present_khr)(self.queue, &present_info) };
if result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR {
return Err(format!("vkQueuePresentKHR failed: VkResult {result}"));
}
Ok(())
}
}
impl Drop for VulkanContext {
fn drop(&mut self) {
unsafe {
(self.fns.device_wait_idle)(self.device);
if self.acquire_semaphore != 0 {
(self.fns.destroy_semaphore)(self.device, self.acquire_semaphore, ptr::null());
}
if self.render_done_semaphore != 0 {
(self.fns.destroy_semaphore)(self.device, self.render_done_semaphore, ptr::null());
}
if self.transition_semaphore != 0 {
(self.fns.destroy_semaphore)(self.device, self.transition_semaphore, ptr::null());
}
if self.in_flight_fence != 0 {
(self.fns.destroy_fence)(self.device, self.in_flight_fence, ptr::null());
}
if !self.command_pool.is_null() {
(self.fns.destroy_command_pool)(self.device, self.command_pool, ptr::null());
}
if self.swapchain != 0 {
(self.fns.destroy_swapchain_khr)(self.device, self.swapchain, ptr::null());
}
(self.fns.destroy_device)(self.device, ptr::null());
(self.fns.destroy_surface_khr)(self.instance, self.surface, ptr::null());
(self.fns.destroy_instance)(self.instance, ptr::null());
if !self.owned_xlib_display.is_null() {
x11::xlib::XCloseDisplay(self.owned_xlib_display as *mut x11::xlib::Display);
}
}
}
}
mod init;
mod render;
mod swapchain;

View file

@ -0,0 +1,423 @@
use super::*;
impl VulkanContext {
pub fn new(native_surface: NativeSurface, width: i32, height: i32) -> Result<Self, String> {
let module = dlopen_first(&["libvulkan.so.1", "libvulkan.so"])
.ok_or("libvulkan.so.1 not found (no Vulkan-capable driver installed?)")?;
let get_instance_proc_addr: PfnGetInstanceProcAddr =
unsafe { std::mem::transmute(dlsym_typed(module, "vkGetInstanceProcAddr")?) };
let create_instance: PfnCreateInstance = unsafe {
std::mem::transmute(get_instance_proc(
get_instance_proc_addr,
ptr::null_mut(),
"vkCreateInstance",
)?)
};
let app_name = CString::new("fluxa-desktop").unwrap();
let app_info = VkApplicationInfo {
s_type: VK_STRUCTURE_TYPE_APPLICATION_INFO,
p_next: ptr::null(),
p_application_name: app_name.as_ptr(),
application_version: 0,
p_engine_name: app_name.as_ptr(),
engine_version: 0,
api_version: VK_API_VERSION_1_3,
};
let surface_ext_name = match native_surface {
NativeSurface::Xlib { .. } => "VK_KHR_xlib_surface",
NativeSurface::Wayland { .. } => "VK_KHR_wayland_surface",
};
let extensions = [
CString::new("VK_KHR_surface").unwrap(),
CString::new(surface_ext_name).unwrap(),
CString::new("VK_EXT_swapchain_colorspace").unwrap(),
];
let extension_ptrs: Vec<*const i8> = extensions.iter().map(|e| e.as_ptr()).collect();
let validation_layer = CString::new("VK_LAYER_KHRONOS_validation").unwrap();
let want_validation = std::env::var_os("FLUXA_VULKAN_VALIDATION").is_some();
let layer_ptrs: Vec<*const i8> = if want_validation {
vec![validation_layer.as_ptr()]
} else {
vec![]
};
let instance_create_info = VkInstanceCreateInfo {
s_type: VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
p_next: ptr::null(),
flags: 0,
p_application_info: &app_info,
enabled_layer_count: layer_ptrs.len() as u32,
pp_enabled_layer_names: layer_ptrs.as_ptr(),
enabled_extension_count: extension_ptrs.len() as u32,
pp_enabled_extension_names: extension_ptrs.as_ptr(),
};
let mut instance: VkInstance = ptr::null_mut();
let result = unsafe { create_instance(&instance_create_info, ptr::null(), &mut instance) };
if result != VK_SUCCESS {
return Err(format!("vkCreateInstance failed: VkResult {result}"));
}
macro_rules! iproc {
($name:expr_2021) => {
unsafe {
std::mem::transmute(get_instance_proc(get_instance_proc_addr, instance, $name)?)
}
};
}
let destroy_instance: PfnDestroyInstance = iproc!("vkDestroyInstance");
let enumerate_physical_devices: PfnEnumeratePhysicalDevices =
iproc!("vkEnumeratePhysicalDevices");
let get_physical_device_properties: PfnGetPhysicalDeviceProperties =
iproc!("vkGetPhysicalDeviceProperties");
let get_queue_family_properties: PfnGetPhysicalDeviceQueueFamilyProperties =
iproc!("vkGetPhysicalDeviceQueueFamilyProperties");
let get_surface_support_khr: PfnGetPhysicalDeviceSurfaceSupportKHR =
iproc!("vkGetPhysicalDeviceSurfaceSupportKHR");
let get_surface_capabilities_khr: PfnGetPhysicalDeviceSurfaceCapabilitiesKHR =
iproc!("vkGetPhysicalDeviceSurfaceCapabilitiesKHR");
let get_surface_formats_khr: PfnGetPhysicalDeviceSurfaceFormatsKHR =
iproc!("vkGetPhysicalDeviceSurfaceFormatsKHR");
let get_surface_present_modes_khr: PfnGetPhysicalDeviceSurfacePresentModesKHR =
iproc!("vkGetPhysicalDeviceSurfacePresentModesKHR");
let destroy_surface_khr: PfnDestroySurfaceKHR = iproc!("vkDestroySurfaceKHR");
let create_device: PfnCreateDevice = iproc!("vkCreateDevice");
let enumerate_device_extension_properties: PfnEnumerateDeviceExtensionProperties =
iproc!("vkEnumerateDeviceExtensionProperties");
let mut surface: VkSurfaceKHR = 0;
let mut owned_xlib_display: *mut c_void = ptr::null_mut();
let result = match native_surface {
NativeSurface::Xlib { display, window } => {
let create_xlib_surface_khr: PfnCreateXlibSurfaceKHR =
iproc!("vkCreateXlibSurfaceKHR");
// The render loop runs on its own thread, but GTK's Display
// connection is not thread-safe (XInitThreads is never called).
// A private connection keeps Mesa's WSI traffic off GTK's.
owned_xlib_display = unsafe { x11::xlib::XOpenDisplay(ptr::null()) as *mut c_void };
let dpy = if owned_xlib_display.is_null() {
display
} else {
owned_xlib_display
};
let create_info = VkXlibSurfaceCreateInfoKHR {
s_type: VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
p_next: ptr::null(),
flags: 0,
dpy,
window,
};
unsafe {
create_xlib_surface_khr(instance, &create_info, ptr::null(), &mut surface)
}
}
NativeSurface::Wayland {
display,
surface: wl_surface,
} => {
let create_wayland_surface_khr: PfnCreateWaylandSurfaceKHR =
iproc!("vkCreateWaylandSurfaceKHR");
let create_info = VkWaylandSurfaceCreateInfoKHR {
s_type: VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR,
p_next: ptr::null(),
flags: 0,
display,
surface: wl_surface,
};
unsafe {
create_wayland_surface_khr(instance, &create_info, ptr::null(), &mut surface)
}
}
};
if result != VK_SUCCESS {
unsafe { destroy_instance(instance, ptr::null()) };
return Err(format!("vkCreate*SurfaceKHR failed: VkResult {result}"));
}
let mut device_count: u32 = 0;
unsafe { enumerate_physical_devices(instance, &mut device_count, ptr::null_mut()) };
if device_count == 0 {
unsafe {
destroy_surface_khr(instance, surface, ptr::null());
destroy_instance(instance, ptr::null());
}
return Err("vkEnumeratePhysicalDevices found no GPUs".into());
}
let mut physical_devices = vec![ptr::null_mut(); device_count as usize];
unsafe {
enumerate_physical_devices(instance, &mut device_count, physical_devices.as_mut_ptr())
};
const VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: u32 = 2;
const VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: u32 = 1;
const VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: u32 = 3;
fn device_type_rank(device_type: u32) -> u32 {
match device_type {
VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU => 0,
VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU => 1,
VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU => 2,
_ => 3,
}
}
let mut candidates: Vec<(VkPhysicalDevice, u32, u32)> = Vec::new();
for &phys_device in &physical_devices {
let mut family_count: u32 = 0;
unsafe { get_queue_family_properties(phys_device, &mut family_count, ptr::null_mut()) };
let mut families: Vec<VkQueueFamilyProperties> = (0..family_count)
.map(|_| VkQueueFamilyProperties {
queue_flags: 0,
queue_count: 0,
timestamp_valid_bits: 0,
min_image_transfer_granularity: [0; 3],
})
.collect();
unsafe {
get_queue_family_properties(phys_device, &mut family_count, families.as_mut_ptr())
};
for (index, family) in families.iter().enumerate() {
if family.queue_flags & VK_QUEUE_GRAPHICS_BIT == 0 {
continue;
}
let mut present_supported: u32 = 0;
unsafe {
get_surface_support_khr(
phys_device,
index as u32,
surface,
&mut present_supported,
)
};
if present_supported != 0 {
let mut props = [0u8; 1024];
unsafe {
get_physical_device_properties(
phys_device,
props.as_mut_ptr() as *mut c_void,
)
};
let device_type = u32::from_ne_bytes(props[16..20].try_into().unwrap());
candidates.push((phys_device, index as u32, device_type));
break;
}
}
}
candidates.sort_by_key(|&(_, _, device_type)| device_type_rank(device_type));
let chosen = candidates
.first()
.map(|&(phys_device, queue_family_index, _)| (phys_device, queue_family_index));
let Some((phys_device, queue_family_index)) = chosen else {
unsafe {
destroy_surface_khr(instance, surface, ptr::null());
destroy_instance(instance, ptr::null());
}
return Err(
"no Vulkan queue family supports both graphics and presenting to this window"
.to_string(),
);
};
let queue_priority: f32 = 1.0;
let queue_create_info = VkDeviceQueueCreateInfo {
s_type: VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
p_next: ptr::null(),
flags: 0,
queue_family_index,
queue_count: 1,
p_queue_priorities: &queue_priority,
};
let mut ext_count: u32 = 0;
unsafe {
enumerate_device_extension_properties(
phys_device,
ptr::null(),
&mut ext_count,
ptr::null_mut(),
)
};
let mut supported_extensions = vec![
VkExtensionProperties {
extension_name: [0; 256],
spec_version: 0,
};
ext_count as usize
];
unsafe {
enumerate_device_extension_properties(
phys_device,
ptr::null(),
&mut ext_count,
supported_extensions.as_mut_ptr(),
)
};
let device_extensions: Vec<CString> = supported_extensions
.iter()
.filter_map(|ext| {
let nul = ext.extension_name.iter().position(|&b| b == 0)?;
CString::new(&ext.extension_name[..nul]).ok()
})
.collect();
let device_extension_ptrs: Vec<*const i8> =
device_extensions.iter().map(|e| e.as_ptr()).collect();
let mut synchronization2_features = VkPhysicalDeviceSynchronization2Features {
s_type: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES,
p_next: ptr::null_mut(),
synchronization2: 1,
};
let mut timeline_semaphore_features = VkPhysicalDeviceTimelineSemaphoreFeatures {
s_type: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES,
p_next: &mut synchronization2_features as *mut _ as *mut c_void,
timeline_semaphore: 1,
};
let device_create_info = VkDeviceCreateInfo {
s_type: VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
p_next: &mut timeline_semaphore_features as *mut _ as *const c_void,
flags: 0,
queue_create_info_count: 1,
p_queue_create_infos: &queue_create_info,
enabled_layer_count: 0,
pp_enabled_layer_names: ptr::null(),
enabled_extension_count: device_extension_ptrs.len() as u32,
pp_enabled_extension_names: device_extension_ptrs.as_ptr(),
p_enabled_features: ptr::null(),
};
let mut device: VkDevice = ptr::null_mut();
let result =
unsafe { create_device(phys_device, &device_create_info, ptr::null(), &mut device) };
if result != VK_SUCCESS {
unsafe {
destroy_surface_khr(instance, surface, ptr::null());
destroy_instance(instance, ptr::null());
}
return Err(format!("vkCreateDevice failed: VkResult {result}"));
}
macro_rules! dproc {
($name:expr_2021) => {
unsafe {
std::mem::transmute(get_instance_proc(get_instance_proc_addr, instance, $name)?)
}
};
}
let destroy_device: PfnDestroyDevice = dproc!("vkDestroyDevice");
let get_device_queue: PfnGetDeviceQueue = dproc!("vkGetDeviceQueue");
let create_swapchain_khr: PfnCreateSwapchainKHR = dproc!("vkCreateSwapchainKHR");
let destroy_swapchain_khr: PfnDestroySwapchainKHR = dproc!("vkDestroySwapchainKHR");
let get_swapchain_images_khr: PfnGetSwapchainImagesKHR = dproc!("vkGetSwapchainImagesKHR");
let acquire_next_image_khr: PfnAcquireNextImageKHR = dproc!("vkAcquireNextImageKHR");
let queue_present_khr: PfnQueuePresentKHR = dproc!("vkQueuePresentKHR");
let create_semaphore: PfnCreateSemaphore = dproc!("vkCreateSemaphore");
let destroy_semaphore: PfnDestroySemaphore = dproc!("vkDestroySemaphore");
let create_fence: PfnCreateFence = dproc!("vkCreateFence");
let destroy_fence: PfnDestroyFence = dproc!("vkDestroyFence");
let wait_for_fences: PfnWaitForFences = dproc!("vkWaitForFences");
let reset_fences: PfnResetFences = dproc!("vkResetFences");
let device_wait_idle: PfnDeviceWaitIdle = dproc!("vkDeviceWaitIdle");
let create_command_pool: PfnCreateCommandPool = dproc!("vkCreateCommandPool");
let destroy_command_pool: PfnDestroyCommandPool = dproc!("vkDestroyCommandPool");
let allocate_command_buffers: PfnAllocateCommandBuffers =
dproc!("vkAllocateCommandBuffers");
let reset_command_buffer: PfnResetCommandBuffer = dproc!("vkResetCommandBuffer");
let begin_command_buffer: PfnBeginCommandBuffer = dproc!("vkBeginCommandBuffer");
let end_command_buffer: PfnEndCommandBuffer = dproc!("vkEndCommandBuffer");
let cmd_pipeline_barrier: PfnCmdPipelineBarrier = dproc!("vkCmdPipelineBarrier");
let queue_submit: PfnQueueSubmit = dproc!("vkQueueSubmit");
let mut queue: VkQueue = ptr::null_mut();
unsafe { get_device_queue(device, queue_family_index, 0, &mut queue) };
let fns = VkFns {
get_instance_proc_addr,
destroy_instance,
get_physical_device_surface_capabilities_khr: get_surface_capabilities_khr,
get_physical_device_surface_formats_khr: get_surface_formats_khr,
get_physical_device_surface_present_modes_khr: get_surface_present_modes_khr,
destroy_surface_khr,
destroy_device,
create_swapchain_khr,
destroy_swapchain_khr,
get_swapchain_images_khr,
acquire_next_image_khr,
queue_present_khr,
create_semaphore,
destroy_semaphore,
create_fence,
destroy_fence,
wait_for_fences,
reset_fences,
device_wait_idle,
create_command_pool,
destroy_command_pool,
allocate_command_buffers,
reset_command_buffer,
begin_command_buffer,
end_command_buffer,
cmd_pipeline_barrier,
queue_submit,
};
let mut ctx = Self {
fns,
instance,
phys_device,
device,
queue,
queue_family_index,
surface,
swapchain: 0,
images: Vec::new(),
image_format: VK_FORMAT_B8G8R8A8_UNORM,
image_usage: VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
extent: VkExtent2D {
width: width.max(2) as u32,
height: height.max(2) as u32,
},
acquire_semaphore: 0,
render_done_semaphore: 0,
transition_semaphore: 0,
in_flight_fence: 0,
command_pool: ptr::null_mut(),
command_buffer: ptr::null_mut(),
hdr: AtomicBool::new(false),
enabled_device_extensions: device_extensions,
owned_xlib_display,
};
ctx.create_swapchain(width.max(2) as u32, height.max(2) as u32)?;
ctx.create_semaphores()?;
ctx.create_command_buffer()?;
Ok(ctx)
}
}
impl Drop for VulkanContext {
fn drop(&mut self) {
unsafe {
(self.fns.device_wait_idle)(self.device);
if self.acquire_semaphore != 0 {
(self.fns.destroy_semaphore)(self.device, self.acquire_semaphore, ptr::null());
}
if self.render_done_semaphore != 0 {
(self.fns.destroy_semaphore)(self.device, self.render_done_semaphore, ptr::null());
}
if self.transition_semaphore != 0 {
(self.fns.destroy_semaphore)(self.device, self.transition_semaphore, ptr::null());
}
if self.in_flight_fence != 0 {
(self.fns.destroy_fence)(self.device, self.in_flight_fence, ptr::null());
}
if !self.command_pool.is_null() {
(self.fns.destroy_command_pool)(self.device, self.command_pool, ptr::null());
}
if self.swapchain != 0 {
(self.fns.destroy_swapchain_khr)(self.device, self.swapchain, ptr::null());
}
(self.fns.destroy_device)(self.device, ptr::null());
(self.fns.destroy_surface_khr)(self.instance, self.surface, ptr::null());
(self.fns.destroy_instance)(self.instance, ptr::null());
if !self.owned_xlib_display.is_null() {
x11::xlib::XCloseDisplay(self.owned_xlib_display as *mut x11::xlib::Display);
}
}
}
}

View file

@ -0,0 +1,190 @@
use super::*;
impl VulkanContext {
pub fn device_handles(&self) -> (*mut c_void, *mut c_void, *mut c_void, u32, u32, *mut c_void) {
(
self.instance,
self.phys_device,
self.device,
self.queue_family_index,
1,
self.fns.get_instance_proc_addr as *mut c_void,
)
}
pub fn image_usage(&self) -> u32 {
self.image_usage
}
pub fn is_hdr(&self) -> bool {
self.hdr.load(Ordering::Acquire)
}
pub fn enabled_device_extension_ptrs(&self) -> Vec<*const i8> {
self.enabled_device_extensions
.iter()
.map(|e| e.as_ptr())
.collect()
}
pub fn render_and_present<F>(&mut self, render: F) -> Result<(), String>
where
F: FnMut(u64, i32, u32, u32, u64, u64) -> Result<i32, String>,
{
let result = self.render_and_present_inner(render);
if result.is_err() {
self.recover_after_render_error();
}
result
}
fn recover_after_render_error(&mut self) {
unsafe {
(self.fns.device_wait_idle)(self.device);
for sem in [
self.acquire_semaphore,
self.render_done_semaphore,
self.transition_semaphore,
] {
if sem != 0 {
(self.fns.destroy_semaphore)(self.device, sem, ptr::null());
}
}
if self.in_flight_fence != 0 {
(self.fns.destroy_fence)(self.device, self.in_flight_fence, ptr::null());
}
}
self.acquire_semaphore = 0;
self.render_done_semaphore = 0;
self.transition_semaphore = 0;
self.in_flight_fence = 0;
let extent = self.extent;
let _ = self.create_semaphores();
let _ = self.create_swapchain(extent.width, extent.height);
}
fn render_and_present_inner<F>(&mut self, mut render: F) -> Result<(), String>
where
F: FnMut(u64, i32, u32, u32, u64, u64) -> Result<i32, String>,
{
unsafe {
let wait_result =
(self.fns.wait_for_fences)(self.device, 1, &self.in_flight_fence, 1, u64::MAX);
if wait_result != VK_SUCCESS {
return Err(format!("vkWaitForFences failed: {wait_result}"));
}
let reset_result = (self.fns.reset_fences)(self.device, 1, &self.in_flight_fence);
if reset_result != VK_SUCCESS {
return Err(format!("vkResetFences failed: {reset_result}"));
}
}
let mut image_index: u32 = 0;
let result = unsafe {
(self.fns.acquire_next_image_khr)(
self.device,
self.swapchain,
100_000_000,
self.acquire_semaphore,
ptr::null_mut(),
&mut image_index,
)
};
if result == VK_TIMEOUT {
return Ok(());
}
if result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR {
return Err(format!("vkAcquireNextImageKHR failed: VkResult {result}"));
}
let image = self.images[image_index as usize];
let out_layout = render(
image,
self.image_format,
self.extent.width,
self.extent.height,
self.acquire_semaphore,
self.render_done_semaphore,
)?;
unsafe {
(self.fns.reset_command_buffer)(self.command_buffer, 0);
let begin_info = VkCommandBufferBeginInfo {
s_type: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
p_next: ptr::null(),
flags: VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
p_inheritance_info: ptr::null(),
};
let result = (self.fns.begin_command_buffer)(self.command_buffer, &begin_info);
if result != VK_SUCCESS {
return Err(format!("vkBeginCommandBuffer failed: VkResult {result}"));
}
let barrier = VkImageMemoryBarrier {
s_type: VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
p_next: ptr::null(),
src_access_mask: VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
dst_access_mask: 0,
old_layout: out_layout,
new_layout: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
src_queue_family_index: VK_QUEUE_FAMILY_IGNORED,
dst_queue_family_index: VK_QUEUE_FAMILY_IGNORED,
image,
subresource_range: VkImageSubresourceRange {
aspect_mask: VK_IMAGE_ASPECT_COLOR_BIT,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
},
};
(self.fns.cmd_pipeline_barrier)(
self.command_buffer,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0,
0,
ptr::null(),
0,
ptr::null(),
1,
&barrier,
);
let result = (self.fns.end_command_buffer)(self.command_buffer);
if result != VK_SUCCESS {
return Err(format!("vkEndCommandBuffer failed: VkResult {result}"));
}
let wait_stage: u32 = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
let submit_info = VkSubmitInfo {
s_type: VK_STRUCTURE_TYPE_SUBMIT_INFO,
p_next: ptr::null(),
wait_semaphore_count: 1,
p_wait_semaphores: &self.render_done_semaphore,
p_wait_dst_stage_mask: &wait_stage,
command_buffer_count: 1,
p_command_buffers: &self.command_buffer,
signal_semaphore_count: 1,
p_signal_semaphores: &self.transition_semaphore,
};
let result = (self.fns.queue_submit)(self.queue, 1, &submit_info, self.in_flight_fence);
if result != VK_SUCCESS {
return Err(format!("vkQueueSubmit failed: VkResult {result}"));
}
}
let present_info = VkPresentInfoKHR {
s_type: VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
p_next: ptr::null(),
wait_semaphore_count: 1,
p_wait_semaphores: &self.transition_semaphore,
swapchain_count: 1,
p_swapchains: &self.swapchain,
p_image_indices: &image_index,
p_results: ptr::null_mut(),
};
let result = unsafe { (self.fns.queue_present_khr)(self.queue, &present_info) };
if result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR {
return Err(format!("vkQueuePresentKHR failed: VkResult {result}"));
}
Ok(())
}
}

View file

@ -0,0 +1,264 @@
use super::*;
impl VulkanContext {
pub(super) fn create_semaphores(&mut self) -> Result<(), String> {
let info = VkSemaphoreCreateInfo {
s_type: VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
p_next: ptr::null(),
flags: 0,
};
let mut acquire: VkSemaphore = 0;
let mut render_done: VkSemaphore = 0;
let mut transition: VkSemaphore = 0;
let r1 =
unsafe { (self.fns.create_semaphore)(self.device, &info, ptr::null(), &mut acquire) };
let r2 = unsafe {
(self.fns.create_semaphore)(self.device, &info, ptr::null(), &mut render_done)
};
let r3 = unsafe {
(self.fns.create_semaphore)(self.device, &info, ptr::null(), &mut transition)
};
if r1 != VK_SUCCESS || r2 != VK_SUCCESS || r3 != VK_SUCCESS {
return Err(format!("vkCreateSemaphore failed: {r1}/{r2}/{r3}"));
}
self.acquire_semaphore = acquire;
self.render_done_semaphore = render_done;
self.transition_semaphore = transition;
let fence_info = VkFenceCreateInfo {
s_type: VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
p_next: ptr::null(),
flags: VK_FENCE_CREATE_SIGNALED_BIT,
};
let mut fence: VkFence = 0;
let r4 =
unsafe { (self.fns.create_fence)(self.device, &fence_info, ptr::null(), &mut fence) };
if r4 != VK_SUCCESS {
return Err(format!("vkCreateFence failed: {r4}"));
}
self.in_flight_fence = fence;
Ok(())
}
pub(super) fn create_command_buffer(&mut self) -> Result<(), String> {
let pool_info = VkCommandPoolCreateInfo {
s_type: VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
p_next: ptr::null(),
flags: VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
queue_family_index: self.queue_family_index,
};
let mut pool: VkCommandPool = ptr::null_mut();
let result = unsafe {
(self.fns.create_command_pool)(self.device, &pool_info, ptr::null(), &mut pool)
};
if result != VK_SUCCESS {
return Err(format!("vkCreateCommandPool failed: VkResult {result}"));
}
let alloc_info = VkCommandBufferAllocateInfo {
s_type: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
p_next: ptr::null(),
command_pool: pool,
level: VK_COMMAND_BUFFER_LEVEL_PRIMARY,
command_buffer_count: 1,
};
let mut command_buffer: VkCommandBuffer = ptr::null_mut();
let result = unsafe {
(self.fns.allocate_command_buffers)(self.device, &alloc_info, &mut command_buffer)
};
if result != VK_SUCCESS {
unsafe { (self.fns.destroy_command_pool)(self.device, pool, ptr::null()) };
return Err(format!(
"vkAllocateCommandBuffers failed: VkResult {result}"
));
}
self.command_pool = pool;
self.command_buffer = command_buffer;
Ok(())
}
pub(super) fn create_swapchain(&mut self, width: u32, height: u32) -> Result<(), String> {
let mut caps = VkSurfaceCapabilitiesKHR {
min_image_count: 0,
max_image_count: 0,
current_extent: VkExtent2D::default(),
min_image_extent: VkExtent2D::default(),
max_image_extent: VkExtent2D::default(),
max_image_array_layers: 0,
supported_transforms: 0,
current_transform: 0,
supported_composite_alpha: 0,
supported_usage_flags: 0,
};
let result = unsafe {
(self.fns.get_physical_device_surface_capabilities_khr)(
self.phys_device,
self.surface,
&mut caps,
)
};
if result != VK_SUCCESS {
return Err(format!(
"vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: {result}"
));
}
let mut format_count: u32 = 0;
unsafe {
(self.fns.get_physical_device_surface_formats_khr)(
self.phys_device,
self.surface,
&mut format_count,
ptr::null_mut(),
)
};
let mut formats = vec![
VkSurfaceFormatKHR {
format: 0,
color_space: 0
};
format_count as usize
];
unsafe {
(self.fns.get_physical_device_surface_formats_khr)(
self.phys_device,
self.surface,
&mut format_count,
formats.as_mut_ptr(),
)
};
let hdr_format: Option<&VkSurfaceFormatKHR> = None;
let (chosen_format, hdr) = if let Some(f) = hdr_format {
(*f, true)
} else {
let f = formats
.iter()
.find(|f| {
f.format == VK_FORMAT_B8G8R8A8_UNORM
&& f.color_space == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
})
.or_else(|| {
formats
.iter()
.find(|f| f.format == VK_FORMAT_B8G8R8A8_UNORM)
})
.or_else(|| formats.first())
.copied()
.unwrap_or(VkSurfaceFormatKHR {
format: VK_FORMAT_B8G8R8A8_UNORM,
color_space: VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
});
(f, false)
};
self.hdr.store(hdr, Ordering::Release);
let mut image_count = caps.min_image_count + 1;
if caps.max_image_count > 0 && image_count > caps.max_image_count {
image_count = caps.max_image_count;
}
let extent = if caps.current_extent.width != u32::MAX {
caps.current_extent
} else {
VkExtent2D { width, height }
};
let image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
| (caps.supported_usage_flags & VK_IMAGE_USAGE_TRANSFER_DST_BIT);
self.image_usage = image_usage;
let mut mode_count: u32 = 0;
unsafe {
(self.fns.get_physical_device_surface_present_modes_khr)(
self.phys_device,
self.surface,
&mut mode_count,
ptr::null_mut(),
)
};
let mut modes = vec![0i32; mode_count as usize];
if mode_count > 0 {
unsafe {
(self.fns.get_physical_device_surface_present_modes_khr)(
self.phys_device,
self.surface,
&mut mode_count,
modes.as_mut_ptr(),
)
};
}
let present_mode = if modes.contains(&VK_PRESENT_MODE_MAILBOX_KHR) {
VK_PRESENT_MODE_MAILBOX_KHR
} else {
VK_PRESENT_MODE_FIFO_KHR
};
let old_swapchain = self.swapchain;
let create_info = VkSwapchainCreateInfoKHR {
s_type: VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
p_next: ptr::null(),
flags: 0,
surface: self.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,
image_sharing_mode: VK_SHARING_MODE_EXCLUSIVE,
queue_family_index_count: 0,
p_queue_family_indices: ptr::null(),
pre_transform: caps.current_transform,
composite_alpha: VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
present_mode,
clipped: 1,
old_swapchain,
};
let mut swapchain: VkSwapchainKHR = 0;
let result = unsafe {
(self.fns.create_swapchain_khr)(self.device, &create_info, ptr::null(), &mut swapchain)
};
if old_swapchain != 0 {
unsafe { (self.fns.destroy_swapchain_khr)(self.device, old_swapchain, ptr::null()) };
}
if result != VK_SUCCESS {
return Err(format!("vkCreateSwapchainKHR failed: VkResult {result}"));
}
let mut image_count_out: u32 = 0;
unsafe {
(self.fns.get_swapchain_images_khr)(
self.device,
swapchain,
&mut image_count_out,
ptr::null_mut(),
)
};
let mut images = vec![0u64; image_count_out as usize];
unsafe {
(self.fns.get_swapchain_images_khr)(
self.device,
swapchain,
&mut image_count_out,
images.as_mut_ptr(),
)
};
self.swapchain = swapchain;
self.images = images;
self.image_format = chosen_format.format;
self.extent = extent;
Ok(())
}
pub fn resize(&mut self, width: i32, height: i32) -> Result<(), String> {
let width = width.max(2) as u32;
let height = height.max(2) as u32;
if self.extent.width == width && self.extent.height == height {
return Ok(());
}
unsafe { (self.fns.device_wait_idle)(self.device) };
self.create_swapchain(width, height)
}
}