use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::io::Cursor; use std::iter::zip; use std::mem; use std::os::fd::{AsFd, AsRawFd, BorrowedFd}; use std::rc::Rc; use std::time::Duration; use anyhow::Context as _; use calloop::timer::{TimeoutAction, Timer}; use calloop::RegistrationToken; use pipewire::context::Context; use pipewire::core::{Core, PW_ID_CORE}; use pipewire::main_loop::MainLoop; use pipewire::properties::Properties; use pipewire::spa::buffer::DataType; use pipewire::spa::param::format::{FormatProperties, MediaSubtype, MediaType}; use pipewire::spa::param::format_utils::parse_format; use pipewire::spa::param::video::{VideoFormat, VideoInfoRaw}; use pipewire::spa::param::ParamType; use pipewire::spa::pod::deserialize::PodDeserializer; use pipewire::spa::pod::serialize::PodSerializer; use pipewire::spa::pod::{self, ChoiceValue, Pod, PodPropFlags, Property, PropertyFlags}; use pipewire::spa::sys::*; use pipewire::spa::utils::{ Choice, ChoiceEnum, ChoiceFlags, Direction, Fraction, Rectangle, SpaTypes, }; use pipewire::spa::{self}; use pipewire::stream::{Stream, StreamFlags, StreamListener, StreamState}; 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::{Format, Fourcc}; use smithay::backend::drm::DrmDeviceFd; use smithay::backend::renderer::damage::OutputDamageTracker; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; use smithay::output::{Output, OutputModeSource}; use smithay::reexports::calloop::generic::Generic; use smithay::reexports::calloop::{Interest, LoopHandle, Mode, PostAction}; use smithay::reexports::gbm::Modifier; use smithay::utils::{Physical, Scale, Size, Transform}; use zbus::object_server::SignalEmitter; use crate::dbus::mutter_screen_cast::{self, CursorMode}; use crate::niri::{CastTarget, State}; use crate::render_helpers::{clear_dmabuf, render_to_dmabuf}; use crate::utils::get_monotonic_time; // Give a 0.1 ms allowance for presentation time errors. const CAST_DELAY_ALLOWANCE: Duration = Duration::from_micros(100); pub struct PipeWire { _context: Context, pub core: Core, pub token: RegistrationToken, to_niri: calloop::channel::Sender, } pub enum PwToNiri { StopCast { session_id: usize }, Redraw { stream_id: usize }, FatalError, } pub struct Cast { pub session_id: usize, pub stream_id: usize, pub stream: Stream, _listener: StreamListener<()>, pub is_active: Rc>, pub target: CastTarget, pub dynamic_target: bool, formats: FormatSet, state: Rc>, refresh: Rc>, offer_alpha: bool, pub cursor_mode: CursorMode, pub last_frame_time: Duration, min_time_between_frames: Rc>, dmabufs: Rc>>, scheduled_redraw: Option, } #[derive(Debug)] pub enum CastState { ResizePending { pending_size: Size, }, ConfirmationPending { size: Size, alpha: bool, modifier: Modifier, plane_count: i32, }, Ready { size: Size, alpha: bool, modifier: Modifier, plane_count: i32, // Lazily-initialized to keep the initialization to a single place. damage_tracker: Option, }, } #[derive(PartialEq, Eq)] pub enum CastSizeChange { Ready, Pending, } macro_rules! make_params { ($params:ident, $formats:expr, $size:expr, $refresh:expr, $alpha:expr) => { let mut b1 = Vec::new(); let mut b2 = Vec::new(); let o1 = make_video_params($formats, $size, $refresh, false); let pod1 = make_pod(&mut b1, o1); let mut p1; let mut p2; $params = if $alpha { let o2 = make_video_params($formats, $size, $refresh, true); p2 = [pod1, make_pod(&mut b2, o2)]; &mut p2[..] } else { p1 = [pod1]; &mut p1[..] }; }; } impl PipeWire { pub fn new( event_loop: &LoopHandle<'static, State>, to_niri: calloop::channel::Sender, ) -> anyhow::Result { let main_loop = MainLoop::new(None).context("error creating MainLoop")?; let context = Context::new(&main_loop).context("error creating Context")?; let core = context.connect(None).context("error creating Core")?; let to_niri_ = to_niri.clone(); let listener = core .add_listener_local() .error(move |id, seq, res, message| { warn!(id, seq, res, message, "pw error"); // Reset PipeWire on connection errors. if id == PW_ID_CORE && res == -32 { if let Err(err) = to_niri_.send(PwToNiri::FatalError) { warn!("error sending FatalError to niri: {err:?}"); } } }) .register(); mem::forget(listener); struct AsFdWrapper(MainLoop); impl AsFd for AsFdWrapper { fn as_fd(&self) -> BorrowedFd<'_> { self.0.loop_().fd() } } let generic = Generic::new(AsFdWrapper(main_loop), Interest::READ, Mode::Level); let token = event_loop .insert_source(generic, move |_, wrapper, _| { let _span = tracy_client::span!("pipewire iteration"); wrapper.0.loop_().iterate(Duration::ZERO); Ok(PostAction::Continue) }) .unwrap(); Ok(Self { _context: context, core, token, to_niri, }) } #[allow(clippy::too_many_arguments)] pub fn start_cast( &self, gbm: GbmDevice, formats: FormatSet, session_id: usize, stream_id: usize, target: CastTarget, dynamic_target: bool, size: Size, refresh: u32, alpha: bool, cursor_mode: CursorMode, signal_ctx: SignalEmitter<'static>, ) -> anyhow::Result { let _span = tracy_client::span!("PipeWire::start_cast"); let to_niri_ = self.to_niri.clone(); let stop_cast = move || { if let Err(err) = to_niri_.send(PwToNiri::StopCast { session_id }) { warn!("error sending StopCast to niri: {err:?}"); } }; let to_niri_ = self.to_niri.clone(); let redraw = move || { if let Err(err) = to_niri_.send(PwToNiri::Redraw { stream_id }) { warn!("error sending Redraw to niri: {err:?}"); } }; let redraw_ = redraw.clone(); let stream = Stream::new(&self.core, "niri-screen-cast-src", Properties::new()) .context("error creating Stream")?; // Like in good old wayland-rs times... let node_id = Rc::new(Cell::new(None)); let is_active = Rc::new(Cell::new(false)); let min_time_between_frames = Rc::new(Cell::new(Duration::ZERO)); let dmabufs = Rc::new(RefCell::new(HashMap::new())); let refresh = Rc::new(Cell::new(refresh)); let pending_size = Size::from((size.w as u32, size.h as u32)); let state = Rc::new(RefCell::new(CastState::ResizePending { pending_size })); let listener = stream .add_local_listener_with_user_data(()) .state_changed({ let is_active = is_active.clone(); let stop_cast = stop_cast.clone(); move |stream, (), old, new| { debug!("pw stream: state changed: {old:?} -> {new:?}"); match new { StreamState::Paused => { if node_id.get().is_none() { let id = stream.node_id(); node_id.set(Some(id)); debug!("pw stream: sending signal with {id}"); let _span = tracy_client::span!("sending PipeWireStreamAdded"); async_io::block_on(async { let res = mutter_screen_cast::Stream::pipe_wire_stream_added( &signal_ctx, id, ) .await; if let Err(err) = res { warn!("error sending PipeWireStreamAdded: {err:?}"); stop_cast(); } }); } is_active.set(false); } StreamState::Error(_) => { if is_active.get() { is_active.set(false); stop_cast(); } } StreamState::Unconnected => (), StreamState::Connecting => (), StreamState::Streaming => { is_active.set(true); redraw(); } } } }) .param_changed({ let min_time_between_frames = min_time_between_frames.clone(); let stop_cast = stop_cast.clone(); let state = state.clone(); let gbm = gbm.clone(); let formats = formats.clone(); let refresh = refresh.clone(); move |stream, (), id, pod| { let id = ParamType::from_raw(id); trace!(?id, "pw stream: param_changed"); if id != ParamType::Format { return; } let Some(pod) = pod else { return }; let (m_type, m_subtype) = match parse_format(pod) { Ok(x) => x, Err(err) => { warn!("pw stream: error parsing format: {err:?}"); return; } }; if m_type != MediaType::Video || m_subtype != MediaSubtype::Raw { return; } let mut format = VideoInfoRaw::new(); format.parse(pod).unwrap(); debug!("pw stream: got format = {format:?}"); let format_size = Size::from((format.size().width, format.size().height)); let mut state = state.borrow_mut(); if format_size != state.expected_format_size() { if !matches!(&*state, CastState::ResizePending { .. }) { warn!("pw stream: wrong size, but we're not resizing"); stop_cast(); return; } debug!("pw stream: wrong size, waiting"); return; } 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 min_frame_time = Duration::from_micros( 1_000_000 * u64::from(max_frame_rate.denom) / u64::from(max_frame_rate.num), ); min_time_between_frames.set(min_frame_time); let object = pod.as_object().unwrap(); let Some(prop_modifier) = 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::>( 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; let damage_tracker = if let CastState::Ready { damage_tracker, .. } = &mut *state { damage_tracker.take() } else { None }; debug!("pw stream: moving to ready state"); *state = CastState::Ready { size, alpha, modifier, plane_count, damage_tracker, }; 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, damage_tracker: None, }; plane_count as i32 } }; // const BPP: u32 = 4; // let stride = format.size().width * BPP; // let size = stride * format.size().height; let o1 = pod::object!( SpaTypes::ObjectParamBuffers, ParamType::Buffers, Property::new( SPA_PARAM_BUFFERS_buffers, pod::Value::Choice(ChoiceValue::Int(Choice( ChoiceFlags::empty(), ChoiceEnum::Range { default: 16, min: 2, max: 16 } ))), ), 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_stride, pod::Value::Int(stride as i32)), // Property::new(SPA_PARAM_BUFFERS_align, pod::Value::Int(16)), Property::new( SPA_PARAM_BUFFERS_dataType, pod::Value::Choice(ChoiceValue::Int(Choice( ChoiceFlags::empty(), ChoiceEnum::Flags { default: 1 << DataType::DmaBuf.as_raw(), flags: vec![1 << DataType::DmaBuf.as_raw()], }, ))), ), ); // FIXME: Hidden / embedded / metadata cursor // let o2 = pod::object!( // SpaTypes::ObjectParamMeta, // ParamType::Meta, // Property::new(SPA_PARAM_META_type, // pod::Value::Id(Id(SPA_META_Header))), // Property::new( // SPA_PARAM_META_size, // pod::Value::Int(size_of::() as i32) // ), // ); let mut b1 = vec![]; // let mut b2 = vec![]; let mut params = [ make_pod(&mut b1, o1), // make_pod(&mut b2, o2) ]; if let Err(err) = stream.update_params(&mut params) { warn!("error updating stream params: {err:?}"); stop_cast(); } } }) .add_buffer({ let dmabufs = dmabufs.clone(); let stop_cast = stop_cast.clone(); let state = state.clone(); move |stream, (), buffer| { let (size, alpha, modifier) = if let CastState::Ready { size, alpha, 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 { let spa_buffer = (*buffer).buffer; let fourcc = if alpha { Fourcc::Argb8888 } else { Fourcc::Xrgb8888 }; let dmabuf = match allocate_dmabuf(&gbm, size, fourcc, modifier) { Ok(dmabuf) => dmabuf, Err(err) => { warn!("error allocating dmabuf: {err:?}"); stop_cast(); return; } }; let plane_count = dmabuf.num_planes(); assert_eq!((*spa_buffer).n_datas as usize, plane_count); for (i, fd) in dmabuf.handles().enumerate() { let spa_data = (*spa_buffer).datas.add(i); assert!((*spa_data).type_ & (1 << DataType::DmaBuf.as_raw()) > 0); (*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()); } // During size re-negotiation, the stream sometimes just keeps running, in // which case we may need to force a redraw once we got a newly sized buffer. if dmabufs.borrow().len() == 1 && stream.state() == StreamState::Streaming { redraw_(); } } }) .remove_buffer({ let dmabufs = dmabufs.clone(); move |_stream, (), buffer| { trace!("pw stream: remove_buffer"); unsafe { let spa_buffer = (*buffer).buffer; let spa_data = (*spa_buffer).datas; assert!((*spa_buffer).n_datas > 0); let fd = (*spa_data).fd; dmabufs.borrow_mut().remove(&fd); } } }) .register() .unwrap(); trace!("starting pw stream with size={pending_size:?}, refresh={refresh:?}"); let params; make_params!(params, &formats, pending_size, refresh.get(), alpha); stream .connect( Direction::Output, None, StreamFlags::DRIVER | StreamFlags::ALLOC_BUFFERS, params, ) .context("error connecting stream")?; let cast = Cast { session_id, stream_id, stream, _listener: listener, is_active, target, dynamic_target, formats, state, refresh, offer_alpha: alpha, cursor_mode, last_frame_time: Duration::ZERO, min_time_between_frames, dmabufs, scheduled_redraw: None, }; Ok(cast) } } impl Cast { pub fn ensure_size(&self, size: Size) -> anyhow::Result { let new_size = Size::from((size.w as u32, size.h as u32)); let mut state = self.state.borrow_mut(); if matches!(&*state, CastState::Ready { size, .. } if *size == new_size) { return Ok(CastSizeChange::Ready); } if state.pending_size() == Some(new_size) { debug!("stream size still hasn't changed, skipping frame"); return Ok(CastSizeChange::Pending); } let _span = tracy_client::span!("Cast::ensure_size"); debug!("cast size changed, updating stream size"); *state = CastState::ResizePending { pending_size: new_size, }; let params; make_params!( params, &self.formats, new_size, self.refresh.get(), self.offer_alpha ); self.stream .update_params(params) .context("error updating stream params")?; Ok(CastSizeChange::Pending) } pub fn set_refresh(&mut self, refresh: u32) -> anyhow::Result<()> { if self.refresh.get() == refresh { return Ok(()); } let _span = tracy_client::span!("Cast::set_refresh"); debug!("cast FPS changed, updating stream FPS"); self.refresh.set(refresh); let size = self.state.borrow().expected_format_size(); let params; make_params!(params, &self.formats, size, refresh, self.offer_alpha); self.stream .update_params(params) .context("error updating stream params")?; Ok(()) } fn compute_extra_delay(&self, target_frame_time: Duration) -> Duration { let last = self.last_frame_time; let min = self.min_time_between_frames.get(); if last.is_zero() { trace!(?target_frame_time, ?last, "last is zero, recording"); return Duration::ZERO; } if target_frame_time < last { // Record frame with a warning; in case it was an overflow this will fix it. warn!( ?target_frame_time, ?last, "target frame time is below last, did it overflow or did we mispredict?" ); return Duration::ZERO; } let diff = target_frame_time - last; if diff < min { let delay = min - diff; trace!( ?target_frame_time, ?last, "frame is too soon: min={min:?}, delay={:?}", delay ); return delay; } else { trace!("overshoot={:?}", diff - min); } Duration::ZERO } fn schedule_redraw( &mut self, event_loop: &LoopHandle<'static, State>, output: Output, target_time: Duration, ) { if self.scheduled_redraw.is_some() { return; } let now = get_monotonic_time(); let duration = target_time.saturating_sub(now); let timer = Timer::from_duration(duration); let token = event_loop .insert_source(timer, move |_, _, state| { // Guard against output disconnecting before the timer has a chance to run. if state.niri.output_state.contains_key(&output) { state.niri.queue_redraw(&output); } TimeoutAction::Drop }) .unwrap(); self.scheduled_redraw = Some(token); } fn remove_scheduled_redraw(&mut self, event_loop: &LoopHandle<'static, State>) { if let Some(token) = self.scheduled_redraw.take() { event_loop.remove(token); } } /// Checks whether this frame should be skipped because it's too soon. /// /// If the frame should be skipped, schedules a redraw and returns `true`. Otherwise, removes a /// scheduled redraw, if any, and returns `false`. /// /// When this method returns `false`, the calling code is assumed to follow up with /// [`Cast::dequeue_buffer_and_render()`]. pub fn check_time_and_schedule( &mut self, event_loop: &LoopHandle<'static, State>, output: &Output, target_frame_time: Duration, ) -> bool { let delay = self.compute_extra_delay(target_frame_time); if delay >= CAST_DELAY_ALLOWANCE { trace!("delay >= allowance, scheduling redraw"); self.schedule_redraw(event_loop, output.clone(), target_frame_time + delay); true } else { self.remove_scheduled_redraw(event_loop); false } } pub fn dequeue_buffer_and_render( &mut self, renderer: &mut GlesRenderer, elements: &[impl RenderElement], size: Size, scale: Scale, wait_for_sync: bool, ) -> bool { let CastState::Ready { damage_tracker, .. } = &mut *self.state.borrow_mut() else { error!("cast must be in Ready state to render"); return false; }; let damage_tracker = damage_tracker .get_or_insert_with(|| OutputDamageTracker::new(size, scale, Transform::Normal)); // Size change will drop the damage tracker, but scale change won't, so check it here. let OutputModeSource::Static { scale: t_scale, .. } = damage_tracker.mode() else { unreachable!(); }; if *t_scale != scale { *damage_tracker = OutputDamageTracker::new(size, scale, Transform::Normal); } let (damage, _states) = damage_tracker.damage_output(1, elements).unwrap(); if damage.is_none() { trace!("no damage, skipping frame"); return false; } let Some(mut buffer) = self.stream.dequeue_buffer() else { warn!("no available buffer in pw stream, skipping frame"); return false; }; let fd = buffer.datas_mut()[0].as_raw().fd; let dmabuf = &self.dmabufs.borrow()[&fd]; match render_to_dmabuf( renderer, dmabuf.clone(), size, scale, Transform::Normal, elements.iter().rev(), ) { Ok(sync_point) => { // FIXME: implement PipeWire explicit sync, and at the very least async wait. if wait_for_sync { let _span = tracy_client::span!("wait for completion"); if let Err(err) = sync_point.wait() { warn!("error waiting for pw frame completion: {err:?}"); } } } Err(err) => { warn!("error rendering to dmabuf: {err:?}"); return false; } } for (data, (stride, offset)) in zip(buffer.datas_mut(), zip(dmabuf.strides(), dmabuf.offsets())) { 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 } pub fn dequeue_buffer_and_clear( &mut self, renderer: &mut GlesRenderer, wait_for_sync: bool, ) -> bool { // Clear out the damage tracker if we're in Ready state. if let CastState::Ready { damage_tracker, .. } = &mut *self.state.borrow_mut() { *damage_tracker = None; }; let Some(mut buffer) = self.stream.dequeue_buffer() else { warn!("no available buffer in pw stream, skipping clear"); return false; }; let fd = buffer.datas_mut()[0].as_raw().fd; let dmabuf = &self.dmabufs.borrow()[&fd]; match clear_dmabuf(renderer, dmabuf.clone()) { Ok(sync_point) => { // FIXME: implement PipeWire explicit sync, and at the very least async wait. if wait_for_sync { let _span = tracy_client::span!("wait for completion"); if let Err(err) = sync_point.wait() { warn!("error waiting for pw frame completion: {err:?}"); } } } Err(err) => { warn!("error clearing dmabuf: {err:?}"); return false; } } for (data, (stride, offset)) in zip(buffer.datas_mut(), zip(dmabuf.strides(), dmabuf.offsets())) { 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 } } impl CastState { fn pending_size(&self) -> Option> { match self { CastState::ResizePending { pending_size } => Some(*pending_size), CastState::ConfirmationPending { size, .. } => Some(*size), CastState::Ready { .. } => None, } } fn expected_format_size(&self) -> Size { match self { CastState::ResizePending { pending_size } => *pending_size, CastState::ConfirmationPending { size, .. } => *size, CastState::Ready { size, .. } => *size, } } } fn make_video_params( formats: &FormatSet, size: Size, refresh: u32, alpha: bool, ) -> pod::Object { let format = if alpha { VideoFormat::BGRA } else { 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!( SpaTypes::ObjectParamFormat, ParamType::EnumFormat, pod::property!(FormatProperties::MediaType, Id, MediaType::Video), pod::property!(FormatProperties::MediaSubtype, Id, MediaSubtype::Raw), pod::property!(FormatProperties::VideoFormat, Id, format), Property { key: FormatProperties::VideoModifier.as_raw(), flags: PropertyFlags::MANDATORY | dont_fixate, value: pod::Value::Choice(ChoiceValue::Long(Choice( ChoiceFlags::empty(), ChoiceEnum::Enum { default: formats[0], alternatives: formats, } ))) }, pod::property!( FormatProperties::VideoSize, Rectangle, Rectangle { width: size.w, height: size.h, } ), pod::property!( FormatProperties::VideoFramerate, Fraction, Fraction { num: 0, denom: 1 } ), pod::property!( FormatProperties::VideoMaxFramerate, Choice, Range, Fraction, Fraction { num: refresh, denom: 1000 }, Fraction { num: 1, denom: 1 }, Fraction { num: refresh, denom: 1000 } ), ) } fn make_pod(buffer: &mut Vec, object: pod::Object) -> &Pod { PodSerializer::serialize(Cursor::new(&mut *buffer), &pod::Value::Object(object)).unwrap(); Pod::from_bytes(buffer).unwrap() } fn find_preferred_modifier( gbm: &GbmDevice, size: Size, fourcc: Fourcc, modifiers: Vec, ) -> 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, size: Size, 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(); let buffer = GbmBuffer::from_bo(bo, false); Ok((buffer, modifier)) } } fn allocate_dmabuf( gbm: &GbmDevice, size: Size, fourcc: Fourcc, modifier: Modifier, ) -> anyhow::Result { 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) }