1
0
Fork 0
forked from wry/wry
wry/src/virtual_output.rs

1097 lines
36 KiB
Rust

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<String, Rc<VirtualOutput>>,
formats: CloneCell<Rc<Vec<&'static Format>>>,
states: CopyHashMap<String, Rc<PersistentVirtualOutputState>>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
enum FrontendState {
#[default]
Disconnected,
Desktop,
NonDesktop,
}
pub struct VirtualOutput {
state: Rc<State>,
id: ConnectorId,
kernel_id: ConnectorKernelId,
output_id: Rc<OutputId>,
name: String,
frontend_state: Cell<FrontendState>,
needs_format_update: Cell<bool>,
events: OnChange<ConnectorEvent>,
damage: NumCell<u64>,
present_trigger: AsyncEvent,
persistent_state: Rc<PersistentVirtualOutputState>,
vo_state: CloneCell<Rc<VoState>>,
tasks: Cell<Option<[SpawnedFuture<()>; 2]>>,
flip_task: Cell<Option<SpawnedFuture<()>>>,
next_vblank_nsec: Cell<u64>,
pre_commit_margin: Cell<u64>,
pre_commit_margin_decay: GeometricDecay,
need_vblank: AsyncEvent,
seq: NumCell<u64>,
pending_flip: Cell<Option<ScheduledFlip>>,
trigger_flip: AsyncEvent,
cursor_damage: Cell<bool>,
cursor_programming: Cell<Option<CursorProgramming>>,
frame_data: RefCell<Option<FrameData>>,
}
struct PersistentVirtualOutputState {
backend_state: RefCell<BackendConnectorState>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct CursorProgramming {
x: i32,
y: i32,
}
struct ScheduledFlip {
on: Rc<OutputNode>,
refresh_ns: u64,
vrr: bool,
tearing: bool,
expected_seq: Option<u64>,
locked: bool,
frame_data: Option<Option<FrameData>>,
}
#[derive(Default, Clone)]
struct VoState {
fbs: Option<Rc<FbState>>,
locked: Cell<bool>,
}
#[derive(Copy, Clone, Linearize, Eq, PartialEq)]
enum FbType {
Primary,
Cursor,
}
struct FbState {
ctx: Rc<dyn GfxContext>,
format: &'static Format,
blend_buffer: Option<Rc<dyn GfxBlendBuffer>>,
fbs: StaticMap<FbType, VoFb>,
}
struct VoFb {
width: i32,
height: i32,
_bo: Rc<dyn BufferObject>,
tex: Rc<dyn GfxTexture>,
fb: Rc<dyn GfxFramebuffer>,
}
struct Transaction {
state: Rc<State>,
changes: AHashMap<ConnectorId, TransactionChange>,
}
struct TransactionChange {
output: Rc<VirtualOutput>,
new: BackendConnectorState,
}
struct PreparedTransaction {
state: Rc<State>,
changes: Vec<PreparedTransactionChange>,
}
struct PreparedTransactionChange {
output: Rc<VirtualOutput>,
old_backend_state: BackendConnectorState,
old_vo_state: Rc<VoState>,
new_backend_state: BackendConnectorState,
new_vo_state: Rc<VoState>,
}
struct Latched {
pass: GfxRenderPass,
damage_count: u64,
damage: Region,
locked: bool,
}
struct CursorChange<'a> {
swap_buffer: Option<Option<FdSync>>,
enabled: bool,
x: i32,
y: i32,
buffer: &'a VoFb,
}
struct DirectScanoutData {
buffer_resv: Option<Rc<dyn BufferResv>>,
tex: Rc<dyn GfxTexture>,
acquire_sync: AcquireSync,
release_sync: ReleaseSync,
pos: DirectScanoutPosition,
}
struct FrameData {
dsd: Option<DirectScanoutData>,
}
const CURSOR_SIZE: i32 = 256;
impl HardwareCursorUpdate for CursorChange<'_> {
fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
fn get_buffer(&self) -> Rc<dyn GfxFramebuffer> {
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<FdSync>) {
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<State>, name: &str) -> Rc<VirtualOutput> {
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<dyn Connector>));
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<State>, 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<ConnectorEvent> {
self.events.events.pop()
}
fn on_change(&self, cb: Rc<dyn Fn()>) {
self.events.on_change.set(Some(cb));
}
fn damage(&self) {
self.damage.fetch_add(1);
self.trigger_present();
}
fn drm_dev(&self) -> Option<DrmDeviceId> {
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<dyn BackendConnectorTransactionTypeDyn> {
#[derive(Eq, PartialEq, Hash)]
struct TT;
impl BackendConnectorTransactionType for TT {}
Box::new(TT)
}
fn create_transaction(
&self,
) -> Result<Box<dyn BackendConnectorTransaction>, BackendConnectorTransactionError> {
Ok(Box::new(self.create_transaction()))
}
fn name(&self) -> String {
self.name.clone()
}
}
struct VirtualHc {
o: Rc<VirtualOutput>,
}
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<Self>) {
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<OutputNode>) -> Option<Latched> {
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<ColorDescription>,
cd: &Rc<ColorDescription>,
latched: &Latched,
) -> Option<DirectScanoutData> {
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<OutputNode>, fb: &VoFb) -> Option<Option<FdSync>> {
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<Self>) {
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<Self>) {
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>) {
self.needs_format_update.set(true);
self.reapply_state();
self.notify_frontend();
}
fn reapply_state(self: &Rc<Self>) {
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<Self>) -> 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<Self>) {
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<VirtualOutput>,
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<PreparedTransaction, BackendConnectorTransactionError> {
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<dyn Connector>,
change: BackendConnectorState,
) -> Result<(), BackendConnectorTransactionError> {
let Ok(connector) = (connector.clone() as Rc<dyn Any>).downcast::<VirtualOutput>() else {
return Err(BackendConnectorTransactionError::UnsupportedConnectorType(
connector.kernel_id(),
));
};
self.add(&connector, change)
}
fn prepare(
mut self: Box<Self>,
) -> Result<Box<dyn BackendPreparedConnectorTransaction>, BackendConnectorTransactionError>
{
(*self).prepare().map(|t| Box::new(t) as _)
}
}
impl BackendPreparedConnectorTransaction for PreparedTransaction {
fn apply(
mut self: Box<Self>,
) -> Result<Box<dyn BackendAppliedConnectorTransaction>, BackendConnectorTransactionError> {
(*self).apply();
Ok(self)
}
}
impl BackendAppliedConnectorTransaction for PreparedTransaction {
fn commit(self: Box<Self>) {
// nothing
}
fn rollback(mut self: Box<Self>) -> 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<State>,
ctx: &Rc<dyn GfxContext>,
format: &'static Format,
sizes: StaticMap<FbType, (i32, i32)>,
) -> Result<StaticMap<FbType, VoFb>, 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)
}