zwlr_screencopy v3 support

This commit is contained in:
galister
2026-01-09 18:47:45 +09:00
parent e6e1764b36
commit 5e77bab588
20 changed files with 578 additions and 192 deletions

View File

@@ -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"
}
}

View File

@@ -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",

View File

@@ -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"
}
}

View File

@@ -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": "ディスプレイの明るさ"
}
}

View File

@@ -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"
}
}

View File

@@ -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<Queue>,
surface_format: Format,
) -> Arc<Self> {
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<Device>) -> Arc<StandardMemoryAllocator> {
pub fn memory_allocator(
device: Arc<Device>,
export_handle_types: Option<ExternalMemoryHandleTypes>,
) -> Arc<StandardMemoryAllocator> {
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<Device>) -> Arc<StandardMemoryAllocator> {
}
}
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()
};

View File

@@ -16,6 +16,7 @@ pub enum WlxFrame {
Dmabuf(DmabufFrame),
MemFd(MemFdFrame),
MemPtr(MemPtrFrame),
Implicit,
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]

View File

@@ -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<ZwlrExportDmabufManagerV1>,
pub maybe_wlr_screencopy_mgr: Option<ZwlrScreencopyManagerV1>,
pub maybe_zwp_linux_dmabuf: Option<ZwpLinuxDmabufV1>,
pub wl_seat: WlSeat,
pub wl_shm: WlShm,
pub outputs: IdMap<u32, WlxOutput>,
@@ -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<WlShm, ()> for WlxClient {
) {
}
}
impl Dispatch<ZwpLinuxDmabufV1, ()> for WlxClient {
fn event(
_state: &mut Self,
_proxy: &ZwpLinuxDmabufV1,
_event: <ZwpLinuxDmabufV1 as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
}
}
impl Dispatch<ZwpLinuxBufferParamsV1, ()> for WlxClient {
fn event(
_state: &mut Self,
_proxy: &ZwpLinuxBufferParamsV1,
_event: <ZwpLinuxBufferParamsV1 as Proxy>::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
}
}

View File

@@ -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<U, R>
where
U: Any,
R: Any,
{
struct CaptureData<U, R> {
sender: mpsc::SyncSender<R>,
receiver: mpsc::Receiver<R>,
user_data: U,
user_data: Option<Box<U>>,
receive_callback: fn(&U, WlxFrame) -> Option<R>,
}
pub struct WlrScreencopyCapture<U, R>
where
U: Any + Send,
R: Any + Send,
{
pub struct WlrScreencopyCapture<U, R> {
output_id: u32,
wl: Option<Box<WlxClient>>,
handle: Option<JoinHandle<Box<WlxClient>>>,
handle: Option<JoinHandle<(Box<WlxClient>, Box<U>)>>,
data: Option<CaptureData<U, R>>,
}
impl<U, R> WlrScreencopyCapture<U, R>
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<U, R> WlxCapture<U, R> for WlrScreencopyCapture<U, R>
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<R> {
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<U, R>(
client: Box<WlxClient>,
output_id: u32,
sender: SyncSender<R>,
user_data: U,
mut user_data: Box<U>,
receive_callback: fn(&U, WlxFrame) -> Option<R>,
wait_for_damage: bool,
) -> Box<WlxClient>
) -> (Box<WlxClient>, Box<U>)
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<ZwlrScreencopyFrameV1, SyncSender<ScreenCopyEvent>> for WlxClient {
fn event(
state: &mut Self,
_state: &mut Self,
proxy: &ZwlrScreencopyFrameV1,
event: <ZwlrScreencopyFrameV1 as Proxy>::Event,
data: &SyncSender<ScreenCopyEvent>,
_conn: &Connection,
qhandle: &QueueHandle<Self>,
_qhandle: &QueueHandle<Self>,
) {
match event {
zwlr_screencopy_frame_v1::Event::Failed => {
@@ -289,58 +409,36 @@ impl Dispatch<ZwlrScreencopyFrameV1, SyncSender<ScreenCopyEvent>> 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);
}
_ => {}
}
}

View File

@@ -18,17 +18,24 @@ pub type SerializedWindowStates = HashMap<Arc<str>, 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)]

View File

