1
0
Fork 0
forked from wry/wry

metal: implement VRR

This commit is contained in:
Julian Orth 2024-07-17 16:30:52 +02:00
parent cd09e57568
commit 2d7c13b0b4
35 changed files with 1320 additions and 91 deletions

View file

@ -95,6 +95,7 @@ pub struct MetalDrmDevice {
pub on_change: OnChange<crate::backend::DrmEvent>,
pub direct_scanout_enabled: Cell<Option<bool>>,
pub is_nvidia: bool,
pub is_amd: bool,
pub lease_ids: MetalLeaseIds,
pub leases: CopyHashMap<MetalLeaseId, MetalLeaseData>,
pub leases_to_break: CopyHashMap<MetalLeaseId, MetalLeaseData>,
@ -299,6 +300,8 @@ pub struct ConnectorDisplayData {
pub refresh: u32,
pub non_desktop: bool,
pub non_desktop_effective: bool,
pub vrr_capable: bool,
pub vrr_requested: bool,
pub monitor_manufacturer: String,
pub monitor_name: String,
@ -319,6 +322,10 @@ impl ConnectorDisplayData {
&& self.monitor_name == other.monitor_name
&& self.monitor_serial_number == other.monitor_serial_number
}
fn should_enable_vrr(&self) -> bool {
self.vrr_requested && self.vrr_capable
}
}
linear_ids!(MetalLeaseIds, MetalLeaseId, u64);
@ -417,6 +424,7 @@ pub struct MetalConnector {
pub can_present: Cell<bool>,
pub has_damage: Cell<bool>,
pub cursor_changed: Cell<bool>,
pub cursor_scheduled: Cell<bool>,
pub next_flip_nsec: Cell<u64>,
pub display: RefCell<ConnectorDisplayData>,
@ -503,7 +511,7 @@ impl HardwareCursor for MetalHardwareCursor {
self.have_changes.set(true);
}
fn commit(&self) {
fn commit(&self, schedule_present: bool) {
if self.generation != self.connector.cursor_generation.get() {
return;
}
@ -520,8 +528,20 @@ impl HardwareCursor for MetalHardwareCursor {
}
self.connector.cursor_sync_file.set(self.sync_file.take());
self.connector.cursor_changed.set(true);
if self.connector.can_present.get() {
self.connector.schedule_present();
if schedule_present {
self.schedule_present();
}
}
fn schedule_present(&self) -> bool {
if self.connector.cursor_changed.get() {
self.connector.cursor_scheduled.set(true);
if self.connector.can_present.get() {
self.connector.schedule_present();
}
true
} else {
false
}
}
@ -604,6 +624,19 @@ impl MetalConnector {
}
}
fn send_vrr_enabled(&self) {
match self.frontend_state.get() {
FrontState::Removed
| FrontState::Disconnected
| FrontState::Unavailable
| FrontState::Connected { non_desktop: true } => return,
FrontState::Connected { non_desktop: false } => {}
}
if let Some(crtc) = self.crtc.get() {
self.send_event(ConnectorEvent::VrrChanged(crtc.vrr_enabled.value.get()));
}
}
fn send_hardware_cursor(self: &Rc<Self>) {
match self.frontend_state.get() {
FrontState::Removed
@ -894,7 +927,7 @@ impl MetalConnector {
Some(crtc) => crtc,
_ => return Ok(()),
};
if (!self.has_damage.get() && !self.cursor_changed.get()) || !self.can_present.get() {
if (!self.has_damage.get() && !self.cursor_scheduled.get()) || !self.can_present.get() {
return Ok(());
}
if !crtc.active.value.get() {
@ -908,6 +941,9 @@ impl MetalConnector {
Some(b) => b,
_ => return Ok(()),
};
let Some(node) = self.state.root.outputs.get(&self.connector_id) else {
return Ok(());
};
let cursor = self.cursor_plane.get();
let mut new_fb = None;
let mut changes = self.master.change();
@ -915,46 +951,52 @@ impl MetalConnector {
if !self.backend.check_render_context(&self.dev) {
return Ok(());
}
if let Some(node) = self.state.root.outputs.get(&self.connector_id) {
let buffer = &buffers[self.next_buffer.get() % buffers.len()];
let mut rr = self.render_result.borrow_mut();
rr.output_id = node.id;
let fb =
self.prepare_present_fb(&mut rr, buffer, &plane, &node, try_direct_scanout)?;
rr.dispatch_frame_requests(self.state.now_msec());
let (crtc_x, crtc_y, crtc_w, crtc_h, src_width, src_height) =
match &fb.direct_scanout_data {
None => {
let plane_w = plane.mode_w.get();
let plane_h = plane.mode_h.get();
(0, 0, plane_w, plane_h, plane_w, plane_h)
}
Some(dsd) => {
let p = &dsd.position;
(
p.crtc_x,
p.crtc_y,
p.crtc_width,
p.crtc_height,
p.src_width,
p.src_height,
)
}
};
let in_fence = fb.sync_file.as_ref().map(|s| s.raw()).unwrap_or(-1);
changes.change_object(plane.id, |c| {
c.change(plane.fb_id, fb.fb.id().0 as _);
c.change(plane.src_w.id, (src_width as u64) << 16);
c.change(plane.src_h.id, (src_height as u64) << 16);
c.change(plane.crtc_x.id, crtc_x as u64);
c.change(plane.crtc_y.id, crtc_y as u64);
c.change(plane.crtc_w.id, crtc_w as u64);
c.change(plane.crtc_h.id, crtc_h as u64);
if !self.dev.is_nvidia {
c.change(plane.in_fence_fd, in_fence as u64);
let buffer = &buffers[self.next_buffer.get() % buffers.len()];
let mut rr = self.render_result.borrow_mut();
rr.output_id = node.id;
let fb = self.prepare_present_fb(&mut rr, buffer, &plane, &node, try_direct_scanout)?;
rr.dispatch_frame_requests(self.state.now_msec());
let (crtc_x, crtc_y, crtc_w, crtc_h, src_width, src_height) =
match &fb.direct_scanout_data {
None => {
let plane_w = plane.mode_w.get();
let plane_h = plane.mode_h.get();
(0, 0, plane_w, plane_h, plane_w, plane_h)
}
});
new_fb = Some(fb);
Some(dsd) => {
let p = &dsd.position;
(
p.crtc_x,
p.crtc_y,
p.crtc_width,
p.crtc_height,
p.src_width,
p.src_height,
)
}
};
let in_fence = fb.sync_file.as_ref().map(|s| s.raw()).unwrap_or(-1);
changes.change_object(plane.id, |c| {
c.change(plane.fb_id, fb.fb.id().0 as _);
c.change(plane.src_w.id, (src_width as u64) << 16);
c.change(plane.src_h.id, (src_height as u64) << 16);
c.change(plane.crtc_x.id, crtc_x as u64);
c.change(plane.crtc_y.id, crtc_y as u64);
c.change(plane.crtc_w.id, crtc_w as u64);
c.change(plane.crtc_h.id, crtc_h as u64);
if !self.dev.is_nvidia {
c.change(plane.in_fence_fd, in_fence as u64);
}
});
new_fb = Some(fb);
} else {
if self.dev.is_amd && crtc.vrr_enabled.value.get() {
// Work around https://gitlab.freedesktop.org/drm/amd/-/issues/2186
if let Some(fb) = &*self.active_framebuffer.borrow() {
changes.change_object(plane.id, |c| {
c.change(plane.fb_id, fb.fb.id().0 as _);
});
}
}
}
let mut cursor_swap_buffer = false;
@ -1027,7 +1069,8 @@ impl MetalConnector {
.discard_presentation_feedback();
Err(MetalError::Commit(e))
} else {
self.perform_screencopies(&new_fb);
node.schedule.presented();
self.perform_screencopies(&new_fb, &node);
if let Some(fb) = new_fb {
if fb.direct_scanout_data.is_none() {
self.next_buffer.fetch_add(1);
@ -1042,14 +1085,12 @@ impl MetalConnector {
self.can_present.set(false);
self.has_damage.set(false);
self.cursor_changed.set(false);
self.cursor_scheduled.set(false);
Ok(())
}
}
fn perform_screencopies(&self, new_fb: &Option<PresentFb>) {
let Some(output) = self.state.root.outputs.get(&self.connector_id) else {
return;
};
fn perform_screencopies(&self, new_fb: &Option<PresentFb>, output: &OutputNode) {
let active_fb;
let fb = match &new_fb {
Some(f) => f,
@ -1173,6 +1214,17 @@ impl MetalConnector {
log::error!("Tried to send available event in invalid state: {state:?}");
}
},
ConnectorEvent::VrrChanged(_) => match state {
FrontState::Connected { non_desktop: false } => {
self.on_change.send_event(event);
}
FrontState::Connected { non_desktop: true }
| FrontState::Removed
| FrontState::Disconnected
| FrontState::Unavailable => {
log::error!("Tried to send vrr-changed event in invalid state: {state:?}");
}
},
}
}
}
@ -1296,6 +1348,32 @@ impl Connector for MetalConnector {
fn drm_object_id(&self) -> Option<DrmConnector> {
Some(self.id)
}
fn set_vrr_enabled(&self, enabled: bool) {
if self.frontend_state.get() != (FrontState::Connected { non_desktop: false }) {
return;
}
let dd = &mut *self.display.borrow_mut();
let old_enabled = dd.should_enable_vrr();
dd.vrr_requested = enabled;
let new_enabled = dd.should_enable_vrr();
if old_enabled == new_enabled {
return;
}
let Some(crtc) = self.crtc.get() else {
return;
};
let mut change = self.master.change();
change.change_object(crtc.id, |c| {
c.change(crtc.vrr_enabled.id, new_enabled as _);
});
if let Err(e) = change.commit(0, 0) {
log::error!("Could not change vrr mode: {}", ErrorFmt(e));
return;
}
crtc.vrr_enabled.value.set(new_enabled);
self.send_vrr_enabled();
}
}
pub struct MetalCrtc {
@ -1312,6 +1390,7 @@ pub struct MetalCrtc {
pub active: MutableProperty<bool>,
pub mode_id: MutableProperty<DrmBlob>,
pub out_fence_ptr: DrmProperty,
pub vrr_enabled: MutableProperty<bool>,
pub mode_blob: CloneCell<Option<Rc<PropBlob>>>,
}
@ -1435,6 +1514,7 @@ fn create_connector(
display: RefCell::new(display),
frontend_state: Cell::new(FrontState::Disconnected),
cursor_changed: Cell::new(false),
cursor_scheduled: Cell::new(false),
cursor_front_buffer: Default::default(),
cursor_swap_buffer: Cell::new(false),
cursor_sync_file: Default::default(),
@ -1545,6 +1625,10 @@ fn create_connector_display_data(
let props = collect_properties(&dev.master, connector)?;
let connector_type = ConnectorType::from_drm(info.connector_type);
let non_desktop = props.get("non-desktop")?.value.get() != 0;
let vrr_capable = match props.get("vrr_capable") {
Ok(c) => c.value.get() == 1,
Err(_) => false,
};
Ok(ConnectorDisplayData {
crtc_id: props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)),
crtcs,
@ -1553,6 +1637,8 @@ fn create_connector_display_data(
refresh,
non_desktop,
non_desktop_effective: non_desktop_override.unwrap_or(non_desktop),
vrr_capable,
vrr_requested: false,
monitor_manufacturer: manufacturer,
monitor_name: name,
monitor_serial_number: serial_number,
@ -1607,6 +1693,7 @@ fn create_crtc(
active: props.get("ACTIVE")?.map(|v| v == 1),
mode_id: props.get("MODE_ID")?.map(|v| DrmBlob(v as u32)),
out_fence_ptr: props.get("OUT_FENCE_PTR")?.id,
vrr_enabled: props.get("VRR_ENABLED")?.map(|v| v == 1),
mode_blob: Default::default(),
})
}
@ -1876,6 +1963,7 @@ impl MetalBackend {
dd.mode = Some(mode.clone());
}
}
dd.vrr_requested = old.vrr_requested;
}
mem::swap(old.deref_mut(), &mut dd);
match c.frontend_state.get() {
@ -1963,8 +2051,10 @@ impl MetalBackend {
width_mm: dd.mm_width as _,
height_mm: dd.mm_height as _,
non_desktop: dd.non_desktop_effective,
vrr_capable: dd.vrr_capable,
}));
connector.send_hardware_cursor();
connector.send_vrr_enabled();
}
pub fn create_drm_device(
@ -2030,9 +2120,11 @@ impl MetalBackend {
};
let mut is_nvidia = false;
let mut is_amd = false;
match gbm.drm.version() {
Ok(v) => {
is_nvidia = v.name.contains_str("nvidia");
is_amd = v.name.contains_str("amdgpu");
if is_nvidia {
log::warn!(
"Device {} use the nvidia driver. IN_FENCE_FD will not be used.",
@ -2068,6 +2160,7 @@ impl MetalBackend {
on_change: Default::default(),
direct_scanout_enabled: Default::default(),
is_nvidia,
is_amd,
lease_ids: Default::default(),
leases: Default::default(),
leases_to_break: Default::default(),
@ -2123,6 +2216,7 @@ impl MetalBackend {
for c in dev.dev.crtcs.values() {
let props = collect_untyped_properties(master, c.id)?;
c.active.value.set(get(&props, c.active.id)? != 0);
c.vrr_enabled.value.set(get(&props, c.vrr_enabled.id)? != 0);
c.mode_id
.value
.set(DrmBlob(get(&props, c.mode_id.id)? as _));
@ -2144,6 +2238,7 @@ impl MetalBackend {
connector.can_present.set(true);
connector.has_damage.set(true);
connector.cursor_changed.set(true);
connector.cursor_scheduled.set(true);
}
if dev.unprocessed_change.get() {
return self.handle_drm_change_(dev, false);
@ -2204,7 +2299,7 @@ impl MetalBackend {
if let Some(fb) = connector.next_framebuffer.take() {
*connector.active_framebuffer.borrow_mut() = Some(fb);
}
if connector.has_damage.get() || connector.cursor_changed.get() {
if connector.has_damage.get() || connector.cursor_scheduled.get() {
connector.schedule_present();
}
let dd = connector.display.borrow_mut();
@ -2282,10 +2377,12 @@ impl MetalBackend {
crtc.connector.set(None);
crtc.active.value.set(false);
crtc.mode_id.value.set(DrmBlob::NONE);
crtc.vrr_enabled.value.set(false);
changes.change_object(crtc.id, |c| {
c.change(crtc.active.id, 0);
c.change(crtc.mode_id.id, 0);
c.change(crtc.out_fence_ptr, 0);
c.change(crtc.vrr_enabled.id, 0);
})
}
}
@ -2483,6 +2580,7 @@ impl MetalBackend {
continue;
}
connector.send_hardware_cursor();
connector.send_vrr_enabled();
connector.update_drm_feedback();
}
Ok(())
@ -2490,6 +2588,7 @@ impl MetalBackend {
fn can_use_current_drm_mode(&self, dev: &Rc<MetalDrmDeviceData>) -> bool {
let mut used_crtcs = AHashSet::new();
let mut vrr_crtcs = AHashSet::new();
let mut used_planes = AHashSet::new();
for connector in dev.connectors.lock().values() {
@ -2507,6 +2606,9 @@ impl MetalBackend {
return false;
}
used_crtcs.insert(crtc_id);
if dd.should_enable_vrr() {
vrr_crtcs.insert(crtc_id);
}
let crtc = dev.dev.crtcs.get(&crtc_id).unwrap();
connector.crtc.set(Some(crtc.clone()));
crtc.connector.set(Some(connector.clone()));
@ -2558,6 +2660,11 @@ impl MetalBackend {
c.change(crtc.active.id, 0);
}
c.change(crtc.out_fence_ptr, 0);
let vrr_requested = vrr_crtcs.contains(&crtc.id);
if crtc.vrr_enabled.value.get() != vrr_requested {
c.change(crtc.vrr_enabled.id, vrr_requested as _);
crtc.vrr_enabled.value.set(vrr_requested);
}
});
}
if let Err(e) = changes.commit(flags, 0) {
@ -2748,6 +2855,7 @@ impl MetalBackend {
changes.change_object(crtc.id, |c| {
c.change(crtc.active.id, 1);
c.change(crtc.mode_id.id, mode_blob.id().0 as _);
c.change(crtc.vrr_enabled.id, dd.should_enable_vrr() as _);
});
connector.crtc.set(Some(crtc.clone()));
dd.crtc_id.value.set(crtc.id);
@ -2755,6 +2863,7 @@ impl MetalBackend {
crtc.active.value.set(true);
crtc.mode_id.value.set(mode_blob.id());
crtc.mode_blob.set(Some(Rc::new(mode_blob)));
crtc.vrr_enabled.value.set(dd.should_enable_vrr() as _);
Ok(())
}
@ -2894,6 +3003,7 @@ impl MetalBackend {
}
connector.has_damage.set(true);
connector.cursor_changed.set(true);
connector.cursor_scheduled.set(true);
connector.schedule_present();
}
}

View file

@ -575,6 +575,7 @@ impl XBackend {
width_mm: output.width.get(),
height_mm: output.height.get(),
non_desktop: false,
vrr_capable: false,
}));
output.changed();
self.present(output).await;