Commit dbdff401f909
tarball7 files changed+1482-1411
@@ -1,1411 +0,0 @@
1
-//! Bare metal. A libseat session, udev for the primary GPU and its2
-//! connectors, libinput for input, and one smithay DRM output per connector,3
-//! each rendered when its own vblank lands.4
-5
-use crate::backend::{Backend, logical_area};6
-use crate::devices::Devices;7
-use crate::input::Motion;8
-use crate::scene::Draw;9
-use crate::state::Services;10
-use crate::state::{State, now_ns};11
-use crate::wm::Wm;12
-use monolith_config::{OutputTransform, Vrr};13
-use monolith_layout::{OutputId, Rect};14
-use smithay::backend::allocator::Fourcc;15
-use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice};16
-use smithay::backend::drm::VrrSupport;17
-use smithay::backend::drm::compositor::{FrameFlags, PrimaryPlaneElement};18
-use smithay::backend::drm::exporter::gbm::GbmFramebufferExporter;19
-use smithay::backend::drm::output::{DrmOutput, DrmOutputManager, DrmOutputRenderElements};20
-use smithay::backend::drm::{21
- DrmDevice, DrmDeviceFd, DrmEvent, DrmEventMetadata, DrmEventTime, DrmNode, NodeType,22
-};23
-use smithay::backend::input::{24
- AbsolutePositionEvent, Axis, AxisSource, Event, InputEvent, InputTime, KeyboardKeyEvent,25
- PointerAxisEvent, PointerButtonEvent, PointerMotionEvent,26
-};27
-use smithay::backend::libinput::{LibinputInputBackend, LibinputSessionInterface};28
-use smithay::backend::session::libseat::LibSeatSession;29
-use smithay::backend::session::{Event as SessionEvent, Session};30
-use smithay::backend::udev::{UdevBackend, UdevEvent, all_gpus, primary_gpu};31
-use smithay::input::pointer::AxisFrame;32
-use smithay::output::{Mode, Output, PhysicalProperties, Scale, Subpixel};33
-use smithay::reexports::calloop::timer::{TimeoutAction, Timer};34
-use smithay::reexports::calloop::{EventLoop, LoopHandle, RegistrationToken};35
-use smithay::reexports::drm::Device as _;36
-use smithay::reexports::drm::control::{self, ModeTypeFlags, connector, crtc};37
-use smithay::reexports::input::Libinput;38
-use smithay::reexports::input::event::switch::{Switch, SwitchState};39
-use smithay::reexports::rustix::fs::OFlags;40
-use smithay::reexports::wayland_server::backend::GlobalId;41
-use smithay::desktop::utils::OutputPresentationFeedback;42
-use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind;43
-use smithay::utils::{DeviceFd, Logical, Monotonic, Point, Time, Transform};44
-use smithay::wayland::presentation::Refresh;45
-use smithay::wayland::drm_syncobj::{DrmSyncobjState, supports_syncobj_eventfd};46
-use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner};47
-use std::collections::{HashMap, HashSet};48
-use std::path::{Path, PathBuf};49
-use std::time::Duration;50
-51
-use crate::frame::{CLEAR, FrameCtx};52
-/// Frames that only move the cursor stay at or below 60 Hz under VRR, so the53
-/// mouse does not override the refresh rate a game is pacing.54
-const CURSOR_FRAME_NS: u64 = 16_666_667;55
-56
-/// How long a disabled wl_output stays bound before it is removed, so57
-/// clients that were mid-bind are not killed by the removal.58
-const GLOBAL_REMOVAL_DELAY: Duration = Duration::from_secs(5);59
-60
-/// Frames render without vblanks while retrying or looping on empty frames,61
-/// and only a vblank drains the samples.62
-const MAX_CPU_SAMPLES: usize = 1024;63
-64
-type Allocator = GbmAllocator<DrmDeviceFd>;65
-type Exporter = GbmFramebufferExporter<DrmDeviceFd>;66
-type Manager = DrmOutputManager<Allocator, Exporter, OutputPresentationFeedback, DrmDeviceFd>;67
-type Out = DrmOutput<Allocator, Exporter, OutputPresentationFeedback, DrmDeviceFd>;68
-69
-pub struct Drm {70
- pub session: LibSeatSession,71
- libinput: Libinput,72
- handle: LoopHandle<'static, State>,73
- node: DrmNode,74
- pub render_node: DrmNode,75
- pub syncobj: Option<DrmSyncobjState>,76
- manager: Manager,77
- scanner: DrmScanner,78
- nvidia: bool,79
- frame_flags: FrameFlags,80
- surfaces: HashMap<crtc::Handle, Surface>,81
- /// Connected connectors the scanner has already reported but that are82
- /// not lit, retried on the next scan.83
- failed: HashSet<connector::Handle>,84
- pub devices: Devices,85
-}86
-87
-struct Surface {88
- output: Output,89
- global: GlobalId,90
- id: OutputId,91
- drm: Out,92
- /// A frame is on its way to the screen and its vblank has not landed.93
- queued: bool,94
- queued_at: u64,95
- /// The panel is lit. Off outputs are not rendered to.96
- powered: bool,97
- /// Out of the layout and Space, but still driven so it can relight.98
- parked: bool,99
- /// Fires when a queued frame's vblank never arrives.100
- watchdog: Option<RegistrationToken>,101
- /// Consecutive frames that failed to render or queue.102
- failures: u32,103
- redraw: bool,104
- last_vblank_ns: Option<u64>,105
- refresh_ns: u64,106
- /// The pending render, timed to start just in time for `target_ns`.107
- scheduled: Option<RegistrationToken>,108
- /// `scheduled` only waits out a cap or a retry, so fresh content may109
- /// cancel it and draw at once.110
- preemptible: bool,111
- /// The vblank the frame being drawn or queued is meant for.112
- target_ns: Option<u64>,113
- budget: crate::pacing::Budget,114
- /// Frames presented since `stats_since`, for the once-a-second debug line.115
- presented: u32,116
- stats_since: u64,117
- vrr_capable: bool,118
- vrr_on: bool,119
- /// Only the cursor moved, which under VRR is drawn at a capped rate.120
- cursor_redraw: bool,121
- /// The last frame went straight to the primary plane without compositing.122
- scanout: bool,123
- /// Frames scanned out and per-frame compositor CPU times since124
- /// `stats_since`, for the targets in decisions.md.125
- scanned_out: u32,126
- frame_cpu_ns: Vec<u64>,127
-}128
-129
-impl Surface {130
- /// The first vblank a render starting at `now` can still make, and the131
- /// latest moment to start it.132
- fn next_deadline(&self, now: u64) -> Option<(u64, u64)> {133
- crate::pacing::deadline(self.last_vblank_ns?, self.refresh_ns, self.budget.ns(), now)134
- }135
-136
- /// How long a queued frame may go without its vblank. Under VRR a flip137
- /// waits for the panel's longest frame, so the floor stays well above it.138
- fn flip_timeout(&self) -> u64 {139
- (self.refresh_ns * 4).max(100_000_000)140
- }141
-}142
-143
-/// The GPU monolith drives on bare metal, the seat's primary or else its144
-/// first.145
-pub fn primary() -> Result<PathBuf, Box<dyn std::error::Error>> {146
- let seat = std::env::var("XDG_SEAT").unwrap_or_else(|_| "seat0".into());147
- Ok(match primary_gpu(&seat)? {148
- Some(p) => p,149
- None => all_gpus(&seat)?.into_iter().next().ok_or("no GPU found")?,150
- })151
-}152
-153
-pub fn init(154
- event_loop: &mut EventLoop<'static, State>,155
- state: &mut State,156
- path: &Path,157
-) -> Result<(), Box<dyn std::error::Error>> {158
- let (mut session, notifier) = LibSeatSession::new()?;159
- let seat = session.seat();160
- let node = DrmNode::from_path(path)?;161
- tracing::info!(path = %path.display(), "primary GPU");162
- let fd = session.open(163
- path,164
- OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK,165
- )?;166
- let fd = DrmDeviceFd::new(DeviceFd::from(fd));167
- let (device, drm_events) = DrmDevice::new(fd.clone(), true)?;168
- let nvidia = device169
- .get_driver()170
- .map(|d| d.name().to_string_lossy().to_lowercase().contains("nvidia"))171
- .unwrap_or(false);172
- let syncobj = if supports_syncobj_eventfd(&fd) {173
- Some(DrmSyncobjState::new::<State>(174
- &state.core.display,175
- fd.clone(),176
- ))177
- } else {178
- tracing::info!("no syncobj eventfd support, clients stay on implicit sync");179
- None180
- };181
- let gbm = GbmDevice::new(fd)?;182
- let render_node = node183
- .node_with_type(NodeType::Render)184
- .and_then(Result::ok)185
- .unwrap_or(node);186
- let allocator = GbmAllocator::new(187
- gbm.clone(),188
- GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,189
- );190
- let exporter = GbmFramebufferExporter::new(gbm.clone(), render_node.into());191
- let render_formats: Vec<_> = state.renderer.render_formats().iter().copied().collect();192
- let manager = DrmOutputManager::new(193
- device,194
- allocator,195
- exporter,196
- Some(gbm),197
- [Fourcc::Xrgb8888, Fourcc::Argb8888],198
- render_formats,199
- );200
-201
- let mut libinput =202
- Libinput::new_with_udev::<LibinputSessionInterface<LibSeatSession>>(session.clone().into());203
- libinput204
- .udev_assign_seat(&seat)205
- .map_err(|()| "libinput could not take the seat")?;206
- let handle = event_loop.handle();207
- handle.insert_source(208
- LibinputInputBackend::new(libinput.clone()),209
- |event, _, state| {210
- on_input(state, event);211
- },212
- )?;213
- handle.insert_source(notifier, |event, _, state| match event {214
- SessionEvent::PauseSession => {215
- if let Some(drm) = state.backend.drm_mut() {216
- drm.libinput.suspend();217
- drm.manager.pause();218
- }219
- }220
- SessionEvent::ActivateSession => {221
- if let Some(drm) = state.backend.drm_mut() {222
- if drm.libinput.resume().is_err() {223
- tracing::warn!("libinput did not resume");224
- }225
- if let Err(e) = drm.manager.lock().activate(false) {226
- tracing::warn!("could not reactivate the GPU: {e}");227
- }228
- for s in drm.surfaces.values_mut() {229
- if let Some(token) = s.scheduled.take() {230
- drm.handle.remove(token);231
- }232
- if let Err(e) = s.drm.frame_submitted() {233
- tracing::warn!("could not drop the flip left over from the pause: {e}");234
- }235
- s.queued = false;236
- s.last_vblank_ns = None;237
- s.target_ns = None;238
- s.failures = 0;239
- s.redraw = true;240
- if !s.powered241
- && let Err(e) = s.drm.with_compositor(|c| c.clear())242
- {243
- tracing::warn!("could not keep the output off: {e}");244
- }245
- }246
- }247
- scan(state);248
- state.dirty.redraw = true;249
- }250
- })?;251
- handle.insert_source(drm_events, |event, meta, state| match event {252
- DrmEvent::VBlank(crtc) => on_vblank(state, crtc, meta),253
- DrmEvent::Error(e) => tracing::warn!("drm: {e}"),254
- })?;255
- let udev = UdevBackend::new(&seat)?;256
- handle.insert_source(udev, move |event, _, state| {257
- if let UdevEvent::Changed { device_id } = event258
- && state259
- .backend260
- .drm()261
- .is_some_and(|d| d.node.dev_id() == device_id && d.session.is_active())262
- {263
- scan(state);264
- }265
- })?;266
-267
- state.backend = Backend::Drm(Box::new(Drm {268
- session,269
- libinput,270
- handle,271
- node,272
- render_node,273
- syncobj,274
- manager,275
- scanner: DrmScanner::new(),276
- nvidia,277
- frame_flags: if std::env::var_os("MONOLITH_NO_SCANOUT").is_some() {278
- FrameFlags::empty()279
- } else {280
- FrameFlags::DEFAULT281
- },282
- surfaces: HashMap::new(),283
- failed: HashSet::new(),284
- devices: Devices::default(),285
- }));286
- scan(state);287
- Ok(())288
-}289
-290
-/// Brings outputs in line with what is plugged in.291
-fn scan(state: &mut State) {292
- let Some(drm) = state.backend.drm_mut() else {293
- return;294
- };295
- let events: Vec<DrmScanEvent> = match drm.scanner.scan_connectors(drm.manager.device()) {296
- Ok(result) => result.into_iter().collect(),297
- Err(e) => {298
- tracing::warn!("scanning connectors failed: {e}");299
- return;300
- }301
- };302
- let retry = std::mem::take(&mut drm.failed);303
- for event in events {304
- match event {305
- DrmScanEvent::Connected {306
- connector,307
- crtc: Some(crtc),308
- } => connect(state, connector, crtc),309
- DrmScanEvent::Connected {310
- connector,311
- crtc: None,312
- } => {313
- tracing::warn!(handle = ?connector.handle(), "no free crtc for the connector");314
- if let Some(drm) = state.backend.drm_mut() {315
- drm.failed.insert(connector.handle());316
- }317
- }318
- DrmScanEvent::Disconnected {319
- crtc: Some(crtc), ..320
- } => disconnect(state, crtc),321
- DrmScanEvent::Changed {322
- connector,323
- crtc: Some(crtc),324
- } => changed(state, connector, crtc),325
- _ => {}326
- }327
- }328
- for handle in retry {329
- retry_connector(state, handle);330
- }331
- state.apply_power();332
- state.lid_act();333
- state.apply_layout();334
-}335
-336
-/// Lights a connector that failed on an earlier scan, which the scanner337
-/// will not report again until it changes.338
-fn retry_connector(state: &mut State, handle: connector::Handle) {339
- let Some(drm) = state.backend.drm_mut() else {340
- return;341
- };342
- if drm.failed.contains(&handle) {343
- return;344
- }345
- let Some(info) = drm.scanner.connectors().get(&handle) else {346
- return;347
- };348
- if info.state() != connector::State::Connected {349
- return;350
- }351
- let info = info.clone();352
- let Some(crtc) = drm.scanner.crtc_for_connector(&handle) else {353
- drm.failed.insert(handle);354
- return;355
- };356
- if !drm.surfaces.contains_key(&crtc) {357
- connect(state, info, crtc);358
- }359
-}360
-361
-fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {362
- let name = connector_name(&connector);363
- let config = state364
- .settings365
- .config366
- .output367
- .named368
- .get(&name)369
- .cloned()370
- .unwrap_or_default();371
- let Some(mode) = choose_mode(&connector, &name, config.mode.as_deref()) else {372
- tracing::warn!(name, "connector has no modes");373
- if let Some(drm) = state.backend.drm_mut() {374
- drm.failed.insert(connector.handle());375
- }376
- return;377
- };378
- let transform = transform(config.transform);379
- let State {380
- backend: Backend::Drm(drm),381
- renderer,382
- wm: Wm { layout, space, .. },383
- core,384
- ..385
- } = state386
- else {387
- return;388
- };389
- let (mm_w, mm_h) = connector.size().unwrap_or((0, 0));390
- let output = Output::new(391
- name.clone(),392
- PhysicalProperties {393
- size: (mm_w as i32, mm_h as i32).into(),394
- subpixel: Subpixel::Unknown,395
- make: "Unknown".into(),396
- model: name.clone(),397
- serial_number: String::new(),398
- },399
- );400
- let wl_mode = Mode::from(mode);401
- let right = layout402
- .outputs()403
- .map(|(_, o)| o.area.right())404
- .fold(0.0, f64::max);405
- let position = match config.position {406
- Some([x, y]) => Point::from((x, y)),407
- None => Point::from((right.ceil() as i32, 0)),408
- };409
- let scale = config.scale();410
- output.change_current_state(411
- Some(wl_mode),412
- Some(transform),413
- Some(Scale::Fractional(scale)),414
- Some(position),415
- );416
- output.set_preferred(wl_mode);417
- let mut planes = drm.manager.device().planes(&crtc).ok();418
- if drm.nvidia419
- && let Some(p) = planes.as_mut()420
- {421
- // NVIDIA's overlay planes break composition, as anvil also found.422
- p.overlay.clear();423
- }424
- let out = match drm.manager.lock().initialize_output::<_, Draw>(425
- crtc,426
- mode,427
- &[connector.handle()],428
- &output,429
- planes,430
- renderer,431
- &DrmOutputRenderElements::default(),432
- ) {433
- Ok(out) => out,434
- Err(e) => {435
- tracing::warn!(name, "could not light up the output: {e}");436
- drm.failed.insert(connector.handle());437
- return;438
- }439
- };440
- let vrr_capable = matches!(441
- out.with_compositor(|c| c.vrr_supported(connector.handle())),442
- Ok(VrrSupport::Supported)443
- );444
- let global = output.create_global::<State>(&core.display);445
- space.map_output(&output, position);446
- let (w, h) = mode.size();447
- let area = logical_area(position, wl_mode.size, transform, scale);448
- let id = layout.add_output(&name, area);449
- tracing::info!(450
- name,451
- w,452
- h,453
- scale,454
- refresh = mode.vrefresh(),455
- ?transform,456
- x = position.x,457
- y = position.y,458
- vrr_capable,459
- "output connected"460
- );461
- drm.surfaces.insert(462
- crtc,463
- Surface {464
- output,465
- global,466
- id,467
- drm: out,468
- queued: false,469
- queued_at: 0,470
- powered: true,471
- parked: false,472
- watchdog: None,473
- failures: 0,474
- redraw: true,475
- last_vblank_ns: None,476
- scheduled: None,477
- preemptible: false,478
- target_ns: None,479
- budget: crate::pacing::Budget::new(),480
- presented: 0,481
- stats_since: 0,482
- vrr_capable,483
- vrr_on: false,484
- cursor_redraw: false,485
- scanout: false,486
- scanned_out: 0,487
- frame_cpu_ns: Vec::new(),488
- refresh_ns: 1_000_000_000_000 / u64::from(wl_mode.refresh.max(1) as u32),489
- },490
- );491
-}492
-493
-fn connector_name(connector: &connector::Info) -> String {494
- format!(495
- "{}-{}",496
- connector.interface().as_str(),497
- connector.interface_id()498
- )499
-}500
-501
-/// The configured mode if the connector has it, else its preferred one.502
-fn choose_mode(503
- connector: &connector::Info,504
- name: &str,505
- configured: Option<&str>,506
-) -> Option<control::Mode> {507
- let chosen = configured.and_then(|spec| {508
- let found = pick_mode(connector.modes(), spec);509
- if found.is_none() {510
- tracing::warn!(name, spec, "no such mode, using the preferred one");511
- }512
- found513
- });514
- chosen.or_else(|| {515
- connector516
- .modes()517
- .iter()518
- .find(|m| m.mode_type().contains(ModeTypeFlags::PREFERRED))519
- .or_else(|| connector.modes().first())520
- .copied()521
- })522
-}523
-524
-fn changed(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {525
- if !remode(state, &connector, crtc) {526
- disconnect(state, crtc);527
- connect(state, connector, crtc);528
- }529
-}530
-531
-/// Moves a lit output to the mode its connector now calls for without532
-/// recreating the wl_output, so clients, windows, the lock surface and the533
-/// power state stay as they are. False means the output has to be rebuilt.534
-fn remode(state: &mut State, connector: &connector::Info, crtc: crtc::Handle) -> bool {535
- let name = connector_name(connector);536
- let State {537
- backend: Backend::Drm(drm),538
- renderer,539
- wm: Wm { layout, .. },540
- settings,541
- ..542
- } = state543
- else {544
- return false;545
- };546
- let Some(s) = drm.surfaces.get_mut(&crtc) else {547
- return false;548
- };549
- let (mm_w, mm_h) = connector.size().unwrap_or((0, 0));550
- if s.output.physical_properties().size != (mm_w as i32, mm_h as i32).into() {551
- return false;552
- }553
- let configured = settings.config.output.named.get(&name);554
- let Some(mode) = choose_mode(connector, &name, configured.and_then(|c| c.mode.as_deref()))555
- else {556
- return false;557
- };558
- let wl_mode = Mode::from(mode);559
- let moved = s.output.current_mode() != Some(wl_mode);560
- if moved {561
- let elements = DrmOutputRenderElements::default();562
- if let Err(e) = s.drm.use_mode::<_, Draw>(mode, renderer, &elements) {563
- tracing::warn!(name, "could not change the mode in place: {e}");564
- return false;565
- }566
- s.output567
- .change_current_state(Some(wl_mode), None, None, None);568
- s.refresh_ns = 1_000_000_000_000 / u64::from(wl_mode.refresh.max(1) as u32);569
- s.last_vblank_ns = None;570
- s.target_ns = None;571
- s.redraw = true;572
- let area = logical_area(573
- s.output.current_location(),574
- wl_mode.size,575
- s.output.current_transform(),576
- s.output.current_scale().fractional_scale(),577
- );578
- layout.set_output_area(s.id, area);579
- tracing::info!(580
- name,581
- w = wl_mode.size.w,582
- h = wl_mode.size.h,583
- refresh = mode.vrefresh(),584
- "output mode changed"585
- );586
- }587
- s.output.set_preferred(wl_mode);588
- if moved {589
- reconfigure_outputs(state);590
- state.reserve_layers();591
- state.reconfigure_lock();592
- }593
- true594
-}595
-596
-/// The mode matching `WIDTHxHEIGHT` or `WIDTHxHEIGHT@HZ`, the highest rate597
-/// when none is given and the nearest one when it is.598
-fn pick_mode(modes: &[control::Mode], spec: &str) -> Option<control::Mode> {599
- let (size, rate) = match spec.split_once('@') {600
- Some((size, rate)) => (size, Some(rate.trim().parse::<f64>().ok()?)),601
- None => (spec, None),602
- };603
- let (w, h) = size.trim().split_once('x')?;604
- let size = (w.trim().parse::<u16>().ok()?, h.trim().parse::<u16>().ok()?);605
- let fitting = modes.iter().filter(|m| m.size() == size);606
- match rate {607
- Some(r) => fitting608
- .min_by(|a, b| {609
- let off = |m: &&control::Mode| (f64::from(m.vrefresh()) - r).abs();610
- off(a).total_cmp(&off(b))611
- })612
- .copied(),613
- None => fitting.max_by_key(|m| m.vrefresh()).copied(),614
- }615
-}616
-617
-fn transform(t: OutputTransform) -> Transform {618
- match t {619
- OutputTransform::Normal => Transform::Normal,620
- OutputTransform::Rotate90 => Transform::_90,621
- OutputTransform::Rotate180 => Transform::_180,622
- OutputTransform::Rotate270 => Transform::_270,623
- OutputTransform::Flipped => Transform::Flipped,624
- OutputTransform::Flipped90 => Transform::Flipped90,625
- OutputTransform::Flipped180 => Transform::Flipped180,626
- OutputTransform::Flipped270 => Transform::Flipped270,627
- }628
-}629
-630
-/// Outputs without a configured position are laid out left to right in631
-/// their current order, so one rotating pushes its neighbours along.632
-pub fn reconfigure_outputs(state: &mut State) {633
- let State {634
- backend: Backend::Drm(drm),635
- wm: Wm { layout, space, .. },636
- settings,637
- ..638
- } = state639
- else {640
- return;641
- };642
- let mut surfaces: Vec<&mut Surface> = drm.surfaces.values_mut().filter(|s| !s.parked).collect();643
- surfaces.sort_by_cached_key(|s| {644
- let name = s.output.name();645
- (646
- s.output.current_location().x,647
- !crate::power::internal(&name),648
- )649
- });650
- let mut right = 0;651
- let mut changed = false;652
- for s in surfaces {653
- let config = settings654
- .config655
- .output656
- .named657
- .get(&s.output.name())658
- .cloned()659
- .unwrap_or_default();660
- let Some(mode) = s.output.current_mode() else {661
- continue;662
- };663
- let transform = transform(config.transform);664
- let scale = config.scale();665
- let position: Point<i32, Logical> = match config.position {666
- Some([x, y]) => (x, y).into(),667
- None => (right, 0).into(),668
- };669
- let area = logical_area(position, mode.size, transform, scale);670
- right = right.max(area.right().ceil() as i32);671
- if s.output.current_transform() == transform672
- && s.output.current_location() == position673
- && s.output.current_scale().fractional_scale() == scale674
- {675
- continue;676
- }677
- s.output.change_current_state(678
- None,679
- Some(transform),680
- Some(Scale::Fractional(scale)),681
- Some(position),682
- );683
- space.map_output(&s.output, position);684
- layout.set_output_area(s.id, area);685
- s.redraw = true;686
- changed = true;687
- tracing::info!(688
- name = s.output.name(),689
- ?transform,690
- scale,691
- x = position.x,692
- y = position.y,693
- "output reconfigured"694
- );695
- }696
- if changed {697
- state.reserve_layers();698
- state.reconfigure_lock();699
- state.apply_layout();700
- }701
-}702
-703
-fn disconnect(state: &mut State, crtc: crtc::Handle) {704
- let Some(drm) = state.backend.drm_mut() else {705
- return;706
- };707
- let Some(surface) = drm.surfaces.remove(&crtc) else {708
- return;709
- };710
- for token in [surface.scheduled, surface.watchdog].into_iter().flatten() {711
- drm.handle.remove(token);712
- }713
- tracing::info!(name = surface.output.name(), "output disconnected");714
- state715
- .core716
- .display717
- .disable_global::<State>(surface.global.clone());718
- let global = surface.global.clone();719
- let removal = Timer::from_duration(GLOBAL_REMOVAL_DELAY);720
- let armed = drm.handle.insert_source(removal, move |_, _, state| {721
- state.core.display.remove_global::<State>(global.clone());722
- TimeoutAction::Drop723
- });724
- if let Err(e) = armed {725
- tracing::warn!("could not defer removing the wl_output: {e}");726
- state727
- .core728
- .display729
- .remove_global::<State>(surface.global.clone());730
- }731
- state.wm.space.unmap_output(&surface.output);732
- state.wm.layout.remove_output(surface.id);733
- state.close_layers(&surface.output);734
- state.services.lock.forget(&surface.output);735
- state.services.power.forget(&surface.output.name());736
- state.protocols.output_power.output_removed(&surface.output);737
-}738
-739
-/// Takes the output called `name` out of the layout and Space, or puts it740
-/// back. Its windows move to the other outputs and return when it does.741
-pub fn set_parked(state: &mut State, name: &str, parked: bool) {742
- let State {743
- backend: Backend::Drm(drm),744
- wm: Wm { layout, space, .. },745
- ..746
- } = state747
- else {748
- return;749
- };750
- let Some(s) = drm.surfaces.values_mut().find(|s| s.output.name() == name) else {751
- return;752
- };753
- if s.parked == parked {754
- return;755
- }756
- s.parked = parked;757
- tracing::info!(name, parked, "output parked");758
- if parked {759
- space.unmap_output(&s.output);760
- layout.remove_output(s.id);761
- } else if let Some(mode) = s.output.current_mode() {762
- let position = s.output.current_location();763
- space.map_output(&s.output, position);764
- let scale = s.output.current_scale().fractional_scale();765
- let area = logical_area(position, mode.size, s.output.current_transform(), scale);766
- s.id = layout.add_output(name, area);767
- s.redraw = true;768
- }769
- let pos = state.input.pointer_pos;770
- let held = clamp(state, pos);771
- if held != pos {772
- state.warp_pointer(held, InputTime::now());773
- }774
- if !parked {775
- reconfigure_outputs(state);776
- }777
- state.reserve_layers();778
- state.reconfigure_lock();779
- state.apply_layout();780
-}781
-782
-impl Drm {783
- pub fn names(&self) -> Vec<String> {784
- self.surfaces.values().map(|s| s.output.name()).collect()785
- }786
-787
- /// Lights or darkens the output called `name`. Dark outputs get DPMS off788
- /// and no frames, and the next frame after turning on is a full modeset.789
- pub fn set_powered(&mut self, name: &str, on: bool) {790
- let active = self.session.is_active();791
- for s in self792
- .surfaces793
- .values_mut()794
- .filter(|s| s.output.name() == name)795
- {796
- if s.powered == on {797
- continue;798
- }799
- s.powered = on;800
- tracing::info!(name, on, "output power");801
- if on {802
- s.redraw = true;803
- s.last_vblank_ns = None;804
- continue;805
- }806
- for token in [s.scheduled.take(), s.watchdog.take()]807
- .into_iter()808
- .flatten()809
- {810
- self.handle.remove(token);811
- }812
- s.queued = false;813
- s.target_ns = None;814
- s.cursor_redraw = false;815
- if active && let Err(e) = s.drm.with_compositor(|c| c.clear()) {816
- tracing::warn!(name, "could not switch the output off: {e}");817
- }818
- }819
- }820
-821
- pub fn output(&self, id: OutputId) -> Option<Output> {822
- self.surfaces823
- .values()824
- .find(|s| s.id == id)825
- .map(|s| s.output.clone())826
- }827
-828
- pub fn timeline(&self, output: &Output) -> Option<crate::timing::Timeline> {829
- let s = self.surfaces.values().find(|s| s.output == *output)?;830
- Some(crate::timing::Timeline {831
- last: s.last_vblank_ns?,832
- refresh: if s.vrr_on { 0 } else { s.refresh_ns },833
- budget: s.budget.ns(),834
- })835
- }836
-}837
-838
-fn on_vblank(state: &mut State, crtc: crtc::Handle, meta: &mut Option<DrmEventMetadata>) {839
- let presented = state840
- .backend841
- .drm()842
- .and_then(|d| d.surfaces.get(&crtc))843
- .map(|s| s.output.clone());844
- if let Some(output) = presented {845
- crate::fifo::presented(state, &output);846
- }847
- let Some(surface) = state848
- .backend849
- .drm_mut()850
- .and_then(|d| d.surfaces.get_mut(&crtc))851
- else {852
- return;853
- };854
- let feedback = surface.drm.frame_submitted().unwrap_or_else(|e| {855
- tracing::warn!("frame was not submitted: {e}");856
- None857
- });858
- surface.queued = false;859
- if let Err(e) = state.renderer.absorb_submit_stall() {860
- tracing::warn!("absorbing a submit stall failed: {e}");861
- }862
- let Some(surface) = state863
- .backend864
- .drm_mut()865
- .and_then(|d| d.surfaces.get_mut(&crtc))866
- else {867
- return;868
- };869
- if let Some(mut feedback) = feedback {870
- let (time, clock) = match meta.as_ref().map(|m| m.time) {871
- Some(DrmEventTime::Monotonic(t)) => (t, Kind::HwClock | Kind::HwCompletion),872
- _ => (Duration::from_nanos(now_ns()), Kind::empty()),873
- };874
- let refresh = Duration::from_nanos(surface.refresh_ns);875
- let refresh = if surface.vrr_on {876
- Refresh::variable(refresh)877
- } else {878
- Refresh::fixed(refresh)879
- };880
- let sequence = meta.as_ref().map_or(0, |m| u64::from(m.sequence));881
- feedback.presented(882
- Time::<Monotonic>::from(time),883
- refresh,884
- sequence,885
- Kind::Vsync | clock,886
- );887
- }888
- if let Some(DrmEventTime::Monotonic(t)) = meta.as_ref().map(|m| m.time) {889
- let t = t.as_nanos() as u64;890
- if let Some(target) = surface.target_ns.take() {891
- if t > target + surface.refresh_ns / 2 {892
- surface.budget.missed(surface.refresh_ns);893
- tracing::debug!(894
- output = surface.output.name(),895
- late_ns = t - target,896
- budget_ns = surface.budget.ns(),897
- "frame missed its vblank"898
- );899
- } else {900
- surface.budget.landed();901
- }902
- }903
- surface.last_vblank_ns = Some(t);904
- surface.presented += 1;905
- let window = t.saturating_sub(surface.stats_since);906
- if window >= 1_000_000_000 {907
- // Under VRR the panel waits for each flip, so the nominal refresh908
- // says nothing about how many vblanks there were.909
- let vblanks = (!surface.vrr_on).then(|| window / surface.refresh_ns.max(1));910
- let cpu = &mut surface.frame_cpu_ns;911
- cpu.sort_unstable();912
- let p99 = cpu.get(cpu.len().saturating_sub(1) * 99 / 100).copied();913
- tracing::debug!(914
- output = surface.output.name(),915
- presented = surface.presented,916
- vblanks,917
- vrr = surface.vrr_on,918
- scanned_out = surface.scanned_out,919
- cpu_p99_us = p99.map(|ns| ns / 1000),920
- budget_us = surface.budget.ns() / 1000,921
- "frame rate"922
- );923
- cpu.clear();924
- surface.scanned_out = 0;925
- surface.presented = 0;926
- surface.stats_since = t;927
- }928
- }929
- if surface.redraw || surface.cursor_redraw {930
- schedule(state, crtc);931
- }932
-}933
-934
-/// Starts a render at the last moment that still makes the next reachable935
-/// vblank, so commits arriving until then make it into the frame.936
-fn schedule(state: &mut State, crtc: crtc::Handle) {937
- let Some(drm) = state.backend.drm_mut() else {938
- return;939
- };940
- if !drm.session.is_active() {941
- return;942
- }943
- let Some(surface) = drm.surfaces.get_mut(&crtc) else {944
- return;945
- };946
- if !surface.powered || surface.queued || surface.scheduled.is_some() {947
- return;948
- }949
- let now = now_ns();950
- if surface.vrr_on {951
- let since_flip = surface952
- .last_vblank_ns953
- .map_or(u64::MAX, |l| now.saturating_sub(l));954
- if surface.redraw || since_flip >= CURSOR_FRAME_NS {955
- draw(state, crtc, now);956
- return;957
- }958
- let wait = Duration::from_nanos(CURSOR_FRAME_NS - since_flip);959
- arm(&drm.handle, surface, crtc, wait, true, move |state| {960
- draw(state, crtc, now_ns());961
- });962
- return;963
- }964
- let Some((vblank, start)) = surface.next_deadline(now) else {965
- draw(state, crtc, now);966
- return;967
- };968
- surface.target_ns = Some(vblank);969
- let wait = Duration::from_nanos(start.saturating_sub(now));970
- arm(&drm.handle, surface, crtc, wait, false, move |state| {971
- let target = state972
- .backend973
- .drm()974
- .and_then(|d| d.surfaces.get(&crtc)?.target_ns);975
- if let Some(target) = target {976
- draw(state, crtc, target);977
- }978
- });979
-}980
-981
-/// Runs `fire` once after `after`, tracked in `surface.scheduled` until then.982
-fn arm(983
- handle: &LoopHandle<'static, State>,984
- surface: &mut Surface,985
- crtc: crtc::Handle,986
- after: Duration,987
- preemptible: bool,988
- mut fire: impl FnMut(&mut State) + 'static,989
-) {990
- let token = handle.insert_source(Timer::from_duration(after), move |_, _, state| {991
- if let Some(s) = state992
- .backend993
- .drm_mut()994
- .and_then(|d| d.surfaces.get_mut(&crtc))995
- {996
- s.scheduled = None;997
- }998
- fire(state);999
- TimeoutAction::Drop1000
- });1001
- match token {1002
- Ok(token) => {1003
- surface.scheduled = Some(token);1004
- surface.preemptible = preemptible;1005
- }1006
- Err(e) => tracing::warn!(1007
- output = surface.output.name(),1008
- "could not arm a render timer: {e}"1009
- ),1010
- }1011
-}1012
-1013
-/// Arms the flip watchdog unless it already runs.1014
-fn watch(handle: &LoopHandle<'static, State>, surface: &mut Surface, crtc: crtc::Handle) {1015
- if surface.watchdog.is_some() {1016
- return;1017
- }1018
- let after = Duration::from_nanos(surface.flip_timeout());1019
- let token = handle.insert_source(Timer::from_duration(after), move |_, _, state| {1020
- flip_watchdog(state, crtc)1021
- });1022
- match token {1023
- Ok(token) => surface.watchdog = Some(token),1024
- Err(e) => tracing::warn!(1025
- output = surface.output.name(),1026
- "could not arm the flip watchdog: {e}"1027
- ),1028
- }1029
-}1030
-1031
-/// Recovers an output whose page flip event never came, as after a1032
-/// `DrmEvent::Error`, so `queued` cannot hold it dark forever.1033
-fn flip_watchdog(state: &mut State, crtc: crtc::Handle) -> TimeoutAction {1034
- let Some(drm) = state.backend.drm_mut() else {1035
- return TimeoutAction::Drop;1036
- };1037
- let active = drm.session.is_active();1038
- let Some(surface) = drm.surfaces.get_mut(&crtc) else {1039
- return TimeoutAction::Drop;1040
- };1041
- let age = now_ns().saturating_sub(surface.queued_at);1042
- let limit = surface.flip_timeout();1043
- if active && surface.queued && age < limit {1044
- return TimeoutAction::ToDuration(Duration::from_nanos(limit - age));1045
- }1046
- surface.watchdog = None;1047
- if !active || !surface.queued {1048
- return TimeoutAction::Drop;1049
- }1050
- tracing::warn!(output = surface.output.name(), "page flip never landed");1051
- if let Err(e) = surface.drm.frame_submitted() {1052
- tracing::warn!("frame was not submitted: {e}");1053
- }1054
- surface.queued = false;1055
- surface.target_ns = None;1056
- surface.redraw = true;1057
- schedule(state, crtc);1058
- TimeoutAction::Drop1059
-}1060
-1061
-/// An animation whose frame had nothing to show, like one hidden behind a1062
-/// fullscreen window, has no flip to wait for. Under VRR scheduling again1063
-/// would draw at once and recurse until the stack overflowed, so the next1064
-/// try waits a refresh.1065
-fn draw(state: &mut State, crtc: crtc::Handle, target: u64) {1066
- if !render_surface(state, crtc, target) {1067
- return;1068
- }1069
- let Some(drm) = state.backend.drm_mut() else {1070
- return;1071
- };1072
- let Some(surface) = drm.surfaces.get_mut(&crtc) else {1073
- return;1074
- };1075
- if surface.scheduled.is_some() {1076
- return;1077
- }1078
- let backoff = surface.failures.saturating_sub(1).min(6);1079
- let wait = (surface.refresh_ns.max(1_000_000) << backoff).min(1_000_000_000);1080
- let wait = Duration::from_nanos(wait);1081
- let preemptible = surface.failures == 0;1082
- arm(1083
- &drm.handle,1084
- surface,1085
- crtc,1086
- wait,1087
- preemptible,1088
- move |state| {1089
- schedule(state, crtc);1090
- },1091
- );1092
-}1093
-1094
-/// Renders every output that has something new and no frame in flight.1095
-pub fn render(state: &mut State) {1096
- let Some(drm) = state.backend.drm_mut() else {1097
- return;1098
- };1099
- let cursor = std::mem::take(&mut state.dirty.cursor);1100
- let all = std::mem::take(&mut state.dirty.redraw);1101
- for s in drm.surfaces.values_mut() {1102
- s.redraw |= all;1103
- s.cursor_redraw |= cursor;1104
- if all1105
- && s.preemptible1106
- && let Some(token) = s.scheduled.take()1107
- {1108
- drm.handle.remove(token);1109
- }1110
- }1111
- let ready: Vec<crtc::Handle> = drm1112
- .surfaces1113
- .iter()1114
- .filter(|(_, s)| (s.redraw || s.cursor_redraw) && !s.queued)1115
- .map(|(c, _)| *c)1116
- .collect();1117
- for crtc in ready {1118
- schedule(state, crtc);1119
- }1120
-}1121
-1122
-/// Draws one output for the vblank at `target`. Returns whether it still1123
-/// animates without a frame in flight, and so needs another render.1124
-#[profiling::function]1125
-fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {1126
- let State {1127
- backend: Backend::Drm(drm),1128
- renderer,1129
- wm,1130
- settings,1131
- core,1132
- input,1133
- services:1134
- Services {1135
- lock,1136
- captures,1137
- xwayland,1138
- ..1139
- },1140
- ..1141
- } = state1142
- else {1143
- return false;1144
- };1145
- if !drm.session.is_active() {1146
- return false;1147
- }1148
- let Some(surface) = drm.surfaces.get_mut(&crtc) else {1149
- return false;1150
- };1151
- if surface.queued || !surface.powered {1152
- return false;1153
- }1154
- surface.cursor_redraw = false;1155
- let shown = crate::frame::shown_windows(wm, surface.id, &surface.output, target);1156
- let fullscreen = shown.fullscreen;1157
- let forced = fullscreen.and_then(|w| wm.get(w)?.rule.vrr);1158
- let want_vrr = surface.vrr_capable1159
- && forced.unwrap_or(match settings.config.output.vrr {1160
- Vrr::Off => false,1161
- Vrr::On => true,1162
- Vrr::Fullscreen => fullscreen.is_some(),1163
- });1164
- if want_vrr != surface.vrr_on {1165
- match surface.drm.with_compositor(|c| c.use_vrr(want_vrr)) {1166
- Ok(()) => {1167
- surface.vrr_on = want_vrr;1168
- tracing::info!(1169
- output = surface.output.name(),1170
- vrr = want_vrr,1171
- "adaptive sync"1172
- );1173
- }1174
- Err(e) => {1175
- tracing::warn!("could not switch adaptive sync: {e}");1176
- surface.vrr_capable = false;1177
- }1178
- }1179
- }1180
- let began = std::time::Instant::now();1181
- let frame_cpu_began = thread_cpu_ns();1182
- let now = target;1183
- if wm.layout.output(surface.id).is_none() {1184
- return false;1185
- }1186
- let scene_began = std::time::Instant::now();1187
- let ctx = FrameCtx {1188
- renderer: &mut *renderer,1189
- wm: &mut *wm,1190
- input: &mut *input,1191
- lock: &*lock,1192
- deco: &settings.config.decoration,1193
- unmanaged: xwayland.as_ref().map_or(&[], |x| &x.unmanaged),1194
- };1195
- let elements = crate::frame::compose(ctx, surface.id, &surface.output, now, true);1196
- let scene_ns = scene_began.elapsed().as_nanos() as u64;1197
- let animating = wm.anim.active(now);1198
- let render_began = std::time::Instant::now();1199
- let cpu_began = thread_cpu_ns();1200
- let mut queue_ns = 0;1201
- let mut failed = false;1202
- match surface1203
- .drm1204
- .render_frame(renderer, &elements, CLEAR, drm.frame_flags)1205
- {1206
- Ok(result) if !result.is_empty => {1207
- let direct = matches!(result.primary_element, PrimaryPlaneElement::Element(_));1208
- let feedback = crate::frame::take_presentation_feedback(1209
- wm,1210
- lock,1211
- xwayland.as_ref().map_or(&[], |x| &x.unmanaged),1212
- &shown,1213
- &surface.output,1214
- &result.states,1215
- );1216
- if direct {1217
- surface.scanned_out += 1;1218
- }1219
- if direct != surface.scanout {1220
- surface.scanout = direct;1221
- tracing::info!(1222
- output = surface.output.name(),1223
- scanout = direct,1224
- elements = elements.len(),1225
- overlay_planes = result.overlay_elements.len(),1226
- cursor_plane = result.cursor_element.is_some(),1227
- "direct scanout"1228
- );1229
- }1230
- let queue_began = std::time::Instant::now();1231
- match surface.drm.queue_frame(feedback) {1232
- Ok(()) => {1233
- surface.queued = true;1234
- surface.queued_at = now_ns();1235
- watch(&drm.handle, surface, crtc);1236
- lock.shown(&surface.output);1237
- captures.output_damaged(&surface.output);1238
- }1239
- Err(e) => {1240
- failed = true;1241
- tracing::warn!("queueing a frame failed: {e}");1242
- }1243
- }1244
- queue_ns = queue_began.elapsed().as_nanos() as u64;1245
- }1246
- Ok(result) => {1247
- drop(crate::frame::take_presentation_feedback(1248
- wm,1249
- lock,1250
- xwayland.as_ref().map_or(&[], |x| &x.unmanaged),1251
- &shown,1252
- &surface.output,1253
- &result.states,1254
- ));1255
- lock.shown(&surface.output);1256
- }1257
- Err(e) => {1258
- failed = true;1259
- tracing::warn!("rendering failed: {e}");1260
- }1261
- }1262
- let render_cpu_ns = thread_cpu_ns() - cpu_began;1263
- let render_ns = render_began.elapsed().as_nanos() as u64 - queue_ns;1264
- let took = began.elapsed().as_nanos() as u64;1265
- surface.budget.record(took);1266
- if surface.frame_cpu_ns.len() < MAX_CPU_SAMPLES {1267
- surface.frame_cpu_ns.push(thread_cpu_ns() - frame_cpu_began);1268
- }1269
- if took > surface.refresh_ns / 2 {1270
- tracing::debug!(1271
- output = surface.output.name(),1272
- took_ns = took,1273
- scene_ns,1274
- render_ns,1275
- render_cpu_ns,1276
- queue_ns,1277
- "slow frame"1278
- );1279
- }1280
- if !surface.queued {1281
- surface.target_ns = None;1282
- }1283
- surface.failures = if failed { surface.failures + 1 } else { 0 };1284
- surface.redraw = animating || failed;1285
- if !surface.queued {1286
- crate::fifo::release_idle(&core.handle, &surface.output);1287
- }1288
- let elapsed = core.start.elapsed();1289
- crate::frame::send_frame_callbacks(1290
- wm,1291
- lock,1292
- xwayland.as_ref().map_or(&[], |x| &x.unmanaged),1293
- &shown,1294
- &surface.output,1295
- elapsed,1296
- input.dnd_icon.as_ref(),1297
- );1298
- crate::frame::send_cursor_frames(wm, input, surface.id, &surface.output, elapsed);1299
- (animating || failed) && !surface.queued1300
-}1301
-1302
-/// CPU time this thread has used, to tell a render that works from one that1303
-/// waits or is descheduled.1304
-fn thread_cpu_ns() -> u64 {1305
- let t = smithay::reexports::rustix::time::clock_gettime(1306
- smithay::reexports::rustix::time::ClockId::ThreadCPUTime,1307
- );1308
- t.tv_sec as u64 * 1_000_000_000 + t.tv_nsec as u641309
-}1310
-1311
-fn on_input(state: &mut State, event: InputEvent<LibinputInputBackend>) {1312
- match event {1313
- InputEvent::Keyboard { event } => {1314
- let time = event.time();1315
- state.key(event.key_code(), event.state(), time);1316
- }1317
- InputEvent::PointerMotion { event } => {1318
- let (delta, unaccel) = (event.delta(), event.delta_unaccel());1319
- let pos = clamp(state, state.input.pointer_pos + delta);1320
- state.pointer_motion(pos, Motion::Relative { delta, unaccel }, event.time());1321
- state.pointer_frame();1322
- state.dirty.cursor = true;1323
- }1324
- InputEvent::PointerMotionAbsolute { event } => {1325
- // Tablets and absolute mice span the whole desktop, as in sway.1326
- let Some(area) = state1327
- .wm1328
- .layout1329
- .outputs()1330
- .map(|(_, o)| o.area)1331
- .reduce(|a, b| a.union(&b))1332
- else {1333
- return;1334
- };1335
- let p = event.position_transformed((area.w as i32, area.h as i32).into());1336
- let pos = clamp(state, Point::from((area.x + p.x, area.y + p.y)));1337
- state.pointer_motion(pos, Motion::Absolute, event.time());1338
- state.pointer_frame();1339
- state.dirty.cursor = true;1340
- }1341
- InputEvent::PointerButton { event } => {1342
- let time = event.time();1343
- state.pointer_button(event.button_code(), event.state(), time);1344
- state.pointer_frame();1345
- }1346
- InputEvent::PointerAxis { event } => {1347
- let factor = state1348
- .backend1349
- .drm()1350
- .map_or(1.0, |d| d.devices.scroll_factor(&event.device()));1351
- let source = event.source();1352
- let mut frame = AxisFrame::new(event.time()).source(source);1353
- for axis in [Axis::Horizontal, Axis::Vertical] {1354
- let v120 = event.amount_v120(axis).map(|v| v * factor);1355
- if let Some(v120) = v120 {1356
- frame = frame.v120(axis, v120 as i32);1357
- }1358
- let amount = event1359
- .amount(axis)1360
- .map(|a| a * factor)1361
- .or_else(|| v120.map(|v| v * 15.0 / 120.0));1362
- match amount {1363
- Some(a) if a != 0.0 => frame = frame.value(axis, a),1364
- Some(_) if source == AxisSource::Finger => frame = frame.stop(axis),1365
- _ => {}1366
- }1367
- }1368
- state.pointer_axis(frame);1369
- state.pointer_frame();1370
- }1371
- InputEvent::SwitchToggle { event } => {1372
- if event.switch() == Some(Switch::Lid) {1373
- state.lid_changed(event.switch_state() == SwitchState::On);1374
- }1375
- }1376
- InputEvent::DeviceAdded { device } => {1377
- if let Some(drm) = state.backend.drm_mut() {1378
- drm.devices.add(device, &state.settings.config.input);1379
- }1380
- }1381
- InputEvent::DeviceRemoved { device } => {1382
- if let Some(drm) = state.backend.drm_mut() {1383
- drm.devices.remove(&device);1384
- }1385
- }1386
- _ => {}1387
- }1388
-}1389
-1390
-/// Keeps the pointer on some output, sliding along the edge it hit.1391
-fn clamp(1392
- state: &State,1393
- pos: Point<f64, smithay::utils::Logical>,1394
-) -> Point<f64, smithay::utils::Logical> {1395
- let inside = |a: &Rect, p: Point<f64, smithay::utils::Logical>| a.contains(p.x, p.y);1396
- let areas: Vec<Rect> = state.wm.layout.outputs().map(|(_, o)| o.area).collect();1397
- if areas.iter().any(|a| inside(a, pos)) {1398
- return pos;1399
- }1400
- let Some(a) = areas1401
- .iter()1402
- .find(|a| inside(a, state.input.pointer_pos))1403
- .or(areas.first())1404
- else {1405
- return pos;1406
- };1407
- Point::from((1408
- pos.x.clamp(a.x, a.right() - 1.0),1409
- pos.y.clamp(a.y, a.bottom() - 1.0),1410
- ))1411
-}@@ -0,0 +1,115 @@
1
+//! libinput events turned into compositor input.2
+3
+use crate::input::Motion;4
+use crate::state::State;5
+use monolith_layout::Rect;6
+use smithay::backend::input::{7
+ AbsolutePositionEvent, Axis, AxisSource, Event, InputEvent, KeyboardKeyEvent, PointerAxisEvent,8
+ PointerButtonEvent, PointerMotionEvent,9
+};10
+use smithay::backend::libinput::LibinputInputBackend;11
+use smithay::input::pointer::AxisFrame;12
+use smithay::reexports::input::event::switch::{Switch, SwitchState};13
+use smithay::utils::Point;14
+15
+pub(super) fn on_input(state: &mut State, event: InputEvent<LibinputInputBackend>) {16
+ match event {17
+ InputEvent::Keyboard { event } => {18
+ let time = event.time();19
+ state.key(event.key_code(), event.state(), time);20
+ }21
+ InputEvent::PointerMotion { event } => {22
+ let (delta, unaccel) = (event.delta(), event.delta_unaccel());23
+ let pos = clamp(state, state.input.pointer_pos + delta);24
+ state.pointer_motion(pos, Motion::Relative { delta, unaccel }, event.time());25
+ state.pointer_frame();26
+ state.dirty.cursor = true;27
+ }28
+ InputEvent::PointerMotionAbsolute { event } => {29
+ // Tablets and absolute mice span the whole desktop, as in sway.30
+ let Some(area) = state31
+ .wm32
+ .layout33
+ .outputs()34
+ .map(|(_, o)| o.area)35
+ .reduce(|a, b| a.union(&b))36
+ else {37
+ return;38
+ };39
+ let p = event.position_transformed((area.w as i32, area.h as i32).into());40
+ let pos = clamp(state, Point::from((area.x + p.x, area.y + p.y)));41
+ state.pointer_motion(pos, Motion::Absolute, event.time());42
+ state.pointer_frame();43
+ state.dirty.cursor = true;44
+ }45
+ InputEvent::PointerButton { event } => {46
+ let time = event.time();47
+ state.pointer_button(event.button_code(), event.state(), time);48
+ state.pointer_frame();49
+ }50
+ InputEvent::PointerAxis { event } => {51
+ let factor = state52
+ .backend53
+ .drm()54
+ .map_or(1.0, |d| d.devices.scroll_factor(&event.device()));55
+ let source = event.source();56
+ let mut frame = AxisFrame::new(event.time()).source(source);57
+ for axis in [Axis::Horizontal, Axis::Vertical] {58
+ let v120 = event.amount_v120(axis).map(|v| v * factor);59
+ if let Some(v120) = v120 {60
+ frame = frame.v120(axis, v120 as i32);61
+ }62
+ let amount = event63
+ .amount(axis)64
+ .map(|a| a * factor)65
+ .or_else(|| v120.map(|v| v * 15.0 / 120.0));66
+ match amount {67
+ Some(a) if a != 0.0 => frame = frame.value(axis, a),68
+ Some(_) if source == AxisSource::Finger => frame = frame.stop(axis),69
+ _ => {}70
+ }71
+ }72
+ state.pointer_axis(frame);73
+ state.pointer_frame();74
+ }75
+ InputEvent::SwitchToggle { event } => {76
+ if event.switch() == Some(Switch::Lid) {77
+ state.lid_changed(event.switch_state() == SwitchState::On);78
+ }79
+ }80
+ InputEvent::DeviceAdded { device } => {81
+ if let Some(drm) = state.backend.drm_mut() {82
+ drm.devices.add(device, &state.settings.config.input);83
+ }84
+ }85
+ InputEvent::DeviceRemoved { device } => {86
+ if let Some(drm) = state.backend.drm_mut() {87
+ drm.devices.remove(&device);88
+ }89
+ }90
+ _ => {}91
+ }92
+}93
+94
+/// Keeps the pointer on some output, sliding along the edge it hit.95
+pub(super) fn clamp(96
+ state: &State,97
+ pos: Point<f64, smithay::utils::Logical>,98
+) -> Point<f64, smithay::utils::Logical> {99
+ let inside = |a: &Rect, p: Point<f64, smithay::utils::Logical>| a.contains(p.x, p.y);100
+ let areas: Vec<Rect> = state.wm.layout.outputs().map(|(_, o)| o.area).collect();101
+ if areas.iter().any(|a| inside(a, pos)) {102
+ return pos;103
+ }104
+ let Some(a) = areas105
+ .iter()106
+ .find(|a| inside(a, state.input.pointer_pos))107
+ .or(areas.first())108
+ else {109
+ return pos;110
+ };111
+ Point::from((112
+ pos.x.clamp(a.x, a.right() - 1.0),113
+ pos.y.clamp(a.y, a.bottom() - 1.0),114
+ ))115
+}@@ -0,0 +1,172 @@
1
+//! Bare metal. A libseat session, udev for the primary GPU and its2
+//! connectors, libinput for input, and one smithay DRM output per connector,3
+//! each rendered when its own vblank lands.4
+5
+mod input;6
+mod outputs;7
+mod pacing;8
+mod render;9
+mod session;10
+11
+pub use outputs::{reconfigure_outputs, set_parked};12
+pub use render::render;13
+pub use session::{init, primary};14
+15
+use crate::devices::Devices;16
+use crate::state::State;17
+use monolith_layout::OutputId;18
+use smithay::backend::allocator::gbm::GbmAllocator;19
+use smithay::backend::drm::compositor::FrameFlags;20
+use smithay::backend::drm::exporter::gbm::GbmFramebufferExporter;21
+use smithay::backend::drm::output::{DrmOutput, DrmOutputManager};22
+use smithay::backend::drm::{DrmDeviceFd, DrmNode};23
+use smithay::backend::session::Session;24
+use smithay::backend::session::libseat::LibSeatSession;25
+use smithay::desktop::utils::OutputPresentationFeedback;26
+use smithay::output::Output;27
+use smithay::reexports::calloop::{LoopHandle, RegistrationToken};28
+use smithay::reexports::drm::control::{connector, crtc};29
+use smithay::reexports::input::Libinput;30
+use smithay::reexports::wayland_server::backend::GlobalId;31
+use smithay::wayland::drm_syncobj::DrmSyncobjState;32
+use smithay_drm_extras::drm_scanner::DrmScanner;33
+use std::collections::{HashMap, HashSet};34
+35
+type Allocator = GbmAllocator<DrmDeviceFd>;36
+type Exporter = GbmFramebufferExporter<DrmDeviceFd>;37
+type Manager = DrmOutputManager<Allocator, Exporter, OutputPresentationFeedback, DrmDeviceFd>;38
+type Out = DrmOutput<Allocator, Exporter, OutputPresentationFeedback, DrmDeviceFd>;39
+40
+pub struct Drm {41
+ pub session: LibSeatSession,42
+ libinput: Libinput,43
+ handle: LoopHandle<'static, State>,44
+ node: DrmNode,45
+ pub render_node: DrmNode,46
+ pub syncobj: Option<DrmSyncobjState>,47
+ manager: Manager,48
+ scanner: DrmScanner,49
+ nvidia: bool,50
+ frame_flags: FrameFlags,51
+ surfaces: HashMap<crtc::Handle, Surface>,52
+ /// Connected connectors the scanner has already reported but that are53
+ /// not lit, retried on the next scan.54
+ failed: HashSet<connector::Handle>,55
+ pub devices: Devices,56
+}57
+58
+struct Surface {59
+ output: Output,60
+ global: GlobalId,61
+ id: OutputId,62
+ drm: Out,63
+ /// A frame is on its way to the screen and its vblank has not landed.64
+ queued: bool,65
+ queued_at: u64,66
+ /// The panel is lit. Off outputs are not rendered to.67
+ powered: bool,68
+ /// Out of the layout and Space, but still driven so it can relight.69
+ parked: bool,70
+ /// Fires when a queued frame's vblank never arrives.71
+ watchdog: Option<RegistrationToken>,72
+ /// Consecutive frames that failed to render or queue.73
+ failures: u32,74
+ redraw: bool,75
+ last_vblank_ns: Option<u64>,76
+ refresh_ns: u64,77
+ /// The pending render, timed to start just in time for `target_ns`.78
+ scheduled: Option<RegistrationToken>,79
+ /// `scheduled` only waits out a cap or a retry, so fresh content may80
+ /// cancel it and draw at once.81
+ preemptible: bool,82
+ /// The vblank the frame being drawn or queued is meant for.83
+ target_ns: Option<u64>,84
+ budget: crate::pacing::Budget,85
+ /// Frames presented since `stats_since`, for the once-a-second debug line.86
+ presented: u32,87
+ stats_since: u64,88
+ vrr_capable: bool,89
+ vrr_on: bool,90
+ /// Only the cursor moved, which under VRR is drawn at a capped rate.91
+ cursor_redraw: bool,92
+ /// The last frame went straight to the primary plane without compositing.93
+ scanout: bool,94
+ /// Frames scanned out and per-frame compositor CPU times since95
+ /// `stats_since`, for the targets in decisions.md.96
+ scanned_out: u32,97
+ frame_cpu_ns: Vec<u64>,98
+}99
+100
+fn surface_mut(state: &mut State, crtc: crtc::Handle) -> Option<&mut Surface> {101
+ state.backend.drm_mut()?.surfaces.get_mut(&crtc)102
+}103
+104
+impl Surface {105
+ /// The first vblank a render starting at `now` can still make, and the106
+ /// latest moment to start it.107
+ fn next_deadline(&self, now: u64) -> Option<(u64, u64)> {108
+ crate::pacing::deadline(self.last_vblank_ns?, self.refresh_ns, self.budget.ns(), now)109
+ }110
+111
+ /// How long a queued frame may go without its vblank. Under VRR a flip112
+ /// waits for the panel's longest frame, so the floor stays well above it.113
+ fn flip_timeout(&self) -> u64 {114
+ (self.refresh_ns * 4).max(100_000_000)115
+ }116
+}117
+118
+impl Drm {119
+ pub fn names(&self) -> Vec<String> {120
+ self.surfaces.values().map(|s| s.output.name()).collect()121
+ }122
+123
+ /// Lights or darkens the output called `name`. Dark outputs get DPMS off124
+ /// and no frames, and the next frame after turning on is a full modeset.125
+ pub fn set_powered(&mut self, name: &str, on: bool) {126
+ let active = self.session.is_active();127
+ for s in self128
+ .surfaces129
+ .values_mut()130
+ .filter(|s| s.output.name() == name)131
+ {132
+ if s.powered == on {133
+ continue;134
+ }135
+ s.powered = on;136
+ tracing::info!(name, on, "output power");137
+ if on {138
+ s.redraw = true;139
+ s.last_vblank_ns = None;140
+ continue;141
+ }142
+ for token in [s.scheduled.take(), s.watchdog.take()]143
+ .into_iter()144
+ .flatten()145
+ {146
+ self.handle.remove(token);147
+ }148
+ s.queued = false;149
+ s.target_ns = None;150
+ s.cursor_redraw = false;151
+ if active && let Err(e) = s.drm.with_compositor(|c| c.clear()) {152
+ tracing::warn!(name, "could not switch the output off: {e}");153
+ }154
+ }155
+ }156
+157
+ pub fn output(&self, id: OutputId) -> Option<Output> {158
+ self.surfaces159
+ .values()160
+ .find(|s| s.id == id)161
+ .map(|s| s.output.clone())162
+ }163
+164
+ pub fn timeline(&self, output: &Output) -> Option<crate::timing::Timeline> {165
+ let s = self.surfaces.values().find(|s| s.output == *output)?;166
+ Some(crate::timing::Timeline {167
+ last: s.last_vblank_ns?,168
+ refresh: if s.vrr_on { 0 } else { s.refresh_ns },169
+ budget: s.budget.ns(),170
+ })171
+ }172
+}@@ -0,0 +1,514 @@
1
+//! Connectors turning into outputs, and back.2
+3
+use super::Surface;4
+use super::input::clamp;5
+use crate::backend::{Backend, logical_area};6
+use crate::scene::Draw;7
+use crate::state::State;8
+use crate::wm::Wm;9
+use monolith_config::OutputTransform;10
+use smithay::backend::drm::VrrSupport;11
+use smithay::backend::drm::output::DrmOutputRenderElements;12
+use smithay::backend::input::InputTime;13
+use smithay::output::{Mode, Output, PhysicalProperties, Scale, Subpixel};14
+use smithay::reexports::calloop::timer::{TimeoutAction, Timer};15
+use smithay::reexports::drm::control::{self, ModeTypeFlags, connector, crtc};16
+use smithay::utils::{Logical, Point, Transform};17
+use smithay_drm_extras::drm_scanner::DrmScanEvent;18
+use std::time::Duration;19
+20
+/// How long a disabled wl_output stays bound before it is removed, so21
+/// clients that were mid-bind are not killed by the removal.22
+const GLOBAL_REMOVAL_DELAY: Duration = Duration::from_secs(5);23
+24
+/// Brings outputs in line with what is plugged in.25
+pub(super) fn scan(state: &mut State) {26
+ let Some(drm) = state.backend.drm_mut() else {27
+ return;28
+ };29
+ let events: Vec<DrmScanEvent> = match drm.scanner.scan_connectors(drm.manager.device()) {30
+ Ok(result) => result.into_iter().collect(),31
+ Err(e) => {32
+ tracing::warn!("scanning connectors failed: {e}");33
+ return;34
+ }35
+ };36
+ let retry = std::mem::take(&mut drm.failed);37
+ for event in events {38
+ match event {39
+ DrmScanEvent::Connected {40
+ connector,41
+ crtc: Some(crtc),42
+ } => connect(state, connector, crtc),43
+ DrmScanEvent::Connected {44
+ connector,45
+ crtc: None,46
+ } => {47
+ tracing::warn!(handle = ?connector.handle(), "no free crtc for the connector");48
+ if let Some(drm) = state.backend.drm_mut() {49
+ drm.failed.insert(connector.handle());50
+ }51
+ }52
+ DrmScanEvent::Disconnected {53
+ crtc: Some(crtc), ..54
+ } => disconnect(state, crtc),55
+ DrmScanEvent::Changed {56
+ connector,57
+ crtc: Some(crtc),58
+ } => changed(state, connector, crtc),59
+ _ => {}60
+ }61
+ }62
+ for handle in retry {63
+ retry_connector(state, handle);64
+ }65
+ state.apply_power();66
+ state.lid_act();67
+ state.apply_layout();68
+}69
+70
+/// Lights a connector that failed on an earlier scan, which the scanner71
+/// will not report again until it changes.72
+pub(super) fn retry_connector(state: &mut State, handle: connector::Handle) {73
+ let Some(drm) = state.backend.drm_mut() else {74
+ return;75
+ };76
+ if drm.failed.contains(&handle) {77
+ return;78
+ }79
+ let Some(info) = drm.scanner.connectors().get(&handle) else {80
+ return;81
+ };82
+ if info.state() != connector::State::Connected {83
+ return;84
+ }85
+ let info = info.clone();86
+ let Some(crtc) = drm.scanner.crtc_for_connector(&handle) else {87
+ drm.failed.insert(handle);88
+ return;89
+ };90
+ if !drm.surfaces.contains_key(&crtc) {91
+ connect(state, info, crtc);92
+ }93
+}94
+95
+pub(super) fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {96
+ let name = connector_name(&connector);97
+ let config = state98
+ .settings99
+ .config100
+ .output101
+ .named102
+ .get(&name)103
+ .cloned()104
+ .unwrap_or_default();105
+ let Some(mode) = choose_mode(&connector, &name, config.mode.as_deref()) else {106
+ tracing::warn!(name, "connector has no modes");107
+ if let Some(drm) = state.backend.drm_mut() {108
+ drm.failed.insert(connector.handle());109
+ }110
+ return;111
+ };112
+ let transform = transform(config.transform);113
+ let State {114
+ backend: Backend::Drm(drm),115
+ renderer,116
+ wm: Wm { layout, space, .. },117
+ core,118
+ ..119
+ } = state120
+ else {121
+ return;122
+ };123
+ let (mm_w, mm_h) = connector.size().unwrap_or((0, 0));124
+ let output = Output::new(125
+ name.clone(),126
+ PhysicalProperties {127
+ size: (mm_w as i32, mm_h as i32).into(),128
+ subpixel: Subpixel::Unknown,129
+ make: "Unknown".into(),130
+ model: name.clone(),131
+ serial_number: String::new(),132
+ },133
+ );134
+ let wl_mode = Mode::from(mode);135
+ let right = layout136
+ .outputs()137
+ .map(|(_, o)| o.area.right())138
+ .fold(0.0, f64::max);139
+ let position = match config.position {140
+ Some([x, y]) => Point::from((x, y)),141
+ None => Point::from((right.ceil() as i32, 0)),142
+ };143
+ let scale = config.scale();144
+ output.change_current_state(145
+ Some(wl_mode),146
+ Some(transform),147
+ Some(Scale::Fractional(scale)),148
+ Some(position),149
+ );150
+ output.set_preferred(wl_mode);151
+ let mut planes = drm.manager.device().planes(&crtc).ok();152
+ if drm.nvidia153
+ && let Some(p) = planes.as_mut()154
+ {155
+ // NVIDIA's overlay planes break composition, as anvil also found.156
+ p.overlay.clear();157
+ }158
+ let out = match drm.manager.lock().initialize_output::<_, Draw>(159
+ crtc,160
+ mode,161
+ &[connector.handle()],162
+ &output,163
+ planes,164
+ renderer,165
+ &DrmOutputRenderElements::default(),166
+ ) {167
+ Ok(out) => out,168
+ Err(e) => {169
+ tracing::warn!(name, "could not light up the output: {e}");170
+ drm.failed.insert(connector.handle());171
+ return;172
+ }173
+ };174
+ let vrr_capable = matches!(175
+ out.with_compositor(|c| c.vrr_supported(connector.handle())),176
+ Ok(VrrSupport::Supported)177
+ );178
+ let global = output.create_global::<State>(&core.display);179
+ space.map_output(&output, position);180
+ let (w, h) = mode.size();181
+ let area = logical_area(position, wl_mode.size, transform, scale);182
+ let id = layout.add_output(&name, area);183
+ tracing::info!(184
+ name,185
+ w,186
+ h,187
+ scale,188
+ refresh = mode.vrefresh(),189
+ ?transform,190
+ x = position.x,191
+ y = position.y,192
+ vrr_capable,193
+ "output connected"194
+ );195
+ drm.surfaces.insert(196
+ crtc,197
+ Surface {198
+ output,199
+ global,200
+ id,201
+ drm: out,202
+ queued: false,203
+ queued_at: 0,204
+ powered: true,205
+ parked: false,206
+ watchdog: None,207
+ failures: 0,208
+ redraw: true,209
+ last_vblank_ns: None,210
+ scheduled: None,211
+ preemptible: false,212
+ target_ns: None,213
+ budget: crate::pacing::Budget::new(),214
+ presented: 0,215
+ stats_since: 0,216
+ vrr_capable,217
+ vrr_on: false,218
+ cursor_redraw: false,219
+ scanout: false,220
+ scanned_out: 0,221
+ frame_cpu_ns: Vec::new(),222
+ refresh_ns: 1_000_000_000_000 / u64::from(wl_mode.refresh.max(1) as u32),223
+ },224
+ );225
+}226
+227
+pub(super) fn connector_name(connector: &connector::Info) -> String {228
+ format!(229
+ "{}-{}",230
+ connector.interface().as_str(),231
+ connector.interface_id()232
+ )233
+}234
+235
+/// The configured mode if the connector has it, else its preferred one.236
+pub(super) fn choose_mode(237
+ connector: &connector::Info,238
+ name: &str,239
+ configured: Option<&str>,240
+) -> Option<control::Mode> {241
+ let chosen = configured.and_then(|spec| {242
+ let found = pick_mode(connector.modes(), spec);243
+ if found.is_none() {244
+ tracing::warn!(name, spec, "no such mode, using the preferred one");245
+ }246
+ found247
+ });248
+ chosen.or_else(|| {249
+ connector250
+ .modes()251
+ .iter()252
+ .find(|m| m.mode_type().contains(ModeTypeFlags::PREFERRED))253
+ .or_else(|| connector.modes().first())254
+ .copied()255
+ })256
+}257
+258
+pub(super) fn changed(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {259
+ if !remode(state, &connector, crtc) {260
+ disconnect(state, crtc);261
+ connect(state, connector, crtc);262
+ }263
+}264
+265
+/// Moves a lit output to the mode its connector now calls for without266
+/// recreating the wl_output, so clients, windows, the lock surface and the267
+/// power state stay as they are. False means the output has to be rebuilt.268
+pub(super) fn remode(state: &mut State, connector: &connector::Info, crtc: crtc::Handle) -> bool {269
+ let name = connector_name(connector);270
+ let State {271
+ backend: Backend::Drm(drm),272
+ renderer,273
+ wm: Wm { layout, .. },274
+ settings,275
+ ..276
+ } = state277
+ else {278
+ return false;279
+ };280
+ let Some(s) = drm.surfaces.get_mut(&crtc) else {281
+ return false;282
+ };283
+ let (mm_w, mm_h) = connector.size().unwrap_or((0, 0));284
+ if s.output.physical_properties().size != (mm_w as i32, mm_h as i32).into() {285
+ return false;286
+ }287
+ let configured = settings.config.output.named.get(&name);288
+ let Some(mode) = choose_mode(connector, &name, configured.and_then(|c| c.mode.as_deref()))289
+ else {290
+ return false;291
+ };292
+ let wl_mode = Mode::from(mode);293
+ let moved = s.output.current_mode() != Some(wl_mode);294
+ if moved {295
+ let elements = DrmOutputRenderElements::default();296
+ if let Err(e) = s.drm.use_mode::<_, Draw>(mode, renderer, &elements) {297
+ tracing::warn!(name, "could not change the mode in place: {e}");298
+ return false;299
+ }300
+ s.output301
+ .change_current_state(Some(wl_mode), None, None, None);302
+ s.refresh_ns = 1_000_000_000_000 / u64::from(wl_mode.refresh.max(1) as u32);303
+ s.last_vblank_ns = None;304
+ s.target_ns = None;305
+ s.redraw = true;306
+ let area = logical_area(307
+ s.output.current_location(),308
+ wl_mode.size,309
+ s.output.current_transform(),310
+ s.output.current_scale().fractional_scale(),311
+ );312
+ layout.set_output_area(s.id, area);313
+ tracing::info!(314
+ name,315
+ w = wl_mode.size.w,316
+ h = wl_mode.size.h,317
+ refresh = mode.vrefresh(),318
+ "output mode changed"319
+ );320
+ }321
+ s.output.set_preferred(wl_mode);322
+ if moved {323
+ reconfigure_outputs(state);324
+ state.reserve_layers();325
+ state.reconfigure_lock();326
+ }327
+ true328
+}329
+330
+/// The mode matching `WIDTHxHEIGHT` or `WIDTHxHEIGHT@HZ`, the highest rate331
+/// when none is given and the nearest one when it is.332
+pub(super) fn pick_mode(modes: &[control::Mode], spec: &str) -> Option<control::Mode> {333
+ let (size, rate) = match spec.split_once('@') {334
+ Some((size, rate)) => (size, Some(rate.trim().parse::<f64>().ok()?)),335
+ None => (spec, None),336
+ };337
+ let (w, h) = size.trim().split_once('x')?;338
+ let size = (w.trim().parse::<u16>().ok()?, h.trim().parse::<u16>().ok()?);339
+ let fitting = modes.iter().filter(|m| m.size() == size);340
+ match rate {341
+ Some(r) => fitting342
+ .min_by(|a, b| {343
+ let off = |m: &&control::Mode| (f64::from(m.vrefresh()) - r).abs();344
+ off(a).total_cmp(&off(b))345
+ })346
+ .copied(),347
+ None => fitting.max_by_key(|m| m.vrefresh()).copied(),348
+ }349
+}350
+351
+pub(super) fn transform(t: OutputTransform) -> Transform {352
+ match t {353
+ OutputTransform::Normal => Transform::Normal,354
+ OutputTransform::Rotate90 => Transform::_90,355
+ OutputTransform::Rotate180 => Transform::_180,356
+ OutputTransform::Rotate270 => Transform::_270,357
+ OutputTransform::Flipped => Transform::Flipped,358
+ OutputTransform::Flipped90 => Transform::Flipped90,359
+ OutputTransform::Flipped180 => Transform::Flipped180,360
+ OutputTransform::Flipped270 => Transform::Flipped270,361
+ }362
+}363
+364
+/// Outputs without a configured position are laid out left to right in365
+/// their current order, so one rotating pushes its neighbours along.366
+pub fn reconfigure_outputs(state: &mut State) {367
+ let State {368
+ backend: Backend::Drm(drm),369
+ wm: Wm { layout, space, .. },370
+ settings,371
+ ..372
+ } = state373
+ else {374
+ return;375
+ };376
+ let mut surfaces: Vec<&mut Surface> = drm.surfaces.values_mut().filter(|s| !s.parked).collect();377
+ surfaces.sort_by_cached_key(|s| {378
+ let name = s.output.name();379
+ (380
+ s.output.current_location().x,381
+ !crate::power::internal(&name),382
+ )383
+ });384
+ let mut right = 0;385
+ let mut changed = false;386
+ for s in surfaces {387
+ let config = settings388
+ .config389
+ .output390
+ .named391
+ .get(&s.output.name())392
+ .cloned()393
+ .unwrap_or_default();394
+ let Some(mode) = s.output.current_mode() else {395
+ continue;396
+ };397
+ let transform = transform(config.transform);398
+ let scale = config.scale();399
+ let position: Point<i32, Logical> = match config.position {400
+ Some([x, y]) => (x, y).into(),401
+ None => (right, 0).into(),402
+ };403
+ let area = logical_area(position, mode.size, transform, scale);404
+ right = right.max(area.right().ceil() as i32);405
+ if s.output.current_transform() == transform406
+ && s.output.current_location() == position407
+ && s.output.current_scale().fractional_scale() == scale408
+ {409
+ continue;410
+ }411
+ s.output.change_current_state(412
+ None,413
+ Some(transform),414
+ Some(Scale::Fractional(scale)),415
+ Some(position),416
+ );417
+ space.map_output(&s.output, position);418
+ layout.set_output_area(s.id, area);419
+ s.redraw = true;420
+ changed = true;421
+ tracing::info!(422
+ name = s.output.name(),423
+ ?transform,424
+ scale,425
+ x = position.x,426
+ y = position.y,427
+ "output reconfigured"428
+ );429
+ }430
+ if changed {431
+ state.reserve_layers();432
+ state.reconfigure_lock();433
+ state.apply_layout();434
+ }435
+}436
+437
+pub(super) fn disconnect(state: &mut State, crtc: crtc::Handle) {438
+ let Some(drm) = state.backend.drm_mut() else {439
+ return;440
+ };441
+ let Some(surface) = drm.surfaces.remove(&crtc) else {442
+ return;443
+ };444
+ for token in [surface.scheduled, surface.watchdog].into_iter().flatten() {445
+ drm.handle.remove(token);446
+ }447
+ tracing::info!(name = surface.output.name(), "output disconnected");448
+ state449
+ .core450
+ .display451
+ .disable_global::<State>(surface.global.clone());452
+ let global = surface.global.clone();453
+ let removal = Timer::from_duration(GLOBAL_REMOVAL_DELAY);454
+ let armed = drm.handle.insert_source(removal, move |_, _, state| {455
+ state.core.display.remove_global::<State>(global.clone());456
+ TimeoutAction::Drop457
+ });458
+ if let Err(e) = armed {459
+ tracing::warn!("could not defer removing the wl_output: {e}");460
+ state461
+ .core462
+ .display463
+ .remove_global::<State>(surface.global.clone());464
+ }465
+ state.wm.space.unmap_output(&surface.output);466
+ state.wm.layout.remove_output(surface.id);467
+ state.close_layers(&surface.output);468
+ state.services.lock.forget(&surface.output);469
+ state.services.power.forget(&surface.output.name());470
+ state.protocols.output_power.output_removed(&surface.output);471
+}472
+473
+/// Takes the output called `name` out of the layout and Space, or puts it474
+/// back. Its windows move to the other outputs and return when it does.475
+pub fn set_parked(state: &mut State, name: &str, parked: bool) {476
+ let State {477
+ backend: Backend::Drm(drm),478
+ wm: Wm { layout, space, .. },479
+ ..480
+ } = state481
+ else {482
+ return;483
+ };484
+ let Some(s) = drm.surfaces.values_mut().find(|s| s.output.name() == name) else {485
+ return;486
+ };487
+ if s.parked == parked {488
+ return;489
+ }490
+ s.parked = parked;491
+ tracing::info!(name, parked, "output parked");492
+ if parked {493
+ space.unmap_output(&s.output);494
+ layout.remove_output(s.id);495
+ } else if let Some(mode) = s.output.current_mode() {496
+ let position = s.output.current_location();497
+ space.map_output(&s.output, position);498
+ let scale = s.output.current_scale().fractional_scale();499
+ let area = logical_area(position, mode.size, s.output.current_transform(), scale);500
+ s.id = layout.add_output(name, area);501
+ s.redraw = true;502
+ }503
+ let pos = state.input.pointer_pos;504
+ let held = clamp(state, pos);505
+ if held != pos {506
+ state.warp_pointer(held, InputTime::now());507
+ }508
+ if !parked {509
+ reconfigure_outputs(state);510
+ }511
+ state.reserve_layers();512
+ state.reconfigure_lock();513
+ state.apply_layout();514
+}@@ -0,0 +1,273 @@
1
+//! When each output renders next, and what happens when its flip lands or2
+//! never does.3
+4
+use crate::state::{State, now_ns};5
+use smithay::backend::drm::{6
+ DrmEventMetadata, DrmEventTime,7
+};8
+use smithay::backend::session::Session;9
+use smithay::reexports::calloop::timer::{TimeoutAction, Timer};10
+use smithay::reexports::calloop::LoopHandle;11
+use smithay::reexports::drm::control::crtc;12
+use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind;13
+use smithay::utils::{Monotonic, Time};14
+use smithay::wayland::presentation::Refresh;15
+use std::time::Duration;16
+use super::{Surface, surface_mut};17
+use super::render::render_surface;18
+19
+/// Frames that only move the cursor stay at or below 60 Hz under VRR, so the20
+/// mouse does not override the refresh rate a game is pacing.21
+const CURSOR_FRAME_NS: u64 = 16_666_667;22
+23
+pub(super) fn on_vblank(24
+ state: &mut State,25
+ crtc: crtc::Handle,26
+ meta: &mut Option<DrmEventMetadata>,27
+) {28
+ let presented = state29
+ .backend30
+ .drm()31
+ .and_then(|d| d.surfaces.get(&crtc))32
+ .map(|s| s.output.clone());33
+ if let Some(output) = presented {34
+ crate::fifo::presented(state, &output);35
+ }36
+ let Some(surface) = surface_mut(state, crtc) else {37
+ return;38
+ };39
+ let feedback = surface.drm.frame_submitted().unwrap_or_else(|e| {40
+ tracing::warn!("frame was not submitted: {e}");41
+ None42
+ });43
+ surface.queued = false;44
+ if let Err(e) = state.renderer.absorb_submit_stall() {45
+ tracing::warn!("absorbing a submit stall failed: {e}");46
+ }47
+ let Some(surface) = surface_mut(state, crtc) else {48
+ return;49
+ };50
+ if let Some(mut feedback) = feedback {51
+ let (time, clock) = match meta.as_ref().map(|m| m.time) {52
+ Some(DrmEventTime::Monotonic(t)) => (t, Kind::HwClock | Kind::HwCompletion),53
+ _ => (Duration::from_nanos(now_ns()), Kind::empty()),54
+ };55
+ let refresh = Duration::from_nanos(surface.refresh_ns);56
+ let refresh = if surface.vrr_on {57
+ Refresh::variable(refresh)58
+ } else {59
+ Refresh::fixed(refresh)60
+ };61
+ let sequence = meta.as_ref().map_or(0, |m| u64::from(m.sequence));62
+ feedback.presented(63
+ Time::<Monotonic>::from(time),64
+ refresh,65
+ sequence,66
+ Kind::Vsync | clock,67
+ );68
+ }69
+ if let Some(DrmEventTime::Monotonic(t)) = meta.as_ref().map(|m| m.time) {70
+ let t = t.as_nanos() as u64;71
+ if let Some(target) = surface.target_ns.take() {72
+ if t > target + surface.refresh_ns / 2 {73
+ surface.budget.missed(surface.refresh_ns);74
+ tracing::debug!(75
+ output = surface.output.name(),76
+ late_ns = t - target,77
+ budget_ns = surface.budget.ns(),78
+ "frame missed its vblank"79
+ );80
+ } else {81
+ surface.budget.landed();82
+ }83
+ }84
+ surface.last_vblank_ns = Some(t);85
+ surface.presented += 1;86
+ let window = t.saturating_sub(surface.stats_since);87
+ if window >= 1_000_000_000 {88
+ // Under VRR the panel waits for each flip, so the nominal refresh89
+ // says nothing about how many vblanks there were.90
+ let vblanks = (!surface.vrr_on).then(|| window / surface.refresh_ns.max(1));91
+ let cpu = &mut surface.frame_cpu_ns;92
+ cpu.sort_unstable();93
+ let p99 = cpu.get(cpu.len().saturating_sub(1) * 99 / 100).copied();94
+ tracing::debug!(95
+ output = surface.output.name(),96
+ presented = surface.presented,97
+ vblanks,98
+ vrr = surface.vrr_on,99
+ scanned_out = surface.scanned_out,100
+ cpu_p99_us = p99.map(|ns| ns / 1000),101
+ budget_us = surface.budget.ns() / 1000,102
+ "frame rate"103
+ );104
+ cpu.clear();105
+ surface.scanned_out = 0;106
+ surface.presented = 0;107
+ surface.stats_since = t;108
+ }109
+ }110
+ if surface.redraw || surface.cursor_redraw {111
+ schedule(state, crtc);112
+ }113
+}114
+115
+/// Starts a render at the last moment that still makes the next reachable116
+/// vblank, so commits arriving until then make it into the frame.117
+pub(super) fn schedule(state: &mut State, crtc: crtc::Handle) {118
+ let Some(drm) = state.backend.drm_mut() else {119
+ return;120
+ };121
+ if !drm.session.is_active() {122
+ return;123
+ }124
+ let Some(surface) = drm.surfaces.get_mut(&crtc) else {125
+ return;126
+ };127
+ if !surface.powered || surface.queued || surface.scheduled.is_some() {128
+ return;129
+ }130
+ let now = now_ns();131
+ if surface.vrr_on {132
+ let since_flip = surface133
+ .last_vblank_ns134
+ .map_or(u64::MAX, |l| now.saturating_sub(l));135
+ if surface.redraw || since_flip >= CURSOR_FRAME_NS {136
+ draw(state, crtc, now);137
+ return;138
+ }139
+ let wait = Duration::from_nanos(CURSOR_FRAME_NS - since_flip);140
+ arm(&drm.handle, surface, crtc, wait, true, move |state| {141
+ draw(state, crtc, now_ns());142
+ });143
+ return;144
+ }145
+ let Some((vblank, start)) = surface.next_deadline(now) else {146
+ draw(state, crtc, now);147
+ return;148
+ };149
+ surface.target_ns = Some(vblank);150
+ let wait = Duration::from_nanos(start.saturating_sub(now));151
+ arm(&drm.handle, surface, crtc, wait, false, move |state| {152
+ let target = state153
+ .backend154
+ .drm()155
+ .and_then(|d| d.surfaces.get(&crtc)?.target_ns);156
+ if let Some(target) = target {157
+ draw(state, crtc, target);158
+ }159
+ });160
+}161
+162
+/// Runs `fire` once after `after`, tracked in `surface.scheduled` until then.163
+pub(super) fn arm(164
+ handle: &LoopHandle<'static, State>,165
+ surface: &mut Surface,166
+ crtc: crtc::Handle,167
+ after: Duration,168
+ preemptible: bool,169
+ mut fire: impl FnMut(&mut State) + 'static,170
+) {171
+ let token = handle.insert_source(Timer::from_duration(after), move |_, _, state| {172
+ if let Some(s) = surface_mut(state, crtc) {173
+ s.scheduled = None;174
+ }175
+ fire(state);176
+ TimeoutAction::Drop177
+ });178
+ match token {179
+ Ok(token) => {180
+ surface.scheduled = Some(token);181
+ surface.preemptible = preemptible;182
+ }183
+ Err(e) => tracing::warn!(184
+ output = surface.output.name(),185
+ "could not arm a render timer: {e}"186
+ ),187
+ }188
+}189
+190
+/// Arms the flip watchdog unless it already runs.191
+pub(super) fn watch(192
+ handle: &LoopHandle<'static, State>,193
+ surface: &mut Surface,194
+ crtc: crtc::Handle,195
+) {196
+ if surface.watchdog.is_some() {197
+ return;198
+ }199
+ let after = Duration::from_nanos(surface.flip_timeout());200
+ let token = handle.insert_source(Timer::from_duration(after), move |_, _, state| {201
+ flip_watchdog(state, crtc)202
+ });203
+ match token {204
+ Ok(token) => surface.watchdog = Some(token),205
+ Err(e) => tracing::warn!(206
+ output = surface.output.name(),207
+ "could not arm the flip watchdog: {e}"208
+ ),209
+ }210
+}211
+212
+/// Recovers an output whose page flip event never came, as after a213
+/// `DrmEvent::Error`, so `queued` cannot hold it dark forever.214
+pub(super) fn flip_watchdog(state: &mut State, crtc: crtc::Handle) -> TimeoutAction {215
+ let Some(drm) = state.backend.drm_mut() else {216
+ return TimeoutAction::Drop;217
+ };218
+ let active = drm.session.is_active();219
+ let Some(surface) = drm.surfaces.get_mut(&crtc) else {220
+ return TimeoutAction::Drop;221
+ };222
+ let age = now_ns().saturating_sub(surface.queued_at);223
+ let limit = surface.flip_timeout();224
+ if active && surface.queued && age < limit {225
+ return TimeoutAction::ToDuration(Duration::from_nanos(limit - age));226
+ }227
+ surface.watchdog = None;228
+ if !active || !surface.queued {229
+ return TimeoutAction::Drop;230
+ }231
+ tracing::warn!(output = surface.output.name(), "page flip never landed");232
+ if let Err(e) = surface.drm.frame_submitted() {233
+ tracing::warn!("frame was not submitted: {e}");234
+ }235
+ surface.queued = false;236
+ surface.target_ns = None;237
+ surface.redraw = true;238
+ schedule(state, crtc);239
+ TimeoutAction::Drop240
+}241
+242
+/// An animation whose frame had nothing to show, like one hidden behind a243
+/// fullscreen window, has no flip to wait for. Under VRR scheduling again244
+/// would draw at once and recurse until the stack overflowed, so the next245
+/// try waits a refresh.246
+pub(super) fn draw(state: &mut State, crtc: crtc::Handle, target: u64) {247
+ if !render_surface(state, crtc, target) {248
+ return;249
+ }250
+ let Some(drm) = state.backend.drm_mut() else {251
+ return;252
+ };253
+ let Some(surface) = drm.surfaces.get_mut(&crtc) else {254
+ return;255
+ };256
+ if surface.scheduled.is_some() {257
+ return;258
+ }259
+ let backoff = surface.failures.saturating_sub(1).min(6);260
+ let wait = (surface.refresh_ns.max(1_000_000) << backoff).min(1_000_000_000);261
+ let wait = Duration::from_nanos(wait);262
+ let preemptible = surface.failures == 0;263
+ arm(264
+ &drm.handle,265
+ surface,266
+ crtc,267
+ wait,268
+ preemptible,269
+ move |state| {270
+ schedule(state, crtc);271
+ },272
+ );273
+}@@ -0,0 +1,232 @@
1
+//! Drawing one output and queueing the frame.2
+3
+use super::pacing::{schedule, watch};4
+use crate::backend::Backend;5
+use crate::frame::{CLEAR, FrameCtx};6
+use crate::state::Services;7
+use crate::state::{State, now_ns};8
+use monolith_config::Vrr;9
+use smithay::backend::drm::compositor::PrimaryPlaneElement;10
+use smithay::backend::session::Session;11
+use smithay::reexports::drm::control::crtc;12
+13
+/// Frames render without vblanks while retrying or looping on empty frames,14
+/// and only a vblank drains the samples.15
+const MAX_CPU_SAMPLES: usize = 1024;16
+17
+/// Renders every output that has something new and no frame in flight.18
+pub fn render(state: &mut State) {19
+ let Some(drm) = state.backend.drm_mut() else {20
+ return;21
+ };22
+ let cursor = std::mem::take(&mut state.dirty.cursor);23
+ let all = std::mem::take(&mut state.dirty.redraw);24
+ for s in drm.surfaces.values_mut() {25
+ s.redraw |= all;26
+ s.cursor_redraw |= cursor;27
+ if all28
+ && s.preemptible29
+ && let Some(token) = s.scheduled.take()30
+ {31
+ drm.handle.remove(token);32
+ }33
+ }34
+ let ready: Vec<crtc::Handle> = drm35
+ .surfaces36
+ .iter()37
+ .filter(|(_, s)| (s.redraw || s.cursor_redraw) && !s.queued)38
+ .map(|(c, _)| *c)39
+ .collect();40
+ for crtc in ready {41
+ schedule(state, crtc);42
+ }43
+}44
+45
+/// Draws one output for the vblank at `target`. Returns whether it still46
+/// animates without a frame in flight, and so needs another render.47
+#[profiling::function]48
+pub(super) fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {49
+ let State {50
+ backend: Backend::Drm(drm),51
+ renderer,52
+ wm,53
+ settings,54
+ core,55
+ input,56
+ services:57
+ Services {58
+ lock,59
+ captures,60
+ xwayland,61
+ ..62
+ },63
+ ..64
+ } = state65
+ else {66
+ return false;67
+ };68
+ if !drm.session.is_active() {69
+ return false;70
+ }71
+ let Some(surface) = drm.surfaces.get_mut(&crtc) else {72
+ return false;73
+ };74
+ if surface.queued || !surface.powered {75
+ return false;76
+ }77
+ surface.cursor_redraw = false;78
+ let shown = crate::frame::shown_windows(wm, surface.id, &surface.output, target);79
+ let fullscreen = shown.fullscreen;80
+ let forced = fullscreen.and_then(|w| wm.get(w)?.rule.vrr);81
+ let want_vrr = surface.vrr_capable82
+ && forced.unwrap_or(match settings.config.output.vrr {83
+ Vrr::Off => false,84
+ Vrr::On => true,85
+ Vrr::Fullscreen => fullscreen.is_some(),86
+ });87
+ if want_vrr != surface.vrr_on {88
+ match surface.drm.with_compositor(|c| c.use_vrr(want_vrr)) {89
+ Ok(()) => {90
+ surface.vrr_on = want_vrr;91
+ tracing::info!(92
+ output = surface.output.name(),93
+ vrr = want_vrr,94
+ "adaptive sync"95
+ );96
+ }97
+ Err(e) => {98
+ tracing::warn!("could not switch adaptive sync: {e}");99
+ surface.vrr_capable = false;100
+ }101
+ }102
+ }103
+ let began = std::time::Instant::now();104
+ let frame_cpu_began = thread_cpu_ns();105
+ let now = target;106
+ if wm.layout.output(surface.id).is_none() {107
+ return false;108
+ }109
+ let scene_began = std::time::Instant::now();110
+ let ctx = FrameCtx {111
+ renderer: &mut *renderer,112
+ wm: &mut *wm,113
+ input: &mut *input,114
+ lock: &*lock,115
+ deco: &settings.config.decoration,116
+ unmanaged: xwayland.as_ref().map_or(&[], |x| &x.unmanaged),117
+ };118
+ let elements = crate::frame::compose(ctx, surface.id, &surface.output, now, true);119
+ let scene_ns = scene_began.elapsed().as_nanos() as u64;120
+ let animating = wm.anim.active(now);121
+ let render_began = std::time::Instant::now();122
+ let cpu_began = thread_cpu_ns();123
+ let mut queue_ns = 0;124
+ let mut failed = false;125
+ match surface126
+ .drm127
+ .render_frame(renderer, &elements, CLEAR, drm.frame_flags)128
+ {129
+ Ok(result) if !result.is_empty => {130
+ let direct = matches!(result.primary_element, PrimaryPlaneElement::Element(_));131
+ let feedback = crate::frame::take_presentation_feedback(132
+ wm,133
+ lock,134
+ xwayland.as_ref().map_or(&[], |x| &x.unmanaged),135
+ &shown,136
+ &surface.output,137
+ &result.states,138
+ );139
+ if direct {140
+ surface.scanned_out += 1;141
+ }142
+ if direct != surface.scanout {143
+ surface.scanout = direct;144
+ tracing::info!(145
+ output = surface.output.name(),146
+ scanout = direct,147
+ elements = elements.len(),148
+ overlay_planes = result.overlay_elements.len(),149
+ cursor_plane = result.cursor_element.is_some(),150
+ "direct scanout"151
+ );152
+ }153
+ let queue_began = std::time::Instant::now();154
+ match surface.drm.queue_frame(feedback) {155
+ Ok(()) => {156
+ surface.queued = true;157
+ surface.queued_at = now_ns();158
+ watch(&drm.handle, surface, crtc);159
+ lock.shown(&surface.output);160
+ captures.output_damaged(&surface.output);161
+ }162
+ Err(e) => {163
+ failed = true;164
+ tracing::warn!("queueing a frame failed: {e}");165
+ }166
+ }167
+ queue_ns = queue_began.elapsed().as_nanos() as u64;168
+ }169
+ Ok(result) => {170
+ drop(crate::frame::take_presentation_feedback(171
+ wm,172
+ lock,173
+ xwayland.as_ref().map_or(&[], |x| &x.unmanaged),174
+ &shown,175
+ &surface.output,176
+ &result.states,177
+ ));178
+ lock.shown(&surface.output);179
+ }180
+ Err(e) => {181
+ failed = true;182
+ tracing::warn!("rendering failed: {e}");183
+ }184
+ }185
+ let render_cpu_ns = thread_cpu_ns() - cpu_began;186
+ let render_ns = render_began.elapsed().as_nanos() as u64 - queue_ns;187
+ let took = began.elapsed().as_nanos() as u64;188
+ surface.budget.record(took);189
+ if surface.frame_cpu_ns.len() < MAX_CPU_SAMPLES {190
+ surface.frame_cpu_ns.push(thread_cpu_ns() - frame_cpu_began);191
+ }192
+ if took > surface.refresh_ns / 2 {193
+ tracing::debug!(194
+ output = surface.output.name(),195
+ took_ns = took,196
+ scene_ns,197
+ render_ns,198
+ render_cpu_ns,199
+ queue_ns,200
+ "slow frame"201
+ );202
+ }203
+ if !surface.queued {204
+ surface.target_ns = None;205
+ }206
+ surface.failures = if failed { surface.failures + 1 } else { 0 };207
+ surface.redraw = animating || failed;208
+ if !surface.queued {209
+ crate::fifo::release_idle(&core.handle, &surface.output);210
+ }211
+ let elapsed = core.start.elapsed();212
+ crate::frame::send_frame_callbacks(213
+ wm,214
+ lock,215
+ xwayland.as_ref().map_or(&[], |x| &x.unmanaged),216
+ &shown,217
+ &surface.output,218
+ elapsed,219
+ input.dnd_icon.as_ref(),220
+ );221
+ crate::frame::send_cursor_frames(wm, input, surface.id, &surface.output, elapsed);222
+ (animating || failed) && !surface.queued223
+}224
+225
+/// CPU time this thread has used, to tell a render that works from one that226
+/// waits or is descheduled.227
+pub(super) fn thread_cpu_ns() -> u64 {228
+ let t = smithay::reexports::rustix::time::clock_gettime(229
+ smithay::reexports::rustix::time::ClockId::ThreadCPUTime,230
+ );231
+ t.tv_sec as u64 * 1_000_000_000 + t.tv_nsec as u64232
+}@@ -0,0 +1,176 @@
1
+//! The libseat session, udev and libinput setup, and the session pause and2
+//! resume that follow.3
+4
+use super::Drm;5
+use super::input::on_input;6
+use super::outputs::scan;7
+use super::pacing::on_vblank;8
+use crate::backend::Backend;9
+use crate::devices::Devices;10
+use crate::state::State;11
+use smithay::backend::allocator::Fourcc;12
+use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice};13
+use smithay::backend::drm::compositor::FrameFlags;14
+use smithay::backend::drm::exporter::gbm::GbmFramebufferExporter;15
+use smithay::backend::drm::output::DrmOutputManager;16
+use smithay::backend::drm::{DrmDevice, DrmDeviceFd, DrmEvent, DrmNode, NodeType};17
+use smithay::backend::libinput::{LibinputInputBackend, LibinputSessionInterface};18
+use smithay::backend::session::libseat::LibSeatSession;19
+use smithay::backend::session::{Event as SessionEvent, Session};20
+use smithay::backend::udev::{UdevBackend, UdevEvent, all_gpus, primary_gpu};21
+use smithay::reexports::calloop::EventLoop;22
+use smithay::reexports::drm::Device as _;23
+use smithay::reexports::input::Libinput;24
+use smithay::reexports::rustix::fs::OFlags;25
+use smithay::utils::DeviceFd;26
+use smithay::wayland::drm_syncobj::{DrmSyncobjState, supports_syncobj_eventfd};27
+use smithay_drm_extras::drm_scanner::DrmScanner;28
+use std::collections::{HashMap, HashSet};29
+use std::path::{Path, PathBuf};30
+31
+/// The GPU monolith drives on bare metal, the seat's primary or else its32
+/// first.33
+pub fn primary() -> Result<PathBuf, Box<dyn std::error::Error>> {34
+ let seat = std::env::var("XDG_SEAT").unwrap_or_else(|_| "seat0".into());35
+ Ok(match primary_gpu(&seat)? {36
+ Some(p) => p,37
+ None => all_gpus(&seat)?.into_iter().next().ok_or("no GPU found")?,38
+ })39
+}40
+41
+pub fn init(42
+ event_loop: &mut EventLoop<'static, State>,43
+ state: &mut State,44
+ path: &Path,45
+) -> Result<(), Box<dyn std::error::Error>> {46
+ let (mut session, notifier) = LibSeatSession::new()?;47
+ let seat = session.seat();48
+ let node = DrmNode::from_path(path)?;49
+ tracing::info!(path = %path.display(), "primary GPU");50
+ let fd = session.open(51
+ path,52
+ OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK,53
+ )?;54
+ let fd = DrmDeviceFd::new(DeviceFd::from(fd));55
+ let (device, drm_events) = DrmDevice::new(fd.clone(), true)?;56
+ let nvidia = device57
+ .get_driver()58
+ .map(|d| d.name().to_string_lossy().to_lowercase().contains("nvidia"))59
+ .unwrap_or(false);60
+ let syncobj = if supports_syncobj_eventfd(&fd) {61
+ Some(DrmSyncobjState::new::<State>(62
+ &state.core.display,63
+ fd.clone(),64
+ ))65
+ } else {66
+ tracing::info!("no syncobj eventfd support, clients stay on implicit sync");67
+ None68
+ };69
+ let gbm = GbmDevice::new(fd)?;70
+ let render_node = node71
+ .node_with_type(NodeType::Render)72
+ .and_then(Result::ok)73
+ .unwrap_or(node);74
+ let allocator = GbmAllocator::new(75
+ gbm.clone(),76
+ GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,77
+ );78
+ let exporter = GbmFramebufferExporter::new(gbm.clone(), render_node.into());79
+ let render_formats: Vec<_> = state.renderer.render_formats().iter().copied().collect();80
+ let manager = DrmOutputManager::new(81
+ device,82
+ allocator,83
+ exporter,84
+ Some(gbm),85
+ [Fourcc::Xrgb8888, Fourcc::Argb8888],86
+ render_formats,87
+ );88
+89
+ let mut libinput =90
+ Libinput::new_with_udev::<LibinputSessionInterface<LibSeatSession>>(session.clone().into());91
+ libinput92
+ .udev_assign_seat(&seat)93
+ .map_err(|()| "libinput could not take the seat")?;94
+ let handle = event_loop.handle();95
+ handle.insert_source(96
+ LibinputInputBackend::new(libinput.clone()),97
+ |event, _, state| {98
+ on_input(state, event);99
+ },100
+ )?;101
+ handle.insert_source(notifier, |event, _, state| match event {102
+ SessionEvent::PauseSession => {103
+ if let Some(drm) = state.backend.drm_mut() {104
+ drm.libinput.suspend();105
+ drm.manager.pause();106
+ }107
+ }108
+ SessionEvent::ActivateSession => {109
+ if let Some(drm) = state.backend.drm_mut() {110
+ if drm.libinput.resume().is_err() {111
+ tracing::warn!("libinput did not resume");112
+ }113
+ if let Err(e) = drm.manager.lock().activate(false) {114
+ tracing::warn!("could not reactivate the GPU: {e}");115
+ }116
+ for s in drm.surfaces.values_mut() {117
+ if let Some(token) = s.scheduled.take() {118
+ drm.handle.remove(token);119
+ }120
+ if let Err(e) = s.drm.frame_submitted() {121
+ tracing::warn!("could not drop the flip left over from the pause: {e}");122
+ }123
+ s.queued = false;124
+ s.last_vblank_ns = None;125
+ s.target_ns = None;126
+ s.failures = 0;127
+ s.redraw = true;128
+ if !s.powered129
+ && let Err(e) = s.drm.with_compositor(|c| c.clear())130
+ {131
+ tracing::warn!("could not keep the output off: {e}");132
+ }133
+ }134
+ }135
+ scan(state);136
+ state.dirty.redraw = true;137
+ }138
+ })?;139
+ handle.insert_source(drm_events, |event, meta, state| match event {140
+ DrmEvent::VBlank(crtc) => on_vblank(state, crtc, meta),141
+ DrmEvent::Error(e) => tracing::warn!("drm: {e}"),142
+ })?;143
+ let udev = UdevBackend::new(&seat)?;144
+ handle.insert_source(udev, move |event, _, state| {145
+ if let UdevEvent::Changed { device_id } = event146
+ && state147
+ .backend148
+ .drm()149
+ .is_some_and(|d| d.node.dev_id() == device_id && d.session.is_active())150
+ {151
+ scan(state);152
+ }153
+ })?;154
+155
+ state.backend = Backend::Drm(Box::new(Drm {156
+ session,157
+ libinput,158
+ handle,159
+ node,160
+ render_node,161
+ syncobj,162
+ manager,163
+ scanner: DrmScanner::new(),164
+ nvidia,165
+ frame_flags: if std::env::var_os("MONOLITH_NO_SCANOUT").is_some() {166
+ FrameFlags::empty()167
+ } else {168
+ FrameFlags::DEFAULT169
+ },170
+ surfaces: HashMap::new(),171
+ failed: HashSet::new(),172
+ devices: Devices::default(),173
+ }));174
+ scan(state);175
+ Ok(())176
+}