@@ -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<smithay::backend::allocator::Format> = vec![];
for f in &gfx_extras.drm_formats {
for f in &*gfx_extras.drm_formats {
formats.push(*f);
}

View File

@@ -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<ImageView>,
pub fd: std::fs::File,
pub offset: u32,
pub stride: i32,
pub modifier: DrmModifier,
}
pub fn export_dmabuf_image(
allocator: Arc<dyn MemoryAllocator>,
extent: [u32; 3],
format: Format,
modifier: DrmModifier,
) -> anyhow::Result<ExportedDmabufImage> {
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<Device>) -> Vec<DrmFormat> {
let possible_formats = [
DrmFourcc::Abgr8888,

View File

@@ -69,7 +69,7 @@ pub const BLEND_ALPHA: AttachmentBlend = AttachmentBlend {
pub struct WGfxExtras {
pub shaders: HashMap<&'static str, Arc<ShaderModule>>,
pub drm_formats: Vec<DrmFormat>,
pub drm_formats: Arc<[DrmFormat]>,
pub queue_capture: Option<Arc<Queue>>,
pub quad_verts: Vert2Buf,
pub fallback_image: Arc<ImageView>,
@@ -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 {

View File

@@ -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<str>,
capture_type: CaptureType,
capture: Box<dyn WlxCapture<WlxCaptureIn, WlxCaptureOut>>,
pipeline: Option<ScreenPipeline>,
cur_frame: Option<WlxCaptureOut>,
@@ -61,10 +69,12 @@ impl ScreenBackend {
pub(super) fn new_raw(
name: Arc<str>,
xr_backend: XrBackend,
capture_type: CaptureType,
capture: Box<dyn WlxCapture<WlxCaptureIn, WlxCaptureOut>>,
) -> 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();

View File

@@ -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<Arc<dyn MemoryAllocator>> = OnceLock::new();
pub(super) struct MyFirstDmaExporter {
gfx: Arc<WGfx>,
drm_formats: Arc<[DrmFormat]>,
images: SmallVec<[ExportedDmabufImage; 2]>,
fourcc: DrmFourcc,
current: usize,
}
impl MyFirstDmaExporter {
pub(super) fn new(gfx: Arc<WGfx>, drm_formats: Arc<[DrmFormat]>) -> Self {
Self {
gfx,
drm_formats,
images: smallvec![],
fourcc: DrmFourcc::Argb8888,
current: 0,
}
}
fn get_current(&self) -> Option<(Arc<ImageView>, 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<str>,
gfx: Arc<WGfx>,
queue: Arc<Queue>,
dma_exporter: Option<MyFirstDmaExporter>,
}
impl WlxCaptureIn {
pub(super) fn new(name: Arc<str>, app: &AppState) -> Self {
pub(super) fn new(
name: Arc<str>,
app: &AppState,
dma_exporter: Option<MyFirstDmaExporter>,
) -> 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<ImageView>,
@@ -501,6 +630,33 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
mouse: frame.mouse,
})
}
WlxFrame::Implicit => {
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<Wlx
// In this case, receive_callback needs to run on the main thread
pub(super) struct MainThreadWlxCapture<T>
where
T: WlxCapture<(), WlxFrame>,
T: WlxCapture<DummyDrmExporter, WlxFrame>,
{
inner: T,
data: Option<WlxCaptureIn>,
@@ -516,7 +672,7 @@ where
impl<T> MainThreadWlxCapture<T>
where
T: WlxCapture<(), WlxFrame>,
T: WlxCapture<DummyDrmExporter, WlxFrame>,
{
pub const fn new(inner: T) -> Self {
Self { inner, data: None }
@@ -525,7 +681,7 @@ where
impl<T> WlxCapture<WlxCaptureIn, WlxCaptureOut> for MainThreadWlxCapture<T>
where
T: WlxCapture<(), WlxFrame>,
T: WlxCapture<DummyDrmExporter, WlxFrame>,
{
fn init(
&mut self,
@@ -534,7 +690,8 @@ where
_: fn(&WlxCaptureIn, WlxFrame) -> Option<WlxCaptureOut>,
) {
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<WlxFrame> {
const fn receive_callback_dummy(_: &DummyDrmExporter, frame: WlxFrame) -> Option<WlxFrame> {
Some(frame)
}

View File

@@ -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

View File

@@ -25,6 +25,8 @@ impl ScreenBackend {
token: Option<&str>,
app: &mut AppState,
) -> anyhow::Result<(Self, Option<String> /* 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,
))
}

View File

@@ -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<Self> {
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<Self> {
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<ScreenBackend> {
let mut capture: Option<ScreenBackend> = 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);
}

View File

@@ -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<ScreenCreateDa
let mut backend = ScreenBackend::new_raw(
m.name.clone(),
app.xr_backend,
CaptureType::PipeWire,
new_wlx_capture!(
app.gfx_extras.queue_capture,
PipewireCapture::new(m.name.clone(), s.node_id)

View File

@@ -1,4 +1,3 @@
use chrono_tz::PST8PDT;
use glam::{Affine2, Affine3A, Quat, Vec2, Vec3, vec2, vec3};
use smithay::{
desktop::PopupManager,