diff --git a/dash-frontend/assets/lang/de.json b/dash-frontend/assets/lang/de.json index f2192eba..eb88e43f 100644 --- a/dash-frontend/assets/lang/de.json +++ b/dash-frontend/assets/lang/de.json @@ -69,12 +69,7 @@ "CAPTURE_METHOD": "Wayland-Bildschirmaufnahme", "CAPTURE_METHOD_HELP": "Versuchen Sie, dies zu ändern, wenn Sie\nschwarze oder fehlerhafte Bildschirme erleben", "KEYBOARD_MIDDLE_CLICK": "Keyboard-Mittelklick", - "KEYBOARD_MIDDLE_CLICK_HELP": "Modifikator bei Eingabe mit violettem Laser", - "OPTION": { - "PIPEWIRE_HELP": "Schnelle GPU-Aufnahme. Empfohlen", - "PW_FALLBACK_HELP": "Langsam. Versuchen Sie dies, falls PipeWire GPU nicht funktioniert.", - "SCREENCOPY_HELP": "Langsam. Funktioniert mit: Hyprland, Niri, River, Sway" - } + "KEYBOARD_MIDDLE_CLICK_HELP": "Modifikator bei Eingabe mit violettem Laser" }, "HELLO": "Hallo!", "AUDIO": { @@ -146,4 +141,4 @@ "LAUNCH": "Starten" }, "DISPLAY_BRIGHTNESS": "Bildschirmhelligkeit" -} \ No newline at end of file +} diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index 7a508844..ab06cc3e 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -67,9 +67,12 @@ "NOTIFICATIONS_SOUND_ENABLED": "Notification sounds", "OPAQUE_BACKGROUND": "Opaque background", "OPTION": { - "PIPEWIRE_HELP": "Fast GPU capture. Recommended", - "PW_FALLBACK_HELP": "Slow. Try in case PipeWire GPU doesn't work", - "SCREENCOPY_HELP": "Slow. Works on: Hyprland, Niri, River, Sway" + "AUTO": "Automatic", + "AUTO_HELP": "ScreenCopy GPU if supported,\notherwise PipeWire GPU.", + "PIPEWIRE_HELP": "Fast GPU capture,\nstandard on all desktops.", + "PW_FALLBACK_HELP": "Slow method with high CPU usage.\nTry in case PipeWire GPU doesn't work", + "SCREENCOPY_GPU_HELP": "Fast, no screen share popups.\nWorks on: Hyprland, Niri, River, Sway", + "SCREENCOPY_HELP": "Slow, no screen share popups.\nWorks on: Hyprland, Niri, River, Sway" }, "POINTER_LERP_FACTOR": "Pointer smoothing", "RESTART_SOFTWARE": "Restart software", diff --git a/dash-frontend/assets/lang/es.json b/dash-frontend/assets/lang/es.json index a5278e4a..e22abff4 100644 --- a/dash-frontend/assets/lang/es.json +++ b/dash-frontend/assets/lang/es.json @@ -69,12 +69,7 @@ "CAPTURE_METHOD": "Captura de pantalla de Wayland", "CAPTURE_METHOD_HELP": "Intente cambiar esta opción si\nexperimenta pantallas negras o con fallos", "KEYBOARD_MIDDLE_CLICK": "Clic del botón central del teclado", - "KEYBOARD_MIDDLE_CLICK_HELP": "Modificador para usar al escribir\ncon láser púrpura", - "OPTION": { - "PIPEWIRE_HELP": "Captura de GPU rápida. Recomendado", - "PW_FALLBACK_HELP": "Lento. Intente si GPU de PipeWire no funciona", - "SCREENCOPY_HELP": "Lento. Funciona en: Hyprland, Niri, River, Sway" - } + "KEYBOARD_MIDDLE_CLICK_HELP": "Modificador para usar al escribir\ncon láser púrpura" }, "HELLO": "¡Hola!", "AUDIO": { @@ -146,4 +141,4 @@ "LAUNCH": "Iniciar" }, "DISPLAY_BRIGHTNESS": "Brillo de la pantalla" -} \ No newline at end of file +} diff --git a/dash-frontend/assets/lang/ja.json b/dash-frontend/assets/lang/ja.json index c854e823..56f1e21c 100644 --- a/dash-frontend/assets/lang/ja.json +++ b/dash-frontend/assets/lang/ja.json @@ -69,12 +69,7 @@ "CAPTURE_METHOD": "Waylandスクリーンキャプチャ", "CAPTURE_METHOD_HELP": "画面が黒くなる、または乱れる場合は、\nこの設定を変更してみてください。", "KEYBOARD_MIDDLE_CLICK": "キーボードの中ボタンクリック", - "KEYBOARD_MIDDLE_CLICK_HELP": "紫色のレーザーで入力する際の修飾キー", - "OPTION": { - "PIPEWIRE_HELP": "高速なGPUキャプチャ。推奨", - "PW_FALLBACK_HELP": "低速です。PipeWire GPU が動作しない場合に試してください。", - "SCREENCOPY_HELP": "遅い。動作する環境: Hyprland, Niri, River, Sway" - } + "KEYBOARD_MIDDLE_CLICK_HELP": "紫色のレーザーで入力する際の修飾キー" }, "HELLO": "こんにちは!", "AUDIO": { @@ -146,4 +141,4 @@ "LAUNCH": "起動" }, "DISPLAY_BRIGHTNESS": "ディスプレイの明るさ" -} \ No newline at end of file +} diff --git a/dash-frontend/assets/lang/pl.json b/dash-frontend/assets/lang/pl.json index ba29a106..f8fab9bf 100644 --- a/dash-frontend/assets/lang/pl.json +++ b/dash-frontend/assets/lang/pl.json @@ -63,12 +63,7 @@ "CAPTURE_METHOD": "Przechwytywanie ekranu Wayland", "CAPTURE_METHOD_HELP": "Spróbuj zmienić tę opcję, jeśli masz\nproblemy z czarnym lub migoczącym ekranem", "KEYBOARD_MIDDLE_CLICK": "Środkowy przycisk myszy na klawiaturze", - "KEYBOARD_MIDDLE_CLICK_HELP": "Modyfikator, który ma zostać użyty podczas pisania\nfioletową wiązką lasera", - "OPTION": { - "PIPEWIRE_HELP": "Szybkie przechwytywanie GPU. Zalecane", - "PW_FALLBACK_HELP": "Wolno. Wypróbuj w przypadku, gdy PipeWire GPU nie działa.", - "SCREENCOPY_HELP": "Wolne. Działa na: Hyprland, Niri, River, Sway" - } + "KEYBOARD_MIDDLE_CLICK_HELP": "Modyfikator, który ma zostać użyty podczas pisania\nfioletową wiązką lasera" }, "APPLICATION_LAUNCHER": "Uruchamiacz aplikacji", "APPLICATIONS": "Aplikacje", @@ -146,4 +141,4 @@ "LAUNCH": "Uruchom" }, "DISPLAY_BRIGHTNESS": "Jasność wyświetlacza" -} \ No newline at end of file +} diff --git a/wgui/src/gfx/mod.rs b/wgui/src/gfx/mod.rs index dfc35386..4864f207 100644 --- a/wgui/src/gfx/mod.rs +++ b/wgui/src/gfx/mod.rs @@ -7,26 +7,26 @@ use std::{marker::PhantomData, slice::Iter, sync::Arc}; use cmd::{GfxCommandBuffer, XferCommandBuffer}; use pipeline::WGfxPipeline; use vulkano::{ - DeviceSize, buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, IndexBuffer, Subbuffer}, command_buffer::{ - AutoCommandBufferBuilder, CommandBufferUsage, allocator::{StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo}, + AutoCommandBufferBuilder, CommandBufferUsage, }, descriptor_set::allocator::{StandardDescriptorSetAllocator, StandardDescriptorSetAllocatorCreateInfo}, device::{Device, Queue}, format::Format, - image::{Image, ImageCreateInfo, ImageType, ImageUsage, sampler::Filter}, + image::{sampler::Filter, Image, ImageCreateInfo, ImageType, ImageUsage}, instance::Instance, memory::{ - MemoryPropertyFlags, allocator::{AllocationCreateInfo, GenericMemoryAllocatorCreateInfo, MemoryTypeFilter, StandardMemoryAllocator}, + ExternalMemoryHandleTypes, MemoryPropertyFlags, }, pipeline::graphics::{ color_blend::{AttachmentBlend, BlendFactor, BlendOp}, vertex_input::Vertex, }, shader::ShaderModule, + DeviceSize, }; use crate::gfx::pipeline::WPipelineCreateInfo; @@ -81,7 +81,7 @@ impl WGfx { queue_xfer: Arc, surface_format: Format, ) -> Arc { - let memory_allocator = memory_allocator(device.clone()); + let memory_allocator = memory_allocator(device.clone(), None); let command_buffer_allocator = Arc::new(StandardCommandBufferAllocator::new( device.clone(), StandardCommandBufferAllocatorCreateInfo { @@ -222,10 +222,14 @@ impl WGfx { } } -fn memory_allocator(device: Arc) -> Arc { +pub fn memory_allocator( + device: Arc, + export_handle_types: Option, +) -> Arc { let props = device.physical_device().memory_properties(); let mut block_sizes = vec![0; props.memory_types.len()]; + let mut memory_type_bits = u32::MAX; for (index, memory_type) in props.memory_types.iter().enumerate() { @@ -252,9 +256,16 @@ fn memory_allocator(device: Arc) -> Arc { } } + let export_handle_types = if let Some(val) = export_handle_types { + vec![val; props.memory_types.len()] + } else { + vec![] + }; + let create_info = GenericMemoryAllocatorCreateInfo { block_sizes: &block_sizes, memory_type_bits, + export_handle_types: &export_handle_types, ..Default::default() }; diff --git a/wlx-capture/src/frame.rs b/wlx-capture/src/frame.rs index 16e57f7d..bbdb843a 100644 --- a/wlx-capture/src/frame.rs +++ b/wlx-capture/src/frame.rs @@ -16,6 +16,7 @@ pub enum WlxFrame { Dmabuf(DmabufFrame), MemFd(MemFdFrame), MemPtr(MemPtrFrame), + Implicit, } #[derive(Debug, Clone, Copy, Default, PartialEq)] diff --git a/wlx-capture/src/wayland.rs b/wlx-capture/src/wayland.rs index ad6ca41c..4ad7c74c 100644 --- a/wlx-capture/src/wayland.rs +++ b/wlx-capture/src/wayland.rs @@ -29,6 +29,9 @@ use wayland_client::{ wl_shm::WlShm, }, }; +use wayland_protocols::wp::linux_dmabuf::zv1::client::{ + zwp_linux_buffer_params_v1::ZwpLinuxBufferParamsV1, zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, +}; use crate::frame; @@ -61,6 +64,7 @@ pub struct WlxClient { pub xdg_output_mgr: ZxdgOutputManagerV1, pub maybe_wlr_dmabuf_mgr: Option, pub maybe_wlr_screencopy_mgr: Option, + pub maybe_zwp_linux_dmabuf: Option, pub wl_seat: WlSeat, pub wl_shm: WlShm, pub outputs: IdMap, @@ -91,7 +95,8 @@ impl WlxClient { .expect(WlSeat::interface().name), wl_shm: globals.bind(&qh, 1..=1, ()).expect(WlShm::interface().name), maybe_wlr_dmabuf_mgr: globals.bind(&qh, 1..=1, ()).ok(), - maybe_wlr_screencopy_mgr: globals.bind(&qh, 2..=2, ()).ok(), + maybe_wlr_screencopy_mgr: globals.bind(&qh, 3..=3, ()).ok(), + maybe_zwp_linux_dmabuf: globals.bind(&qh, 4..=4, ()).ok(), outputs: IdMap::new(), queue: Arc::new(Mutex::new(queue)), globals, @@ -441,3 +446,27 @@ impl Dispatch for WlxClient { ) { } } + +impl Dispatch for WlxClient { + fn event( + _state: &mut Self, + _proxy: &ZwpLinuxDmabufV1, + _event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +impl Dispatch for WlxClient { + fn event( + _state: &mut Self, + _proxy: &ZwpLinuxBufferParamsV1, + _event: ::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} diff --git a/wlx-capture/src/wlr_screencopy.rs b/wlx-capture/src/wlr_screencopy.rs index c9cc9ae8..057c36f2 100644 --- a/wlx-capture/src/wlr_screencopy.rs +++ b/wlx-capture/src/wlr_screencopy.rs @@ -14,6 +14,7 @@ use wayland_client::{ Connection, Dispatch, Proxy, QueueHandle, WEnum, protocol::{wl_buffer::WlBuffer, wl_shm::Format, wl_shm_pool::WlShmPool}, }; +use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1; use smithay_client_toolkit::reexports::protocols_wlr::screencopy::v1::client::zwlr_screencopy_frame_v1::{ZwlrScreencopyFrameV1, self}; @@ -23,59 +24,82 @@ use crate::{ wayland::WlxClient, }; -struct BufData { - wl_buffer: WlBuffer, - wl_pool: WlShmPool, - fd: RawFd, +pub trait DmaExporter { + fn next_frame( + &mut self, + width: u32, + height: u32, + fourcc: DrmFourcc, + ) -> Option<(FramePlane, DrmModifier)>; +} + +enum BufData { + Shm { + wl_buffer: WlBuffer, + wl_pool: WlShmPool, + fd: RawFd, + }, + Dma { + wl_buffer: WlBuffer, + }, } impl Drop for BufData { fn drop(&mut self) { - self.wl_buffer.destroy(); - self.wl_pool.destroy(); - unsafe { - libc::close(self.fd); + match self { + Self::Shm { + wl_buffer, + wl_pool, + fd, + .. + } => { + wl_buffer.destroy(); + wl_pool.destroy(); + unsafe { + libc::close(*fd); + } + } + Self::Dma { wl_buffer } => { + wl_buffer.destroy(); + } } } } enum ScreenCopyEvent { Buffer { - data: BufData, - drm_format: DrmFormat, + shm_format: Format, width: u32, height: u32, stride: u32, }, + DmaBuf { + format: DrmFourcc, + width: u32, + height: u32, + }, + BuffersDone, Ready, Failed, } -struct CaptureData -where - U: Any, - R: Any, -{ +struct CaptureData { sender: mpsc::SyncSender, receiver: mpsc::Receiver, - user_data: U, + user_data: Option>, receive_callback: fn(&U, WlxFrame) -> Option, } -pub struct WlrScreencopyCapture -where - U: Any + Send, - R: Any + Send, -{ +pub struct WlrScreencopyCapture { output_id: u32, wl: Option>, - handle: Option>>, + handle: Option, Box)>>, data: Option>, } impl WlrScreencopyCapture where - U: Any + Send, + U: Any + Send + DmaExporter, R: Any + Send, { pub fn new(wl: WlxClient, output_id: u32) -> Self { @@ -90,7 +114,7 @@ where impl WlxCapture for WlrScreencopyCapture where - U: Any + Send + Clone, + U: Any + Send + DmaExporter, R: Any + Send, { fn init( @@ -105,7 +129,7 @@ where self.data = Some(CaptureData { sender, receiver, - user_data, + user_data: Some(Box::new(user_data)), receive_callback, }); } @@ -113,7 +137,7 @@ where self.data.is_some() } fn supports_dmbuf(&self) -> bool { - false // screencopy v1 + true // screencopy v3+ } fn receive(&mut self) -> Option { if let Some(data) = self.data.as_ref() { @@ -135,7 +159,9 @@ where if let Some(handle) = self.handle.take() { if handle.is_finished() { wait_for_damage = true; - self.wl = Some(handle.join().unwrap()); // safe to unwrap because we checked is_finished + let (wl, u) = handle.join().unwrap(); // safe to unwrap because is_finished + self.wl = Some(wl); + self.data.as_mut().unwrap().user_data = Some(u); } else { self.handle = Some(handle); return; @@ -148,12 +174,12 @@ where let data = self .data - .as_ref() + .as_mut() .expect("must call init once before request_new_frame"); self.handle = Some(std::thread::spawn({ let sender = data.sender.clone(); - let user_data = data.user_data.clone(); + let user_data = data.user_data.take().unwrap(); let receive_callback = data.receive_callback; let output_id = self.output_id; @@ -176,20 +202,20 @@ fn request_screencopy_frame( client: Box, output_id: u32, sender: SyncSender, - user_data: U, + mut user_data: Box, receive_callback: fn(&U, WlxFrame) -> Option, wait_for_damage: bool, -) -> Box +) -> (Box, Box) where - U: Any + Send, + U: Any + Send + DmaExporter, R: Any + Send, { let Some(screencopy_manager) = client.maybe_wlr_screencopy_mgr.as_ref() else { - return client; + return (client, user_data); }; let Some(output) = client.outputs.get(output_id) else { - return client; + return (client, user_data); }; let transform = output.transform; @@ -206,44 +232,138 @@ where let mut frame_buffer = None; + let mut maybe_buffer = None; + let mut maybe_dmabuf = None; + 'receiver: loop { for event in rx.try_iter() { match event { - ScreenCopyEvent::Buffer { - data, - drm_format, - width, - height, - stride, - } => { - let frame = MemFdFrame { - format: FrameFormat { + ScreenCopyEvent::Buffer { .. } => { + log::trace!("{name}: ScreenCopy Buffer event received"); + maybe_buffer = Some(event); + } + ScreenCopyEvent::DmaBuf { .. } => { + log::trace!("{name}: ScreenCopy LinuxDmabuf event received"); + maybe_dmabuf = Some(event); + } + ScreenCopyEvent::BuffersDone => { + log::trace!("{name}: ScreenCopy BuffersDone event received"); + if let Some(zwp_linux_dmabuf) = client.maybe_zwp_linux_dmabuf.as_ref() + && let Some(ScreenCopyEvent::DmaBuf { + format, width, height, - drm_format, - transform, - }, - plane: FramePlane { - fd: Some(data.fd), - offset: 0, - stride: stride as _, - }, - mouse: None, - }; - log::trace!("{}: Received screencopy buffer, copying", name.as_ref()); - if wait_for_damage { - proxy.copy_with_damage(&data.wl_buffer); + }) = maybe_dmabuf + && let Some((plane, modifier)) = user_data.next_frame(width, height, format) + { + let mod_hi = (u64::from(modifier) >> 32) as _; + let mod_lo = (u64::from(modifier) & 0xFFFFFFFF) as _; + let fd = unsafe { BorrowedFd::borrow_raw(plane.fd.unwrap()) }; + + let params = zwp_linux_dmabuf.create_params(&client.queue_handle, ()); + params.add(fd, 0, plane.offset, plane.stride as _, mod_hi, mod_lo); + + let wl_buffer = params.create_immed( + width as _, + height as _, + format as _, + zwp_linux_buffer_params_v1::Flags::empty(), + &client.queue_handle, + (), + ); + + log::trace!("{name}: ScreenCopy with Dmabuf"); + // copy_with_damage seems to not work here + proxy.copy(&wl_buffer); + + frame_buffer = Some((WlxFrame::Implicit, BufData::Dma { wl_buffer })); + } else if let Some(ScreenCopyEvent::Buffer { + shm_format, + width, + height, + stride, + }) = maybe_buffer + && let Some(fourcc) = fourcc_from_wlshm(shm_format) + { + let fd_num = FD_COUNTER.fetch_add(1, Ordering::Relaxed); + let shm_name = CString::new(format!("wlx-{}", fd_num)).unwrap(); // safe + let size = stride * height; + let fd = unsafe { + let fd = libc::shm_open( + shm_name.as_ptr(), + O_CREAT | O_RDWR, + S_IRUSR | S_IWUSR, + ); + libc::shm_unlink(shm_name.as_ptr()); + libc::ftruncate(fd, size as _); + fd + }; + + let borrowed_fd = unsafe { BorrowedFd::borrow_raw(fd) }; + + let wl_pool = client.wl_shm.create_pool( + borrowed_fd, + size as _, + &client.queue_handle, + (), + ); + + let wl_buffer = wl_pool.create_buffer( + 0, + width as _, + height as _, + stride as _, + shm_format, + &client.queue_handle, + (), + ); + + log::trace!("{name}: ScreenCopy with SHM"); + if wait_for_damage { + proxy.copy_with_damage(&wl_buffer); + } else { + proxy.copy(&wl_buffer); + } + + let frame = MemFdFrame { + format: FrameFormat { + width, + height, + drm_format: DrmFormat { + code: fourcc, + modifier: DrmModifier::Invalid, + }, + transform, + }, + plane: FramePlane { + fd: Some(fd), + offset: 0, + stride: stride as _, + }, + mouse: None, + }; + frame_buffer = Some(( + WlxFrame::MemFd(frame), + BufData::Shm { + wl_buffer, + wl_pool, + fd, + }, + )); } else { - proxy.copy(&data.wl_buffer); + log::error!("{name}: No usable ScreenCopy buffers received."); + proxy.destroy(); + break 'receiver; } - frame_buffer = Some((frame, data)); + client.dispatch(); } ScreenCopyEvent::Ready => { + log::trace!("{}: Frame ready?", name.as_ref()); if let Some((frame, buffer)) = frame_buffer { - if let Some(r) = receive_callback(&user_data, WlxFrame::MemFd(frame)) { + if let Some(r) = receive_callback(&user_data, frame) { let _ = sender.send(r); - log::trace!("{}: Frame ready", name.as_ref()); + log::trace!("{}: Frame ready!", name.as_ref()); } drop(buffer); } @@ -257,19 +377,19 @@ where } } - client + (client, user_data) } static FD_COUNTER: AtomicUsize = AtomicUsize::new(0); impl Dispatch> for WlxClient { fn event( - state: &mut Self, + _state: &mut Self, proxy: &ZwlrScreencopyFrameV1, event: ::Event, data: &SyncSender, _conn: &Connection, - qhandle: &QueueHandle, + _qhandle: &QueueHandle, ) { match event { zwlr_screencopy_frame_v1::Event::Failed => { @@ -289,58 +409,36 @@ impl Dispatch> for WlxClient return; }; - let Some(fourcc) = fourcc_from_wlshm(shm_format) else { - log::warn!("Unsupported screencopy format"); - let _ = data.send(ScreenCopyEvent::Failed); - proxy.destroy(); - return; - }; - - let fd_num = FD_COUNTER.fetch_add(1, Ordering::Relaxed); - let name = CString::new(format!("wlx-{}", fd_num)).unwrap(); // safe - let size = stride * height; - let fd = unsafe { - let fd = libc::shm_open(name.as_ptr(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); - libc::shm_unlink(name.as_ptr()); - libc::ftruncate(fd, size as _); - fd - }; - - let borrowed_fd = unsafe { BorrowedFd::borrow_raw(fd) }; - - let wl_pool = state - .wl_shm - .create_pool(borrowed_fd, size as _, qhandle, ()); - - let wl_buffer = wl_pool.create_buffer( - 0, - width as _, - height as _, - stride as _, - shm_format, - qhandle, - (), - ); - let _ = data.send(ScreenCopyEvent::Buffer { - data: BufData { - wl_buffer, - wl_pool, - fd, - }, - drm_format: DrmFormat { - code: fourcc, - modifier: DrmModifier::Invalid, - }, width, height, stride, + shm_format, }); } zwlr_screencopy_frame_v1::Event::Ready { .. } => { let _ = data.send(ScreenCopyEvent::Ready); proxy.destroy(); } + zwlr_screencopy_frame_v1::Event::LinuxDmabuf { + format, + width, + height, + } => { + let Ok(format) = DrmFourcc::try_from(format) else { + log::warn!("{format} is not a known FourCC"); + return; + }; + + let _ = data.send(ScreenCopyEvent::DmaBuf { + width, + height, + format, + }); + } + zwlr_screencopy_frame_v1::Event::BufferDone => { + let _ = data.send(ScreenCopyEvent::BuffersDone); + } _ => {} } } diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index b690aa65..4403d234 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -18,17 +18,24 @@ pub type SerializedWindowStates = HashMap, OverlayWindowState>; #[derive(Default, Clone, Copy, Serialize, Deserialize, AsRefStr, EnumString, EnumProperty, VariantArray)] pub enum CaptureMethod { #[default] - #[serde(alias = "pipewire", alias = "auto")] + #[serde(alias = "auto")] + #[strum(props(Translation = "APP_SETTINGS.OPTION.AUTO", Tooltip = "APP_SETTINGS.OPTION.AUTO_HELP"))] + Auto, + + #[serde(alias = "pipewire")] #[strum(props(Text = "PipeWire GPU", Tooltip = "APP_SETTINGS.OPTION.PIPEWIRE_HELP"))] PipeWire, + #[strum(props(Text = "ScreenCopy GPU", Tooltip = "APP_SETTINGS.OPTION.SCREENCOPY_GPU_HELP"))] + ScreenCopyGpu, + #[serde(alias = "pw-fallback")] #[strum(props(Text = "PipeWire CPU", Tooltip = "APP_SETTINGS.OPTION.PW_FALLBACK_HELP"))] - PwFallback, + PipeWireCpu, #[serde(alias = "screencopy")] #[strum(props(Text = "ScreenCopy CPU", Tooltip = "APP_SETTINGS.OPTION.SCREENCOPY_HELP"))] - ScreenCopy, + ScreenCopyCpu, } #[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, AsRefStr, EnumString, EnumProperty, VariantArray)] diff --git a/wlx-overlay-s/src/backend/wayvr/mod.rs b/wlx-overlay-s/src/backend/wayvr/mod.rs index 6771bb1a..adb8b26c 100644 --- a/wlx-overlay-s/src/backend/wayvr/mod.rs +++ b/wlx-overlay-s/src/backend/wayvr/mod.rs @@ -178,7 +178,7 @@ impl WvrServerState { // this will throw a compile-time error if smithay's drm-fourcc is out of sync with wlx-capture's let mut formats: Vec = vec![]; - for f in &gfx_extras.drm_formats { + for f in &*gfx_extras.drm_formats { formats.push(*f); } diff --git a/wlx-overlay-s/src/graphics/dmabuf.rs b/wlx-overlay-s/src/graphics/dmabuf.rs index 671be50e..35bdcd2e 100644 --- a/wlx-overlay-s/src/graphics/dmabuf.rs +++ b/wlx-overlay-s/src/graphics/dmabuf.rs @@ -10,11 +10,14 @@ use vulkano::{ VulkanError, VulkanObject, device::Device, format::Format, - image::{Image, ImageCreateInfo, ImageTiling, ImageUsage, SubresourceLayout, sys::RawImage}, + image::{ + Image, ImageCreateInfo, ImageMemory, ImageTiling, ImageType, ImageUsage, SubresourceLayout, + sys::RawImage, view::ImageView, + }, memory::{ DedicatedAllocation, DeviceMemory, ExternalMemoryHandleType, ExternalMemoryHandleTypes, MemoryAllocateInfo, MemoryImportInfo, MemoryPropertyFlags, ResourceMemory, - allocator::{MemoryAllocator, MemoryTypeFilter}, + allocator::{AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter}, }, sync::Sharing, }; @@ -299,6 +302,70 @@ pub(super) unsafe fn create_dmabuf_image( } } +pub struct ExportedDmabufImage { + pub view: Arc, + pub fd: std::fs::File, + pub offset: u32, + pub stride: i32, + pub modifier: DrmModifier, +} + +pub fn export_dmabuf_image( + allocator: Arc, + extent: [u32; 3], + format: Format, + modifier: DrmModifier, +) -> anyhow::Result { + let layout = SubresourceLayout { + offset: 0, + size: 0, + row_pitch: align_to(format.block_size() * (extent[0] as u64), 64), + array_pitch: None, + depth_pitch: None, + }; + + let image = Image::new( + allocator.clone(), + ImageCreateInfo { + image_type: ImageType::Dim2d, + format, + extent, + usage: ImageUsage::TRANSFER_DST | ImageUsage::TRANSFER_SRC | ImageUsage::SAMPLED, + tiling: ImageTiling::DrmFormatModifier, + drm_format_modifiers: vec![modifier.into()], + drm_format_modifier_plane_layouts: vec![layout], + external_memory_handle_types: ExternalMemoryHandleTypes::DMA_BUF, + ..Default::default() + }, + AllocationCreateInfo { + ..Default::default() + }, + ) + .context("Could not create image to export")?; + + let fd = match image.memory() { + ImageMemory::Normal(planes) if planes.len() == 1 => { + let plane = planes.first().unwrap(); + plane + .device_memory() + .export_fd(ExternalMemoryHandleType::DmaBuf)? + } + _ => anyhow::bail!("Could not export DMA-buf: invalid ImageMemory"), + }; + + Ok(ExportedDmabufImage { + view: ImageView::new_default(image)?, + fd, + modifier: DrmModifier::from(modifier), + offset: layout.offset as _, + stride: layout.row_pitch as _, + }) +} + +fn align_to(value: u64, alignment: u64) -> u64 { + ((value + alignment - 1) / alignment) * alignment +} + pub(super) fn get_drm_formats(device: Arc) -> Vec { let possible_formats = [ DrmFourcc::Abgr8888, diff --git a/wlx-overlay-s/src/graphics/mod.rs b/wlx-overlay-s/src/graphics/mod.rs index f25a5bd9..b09156ba 100644 --- a/wlx-overlay-s/src/graphics/mod.rs +++ b/wlx-overlay-s/src/graphics/mod.rs @@ -69,7 +69,7 @@ pub const BLEND_ALPHA: AttachmentBlend = AttachmentBlend { pub struct WGfxExtras { pub shaders: HashMap<&'static str, Arc>, - pub drm_formats: Vec, + pub drm_formats: Arc<[DrmFormat]>, pub queue_capture: Option>, pub quad_verts: Vert2Buf, pub fallback_image: Arc, @@ -95,7 +95,7 @@ impl WGfxExtras { let shader = frag_screen::load(gfx.device.clone())?; shaders.insert("frag_screen", shader); - let drm_formats = get_drm_formats(gfx.device.clone()); + let drm_formats = get_drm_formats(gfx.device.clone()).into(); let vertices = [ Vert2Uv { diff --git a/wlx-overlay-s/src/overlays/screen/backend.rs b/wlx-overlay-s/src/overlays/screen/backend.rs index 06ee3036..227c2466 100644 --- a/wlx-overlay-s/src/overlays/screen/backend.rs +++ b/wlx-overlay-s/src/overlays/screen/backend.rs @@ -12,6 +12,7 @@ use crate::{ input::{HoverResult, PointerHit, PointerMode}, }, graphics::ExtentExt, + overlays::screen::capture::MyFirstDmaExporter, state::AppState, subsystem::hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT, WheelDelta}, windowing::backend::{ @@ -41,8 +42,15 @@ fn set_next_move(millis_from_now: u64) { ); } +pub(super) enum CaptureType { + PipeWire, + ScreenCopy, + Xshm, +} + pub struct ScreenBackend { name: Arc, + capture_type: CaptureType, capture: Box>, pipeline: Option, cur_frame: Option, @@ -61,10 +69,12 @@ impl ScreenBackend { pub(super) fn new_raw( name: Arc, xr_backend: XrBackend, + capture_type: CaptureType, capture: Box>, ) -> Self { Self { name, + capture_type, capture, pipeline: None, cur_frame: None, @@ -151,17 +161,17 @@ impl OverlayBackend for ScreenBackend { let allow_dmabuf = !matches!( capture_method, - CaptureMethod::PwFallback | CaptureMethod::ScreenCopy + CaptureMethod::PipeWireCpu | CaptureMethod::ScreenCopyCpu ); - let dmabuf_formats = if !supports_dmabuf { + let (dmabuf_formats, dma_exporter) = if !supports_dmabuf { log::info!("Capture method does not support DMA-buf"); if app.gfx_extras.queue_capture.is_none() { log::warn!( "Current GPU does not support multiple queues. Software capture will take place on the main thread. Expect degraded performance." ); } - &Vec::new() + ([].as_slice(), None) } else if !allow_dmabuf { log::info!( "Not using DMA-buf capture due to {}", @@ -172,16 +182,25 @@ impl OverlayBackend for ScreenBackend { "Current GPU does not support multiple queues. Software capture will take place on the main thread. Expect degraded performance." ); } - &Vec::new() + ([].as_slice(), None) } else { log::warn!( "Using GPU capture. If you're having issues with screens, go to the Dashboard's Settings tab and switch 'Wayland capture method' to a CPU option!" ); - &app.gfx_extras.drm_formats + let dma_exporter = if matches!(self.capture_type, CaptureType::ScreenCopy) { + Some(MyFirstDmaExporter::new( + app.gfx.clone(), + app.gfx_extras.drm_formats.clone(), + )) + } else { + None + }; + + (&*app.gfx_extras.drm_formats, dma_exporter) }; - let user_data = WlxCaptureIn::new(self.name.clone(), app); + let user_data = WlxCaptureIn::new(self.name.clone(), app, dma_exporter); self.capture .init(dmabuf_formats, user_data, receive_callback); self.capture.request_new_frame(); diff --git a/wlx-overlay-s/src/overlays/screen/capture.rs b/wlx-overlay-s/src/overlays/screen/capture.rs index 4df0cca4..4e35d4f1 100644 --- a/wlx-overlay-s/src/overlays/screen/capture.rs +++ b/wlx-overlay-s/src/overlays/screen/capture.rs @@ -1,4 +1,8 @@ -use std::{f32::consts::PI, sync::Arc}; +use std::{ + f32::consts::PI, + os::fd::AsRawFd, + sync::{Arc, OnceLock}, +}; use glam::{Affine3A, Vec3}; use smallvec::{SmallVec, smallvec}; @@ -8,24 +12,30 @@ use vulkano::{ device::Queue, format::Format, image::{Image, sampler::Filter, view::ImageView}, + memory::{ExternalMemoryHandleTypes, allocator::MemoryAllocator}, pipeline::graphics::color_blend::AttachmentBlend, }; -use wgui::gfx::{ - WGfx, - cmd::WGfxClearMode, - pass::WGfxPass, - pipeline::{WGfxPipeline, WPipelineCreateInfo}, +use wgui::{ + gfx::{ + WGfx, + cmd::WGfxClearMode, + memory_allocator, + pass::WGfxPass, + pipeline::{WGfxPipeline, WPipelineCreateInfo}, + }, + log::LogErr, }; use wlx_capture::{ - DrmFormat, WlxCapture, + DrmFormat, DrmFourcc, DrmModifier, WlxCapture, frame::{self as wlx_frame, FrameFormat, MouseMeta, WlxFrame}, + wlr_screencopy::DmaExporter, }; use wlx_common::{config::GeneralConfig, overlays::StereoMode}; use crate::{ graphics::{ - Vert2Uv, - dmabuf::{WGfxDmabuf, fourcc_to_vk}, + ExtentExt, Vert2Uv, + dmabuf::{ExportedDmabufImage, WGfxDmabuf, export_dmabuf_image, fourcc_to_vk}, upload_quad_vertices, }, state::AppState, @@ -343,15 +353,120 @@ fn stereo_mode_to_verts(stereo: StereoMode, array_index: usize) -> [Vert2Uv; 4] } } -#[derive(Clone)] +static DMA_ALLOCATOR: OnceLock> = OnceLock::new(); + +pub(super) struct MyFirstDmaExporter { + gfx: Arc, + drm_formats: Arc<[DrmFormat]>, + images: SmallVec<[ExportedDmabufImage; 2]>, + fourcc: DrmFourcc, + current: usize, +} + +impl MyFirstDmaExporter { + pub(super) fn new(gfx: Arc, drm_formats: Arc<[DrmFormat]>) -> Self { + Self { + gfx, + drm_formats, + images: smallvec![], + fourcc: DrmFourcc::Argb8888, + current: 0, + } + } + + fn get_current(&self) -> Option<(Arc, FrameFormat)> { + let image = self.images.get(self.current)?; + let extent = image.view.extent_u32arr(); + Some(( + image.view.clone(), + FrameFormat { + width: extent[0], + height: extent[1], + drm_format: DrmFormat { + code: self.fourcc, + modifier: image.modifier, + }, + transform: wlx_frame::Transform::Undefined, + }, + )) + } + + fn set_format( + &mut self, + width: u32, + height: u32, + fourcc: wlx_capture::DrmFourcc, + ) -> Option<()> { + if let Some(image) = self.images.first() { + let extent = image.view.image().extent(); + if self.fourcc == fourcc && extent[0] == width && extent[1] == height { + return Some(()); + } + } + self.images.clear(); + + let Some(modifier) = self + .drm_formats + .iter() + .filter(|f| f.code == fourcc) + .map(|f| f.modifier) + .next() + else { + log::error!("Unsupported format requested: {fourcc}"); + return None; + }; + + let format = fourcc_to_vk(fourcc) + .log_err("Could not export new dmabuf due to invalid format") + .ok()?; + + let allocator = DMA_ALLOCATOR.get_or_init(|| { + memory_allocator( + self.gfx.device.clone(), + Some(ExternalMemoryHandleTypes::DMA_BUF), + ) + }); + + for _ in 0..2 { + let image = + export_dmabuf_image(allocator.clone(), [width, height, 1], format, modifier) + .log_err("Could not export DMA-buf image") + .ok()?; + + self.images.push(image); + } + + Some(()) + } + + fn next_frame(&mut self) -> Option<(wlx_frame::FramePlane, DrmModifier)> { + self.current = 1 - self.current; + let image = self.images.get(self.current)?; + + Some(( + wlx_frame::FramePlane { + fd: Some(image.fd.as_raw_fd()), + offset: image.offset, + stride: image.stride, + }, + image.modifier, + )) + } +} + pub struct WlxCaptureIn { name: Arc, gfx: Arc, queue: Arc, + dma_exporter: Option, } impl WlxCaptureIn { - pub(super) fn new(name: Arc, app: &AppState) -> Self { + pub(super) fn new( + name: Arc, + app: &AppState, + dma_exporter: Option, + ) -> Self { Self { name, gfx: app.gfx.clone(), @@ -361,10 +476,24 @@ impl WlxCaptureIn { .as_ref() .unwrap_or_else(|| &app.gfx.queue_xfer) .clone(), + dma_exporter, } } } +impl DmaExporter for WlxCaptureIn { + fn next_frame( + &mut self, + width: u32, + height: u32, + fourcc: DrmFourcc, + ) -> Option<(wlx_frame::FramePlane, DrmModifier)> { + let dma_exporter = self.dma_exporter.as_mut()?; + dma_exporter.set_format(width, height, fourcc)?; + dma_exporter.next_frame() + } +} + #[derive(Clone)] pub(super) struct WlxCaptureOut { pub(super) image: Arc, @@ -501,6 +630,33 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option { + log::trace!("{}: New Implicit frame", me.name); + + let Some((image, format)) = me.dma_exporter.as_ref().unwrap().get_current() else { + log::error!("{}: Implicit frame is missing!", me.name); + return None; + }; + + Some(WlxCaptureOut { + image, + format, + mouse: None, + }) + } + } +} + +/// DmaExporter is not used for SHM capture +pub(super) struct DummyDrmExporter; +impl DmaExporter for DummyDrmExporter { + fn next_frame( + &mut self, + _: u32, + _: u32, + _: DrmFourcc, + ) -> Option<(wlx_frame::FramePlane, DrmModifier)> { + unreachable!() } } @@ -508,7 +664,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option where - T: WlxCapture<(), WlxFrame>, + T: WlxCapture, { inner: T, data: Option, @@ -516,7 +672,7 @@ where impl MainThreadWlxCapture where - T: WlxCapture<(), WlxFrame>, + T: WlxCapture, { pub const fn new(inner: T) -> Self { Self { inner, data: None } @@ -525,7 +681,7 @@ where impl WlxCapture for MainThreadWlxCapture where - T: WlxCapture<(), WlxFrame>, + T: WlxCapture, { fn init( &mut self, @@ -534,7 +690,8 @@ where _: fn(&WlxCaptureIn, WlxFrame) -> Option, ) { self.data = Some(user_data); - self.inner.init(dmabuf_formats, (), receive_callback_dummy); + self.inner + .init(dmabuf_formats, DummyDrmExporter, receive_callback_dummy); } fn is_ready(&self) -> bool { self.inner.is_ready() @@ -559,7 +716,7 @@ where } #[allow(clippy::trivially_copy_pass_by_ref, clippy::unnecessary_wraps)] -const fn receive_callback_dummy(_: &(), frame: WlxFrame) -> Option { +const fn receive_callback_dummy(_: &DummyDrmExporter, frame: WlxFrame) -> Option { Some(frame) } diff --git a/wlx-overlay-s/src/overlays/screen/mirror.rs b/wlx-overlay-s/src/overlays/screen/mirror.rs index ed80858b..0cc5f5a4 100644 --- a/wlx-overlay-s/src/overlays/screen/mirror.rs +++ b/wlx-overlay-s/src/overlays/screen/mirror.rs @@ -22,7 +22,10 @@ use crate::{ input::{HoverResult, PointerHit}, task::{OverlayTask, TaskType, ToggleMode}, }, - overlays::screen::capture::{MainThreadWlxCapture, new_wlx_capture}, + overlays::screen::{ + backend::CaptureType, + capture::{MainThreadWlxCapture, new_wlx_capture}, + }, state::{AppSession, AppState}, subsystem::hid::WheelDelta, windowing::{ @@ -94,6 +97,7 @@ impl OverlayBackend for MirrorBackend { self.renderer = Some(ScreenBackend::new_raw( self.name.clone(), app.xr_backend, + CaptureType::PipeWire, capture, )); app.tasks diff --git a/wlx-overlay-s/src/overlays/screen/pw.rs b/wlx-overlay-s/src/overlays/screen/pw.rs index 89193d1e..7a254c84 100644 --- a/wlx-overlay-s/src/overlays/screen/pw.rs +++ b/wlx-overlay-s/src/overlays/screen/pw.rs @@ -25,6 +25,8 @@ impl ScreenBackend { token: Option<&str>, app: &mut AppState, ) -> anyhow::Result<(Self, Option /* pipewire restore token */)> { + use crate::overlays::screen::backend::CaptureType; + let name = output.name.clone(); let embed_mouse = !app.session.config.double_cursor_fix; @@ -57,7 +59,12 @@ impl ScreenBackend { PipewireCapture::new(name, node_id) ); Ok(( - Self::new_raw(output.name.clone(), app.xr_backend, capture), + Self::new_raw( + output.name.clone(), + app.xr_backend, + CaptureType::PipeWire, + capture, + ), select_screen_result.restore_token, )) } diff --git a/wlx-overlay-s/src/overlays/screen/wl.rs b/wlx-overlay-s/src/overlays/screen/wl.rs index f2d27f6a..899fc4be 100644 --- a/wlx-overlay-s/src/overlays/screen/wl.rs +++ b/wlx-overlay-s/src/overlays/screen/wl.rs @@ -3,7 +3,6 @@ use wlx_capture::{ WlxCapture, frame::Transform, wayland::{WlxClient, WlxOutput}, - wlr_dmabuf::WlrDmabufCapture, wlr_screencopy::WlrScreencopyCapture, }; use wlx_common::{ @@ -12,7 +11,7 @@ use wlx_common::{ }; use crate::{ - overlays::screen::create_screen_from_backend, + overlays::screen::{backend::CaptureType, create_screen_from_backend}, state::{AppState, ScreenMeta}, }; @@ -24,22 +23,18 @@ use super::{ }; impl ScreenBackend { - pub fn new_wlr_dmabuf(output: &WlxOutput, app: &AppState) -> Option { - let client = WlxClient::new()?; - let capture = new_wlx_capture!( - app.gfx_extras.queue_capture, - WlrDmabufCapture::new(client, output.id) - ); - Some(Self::new_raw(output.name.clone(), app.xr_backend, capture)) - } - pub fn new_wlr_screencopy(output: &WlxOutput, app: &AppState) -> Option { let client = WlxClient::new()?; let capture = new_wlx_capture!( app.gfx_extras.queue_capture, WlrScreencopyCapture::new(client, output.id) ); - Some(Self::new_raw(output.name.clone(), app.xr_backend, capture)) + Some(Self::new_raw( + output.name.clone(), + app.xr_backend, + CaptureType::ScreenCopy, + capture, + )) } } @@ -52,9 +47,12 @@ pub fn create_screen_renderer_wl( ) -> Option { let mut capture: Option = None; - if matches!(app.session.config.capture_method, CaptureMethod::ScreenCopy) && has_wlr_screencopy + if matches!( + app.session.config.capture_method, + CaptureMethod::ScreenCopyCpu | CaptureMethod::ScreenCopyGpu | CaptureMethod::Auto + ) && has_wlr_screencopy { - log::info!("{}: Using Wlr Screencopy Wl-SHM", &output.name); + log::info!("{}: Using ScreenCopy capture", &output.name); capture = ScreenBackend::new_wlr_screencopy(output, app); } diff --git a/wlx-overlay-s/src/overlays/screen/x11.rs b/wlx-overlay-s/src/overlays/screen/x11.rs index ca472738..f1f40687 100644 --- a/wlx-overlay-s/src/overlays/screen/x11.rs +++ b/wlx-overlay-s/src/overlays/screen/x11.rs @@ -8,7 +8,7 @@ use wlx_capture::{ }; use crate::{ - overlays::screen::create_screen_from_backend, + overlays::screen::{backend::CaptureType, create_screen_from_backend}, state::{AppState, ScreenMeta}, }; @@ -27,7 +27,12 @@ impl ScreenBackend { app.gfx_extras.queue_capture, XshmCapture::new(screen.clone()) ); - Self::new_raw(screen.name.clone(), app.xr_backend, capture) + Self::new_raw( + screen.name.clone(), + app.xr_backend, + CaptureType::Xshm, + capture, + ) } } @@ -97,6 +102,7 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result