From 68713b2e39016524e4f736eb68e0f8717e16d74d Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 10 May 2025 22:44:00 +0200 Subject: [PATCH] vulkan: create high-priority queues if possible --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/compositor.rs | 24 ++++++-- src/gfx_apis.rs | 7 ++- src/gfx_apis/vulkan.rs | 18 +++++- src/gfx_apis/vulkan/device.rs | 113 +++++++++++++++++++++++++++------- src/portal/ptl_display.rs | 27 ++++---- src/pr_caps.rs | 97 +++++++++++++++++++++++++++++ src/state.rs | 3 + 9 files changed, 253 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89371e19..43cd50e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -611,6 +611,7 @@ dependencies = [ "num-derive", "num-traits", "once_cell", + "opera", "parking_lot", "pin-project", "png", @@ -859,6 +860,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "opera" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444847837ad2026bf6addabcf0c3c25bf3e9f92e435dedb555f4a6a0180ded1" + [[package]] name = "option-ext" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 254e0146..30e475ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ kbvm = "0.1.4" tiny-skia = { version = "0.11.4", default-features = false, features = ["std"] } regex = "1.11.1" cfg-if = "1.0.0" +opera = "1.0.1" [build-dependencies] repc = "0.1.1" diff --git a/src/compositor.rs b/src/compositor.rs index b80076e4..c220f868 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -39,7 +39,7 @@ use { logger::Logger, output_schedule::OutputSchedule, portal::{self, PortalStartup}, - pr_caps::pr_caps, + pr_caps::{PrCapsThread, pr_caps}, scale::Scale, sighand::{self, SighandError}, state::{ConnectorData, IdleState, ScreenlockState, State, XWaylandState}, @@ -82,10 +82,13 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) { sighand::reset_all(); let reaper_pid = ensure_reaper(); let caps = pr_caps().into_comp(); - if caps.has_nice() { + let caps_thread = if caps.has_nice() { elevate_scheduler(); - } - drop(caps); + Some(caps.into_thread()) + } else { + drop(caps); + None + }; let forker = create_forker(reaper_pid); let portal = portal::run_from_compositor(global.log_level.into()); enable_profiler(); @@ -97,7 +100,14 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) { None } }; - let res = start_compositor2(Some(forker), portal, Some(logger.clone()), args, None); + let res = start_compositor2( + Some(forker), + portal, + Some(logger.clone()), + args, + None, + caps_thread, + ); leaks::log_leaked(); if let Err(e) = res { let e = ErrorFmt(e); @@ -111,7 +121,7 @@ pub fn start_compositor(global: GlobalArgs, args: RunArgs) { #[cfg(feature = "it")] pub fn start_compositor_for_test(future: TestFuture) -> Result<(), CompositorError> { - let res = start_compositor2(None, None, None, RunArgs::default(), Some(future)); + let res = start_compositor2(None, None, None, RunArgs::default(), Some(future), None); leaks::log_leaked(); res } @@ -157,6 +167,7 @@ fn start_compositor2( logger: Option>, run_args: RunArgs, test_future: Option, + caps_thread: Option, ) -> Result<(), CompositorError> { log::info!("pid = {}", uapi::getpid()); log::info!("version = {VERSION}"); @@ -321,6 +332,7 @@ fn start_compositor2( show_pin_icon: Cell::new(false), cl_matcher_manager: ClMatcherManager::new(&crit_ids), tl_matcher_manager: TlMatcherManager::new(&crit_ids), + caps_thread, }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/gfx_apis.rs b/src/gfx_apis.rs index 6226e25c..2a393b09 100644 --- a/src/gfx_apis.rs +++ b/src/gfx_apis.rs @@ -4,6 +4,7 @@ use { async_engine::AsyncEngine, gfx_api::{GfxContext, GfxError}, io_uring::IoUring, + pr_caps::PrCapsThread, utils::errorfmt::ErrorFmt, video::drm::Drm, }, @@ -19,12 +20,13 @@ pub fn create_gfx_context( ring: &Rc, drm: &Drm, api: GfxApi, + caps_thread: Option<&PrCapsThread>, ) -> Result, GfxError> { let mut apis = [GfxApi::OpenGl, GfxApi::Vulkan]; apis.sort_by_key(|&a| if a == api { -1 } else { a as i32 }); let mut last_err = None; for api in apis { - let res = create_gfx_context_(eng, ring, drm, api); + let res = create_gfx_context_(eng, ring, drm, api, caps_thread); match res { Ok(_) => return res, Err(e) => { @@ -41,10 +43,11 @@ fn create_gfx_context_( ring: &Rc, drm: &Drm, api: GfxApi, + caps_thread: Option<&PrCapsThread>, ) -> Result, GfxError> { match api { GfxApi::OpenGl => gl::create_gfx_context(drm), - GfxApi::Vulkan => vulkan::create_graphics_context(eng, ring, drm), + GfxApi::Vulkan => vulkan::create_graphics_context(eng, ring, drm, caps_thread), _ => unreachable!(), } } diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index cddac79e..4b9282fa 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -35,8 +35,9 @@ use { image::VulkanImageMemory, instance::VulkanInstance, renderer::VulkanRenderer, }, io_uring::IoUring, + pr_caps::PrCapsThread, rect::Rect, - utils::oserror::OsError, + utils::{errorfmt::ErrorFmt, oserror::OsError}, video::{ dmabuf::DmaBuf, drm::{Drm, DrmError, sync_obj::SyncObjCtx}, @@ -224,16 +225,27 @@ pub fn create_graphics_context( eng: &Rc, ring: &Rc, drm: &Drm, + caps_thread: Option<&PrCapsThread>, ) -> Result, GfxError> { let instance = VulkanInstance::new(Level::Info, *VULKAN_VALIDATION)?; - let device = instance.create_device(drm)?; + let device = 'device: { + if let Some(t) = caps_thread { + match unsafe { t.run(|| instance.create_device(drm, true)) } { + Ok(d) => break 'device d, + Err(e) => { + log::warn!("Could not create high-priority device: {}", ErrorFmt(e)); + } + } + } + instance.create_device(drm, false)? + }; let renderer = device.create_renderer(eng, ring)?; Ok(Rc::new(Context(renderer))) } pub fn create_vulkan_allocator(drm: &Drm) -> Result, AllocatorError> { let instance = VulkanInstance::new(Level::Debug, *VULKAN_VALIDATION)?; - let device = instance.create_device(drm)?; + let device = instance.create_device(drm, false)?; let allocator = device.create_bo_allocator(drm)?; Ok(Rc::new(allocator)) } diff --git a/src/gfx_apis/vulkan/device.rs b/src/gfx_apis/vulkan/device.rs index 5734b4d9..cd139a56 100644 --- a/src/gfx_apis/vulkan/device.rs +++ b/src/gfx_apis/vulkan/device.rs @@ -24,12 +24,12 @@ use { physical_device_drm, queue_family_foreign, }, khr::{ - driver_properties, external_fence_fd, external_memory_fd, external_semaphore_fd, + self, driver_properties, external_fence_fd, external_memory_fd, external_semaphore_fd, push_descriptor, }, vk::{ - self, DeviceCreateInfo, DeviceQueueCreateInfo, DeviceSize, DriverId, - ExternalSemaphoreFeatureFlags, ExternalSemaphoreHandleTypeFlags, + self, DeviceCreateInfo, DeviceQueueCreateInfo, DeviceQueueGlobalPriorityCreateInfoKHR, + DeviceSize, DriverId, ExternalSemaphoreFeatureFlags, ExternalSemaphoreHandleTypeFlags, ExternalSemaphoreProperties, MAX_MEMORY_TYPES, MemoryPropertyFlags, MemoryType, PhysicalDevice, PhysicalDeviceBufferDeviceAddressFeatures, PhysicalDeviceDescriptorBufferFeaturesEXT, PhysicalDeviceDescriptorBufferPropertiesEXT, @@ -39,7 +39,7 @@ use { PhysicalDeviceProperties2, PhysicalDeviceSynchronization2Features, PhysicalDeviceTimelineSemaphoreFeatures, PhysicalDeviceUniformBufferStandardLayoutFeatures, PhysicalDeviceVulkan12Properties, - Queue, QueueFlags, + Queue, QueueFamilyProperties2, QueueFlags, QueueGlobalPriorityKHR, }, }, isnt::std_1::collections::IsntHashMap2Ext, @@ -50,6 +50,7 @@ use { sync::Arc, }, uapi::Ustr, + vk::QueueFamilyGlobalPriorityPropertiesKHR, }; pub struct VulkanDevice { @@ -210,20 +211,42 @@ impl VulkanInstance { fn find_queues( &self, phy_dev: PhysicalDevice, - ) -> Result<(u32, Option<(u32, u32, u32)>), VulkanError> { - let props = unsafe { + ) -> Result< + ( + u32, + Option<(u32, u32, u32)>, + QueueGlobalPriorityKHR, + QueueGlobalPriorityKHR, + ), + VulkanError, + > { + let len = unsafe { self.instance - .get_physical_device_queue_family_properties(phy_dev) + .get_physical_device_queue_family_properties2_len(phy_dev) }; + let mut priority_props = vec![QueueFamilyGlobalPriorityPropertiesKHR::default(); len]; + let mut props: Vec<_> = priority_props + .iter_mut() + .map(|p| QueueFamilyProperties2::default().push_next(p)) + .collect(); + unsafe { + self.instance + .get_physical_device_queue_family_properties2(phy_dev, &mut props[..]) + } let gfx_queue = props .iter() - .position(|p| p.queue_flags.contains(QueueFlags::GRAPHICS)) + .position(|p| { + p.queue_family_properties + .queue_flags + .contains(QueueFlags::GRAPHICS) + }) .ok_or(VulkanError::NoGraphicsQueue)?; let transfer_queue = 'transfer: { let mut transfer_only = None; let mut compute_only = None; let mut separate_gfx = None; for (idx, props) in props.iter().enumerate() { + let props = &props.queue_family_properties; if idx == gfx_queue { continue; } @@ -244,7 +267,7 @@ impl VulkanInstance { if let Some(idx) = transfer_only.or(compute_only).or(separate_gfx) { break 'transfer Some(idx); } - if props[gfx_queue].queue_count > 1 { + if props[gfx_queue].queue_family_properties.queue_count > 1 { break 'transfer Some(gfx_queue); } None @@ -252,13 +275,27 @@ impl VulkanInstance { let mut width_mask = 0; let mut height_mask = 0; if let Some(idx) = transfer_queue { - let g = &props[idx].min_image_transfer_granularity; + let g = &props[idx] + .queue_family_properties + .min_image_transfer_granularity; width_mask = g.width.wrapping_sub(1); height_mask = g.height.wrapping_sub(1); } + let get_priority = |idx: usize| { + let props = &priority_props[idx]; + if props.priority_count > 0 { + props.priorities[props.priority_count as usize - 1] + } else { + QueueGlobalPriorityKHR::MEDIUM + } + }; Ok(( gfx_queue as _, transfer_queue.map(|v| (v as _, width_mask, height_mask)), + get_priority(gfx_queue), + transfer_queue + .map(get_priority) + .unwrap_or(QueueGlobalPriorityKHR::MEDIUM), )) } @@ -275,7 +312,11 @@ impl VulkanInstance { .contains(ExternalSemaphoreFeatureFlags::IMPORTABLE) } - pub fn create_device(self: &Rc, drm: &Drm) -> Result, VulkanError> { + pub fn create_device( + self: &Rc, + drm: &Drm, + mut high_priority: bool, + ) -> Result, VulkanError> { let render_node = drm .get_render_node() .map_err(VulkanError::FetchRenderNode)? @@ -293,7 +334,17 @@ impl VulkanInstance { if !supports_descriptor_buffer { log::warn!("Vulkan device does not support descriptor buffers"); } - let (graphics_queue_family_idx, transfer_queue_family) = self.find_queues(phy_dev)?; + let supports_queue_priority = extensions.contains_key(khr::global_priority::NAME); + if !supports_queue_priority && high_priority { + high_priority = false; + log::warn!("Vulkan device does not support queue priorities"); + } + let ( + graphics_queue_family_idx, + transfer_queue_family, + max_graphics_priority, + max_transfer_priority, + ) = self.find_queues(phy_dev)?; let mut distinct_transfer_queue_family_idx = None; let mut transfer_granularity_mask = (0, 0); if let Some((idx, width_mask, height_mask)) = transfer_queue_family { @@ -325,18 +376,31 @@ impl VulkanInstance { let mut uniform_buffer_standard_layout_features = PhysicalDeviceUniformBufferStandardLayoutFeatures::default() .uniform_buffer_standard_layout(true); + let mut gfx_queue_device_queue_global_priority_create_info = + DeviceQueueGlobalPriorityCreateInfoKHR::default() + .global_priority(max_graphics_priority); + let mut trn_queue_device_queue_global_priority_create_info = + DeviceQueueGlobalPriorityCreateInfoKHR::default() + .global_priority(max_transfer_priority); let mut queue_create_infos = ArrayVec::<_, 2>::new(); - queue_create_infos.push( - DeviceQueueCreateInfo::default() - .queue_family_index(graphics_queue_family_idx) - .queue_priorities(&[1.0]), - ); + let queue_create_info = |idx, priority_info| { + let mut info = DeviceQueueCreateInfo::default() + .queue_family_index(idx) + .queue_priorities(&[1.0]); + if high_priority { + info = info.push_next(priority_info); + } + info + }; + queue_create_infos.push(queue_create_info( + graphics_queue_family_idx, + &mut gfx_queue_device_queue_global_priority_create_info, + )); if let Some((tq, _, _)) = transfer_queue_family { - queue_create_infos.push( - DeviceQueueCreateInfo::default() - .queue_family_index(tq) - .queue_priorities(&[1.0]), - ); + queue_create_infos.push(queue_create_info( + tq, + &mut trn_queue_device_queue_global_priority_create_info, + )); } let mut device_create_info = DeviceCreateInfo::default() .push_next(&mut semaphore_features) @@ -433,6 +497,11 @@ impl VulkanInstance { }; unsafe { device.get_device_queue(family_idx, queue_idx) } }); + if high_priority { + log::info!( + "Created queues with priorities {max_graphics_priority:?}/{max_transfer_priority:?}", + ); + } Ok(Rc::new(VulkanDevice { physical_device: phy_dev, render_node, diff --git a/src/portal/ptl_display.rs b/src/portal/ptl_display.rs index bd0d2dd1..f84c8663 100644 --- a/src/portal/ptl_display.rs +++ b/src/portal/ptl_display.rs @@ -185,17 +185,22 @@ impl UsrJayRenderCtxOwner for PortalDisplay { } } if render_ctx.is_none() { - let ctx = - match create_gfx_context(&self.state.eng, &self.state.ring, &drm, GfxApi::OpenGl) { - Ok(c) => c, - Err(e) => { - log::error!( - "Could not create render context from drm device: {}", - ErrorFmt(e) - ); - return; - } - }; + let ctx = match create_gfx_context( + &self.state.eng, + &self.state.ring, + &drm, + GfxApi::OpenGl, + None, + ) { + Ok(c) => c, + Err(e) => { + log::error!( + "Could not create render context from drm device: {}", + ErrorFmt(e) + ); + return; + } + }; let ctx = Rc::new(PortalRenderCtx { _dev_id: dev_id, ctx, diff --git a/src/pr_caps.rs b/src/pr_caps.rs index 1ab65757..d5599f86 100644 --- a/src/pr_caps.rs +++ b/src/pr_caps.rs @@ -6,6 +6,13 @@ use { }, utils::{bitflags::BitflagsExt, errorfmt::ErrorFmt, oserror::OsError}, }, + opera::PhantomNotSend, + parking_lot::{Condvar, Mutex}, + std::{ + mem, + sync::Arc, + thread::{self, JoinHandle}, + }, uapi::{ c::{SYS_capget, SYS_capset, syscall}, map_err, @@ -22,6 +29,24 @@ pub struct PrCompCaps { caps: PrCaps, } +pub struct PrCapsThread { + thread: Option>, + data: Arc, + _no_send: PhantomNotSend, +} + +#[derive(Default)] +struct ThreadData { + cond: Condvar, + mutex: Mutex, +} + +#[derive(Default)] +struct MutData { + exit: bool, + fun: Option>, +} + pub fn pr_caps() -> PrCaps { let mut hdr = cap_user_header_t { version: _LINUX_CAPABILITY_VERSION_3, @@ -103,6 +128,70 @@ impl PrCompCaps { pub fn has_nice(&self) -> bool { self.caps.effective.contains(1 << CAP_SYS_NICE) } + + pub fn into_thread(self) -> PrCapsThread { + let data = Arc::new(ThreadData::default()); + let data2 = data.clone(); + let jh = thread::Builder::new() + .name("SYS_nice thread".to_string()) + .spawn(move || { + let data2 = data2; + let mut lock = data2.mutex.lock(); + loop { + if lock.exit { + return; + } + if let Some(f) = lock.fun.take() { + f(); + } + data2.cond.wait(&mut lock); + } + }) + .expect("Could not spawn SYS_nice thread"); + PrCapsThread { + thread: Some(jh), + data, + _no_send: Default::default(), + } + } +} + +impl PrCapsThread { + pub unsafe fn run(&self, f: F) -> T + where + F: FnOnce() -> T, + { + struct AssertSend(T); + unsafe impl Send for AssertSend {} + struct Data { + cond: Condvar, + mutex: Mutex>>, + } + let data = Arc::new(Data { + cond: Default::default(), + mutex: Default::default(), + }); + let data2 = data.clone(); + let f = AssertSend(f); + let fun = Box::new(move || { + let f = f; + let t = f.0(); + *data2.mutex.lock() = Some(AssertSend(t)); + data2.cond.notify_all(); + }); + let fun = unsafe { + mem::transmute::, Box>(fun) + }; + self.data.mutex.lock().fun = Some(fun); + self.data.cond.notify_all(); + let mut lock = data.mutex.lock(); + loop { + if let Some(t) = lock.take() { + return t.0; + } + data.cond.wait(&mut lock); + } + } } impl Drop for PrCaps { @@ -111,6 +200,14 @@ impl Drop for PrCaps { } } +impl Drop for PrCapsThread { + fn drop(&mut self) { + self.data.mutex.lock().exit = true; + self.data.cond.notify_all(); + let _ = self.thread.take().unwrap().join(); + } +} + mod sys { #![allow(dead_code)] diff --git a/src/state.rs b/src/state.rs index b03cdddc..cfd3f16b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -74,6 +74,7 @@ use { keyboard::KeyboardStateIds, leaks::Tracker, logger::Logger, + pr_caps::PrCapsThread, rect::{Rect, Region}, renderer::Renderer, scale::Scale, @@ -246,6 +247,7 @@ pub struct State { pub show_pin_icon: Cell, pub cl_matcher_manager: ClMatcherManager, pub tl_matcher_manager: TlMatcherManager, + pub caps_thread: Option, } // impl Drop for State { @@ -446,6 +448,7 @@ impl State { &self.ring, drm, api.unwrap_or(self.default_gfx_api.get()), + self.caps_thread.as_ref(), ) }