mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
Implement PipeWire DMA-BUF modifier negotiation
This commit is contained in:
Generated
+4
-8
@@ -2002,8 +2002,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "libspa"
|
name = "libspa"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git#016e554768ae251c8de3724ea4b06f4749a8dd00"
|
||||||
checksum = "65f3a4b81b2a2d8c7f300643676202debd1b7c929dbf5c9bb89402ea11d19810"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"cc",
|
"cc",
|
||||||
@@ -2019,8 +2018,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "libspa-sys"
|
name = "libspa-sys"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git#016e554768ae251c8de3724ea4b06f4749a8dd00"
|
||||||
checksum = "bf0d9716420364790e85cbb9d3ac2c950bde16a7dd36f3209b7dfdfc4a24d01f"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"cc",
|
"cc",
|
||||||
@@ -2849,8 +2847,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "pipewire"
|
name = "pipewire"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git#016e554768ae251c8de3724ea4b06f4749a8dd00"
|
||||||
checksum = "08e645ba5c45109106d56610b3ee60eb13a6f2beb8b74f8dc8186cf261788dda"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
@@ -2866,8 +2863,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "pipewire-sys"
|
name = "pipewire-sys"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs.git#016e554768ae251c8de3724ea4b06f4749a8dd00"
|
||||||
checksum = "849e188f90b1dda88fe2bfe1ad31fe5f158af2c98f80fb5d13726c44f3f01112"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"libspa-sys",
|
"libspa-sys",
|
||||||
|
|||||||
+1
-1
@@ -66,7 +66,7 @@ notify-rust = { version = "~4.10.0", optional = true }
|
|||||||
ordered-float = "4.2.1"
|
ordered-float = "4.2.1"
|
||||||
pango = { version = "0.20.0", features = ["v1_44"] }
|
pango = { version = "0.20.0", features = ["v1_44"] }
|
||||||
pangocairo = "0.20.0"
|
pangocairo = "0.20.0"
|
||||||
pipewire = { version = "0.8.0", optional = true }
|
pipewire = { git = "https://gitlab.freedesktop.org/pipewire/pipewire-rs.git", optional = true, features = ["v0_3_33"] }
|
||||||
png = "0.17.13"
|
png = "0.17.13"
|
||||||
portable-atomic = { version = "1.7.0", default-features = false, features = ["float"] }
|
portable-atomic = { version = "1.7.0", default-features = false, features = ["float"] }
|
||||||
profiling = "1.0.15"
|
profiling = "1.0.15"
|
||||||
|
|||||||
@@ -1369,8 +1369,16 @@ impl State {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let render_formats = self
|
||||||
|
.backend
|
||||||
|
.with_primary_renderer(|renderer| {
|
||||||
|
renderer.egl_context().dmabuf_render_formats().clone()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let res = pw.start_cast(
|
let res = pw.start_cast(
|
||||||
gbm,
|
gbm,
|
||||||
|
render_formats,
|
||||||
session_id,
|
session_id,
|
||||||
target,
|
target,
|
||||||
size,
|
size,
|
||||||
|
|||||||
+409
-134
@@ -1,6 +1,7 @@
|
|||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
use std::iter::zip;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd};
|
use std::os::fd::{AsFd, AsRawFd, BorrowedFd};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@@ -16,16 +17,19 @@ use pipewire::spa::param::format::{FormatProperties, MediaSubtype, MediaType};
|
|||||||
use pipewire::spa::param::format_utils::parse_format;
|
use pipewire::spa::param::format_utils::parse_format;
|
||||||
use pipewire::spa::param::video::{VideoFormat, VideoInfoRaw};
|
use pipewire::spa::param::video::{VideoFormat, VideoInfoRaw};
|
||||||
use pipewire::spa::param::ParamType;
|
use pipewire::spa::param::ParamType;
|
||||||
|
use pipewire::spa::pod::deserialize::PodDeserializer;
|
||||||
use pipewire::spa::pod::serialize::PodSerializer;
|
use pipewire::spa::pod::serialize::PodSerializer;
|
||||||
use pipewire::spa::pod::{self, ChoiceValue, Pod, Property, PropertyFlags};
|
use pipewire::spa::pod::{self, ChoiceValue, Pod, PodPropFlags, Property, PropertyFlags};
|
||||||
use pipewire::spa::sys::*;
|
use pipewire::spa::sys::*;
|
||||||
use pipewire::spa::utils::{
|
use pipewire::spa::utils::{
|
||||||
Choice, ChoiceEnum, ChoiceFlags, Direction, Fraction, Rectangle, SpaTypes,
|
Choice, ChoiceEnum, ChoiceFlags, Direction, Fraction, Rectangle, SpaTypes,
|
||||||
};
|
};
|
||||||
|
use pipewire::spa::{self};
|
||||||
use pipewire::stream::{Stream, StreamFlags, StreamListener, StreamState};
|
use pipewire::stream::{Stream, StreamFlags, StreamListener, StreamState};
|
||||||
use smithay::backend::allocator::dmabuf::{AsDmabuf, Dmabuf};
|
use smithay::backend::allocator::dmabuf::{AsDmabuf, Dmabuf};
|
||||||
|
use smithay::backend::allocator::format::FormatSet;
|
||||||
use smithay::backend::allocator::gbm::{GbmBuffer, GbmBufferFlags, GbmDevice};
|
use smithay::backend::allocator::gbm::{GbmBuffer, GbmBufferFlags, GbmDevice};
|
||||||
use smithay::backend::allocator::Fourcc;
|
use smithay::backend::allocator::{Format, Fourcc};
|
||||||
use smithay::backend::drm::DrmDeviceFd;
|
use smithay::backend::drm::DrmDeviceFd;
|
||||||
use smithay::backend::renderer::element::RenderElement;
|
use smithay::backend::renderer::element::RenderElement;
|
||||||
use smithay::backend::renderer::gles::GlesRenderer;
|
use smithay::backend::renderer::gles::GlesRenderer;
|
||||||
@@ -57,22 +61,32 @@ pub struct Cast {
|
|||||||
_listener: StreamListener<()>,
|
_listener: StreamListener<()>,
|
||||||
pub is_active: Rc<Cell<bool>>,
|
pub is_active: Rc<Cell<bool>>,
|
||||||
pub target: CastTarget,
|
pub target: CastTarget,
|
||||||
pub size: Rc<Cell<CastSize>>,
|
formats: FormatSet,
|
||||||
pub refresh: u32,
|
state: Rc<RefCell<CastState>>,
|
||||||
|
refresh: Rc<Cell<u32>>,
|
||||||
offer_alpha: bool,
|
offer_alpha: bool,
|
||||||
pub cursor_mode: CursorMode,
|
pub cursor_mode: CursorMode,
|
||||||
pub last_frame_time: Duration,
|
pub last_frame_time: Duration,
|
||||||
pub min_time_between_frames: Rc<Cell<Duration>>,
|
min_time_between_frames: Rc<Cell<Duration>>,
|
||||||
pub dmabufs: Rc<RefCell<HashMap<i32, Dmabuf>>>,
|
dmabufs: Rc<RefCell<HashMap<i64, Dmabuf>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug)]
|
||||||
pub enum CastSize {
|
pub enum CastState {
|
||||||
InitialPending(Size<i32, Physical>),
|
ResizePending {
|
||||||
Ready(Size<i32, Physical>),
|
pending_size: Size<u32, Physical>,
|
||||||
ChangePending {
|
},
|
||||||
last_negotiated: Size<i32, Physical>,
|
ConfirmationPending {
|
||||||
pending: Size<i32, Physical>,
|
size: Size<u32, Physical>,
|
||||||
|
alpha: bool,
|
||||||
|
modifier: Modifier,
|
||||||
|
plane_count: i32,
|
||||||
|
},
|
||||||
|
Ready {
|
||||||
|
size: Size<u32, Physical>,
|
||||||
|
alpha: bool,
|
||||||
|
modifier: Modifier,
|
||||||
|
plane_count: i32,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,17 +103,17 @@ pub enum CastTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! make_params {
|
macro_rules! make_params {
|
||||||
($params:ident, $size:expr, $refresh:expr, $alpha:expr) => {
|
($params:ident, $formats:expr, $size:expr, $refresh:expr, $alpha:expr) => {
|
||||||
let mut b1 = Vec::new();
|
let mut b1 = Vec::new();
|
||||||
let mut b2 = Vec::new();
|
let mut b2 = Vec::new();
|
||||||
|
|
||||||
let o1 = make_video_params($size, $refresh, false);
|
let o1 = make_video_params($formats, $size, $refresh, false);
|
||||||
let pod1 = make_pod(&mut b1, o1);
|
let pod1 = make_pod(&mut b1, o1);
|
||||||
|
|
||||||
let mut p1;
|
let mut p1;
|
||||||
let mut p2;
|
let mut p2;
|
||||||
$params = if $alpha {
|
$params = if $alpha {
|
||||||
let o2 = make_video_params($size, $refresh, true);
|
let o2 = make_video_params($formats, $size, $refresh, true);
|
||||||
p2 = [pod1, make_pod(&mut b2, o2)];
|
p2 = [pod1, make_pod(&mut b2, o2)];
|
||||||
&mut p2[..]
|
&mut p2[..]
|
||||||
} else {
|
} else {
|
||||||
@@ -157,6 +171,7 @@ impl PipeWire {
|
|||||||
pub fn start_cast(
|
pub fn start_cast(
|
||||||
&self,
|
&self,
|
||||||
gbm: GbmDevice<DrmDeviceFd>,
|
gbm: GbmDevice<DrmDeviceFd>,
|
||||||
|
formats: FormatSet,
|
||||||
session_id: usize,
|
session_id: usize,
|
||||||
target: CastTarget,
|
target: CastTarget,
|
||||||
size: Size<i32, Physical>,
|
size: Size<i32, Physical>,
|
||||||
@@ -190,10 +205,10 @@ impl PipeWire {
|
|||||||
let is_active = Rc::new(Cell::new(false));
|
let is_active = Rc::new(Cell::new(false));
|
||||||
let min_time_between_frames = Rc::new(Cell::new(Duration::ZERO));
|
let min_time_between_frames = Rc::new(Cell::new(Duration::ZERO));
|
||||||
let dmabufs = Rc::new(RefCell::new(HashMap::new()));
|
let dmabufs = Rc::new(RefCell::new(HashMap::new()));
|
||||||
let negotiated_alpha = Rc::new(Cell::new(false));
|
let refresh = Rc::new(Cell::new(refresh));
|
||||||
|
|
||||||
let pending_size = size;
|
let pending_size = Size::from((size.w as u32, size.h as u32));
|
||||||
let size = Rc::new(Cell::new(CastSize::InitialPending(size)));
|
let state = Rc::new(RefCell::new(CastState::ResizePending { pending_size }));
|
||||||
|
|
||||||
let listener = stream
|
let listener = stream
|
||||||
.add_local_listener_with_user_data(())
|
.add_local_listener_with_user_data(())
|
||||||
@@ -244,8 +259,11 @@ impl PipeWire {
|
|||||||
})
|
})
|
||||||
.param_changed({
|
.param_changed({
|
||||||
let min_time_between_frames = min_time_between_frames.clone();
|
let min_time_between_frames = min_time_between_frames.clone();
|
||||||
let size = size.clone();
|
let stop_cast = stop_cast.clone();
|
||||||
let negotiated_alpha = negotiated_alpha.clone();
|
let state = state.clone();
|
||||||
|
let gbm = gbm.clone();
|
||||||
|
let formats = formats.clone();
|
||||||
|
let refresh = refresh.clone();
|
||||||
move |stream, (), id, pod| {
|
move |stream, (), id, pod| {
|
||||||
let id = ParamType::from_raw(id);
|
let id = ParamType::from_raw(id);
|
||||||
trace!(?id, "pw stream: param_changed");
|
trace!(?id, "pw stream: param_changed");
|
||||||
@@ -270,22 +288,28 @@ impl PipeWire {
|
|||||||
|
|
||||||
let mut format = VideoInfoRaw::new();
|
let mut format = VideoInfoRaw::new();
|
||||||
format.parse(pod).unwrap();
|
format.parse(pod).unwrap();
|
||||||
trace!("pw stream: got format = {format:?}");
|
debug!("pw stream: got format = {format:?}");
|
||||||
|
|
||||||
let expected_size = size.get().expected_format_size();
|
let format_size = Size::from((format.size().width, format.size().height));
|
||||||
let format_size =
|
|
||||||
Size::from((format.size().width as i32, format.size().height as i32));
|
|
||||||
|
|
||||||
if format_size == expected_size {
|
let mut state = state.borrow_mut();
|
||||||
size.set(CastSize::Ready(expected_size));
|
if format_size != state.expected_format_size() {
|
||||||
} else {
|
if !matches!(&*state, CastState::ResizePending { .. }) {
|
||||||
size.set(CastSize::ChangePending {
|
warn!("pw stream: wrong size, but we're not resizing");
|
||||||
last_negotiated: format_size,
|
stop_cast();
|
||||||
pending: expected_size,
|
return;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
debug!("pw stream: wrong size, waiting");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
negotiated_alpha.set(format.format() == VideoFormat::BGRA);
|
let format_has_alpha = format.format() == VideoFormat::BGRA;
|
||||||
|
let fourcc = if format_has_alpha {
|
||||||
|
Fourcc::Argb8888
|
||||||
|
} else {
|
||||||
|
Fourcc::Xrgb8888
|
||||||
|
};
|
||||||
|
|
||||||
let max_frame_rate = format.max_framerate();
|
let max_frame_rate = format.max_framerate();
|
||||||
// Subtract 0.5 ms to improve edge cases when equal to refresh rate.
|
// Subtract 0.5 ms to improve edge cases when equal to refresh rate.
|
||||||
@@ -294,9 +318,161 @@ impl PipeWire {
|
|||||||
) - Duration::from_micros(500);
|
) - Duration::from_micros(500);
|
||||||
min_time_between_frames.set(min_frame_time);
|
min_time_between_frames.set(min_frame_time);
|
||||||
|
|
||||||
const BPP: u32 = 4;
|
let object = pod.as_object().unwrap();
|
||||||
let stride = format.size().width * BPP;
|
let Some(prop_modifier) =
|
||||||
let size = stride * format.size().height;
|
object.find_prop(spa::utils::Id(FormatProperties::VideoModifier.0))
|
||||||
|
else {
|
||||||
|
warn!("pw stream: modifier prop missing");
|
||||||
|
stop_cast();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if prop_modifier.flags().contains(PodPropFlags::DONT_FIXATE) {
|
||||||
|
debug!("pw stream: fixating the modifier");
|
||||||
|
|
||||||
|
let pod_modifier = prop_modifier.value();
|
||||||
|
let Ok((_, modifiers)) = PodDeserializer::deserialize_from::<Choice<i64>>(
|
||||||
|
pod_modifier.as_bytes(),
|
||||||
|
) else {
|
||||||
|
warn!("pw stream: wrong modifier property type");
|
||||||
|
stop_cast();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let ChoiceEnum::Enum { alternatives, .. } = modifiers.1 else {
|
||||||
|
warn!("pw stream: wrong modifier choice type");
|
||||||
|
stop_cast();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let (modifier, plane_count) = match find_preferred_modifier(
|
||||||
|
&gbm,
|
||||||
|
format_size,
|
||||||
|
fourcc,
|
||||||
|
alternatives,
|
||||||
|
) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(err) => {
|
||||||
|
warn!("pw stream: couldn't find preferred modifier: {err:?}");
|
||||||
|
stop_cast();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"pw stream: allocation successful \
|
||||||
|
(modifier={modifier:?}, plane_count={plane_count}), \
|
||||||
|
moving to confirmation pending"
|
||||||
|
);
|
||||||
|
|
||||||
|
*state = CastState::ConfirmationPending {
|
||||||
|
size: format_size,
|
||||||
|
alpha: format_has_alpha,
|
||||||
|
modifier,
|
||||||
|
plane_count: plane_count as i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
let fixated_format = FormatSet::from_iter([Format {
|
||||||
|
code: fourcc,
|
||||||
|
modifier,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
let mut b1 = Vec::new();
|
||||||
|
let mut b2 = Vec::new();
|
||||||
|
|
||||||
|
let o1 = make_video_params(
|
||||||
|
&fixated_format,
|
||||||
|
format_size,
|
||||||
|
refresh.get(),
|
||||||
|
format_has_alpha,
|
||||||
|
);
|
||||||
|
let pod1 = make_pod(&mut b1, o1);
|
||||||
|
|
||||||
|
let o2 = make_video_params(
|
||||||
|
&formats,
|
||||||
|
format_size,
|
||||||
|
refresh.get(),
|
||||||
|
format_has_alpha,
|
||||||
|
);
|
||||||
|
let mut params = [pod1, make_pod(&mut b2, o2)];
|
||||||
|
|
||||||
|
if let Err(err) = stream.update_params(&mut params) {
|
||||||
|
warn!("error updating stream params: {err:?}");
|
||||||
|
stop_cast();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that alpha and modifier didn't change.
|
||||||
|
let plane_count = match &*state {
|
||||||
|
CastState::ConfirmationPending {
|
||||||
|
size,
|
||||||
|
alpha,
|
||||||
|
modifier,
|
||||||
|
plane_count,
|
||||||
|
}
|
||||||
|
| CastState::Ready {
|
||||||
|
size,
|
||||||
|
alpha,
|
||||||
|
modifier,
|
||||||
|
plane_count,
|
||||||
|
} if *alpha == format_has_alpha
|
||||||
|
&& *modifier == Modifier::from(format.modifier()) =>
|
||||||
|
{
|
||||||
|
let size = *size;
|
||||||
|
let alpha = *alpha;
|
||||||
|
let modifier = *modifier;
|
||||||
|
let plane_count = *plane_count;
|
||||||
|
|
||||||
|
debug!("pw stream: moving to ready state");
|
||||||
|
|
||||||
|
*state = CastState::Ready {
|
||||||
|
size,
|
||||||
|
alpha,
|
||||||
|
modifier,
|
||||||
|
plane_count,
|
||||||
|
};
|
||||||
|
|
||||||
|
plane_count
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// We're negotiating a single modifier, or alpha or modifier changed,
|
||||||
|
// so we need to do a test allocation.
|
||||||
|
let (modifier, plane_count) = match find_preferred_modifier(
|
||||||
|
&gbm,
|
||||||
|
format_size,
|
||||||
|
fourcc,
|
||||||
|
vec![format.modifier() as i64],
|
||||||
|
) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(err) => {
|
||||||
|
warn!("pw stream: test allocation failed: {err:?}");
|
||||||
|
stop_cast();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"pw stream: allocation successful \
|
||||||
|
(modifier={modifier:?}, plane_count={plane_count}), \
|
||||||
|
moving to ready"
|
||||||
|
);
|
||||||
|
|
||||||
|
*state = CastState::Ready {
|
||||||
|
size: format_size,
|
||||||
|
alpha: format_has_alpha,
|
||||||
|
modifier,
|
||||||
|
plane_count: plane_count as i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
plane_count as i32
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// const BPP: u32 = 4;
|
||||||
|
// let stride = format.size().width * BPP;
|
||||||
|
// let size = stride * format.size().height;
|
||||||
|
|
||||||
let o1 = pod::object!(
|
let o1 = pod::object!(
|
||||||
SpaTypes::ObjectParamBuffers,
|
SpaTypes::ObjectParamBuffers,
|
||||||
@@ -312,10 +488,10 @@ impl PipeWire {
|
|||||||
}
|
}
|
||||||
))),
|
))),
|
||||||
),
|
),
|
||||||
Property::new(SPA_PARAM_BUFFERS_blocks, pod::Value::Int(1)),
|
Property::new(SPA_PARAM_BUFFERS_blocks, pod::Value::Int(plane_count)),
|
||||||
Property::new(SPA_PARAM_BUFFERS_size, pod::Value::Int(size as i32)),
|
// Property::new(SPA_PARAM_BUFFERS_size, pod::Value::Int(size as i32)),
|
||||||
Property::new(SPA_PARAM_BUFFERS_stride, pod::Value::Int(stride as i32)),
|
// Property::new(SPA_PARAM_BUFFERS_stride, pod::Value::Int(stride as i32)),
|
||||||
Property::new(SPA_PARAM_BUFFERS_align, pod::Value::Int(16)),
|
// Property::new(SPA_PARAM_BUFFERS_align, pod::Value::Int(16)),
|
||||||
Property::new(
|
Property::new(
|
||||||
SPA_PARAM_BUFFERS_dataType,
|
SPA_PARAM_BUFFERS_dataType,
|
||||||
pod::Value::Choice(ChoiceValue::Int(Choice(
|
pod::Value::Choice(ChoiceValue::Int(Choice(
|
||||||
@@ -345,25 +521,38 @@ impl PipeWire {
|
|||||||
let mut params = [
|
let mut params = [
|
||||||
make_pod(&mut b1, o1), // make_pod(&mut b2, o2)
|
make_pod(&mut b1, o1), // make_pod(&mut b2, o2)
|
||||||
];
|
];
|
||||||
stream.update_params(&mut params).unwrap();
|
|
||||||
|
if let Err(err) = stream.update_params(&mut params) {
|
||||||
|
warn!("error updating stream params: {err:?}");
|
||||||
|
stop_cast();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.add_buffer({
|
.add_buffer({
|
||||||
let dmabufs = dmabufs.clone();
|
let dmabufs = dmabufs.clone();
|
||||||
let stop_cast = stop_cast.clone();
|
let stop_cast = stop_cast.clone();
|
||||||
let size = size.clone();
|
let state = state.clone();
|
||||||
let negotiated_alpha = negotiated_alpha.clone();
|
|
||||||
move |stream, (), buffer| {
|
move |stream, (), buffer| {
|
||||||
let size = size.get().negotiated_size();
|
let (size, alpha, modifier) = if let CastState::Ready {
|
||||||
let alpha = negotiated_alpha.get();
|
size,
|
||||||
trace!("pw stream: add_buffer, size={:?}, alpha={alpha}", size);
|
alpha,
|
||||||
let size = size.expect("size must be negotiated to allocate buffers");
|
modifier,
|
||||||
|
..
|
||||||
|
} = &*state.borrow()
|
||||||
|
{
|
||||||
|
(*size, *alpha, *modifier)
|
||||||
|
} else {
|
||||||
|
trace!("pw stream: add buffer, but not ready yet");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"pw stream: add_buffer, size={size:?}, alpha={alpha}, \
|
||||||
|
modifier={modifier:?}"
|
||||||
|
);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let spa_buffer = (*buffer).buffer;
|
let spa_buffer = (*buffer).buffer;
|
||||||
let spa_data = (*spa_buffer).datas;
|
|
||||||
assert!((*spa_buffer).n_datas > 0);
|
|
||||||
assert!((*spa_data).type_ & (1 << DataType::DmaBuf.as_raw()) > 0);
|
|
||||||
|
|
||||||
let fourcc = if alpha {
|
let fourcc = if alpha {
|
||||||
Fourcc::Argb8888
|
Fourcc::Argb8888
|
||||||
@@ -371,36 +560,29 @@ impl PipeWire {
|
|||||||
Fourcc::Xrgb8888
|
Fourcc::Xrgb8888
|
||||||
};
|
};
|
||||||
|
|
||||||
let bo = match gbm.create_buffer_object::<()>(
|
let dmabuf = match allocate_dmabuf(&gbm, size, fourcc, modifier) {
|
||||||
size.w as u32,
|
|
||||||
size.h as u32,
|
|
||||||
fourcc,
|
|
||||||
GbmBufferFlags::RENDERING,
|
|
||||||
) {
|
|
||||||
Ok(bo) => bo,
|
|
||||||
Err(err) => {
|
|
||||||
warn!("error creating GBM buffer object: {err:?}");
|
|
||||||
stop_cast();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let buffer = GbmBuffer::from_bo(bo, true);
|
|
||||||
let dmabuf = match buffer.export() {
|
|
||||||
Ok(dmabuf) => dmabuf,
|
Ok(dmabuf) => dmabuf,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("error exporting GBM buffer object as dmabuf: {err:?}");
|
warn!("error allocating dmabuf: {err:?}");
|
||||||
stop_cast();
|
stop_cast();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let fd = dmabuf.handles().next().unwrap().as_raw_fd();
|
let plane_count = dmabuf.num_planes();
|
||||||
|
assert_eq!((*spa_buffer).n_datas as usize, plane_count);
|
||||||
|
|
||||||
(*spa_data).type_ = DataType::DmaBuf.as_raw();
|
for (i, fd) in dmabuf.handles().enumerate() {
|
||||||
(*spa_data).maxsize = dmabuf.strides().next().unwrap() * size.h as u32;
|
let spa_data = (*spa_buffer).datas.add(i);
|
||||||
(*spa_data).fd = fd as i64;
|
assert!((*spa_data).type_ & (1 << DataType::DmaBuf.as_raw()) > 0);
|
||||||
(*spa_data).flags = SPA_DATA_FLAG_READWRITE;
|
|
||||||
|
|
||||||
|
(*spa_data).type_ = DataType::DmaBuf.as_raw();
|
||||||
|
(*spa_data).maxsize = 1;
|
||||||
|
(*spa_data).fd = fd.as_raw_fd() as i64;
|
||||||
|
(*spa_data).flags = SPA_DATA_FLAG_READWRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fd = (*(*spa_buffer).datas).fd;
|
||||||
assert!(dmabufs.borrow_mut().insert(fd, dmabuf).is_none());
|
assert!(dmabufs.borrow_mut().insert(fd, dmabuf).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,7 +603,7 @@ impl PipeWire {
|
|||||||
let spa_data = (*spa_buffer).datas;
|
let spa_data = (*spa_buffer).datas;
|
||||||
assert!((*spa_buffer).n_datas > 0);
|
assert!((*spa_buffer).n_datas > 0);
|
||||||
|
|
||||||
let fd = (*spa_data).fd as i32;
|
let fd = (*spa_data).fd;
|
||||||
dmabufs.borrow_mut().remove(&fd);
|
dmabufs.borrow_mut().remove(&fd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -429,10 +611,10 @@ impl PipeWire {
|
|||||||
.register()
|
.register()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
trace!("starting pw stream with size={pending_size:?}, refresh={refresh}");
|
trace!("starting pw stream with size={pending_size:?}, refresh={refresh:?}");
|
||||||
|
|
||||||
let params;
|
let params;
|
||||||
make_params!(params, pending_size, refresh, alpha);
|
make_params!(params, &formats, pending_size, refresh.get(), alpha);
|
||||||
stream
|
stream
|
||||||
.connect(
|
.connect(
|
||||||
Direction::Output,
|
Direction::Output,
|
||||||
@@ -448,7 +630,8 @@ impl PipeWire {
|
|||||||
_listener: listener,
|
_listener: listener,
|
||||||
is_active,
|
is_active,
|
||||||
target,
|
target,
|
||||||
size,
|
formats,
|
||||||
|
state,
|
||||||
refresh,
|
refresh,
|
||||||
offer_alpha: alpha,
|
offer_alpha: alpha,
|
||||||
cursor_mode,
|
cursor_mode,
|
||||||
@@ -462,12 +645,14 @@ impl PipeWire {
|
|||||||
|
|
||||||
impl Cast {
|
impl Cast {
|
||||||
pub fn ensure_size(&self, size: Size<i32, Physical>) -> anyhow::Result<CastSizeChange> {
|
pub fn ensure_size(&self, size: Size<i32, Physical>) -> anyhow::Result<CastSizeChange> {
|
||||||
let current_size = self.size.get();
|
let new_size = Size::from((size.w as u32, size.h as u32));
|
||||||
if current_size == CastSize::Ready(size) {
|
|
||||||
|
let mut state = self.state.borrow_mut();
|
||||||
|
if matches!(&*state, CastState::Ready { size, .. } if *size == new_size) {
|
||||||
return Ok(CastSizeChange::Ready);
|
return Ok(CastSizeChange::Ready);
|
||||||
}
|
}
|
||||||
|
|
||||||
if current_size.pending_size() == Some(size) {
|
if state.pending_size() == Some(new_size) {
|
||||||
debug!("stream size still hasn't changed, skipping frame");
|
debug!("stream size still hasn't changed, skipping frame");
|
||||||
return Ok(CastSizeChange::Pending);
|
return Ok(CastSizeChange::Pending);
|
||||||
}
|
}
|
||||||
@@ -475,10 +660,18 @@ impl Cast {
|
|||||||
let _span = tracy_client::span!("Cast::ensure_size");
|
let _span = tracy_client::span!("Cast::ensure_size");
|
||||||
debug!("cast size changed, updating stream size");
|
debug!("cast size changed, updating stream size");
|
||||||
|
|
||||||
self.size.set(current_size.with_pending(size));
|
*state = CastState::ResizePending {
|
||||||
|
pending_size: new_size,
|
||||||
|
};
|
||||||
|
|
||||||
let params;
|
let params;
|
||||||
make_params!(params, size, self.refresh, self.offer_alpha);
|
make_params!(
|
||||||
|
params,
|
||||||
|
&self.formats,
|
||||||
|
new_size,
|
||||||
|
self.refresh.get(),
|
||||||
|
self.offer_alpha
|
||||||
|
);
|
||||||
self.stream
|
self.stream
|
||||||
.update_params(params)
|
.update_params(params)
|
||||||
.context("error updating stream params")?;
|
.context("error updating stream params")?;
|
||||||
@@ -487,17 +680,17 @@ impl Cast {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_refresh(&mut self, refresh: u32) -> anyhow::Result<()> {
|
pub fn set_refresh(&mut self, refresh: u32) -> anyhow::Result<()> {
|
||||||
if self.refresh == refresh {
|
if self.refresh.get() == refresh {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let _span = tracy_client::span!("Cast::set_refresh");
|
let _span = tracy_client::span!("Cast::set_refresh");
|
||||||
debug!("cast FPS changed, updating stream FPS");
|
debug!("cast FPS changed, updating stream FPS");
|
||||||
self.refresh = refresh;
|
self.refresh.set(refresh);
|
||||||
|
|
||||||
let size = self.size.get().expected_format_size();
|
let size = self.state.borrow().expected_format_size();
|
||||||
let params;
|
let params;
|
||||||
make_params!(params, size, self.refresh, self.offer_alpha);
|
make_params!(params, &self.formats, size, refresh, self.offer_alpha);
|
||||||
self.stream
|
self.stream
|
||||||
.update_params(params)
|
.update_params(params)
|
||||||
.context("error updating stream params")?;
|
.context("error updating stream params")?;
|
||||||
@@ -552,77 +745,88 @@ impl Cast {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let data = &mut buffer.datas_mut()[0];
|
let fd = buffer.datas_mut()[0].as_raw().fd;
|
||||||
let fd = data.as_raw().fd as i32;
|
let dmabuf = &self.dmabufs.borrow()[&fd];
|
||||||
let dmabuf = self.dmabufs.borrow()[&fd].clone();
|
|
||||||
|
|
||||||
if let Err(err) =
|
if let Err(err) = render_to_dmabuf(
|
||||||
render_to_dmabuf(renderer, dmabuf, size, scale, Transform::Normal, elements)
|
renderer,
|
||||||
{
|
dmabuf.clone(),
|
||||||
|
size,
|
||||||
|
scale,
|
||||||
|
Transform::Normal,
|
||||||
|
elements,
|
||||||
|
) {
|
||||||
warn!("error rendering to dmabuf: {err:?}");
|
warn!("error rendering to dmabuf: {err:?}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let maxsize = data.as_raw().maxsize;
|
for (data, (stride, offset)) in
|
||||||
let chunk = data.chunk_mut();
|
zip(buffer.datas_mut(), zip(dmabuf.strides(), dmabuf.offsets()))
|
||||||
*chunk.size_mut() = maxsize;
|
{
|
||||||
*chunk.stride_mut() = maxsize as i32 / size.h;
|
let chunk = data.chunk_mut();
|
||||||
|
*chunk.size_mut() = 1;
|
||||||
|
*chunk.stride_mut() = stride as i32;
|
||||||
|
*chunk.offset_mut() = offset;
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"pw buffer: fd = {}, stride = {stride}, offset = {offset}",
|
||||||
|
data.as_raw().fd
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CastSize {
|
impl CastState {
|
||||||
fn pending_size(self) -> Option<Size<i32, Physical>> {
|
fn pending_size(&self) -> Option<Size<u32, Physical>> {
|
||||||
match self {
|
match self {
|
||||||
CastSize::InitialPending(pending) => Some(pending),
|
CastState::ResizePending { pending_size } => Some(*pending_size),
|
||||||
CastSize::Ready(_) => None,
|
CastState::ConfirmationPending { size, .. } => Some(*size),
|
||||||
CastSize::ChangePending { pending, .. } => Some(pending),
|
CastState::Ready { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn negotiated_size(self) -> Option<Size<i32, Physical>> {
|
fn expected_format_size(&self) -> Size<u32, Physical> {
|
||||||
match self {
|
match self {
|
||||||
CastSize::InitialPending(_) => None,
|
CastState::ResizePending { pending_size } => *pending_size,
|
||||||
CastSize::Ready(size) => Some(size),
|
CastState::ConfirmationPending { size, .. } => *size,
|
||||||
CastSize::ChangePending {
|
CastState::Ready { size, .. } => *size,
|
||||||
last_negotiated, ..
|
|
||||||
} => Some(last_negotiated),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expected_format_size(self) -> Size<i32, Physical> {
|
|
||||||
match self {
|
|
||||||
CastSize::InitialPending(pending) => pending,
|
|
||||||
CastSize::Ready(size) => size,
|
|
||||||
CastSize::ChangePending { pending, .. } => pending,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_pending(self, pending: Size<i32, Physical>) -> Self {
|
|
||||||
match self {
|
|
||||||
CastSize::InitialPending(_) => CastSize::InitialPending(pending),
|
|
||||||
CastSize::Ready(size) => CastSize::ChangePending {
|
|
||||||
last_negotiated: size,
|
|
||||||
pending,
|
|
||||||
},
|
|
||||||
CastSize::ChangePending {
|
|
||||||
last_negotiated, ..
|
|
||||||
} => CastSize::ChangePending {
|
|
||||||
last_negotiated,
|
|
||||||
pending,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_video_params(size: Size<i32, Physical>, refresh: u32, alpha: bool) -> pod::Object {
|
fn make_video_params(
|
||||||
|
formats: &FormatSet,
|
||||||
|
size: Size<u32, Physical>,
|
||||||
|
refresh: u32,
|
||||||
|
alpha: bool,
|
||||||
|
) -> pod::Object {
|
||||||
let format = if alpha {
|
let format = if alpha {
|
||||||
VideoFormat::BGRA
|
VideoFormat::BGRA
|
||||||
} else {
|
} else {
|
||||||
VideoFormat::BGRx
|
VideoFormat::BGRx
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let fourcc = if alpha {
|
||||||
|
Fourcc::Argb8888
|
||||||
|
} else {
|
||||||
|
Fourcc::Xrgb8888
|
||||||
|
};
|
||||||
|
|
||||||
|
let formats: Vec<_> = formats
|
||||||
|
.iter()
|
||||||
|
.filter_map(|f| (f.code == fourcc).then_some(u64::from(f.modifier) as i64))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
trace!("offering: {formats:?}");
|
||||||
|
|
||||||
|
let dont_fixate = if formats.len() > 1 {
|
||||||
|
PropertyFlags::DONT_FIXATE
|
||||||
|
} else {
|
||||||
|
PropertyFlags::empty()
|
||||||
|
};
|
||||||
|
|
||||||
pod::object!(
|
pod::object!(
|
||||||
SpaTypes::ObjectParamFormat,
|
SpaTypes::ObjectParamFormat,
|
||||||
ParamType::EnumFormat,
|
ParamType::EnumFormat,
|
||||||
@@ -631,15 +835,21 @@ fn make_video_params(size: Size<i32, Physical>, refresh: u32, alpha: bool) -> po
|
|||||||
pod::property!(FormatProperties::VideoFormat, Id, format),
|
pod::property!(FormatProperties::VideoFormat, Id, format),
|
||||||
Property {
|
Property {
|
||||||
key: FormatProperties::VideoModifier.as_raw(),
|
key: FormatProperties::VideoModifier.as_raw(),
|
||||||
value: pod::Value::Long(u64::from(Modifier::Invalid) as i64),
|
flags: PropertyFlags::MANDATORY | dont_fixate,
|
||||||
flags: PropertyFlags::MANDATORY,
|
value: pod::Value::Choice(ChoiceValue::Long(Choice(
|
||||||
|
ChoiceFlags::empty(),
|
||||||
|
ChoiceEnum::Enum {
|
||||||
|
default: formats[0],
|
||||||
|
alternatives: formats,
|
||||||
|
}
|
||||||
|
)))
|
||||||
},
|
},
|
||||||
pod::property!(
|
pod::property!(
|
||||||
FormatProperties::VideoSize,
|
FormatProperties::VideoSize,
|
||||||
Rectangle,
|
Rectangle,
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: size.w as u32,
|
width: size.w,
|
||||||
height: size.h as u32,
|
height: size.h,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
pod::property!(
|
pod::property!(
|
||||||
@@ -669,3 +879,68 @@ fn make_pod(buffer: &mut Vec<u8>, object: pod::Object) -> &Pod {
|
|||||||
PodSerializer::serialize(Cursor::new(&mut *buffer), &pod::Value::Object(object)).unwrap();
|
PodSerializer::serialize(Cursor::new(&mut *buffer), &pod::Value::Object(object)).unwrap();
|
||||||
Pod::from_bytes(buffer).unwrap()
|
Pod::from_bytes(buffer).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_preferred_modifier(
|
||||||
|
gbm: &GbmDevice<DrmDeviceFd>,
|
||||||
|
size: Size<u32, Physical>,
|
||||||
|
fourcc: Fourcc,
|
||||||
|
modifiers: Vec<i64>,
|
||||||
|
) -> anyhow::Result<(Modifier, usize)> {
|
||||||
|
debug!("find_preferred_modifier: size={size:?}, fourcc={fourcc}, modifiers={modifiers:?}");
|
||||||
|
|
||||||
|
let (buffer, modifier) = allocate_buffer(gbm, size, fourcc, &modifiers)?;
|
||||||
|
|
||||||
|
let dmabuf = buffer
|
||||||
|
.export()
|
||||||
|
.context("error exporting GBM buffer object as dmabuf")?;
|
||||||
|
let plane_count = dmabuf.num_planes();
|
||||||
|
|
||||||
|
// FIXME: Ideally this also needs to try binding the dmabuf for rendering.
|
||||||
|
|
||||||
|
Ok((modifier, plane_count))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocate_buffer(
|
||||||
|
gbm: &GbmDevice<DrmDeviceFd>,
|
||||||
|
size: Size<u32, Physical>,
|
||||||
|
fourcc: Fourcc,
|
||||||
|
modifiers: &[i64],
|
||||||
|
) -> anyhow::Result<(GbmBuffer, Modifier)> {
|
||||||
|
let (w, h) = (size.w, size.h);
|
||||||
|
let flags = GbmBufferFlags::RENDERING;
|
||||||
|
|
||||||
|
if modifiers.len() == 1 && Modifier::from(modifiers[0] as u64) == Modifier::Invalid {
|
||||||
|
let bo = gbm
|
||||||
|
.create_buffer_object::<()>(w, h, fourcc, flags)
|
||||||
|
.context("error creating GBM buffer object")?;
|
||||||
|
|
||||||
|
let buffer = GbmBuffer::from_bo(bo, true);
|
||||||
|
Ok((buffer, Modifier::Invalid))
|
||||||
|
} else {
|
||||||
|
let modifiers = modifiers
|
||||||
|
.iter()
|
||||||
|
.map(|m| Modifier::from(*m as u64))
|
||||||
|
.filter(|m| *m != Modifier::Invalid);
|
||||||
|
|
||||||
|
let bo = gbm
|
||||||
|
.create_buffer_object_with_modifiers2::<()>(w, h, fourcc, modifiers, flags)
|
||||||
|
.context("error creating GBM buffer object")?;
|
||||||
|
|
||||||
|
let modifier = bo.modifier().unwrap();
|
||||||
|
let buffer = GbmBuffer::from_bo(bo, false);
|
||||||
|
Ok((buffer, modifier))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocate_dmabuf(
|
||||||
|
gbm: &GbmDevice<DrmDeviceFd>,
|
||||||
|
size: Size<u32, Physical>,
|
||||||
|
fourcc: Fourcc,
|
||||||
|
modifier: Modifier,
|
||||||
|
) -> anyhow::Result<Dmabuf> {
|
||||||
|
let (buffer, _modifier) = allocate_buffer(gbm, size, fourcc, &[u64::from(modifier) as i64])?;
|
||||||
|
let dmabuf = buffer
|
||||||
|
.export()
|
||||||
|
.context("error exporting GBM buffer object as dmabuf")?;
|
||||||
|
Ok(dmabuf)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user