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

@ -744,6 +744,10 @@ impl Client {
self.send(&ClientMessage::SetDirectScanoutEnabled { device, enabled });
}
pub fn set_flip_margin(&self, device: DrmDevice, margin: Duration) {
self.send(&ClientMessage::SetFlipMargin { device, margin });
}
pub fn connector_connected(&self, connector: Connector) -> bool {
let res = self.send_with_response(&ClientMessage::ConnectorConnected { connector });
get_response!(res, false, ConnectorConnected { connected });

View file

@ -513,6 +513,10 @@ pub enum ClientMessage<'a> {
connector: Connector,
format: Format,
},
SetFlipMargin {
device: DrmDevice,
margin: Duration,
},
}
#[derive(Serialize, Deserialize, Debug)]

View file

@ -12,7 +12,7 @@ use {
_private::WireMode,
},
serde::{Deserialize, Serialize},
std::str::FromStr,
std::{str::FromStr, time::Duration},
};
/// The mode of a connector.
@ -504,6 +504,16 @@ impl DrmDevice {
pub fn set_direct_scanout_enabled(self, enabled: bool) {
get!().set_direct_scanout_enabled(Some(self), enabled);
}
/// Sets the flip margin of this device.
///
/// 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.
pub fn set_flip_margin(self, margin: Duration) {
get!().set_flip_margin(self, margin);
}
}
/// A graphics API.

View file

@ -7,6 +7,7 @@
- Upload shm textures on a separate thread in the Vulkan renderer.
- Disable implicit sync in KMS.
- Implement frame scheduling for KMS.
- The JAY_MAX_RENDER_TIME_NSEC environment variable has been removed.
# 1.5.0 (2024-09-02)

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 {

View file

@ -274,6 +274,7 @@ pub struct ConfigDrmDevice {
pub match_: DrmDeviceMatch,
pub gfx_api: Option<GfxApi>,
pub direct_scanout_enabled: Option<bool>,
pub flip_margin_ms: Option<f64>,
}
#[derive(Debug, Clone)]

View file

@ -154,7 +154,7 @@ macro_rules! ty {
ty!(str, 'a, String, &'a str, v, v.as_str(), "a string");
ty!(int, 'a, Integer, i64, v, *v, "an integer");
ty!(flt, 'a, Float, f64, v, *v, "a float");
// ty!(flt, 'a, Float, f64, v, *v, "a float");
ty!(bol, 'a, Boolean, bool, v, *v, "a boolean");
ty!(arr, 'a, Array, &'a [Spanned<Value>], v, &**v, "an array");
// ty!(tbl, 'a, Table, &'a IndexMap<Spanned<String>, Spanned<Value>>, v, v, "a table");

View file

@ -2,7 +2,7 @@ use {
crate::{
config::{
context::Context,
extractor::{bol, opt, recover, str, val, Extractor, ExtractorError},
extractor::{bol, fltorint, opt, recover, str, val, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
parsers::{
drm_device_match::{DrmDeviceMatchParser, DrmDeviceMatchParserError},
@ -45,12 +45,14 @@ impl<'a> Parser for DrmDeviceParser<'a> {
table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.cx, span, table);
let (name, match_val, direct_scanout_enabled, gfx_api_val) = ext.extract((
opt(str("name")),
val("match"),
recover(opt(bol("direct-scanout"))),
opt(val("gfx-api")),
))?;
let (name, match_val, direct_scanout_enabled, gfx_api_val, flip_margin_ms) =
ext.extract((
opt(str("name")),
val("match"),
recover(opt(bol("direct-scanout"))),
opt(val("gfx-api")),
recover(opt(fltorint("flip-margin-ms"))),
))?;
let gfx_api = match gfx_api_val {
Some(api) => match api.parse(&mut GfxApiParser) {
Ok(m) => Some(m),
@ -80,6 +82,7 @@ impl<'a> Parser for DrmDeviceParser<'a> {
match_: match_val.parse_map(&mut DrmDeviceMatchParser(self.cx))?,
direct_scanout_enabled: direct_scanout_enabled.despan(),
gfx_api,
flip_margin_ms: flip_margin_ms.despan(),
})
}
}

View file

@ -2,7 +2,7 @@ use {
crate::{
config::{
context::Context,
extractor::{flt, opt, s32, Extractor, ExtractorError},
extractor::{fltorint, opt, s32, Extractor, ExtractorError},
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
Mode,
},
@ -37,7 +37,7 @@ impl<'a> Parser for ModeParser<'a> {
) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table);
let (width, height, refresh_rate) =
ext.extract((s32("width"), s32("height"), opt(flt("refresh-rate"))))?;
ext.extract((s32("width"), s32("height"), opt(fltorint("refresh-rate"))))?;
Ok(Mode {
width: width.value,
height: height.value,

View file

@ -35,7 +35,7 @@ use {
set_vrr_mode, Connector, DrmDevice,
},
},
std::{cell::RefCell, io::ErrorKind, path::PathBuf, rc::Rc},
std::{cell::RefCell, io::ErrorKind, path::PathBuf, rc::Rc, time::Duration},
};
fn default_seat() -> Seat {
@ -245,6 +245,9 @@ impl ConfigDrmDevice {
if let Some(dse) = self.direct_scanout_enabled {
d.set_direct_scanout_enabled(dse);
}
if let Some(fm) = self.flip_margin_ms {
d.set_flip_margin(Duration::from_nanos((fm * 1_000_000.0) as _));
}
}
}

View file

@ -653,6 +653,10 @@
"gfx-api": {
"description": "If specified, sets the graphics API to use for this device.\n",
"$ref": "#/$defs/GfxApi"
},
"flip-margin-ms": {
"type": "number",
"description": "If specified, sets the flip margin of this device.\n\nThis is duration between the compositor initiating a page flip and the output's\nvblank event. This determines the minimum input latency. The default is 1.5 ms.\n\nNote that if the margin is too small, the compositor will dynamically increase it.\n"
}
},
"required": [

View file

@ -1266,6 +1266,17 @@ The table has the following fields:
The value of this field should be a [GfxApi](#types-GfxApi).
- `flip-margin-ms` (optional):
If specified, sets the flip margin of this device.
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.
The value of this field should be a number.
<a name="types-DrmDeviceMatch"></a>
### `DrmDeviceMatch`

View file

@ -944,6 +944,16 @@ DrmDevice:
required: false
description: |
If specified, sets the graphics API to use for this device.
flip-margin-ms:
kind: number
required: false
description: |
If specified, sets the flip margin of this device.
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.
GfxApi:

View file

@ -75,6 +75,11 @@ request set_fb_format (since = 8) {
format: str,
}
request set_flip_margin (since = 10) {
dev: str,
margin_ns: pod(u64),
}
# events
event global {
@ -151,3 +156,7 @@ event fb_format (since = 8) {
name: str,
current: u32,
}
event flip_margin (since = 10) {
margin_ns: pod(u64),
}