Use Wayland subsurfaces, dmabuf screencopy
More efficient than shm screencopy, and rendering an iced image into the window. Done right, should espeically help with multiple GPUs, but more testing and work is needed there. Removes mmapping of dmabuf, which errored... I guess not allocated with right options for that. Relevant only if we want a fallback/test that involves reading dmabufs. Using `OnDamage` doesn't seem to work correctly currently. Likely a compositor issue.
This commit is contained in:
parent
1bfcd50383
commit
78b7bfe957
4 changed files with 79 additions and 50 deletions
|
|
@ -14,6 +14,7 @@ use cosmic::{
|
||||||
Border,
|
Border,
|
||||||
},
|
},
|
||||||
iced_core::Shadow,
|
iced_core::Shadow,
|
||||||
|
iced_sctk::subsurface_widget::Subsurface,
|
||||||
widget,
|
widget,
|
||||||
};
|
};
|
||||||
use cosmic_comp_config::workspace::WorkspaceLayout;
|
use cosmic_comp_config::workspace::WorkspaceLayout;
|
||||||
|
|
@ -284,7 +285,7 @@ fn toplevel_previews<'a>(
|
||||||
|
|
||||||
fn capture_image(image: Option<&CaptureImage>) -> cosmic::Element<'_, Msg> {
|
fn capture_image(image: Option<&CaptureImage>) -> cosmic::Element<'_, Msg> {
|
||||||
if let Some(image) = image {
|
if let Some(image) = image {
|
||||||
widget::Image::new(image.img.clone()).into()
|
Subsurface::new(image.width, image.height, &image.wl_buffer).into()
|
||||||
} else {
|
} else {
|
||||||
widget::Image::new(widget::image::Handle::from_pixels(1, 1, vec![0, 0, 0, 255])).into()
|
widget::Image::new(widget::image::Handle::from_pixels(1, 1, vec![0, 0, 0, 255])).into()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,13 @@ use cctk::{
|
||||||
};
|
};
|
||||||
use cosmic::cctk;
|
use cosmic::cctk;
|
||||||
use cosmic::iced::widget::image;
|
use cosmic::iced::widget::image;
|
||||||
|
use cosmic::iced_sctk::subsurface_widget::{BufferSource, Dmabuf, Plane, Shmbuf, SubsurfaceBuffer};
|
||||||
use memmap2::Mmap;
|
use memmap2::Mmap;
|
||||||
use rustix::{io::Errno, shm::ShmOFlags};
|
use rustix::{io::Errno, shm::ShmOFlags};
|
||||||
use std::{
|
use std::{
|
||||||
os::fd::{AsFd, OwnedFd},
|
os::fd::{AsFd, OwnedFd},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1;
|
use wayland_protocols::wp::linux_dmabuf::zv1::client::zwp_linux_buffer_params_v1;
|
||||||
|
|
@ -70,10 +72,9 @@ enum BufferBacking {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Buffer {
|
pub struct Buffer {
|
||||||
backing: BufferBacking,
|
pub backing: Arc<BufferSource>,
|
||||||
pub buffer: wl_buffer::WlBuffer,
|
pub buffer: wl_buffer::WlBuffer,
|
||||||
pub buffer_info: BufferInfo,
|
pub buffer_info: BufferInfo,
|
||||||
mmap: Mmap,
|
|
||||||
node: Option<PathBuf>,
|
node: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,6 +90,12 @@ impl AppData {
|
||||||
(),
|
(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
pool.destroy();
|
||||||
|
|
||||||
|
// XXX
|
||||||
|
let fd = rustix::fs::memfd_create("shm-buffer", rustix::fs::MemfdFlags::CLOEXEC).unwrap();
|
||||||
|
rustix::fs::ftruncate(&fd, buffer_info.stride as u64 * buffer_info.height as u64).unwrap();
|
||||||
|
|
||||||
let format = wl_shm::Format::try_from(buffer_info.format).unwrap();
|
let format = wl_shm::Format::try_from(buffer_info.format).unwrap();
|
||||||
let buffer = pool.create_buffer(
|
let buffer = pool.create_buffer(
|
||||||
0,
|
0,
|
||||||
|
|
@ -100,11 +107,18 @@ impl AppData {
|
||||||
(),
|
(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mmap = unsafe { Mmap::map(&fd).unwrap() };
|
|
||||||
|
|
||||||
Buffer {
|
Buffer {
|
||||||
backing: BufferBacking::Shm { fd },
|
backing: Arc::new(
|
||||||
mmap,
|
Shmbuf {
|
||||||
|
fd,
|
||||||
|
offset: 0,
|
||||||
|
width: buffer_info.width as i32,
|
||||||
|
height: buffer_info.height as i32,
|
||||||
|
stride: buffer_info.stride as i32,
|
||||||
|
format,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
buffer,
|
buffer,
|
||||||
buffer_info: buffer_info.clone(),
|
buffer_info: buffer_info.clone(),
|
||||||
node: None,
|
node: None,
|
||||||
|
|
@ -158,8 +172,8 @@ impl AppData {
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let fd = bo.fd()?;
|
let mut planes = Vec::new();
|
||||||
let stride = bo.stride()?;
|
|
||||||
let params = self.dmabuf_state.create_params(&self.qh)?;
|
let params = self.dmabuf_state.create_params(&self.qh)?;
|
||||||
let modifier = bo.modifier()?;
|
let modifier = bo.modifier()?;
|
||||||
for i in 0..bo.plane_count()? as i32 {
|
for i in 0..bo.plane_count()? as i32 {
|
||||||
|
|
@ -173,6 +187,12 @@ impl AppData {
|
||||||
plane_stride,
|
plane_stride,
|
||||||
modifier.into(),
|
modifier.into(),
|
||||||
);
|
);
|
||||||
|
planes.push(Plane {
|
||||||
|
fd: plane_fd,
|
||||||
|
plane_idx: i as u32,
|
||||||
|
offset: plane_offset,
|
||||||
|
stride: plane_stride,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
let buffer = params
|
let buffer = params
|
||||||
.create_immed(
|
.create_immed(
|
||||||
|
|
@ -184,12 +204,17 @@ impl AppData {
|
||||||
)
|
)
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
// Is there any cost to mmapping dma memory if it isn't accessed?
|
|
||||||
let mmap = unsafe { Mmap::map(&fd).unwrap() };
|
|
||||||
|
|
||||||
Ok(Some(Buffer {
|
Ok(Some(Buffer {
|
||||||
backing: BufferBacking::Dmabuf { fd, stride },
|
backing: Arc::new(
|
||||||
mmap,
|
Dmabuf {
|
||||||
|
width: buffer_info.width as i32,
|
||||||
|
height: buffer_info.height as i32,
|
||||||
|
planes,
|
||||||
|
format: buffer_info.format,
|
||||||
|
modifier: modifier.into(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
buffer,
|
buffer,
|
||||||
buffer_info: buffer_info.clone(),
|
buffer_info: buffer_info.clone(),
|
||||||
node: Some(node.clone()),
|
node: Some(node.clone()),
|
||||||
|
|
@ -200,12 +225,11 @@ impl AppData {
|
||||||
// XXX Handle other formats?
|
// XXX Handle other formats?
|
||||||
let format = wl_shm::Format::Abgr8888.into();
|
let format = wl_shm::Format::Abgr8888.into();
|
||||||
|
|
||||||
/*
|
|
||||||
if let Some(buffer_info) = buffer_infos
|
if let Some(buffer_info) = buffer_infos
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x.type_ == WEnum::Value(BufferType::Dmabuf) && x.format == format)
|
.find(|x| x.type_ == WEnum::Value(BufferType::Dmabuf) && x.format == format)
|
||||||
{
|
{
|
||||||
match self.create_gbm_buffer(buffer_info, true) {
|
match self.create_gbm_buffer(buffer_info, false) {
|
||||||
Ok(Some(buffer)) => {
|
Ok(Some(buffer)) => {
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
@ -213,7 +237,6 @@ impl AppData {
|
||||||
Err(err) => eprintln!("Failed to create gbm buffer: {}", err),
|
Err(err) => eprintln!("Failed to create gbm buffer: {}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
// Fallback to shm buffer
|
// Fallback to shm buffer
|
||||||
// Assume format is already known to be valid
|
// Assume format is already known to be valid
|
||||||
|
|
@ -226,33 +249,6 @@ impl AppData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Buffer {
|
impl Buffer {
|
||||||
// Buffer must be released by server for safety
|
|
||||||
// XXX is this at all a performance issue?
|
|
||||||
#[allow(clippy::wrong_self_convention)]
|
|
||||||
pub unsafe fn to_image(&mut self) -> image::Handle {
|
|
||||||
let pixels = match &self.backing {
|
|
||||||
BufferBacking::Shm { .. } => self.mmap.to_vec(),
|
|
||||||
// NOTE: Only will work with linear modifier
|
|
||||||
BufferBacking::Dmabuf { fd, stride } => {
|
|
||||||
if self.buffer_info.stride == self.buffer_info.width * 4 {
|
|
||||||
self.mmap.to_vec()
|
|
||||||
} else {
|
|
||||||
let width = self.buffer_info.width as usize;
|
|
||||||
let height = self.buffer_info.height as usize;
|
|
||||||
let stride = *stride as usize;
|
|
||||||
let output_stride = width * 4;
|
|
||||||
let mut pixels = vec![0; height * output_stride];
|
|
||||||
for y in 0..height {
|
|
||||||
pixels[y * output_stride..y * output_stride + output_stride]
|
|
||||||
.copy_from_slice(&self.mmap[y * stride..y * stride + output_stride]);
|
|
||||||
}
|
|
||||||
pixels
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
image::Handle::from_pixels(self.buffer_info.width, self.buffer_info.height, pixels)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn node(&self) -> Option<&Path> {
|
pub fn node(&self) -> Option<&Path> {
|
||||||
self.node.as_deref()
|
self.node.as_deref()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ use cctk::{
|
||||||
toplevel_management::ToplevelManagerState,
|
toplevel_management::ToplevelManagerState,
|
||||||
wayland_client::{
|
wayland_client::{
|
||||||
globals::registry_queue_init,
|
globals::registry_queue_init,
|
||||||
protocol::{wl_output, wl_seat},
|
protocol::{wl_buffer, wl_output, wl_seat},
|
||||||
Connection, QueueHandle,
|
Connection, QueueHandle,
|
||||||
},
|
},
|
||||||
workspace::WorkspaceState,
|
workspace::WorkspaceState,
|
||||||
|
|
@ -30,6 +30,7 @@ use cosmic::iced::{
|
||||||
self,
|
self,
|
||||||
futures::{executor::block_on, FutureExt, SinkExt},
|
futures::{executor::block_on, FutureExt, SinkExt},
|
||||||
};
|
};
|
||||||
|
use cosmic::iced_sctk::subsurface_widget::SubsurfaceBuffer;
|
||||||
use futures_channel::mpsc;
|
use futures_channel::mpsc;
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
|
|
@ -83,7 +84,9 @@ pub enum Event {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct CaptureImage {
|
pub struct CaptureImage {
|
||||||
pub img: iced::widget::image::Handle,
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub wl_buffer: SubsurfaceBuffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subscription(conn: Connection) -> iced::Subscription<Event> {
|
pub fn subscription(conn: Connection) -> iced::Subscription<Event> {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use cosmic::cctk::{
|
||||||
},
|
},
|
||||||
wayland_client::{Connection, QueueHandle, WEnum},
|
wayland_client::{Connection, QueueHandle, WEnum},
|
||||||
};
|
};
|
||||||
|
use cosmic::iced_sctk::subsurface_widget::{SubsurfaceBuffer, SubsurfaceBufferRelease};
|
||||||
use std::{
|
use std::{
|
||||||
array,
|
array,
|
||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
|
|
@ -21,6 +22,9 @@ pub struct ScreencopySession {
|
||||||
buffers: Option<[Buffer; 2]>,
|
buffers: Option<[Buffer; 2]>,
|
||||||
session: zcosmic_screencopy_session_v1::ZcosmicScreencopySessionV1,
|
session: zcosmic_screencopy_session_v1::ZcosmicScreencopySessionV1,
|
||||||
first_frame: bool,
|
first_frame: bool,
|
||||||
|
// Future signaled when buffer is signaled.
|
||||||
|
// if triple buffer is used, will need more than one.
|
||||||
|
release: Option<SubsurfaceBufferRelease>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScreencopySession {
|
impl ScreencopySession {
|
||||||
|
|
@ -54,6 +58,7 @@ impl ScreencopySession {
|
||||||
buffers: None,
|
buffers: None,
|
||||||
session,
|
session,
|
||||||
first_frame: true,
|
first_frame: true,
|
||||||
|
release: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,8 +75,9 @@ impl ScreencopySession {
|
||||||
.commit(zcosmic_screencopy_session_v1::Options::empty());
|
.commit(zcosmic_screencopy_session_v1::Options::empty());
|
||||||
self.first_frame = false;
|
self.first_frame = false;
|
||||||
} else {
|
} else {
|
||||||
|
// TODO Not updating properly if `Options::OnDamage` is used
|
||||||
self.session
|
self.session
|
||||||
.commit(zcosmic_screencopy_session_v1::Options::OnDamage);
|
.commit(zcosmic_screencopy_session_v1::Options::empty());
|
||||||
}
|
}
|
||||||
conn.flush().unwrap();
|
conn.flush().unwrap();
|
||||||
}
|
}
|
||||||
|
|
@ -149,11 +155,34 @@ impl ScreencopyHandler for AppData {
|
||||||
session.buffers.as_mut().unwrap().rotate_left(1);
|
session.buffers.as_mut().unwrap().rotate_left(1);
|
||||||
|
|
||||||
// Capture again on damage
|
// Capture again on damage
|
||||||
session.attach_buffer_and_commit(&capture, conn);
|
let capture_clone = capture.clone();
|
||||||
|
let conn = conn.clone();
|
||||||
|
let release = session.release.take();
|
||||||
|
self.scheduler
|
||||||
|
.schedule(async move {
|
||||||
|
if let Some(release) = release {
|
||||||
|
// Wait for buffer to be released by server
|
||||||
|
release.await;
|
||||||
|
}
|
||||||
|
let mut session = capture_clone.session.lock().unwrap();
|
||||||
|
let Some(session) = session.as_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
session.attach_buffer_and_commit(&capture_clone, &conn);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let front = session.buffers.as_mut().unwrap().first_mut().unwrap();
|
let front = session.buffers.as_mut().unwrap().first_mut().unwrap();
|
||||||
let img = unsafe { front.to_image() };
|
let (buffer, release) = SubsurfaceBuffer::new(front.backing.clone());
|
||||||
let image = CaptureImage { img };
|
session.release = Some(release);
|
||||||
|
// let img = unsafe { front.to_image() };
|
||||||
|
// let image = CaptureImage { img };
|
||||||
|
let buffer_info = &front.buffer_info;
|
||||||
|
let image = CaptureImage {
|
||||||
|
wl_buffer: buffer,
|
||||||
|
width: buffer_info.width,
|
||||||
|
height: buffer_info.height,
|
||||||
|
};
|
||||||
match &capture.source {
|
match &capture.source {
|
||||||
CaptureSource::Toplevel(toplevel) => {
|
CaptureSource::Toplevel(toplevel) => {
|
||||||
self.send_event(Event::ToplevelCapture(toplevel.clone(), image))
|
self.send_event(Event::ToplevelCapture(toplevel.clone(), image))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue