use { crate::{ allocator::{AllocatorError, BO_USE_RENDERING, BufferObject, BufferUsage}, async_engine::{Phase, SpawnedFuture}, backend::{ BackendConnectorState, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, HardwareCursor, HardwareCursorUpdate, Mode, MonitorInfo, transaction::{ BackendAppliedConnectorTransaction, BackendConnectorTransaction, BackendConnectorTransactionError, BackendConnectorTransactionType, BackendConnectorTransactionTypeDyn, BackendPreparedConnectorTransaction, }, }, cmm::{cmm_description::ColorDescription, cmm_primaries::Primaries}, format::{Format, XRGB8888}, gfx_api::{ AcquireSync, BufferResv, DirectScanoutPosition, FdSync, GfxBlendBuffer, GfxContext, GfxError, GfxFramebuffer, GfxRenderPass, GfxTexture, ReleaseSync, create_render_pass, }, ifs::{ wl_output::{BlendSpace, OutputId}, wp_presentation_feedback::{ KIND_HW_CLOCK, KIND_HW_COMPLETION, KIND_VSYNC, KIND_ZERO_COPY, }, }, rect::Region, state::State, tasks::handle_connector, tree::OutputNode, utils::{ asyncevent::AsyncEvent, cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, geometric_decay::GeometricDecay, hash_map_ext::HashMapExt, numcell::NumCell, on_change::OnChange, rc_eq::rc_eq, timer::TimerFd, }, video::drm::ConnectorType, }, ahash::AHashMap, linearize::{Linearize, LinearizeExt, StaticMap, static_map}, std::{ any::Any, cell::{Cell, RefCell}, fmt::{Debug, Formatter}, mem, rc::Rc, time::Duration, }, thiserror::Error, uapi::c, }; #[derive(Default)] pub struct VirtualOutputs { pub outputs: CopyHashMap>, formats: CloneCell>>, states: CopyHashMap>, } #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] enum FrontendState { #[default] Disconnected, Desktop, NonDesktop, } pub struct VirtualOutput { state: Rc, id: ConnectorId, kernel_id: ConnectorKernelId, output_id: Rc, name: String, frontend_state: Cell, needs_format_update: Cell, events: OnChange, damage: NumCell, present_trigger: AsyncEvent, persistent_state: Rc, vo_state: CloneCell>, tasks: Cell; 2]>>, flip_task: Cell>>, next_vblank_nsec: Cell, pre_commit_margin: Cell, pre_commit_margin_decay: GeometricDecay, need_vblank: AsyncEvent, seq: NumCell, pending_flip: Cell>, trigger_flip: AsyncEvent, cursor_damage: Cell, cursor_programming: Cell>, frame_data: RefCell>, } struct PersistentVirtualOutputState { backend_state: RefCell, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] struct CursorProgramming { x: i32, y: i32, } struct ScheduledFlip { on: Rc, refresh_ns: u64, vrr: bool, tearing: bool, expected_seq: Option, locked: bool, frame_data: Option>, } #[derive(Default, Clone)] struct VoState { fbs: Option>, locked: Cell, } #[derive(Copy, Clone, Linearize, Eq, PartialEq)] enum FbType { Primary, Cursor, } struct FbState { ctx: Rc, format: &'static Format, blend_buffer: Option>, fbs: StaticMap, } struct VoFb { width: i32, height: i32, _bo: Rc, tex: Rc, fb: Rc, } struct Transaction { state: Rc, changes: AHashMap, } struct TransactionChange { output: Rc, new: BackendConnectorState, } struct PreparedTransaction { state: Rc, changes: Vec, } struct PreparedTransactionChange { output: Rc, old_backend_state: BackendConnectorState, old_vo_state: Rc, new_backend_state: BackendConnectorState, new_vo_state: Rc, } struct Latched { pass: GfxRenderPass, damage_count: u64, damage: Region, locked: bool, } struct CursorChange<'a> { swap_buffer: Option>, enabled: bool, x: i32, y: i32, buffer: &'a VoFb, } struct DirectScanoutData { buffer_resv: Option>, tex: Rc, acquire_sync: AcquireSync, release_sync: ReleaseSync, pos: DirectScanoutPosition, } struct FrameData { dsd: Option, } const CURSOR_SIZE: i32 = 256; impl HardwareCursorUpdate for CursorChange<'_> { fn set_enabled(&mut self, enabled: bool) { self.enabled = enabled; } fn get_buffer(&self) -> Rc { self.buffer.fb.clone() } fn set_position(&mut self, x: i32, y: i32) { self.x = x; self.y = y; } fn swap_buffer(&mut self, sync: Option) { self.swap_buffer = Some(sync); } fn size(&self) -> (i32, i32) { (CURSOR_SIZE, CURSOR_SIZE) } } fn default_state(state: &State) -> BackendConnectorState { BackendConnectorState { serial: state.backend_connector_state_serials.next(), enabled: false, active: true, mode: Mode { width: 800, height: 600, refresh_rate_millihz: 60_000, }, non_desktop_override: Default::default(), vrr: Default::default(), tearing: Default::default(), format: XRGB8888, color_space: Default::default(), eotf: Default::default(), gamma_lut: Default::default(), } } impl VirtualOutputs { pub fn get_or_create(&self, state: &Rc, name: &str) -> Rc { if let Some(vo) = self.outputs.get(name) { return vo; } let id = state.connector_ids.next(); let kernel_id = ConnectorKernelId { ty: ConnectorType::VirtualOutput, idx: id.raw(), }; let persistent_state = match self.states.get(name) { Some(s) => s, _ => { let state = Rc::new(PersistentVirtualOutputState { backend_state: RefCell::new(default_state(state)), }); self.states.set(name.to_string(), state.clone()); state } }; let vo = Rc::new(VirtualOutput { state: state.clone(), id, kernel_id, output_id: OutputId::new(kernel_id.to_string(), "Jay", "VirtualOutput", name), name: format!("VO-{}", name), frontend_state: Default::default(), needs_format_update: Default::default(), events: Default::default(), damage: Default::default(), present_trigger: Default::default(), persistent_state, vo_state: Default::default(), tasks: Default::default(), flip_task: Default::default(), next_vblank_nsec: Default::default(), pre_commit_margin: Cell::new(PRE_COMMIT_MARGIN), pre_commit_margin_decay: GeometricDecay::new(0.5, PRE_COMMIT_MARGIN), need_vblank: Default::default(), seq: Default::default(), pending_flip: Default::default(), trigger_flip: Default::default(), cursor_damage: Default::default(), cursor_programming: Default::default(), frame_data: Default::default(), }); vo.handle_render_ctx_change(); handle_connector(state, &(vo.clone() as Rc)); self.outputs.set(name.to_string(), vo.clone()); vo.flip_task .set(Some(state.eng.spawn("vo-flip", vo.clone().flip_task()))); vo } pub fn remove_output(&self, _state: &Rc, name: &str) { let Some(o) = self.outputs.remove(name) else { return; }; o.clear(); o.events.send_event(ConnectorEvent::Disconnected); o.events.send_event(ConnectorEvent::Removed); } pub fn clear(&self) { for o in self.outputs.lock().drain_values() { o.clear(); o.events.clear(); } } pub fn handle_render_ctx_change(&self, state: &State) { let formats = match state.render_ctx.get() { None => vec![], Some(c) => c.formats().values().map(|f| f.format).collect(), }; self.formats.set(Rc::new(formats)); for o in self.outputs.lock().values() { o.handle_render_ctx_change(); } } } impl Connector for VirtualOutput { fn id(&self) -> ConnectorId { self.id } fn kernel_id(&self) -> ConnectorKernelId { self.kernel_id } fn event(&self) -> Option { self.events.events.pop() } fn on_change(&self, cb: Rc) { self.events.on_change.set(Some(cb)); } fn damage(&self) { self.damage.fetch_add(1); self.trigger_present(); } fn drm_dev(&self) -> Option { None } fn effectively_locked(&self) -> bool { self.vo_state.get().locked.get() } fn state(&self) -> BackendConnectorState { self.persistent_state.backend_state.borrow().clone() } fn transaction_type(&self) -> Box { #[derive(Eq, PartialEq, Hash)] struct TT; impl BackendConnectorTransactionType for TT {} Box::new(TT) } fn create_transaction( &self, ) -> Result, BackendConnectorTransactionError> { Ok(Box::new(self.create_transaction())) } fn name(&self) -> String { self.name.clone() } } struct VirtualHc { o: Rc, } impl Debug for VirtualHc { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("VirtualOutput") .field("id", &self.o.id) .finish_non_exhaustive() } } impl HardwareCursor for VirtualHc { fn damage(&self) { self.o.cursor_damage.set(true); self.o.trigger_present(); } } const NSEC_PER_SEC: u64 = 1_000_000_000; const PRE_COMMIT_MARGIN: u64 = 500_000; const PRE_COMMIT_MARGIN_DELTA: u64 = 50_000; const POST_COMMIT_MARGIN: u64 = 500_000; impl VirtualOutput { fn clear(&self) { self.flip_task.take(); self.tasks.take(); self.pending_flip.take(); self.frame_data.take(); } fn trigger_present(&self) { if self.pending_flip.is_none() { if self.cursor_damage.get() || self.damage.get() > 0 { self.present_trigger.trigger(); } } } async fn present_task(self: Rc) { let be_state = self.persistent_state.backend_state.borrow().clone(); let refresh_ns = be_state.mode.refresh_nsec(); let vo_state = self.vo_state.get(); let vrr = be_state.vrr; let tearing = be_state.tearing; let Some(fbs) = &vo_state.fbs else { return; }; let mut max = 0; let mut cur_sec = 0; loop { self.present_trigger.triggered().await; if self.pending_flip.is_some() { continue; } let mut start = self.state.now_nsec(); let mut expected_seq = self.seq.get() + 1; if !tearing { let next_present = self .next_vblank_nsec .get() .saturating_sub(self.pre_commit_margin.get()) .saturating_sub(POST_COMMIT_MARGIN); if start < next_present { self.state.ring.timeout(next_present).await.unwrap(); start = self.state.now_nsec(); } else if !vrr { expected_seq += 1; } } let Some(on) = self.state.root.outputs.get(&self.id) else { continue; }; let fb = &fbs.fbs[FbType::Primary]; let cd = on.global.color_description.get(); let linear_cd = on.global.linear_color_description.get(); let blend_cd = match on.global.persistent.blend_space.get() { BlendSpace::Linear => &linear_cd, BlendSpace::Srgb => self.state.color_manager.srgb_gamma22(), }; let flip = match tearing { true => start, false => self.next_vblank_nsec.get().max(start), }; on.before_latch(flip).await; if self.damage.get() > 0 || self.cursor_damage.get() { on.schedule.commit_cursor(); } let cursor_latched = self.latch_cursor(&on, &fbs.fbs[FbType::Cursor]); let latched = self.latch(&on); on.latched(tearing); if latched.is_none() && cursor_latched.is_none() { continue; } let mut frame_data = None; if let Some(latched) = &latched { let sync; if let Some(dsd) = self.prepare_direct_scanout(&be_state, blend_cd, &cd, latched) { sync = match dsd.acquire_sync.clone() { AcquireSync::None => None, AcquireSync::Implicit => None, AcquireSync::FdSync(sync) => Some(sync), AcquireSync::Unnecessary => None, }; frame_data = Some(FrameData { dsd: Some(dsd) }); } else { let res = fb.fb.perform_render_pass( AcquireSync::Unnecessary, ReleaseSync::Explicit, &cd, &latched.pass, &latched.damage, fbs.blend_buffer.as_ref(), blend_cd, ); sync = match res { Ok(sync) => sync, Err(e) => { log::error!("Could not present: {}", ErrorFmt(e)); return; } }; frame_data = Some(FrameData { dsd: None }); }; if let Some(sync) = sync { sync.signaled(&self.state.ring, "primary").await; } } { let prev_frame_data = &*self.frame_data.borrow(); let effective_frame_data = frame_data.as_ref().or(prev_frame_data.as_ref()); if let Some(fd) = effective_frame_data { match &fd.dsd { None => { on.perform_screencopies( &fb.tex, &cd, None, &AcquireSync::Unnecessary, ReleaseSync::None, true, 0, 0, None, ); } Some(dsd) => { on.perform_screencopies( &dsd.tex, &cd, dsd.buffer_resv.as_ref(), &dsd.acquire_sync, dsd.release_sync, true, dsd.pos.crtc_x, dsd.pos.crtc_y, Some((dsd.pos.crtc_width, dsd.pos.crtc_height)), ); } } } } if let Some(Some(sync)) = cursor_latched { sync.signaled(&self.state.ring, "cursor").await; } if let Some(latched) = &latched { vo_state.locked.set(latched.locked); self.damage.fetch_sub(latched.damage_count); } self.pending_flip.set(Some(ScheduledFlip { on, refresh_ns, vrr, tearing, expected_seq: (!tearing).then_some(expected_seq), locked: vo_state.locked.get(), frame_data: frame_data.map(Some), })); if vrr { self.need_vblank.trigger(); } if tearing { self.trigger_flip.trigger(); } let duration = self.state.now_nsec() - start; max = max.max(duration); if start / NSEC_PER_SEC != cur_sec { cur_sec = start / NSEC_PER_SEC; self.pre_commit_margin_decay.add(max); self.pre_commit_margin .set(self.pre_commit_margin_decay.get()); max = 0; } } } fn latch(&self, on: &Rc) -> Option { let damage_count = self.damage.get(); if damage_count == 0 { return None; } let damage = { on.global.connector.damaged.set(false); on.global.add_visualizer_damage(); let damage = &mut *on.global.connector.damage.borrow_mut(); let region = Region::from_rects2(damage); damage.clear(); region }; let pass = create_render_pass( on.global.mode.get().size(), &**on, &self.state, Some(on.global.pos.get()), on.global.persistent.scale.get(), true, false, on.has_fullscreen(), true, on.global.persistent.transform.get(), Some(&self.state.damage_visualizer), ); Some(Latched { pass, damage_count, damage, locked: self.state.lock.locked.get(), }) } fn prepare_direct_scanout( &self, be_state: &BackendConnectorState, blend_cd: &Rc, cd: &Rc, latched: &Latched, ) -> Option { let (ct, position) = latched.pass.prepare_direct_scanout( be_state.mode.width, be_state.mode.height, blend_cd, &cd, true, )?; Some(DirectScanoutData { buffer_resv: ct.buffer_resv.clone(), tex: ct.tex.clone(), acquire_sync: ct.acquire_sync.clone(), release_sync: ct.release_sync, pos: position, }) } fn latch_cursor(&self, on: &Rc, fb: &VoFb) -> Option> { if !self.cursor_damage.take() { return None; } let mut c = CursorChange { enabled: false, swap_buffer: None, x: 0, y: 0, buffer: fb, }; if let Some(p) = self.cursor_programming.get() { c.enabled = true; c.x = p.x; c.y = p.y; } self.state.present_hardware_cursor(on, &mut c); let p = c.enabled.then_some(CursorProgramming { x: c.x, y: c.y }); let mut cursor_changed = false; cursor_changed |= self.cursor_programming.replace(p) != p; cursor_changed |= c.swap_buffer.is_some(); cursor_changed.then_some(c.swap_buffer.take().flatten()) } async fn vblank_task(self: Rc) { let be_state = self.persistent_state.backend_state.borrow().clone(); let refresh_nsec = be_state.mode.refresh_nsec(); let vrr = be_state.vrr; let handle_vblank = || { let next_vblank = self.state.now_nsec().saturating_add(refresh_nsec); self.next_vblank_nsec.set(next_vblank); self.seq.fetch_add(1); if self.pending_flip.is_some() { self.trigger_flip.trigger(); } if let Some(on) = self.state.root.outputs.get(&self.id) { on.vblank(); } next_vblank }; if vrr { loop { let next_vblank = handle_vblank(); if let Err(e) = self.state.ring.timeout(next_vblank).await { log::error!("Could not wait for next vblank: {}", e); return; } self.need_vblank.triggered().await; } } else { let tfd = match TimerFd::new(c::CLOCK_MONOTONIC) { Ok(fd) => fd, Err(e) => { log::error!("Could not create a timer fd: {}", ErrorFmt(e)); return; } }; let duration = Some(Duration::from_nanos(refresh_nsec)); let res = tfd.program(duration, duration); if let Err(e) = res { log::error!("Could not program the timer fd: {}", ErrorFmt(e)); return; } loop { handle_vblank(); if let Err(e) = tfd.expired(&self.state.ring).await { log::error!("Could not wait for timer fd to expire: {}", ErrorFmt(e)); return; } } } } async fn flip_task(self: Rc) { let debounce = self.state.ring.debouncer(0); loop { self.trigger_flip.triggered().await; let Some(mut flip) = self.pending_flip.take() else { continue; }; let direct_scanout = { let fd = &mut *self.frame_data.borrow_mut(); if let Some(frame) = flip.frame_data.take() { *fd = frame; } matches!(*fd, Some(FrameData { dsd: Some(..), .. })) }; let flip_ns = self.state.now_nsec(); let tv_sec = flip_ns / NSEC_PER_SEC; let tv_nsec = (flip_ns % NSEC_PER_SEC) as u32; let mut flags = KIND_HW_COMPLETION | KIND_HW_CLOCK; if !flip.tearing { flags |= KIND_VSYNC; } if direct_scanout { flags |= KIND_ZERO_COPY; } let seq = self.seq.get(); flip.on.presented( tv_sec, tv_nsec, flip.refresh_ns.try_into().unwrap_or(0), seq, flags, flip.vrr, flip.locked, ); self.trigger_present(); if let Some(expected_seq) = flip.expected_seq && seq > expected_seq { let mut margin = self.pre_commit_margin.get(); if margin < flip.refresh_ns { margin += PRE_COMMIT_MARGIN_DELTA; self.pre_commit_margin.set(margin); self.pre_commit_margin_decay.reset(margin); } } debounce.debounce().await; } } fn create_transaction(&self) -> Transaction { Transaction { state: self.state.clone(), changes: Default::default(), } } fn handle_render_ctx_change(self: &Rc) { self.needs_format_update.set(true); self.reapply_state(); self.notify_frontend(); } fn reapply_state(self: &Rc) { let Err(e) = self.reapply_state_() else { return; }; log::error!("Could not reapply state: {}", ErrorFmt(e)); let retry = { let bs = &mut *self.persistent_state.backend_state.borrow_mut(); mem::replace(&mut bs.format, XRGB8888) != XRGB8888 }; if retry { log::info!("Retrying with format {}", XRGB8888.name); let Err(e) = self.reapply_state_() else { return; }; log::error!("Could not reapply state: {}", ErrorFmt(e)); } let retry = { let def = default_state(&self.state); let bs = &mut *self.persistent_state.backend_state.borrow_mut(); mem::replace(bs, def.clone()) != def }; if retry { log::info!("Retrying with default state"); let Err(e) = self.reapply_state_() else { return; }; log::error!("Could not reapply state: {}", ErrorFmt(e)); } self.tasks.take(); self.vo_state.take(); } fn reapply_state_(self: &Rc) -> Result<(), BackendConnectorTransactionError> { let mut transaction = self.create_transaction(); transaction.add(self, self.persistent_state.backend_state.borrow().clone())?; transaction.prepare()?.apply(); Ok(()) } fn notify_frontend(self: &Rc) { let state = self.persistent_state.backend_state.borrow().clone(); let desired_state = match state.enabled { true => FrontendState::Desktop, false => FrontendState::NonDesktop, }; let current_state = self.frontend_state.get(); if desired_state != current_state { if current_state != FrontendState::Disconnected { self.events.send_event(ConnectorEvent::Disconnected); } self.events .send_event(ConnectorEvent::Connected(MonitorInfo { modes: None, output_id: self.output_id.clone(), width_mm: Default::default(), height_mm: Default::default(), non_desktop: Default::default(), non_desktop_effective: !state.enabled, vrr_capable: true, eotfs: LinearizeExt::variants() .filter(|v| *v != Default::default()) .collect(), color_spaces: LinearizeExt::variants() .filter(|v| *v != Default::default()) .collect(), primaries: Primaries::SRGB, luminance: Default::default(), state: state.clone(), })); if state.enabled { self.needs_format_update.set(true); let hc = Rc::new(VirtualHc { o: self.clone() }); self.events .send_event(ConnectorEvent::HardwareCursor(Some(hc))); } } if state.enabled && self.needs_format_update.take() { self.events.send_event(ConnectorEvent::FormatsChanged( self.state.virtual_outputs.formats.get(), )); } self.frontend_state.set(desired_state); } } impl Transaction { fn add( &mut self, connector: &Rc, change: BackendConnectorState, ) -> Result<(), BackendConnectorTransactionError> { if change.mode.width <= 0 || change.mode.height <= 0 { return Err(BackendConnectorTransactionError::UnsupportedMode( connector.kernel_id(), change.mode, )); } if change.gamma_lut.is_some() { return Err(BackendConnectorTransactionError::GammaLutNotSupported( connector.kernel_id(), )); } self.changes.insert( connector.id, TransactionChange { output: connector.clone(), new: change, }, ); Ok(()) } fn prepare(&mut self) -> Result { let mut changes = vec![]; let ctx = self.state.render_ctx.get(); for change in self.changes.drain_values() { let old_backend_state = change .output .persistent_state .backend_state .borrow() .clone(); let old_vo_state = change.output.vo_state.get(); let mut new_vo_state = (*old_vo_state).clone(); let mode = change.new.mode; 'discard_fbs: { if let Some(fbs) = &new_vo_state.fbs { macro_rules! discard { () => { new_vo_state.fbs = None; break 'discard_fbs; }; } if !change.new.enabled { discard!(); } let Some(ctx) = &ctx else { discard!(); }; if !rc_eq(&fbs.ctx, ctx) { discard!(); } if fbs.format != change.new.format { discard!(); } let fb = &fbs.fbs[FbType::Primary]; if (fb.width, fb.height) != mode.size() { discard!(); } } } if new_vo_state.fbs.is_none() { new_vo_state.locked.set(true); } if change.new.enabled && new_vo_state.fbs.is_none() { let Some(ctx) = &ctx else { return Err(BackendConnectorTransactionError::NoRenderContext); }; let bb = match ctx.acquire_blend_buffer(mode.width, mode.height) { Ok(bb) => Some(bb), Err(e) => { log::warn!("Could not create a blend buffer: {}", ErrorFmt(e)); None } }; let sizes = static_map! { FbType::Cursor => (CURSOR_SIZE, CURSOR_SIZE), FbType::Primary => mode.size(), }; let fbs = allocate_scanout_buffers(&self.state, ctx, change.new.format, sizes) .map_err(|e| { BackendConnectorTransactionError::AllocateScanoutBuffers( change.output.kernel_id(), Box::new(e), ) })?; new_vo_state.fbs = Some(Rc::new(FbState { ctx: ctx.clone(), format: change.new.format, blend_buffer: bb, fbs, })); } changes.push(PreparedTransactionChange { output: change.output, old_backend_state, old_vo_state, new_backend_state: change.new, new_vo_state: Rc::new(new_vo_state), }); } Ok(PreparedTransaction { state: self.state.clone(), changes, }) } } impl PreparedTransaction { fn apply(&mut self) { let eng = &self.state.eng; for change in &mut self.changes { let o = &change.output; let mut tasks = None; let ns = &change.new_backend_state; if ns.enabled && ns.active { tasks = Some([ eng.spawn2("vo-present", Phase::Present, o.clone().present_task()), eng.spawn("vo-vblank", o.clone().vblank_task()), ]); o.damage(); if let Some(on) = self.state.root.outputs.get(&o.id) { on.global.add_damage_area(&on.global.pos.get()); on.global.connector.damage(); } } else { if let Some(mut flip) = o.pending_flip.take() { flip.frame_data = Some(None); o.pending_flip.set(Some(flip)); } else { o.frame_data.take(); } } o.tasks.set(tasks); o.trigger_flip.trigger(); *o.persistent_state.backend_state.borrow_mut() = ns.clone(); o.vo_state.set(change.new_vo_state.clone()); o.notify_frontend(); mem::swap(&mut change.new_vo_state, &mut change.old_vo_state); mem::swap(&mut change.new_backend_state, &mut change.old_backend_state); } } } impl BackendConnectorTransaction for Transaction { fn add( &mut self, connector: &Rc, change: BackendConnectorState, ) -> Result<(), BackendConnectorTransactionError> { let Ok(connector) = (connector.clone() as Rc).downcast::() else { return Err(BackendConnectorTransactionError::UnsupportedConnectorType( connector.kernel_id(), )); }; self.add(&connector, change) } fn prepare( mut self: Box, ) -> Result, BackendConnectorTransactionError> { (*self).prepare().map(|t| Box::new(t) as _) } } impl BackendPreparedConnectorTransaction for PreparedTransaction { fn apply( mut self: Box, ) -> Result, BackendConnectorTransactionError> { (*self).apply(); Ok(self) } } impl BackendAppliedConnectorTransaction for PreparedTransaction { fn commit(self: Box) { // nothing } fn rollback(mut self: Box) -> Result<(), BackendConnectorTransactionError> { (*self).apply(); Ok(()) } } #[derive(Debug, Error)] enum AllocError { #[error("GfxContext does not support the format")] GfxFormatNotSupported, #[error("Could not allocate the BO")] CreateBo(#[source] AllocatorError), #[error("Could not import the dmabuf into the GfxContext")] ImportImage(#[source] GfxError), #[error("Could not create a texture")] CreateTexture(#[source] GfxError), #[error("Could not create a framebuffer")] CreateFb(#[source] GfxError), } fn allocate_scanout_buffers( state: &Rc, ctx: &Rc, format: &'static Format, sizes: StaticMap, ) -> Result, AllocError> { let Some(gfx_format) = ctx.formats().get(&format.drm) else { return Err(AllocError::GfxFormatNotSupported); }; let mut needs_render_usage = false; let mut modifiers = vec![]; for modifier in gfx_format.read_modifiers.iter().copied() { let Some(write_modifier) = gfx_format.write_modifiers.get(&modifier) else { continue; }; needs_render_usage |= write_modifier.needs_render_usage; modifiers.push(modifier); } let mut usage = BufferUsage::none(); if needs_render_usage { usage |= BO_USE_RENDERING; } let create_fb = |(width, height): (i32, i32)| { let bo = ctx .allocator() .create_bo(&state.dma_buf_ids, width, height, format, &modifiers, usage) .map_err(AllocError::CreateBo)?; let img = ctx .clone() .dmabuf_img(bo.dmabuf()) .map_err(AllocError::ImportImage)?; let tex = img .clone() .to_texture() .map_err(AllocError::CreateTexture)?; let fb = img.clone().to_framebuffer().map_err(AllocError::CreateFb)?; Ok(VoFb { width, height, _bo: bo, tex, fb, }) }; let fbs = static_map! { t => create_fb(sizes[t])?, }; Ok(fbs) }