pub mod dds; pub mod dmabuf; use std::{ collections::HashMap, sync::{Arc, OnceLock}, }; use glam::{Vec2, vec2}; use smallvec::SmallVec; use vulkano::{ buffer::{BufferCreateInfo, BufferUsage}, command_buffer::{CommandBufferUsage, PrimaryAutoCommandBuffer, PrimaryCommandBufferAbstract}, image::view::ImageView, memory::allocator::{AllocationCreateInfo, MemoryTypeFilter}, sync::GpuFuture, }; use wgui::gfx::WGfx; #[cfg(feature = "openvr")] use vulkano::instance::InstanceCreateFlags; use wlx_capture::frame::DrmFormat; use crate::shaders::{frag_color, frag_grid, frag_screen, frag_srgb, vert_quad}; #[cfg(feature = "openxr")] use {ash::vk, std::os::raw::c_void}; use vulkano::{ self, VulkanObject, buffer::{Buffer, BufferContents, IndexBuffer, Subbuffer}, device::{ DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, QueueFlags, physical::{PhysicalDevice, PhysicalDeviceType}, }, format::Format, instance::{Instance, InstanceCreateInfo, InstanceExtensions}, pipeline::graphics::{ color_blend::{AttachmentBlend, BlendFactor, BlendOp}, vertex_input::Vertex, }, shader::ShaderModule, }; use dmabuf::get_drm_formats; pub type Vert2Buf = Subbuffer<[Vert2Uv]>; pub type IndexBuf = IndexBuffer; #[repr(C)] #[derive(BufferContents, Vertex, Copy, Clone, Debug)] pub struct Vert2Uv { #[format(R32G32_SFLOAT)] pub in_pos: [f32; 2], #[format(R32G32_SFLOAT)] pub in_uv: [f32; 2], } pub const INDICES: [u16; 6] = [2, 1, 0, 1, 2, 3]; pub const BLEND_ALPHA: AttachmentBlend = AttachmentBlend { src_color_blend_factor: BlendFactor::SrcAlpha, dst_color_blend_factor: BlendFactor::OneMinusSrcAlpha, color_blend_op: BlendOp::Add, src_alpha_blend_factor: BlendFactor::One, dst_alpha_blend_factor: BlendFactor::One, alpha_blend_op: BlendOp::Max, }; pub struct WGfxExtras { pub shaders: HashMap<&'static str, Arc>, pub drm_formats: Vec, pub queue_capture: Option>, pub quad_verts: Vert2Buf, pub fallback_image: Arc, } impl WGfxExtras { pub fn new(gfx: Arc, queue_capture: Option>) -> anyhow::Result { let mut shaders = HashMap::new(); let shader = vert_quad::load(gfx.device.clone())?; shaders.insert("vert_quad", shader); let shader = frag_color::load(gfx.device.clone())?; shaders.insert("frag_color", shader); let shader = frag_srgb::load(gfx.device.clone())?; shaders.insert("frag_srgb", shader); let shader = frag_grid::load(gfx.device.clone())?; shaders.insert("frag_grid", shader); let shader = frag_screen::load(gfx.device.clone())?; shaders.insert("frag_screen", shader); let drm_formats = get_drm_formats(gfx.device.clone()); let vertices = [ Vert2Uv { in_pos: [0., 0.], in_uv: [0., 0.], }, Vert2Uv { in_pos: [1., 0.], in_uv: [1., 0.], }, Vert2Uv { in_pos: [0., 1.], in_uv: [0., 1.], }, Vert2Uv { in_pos: [1., 1.], in_uv: [1., 1.], }, ]; let quad_verts = Buffer::from_iter( gfx.memory_allocator.clone(), BufferCreateInfo { usage: BufferUsage::VERTEX_BUFFER, ..Default::default() }, AllocationCreateInfo { memory_type_filter: MemoryTypeFilter::PREFER_DEVICE | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE, ..Default::default() }, vertices.into_iter(), )?; let mut cmd_xfer = gfx.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?; let fallback_image = cmd_xfer.upload_image(1, 1, Format::R8G8B8A8_SRGB, &[255, 0, 255, 255])?; cmd_xfer.build_and_execute_now()?; let fallback_image = ImageView::new_default(fallback_image)?; Ok(Self { shaders, drm_formats, queue_capture, quad_verts, fallback_image, }) } } const fn get_dmabuf_extensions() -> DeviceExtensions { DeviceExtensions { khr_external_memory: true, khr_external_memory_fd: true, ext_external_memory_dma_buf: true, ..DeviceExtensions::empty() } } static VULKAN_LIBRARY: OnceLock> = OnceLock::new(); fn get_vulkan_library() -> &'static Arc { VULKAN_LIBRARY.get_or_init(|| vulkano::VulkanLibrary::new().unwrap()) // want panic } #[cfg(feature = "openxr")] unsafe extern "system" fn get_instance_proc_addr( instance: openxr::sys::platform::VkInstance, name: *const std::ffi::c_char, ) -> Option { use vulkano::Handle; let instance = ash::vk::Instance::from_raw(instance as _); let library = get_vulkan_library(); unsafe { library.get_instance_proc_addr(instance, name) } } #[cfg(feature = "openxr")] #[allow(clippy::too_many_lines)] pub fn init_openxr_graphics( xr_instance: openxr::Instance, system: openxr::SystemId, ) -> anyhow::Result<(Arc, WGfxExtras)> { use std::ffi::{self, CString}; use vulkano::{Handle, Version}; let instance_extensions = InstanceExtensions { khr_get_physical_device_properties2: true, ..InstanceExtensions::empty() }; let instance_extensions_raw = instance_extensions .into_iter() .filter_map(|(name, enabled)| { if enabled { Some(ffi::CString::new(name).unwrap().into_raw().cast_const()) // want panic } else { None } }) .collect::>(); let vk_target_version = vk::make_api_version(0, 1, 3, 0); let target_version = vulkano::Version::V1_3; let library = get_vulkan_library(); let vk_app_info_raw = vk::ApplicationInfo::default() .application_version(0) .engine_version(0) .api_version(vk_target_version); let instance = unsafe { let vk_instance = xr_instance .create_vulkan_instance( system, get_instance_proc_addr, std::ptr::from_ref( &vk::InstanceCreateInfo::default() .application_info(&vk_app_info_raw) .enabled_extension_names(&instance_extensions_raw), ) .cast(), ) .expect("XR error creating Vulkan instance") .map_err(vk::Result::from_raw) .expect("Vulkan error creating Vulkan instance"); Instance::from_handle( library.clone(), ash::vk::Instance::from_raw(vk_instance as _), InstanceCreateInfo { application_version: Version::major_minor(0, 0), engine_version: Version::major_minor(0, 0), max_api_version: Some(Version::V1_3), enabled_extensions: instance_extensions, ..Default::default() }, ) }; let physical_device = unsafe { PhysicalDevice::from_handle( instance.clone(), vk::PhysicalDevice::from_raw( xr_instance.vulkan_graphics_device(system, instance.handle().as_raw() as _)? as _, ), ) }?; let vk_device_properties = physical_device.properties(); assert!( (vk_device_properties.api_version >= target_version), "Vulkan physical device doesn't support Vulkan {target_version}" ); log::info!( "Using vkPhysicalDevice: {}", physical_device.properties().device_name, ); let queue_families = try_all_queue_families(physical_device.as_ref()) .expect("vkPhysicalDevice does not have a GRAPHICS / TRANSFER queue."); let mut device_extensions = DeviceExtensions::empty(); let dmabuf_extensions = get_dmabuf_extensions(); if physical_device .supported_extensions() .contains(&dmabuf_extensions) { device_extensions = device_extensions.union(&dmabuf_extensions); device_extensions.ext_image_drm_format_modifier = physical_device .supported_extensions() .ext_image_drm_format_modifier; } let device_extensions_raw = device_extensions .into_iter() .filter_map(|(name, enabled)| { if enabled { Some(ffi::CString::new(name).unwrap().into_raw().cast_const()) // want panic } else { None } }) .collect::>(); // If adding anything here, also add to the native device_create_info below let features = DeviceFeatures { dynamic_rendering: true, descriptor_binding_sampled_image_update_after_bind: true, ..DeviceFeatures::empty() }; let queue_create_infos = queue_families .iter() .map(|fam| { vk::DeviceQueueCreateInfo::default() .queue_family_index(fam.queue_family_index) .queue_priorities(&fam.priorities) }) .collect::>(); let mut device_create_info = vk::DeviceCreateInfo::default() .queue_create_infos(&queue_create_infos) .enabled_extension_names(&device_extensions_raw); let mut dynamic_rendering = vk::PhysicalDeviceDynamicRenderingFeatures::default().dynamic_rendering(true); let mut indexing_features = vk::PhysicalDeviceDescriptorIndexingFeatures::default() .descriptor_binding_sampled_image_update_after_bind(true); dynamic_rendering.p_next = device_create_info.p_next.cast_mut(); indexing_features.p_next = (&raw mut dynamic_rendering).cast::(); device_create_info.p_next = &raw mut indexing_features as *const c_void; let (device, queues) = unsafe { let vk_device = xr_instance .create_vulkan_device( system, get_instance_proc_addr, physical_device.handle().as_raw() as _, (&raw const device_create_info).cast(), ) .expect("XR error creating Vulkan device") .map_err(vk::Result::from_raw) .expect("Vulkan error creating Vulkan device"); vulkano::device::Device::from_handle( physical_device, vk::Device::from_raw(vk_device as _), DeviceCreateInfo { queue_create_infos: queue_families .iter() .map(|fam| QueueCreateInfo { queue_family_index: fam.queue_family_index, queues: fam.priorities.clone(), ..Default::default() }) .collect::>(), enabled_extensions: device_extensions, enabled_features: features, ..Default::default() }, ) }; log::debug!( " DMA-buf supported: {}", device.enabled_extensions().ext_external_memory_dma_buf ); log::debug!( " DRM format modifiers supported: {}", device.enabled_extensions().ext_image_drm_format_modifier ); // Drop the CStrings device_extensions_raw .into_iter() .for_each(|c_string| unsafe { let _ = CString::from_raw(c_string.cast_mut()); }); let (queue_gfx, queue_xfer, queue_capture) = unwrap_queues(queues.collect()); let gfx = WGfx::new_from_raw( instance, device, queue_gfx, queue_xfer, Format::R8G8B8A8_SRGB, ); let extras = WGfxExtras::new(gfx.clone(), queue_capture)?; Ok((gfx, extras)) } #[allow(clippy::too_many_lines)] #[cfg(feature = "openvr")] pub fn init_openvr_graphics( mut vk_instance_extensions: InstanceExtensions, mut vk_device_extensions_fn: impl FnMut(&PhysicalDevice) -> DeviceExtensions, ) -> anyhow::Result<(Arc, WGfxExtras)> { use vulkano::device::Device; //#[cfg(debug_assertions)] //let layers = vec!["VK_LAYER_KHRONOS_validation".to_owned()]; //#[cfg(not(debug_assertions))] let layers = vec![]; log::debug!("Instance exts for runtime: {:?}", &vk_instance_extensions); vk_instance_extensions.khr_get_physical_device_properties2 = true; let instance = Instance::new( get_vulkan_library().clone(), InstanceCreateInfo { flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, enabled_extensions: vk_instance_extensions, enabled_layers: layers, ..Default::default() }, )?; let dmabuf_extensions = get_dmabuf_extensions(); let (physical_device, my_extensions, queue_families) = instance .enumerate_physical_devices()? .filter_map(|p| { let mut my_extensions = vk_device_extensions_fn(&p); if !p.supported_extensions().contains(&my_extensions) { log::debug!( "Not using {} due to missing extensions:", p.properties().device_name, ); for (ext, missing) in p.supported_extensions().difference(&my_extensions) { if missing { log::debug!(" {ext}"); } } return None; } if p.supported_extensions().contains(&dmabuf_extensions) { my_extensions = my_extensions.union(&dmabuf_extensions); my_extensions.ext_image_drm_format_modifier = p.supported_extensions().ext_image_drm_format_modifier; } if p.supported_extensions().ext_filter_cubic { my_extensions.ext_filter_cubic = true; } log::debug!( "Device exts for {}: {:?}", p.properties().device_name, &my_extensions ); Some((p, my_extensions)) }) .filter_map(|(p, my_extensions)| { try_all_queue_families(p.as_ref()).map(|families| (p, my_extensions, families)) }) .min_by_key(|(p, _, families)| prio_from_device_type(p) * 10 + prio_from_families(families)) .expect("no suitable physical device found"); log::info!( "Using vkPhysicalDevice: {}", physical_device.properties().device_name, ); let (device, queues) = Device::new( physical_device, DeviceCreateInfo { enabled_extensions: my_extensions, enabled_features: DeviceFeatures { dynamic_rendering: true, descriptor_binding_sampled_image_update_after_bind: true, ..DeviceFeatures::empty() }, queue_create_infos: queue_families .iter() .map(|fam| QueueCreateInfo { queue_family_index: fam.queue_family_index, queues: fam.priorities.clone(), ..Default::default() }) .collect::>(), ..Default::default() }, )?; log::debug!( " DMA-buf supported: {}", device.enabled_extensions().ext_external_memory_dma_buf ); log::debug!( " DRM format modifiers supported: {}", device.enabled_extensions().ext_image_drm_format_modifier ); let (queue_gfx, queue_xfer, queue_capture) = unwrap_queues(queues.collect()); let gfx = WGfx::new_from_raw( instance, device, queue_gfx, queue_xfer, Format::R8G8B8A8_SRGB, ); let extras = WGfxExtras::new(gfx.clone(), queue_capture)?; Ok((gfx, extras)) } pub fn upload_quad_vertices( buf: &mut Subbuffer<[Vert2Uv]>, width: f32, height: f32, x: f32, y: f32, w: f32, h: f32, ) -> anyhow::Result<()> { let rw = width; let rh = height; let x0 = x / rw; let y0 = y / rh; let x1 = w / rw + x0; let y1 = h / rh + y0; let data = [ Vert2Uv { in_pos: [x0, y0], in_uv: [0.0, 0.0], }, Vert2Uv { in_pos: [x0, y1], in_uv: [0.0, 1.0], }, Vert2Uv { in_pos: [x1, y0], in_uv: [1.0, 0.0], }, Vert2Uv { in_pos: [x1, y1], in_uv: [1.0, 1.0], }, ]; buf.write()?[0..4].copy_from_slice(&data); Ok(()) } #[derive(Debug)] struct QueueFamilyLayout { queue_family_index: u32, priorities: Vec, } fn prio_from_device_type(physical_device: &PhysicalDevice) -> u32 { match physical_device.properties().device_type { PhysicalDeviceType::DiscreteGpu => 0, PhysicalDeviceType::IntegratedGpu => 1, PhysicalDeviceType::VirtualGpu => 2, PhysicalDeviceType::Cpu => 3, _ => 4, } } const fn prio_from_families(families: &[QueueFamilyLayout]) -> u32 { match families.len() { 2 | 3 => 0, _ => 1, } } fn unwrap_queues(queues: Vec>) -> (Arc, Arc, Option>) { match queues[..] { [ref g, ref t, ref c] => (g.clone(), t.clone(), Some(c.clone())), [ref gt, ref c] => (gt.clone(), gt.clone(), Some(c.clone())), [ref gt] => (gt.clone(), gt.clone(), None), _ => unreachable!(), } } fn try_all_queue_families(physical_device: &PhysicalDevice) -> Option> { queue_families_priorities( physical_device, vec![ // main-thread graphics + uploads QueueFlags::GRAPHICS | QueueFlags::TRANSFER, // capture-thread uploads QueueFlags::TRANSFER, ], ) .or_else(|| { queue_families_priorities( physical_device, vec![ // main thread graphics QueueFlags::GRAPHICS, // main thread uploads QueueFlags::TRANSFER, // capture thread uploads QueueFlags::TRANSFER, ], ) }) .or_else(|| { queue_families_priorities( physical_device, // main thread-only. software capture not supported. vec![QueueFlags::GRAPHICS | QueueFlags::TRANSFER], ) }) } fn queue_families_priorities( physical_device: &PhysicalDevice, mut requested_queues: Vec, ) -> Option> { let mut result = Vec::with_capacity(3); for (idx, props) in physical_device.queue_family_properties().iter().enumerate() { let mut remaining = props.queue_count; let mut want = 0usize; requested_queues.retain(|requested| { if props.queue_flags.intersects(*requested) && remaining > 0 { remaining -= 1; want += 1; false } else { true } }); if want > 0 { result.push(QueueFamilyLayout { queue_family_index: idx as u32, priorities: std::iter::repeat_n(1.0, want).collect(), }); } } if requested_queues.is_empty() { log::debug!("Selected GPU queue families: {result:?}"); Some(result) } else { None } } pub struct RenderResult { pub queue: Arc, pub cmd_buf: Arc, } #[derive(Default)] pub struct GpuFutures { futures: Vec>, } impl GpuFutures { pub fn execute( &mut self, queue: Arc, cmd_buf: Arc, ) -> anyhow::Result<()> { self.futures.push(cmd_buf.execute(queue)?.boxed()); Ok(()) } pub fn execute_results(&mut self, results: SmallVec<[RenderResult; 2]>) -> anyhow::Result<()> { for (i, res) in results.into_iter().enumerate() { if i == 0 { let future = res.cmd_buf.execute(res.queue)?; self.futures.push(Box::new(future)); } else { let future = self.futures.pop().unwrap(); let future = future.then_execute(res.queue, res.cmd_buf)?; self.futures.push(Box::new(future)); } } Ok(()) } pub fn wait(self) -> anyhow::Result<()> { let mut it = self.futures.into_iter(); let Some(mut all) = it.next() else { return Ok(()); }; for f in it { all = all.join(f).boxed(); } let finished = all.then_signal_fence_and_flush()?; finished.wait(None)?; Ok(()) } } pub trait ExtentExt { fn extent_f32(&self) -> [f32; 2]; fn extent_vec2(&self) -> Vec2; fn extent_u32arr(&self) -> [u32; 2]; } impl ExtentExt for Arc { fn extent_f32(&self) -> [f32; 2] { let [w, h, _] = self.image().extent(); [w as _, h as _] } fn extent_vec2(&self) -> Vec2 { let [w, h, _] = self.image().extent(); vec2(w as _, h as _) } fn extent_u32arr(&self) -> [u32; 2] { let [w, h, _] = self.image().extent(); [w, h] } } impl ExtentExt for [u32; 3] { fn extent_f32(&self) -> [f32; 2] { let [w, h, _] = *self; [w as _, h as _] } fn extent_vec2(&self) -> Vec2 { let [w, h, _] = *self; Vec2 { x: w as _, y: h as _, } } fn extent_u32arr(&self) -> [u32; 2] { let [w, h, _] = *self; [w, h] } }