Commit 8e6f96f0a32d
tarball4 files changed+102-9
@@ -19,6 +19,10 @@ border = 2
1919
focused = "#88bfd1"2020
unfocused = "#3b4252"2121
22
+[output]23
+# Variable refresh rate on outputs that support it, off, on, or fullscreen.24
+vrr = "fullscreen"25
+2226
[animation]2327
enabled = true2428
# orthogonal moves each window along one axis per phase, direct allows both.@@ -12,6 +12,7 @@ pub const DEFAULT: &str = include_str!("../default.toml");
1212
#[derive(Debug, Clone)]1313
pub struct Config {1414
pub general: General,15
+ pub output: Outputs,1516
pub decoration: Decoration,1617
pub animation: Animation,1718
pub keyboard: Keyboard,@@ -68,6 +69,22 @@ impl Default for Gaps {
6869
}6970
}7071
72
+#[derive(Deserialize, Debug, Clone, PartialEq, Default)]73
+#[serde(default, deny_unknown_fields)]74
+pub struct Outputs {75
+ pub vrr: Vrr,76
+}77
+78
+#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]79
+#[serde(rename_all = "lowercase")]80
+pub enum Vrr {81
+ Off,82
+ On,83
+ /// Only while a fullscreen window is shown, where a game sets the pace.84
+ #[default]85
+ Fullscreen,86
+}87
+7188
#[derive(Deserialize, Debug, Clone, PartialEq)]7289
#[serde(default, deny_unknown_fields)]7390
pub struct Decoration {@@ -256,6 +273,8 @@ struct Raw {
256273
#[serde(default)]257274
decoration: Decoration,258275
#[serde(default)]276
+ output: Outputs,277
+ #[serde(default)]259278
binds: BTreeMap<String, Words>,260279
#[serde(default)]261280
rules: Vec<RawRule>,@@ -335,6 +354,7 @@ impl Config {
335354
general: raw.general,336355
animation: raw.animation,337356
decoration: raw.decoration,357
+ output: raw.output,338358
keyboard: raw.keyboard,339359
binds,340360
rules,@@ -4,9 +4,11 @@
44
55
use crate::scene::Draw;66
use crate::state::{State, now_ns};7
+use monolith_config::Vrr;78
use monolith_layout::{OutputId, Rect};89
use smithay::backend::allocator::Fourcc;910
use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice};11
+use smithay::backend::drm::VrrSupport;1012
use smithay::backend::drm::compositor::FrameFlags;1113
use smithay::backend::drm::exporter::gbm::GbmFramebufferExporter;1214
use smithay::backend::drm::output::{DrmOutput, DrmOutputManager, DrmOutputRenderElements};@@ -37,6 +39,9 @@ use std::path::{Path, PathBuf};
3739
use std::time::Duration;3840
3941
const CLEAR: [f32; 4] = [0.07, 0.075, 0.09, 1.0];42
+/// Frames that only move the cursor stay at or below 60 Hz under VRR, so the43
+/// mouse does not override the refresh rate a game is pacing.44
+const CURSOR_FRAME_NS: u64 = 16_666_667;4045
4146
type Allocator = GbmAllocator<DrmDeviceFd>;4247
type Exporter = GbmFramebufferExporter<DrmDeviceFd>;@@ -72,6 +77,10 @@ struct Surface {
7277
/// Frames presented since `stats_since`, for the once-a-second debug line.7378
presented: u32,7479
stats_since: u64,80
+ vrr_capable: bool,81
+ vrr_on: bool,82
+ /// Only the cursor moved, which under VRR is drawn at a capped rate.83
+ cursor_redraw: bool,7584
}7685
7786
impl Surface {@@ -378,11 +387,22 @@ fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
378387
return;379388
}380389
};390
+ let vrr_capable = matches!(391
+ out.with_compositor(|c| c.vrr_supported(connector.handle())),392
+ Ok(VrrSupport::Supported)393
+ );381394
space.map_output(&output, position);382395
let (w, h) = mode.size();383396
let area = Rect::new(x, 0.0, f64::from(w), f64::from(h));384397
let id = layout.add_output(&name, area);385
- tracing::info!(name, w, h, refresh = mode.vrefresh(), "output connected");398
+ tracing::info!(399
+ name,400
+ w,401
+ h,402
+ refresh = mode.vrefresh(),403
+ vrr_capable,404
+ "output connected"405
+ );386406
drm.surfaces.insert(387407
crtc,388408
Surface {@@ -397,6 +417,9 @@ fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
397417
budget: Budget::new(),398418
presented: 0,399419
stats_since: 0,420
+ vrr_capable,421
+ vrr_on: false,422
+ cursor_redraw: false,400423
refresh_ns: 1_000_000_000_000 / u64::from(wl_mode.refresh.max(1) as u32),401424
},402425
);@@ -464,7 +487,7 @@ fn on_vblank(state: &mut State, crtc: crtc::Handle, meta: &mut Option<DrmEventMe
464487
surface.stats_since = t;465488
}466489
}467
- if surface.redraw {490
+ if surface.redraw || surface.cursor_redraw {468491
schedule(state, crtc);469492
}470493
}@@ -482,6 +505,25 @@ fn schedule(state: &mut State, crtc: crtc::Handle) {
482505
return;483506
}484507
let now = now_ns();508
+ if surface.vrr_on {509
+ let since_flip = surface510
+ .last_vblank_ns511
+ .map_or(u64::MAX, |l| now.saturating_sub(l));512
+ if surface.redraw || since_flip >= CURSOR_FRAME_NS {513
+ draw(state, crtc, now);514
+ return;515
+ }516
+ let wait = Timer::from_duration(Duration::from_nanos(CURSOR_FRAME_NS - since_flip));517
+ let token = drm.handle.insert_source(wait, move |_, _, state| {518
+ if let Some(s) = state.drm.as_mut().and_then(|d| d.surfaces.get_mut(&crtc)) {519
+ s.scheduled = None;520
+ }521
+ draw(state, crtc, now_ns());522
+ TimeoutAction::Drop523
+ });524
+ surface.scheduled = token.ok();525
+ return;526
+ }485527
let Some((vblank, start)) = surface.next_deadline(now) else {486528
draw(state, crtc, now);487529
return;@@ -513,15 +555,16 @@ pub fn render(state: &mut State) {
513555
let Some(drm) = state.drm.as_mut() else {514556
return;515557
};516
- if std::mem::take(&mut state.needs_redraw) {517
- for s in drm.surfaces.values_mut() {518
- s.redraw = true;519
- }558
+ let cursor = std::mem::take(&mut state.cursor_moved);559
+ let all = std::mem::take(&mut state.needs_redraw);560
+ for s in drm.surfaces.values_mut() {561
+ s.redraw |= all;562
+ s.cursor_redraw |= cursor;520563
}521564
let ready: Vec<crtc::Handle> = drm522565
.surfaces523566
.iter()524
- .filter(|(_, s)| s.redraw && !s.queued)567
+ .filter(|(_, s)| (s.redraw || s.cursor_redraw) && !s.queued)525568
.map(|(c, _)| *c)526569
.collect();527570
for crtc in ready {@@ -560,6 +603,29 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
560603
if surface.queued {561604
return false;562605
}606
+ surface.cursor_redraw = false;607
+ let want_vrr = surface.vrr_capable608
+ && match config.output.vrr {609
+ Vrr::Off => false,610
+ Vrr::On => true,611
+ Vrr::Fullscreen => layout.arrange(surface.id).fullscreen.is_some(),612
+ };613
+ if want_vrr != surface.vrr_on {614
+ match surface.drm.with_compositor(|c| c.use_vrr(want_vrr)) {615
+ Ok(()) => {616
+ surface.vrr_on = want_vrr;617
+ tracing::info!(618
+ output = surface.output.name(),619
+ vrr = want_vrr,620
+ "adaptive sync"621
+ );622
+ }623
+ Err(e) => {624
+ tracing::warn!("could not switch adaptive sync: {e}");625
+ surface.vrr_capable = false;626
+ }627
+ }628
+ }563629
let began = std::time::Instant::now();564630
let now = target;565631
let Some(area) = layout.output(surface.id).map(|o| o.area) else {@@ -650,7 +716,7 @@ fn on_input(state: &mut State, event: InputEvent<LibinputInputBackend>) {
650716
let pos = clamp(state, state.pointer_pos + event.delta());651717
state.pointer_to(pos, event.time());652718
state.pointer_frame();653
- state.needs_redraw = true;719
+ state.cursor_moved = true;654720
}655721
InputEvent::PointerMotionAbsolute { event } => {656722
let Some(area) = state.layout.outputs().next().map(|(_, o)| o.area) else {@@ -660,7 +726,7 @@ fn on_input(state: &mut State, event: InputEvent<LibinputInputBackend>) {
660726
let pos = Point::from((area.x + p.x, area.y + p.y));661727
state.pointer_to(pos, event.time());662728
state.pointer_frame();663
- state.needs_redraw = true;729
+ state.cursor_moved = true;664730
}665731
InputEvent::PointerButton { event } => {666732
let time = event.time();@@ -50,6 +50,8 @@ pub struct State {
5050
pub cursor: crate::cursor::Cursor,5151
pub cursor_status: CursorImageStatus,5252
pub needs_redraw: bool,53
+ /// The pointer moved, which on its own only redraws the cursor.54
+ pub cursor_moved: bool,5355
5456
pub layout: Layout,5557
pub windows: HashMap<WindowId, Window>,@@ -150,6 +152,7 @@ impl State {
150152
cursor: crate::cursor::Cursor::load(),151153
cursor_status: CursorImageStatus::default_named(),152154
needs_redraw: true,155
+ cursor_moved: false,153156
layout,154157
windows: HashMap::new(),155158
unmapped: Vec::new(),