1
0
Fork 0
forked from wry/wry

metal: make post_commit_margin configurable

This commit is contained in:
Julian Orth 2024-09-14 13:56:22 +02:00
parent 76b0f2f734
commit 02ece60909
24 changed files with 224 additions and 55 deletions

View file

@ -456,6 +456,9 @@ pub trait BackendDrmDevice {
let _ = lessee;
let _ = connector_ids;
}
fn set_flip_margin(&self, margin: u64) {
let _ = margin;
}
}
pub trait BackendDrmLease {

View file

@ -23,11 +23,7 @@ use {
},
},
},
std::{
env,
rc::{Rc, Weak},
sync::LazyLock,
},
std::rc::{Rc, Weak},
uapi::c,
};
@ -85,19 +81,9 @@ enum CursorProgramming {
}
pub const DEFAULT_PRE_COMMIT_MARGIN: u64 = 16_000_000; // 16ms
pub const MIN_POST_COMMIT_MARGIN: u64 = 1_500_000; // 1.5ms
pub const MAX_POST_COMMIT_MARGIN: u64 = 16_000_000; // 16ms
pub const DEFAULT_POST_COMMIT_MARGIN: u64 = MIN_POST_COMMIT_MARGIN;
pub const DEFAULT_POST_COMMIT_MARGIN: u64 = 1_500_000; // 1.5ms;
pub const POST_COMMIT_MARGIN_DELTA: u64 = 500_000; // 500us
static NO_FRAME_SCHEDULING: LazyLock<bool> = LazyLock::new(|| {
let res = env::var("JAY_NO_FRAME_SCHEDULING").ok().as_deref() == Some("1");
if res {
log::warn!("Frame scheduling is disabled.");
}
res
});
impl MetalConnector {
pub fn schedule_present(&self) {
self.present_trigger.trigger();
@ -113,10 +99,13 @@ impl MetalConnector {
}
let mut expected_sequence = self.sequence.get() + 1;
let mut start = Time::now_unchecked();
let use_frame_scheduling = !self.try_async_flip() && !*NO_FRAME_SCHEDULING;
let use_frame_scheduling = !self.try_async_flip();
if use_frame_scheduling {
let margin = self.pre_commit_margin.get() + self.post_commit_margin.get();
let next_present = self.next_flip_nsec.get().saturating_sub(margin);
let next_present = self
.next_flip_nsec
.get()
.saturating_sub(self.pre_commit_margin.get())
.saturating_sub(self.post_commit_margin.get());
if start.nsec() < next_present {
self.state.ring.timeout(next_present).await.unwrap();
start = Time::now_unchecked();

View file

@ -10,8 +10,7 @@ use {
backends::metal::{
present::{
DirectScanoutCache, PresentFb, DEFAULT_POST_COMMIT_MARGIN,
DEFAULT_PRE_COMMIT_MARGIN, MAX_POST_COMMIT_MARGIN, MIN_POST_COMMIT_MARGIN,
POST_COMMIT_MARGIN_DELTA,
DEFAULT_PRE_COMMIT_MARGIN, POST_COMMIT_MARGIN_DELTA,
},
MetalBackend, MetalError,
},
@ -27,6 +26,7 @@ use {
wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC, KIND_ZERO_COPY},
},
state::State,
tree::OutputNode,
udev::UdevDevice,
utils::{
asyncevent::AsyncEvent, bitflags::BitflagsExt, cell_ext::CellExt, clonecell::CloneCell,
@ -108,6 +108,7 @@ pub struct MetalDrmDevice {
pub leases: CopyHashMap<MetalLeaseId, MetalLeaseData>,
pub leases_to_break: CopyHashMap<MetalLeaseId, MetalLeaseData>,
pub paused: Cell<bool>,
pub min_post_commit_margin: Cell<u64>,
}
impl Debug for MetalDrmDevice {
@ -279,6 +280,19 @@ impl BackendDrmDevice for MetalDrmDevice {
});
lessee.created(lease);
}
fn set_flip_margin(&self, margin: u64) {
self.min_post_commit_margin.set(margin);
if let Some(dd) = self.backend.device_holder.drm_devices.get(&self.devnum) {
for c in dd.connectors.lock().values() {
c.post_commit_margin.set(margin);
c.post_commit_margin_decay.reset(margin);
if let Some(output) = self.backend.state.root.outputs.get(&c.connector_id) {
output.flip_margin_ns.set(Some(margin));
}
}
}
}
}
pub struct HandleEvents {
@ -1067,8 +1081,8 @@ fn create_connector(
expected_sequence: Default::default(),
pre_commit_margin_decay: GeometricDecay::new(0.5, DEFAULT_PRE_COMMIT_MARGIN),
pre_commit_margin: Cell::new(DEFAULT_PRE_COMMIT_MARGIN),
post_commit_margin_decay: GeometricDecay::new(0.1, DEFAULT_POST_COMMIT_MARGIN),
post_commit_margin: Cell::new(DEFAULT_POST_COMMIT_MARGIN),
post_commit_margin_decay: GeometricDecay::new(0.1, dev.min_post_commit_margin.get()),
post_commit_margin: Cell::new(dev.min_post_commit_margin.get()),
vblank_miss_sec: Cell::new(0),
vblank_miss_this_sec: Default::default(),
presentation_is_sync: Cell::new(false),
@ -1750,6 +1764,7 @@ impl MetalBackend {
leases: Default::default(),
leases_to_break: Default::default(),
paused: Cell::new(false),
min_post_commit_margin: Cell::new(DEFAULT_POST_COMMIT_MARGIN),
});
let (connectors, futures) = get_connectors(self, &dev, &resources.connectors)?;
@ -1943,24 +1958,11 @@ impl MetalBackend {
if let Some(fb) = connector.next_framebuffer.take() {
*connector.active_framebuffer.borrow_mut() = Some(fb);
}
let dd = connector.display.borrow();
let global = self.state.root.outputs.get(&connector.connector_id);
if let Some(expected) = connector.expected_sequence.take() {
if connector.vblank_miss_sec.replace(tv_sec) != tv_sec {
let n_missed = connector.vblank_miss_this_sec.replace(0);
if n_missed > 0 {
log::debug!("{}: Missed {n_missed} page flips", connector.kernel_id());
let new_margin = (connector.post_commit_margin.get()
+ POST_COMMIT_MARGIN_DELTA)
.min(MAX_POST_COMMIT_MARGIN);
connector.post_commit_margin_decay.reset(new_margin);
connector.post_commit_margin.set(new_margin);
} else {
connector
.post_commit_margin_decay
.add(MIN_POST_COMMIT_MARGIN);
connector
.post_commit_margin
.set(connector.post_commit_margin_decay.get());
}
self.update_post_commit_margin(dev, &connector, &dd, global.as_deref());
}
let actual = connector.sequence.get();
if expected < actual {
@ -1973,12 +1975,10 @@ impl MetalBackend {
{
connector.schedule_present();
}
let dd = connector.display.borrow_mut();
connector
.next_flip_nsec
.set(tv_sec as u64 * 1_000_000_000 + tv_usec as u64 * 1000 + dd.refresh as u64);
{
let global = self.state.root.outputs.get(&connector.connector_id);
let mut flags = KIND_HW_COMPLETION;
if connector.presentation_is_sync.get() {
flags |= KIND_VSYNC;
@ -2002,6 +2002,38 @@ impl MetalBackend {
}
}
fn update_post_commit_margin(
&self,
dev: &MetalDrmDeviceData,
connector: &MetalConnector,
dd: &ConnectorDisplayData,
global: Option<&OutputNode>,
) {
let n_missed = connector.vblank_miss_this_sec.replace(0);
let old_margin = connector.post_commit_margin.get();
let new_margin = if n_missed > 0 {
log::debug!("{}: Missed {n_missed} page flips", connector.kernel_id());
let refresh = dd.refresh as u64;
if old_margin >= refresh {
return;
}
let new_margin = (old_margin + POST_COMMIT_MARGIN_DELTA).min(refresh);
connector.post_commit_margin_decay.reset(new_margin);
new_margin
} else {
let min_margin = dev.dev.min_post_commit_margin.get();
if min_margin >= connector.post_commit_margin.get() {
return;
}
connector.post_commit_margin_decay.add(min_margin);
connector.post_commit_margin_decay.get()
};
connector.post_commit_margin.set(new_margin);
if let Some(global) = &global {
global.flip_margin_ns.set(Some(new_margin));
}
}
fn reset_planes(&self, dev: &MetalDrmDeviceData, changes: &mut Change, preserve: &Preserve) {
for plane in dev.dev.planes.values() {
if preserve.planes.contains(&plane.id) {

View file

@ -15,6 +15,7 @@ use {
fmt::{Display, Formatter},
rc::Rc,
str::FromStr,
time::Duration,
},
};
@ -66,6 +67,31 @@ pub enum CardCommand {
Api(ApiArgs),
/// Modify the direct scanout setting of the card.
DirectScanout(DirectScanoutArgs),
/// Modify timing settings of the card.
Timing(TimingArgs),
}
#[derive(Args, Debug, Clone)]
pub struct TimingArgs {
#[clap(subcommand)]
pub cmd: TimingCmd,
}
#[derive(Subcommand, Debug, Clone)]
pub enum TimingCmd {
/// Sets the margin to use for page flips.
///
/// This is duration between the compositor initiating a page flip and the output's
/// vblank event. This determines the minimum input latency. The default is 1.5 ms.
///
/// Note that if the margin is too small, the compositor will dynamically increase it.
SetFlipMargin(SetFlipMarginArgs),
}
#[derive(Args, Debug, Clone)]
pub struct SetFlipMarginArgs {
/// The margin in milliseconds.
pub margin_ms: f64,
}
#[derive(Args, Debug, Clone)]
@ -341,6 +367,7 @@ struct Output {
pub tearing_mode: TearingMode,
pub formats: Vec<String>,
pub format: Option<String>,
pub flip_margin_ns: Option<u64>,
}
#[derive(Copy, Clone, Debug)]
@ -626,6 +653,18 @@ impl Randr {
},
});
}
CardCommand::Timing(ts) => match ts.cmd {
TimingCmd::SetFlipMargin(sfm) => {
self.handle_error(randr, |msg| {
eprintln!("Could not modify the flip margin: {}", msg);
});
tc.send(jay_randr::SetFlipMargin {
self_id: randr,
dev: &args.card,
margin_ns: (sfm.margin_ms * 1_000_000.0) as u64,
});
}
},
}
tc.round_trip().await;
}
@ -759,6 +798,14 @@ impl Randr {
};
println!(" transform: {}", name);
}
if let Some(flip_margin_ns) = o.flip_margin_ns {
if flip_margin_ns != 1_500_000 {
println!(
" flip margin: {:?}",
Duration::from_nanos(flip_margin_ns)
);
}
}
if o.modes.is_not_empty() && modes {
println!(" modes:");
for mode in &o.modes {
@ -838,6 +885,7 @@ impl Randr {
tearing_mode: TearingMode::NEVER,
formats: vec![],
format: None,
flip_margin_ns: None,
});
});
jay_randr::NonDesktopOutput::handle(tc, randr, data.clone(), |data, msg| {
@ -865,6 +913,7 @@ impl Randr {
tearing_mode: TearingMode::NEVER,
formats: vec![],
format: None,
flip_margin_ns: None,
});
});
jay_randr::VrrState::handle(tc, randr, data.clone(), |data, msg| {
@ -896,6 +945,12 @@ impl Randr {
output.format = Some(msg.name.to_string());
}
});
jay_randr::FlipMargin::handle(tc, randr, data.clone(), |data, msg| {
let mut data = data.borrow_mut();
let c = data.connectors.last_mut().unwrap();
let output = c.output.as_mut().unwrap();
output.flip_margin_ns = Some(msg.margin_ns);
});
jay_randr::Mode::handle(tc, randr, data.clone(), |data, msg| {
let mut data = data.borrow_mut();
let c = data.connectors.last_mut().unwrap();

View file

@ -507,6 +507,7 @@ fn create_dummy_output(state: &Rc<State>) {
vblank_event: Default::default(),
latch_event: Default::default(),
presentation_event: Default::default(),
flip_margin_ns: Default::default(),
});
let dummy_workspace = Rc::new(WorkspaceNode {
id: state.node_ids.next(),

View file

@ -752,6 +752,13 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_set_flip_margin(&self, device: DrmDevice, margin: Duration) -> Result<(), CphError> {
self.get_drm_device(device)?
.dev
.set_flip_margin(margin.as_nanos().try_into().unwrap_or(u64::MAX));
Ok(())
}
fn handle_set_direct_scanout_enabled(
&self,
device: Option<DrmDevice>,
@ -1936,6 +1943,9 @@ impl ConfigProxyHandler {
ClientMessage::ConnectorSetFormat { connector, format } => self
.handle_connector_set_format(connector, format)
.wrn("connector_set_format")?,
ClientMessage::SetFlipMargin { device, margin } => self
.handle_set_flip_margin(device, margin)
.wrn("set_flip_margin")?,
}
Ok(())
}

View file

@ -70,7 +70,7 @@ impl Global for JayCompositorGlobal {
}
fn version(&self) -> u32 {
9
10
}
fn required_caps(&self) -> ClientCaps {

View file

@ -29,6 +29,7 @@ pub struct JayRandr {
const VRR_CAPABLE_SINCE: Version = Version(2);
const TEARING_SINCE: Version = Version(3);
const FORMAT_SINCE: Version = Version(8);
const FLIP_MARGIN_SINCE: Version = Version(10);
impl JayRandr {
pub fn new(id: JayRandrId, client: &Rc<Client>, version: Version) -> Self {
@ -144,6 +145,14 @@ impl JayRandr {
}
}
}
if self.version >= FLIP_MARGIN_SINCE {
if let Some(margin_ns) = node.flip_margin_ns.get() {
self.client.event(FlipMargin {
self_id: self.id,
margin_ns,
});
}
}
let current_mode = global.mode.get();
for mode in &global.modes {
self.client.event(Mode {
@ -395,6 +404,14 @@ impl JayRandrRequestHandler for JayRandr {
c.global.connector.connector.set_fb_format(format);
Ok(())
}
fn set_flip_margin(&self, req: SetFlipMargin<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let Some(dev) = self.get_device(req.dev) else {
return Ok(());
};
dev.dev.set_flip_margin(req.margin_ns);
Ok(())
}
}
object_base! {

View file

@ -179,6 +179,7 @@ impl ConnectorHandler {
latch_event: Default::default(),
vblank_event: Default::default(),
presentation_event: Default::default(),
flip_margin_ns: Default::default(),
});
on.update_visible();
on.update_rects();

View file

@ -330,7 +330,7 @@ impl ToolClient {
self_id: s.registry,
name: s.jay_compositor.0,
interface: JayCompositor.name(),
version: s.jay_compositor.1.min(8),
version: s.jay_compositor.1.min(10),
id: id.into(),
});
self.jay_compositor.set(Some(id));

View file

@ -83,6 +83,7 @@ pub struct OutputNode {
pub latch_event: EventSource<dyn LatchListener>,
pub vblank_event: EventSource<dyn VblankListener>,
pub presentation_event: EventSource<dyn PresentationListener>,
pub flip_margin_ns: Cell<Option<u64>>,
}
pub trait LatchListener {