mirror of
https://github.com/wayvr-org/wayvr.git
synced 2026-10-29 03:02:36 +01:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92009e56fa | ||
|
|
eeff59abeb | ||
|
|
3b8b73c0c3 | ||
|
|
07ae2c0037 |
No files matched your search
Generated
+819
-587
File diff suppressed because it is too large.
Load diff
@@ -114,6 +114,8 @@ impl ImageImporter {
|
|||||||
num_planes: dmabuf.num_planes(),
|
num_planes: dmabuf.num_planes(),
|
||||||
planes: Default::default(),
|
planes: Default::default(),
|
||||||
mouse: None,
|
mouse: None,
|
||||||
|
acquire_sync: Default::default(),
|
||||||
|
lease: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (i, handle) in dmabuf.handles().enumerate() {
|
for (i, handle) in dmabuf.handles().enumerate() {
|
||||||
@@ -129,7 +131,7 @@ impl ImageImporter {
|
|||||||
frame.planes[i].stride = stride as _;
|
frame.planes[i].stride = stride as _;
|
||||||
}
|
}
|
||||||
|
|
||||||
let image = self.gfx.dmabuf_texture(frame)?;
|
let image = self.gfx.dmabuf_texture(&frame)?;
|
||||||
log::info!("DMA-buf import: creating image view");
|
log::info!("DMA-buf import: creating image view");
|
||||||
let image_view = self.gfx.create_image_view(image)?;
|
let image_view = self.gfx.create_image_view(image)?;
|
||||||
log::info!("DMA-buf import: image view created");
|
log::info!("DMA-buf import: image view created");
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ use wgui::gfx::{DmabufImportInfo, DmabufPlaneLayout, Format, Image, ImageView, W
|
|||||||
use wlx_capture::{DrmFormat, DrmFourcc, DrmModifier, frame::DmabufFrame};
|
use wlx_capture::{DrmFormat, DrmFourcc, DrmModifier, frame::DmabufFrame};
|
||||||
|
|
||||||
pub trait WGfxDmabuf {
|
pub trait WGfxDmabuf {
|
||||||
fn dmabuf_texture(&self, frame: DmabufFrame) -> anyhow::Result<Arc<Image>>;
|
fn dmabuf_texture(&self, frame: &DmabufFrame) -> anyhow::Result<Arc<Image>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WGfxDmabuf for WGfx {
|
impl WGfxDmabuf for WGfx {
|
||||||
fn dmabuf_texture(&self, frame: DmabufFrame) -> anyhow::Result<Arc<Image>> {
|
fn dmabuf_texture(&self, frame: &DmabufFrame) -> anyhow::Result<Arc<Image>> {
|
||||||
let format = fourcc_to_vk(frame.format.drm_format.code)?;
|
let format = fourcc_to_vk(frame.format.drm_format.code)?;
|
||||||
let modifier = (!matches!(frame.format.drm_format.modifier, DrmModifier::Invalid))
|
let modifier = (!matches!(frame.format.drm_format.modifier, DrmModifier::Invalid))
|
||||||
.then(|| u64::from(frame.format.drm_format.modifier));
|
.then(|| u64::from(frame.format.drm_format.modifier));
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ use wgui::gfx::{
|
|||||||
BufferUsage, BuiltCommandBuffer, Format, ShaderModule, Vert2Buf, Vert2Uv, WGfx, WGfxCreateInfo,
|
BufferUsage, BuiltCommandBuffer, Format, ShaderModule, Vert2Buf, Vert2Uv, WGfx, WGfxCreateInfo,
|
||||||
};
|
};
|
||||||
use wlx_capture::DrmFormat;
|
use wlx_capture::DrmFormat;
|
||||||
|
#[cfg(feature = "pipewire")]
|
||||||
|
use wlx_capture::syncobj::DrmRenderNode;
|
||||||
|
|
||||||
use crate::shaders::{
|
use crate::shaders::{
|
||||||
frag_color, frag_grid, frag_screen, frag_simple, frag_sky, frag_srgb, frag_wayland, vert_quad,
|
frag_color, frag_grid, frag_screen, frag_simple, frag_sky, frag_srgb, frag_wayland, vert_quad,
|
||||||
@@ -20,6 +22,8 @@ pub struct WGfxExtras {
|
|||||||
pub drm_formats: Arc<[DrmFormat]>,
|
pub drm_formats: Arc<[DrmFormat]>,
|
||||||
pub quad_verts: Vert2Buf,
|
pub quad_verts: Vert2Buf,
|
||||||
pub drm_device: Option<(i64, i64)>,
|
pub drm_device: Option<(i64, i64)>,
|
||||||
|
#[cfg(feature = "pipewire")]
|
||||||
|
pub drm_render_node: Option<DrmRenderNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WGfxExtras {
|
impl WGfxExtras {
|
||||||
@@ -55,19 +59,32 @@ impl WGfxExtras {
|
|||||||
];
|
];
|
||||||
let quad_verts = gfx.new_buffer(BufferUsage::VERTEX_BUFFER, &vertices)?;
|
let quad_verts = gfx.new_buffer(BufferUsage::VERTEX_BUFFER, &vertices)?;
|
||||||
|
|
||||||
|
#[cfg(feature = "pipewire")]
|
||||||
let drm_device = gfx.device_info().drm_render_node.map(|(major, minor)| {
|
let drm_device = gfx.device_info().drm_render_node.map(|(major, minor)| {
|
||||||
log::info!("DRM render device: {major} {minor}");
|
log::info!("DRM render device: {major} {minor}");
|
||||||
(i64::from(major), i64::from(minor))
|
(i64::from(major), i64::from(minor))
|
||||||
});
|
});
|
||||||
|
#[cfg(feature = "pipewire")]
|
||||||
if drm_device.is_none() {
|
if drm_device.is_none() {
|
||||||
log::warn!("No DRM device.");
|
log::warn!("No DRM device.");
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "pipewire")]
|
||||||
|
let drm_render_node = drm_device.and_then(|(maj, min)| {
|
||||||
|
use wgui::log::LogErr;
|
||||||
|
use wlx_capture::syncobj::open_render_node;
|
||||||
|
|
||||||
|
open_render_node(maj as u32, min as u32)
|
||||||
|
.log_err("Could not open DRM render node")
|
||||||
|
.ok()
|
||||||
|
});
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
shaders,
|
shaders,
|
||||||
drm_formats,
|
drm_formats,
|
||||||
quad_verts,
|
quad_verts,
|
||||||
drm_device,
|
drm_device,
|
||||||
|
#[cfg(feature = "pipewire")]
|
||||||
|
drm_render_node,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,10 +48,12 @@ pub(super) enum CaptureType {
|
|||||||
|
|
||||||
pub struct ScreenBackend {
|
pub struct ScreenBackend {
|
||||||
name: Arc<str>,
|
name: Arc<str>,
|
||||||
|
// frames mut come before `capture` to maintain drop order
|
||||||
|
cur_frame: Option<WlxCaptureOut>,
|
||||||
|
pending_frame: Option<WlxCaptureOut>,
|
||||||
capture_type: CaptureType,
|
capture_type: CaptureType,
|
||||||
capture: Box<dyn WlxCapture<WlxCaptureIn, WlxCaptureOut>>,
|
capture: Box<dyn WlxCapture<WlxCaptureIn, WlxCaptureOut>>,
|
||||||
pipeline: Option<ScreenPipeline>,
|
pipeline: Option<ScreenPipeline>,
|
||||||
cur_frame: Option<WlxCaptureOut>,
|
|
||||||
meta: Option<FrameMeta>,
|
meta: Option<FrameMeta>,
|
||||||
mouse_transform: Affine2,
|
mouse_transform: Affine2,
|
||||||
interaction_transform: Option<Affine2>,
|
interaction_transform: Option<Affine2>,
|
||||||
@@ -77,6 +79,7 @@ impl ScreenBackend {
|
|||||||
capture,
|
capture,
|
||||||
pipeline: None,
|
pipeline: None,
|
||||||
cur_frame: None,
|
cur_frame: None,
|
||||||
|
pending_frame: None,
|
||||||
meta: None,
|
meta: None,
|
||||||
mouse_transform: Affine2::ZERO,
|
mouse_transform: Affine2::ZERO,
|
||||||
interaction_transform: None,
|
interaction_transform: None,
|
||||||
@@ -204,7 +207,26 @@ impl OverlayBackend for ScreenBackend {
|
|||||||
return Ok(ShouldRender::Unable);
|
return Ok(ShouldRender::Unable);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(frame) = self.capture.receive() {
|
// don't submit the new image until the producer signaled
|
||||||
|
if let Some(mut frame) = self.capture.receive()
|
||||||
|
&& (self.pending_frame.is_none() || matches!(frame.is_ready(), Ok(true)))
|
||||||
|
{
|
||||||
|
self.pending_frame = Some(frame);
|
||||||
|
}
|
||||||
|
let pending_ready = match self.pending_frame.as_mut() {
|
||||||
|
Some(frame) => match frame.is_ready() {
|
||||||
|
Ok(ready) => ready,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("{}: failed to check DMA-buf acquire fence: {e}", self.name);
|
||||||
|
self.pending_frame = None;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if pending_ready {
|
||||||
|
let frame = self.pending_frame.take().unwrap();
|
||||||
let stereo = self.stereo.unwrap_or(StereoMode::None);
|
let stereo = self.stereo.unwrap_or(StereoMode::None);
|
||||||
let mut meta = frame.get_frame_meta(&app.session.config, stereo);
|
let mut meta = frame.get_frame_meta(&app.session.config, stereo);
|
||||||
|
|
||||||
|
|||||||
@@ -558,9 +558,15 @@ pub(super) struct WlxCaptureOut {
|
|||||||
pub(super) image: Arc<ImageView>,
|
pub(super) image: Arc<ImageView>,
|
||||||
pub(super) format: FrameFormat,
|
pub(super) format: FrameFormat,
|
||||||
pub(super) mouse: Option<MouseMeta>,
|
pub(super) mouse: Option<MouseMeta>,
|
||||||
|
pub(super) acquire_sync: wlx_frame::FrameAcquireSync,
|
||||||
|
pub(super) _lease: Option<wlx_frame::FrameLease>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WlxCaptureOut {
|
impl WlxCaptureOut {
|
||||||
|
pub(super) fn is_ready(&mut self) -> std::io::Result<bool> {
|
||||||
|
self.acquire_sync.check_ready()
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn get_frame_meta(&self, config: &GeneralConfig, stereo: StereoMode) -> FrameMeta {
|
pub(super) fn get_frame_meta(&self, config: &GeneralConfig, stereo: StereoMode) -> FrameMeta {
|
||||||
FrameMeta {
|
FrameMeta {
|
||||||
clear: WGfxClearMode::DontCare,
|
clear: WGfxClearMode::DontCare,
|
||||||
@@ -719,18 +725,23 @@ fn upload_image_with_row_length(
|
|||||||
|
|
||||||
pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<WlxCaptureOut> {
|
pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<WlxCaptureOut> {
|
||||||
match frame {
|
match frame {
|
||||||
WlxFrame::Dmabuf(frame) => {
|
WlxFrame::Dmabuf(mut frame) => {
|
||||||
if !frame.is_valid() {
|
if !frame.is_valid() {
|
||||||
log::error!("{}: Invalid frame", me.name);
|
log::error!("{}: Invalid frame", me.name);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
log::trace!("{}: New DMA-buf frame", me.name);
|
log::trace!("{}: New DMA-buf frame", me.name);
|
||||||
let format = frame.format;
|
let format = frame.format;
|
||||||
match me.gfx.dmabuf_texture(frame) {
|
let lease = frame.lease.clone();
|
||||||
|
let acquire_sync = std::mem::take(&mut frame.acquire_sync);
|
||||||
|
|
||||||
|
match me.gfx.dmabuf_texture(&frame) {
|
||||||
Ok(image) => Some(WlxCaptureOut {
|
Ok(image) => Some(WlxCaptureOut {
|
||||||
image: me.gfx.create_image_view(image).ok()?,
|
image: me.gfx.create_image_view(image).ok()?,
|
||||||
format,
|
format,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
|
acquire_sync,
|
||||||
|
_lease: lease,
|
||||||
}),
|
}),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("{}: Failed to create DMA-buf vkImage: {}", me.name, e);
|
log::error!("{}: Failed to create DMA-buf vkImage: {}", me.name, e);
|
||||||
@@ -766,6 +777,8 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
|
|||||||
image: me.gfx.create_image_view(image).ok()?,
|
image: me.gfx.create_image_view(image).ok()?,
|
||||||
format: frame.format,
|
format: frame.format,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
|
acquire_sync: wlx_frame::FrameAcquireSync::Ready,
|
||||||
|
_lease: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
WlxFrame::MemPtr(frame) => {
|
WlxFrame::MemPtr(frame) => {
|
||||||
@@ -800,6 +813,8 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
|
|||||||
image: me.gfx.create_image_view(image).ok()?,
|
image: me.gfx.create_image_view(image).ok()?,
|
||||||
format: frame.format,
|
format: frame.format,
|
||||||
mouse: frame.mouse,
|
mouse: frame.mouse,
|
||||||
|
acquire_sync: wlx_frame::FrameAcquireSync::Ready,
|
||||||
|
_lease: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
WlxFrame::Implicit(transform) => {
|
WlxFrame::Implicit(transform) => {
|
||||||
@@ -823,6 +838,8 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
|
|||||||
image,
|
image,
|
||||||
format,
|
format,
|
||||||
mouse: None,
|
mouse: None,
|
||||||
|
acquire_sync: wlx_frame::FrameAcquireSync::Ready,
|
||||||
|
_lease: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -344,8 +344,14 @@ fn check(mut par: CheckParams, finalize_fn: ScreenCastFinalizeFn) {
|
|||||||
par.app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
|
par.app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
|
||||||
OverlaySelector::Name(par.name.clone()),
|
OverlaySelector::Name(par.name.clone()),
|
||||||
Box::new(move |app, owc| {
|
Box::new(move |app, owc| {
|
||||||
let capture =
|
let capture = new_wlx_capture!(
|
||||||
new_wlx_capture!(app, PipewireCapture::new(par.name.clone(), node_id));
|
app,
|
||||||
|
PipewireCapture::new(
|
||||||
|
par.name.clone(),
|
||||||
|
node_id,
|
||||||
|
app.gfx_extras.drm_render_node.clone()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
owc.backend =
|
owc.backend =
|
||||||
finalize_fn(par.name, par.logical_pos, par.logical_size, capture, app);
|
finalize_fn(par.name, par.logical_pos, par.logical_size, capture, app);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ idmap = { workspace = true }
|
|||||||
libc.workspace = true
|
libc.workspace = true
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
pipewire = { workspace = true, optional = true }
|
pipewire = { workspace = true, optional = true }
|
||||||
|
timeline_syncobj = { git = "https://github.com/Schmarni-Dev/timeline_syncobj", rev = "7f67e49ebf466487cf35138fc108618092acb9e8", optional = true }
|
||||||
slotmap.workspace = true
|
slotmap.workspace = true
|
||||||
wayland-client = { workspace = true, optional = true }
|
wayland-client = { workspace = true, optional = true }
|
||||||
wayland-protocols = {
|
wayland-protocols = {
|
||||||
@@ -46,7 +47,7 @@ smithay-client-toolkit = { version = "0.20.0", optional = true }
|
|||||||
[features]
|
[features]
|
||||||
default = ["pipewire", "wayland"]
|
default = ["pipewire", "wayland"]
|
||||||
egl = []
|
egl = []
|
||||||
pipewire = ["dep:dbus", "dep:pipewire", "wayland"]
|
pipewire = ["dep:dbus", "dep:pipewire", "dep:timeline_syncobj", "wayland"]
|
||||||
wayland = [
|
wayland = [
|
||||||
"dep:smithay-client-toolkit",
|
"dep:smithay-client-toolkit",
|
||||||
"dep:wayland-client",
|
"dep:wayland-client",
|
||||||
|
|||||||
@@ -30,17 +30,6 @@ let capture = PipewireCapture::new(
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Wlr-Dmabuf Setup
|
|
||||||
```rust
|
|
||||||
let wl = WlxClient::new();
|
|
||||||
|
|
||||||
// select desired screen
|
|
||||||
let output_id = wl.outputs.keys().first().unwrap();
|
|
||||||
|
|
||||||
let mut capture = WlrDmabufCapture::new(wl, *output_id).unwrap();
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### XSHM Setup
|
### XSHM Setup
|
||||||
```rust
|
```rust
|
||||||
let monitors = XshmCapture::get_monitors();
|
let monitors = XshmCapture::get_monitors();
|
||||||
|
|||||||
+110
-1
@@ -1,4 +1,10 @@
|
|||||||
use std::os::fd::RawFd;
|
use std::{
|
||||||
|
os::fd::{AsRawFd, OwnedFd, RawFd},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "pipewire")]
|
||||||
|
use timeline_syncobj::timeline_syncobj::TimelineSyncObj;
|
||||||
|
|
||||||
use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier};
|
use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier};
|
||||||
|
|
||||||
@@ -69,6 +75,8 @@ pub struct DmabufFrame {
|
|||||||
pub num_planes: usize,
|
pub num_planes: usize,
|
||||||
pub planes: [FramePlane; 4],
|
pub planes: [FramePlane; 4],
|
||||||
pub mouse: Option<MouseMeta>,
|
pub mouse: Option<MouseMeta>,
|
||||||
|
pub acquire_sync: FrameAcquireSync,
|
||||||
|
pub lease: Option<FrameLease>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DmabufFrame {
|
impl DmabufFrame {
|
||||||
@@ -139,6 +147,107 @@ pub struct MouseMeta {
|
|||||||
pub y: f32,
|
pub y: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct FrameLease(Arc<FrameLeaseInner>);
|
||||||
|
|
||||||
|
struct FrameLeaseInner(Box<dyn Fn() + Send + Sync>);
|
||||||
|
|
||||||
|
impl FrameLease {
|
||||||
|
pub fn new(f: impl Fn() + Send + Sync + 'static) -> Self {
|
||||||
|
Self(Arc::new(FrameLeaseInner(Box::new(f))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for FrameLeaseInner {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
(self.0)();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub enum FrameAcquireSync {
|
||||||
|
#[default]
|
||||||
|
/// Frame is ready, no sync needed
|
||||||
|
Ready,
|
||||||
|
/// Implicit sync using polled FDs
|
||||||
|
Implicit { fds: Arc<[OwnedFd]> },
|
||||||
|
#[cfg(feature = "pipewire")]
|
||||||
|
/// Explicit sync using DRM SyncObj
|
||||||
|
Explicit {
|
||||||
|
timeline: Arc<TimelineSyncObj>,
|
||||||
|
point: u64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameAcquireSync {
|
||||||
|
pub fn check_ready(&mut self) -> std::io::Result<bool> {
|
||||||
|
match self {
|
||||||
|
FrameAcquireSync::Ready => Ok(true),
|
||||||
|
FrameAcquireSync::Implicit { fds } => {
|
||||||
|
if fds.is_empty() {
|
||||||
|
return Err(std::io::Error::other("implicit DMA-buf without planes"));
|
||||||
|
}
|
||||||
|
for plane_fd in fds.iter() {
|
||||||
|
let fd = plane_fd.as_raw_fd();
|
||||||
|
let mut pfd = libc::pollfd {
|
||||||
|
fd,
|
||||||
|
events: libc::POLLIN,
|
||||||
|
revents: 0,
|
||||||
|
};
|
||||||
|
let result = unsafe { libc::poll(&mut pfd, 1, 0) };
|
||||||
|
if result < 0 {
|
||||||
|
let error = std::io::Error::last_os_error();
|
||||||
|
if error.kind() == std::io::ErrorKind::Interrupted {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
if result == 0 {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
if pfd.revents & (libc::POLLNVAL | libc::POLLHUP) != 0 {
|
||||||
|
return Err(std::io::Error::other(format!(
|
||||||
|
"DMA-buf plane fd {fd} poll returned with {:#x}",
|
||||||
|
pfd.revents
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if pfd.revents & libc::POLLERR != 0 {
|
||||||
|
static WARNED: std::sync::atomic::AtomicBool =
|
||||||
|
std::sync::atomic::AtomicBool::new(false);
|
||||||
|
if !WARNED.swap(true, std::sync::atomic::Ordering::Relaxed) {
|
||||||
|
log::error!(
|
||||||
|
"Could not poll implicit acquire fence. Falling back to unsynchronized behavior. Visual artifacts may happen."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if pfd.revents & libc::POLLIN == 0 {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*self = FrameAcquireSync::Ready;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "pipewire")]
|
||||||
|
FrameAcquireSync::Explicit { timeline, point } => {
|
||||||
|
if *point == 0 {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
if timeline
|
||||||
|
.query_signaled_point()
|
||||||
|
.map(|signaled| signaled >= *point)
|
||||||
|
.map_err(|e| std::io::Error::from_raw_os_error(e.raw_os_error()))?
|
||||||
|
{
|
||||||
|
*self = FrameAcquireSync::Ready;
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait DmaExporter {
|
pub trait DmaExporter {
|
||||||
fn next_frame(
|
fn next_frame(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ use frame::WlxFrame;
|
|||||||
|
|
||||||
pub mod frame;
|
pub mod frame;
|
||||||
|
|
||||||
|
#[cfg(feature = "pipewire")]
|
||||||
|
pub mod syncobj;
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
pub mod wayland;
|
pub mod wayland;
|
||||||
|
|
||||||
#[cfg(feature = "wlr")]
|
|
||||||
pub mod wlr_dmabuf;
|
|
||||||
|
|
||||||
#[cfg(feature = "wlr")]
|
#[cfg(feature = "wlr")]
|
||||||
pub mod wlr_screencopy;
|
pub mod wlr_screencopy;
|
||||||
|
|
||||||
|
|||||||
+346
-163
@@ -1,5 +1,9 @@
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::os::fd::{FromRawFd, OwnedFd, RawFd};
|
||||||
|
use std::ptr::NonNull;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::thread::JoinHandle;
|
use std::thread::JoinHandle;
|
||||||
@@ -10,8 +14,9 @@ use drm_fourcc::DrmModifier;
|
|||||||
use pipewire as pw;
|
use pipewire as pw;
|
||||||
use pipewire::context::ContextRc;
|
use pipewire::context::ContextRc;
|
||||||
use pipewire::main_loop::MainLoopRc;
|
use pipewire::main_loop::MainLoopRc;
|
||||||
use pipewire::spa::buffer::meta::MetaVideoTransformValue;
|
use pipewire::spa::buffer::meta::{
|
||||||
use pipewire::spa::buffer::meta::{MetaCursor, MetaHeader, MetaHeaderFlags, MetaVideoTransform};
|
MetaCursor, MetaHeader, MetaHeaderFlags, MetaVideoTransform, MetaVideoTransformValue,
|
||||||
|
};
|
||||||
use pipewire::stream::StreamRc;
|
use pipewire::stream::StreamRc;
|
||||||
use pw::spa;
|
use pw::spa;
|
||||||
|
|
||||||
@@ -21,7 +26,7 @@ use pw::stream::{Stream, StreamFlags};
|
|||||||
use spa::buffer::DataType;
|
use spa::buffer::DataType;
|
||||||
use spa::param::ParamType;
|
use spa::param::ParamType;
|
||||||
use spa::param::video::VideoFormat;
|
use spa::param::video::VideoFormat;
|
||||||
use spa::param::video::VideoInfoRaw;
|
use spa::param::video::{VideoFlags, VideoInfoRaw};
|
||||||
use spa::pod::ChoiceValue;
|
use spa::pod::ChoiceValue;
|
||||||
use spa::pod::Pod;
|
use spa::pod::Pod;
|
||||||
use spa::pod::serialize::GenError;
|
use spa::pod::serialize::GenError;
|
||||||
@@ -29,13 +34,24 @@ use spa::pod::{Object, Property, PropertyFlags, Value};
|
|||||||
use spa::utils::Choice;
|
use spa::utils::Choice;
|
||||||
use spa::utils::ChoiceEnum;
|
use spa::utils::ChoiceEnum;
|
||||||
use spa::utils::ChoiceFlags;
|
use spa::utils::ChoiceFlags;
|
||||||
|
use timeline_syncobj::render_node::DrmRenderNode;
|
||||||
|
use wayland_client::backend::smallvec::SmallVec;
|
||||||
|
|
||||||
|
use super::raw_buffer::DequeuedBuffer;
|
||||||
use crate::WlxCapture;
|
use crate::WlxCapture;
|
||||||
use crate::frame::FrameFormat;
|
use crate::frame::FrameFormat;
|
||||||
use crate::frame::MouseMeta;
|
use crate::frame::MouseMeta;
|
||||||
use crate::frame::Transform;
|
use crate::frame::Transform;
|
||||||
use crate::frame::WlxFrame;
|
use crate::frame::WlxFrame;
|
||||||
use crate::frame::{DmabufFrame, FramePlane, MemFdFrame, MemPtrFrame};
|
use crate::frame::{
|
||||||
|
DmabufFrame, FrameAcquireSync, FrameLease, FramePlane, MemFdFrame, MemPtrFrame,
|
||||||
|
};
|
||||||
|
use crate::pipewire::raw_buffer::{HeldBuffers, MAX_PW_BUFFERS};
|
||||||
|
use crate::syncobj::import_timeline;
|
||||||
|
|
||||||
|
const SPA_DATA_SYNCOBJ: u32 = 5;
|
||||||
|
const SPA_META_SYNC_TIMELINE: u32 = 9;
|
||||||
|
const SPA_PARAM_BUFFERS_META_TYPE: u32 = 7;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct StreamData {
|
struct StreamData {
|
||||||
@@ -48,6 +64,7 @@ pub enum PwChangeRequest {
|
|||||||
Pause,
|
Pause,
|
||||||
Resume,
|
Resume,
|
||||||
Stop,
|
Stop,
|
||||||
|
ReleaseBuffer(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CaptureData<R>
|
struct CaptureData<R>
|
||||||
@@ -66,18 +83,20 @@ where
|
|||||||
data: Option<CaptureData<R>>,
|
data: Option<CaptureData<R>>,
|
||||||
node_id: u32,
|
node_id: u32,
|
||||||
handle: Option<JoinHandle<Result<(), Error>>>,
|
handle: Option<JoinHandle<Result<(), Error>>>,
|
||||||
|
drm_render_node: Option<DrmRenderNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> PipewireCapture<R>
|
impl<R> PipewireCapture<R>
|
||||||
where
|
where
|
||||||
R: Any + Send,
|
R: Any + Send,
|
||||||
{
|
{
|
||||||
pub fn new(name: Arc<str>, node_id: u32) -> Self {
|
pub fn new(name: Arc<str>, node_id: u32, drm_render_node: Option<DrmRenderNode>) -> Self {
|
||||||
PipewireCapture {
|
PipewireCapture {
|
||||||
name,
|
name,
|
||||||
data: None,
|
data: None,
|
||||||
node_id,
|
node_id,
|
||||||
handle: None,
|
handle: None,
|
||||||
|
drm_render_node,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,7 +106,8 @@ where
|
|||||||
R: Any + Send,
|
R: Any + Send,
|
||||||
{
|
{
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(data) = &self.data {
|
if let Some(data) = self.data.take() {
|
||||||
|
drop(data.rx_frame);
|
||||||
let _ = data.tx_ctrl.send(PwChangeRequest::Stop);
|
let _ = data.tx_ctrl.send(PwChangeRequest::Stop);
|
||||||
}
|
}
|
||||||
if let Some(handle) = self.handle.take() {
|
if let Some(handle) = self.handle.take() {
|
||||||
@@ -107,10 +127,15 @@ where
|
|||||||
user_data: U,
|
user_data: U,
|
||||||
receive_callback: fn(&U, WlxFrame) -> Option<R>,
|
receive_callback: fn(&U, WlxFrame) -> Option<R>,
|
||||||
) {
|
) {
|
||||||
let (tx_frame, rx_frame) = mpsc::sync_channel(2);
|
let (tx_frame, rx_frame) = mpsc::sync_channel(4);
|
||||||
let (tx_ctrl, rx_ctrl) = pw::channel::channel();
|
let (tx_ctrl, rx_ctrl) = pw::channel::channel();
|
||||||
|
|
||||||
self.data = Some(CaptureData { tx_ctrl, rx_frame });
|
self.data = Some(CaptureData {
|
||||||
|
tx_ctrl: tx_ctrl.clone(),
|
||||||
|
rx_frame,
|
||||||
|
});
|
||||||
|
|
||||||
|
let drm_render_node = self.drm_render_node.clone();
|
||||||
|
|
||||||
self.handle = Some(std::thread::spawn({
|
self.handle = Some(std::thread::spawn({
|
||||||
let name = self.name.clone();
|
let name = self.name.clone();
|
||||||
@@ -123,9 +148,11 @@ where
|
|||||||
node_id,
|
node_id,
|
||||||
formats,
|
formats,
|
||||||
tx_frame,
|
tx_frame,
|
||||||
|
tx_ctrl,
|
||||||
rx_ctrl,
|
rx_ctrl,
|
||||||
user_data,
|
user_data,
|
||||||
receive_callback,
|
receive_callback,
|
||||||
|
drm_render_node,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -175,10 +202,12 @@ fn main_loop<U, R>(
|
|||||||
name: Arc<str>,
|
name: Arc<str>,
|
||||||
node_id: u32,
|
node_id: u32,
|
||||||
dmabuf_formats: Vec<DrmFormat>,
|
dmabuf_formats: Vec<DrmFormat>,
|
||||||
sender: mpsc::SyncSender<R>,
|
tx_frame: mpsc::SyncSender<R>,
|
||||||
receiver: pw::channel::Receiver<PwChangeRequest>,
|
tx_ctrl: pw::channel::Sender<PwChangeRequest>,
|
||||||
|
rx_ctrl: pw::channel::Receiver<PwChangeRequest>,
|
||||||
user_data: U,
|
user_data: U,
|
||||||
receive_callback: fn(&U, WlxFrame) -> Option<R>,
|
receive_callback: fn(&U, WlxFrame) -> Option<R>,
|
||||||
|
drm_render_node: Option<DrmRenderNode>,
|
||||||
) -> Result<(), Error>
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
U: Any,
|
U: Any,
|
||||||
@@ -189,6 +218,8 @@ where
|
|||||||
let context = ContextRc::new(&main_loop, None)?;
|
let context = ContextRc::new(&main_loop, None)?;
|
||||||
let core = context.connect_rc(None)?;
|
let core = context.connect_rc(None)?;
|
||||||
|
|
||||||
|
let held_buffers = Rc::new(RefCell::new(HeldBuffers::default()));
|
||||||
|
|
||||||
let stream = StreamRc::new(
|
let stream = StreamRc::new(
|
||||||
core,
|
core,
|
||||||
&name,
|
&name,
|
||||||
@@ -217,6 +248,7 @@ where
|
|||||||
})
|
})
|
||||||
.param_changed({
|
.param_changed({
|
||||||
let name = name.clone();
|
let name = name.clone();
|
||||||
|
let support_explicit_sync = drm_render_node.is_some();
|
||||||
move |stream, format, id, param| {
|
move |stream, format, id, param| {
|
||||||
let Some(param) = param else {
|
let Some(param) = param else {
|
||||||
return;
|
return;
|
||||||
@@ -234,186 +266,278 @@ where
|
|||||||
format.drm_format.code = spa_to_fourcc(info.format());
|
format.drm_format.code = spa_to_fourcc(info.format());
|
||||||
format.drm_format.modifier = DrmModifier::from(info.modifier());
|
format.drm_format.modifier = DrmModifier::from(info.modifier());
|
||||||
|
|
||||||
let kind = if info.modifier() != 0 {
|
let dmabuf = info.flags().contains(VideoFlags::MODIFIER);
|
||||||
"DMA-buf"
|
let kind = if dmabuf { "DMA-buf" } else { "SHM" };
|
||||||
} else {
|
|
||||||
"SHM"
|
|
||||||
};
|
|
||||||
|
|
||||||
log::info!("{}: got {} video format:", name, kind);
|
log::info!("{}: got {} video format:", name, kind);
|
||||||
log::info!(" format: {} ({:?})", info.format().as_raw(), info.format());
|
log::info!(" format: {} ({:?})", info.format().as_raw(), info.format());
|
||||||
log::info!(" size: {}x{}", info.size().width, info.size().height);
|
log::info!(" size: {}x{}", info.size().width, info.size().height);
|
||||||
log::info!(" modifier: {}", info.modifier());
|
log::info!(" modifier: {}", info.modifier());
|
||||||
let Ok(params_bytes) = obj_to_bytes(get_buffer_params()) else {
|
|
||||||
log::warn!("{}: failed to serialize buffer params", name);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(params_pod) = Pod::from_bytes(¶ms_bytes) else {
|
|
||||||
log::warn!("{}: failed to deserialize buffer params", name);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let header_bytes = obj_to_bytes(get_meta_object(
|
let mut param_bytes = Vec::new();
|
||||||
spa::sys::SPA_META_Header,
|
if dmabuf && support_explicit_sync {
|
||||||
std::mem::size_of::<spa::sys::spa_meta_header>(),
|
param_bytes.push(obj_to_bytes(get_sync_buffer_params()).unwrap());
|
||||||
))
|
// order defines preference, we prefer explicit sync
|
||||||
.unwrap(); // want panic
|
param_bytes.push(
|
||||||
let header_pod = Pod::from_bytes(&header_bytes).unwrap(); // want panic
|
obj_to_bytes(get_meta_object(
|
||||||
|
SPA_META_SYNC_TIMELINE,
|
||||||
|
std::mem::size_of::<MetaSyncTimeline>(),
|
||||||
|
))
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
param_bytes.push(obj_to_bytes(get_buffer_params()).unwrap());
|
||||||
|
param_bytes.push(
|
||||||
|
obj_to_bytes(get_meta_object(
|
||||||
|
spa::sys::SPA_META_Header,
|
||||||
|
std::mem::size_of::<spa::sys::spa_meta_header>(),
|
||||||
|
))
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
param_bytes.push(
|
||||||
|
obj_to_bytes(get_meta_object(
|
||||||
|
spa::sys::SPA_META_VideoTransform,
|
||||||
|
std::mem::size_of::<spa::sys::spa_meta_videotransform>(),
|
||||||
|
))
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
let xform_bytes = obj_to_bytes(get_meta_object(
|
let mut pods: Vec<&Pod> = param_bytes
|
||||||
spa::sys::SPA_META_VideoTransform,
|
.iter()
|
||||||
std::mem::size_of::<spa::sys::spa_meta_videotransform>(),
|
.map(|bytes| Pod::from_bytes(bytes).unwrap())
|
||||||
))
|
.collect();
|
||||||
.unwrap(); // want panic
|
|
||||||
let xform_pod = Pod::from_bytes(&xform_bytes).unwrap(); // want panic
|
|
||||||
|
|
||||||
let mut pods = [params_pod, header_pod, xform_pod];
|
|
||||||
if let Err(e) = stream.update_params(&mut pods) {
|
if let Err(e) = stream.update_params(&mut pods) {
|
||||||
log::error!("{}: failed to update params: {}", name, e);
|
log::error!("{}: failed to update params: {}", name, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.remove_buffer({
|
||||||
|
let held_buffers = held_buffers.clone();
|
||||||
|
let name = name.clone();
|
||||||
|
move |_, _, ptr| {
|
||||||
|
if let Some(buffer) = NonNull::new(ptr)
|
||||||
|
&& held_buffers.borrow_mut().on_removed(buffer)
|
||||||
|
{
|
||||||
|
log::warn!("{name}: PipeWire removed a still-leased buffer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
.process({
|
.process({
|
||||||
let name = name.clone();
|
let name = name.clone();
|
||||||
|
let sync_device = drm_render_node.clone();
|
||||||
|
let held_buffers = held_buffers.clone();
|
||||||
|
let tx_ctrl = tx_ctrl.clone();
|
||||||
let u = user_data;
|
let u = user_data;
|
||||||
move |stream, format| {
|
move |stream, format| {
|
||||||
let mut maybe_buffer = None;
|
let mut new_buffer = None;
|
||||||
// discard all but the newest frame
|
while let Some(buffer) = DequeuedBuffer::dequeue(stream) {
|
||||||
while let Some(buffer) = stream.dequeue_buffer() {
|
new_buffer = Some(buffer);
|
||||||
maybe_buffer = Some(buffer);
|
}
|
||||||
|
let Some(buffer) = new_buffer else { return };
|
||||||
|
|
||||||
|
if let Some(header) = buffer.find_meta::<MetaHeader>()
|
||||||
|
&& header.flags().contains(MetaHeaderFlags::CORRUPTED)
|
||||||
|
{
|
||||||
|
log::warn!("{name}: PipeWire buffer is corrupt");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mut buffer) = maybe_buffer {
|
if let Some(transform) = buffer.find_meta::<MetaVideoTransform>() {
|
||||||
if let Some(header) = buffer.find_meta::<MetaHeader>()
|
format.transform = match transform.transform() {
|
||||||
&& header.flags().contains(MetaHeaderFlags::CORRUPTED)
|
MetaVideoTransformValue::NONE => Transform::Normal,
|
||||||
{
|
MetaVideoTransformValue::ROTATED90 => Transform::Rotated90,
|
||||||
log::warn!("{}: PipeWire buffer is corrupt.", name);
|
MetaVideoTransformValue::ROTATED180 => Transform::Rotated180,
|
||||||
return;
|
MetaVideoTransformValue::ROTATED270 => Transform::Rotated270,
|
||||||
}
|
MetaVideoTransformValue::FLIPPED => Transform::Flipped,
|
||||||
|
MetaVideoTransformValue::FLIPPED90 => Transform::Flipped90,
|
||||||
|
MetaVideoTransformValue::FLIPPED180 => Transform::Flipped180,
|
||||||
|
MetaVideoTransformValue::FLIPPED270 => Transform::Flipped270,
|
||||||
|
_ => Transform::Undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(transform) = buffer.find_meta::<MetaVideoTransform>() {
|
let mouse_meta = buffer
|
||||||
format.transform = match transform.transform() {
|
.find_meta::<MetaCursor>()
|
||||||
MetaVideoTransformValue::NONE => Transform::Normal,
|
.filter(|c| c.is_valid())
|
||||||
MetaVideoTransformValue::ROTATED90 => Transform::Rotated90,
|
.map(|c| MouseMeta {
|
||||||
MetaVideoTransformValue::ROTATED180 => Transform::Rotated180,
|
x: c.position().x as f32 / format.width as f32,
|
||||||
MetaVideoTransformValue::ROTATED270 => Transform::Rotated270,
|
y: c.position().y as f32 / format.height as f32,
|
||||||
MetaVideoTransformValue::FLIPPED => Transform::Flipped,
|
});
|
||||||
MetaVideoTransformValue::FLIPPED90 => Transform::Flipped90,
|
|
||||||
MetaVideoTransformValue::FLIPPED180 => Transform::Flipped180,
|
let acquire_point = buffer
|
||||||
MetaVideoTransformValue::FLIPPED270 => Transform::Flipped270,
|
.find_meta::<MetaSyncTimeline>()
|
||||||
_ => Transform::Undefined,
|
.map(|meta| meta.acquire_point);
|
||||||
|
|
||||||
|
let datas = buffer.datas_mut();
|
||||||
|
if datas.is_empty() {
|
||||||
|
log::debug!("{name}: empty PipeWire frame");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let is_explicit = datas.len() >= 3
|
||||||
|
&& datas[datas.len() - 2].type_().as_raw() == SPA_DATA_SYNCOBJ
|
||||||
|
&& datas[datas.len() - 1].type_().as_raw() == SPA_DATA_SYNCOBJ;
|
||||||
|
if is_explicit != acquire_point.is_some() {
|
||||||
|
log::warn!("{name}: inconsistent SyncTimeline metadata / SyncObj FD layout");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let acquire_fd = is_explicit.then(|| datas[datas.len() - 2].fd());
|
||||||
|
let datas = if is_explicit {
|
||||||
|
&datas[..datas.len() - 2]
|
||||||
|
} else {
|
||||||
|
datas
|
||||||
|
};
|
||||||
|
if datas.is_empty()
|
||||||
|
|| datas.len() > 4
|
||||||
|
|| datas
|
||||||
|
.iter()
|
||||||
|
.any(|d| d.type_().as_raw() == SPA_DATA_SYNCOBJ)
|
||||||
|
{
|
||||||
|
log::warn!("{name}: invalid PipeWire plane layout");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let data_type = datas[0].type_();
|
||||||
|
if datas.iter().any(|d| d.type_() != data_type) {
|
||||||
|
log::warn!("{name}: mixed PipeWire data types");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match datas[0].type_() {
|
||||||
|
DataType::DmaBuf => {
|
||||||
|
let mut planes: SmallVec<[FramePlane; 4]> = datas
|
||||||
|
.iter()
|
||||||
|
.map(|p| FramePlane {
|
||||||
|
fd: Some(p.as_raw().fd as _),
|
||||||
|
offset: p.chunk().offset(),
|
||||||
|
stride: p.chunk().stride(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let acquire_sync = if is_explicit {
|
||||||
|
let Some(device) = sync_device.as_ref() else {
|
||||||
|
log::error!("{name}: explicit sync without DRM timeline support");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let acquire_point = acquire_point.unwrap();
|
||||||
|
let fd = acquire_fd.unwrap();
|
||||||
|
let acquire = match import_timeline(device, fd as RawFd) {
|
||||||
|
Ok(obj) => obj,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{name}: acquire SyncObj import failed: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FrameAcquireSync::Explicit {
|
||||||
|
timeline: acquire,
|
||||||
|
point: acquire_point,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut fds = Vec::with_capacity(planes.len());
|
||||||
|
for (index, plane) in planes.iter_mut().enumerate() {
|
||||||
|
let fd = plane.fd.unwrap(); // always Some
|
||||||
|
// duplicate the fd so that we can poll it for acquire sync
|
||||||
|
let duplicate = unsafe { libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 0) };
|
||||||
|
if duplicate < 0 {
|
||||||
|
log::error!(
|
||||||
|
"{name}: failed to duplicate DMA-buf plane {index} FD {fd}: {}",
|
||||||
|
std::io::Error::last_os_error()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fds.push(unsafe { OwnedFd::from_raw_fd(duplicate) });
|
||||||
|
plane.fd = Some(duplicate);
|
||||||
|
}
|
||||||
|
FrameAcquireSync::Implicit { fds: fds.into() }
|
||||||
};
|
};
|
||||||
log::debug!("{}: Transform: {:?}", name, format.transform);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mouse_meta = buffer
|
let lease_id = held_buffers.borrow_mut().insert(buffer);
|
||||||
.find_meta::<MetaCursor>()
|
let tx = tx_ctrl.clone();
|
||||||
.filter(|c| c.is_valid())
|
let lease = FrameLease::new(move || {
|
||||||
.map(|c| MouseMeta {
|
let _ = tx.send(PwChangeRequest::ReleaseBuffer(lease_id));
|
||||||
x: c.position().x as f32 / format.width as f32,
|
|
||||||
y: c.position().y as f32 / format.height as f32,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let datas = buffer.datas_mut();
|
let mut dmabuf = DmabufFrame {
|
||||||
if datas.is_empty() {
|
format: *format,
|
||||||
log::debug!("{}: no data", name);
|
num_planes: planes.len(),
|
||||||
return;
|
planes: Default::default(),
|
||||||
}
|
mouse: mouse_meta,
|
||||||
|
acquire_sync,
|
||||||
|
lease: Some(lease),
|
||||||
|
};
|
||||||
|
|
||||||
let planes: Vec<FramePlane> = datas
|
dmabuf.planes[..planes.len()].copy_from_slice(&planes[..planes.len()]);
|
||||||
.iter()
|
|
||||||
.map(|p| FramePlane {
|
|
||||||
fd: Some(p.as_raw().fd as _),
|
|
||||||
offset: p.chunk().offset(),
|
|
||||||
stride: p.chunk().stride(),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
match datas[0].type_() {
|
if let Some(result) = receive_callback(&u, WlxFrame::Dmabuf(dmabuf)) {
|
||||||
DataType::DmaBuf => {
|
match tx_frame.try_send(result) {
|
||||||
let mut dmabuf = DmabufFrame {
|
Ok(()) | Err(mpsc::TrySendError::Full(_)) => (),
|
||||||
format: *format,
|
Err(mpsc::TrySendError::Disconnected(_)) => {
|
||||||
num_planes: planes.len(),
|
log::warn!("{name}: frame receiver disconnected");
|
||||||
mouse: mouse_meta,
|
let _ = stream.disconnect();
|
||||||
planes: Default::default(),
|
|
||||||
};
|
|
||||||
dmabuf.planes[..planes.len()].copy_from_slice(&planes[..planes.len()]);
|
|
||||||
|
|
||||||
let frame = WlxFrame::Dmabuf(dmabuf);
|
|
||||||
|
|
||||||
if let Some(r) = receive_callback(&u, frame) {
|
|
||||||
match sender.try_send(r) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(mpsc::TrySendError::Full(_)) => (),
|
|
||||||
Err(mpsc::TrySendError::Disconnected(_)) => {
|
|
||||||
log::warn!("{}: disconnected, stopping stream", name);
|
|
||||||
let _ = stream.disconnect();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType::MemFd => {
|
|
||||||
let memfd = MemFdFrame {
|
|
||||||
format: *format,
|
|
||||||
plane: FramePlane {
|
|
||||||
fd: Some(datas[0].as_raw().fd as _),
|
|
||||||
offset: datas[0].chunk().offset(),
|
|
||||||
stride: datas[0].chunk().stride(),
|
|
||||||
},
|
|
||||||
mouse: mouse_meta,
|
|
||||||
};
|
|
||||||
|
|
||||||
let frame = WlxFrame::MemFd(memfd);
|
|
||||||
if let Some(r) = receive_callback(&u, frame) {
|
|
||||||
match sender.try_send(r) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(mpsc::TrySendError::Full(_)) => (),
|
|
||||||
Err(mpsc::TrySendError::Disconnected(_)) => {
|
|
||||||
log::warn!("{}: disconnected, stopping stream", name);
|
|
||||||
let _ = stream.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType::MemPtr => {
|
|
||||||
let raw = datas[0].as_raw();
|
|
||||||
let chunk = datas[0].chunk();
|
|
||||||
let offset = chunk.offset() as usize;
|
|
||||||
let chunk_size = chunk.size() as usize;
|
|
||||||
let Some(size) = offset.checked_add(chunk_size) else {
|
|
||||||
log::error!("{name}: PipeWire chunk range overflow");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if size > raw.maxsize as usize {
|
|
||||||
log::error!("{name}: PipeWire chunk extends beyond allocation");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let memptr = MemPtrFrame {
|
|
||||||
format: *format,
|
|
||||||
ptr: raw.data as _,
|
|
||||||
size,
|
|
||||||
offset,
|
|
||||||
stride: chunk.stride() as _,
|
|
||||||
mouse: mouse_meta,
|
|
||||||
};
|
|
||||||
|
|
||||||
let frame = WlxFrame::MemPtr(memptr);
|
|
||||||
if let Some(r) = receive_callback(&u, frame) {
|
|
||||||
match sender.try_send(r) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(mpsc::TrySendError::Full(_)) => (),
|
|
||||||
Err(mpsc::TrySendError::Disconnected(_)) => {
|
|
||||||
log::warn!("{}: disconnected, stopping stream", name);
|
|
||||||
let _ = stream.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
log::error!("Received invalid frame data type ({:?})", datas[0].type_())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
DataType::MemFd => {
|
||||||
|
let memfd = MemFdFrame {
|
||||||
|
format: *format,
|
||||||
|
plane: FramePlane {
|
||||||
|
fd: Some(datas[0].as_raw().fd as _),
|
||||||
|
offset: datas[0].chunk().offset(),
|
||||||
|
stride: datas[0].chunk().stride(),
|
||||||
|
},
|
||||||
|
mouse: mouse_meta,
|
||||||
|
};
|
||||||
|
|
||||||
|
let frame = WlxFrame::MemFd(memfd);
|
||||||
|
if let Some(r) = receive_callback(&u, frame) {
|
||||||
|
match tx_frame.try_send(r) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(mpsc::TrySendError::Full(_)) => (),
|
||||||
|
Err(mpsc::TrySendError::Disconnected(_)) => {
|
||||||
|
log::warn!("{}: disconnected, stopping stream", name);
|
||||||
|
let _ = stream.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType::MemPtr => {
|
||||||
|
let raw = datas[0].as_raw();
|
||||||
|
let chunk = datas[0].chunk();
|
||||||
|
let offset = chunk.offset() as usize;
|
||||||
|
let chunk_size = chunk.size() as usize;
|
||||||
|
let Some(size) = offset.checked_add(chunk_size) else {
|
||||||
|
log::error!("{name}: PipeWire chunk range overflow");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if size > raw.maxsize as usize {
|
||||||
|
log::error!("{name}: PipeWire chunk extends beyond allocation");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let memptr = MemPtrFrame {
|
||||||
|
format: *format,
|
||||||
|
ptr: raw.data as _,
|
||||||
|
size,
|
||||||
|
offset,
|
||||||
|
stride: chunk.stride() as _,
|
||||||
|
mouse: mouse_meta,
|
||||||
|
};
|
||||||
|
|
||||||
|
let frame = WlxFrame::MemPtr(memptr);
|
||||||
|
if let Some(r) = receive_callback(&u, frame) {
|
||||||
|
match tx_frame.try_send(r) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(mpsc::TrySendError::Full(_)) => (),
|
||||||
|
Err(mpsc::TrySendError::Disconnected(_)) => {
|
||||||
|
log::warn!("{}: disconnected, stopping stream", name);
|
||||||
|
let _ = stream.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => log::error!("Received invalid frame data type ({:?})", datas[0].type_()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DMA-buf frames transfer ownership to held_buffers,
|
||||||
|
// requeued when their lease expires
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.register()?;
|
.register()?;
|
||||||
@@ -448,9 +572,10 @@ where
|
|||||||
params.as_mut_slice(),
|
params.as_mut_slice(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let _receiver = receiver.attach(main_loop.loop_(), {
|
let _receiver = rx_ctrl.attach(main_loop.loop_(), {
|
||||||
let name = name.clone();
|
let name = name.clone();
|
||||||
let main_loop = main_loop.clone();
|
let main_loop = main_loop.clone();
|
||||||
|
let held_buffers = held_buffers.clone();
|
||||||
|
|
||||||
move |req| {
|
move |req| {
|
||||||
log::debug!("{name}: request pipewire stream to {req:?}");
|
log::debug!("{name}: request pipewire stream to {req:?}");
|
||||||
@@ -465,6 +590,9 @@ where
|
|||||||
.set_active(true)
|
.set_active(true)
|
||||||
.inspect_err(|e| log::warn!("Could not {req:?} pipewire stream: {e:?}"));
|
.inspect_err(|e| log::warn!("Could not {req:?} pipewire stream: {e:?}"));
|
||||||
}
|
}
|
||||||
|
PwChangeRequest::ReleaseBuffer(id) => {
|
||||||
|
held_buffers.borrow_mut().requeue(&stream, id);
|
||||||
|
}
|
||||||
PwChangeRequest::Stop => {
|
PwChangeRequest::Stop => {
|
||||||
main_loop.quit();
|
main_loop.quit();
|
||||||
}
|
}
|
||||||
@@ -491,7 +619,25 @@ fn get_buffer_params() -> Object {
|
|||||||
| (1 << DataType::MemPtr.as_raw())
|
| (1 << DataType::MemPtr.as_raw())
|
||||||
| (1 << DataType::DmaBuf.as_raw());
|
| (1 << DataType::DmaBuf.as_raw());
|
||||||
|
|
||||||
let property = Property {
|
get_buffer_params_with_data_types(data_types)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_buffer_params_with_data_types(data_types: i32) -> Object {
|
||||||
|
// since we hold onto buffers until rendering is done,
|
||||||
|
// fewer than 4 buffers may produce stalls
|
||||||
|
let buffers = Property {
|
||||||
|
key: spa::sys::SPA_PARAM_BUFFERS_buffers,
|
||||||
|
flags: PropertyFlags::empty(),
|
||||||
|
value: Value::Choice(ChoiceValue::Int(Choice(
|
||||||
|
ChoiceFlags::empty(),
|
||||||
|
ChoiceEnum::Range {
|
||||||
|
default: 6,
|
||||||
|
min: 4,
|
||||||
|
max: MAX_PW_BUFFERS,
|
||||||
|
},
|
||||||
|
))),
|
||||||
|
};
|
||||||
|
let data_types = Property {
|
||||||
key: spa::sys::SPA_PARAM_BUFFERS_dataType,
|
key: spa::sys::SPA_PARAM_BUFFERS_dataType,
|
||||||
flags: PropertyFlags::empty(),
|
flags: PropertyFlags::empty(),
|
||||||
value: Value::Int(data_types),
|
value: Value::Int(data_types),
|
||||||
@@ -500,10 +646,35 @@ fn get_buffer_params() -> Object {
|
|||||||
spa::pod::object!(
|
spa::pod::object!(
|
||||||
spa::utils::SpaTypes::ObjectParamBuffers,
|
spa::utils::SpaTypes::ObjectParamBuffers,
|
||||||
spa::param::ParamType::Buffers,
|
spa::param::ParamType::Buffers,
|
||||||
property,
|
buffers,
|
||||||
|
data_types,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sync_buffer_params() -> Object {
|
||||||
|
let mut obj = get_buffer_params_with_data_types(1 << DataType::DmaBuf.as_raw());
|
||||||
|
obj.properties.push(Property {
|
||||||
|
key: spa::sys::SPA_PARAM_BUFFERS_blocks,
|
||||||
|
flags: PropertyFlags::empty(),
|
||||||
|
value: Value::Choice(ChoiceValue::Int(Choice(
|
||||||
|
ChoiceFlags::empty(),
|
||||||
|
ChoiceEnum::Range {
|
||||||
|
default: 3,
|
||||||
|
// when using sync timeline, we want at least 1 image plane + 2 syncobj fds
|
||||||
|
min: 3,
|
||||||
|
// up to 4 image planes
|
||||||
|
max: 6,
|
||||||
|
},
|
||||||
|
))),
|
||||||
|
});
|
||||||
|
obj.properties.push(Property {
|
||||||
|
key: SPA_PARAM_BUFFERS_META_TYPE,
|
||||||
|
flags: PropertyFlags::MANDATORY,
|
||||||
|
value: Value::Int(1_i32 << SPA_META_SYNC_TIMELINE),
|
||||||
|
});
|
||||||
|
obj
|
||||||
|
}
|
||||||
|
|
||||||
fn get_meta_object(key: u32, size: usize) -> Object {
|
fn get_meta_object(key: u32, size: usize) -> Object {
|
||||||
let meta_type_property = Property {
|
let meta_type_property = Property {
|
||||||
key: spa::sys::SPA_PARAM_META_type,
|
key: spa::sys::SPA_PARAM_META_type,
|
||||||
@@ -641,3 +812,15 @@ fn spa_to_fourcc(spa: VideoFormat) -> DrmFourcc {
|
|||||||
_ => panic!("Unsupported format"),
|
_ => panic!("Unsupported format"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct MetaSyncTimeline {
|
||||||
|
_flags: u32,
|
||||||
|
_padding: u32,
|
||||||
|
acquire_point: u64,
|
||||||
|
_release_point: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl spa::buffer::meta::Metadata for MetaSyncTimeline {
|
||||||
|
const META_TYPE: u32 = SPA_META_SYNC_TIMELINE;
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ slotmap::new_key_type! {
|
|||||||
|
|
||||||
pub mod capture;
|
pub mod capture;
|
||||||
mod dbus_screencast;
|
mod dbus_screencast;
|
||||||
|
mod raw_buffer;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PipewireStream {
|
pub struct PipewireStream {
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
use std::{marker::PhantomData, mem, ptr::NonNull};
|
||||||
|
|
||||||
|
use pipewire::{
|
||||||
|
self as pw,
|
||||||
|
spa::{self, buffer::meta::Metadata},
|
||||||
|
stream::Stream,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(super) struct DequeuedBuffer<'a> {
|
||||||
|
stream: &'a Stream,
|
||||||
|
ptr: NonNull<pw::sys::pw_buffer>,
|
||||||
|
_not_send: PhantomData<std::rc::Rc<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DequeuedBuffer<'a> {
|
||||||
|
pub(super) fn dequeue(stream: &'a Stream) -> Option<Self> {
|
||||||
|
let ptr = NonNull::new(unsafe { stream.dequeue_raw_buffer() })?;
|
||||||
|
Some(Self {
|
||||||
|
stream,
|
||||||
|
ptr,
|
||||||
|
_not_send: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spa(&self) -> Option<NonNull<spa::sys::spa_buffer>> {
|
||||||
|
NonNull::new(unsafe { self.ptr.read().buffer })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn datas_mut(&self) -> &'a mut [pw::spa::buffer::Data] {
|
||||||
|
let Some(buffer) = self.spa() else {
|
||||||
|
return &mut [];
|
||||||
|
};
|
||||||
|
unsafe {
|
||||||
|
let n_datas = buffer.read().n_datas;
|
||||||
|
if n_datas > 0
|
||||||
|
&& let Some(datas) = NonNull::new(buffer.read().datas)
|
||||||
|
{
|
||||||
|
let datas = datas.as_ptr() as *mut pw::spa::buffer::Data;
|
||||||
|
std::slice::from_raw_parts_mut(datas, usize::try_from(n_datas).unwrap())
|
||||||
|
} else {
|
||||||
|
&mut []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn find_meta<T>(&self) -> Option<&'a T>
|
||||||
|
where
|
||||||
|
T: Metadata,
|
||||||
|
{
|
||||||
|
let buffer = self.spa()?;
|
||||||
|
unsafe {
|
||||||
|
if buffer.read().n_metas != 0 {
|
||||||
|
match T::META_TYPE {
|
||||||
|
pw::spa::sys::SPA_META_VideoDamage => {
|
||||||
|
let meta_data =
|
||||||
|
pw::spa::sys::spa_buffer_find_meta(buffer.as_ptr(), T::META_TYPE)
|
||||||
|
as *const T;
|
||||||
|
if !meta_data.is_null() {
|
||||||
|
return Some(&*meta_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let meta_data = pw::spa::sys::spa_buffer_find_meta_data(
|
||||||
|
buffer.as_ptr(),
|
||||||
|
T::META_TYPE,
|
||||||
|
std::mem::size_of::<T>(),
|
||||||
|
) as *const T;
|
||||||
|
if !meta_data.is_null() {
|
||||||
|
return Some(&*meta_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) const MAX_PW_BUFFERS: i32 = 8;
|
||||||
|
|
||||||
|
impl Drop for DequeuedBuffer<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { self.stream.queue_raw_buffer(self.ptr.as_ptr()) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(super) struct HeldBuffers {
|
||||||
|
buffers: Vec<NonNull<pw::sys::pw_buffer>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HeldBuffers {
|
||||||
|
pub(super) fn insert<'a>(&mut self, buffer: DequeuedBuffer<'a>) -> usize {
|
||||||
|
self.buffers.push(buffer.ptr);
|
||||||
|
let ret_val = buffer.ptr.as_ptr() as usize;
|
||||||
|
mem::forget(buffer);
|
||||||
|
ret_val
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn on_removed(&mut self, buffer: NonNull<pw::sys::pw_buffer>) -> bool {
|
||||||
|
let before_count = self.buffers.len();
|
||||||
|
self.buffers.retain(|ptr| ptr.as_ptr() != buffer.as_ptr());
|
||||||
|
before_count != self.buffers.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn requeue(&mut self, stream: &pw::stream::Stream, id: usize) {
|
||||||
|
self.buffers.retain(|ptr| {
|
||||||
|
if ptr.as_ptr() as usize == id {
|
||||||
|
unsafe { stream.queue_raw_buffer(ptr.as_ptr()) };
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for HeldBuffers {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
log::warn!("{} checked-out buffers at shutdown", self.buffers.len());
|
||||||
|
self.buffers.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
use std::{
|
||||||
|
fs, io,
|
||||||
|
os::{
|
||||||
|
fd::{AsFd, AsRawFd, BorrowedFd, RawFd},
|
||||||
|
unix::fs::{FileTypeExt, MetadataExt},
|
||||||
|
},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use timeline_syncobj::render_node::DrmRenderNode;
|
||||||
|
use timeline_syncobj::timeline_syncobj::TimelineSyncObj;
|
||||||
|
|
||||||
|
const DRM_CAP_SYNCOBJ: u64 = 0x13;
|
||||||
|
const DRM_CAP_SYNCOBJ_TIMELINE: u64 = 0x14;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct DrmGetCap {
|
||||||
|
capability: u64,
|
||||||
|
value: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
const DRM_IOCTL_GET_CAP: libc::c_ulong = ((3_u64 << 30)
|
||||||
|
| ((std::mem::size_of::<DrmGetCap>() as u64) << 16)
|
||||||
|
| ((b'd' as u64) << 8)
|
||||||
|
| 0x0c) as libc::c_ulong;
|
||||||
|
|
||||||
|
fn get_cap(node: &DrmRenderNode, capability: u64) -> io::Result<u64> {
|
||||||
|
let mut arg = DrmGetCap {
|
||||||
|
capability,
|
||||||
|
value: 0,
|
||||||
|
};
|
||||||
|
let ret = unsafe { libc::ioctl(node.as_fd().as_raw_fd(), DRM_IOCTL_GET_CAP, &mut arg) };
|
||||||
|
if ret < 0 {
|
||||||
|
Err(io::Error::last_os_error())
|
||||||
|
} else {
|
||||||
|
Ok(arg.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supports_timeline(node: &DrmRenderNode) -> bool {
|
||||||
|
[DRM_CAP_SYNCOBJ, DRM_CAP_SYNCOBJ_TIMELINE]
|
||||||
|
.into_iter()
|
||||||
|
.all(|cap| get_cap(node, cap).is_ok_and(|value| value != 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_render_node(render_major: u32, render_minor: u32) -> io::Result<DrmRenderNode> {
|
||||||
|
if !(128..=255).contains(&render_minor) {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::Unsupported,
|
||||||
|
format!("Unsupported DRM render minor: {render_minor}"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = format!("/dev/dri/renderD{render_minor}");
|
||||||
|
let metadata = fs::metadata(&path)?;
|
||||||
|
|
||||||
|
// verify that device actually matches the provided minor/major
|
||||||
|
if !metadata.file_type().is_char_device()
|
||||||
|
|| metadata.rdev() != libc::makedev(render_major, render_minor)
|
||||||
|
{
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
format!("{path} does not match DRM device {render_major}:{render_minor}"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let node = DrmRenderNode::new(render_minor.into())
|
||||||
|
.map_err(|e| io::Error::from_raw_os_error(e.raw_os_error()))?;
|
||||||
|
|
||||||
|
if !supports_timeline(&node) {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::Unsupported,
|
||||||
|
format!("{path} does not support DRM syncobj timelines"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn import_timeline(node: &DrmRenderNode, fd: RawFd) -> io::Result<Arc<TimelineSyncObj>> {
|
||||||
|
if fd < 0 {
|
||||||
|
return Err(io::Error::from_raw_os_error(libc::EBADF));
|
||||||
|
}
|
||||||
|
let fd = unsafe { BorrowedFd::borrow_raw(fd) };
|
||||||
|
TimelineSyncObj::import(node, fd)
|
||||||
|
.map(Arc::new)
|
||||||
|
.map_err(|e| io::Error::from_raw_os_error(e.raw_os_error()))
|
||||||
|
}
|
||||||
@@ -11,10 +11,7 @@ use smithay_client_toolkit::reexports::{
|
|||||||
zxdg_output_manager_v1::ZxdgOutputManagerV1,
|
zxdg_output_manager_v1::ZxdgOutputManagerV1,
|
||||||
zxdg_output_v1::{self, ZxdgOutputV1},
|
zxdg_output_v1::{self, ZxdgOutputV1},
|
||||||
},
|
},
|
||||||
protocols_wlr::{
|
protocols_wlr::screencopy::v1::client::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1,
|
||||||
export_dmabuf::v1::client::zwlr_export_dmabuf_manager_v1::ZwlrExportDmabufManagerV1,
|
|
||||||
screencopy::v1::client::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use wayland_client::{
|
use wayland_client::{
|
||||||
@@ -61,7 +58,6 @@ pub struct WlxOutput {
|
|||||||
pub struct WlxClient {
|
pub struct WlxClient {
|
||||||
pub connection: Arc<Connection>,
|
pub connection: Arc<Connection>,
|
||||||
pub xdg_output_mgr: ZxdgOutputManagerV1,
|
pub xdg_output_mgr: ZxdgOutputManagerV1,
|
||||||
pub maybe_wlr_dmabuf_mgr: Option<ZwlrExportDmabufManagerV1>,
|
|
||||||
pub maybe_wlr_screencopy_mgr: Option<ZwlrScreencopyManagerV1>,
|
pub maybe_wlr_screencopy_mgr: Option<ZwlrScreencopyManagerV1>,
|
||||||
pub maybe_zwp_linux_dmabuf: Option<ZwpLinuxDmabufV1>,
|
pub maybe_zwp_linux_dmabuf: Option<ZwpLinuxDmabufV1>,
|
||||||
pub wl_seat: WlSeat,
|
pub wl_seat: WlSeat,
|
||||||
@@ -93,7 +89,6 @@ impl WlxClient {
|
|||||||
.bind(&qh, 4..=9, ())
|
.bind(&qh, 4..=9, ())
|
||||||
.expect(WlSeat::interface().name),
|
.expect(WlSeat::interface().name),
|
||||||
wl_shm: globals.bind(&qh, 1..=1, ()).expect(WlShm::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, 3..=3, ()).ok(),
|
maybe_wlr_screencopy_mgr: globals.bind(&qh, 3..=3, ()).ok(),
|
||||||
maybe_zwp_linux_dmabuf: globals.bind(&qh, 4..=4, ()).ok(),
|
maybe_zwp_linux_dmabuf: globals.bind(&qh, 4..=4, ()).ok(),
|
||||||
outputs: IdMap::new(),
|
outputs: IdMap::new(),
|
||||||
@@ -398,18 +393,6 @@ impl Dispatch<ZxdgOutputManagerV1, ()> for WlxClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dispatch<ZwlrExportDmabufManagerV1, ()> for WlxClient {
|
|
||||||
fn event(
|
|
||||||
_state: &mut Self,
|
|
||||||
_proxy: &ZwlrExportDmabufManagerV1,
|
|
||||||
_event: <ZwlrExportDmabufManagerV1 as Proxy>::Event,
|
|
||||||
_data: &(),
|
|
||||||
_conn: &Connection,
|
|
||||||
_qhandle: &QueueHandle<Self>,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dispatch<ZwlrScreencopyManagerV1, ()> for WlxClient {
|
impl Dispatch<ZwlrScreencopyManagerV1, ()> for WlxClient {
|
||||||
fn event(
|
fn event(
|
||||||
_state: &mut Self,
|
_state: &mut Self,
|
||||||
|
|||||||
@@ -1,258 +0,0 @@
|
|||||||
use std::{
|
|
||||||
any::Any,
|
|
||||||
collections::VecDeque,
|
|
||||||
os::fd::{FromRawFd, IntoRawFd, OwnedFd, RawFd},
|
|
||||||
sync::mpsc,
|
|
||||||
thread::JoinHandle,
|
|
||||||
};
|
|
||||||
|
|
||||||
use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier};
|
|
||||||
use smithay_client_toolkit::reexports::protocols_wlr::export_dmabuf::v1::client::zwlr_export_dmabuf_frame_v1::{self, ZwlrExportDmabufFrameV1};
|
|
||||||
use wayland_client::{Connection, QueueHandle, Dispatch, Proxy};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
WlxCapture,
|
|
||||||
frame::{DmabufFrame, FrameFormat, FramePlane, WlxFrame},
|
|
||||||
wayland::WlxClient,
|
|
||||||
};
|
|
||||||
|
|
||||||
use log::{debug, warn};
|
|
||||||
|
|
||||||
struct CaptureData<U, R>
|
|
||||||
where
|
|
||||||
U: Any,
|
|
||||||
R: Any,
|
|
||||||
{
|
|
||||||
sender: mpsc::SyncSender<WlxFrame>,
|
|
||||||
receiver: mpsc::Receiver<WlxFrame>,
|
|
||||||
user_data: U,
|
|
||||||
receive_callback: fn(&U, WlxFrame) -> Option<R>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct WlrDmabufCapture<U, R>
|
|
||||||
where
|
|
||||||
U: Any + Send,
|
|
||||||
R: Any + Send,
|
|
||||||
{
|
|
||||||
output_id: u32,
|
|
||||||
wl: Option<Box<WlxClient>>,
|
|
||||||
handle: Option<JoinHandle<Box<WlxClient>>>,
|
|
||||||
data: Option<CaptureData<U, R>>,
|
|
||||||
fds: VecDeque<RawFd>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<U, R> WlrDmabufCapture<U, R>
|
|
||||||
where
|
|
||||||
U: Any + Send,
|
|
||||||
R: Any + Send,
|
|
||||||
{
|
|
||||||
pub fn new(wl: WlxClient, output_id: u32) -> Self {
|
|
||||||
Self {
|
|
||||||
output_id,
|
|
||||||
wl: Some(Box::new(wl)),
|
|
||||||
handle: None,
|
|
||||||
data: None,
|
|
||||||
fds: VecDeque::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<U, R> WlxCapture<U, R> for WlrDmabufCapture<U, R>
|
|
||||||
where
|
|
||||||
U: Any + Send,
|
|
||||||
R: Any + Send,
|
|
||||||
{
|
|
||||||
fn init(
|
|
||||||
&mut self,
|
|
||||||
_: &[DrmFormat],
|
|
||||||
user_data: U,
|
|
||||||
receive_callback: fn(&U, WlxFrame) -> Option<R>,
|
|
||||||
) {
|
|
||||||
debug_assert!(self.wl.is_some());
|
|
||||||
|
|
||||||
let (sender, receiver) = std::sync::mpsc::sync_channel::<WlxFrame>(2);
|
|
||||||
self.data = Some(CaptureData {
|
|
||||||
sender,
|
|
||||||
receiver,
|
|
||||||
user_data,
|
|
||||||
receive_callback,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
fn is_ready(&self) -> bool {
|
|
||||||
self.data.is_some()
|
|
||||||
}
|
|
||||||
fn supports_dmbuf(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
fn receive(&mut self) -> Option<R> {
|
|
||||||
if let Some(data) = self.data.as_ref()
|
|
||||||
&& let Some(WlxFrame::Dmabuf(last)) = data.receiver.try_iter().last()
|
|
||||||
{
|
|
||||||
// this is the only protocol that requires us to manually close the FD
|
|
||||||
while self.fds.len() > 6 * last.num_planes {
|
|
||||||
// safe unwrap
|
|
||||||
let _ = unsafe { OwnedFd::from_raw_fd(self.fds.pop_back().unwrap()) };
|
|
||||||
}
|
|
||||||
for p in 0..last.num_planes {
|
|
||||||
if let Some(fd) = last.planes[p].fd {
|
|
||||||
self.fds.push_front(fd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (data.receive_callback)(&data.user_data, WlxFrame::Dmabuf(last));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
fn pause(&mut self) {}
|
|
||||||
fn resume(&mut self) {
|
|
||||||
self.receive(); // clear old frames
|
|
||||||
}
|
|
||||||
fn request_new_frame(&mut self) {
|
|
||||||
if let Some(handle) = self.handle.take() {
|
|
||||||
if handle.is_finished() {
|
|
||||||
self.wl = Some(handle.join().unwrap()); // safe to unwrap because we checked is_finished
|
|
||||||
} else {
|
|
||||||
self.handle = Some(handle);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(wl) = self.wl.take() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.handle = Some(std::thread::spawn({
|
|
||||||
let sender = self
|
|
||||||
.data
|
|
||||||
.as_ref()
|
|
||||||
.expect("must call init once before request_new_frame")
|
|
||||||
.sender
|
|
||||||
.clone();
|
|
||||||
let output_id = self.output_id;
|
|
||||||
move || request_dmabuf_frame(wl, output_id, sender)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Request a new DMA-Buf frame using the wlr-export-dmabuf protocol.
|
|
||||||
fn request_dmabuf_frame(
|
|
||||||
client: Box<WlxClient>,
|
|
||||||
output_id: u32,
|
|
||||||
sender: mpsc::SyncSender<WlxFrame>,
|
|
||||||
) -> Box<WlxClient> {
|
|
||||||
let Some(dmabuf_manager) = client.maybe_wlr_dmabuf_mgr.as_ref() else {
|
|
||||||
return client;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(output) = client.outputs.get(output_id) else {
|
|
||||||
return client;
|
|
||||||
};
|
|
||||||
|
|
||||||
let transform = output.transform;
|
|
||||||
|
|
||||||
let (tx, rx) = mpsc::sync_channel::<zwlr_export_dmabuf_frame_v1::Event>(16);
|
|
||||||
let name = output.name.clone();
|
|
||||||
|
|
||||||
let _ = dmabuf_manager.capture_output(1, &output.wl_output, &client.queue_handle, tx.clone());
|
|
||||||
|
|
||||||
let mut client = client;
|
|
||||||
client.dispatch();
|
|
||||||
|
|
||||||
let mut frame = None;
|
|
||||||
|
|
||||||
rx.try_iter().for_each(|event| match event {
|
|
||||||
zwlr_export_dmabuf_frame_v1::Event::Frame {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
format,
|
|
||||||
mod_high,
|
|
||||||
mod_low,
|
|
||||||
num_objects,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let mut new_frame = DmabufFrame {
|
|
||||||
format: FrameFormat {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
drm_format: DrmFormat {
|
|
||||||
code: match DrmFourcc::try_from(format) {
|
|
||||||
Ok(code) => code,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Unrecognized fourcc: {e:?}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier: DrmModifier::Invalid,
|
|
||||||
},
|
|
||||||
transform,
|
|
||||||
},
|
|
||||||
mouse: None,
|
|
||||||
num_planes: num_objects as _,
|
|
||||||
planes: Default::default(),
|
|
||||||
};
|
|
||||||
new_frame.format.set_mod(mod_high, mod_low);
|
|
||||||
frame = Some(new_frame);
|
|
||||||
}
|
|
||||||
zwlr_export_dmabuf_frame_v1::Event::Object {
|
|
||||||
index,
|
|
||||||
fd,
|
|
||||||
offset,
|
|
||||||
stride,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let Some(ref mut frame) = frame else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
frame.planes[index as usize] = FramePlane {
|
|
||||||
fd: Some(fd.into_raw_fd()),
|
|
||||||
offset,
|
|
||||||
stride: stride as _,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
zwlr_export_dmabuf_frame_v1::Event::Ready { .. } => {
|
|
||||||
let Some(frame) = frame.take() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
debug!("DMA-Buf frame captured");
|
|
||||||
let frame = WlxFrame::Dmabuf(frame);
|
|
||||||
match sender.try_send(frame) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(mpsc::TrySendError::Full(_)) => (),
|
|
||||||
Err(mpsc::TrySendError::Disconnected(_)) => {
|
|
||||||
log::warn!("{}: disconnected", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
zwlr_export_dmabuf_frame_v1::Event::Cancel { .. } => {
|
|
||||||
warn!("DMA-Buf frame capture cancelled");
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
});
|
|
||||||
|
|
||||||
client
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dispatch<ZwlrExportDmabufFrameV1, mpsc::SyncSender<zwlr_export_dmabuf_frame_v1::Event>>
|
|
||||||
for WlxClient
|
|
||||||
{
|
|
||||||
fn event(
|
|
||||||
_state: &mut Self,
|
|
||||||
proxy: &ZwlrExportDmabufFrameV1,
|
|
||||||
event: <ZwlrExportDmabufFrameV1 as Proxy>::Event,
|
|
||||||
data: &mpsc::SyncSender<zwlr_export_dmabuf_frame_v1::Event>,
|
|
||||||
_conn: &Connection,
|
|
||||||
_qhandle: &QueueHandle<Self>,
|
|
||||||
) {
|
|
||||||
match event {
|
|
||||||
zwlr_export_dmabuf_frame_v1::Event::Ready { .. }
|
|
||||||
| zwlr_export_dmabuf_frame_v1::Event::Cancel { .. } => {
|
|
||||||
proxy.destroy();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = data.send(event).or_else(|err| {
|
|
||||||
warn!("Failed to send DMA-Buf frame event: {}", err);
|
|
||||||
Ok::<(), mpsc::SendError<zwlr_export_dmabuf_frame_v1::Event>>(())
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user