1097 lines
36 KiB
Rust
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)
|
|
}
|