atagen

monolith

public
Star0
Watch0
8e6f96f0a32d329d75ee602576de162d212a6169
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git
crates/monolith/src/drm.rsRust782 lines25.4 KB
1
//! Bare metal. A libseat session, udev for the primary GPU and its
2
//! connectors, libinput for input, and one smithay DRM output per connector,
3
//! each rendered when its own vblank lands.
4
5
use crate::scene::Draw;
6
use crate::state::{State, now_ns};
7
use monolith_config::Vrr;
8
use monolith_layout::{OutputId, Rect};
9
use smithay::backend::allocator::Fourcc;
10
use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice};
11
use smithay::backend::drm::VrrSupport;
12
use smithay::backend::drm::compositor::FrameFlags;
13
use smithay::backend::drm::exporter::gbm::GbmFramebufferExporter;
14
use smithay::backend::drm::output::{DrmOutput, DrmOutputManager, DrmOutputRenderElements};
15
use smithay::backend::drm::{
16
    DrmDevice, DrmDeviceFd, DrmEvent, DrmEventMetadata, DrmEventTime, DrmNode, NodeType,
17
};
18
use smithay::backend::input::{
19
    AbsolutePositionEvent, Axis, AxisSource, Event, InputEvent, KeyboardKeyEvent, PointerAxisEvent,
20
    PointerButtonEvent, PointerMotionEvent,
21
};
22
use smithay::backend::libinput::{LibinputInputBackend, LibinputSessionInterface};
23
use smithay::backend::session::libseat::LibSeatSession;
24
use smithay::backend::session::{Event as SessionEvent, Session};
25
use smithay::backend::udev::{UdevBackend, UdevEvent, all_gpus, primary_gpu};
26
use smithay::input::pointer::AxisFrame;
27
use smithay::output::{Mode, Output, PhysicalProperties, Scale, Subpixel};
28
use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
29
use smithay::reexports::calloop::{EventLoop, LoopHandle, RegistrationToken};
30
use smithay::reexports::drm::Device as _;
31
use smithay::reexports::drm::control::{ModeTypeFlags, connector, crtc};
32
use smithay::reexports::input::Libinput;
33
use smithay::reexports::rustix::fs::OFlags;
34
use smithay::utils::{DeviceFd, Point, Transform};
35
use smithay::wayland::drm_syncobj::{DrmSyncobjState, supports_syncobj_eventfd};
36
use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner};
37
use std::collections::{HashMap, VecDeque};
38
use std::path::{Path, PathBuf};
39
use std::time::Duration;
40
41
const CLEAR: [f32; 4] = [0.07, 0.075, 0.09, 1.0];
42
/// Frames that only move the cursor stay at or below 60 Hz under VRR, so the
43
/// mouse does not override the refresh rate a game is pacing.
44
const CURSOR_FRAME_NS: u64 = 16_666_667;
45
46
type Allocator = GbmAllocator<DrmDeviceFd>;
47
type Exporter = GbmFramebufferExporter<DrmDeviceFd>;
48
type Manager = DrmOutputManager<Allocator, Exporter, (), DrmDeviceFd>;
49
type Out = DrmOutput<Allocator, Exporter, (), DrmDeviceFd>;
50
51
pub struct Drm {
52
    pub session: LibSeatSession,
53
    libinput: Libinput,
54
    handle: LoopHandle<'static, State>,
55
    node: DrmNode,
56
    manager: Manager,
57
    scanner: DrmScanner,
58
    nvidia: bool,
59
    frame_flags: FrameFlags,
60
    surfaces: HashMap<crtc::Handle, Surface>,
61
}
62
63
struct Surface {
64
    output: Output,
65
    id: OutputId,
66
    drm: Out,
67
    /// A frame is on its way to the screen and its vblank has not landed.
68
    queued: bool,
69
    redraw: bool,
70
    last_vblank_ns: Option<u64>,
71
    refresh_ns: u64,
72
    /// The pending render, timed to start just in time for `target_ns`.
73
    scheduled: Option<RegistrationToken>,
74
    /// The vblank the frame being drawn or queued is meant for.
75
    target_ns: Option<u64>,
76
    budget: Budget,
77
    /// Frames presented since `stats_since`, for the once-a-second debug line.
78
    presented: u32,
79
    stats_since: u64,
80
    vrr_capable: bool,
81
    vrr_on: bool,
82
    /// Only the cursor moved, which under VRR is drawn at a capped rate.
83
    cursor_redraw: bool,
84
}
85
86
impl Surface {
87
    /// The first vblank a render starting at `now` can still make, and the
88
    /// latest moment to start it.
89
    fn next_deadline(&self, now: u64) -> Option<(u64, u64)> {
90
        let last = self.last_vblank_ns?;
91
        let refresh = self.refresh_ns;
92
        if refresh == 0 {
93
            return None;
94
        }
95
        let budget = self.budget.ns().min(refresh);
96
        let periods = (now + budget).saturating_sub(last).div_ceil(refresh).max(1);
97
        let vblank = last + periods * refresh;
98
        Some((vblank, vblank - budget))
99
    }
100
}
101
102
/// How long before a vblank rendering must start. A high percentile of
103
/// recent CPU render times covers the CPU side without one spike inflating
104
/// it, and a slack that grows on every missed vblank and shrinks over seconds
105
/// while frames land covers GPU time and the commit.
106
struct Budget {
107
    renders: VecDeque<u64>,
108
    slack_ns: u64,
109
    /// The slack that last proved enough after a miss. Decay stops there
110
    /// for `floor_frames` frames, so it does not keep rediscovering the same
111
    /// limit one dropped frame at a time.
112
    floor_ns: u64,
113
    floor_frames: u64,
114
}
115
116
impl Budget {
117
    const KEEP: usize = 32;
118
    const MIN_SLACK_NS: u64 = 1_000_000;
119
    const STEP_NS: u64 = 500_000;
120
    const DECAY_NS: u64 = 1_000;
121
    const FLOOR_NS: u64 = 30_000_000_000;
122
123
    fn new() -> Self {
124
        Self {
125
            renders: VecDeque::with_capacity(Self::KEEP),
126
            slack_ns: 2_000_000,
127
            floor_ns: 0,
128
            floor_frames: 0,
129
        }
130
    }
131
132
    fn ns(&self) -> u64 {
133
        let mut renders: Vec<u64> = self.renders.iter().copied().collect();
134
        renders.sort_unstable();
135
        let p90 = renders
136
            .get(renders.len().saturating_sub(1) * 9 / 10)
137
            .copied()
138
            .unwrap_or(0);
139
        p90 + self.slack_ns
140
    }
141
142
    fn record(&mut self, render_ns: u64) {
143
        if self.renders.len() == Self::KEEP {
144
            self.renders.pop_front();
145
        }
146
        self.renders.push_back(render_ns);
147
    }
148
149
    fn missed(&mut self, refresh_ns: u64) {
150
        self.slack_ns = (self.slack_ns + Self::STEP_NS).min(refresh_ns / 2);
151
        self.floor_ns = self.slack_ns;
152
        self.floor_frames = Self::FLOOR_NS / refresh_ns.max(1);
153
    }
154
155
    fn landed(&mut self) {
156
        self.floor_frames = self.floor_frames.saturating_sub(1);
157
        let floor = if self.floor_frames > 0 {
158
            self.floor_ns
159
        } else {
160
            Self::MIN_SLACK_NS
161
        };
162
        self.slack_ns = self.slack_ns.saturating_sub(Self::DECAY_NS).max(floor);
163
    }
164
}
165
166
/// The GPU monolith drives on bare metal, the seat's primary or else its
167
/// first.
168
pub fn primary() -> Result<PathBuf, Box<dyn std::error::Error>> {
169
    let seat = std::env::var("XDG_SEAT").unwrap_or_else(|_| "seat0".into());
170
    Ok(match primary_gpu(&seat)? {
171
        Some(p) => p,
172
        None => all_gpus(&seat)?.into_iter().next().ok_or("no GPU found")?,
173
    })
174
}
175
176
pub fn init(
177
    event_loop: &mut EventLoop<'static, State>,
178
    state: &mut State,
179
    path: &Path,
180
) -> Result<(), Box<dyn std::error::Error>> {
181
    let (mut session, notifier) = LibSeatSession::new()?;
182
    let seat = session.seat();
183
    let node = DrmNode::from_path(path)?;
184
    tracing::info!(path = %path.display(), "primary GPU");
185
    let fd = session.open(
186
        path,
187
        OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK,
188
    )?;
189
    let fd = DrmDeviceFd::new(DeviceFd::from(fd));
190
    let (device, drm_events) = DrmDevice::new(fd.clone(), true)?;
191
    let nvidia = device
192
        .get_driver()
193
        .map(|d| d.name().to_string_lossy().to_lowercase().contains("nvidia"))
194
        .unwrap_or(false);
195
    if supports_syncobj_eventfd(&fd) {
196
        state.syncobj = Some(DrmSyncobjState::new::<State>(&state.display, fd.clone()));
197
    } else {
198
        tracing::info!("no syncobj eventfd support, clients stay on implicit sync");
199
    }
200
    let gbm = GbmDevice::new(fd)?;
201
    let render_node = node
202
        .node_with_type(NodeType::Render)
203
        .and_then(Result::ok)
204
        .unwrap_or(node);
205
    let allocator = GbmAllocator::new(
206
        gbm.clone(),
207
        GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,
208
    );
209
    let exporter = GbmFramebufferExporter::new(gbm.clone(), render_node.into());
210
    let render_formats: Vec<_> = state.renderer.render_formats().iter().copied().collect();
211
    let manager = DrmOutputManager::new(
212
        device,
213
        allocator,
214
        exporter,
215
        Some(gbm),
216
        [Fourcc::Xrgb8888, Fourcc::Argb8888],
217
        render_formats,
218
    );
219
220
    let mut libinput =
221
        Libinput::new_with_udev::<LibinputSessionInterface<LibSeatSession>>(session.clone().into());
222
    libinput
223
        .udev_assign_seat(&seat)
224
        .map_err(|()| "libinput could not take the seat")?;
225
    let handle = event_loop.handle();
226
    handle.insert_source(
227
        LibinputInputBackend::new(libinput.clone()),
228
        |event, _, state| {
229
            on_input(state, event);
230
        },
231
    )?;
232
    handle.insert_source(notifier, |event, _, state| match event {
233
        SessionEvent::PauseSession => {
234
            if let Some(drm) = state.drm.as_mut() {
235
                drm.libinput.suspend();
236
                drm.manager.pause();
237
            }
238
        }
239
        SessionEvent::ActivateSession => {
240
            if let Some(drm) = state.drm.as_mut() {
241
                if drm.libinput.resume().is_err() {
242
                    tracing::warn!("libinput did not resume");
243
                }
244
                if let Err(e) = drm.manager.lock().activate(false) {
245
                    tracing::warn!("could not reactivate the GPU: {e}");
246
                }
247
                for s in drm.surfaces.values_mut() {
248
                    s.queued = false;
249
                    s.redraw = true;
250
                }
251
            }
252
            state.needs_redraw = true;
253
        }
254
    })?;
255
    handle.insert_source(drm_events, |event, meta, state| match event {
256
        DrmEvent::VBlank(crtc) => on_vblank(state, crtc, meta),
257
        DrmEvent::Error(e) => tracing::warn!("drm: {e}"),
258
    })?;
259
    let udev = UdevBackend::new(&seat)?;
260
    handle.insert_source(udev, move |event, _, state| {
261
        if let UdevEvent::Changed { device_id } = event
262
            && state
263
                .drm
264
                .as_ref()
265
                .is_some_and(|d| d.node.dev_id() == device_id)
266
        {
267
            scan(state);
268
        }
269
    })?;
270
271
    state.drm = Some(Drm {
272
        session,
273
        libinput,
274
        handle,
275
        node,
276
        manager,
277
        scanner: DrmScanner::new(),
278
        nvidia,
279
        frame_flags: if std::env::var_os("MONOLITH_NO_SCANOUT").is_some() {
280
            FrameFlags::empty()
281
        } else {
282
            FrameFlags::DEFAULT
283
        },
284
        surfaces: HashMap::new(),
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.drm.as_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
    for event in events {
303
        match event {
304
            DrmScanEvent::Connected {
305
                connector,
306
                crtc: Some(crtc),
307
            } => connect(state, connector, crtc),
308
            DrmScanEvent::Disconnected {
309
                crtc: Some(crtc), ..
310
            } => disconnect(state, crtc),
311
            _ => {}
312
        }
313
    }
314
    state.apply_layout();
315
}
316
317
fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
318
    let name = format!(
319
        "{}-{}",
320
        connector.interface().as_str(),
321
        connector.interface_id()
322
    );
323
    let Some(&mode) = connector
324
        .modes()
325
        .iter()
326
        .find(|m| m.mode_type().contains(ModeTypeFlags::PREFERRED))
327
        .or_else(|| connector.modes().first())
328
    else {
329
        tracing::warn!(name, "connector has no modes");
330
        return;
331
    };
332
    let State {
333
        drm: Some(drm),
334
        renderer,
335
        layout,
336
        space,
337
        display,
338
        ..
339
    } = state
340
    else {
341
        return;
342
    };
343
    let (mm_w, mm_h) = connector.size().unwrap_or((0, 0));
344
    let output = Output::new(
345
        name.clone(),
346
        PhysicalProperties {
347
            size: (mm_w as i32, mm_h as i32).into(),
348
            subpixel: Subpixel::Unknown,
349
            make: "Unknown".into(),
350
            model: name.clone(),
351
            serial_number: String::new(),
352
        },
353
    );
354
    let wl_mode = Mode::from(mode);
355
    let x = layout
356
        .outputs()
357
        .map(|(_, o)| o.area.right())
358
        .fold(0.0, f64::max);
359
    let position = Point::from((x as i32, 0));
360
    output.change_current_state(
361
        Some(wl_mode),
362
        Some(Transform::Normal),
363
        Some(Scale::Integer(1)),
364
        Some(position),
365
    );
366
    output.set_preferred(wl_mode);
367
    output.create_global::<State>(display);
368
    let mut planes = drm.manager.device().planes(&crtc).ok();
369
    if drm.nvidia
370
        && let Some(p) = planes.as_mut()
371
    {
372
        // NVIDIA's overlay planes break composition, as anvil also found.
373
        p.overlay.clear();
374
    }
375
    let out = match drm.manager.lock().initialize_output::<_, Draw>(
376
        crtc,
377
        mode,
378
        &[connector.handle()],
379
        &output,
380
        planes,
381
        renderer,
382
        &DrmOutputRenderElements::default(),
383
    ) {
384
        Ok(out) => out,
385
        Err(e) => {
386
            tracing::warn!(name, "could not light up the output: {e}");
387
            return;
388
        }
389
    };
390
    let vrr_capable = matches!(
391
        out.with_compositor(|c| c.vrr_supported(connector.handle())),
392
        Ok(VrrSupport::Supported)
393
    );
394
    space.map_output(&output, position);
395
    let (w, h) = mode.size();
396
    let area = Rect::new(x, 0.0, f64::from(w), f64::from(h));
397
    let id = layout.add_output(&name, area);
398
    tracing::info!(
399
        name,
400
        w,
401
        h,
402
        refresh = mode.vrefresh(),
403
        vrr_capable,
404
        "output connected"
405
    );
406
    drm.surfaces.insert(
407
        crtc,
408
        Surface {
409
            output,
410
            id,
411
            drm: out,
412
            queued: false,
413
            redraw: true,
414
            last_vblank_ns: None,
415
            scheduled: None,
416
            target_ns: None,
417
            budget: Budget::new(),
418
            presented: 0,
419
            stats_since: 0,
420
            vrr_capable,
421
            vrr_on: false,
422
            cursor_redraw: false,
423
            refresh_ns: 1_000_000_000_000 / u64::from(wl_mode.refresh.max(1) as u32),
424
        },
425
    );
426
}
427
428
fn disconnect(state: &mut State, crtc: crtc::Handle) {
429
    let Some(drm) = state.drm.as_mut() else {
430
        return;
431
    };
432
    let Some(surface) = drm.surfaces.remove(&crtc) else {
433
        return;
434
    };
435
    if let Some(token) = surface.scheduled {
436
        drm.handle.remove(token);
437
    }
438
    tracing::info!(name = surface.output.name(), "output disconnected");
439
    state.space.unmap_output(&surface.output);
440
    state.layout.remove_output(surface.id);
441
}
442
443
/// The smithay output for a layout output, for protocols that need one.
444
pub fn output_for(drm: &Drm, id: OutputId) -> Option<Output> {
445
    drm.surfaces
446
        .values()
447
        .find(|s| s.id == id)
448
        .map(|s| s.output.clone())
449
}
450
451
fn on_vblank(state: &mut State, crtc: crtc::Handle, meta: &mut Option<DrmEventMetadata>) {
452
    let Some(surface) = state.drm.as_mut().and_then(|d| d.surfaces.get_mut(&crtc)) else {
453
        return;
454
    };
455
    if let Err(e) = surface.drm.frame_submitted() {
456
        tracing::warn!("frame was not submitted: {e}");
457
    }
458
    surface.queued = false;
459
    if let Some(DrmEventTime::Monotonic(t)) = meta.as_ref().map(|m| m.time) {
460
        let t = t.as_nanos() as u64;
461
        if let Some(target) = surface.target_ns.take() {
462
            if t > target + surface.refresh_ns / 2 {
463
                surface.budget.missed(surface.refresh_ns);
464
                tracing::debug!(
465
                    output = surface.output.name(),
466
                    late_ns = t - target,
467
                    budget_ns = surface.budget.ns(),
468
                    "frame missed its vblank"
469
                );
470
            } else {
471
                surface.budget.landed();
472
            }
473
        }
474
        surface.last_vblank_ns = Some(t);
475
        surface.presented += 1;
476
        let window = t.saturating_sub(surface.stats_since);
477
        if window >= 1_000_000_000 {
478
            let vblanks = window / surface.refresh_ns.max(1);
479
            tracing::debug!(
480
                output = surface.output.name(),
481
                presented = surface.presented,
482
                vblanks,
483
                budget_us = surface.budget.ns() / 1000,
484
                "frame rate"
485
            );
486
            surface.presented = 0;
487
            surface.stats_since = t;
488
        }
489
    }
490
    if surface.redraw || surface.cursor_redraw {
491
        schedule(state, crtc);
492
    }
493
}
494
495
/// Starts a render at the last moment that still makes the next reachable
496
/// vblank, so commits arriving until then make it into the frame.
497
fn schedule(state: &mut State, crtc: crtc::Handle) {
498
    let Some(drm) = state.drm.as_mut() else {
499
        return;
500
    };
501
    let Some(surface) = drm.surfaces.get_mut(&crtc) else {
502
        return;
503
    };
504
    if surface.queued || surface.scheduled.is_some() {
505
        return;
506
    }
507
    let now = now_ns();
508
    if surface.vrr_on {
509
        let since_flip = surface
510
            .last_vblank_ns
511
            .map_or(u64::MAX, |l| now.saturating_sub(l));
512
        if surface.redraw || since_flip >= CURSOR_FRAME_NS {
513
            draw(state, crtc, now);
514
            return;
515
        }
516
        let wait = Timer::from_duration(Duration::from_nanos(CURSOR_FRAME_NS - since_flip));
517
        let token = drm.handle.insert_source(wait, move |_, _, state| {
518
            if let Some(s) = state.drm.as_mut().and_then(|d| d.surfaces.get_mut(&crtc)) {
519
                s.scheduled = None;
520
            }
521
            draw(state, crtc, now_ns());
522
            TimeoutAction::Drop
523
        });
524
        surface.scheduled = token.ok();
525
        return;
526
    }
527
    let Some((vblank, start)) = surface.next_deadline(now) else {
528
        draw(state, crtc, now);
529
        return;
530
    };
531
    surface.target_ns = Some(vblank);
532
    let wait = Timer::from_duration(Duration::from_nanos(start.saturating_sub(now)));
533
    let token = drm.handle.insert_source(wait, move |_, _, state| {
534
        let target = state.drm.as_mut().and_then(|d| {
535
            let s = d.surfaces.get_mut(&crtc)?;
536
            s.scheduled = None;
537
            s.target_ns
538
        });
539
        if let Some(target) = target {
540
            draw(state, crtc, target);
541
        }
542
        TimeoutAction::Drop
543
    });
544
    surface.scheduled = token.ok();
545
}
546
547
fn draw(state: &mut State, crtc: crtc::Handle, target: u64) {
548
    if render_surface(state, crtc, target) {
549
        schedule(state, crtc);
550
    }
551
}
552
553
/// Renders every output that has something new and no frame in flight.
554
pub fn render(state: &mut State) {
555
    let Some(drm) = state.drm.as_mut() else {
556
        return;
557
    };
558
    let cursor = std::mem::take(&mut state.cursor_moved);
559
    let all = std::mem::take(&mut state.needs_redraw);
560
    for s in drm.surfaces.values_mut() {
561
        s.redraw |= all;
562
        s.cursor_redraw |= cursor;
563
    }
564
    let ready: Vec<crtc::Handle> = drm
565
        .surfaces
566
        .iter()
567
        .filter(|(_, s)| (s.redraw || s.cursor_redraw) && !s.queued)
568
        .map(|(c, _)| *c)
569
        .collect();
570
    for crtc in ready {
571
        schedule(state, crtc);
572
    }
573
}
574
575
/// Draws one output for the vblank at `target`. Returns whether it still
576
/// animates without a frame in flight, and so needs another render.
577
#[profiling::function]
578
fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
579
    let State {
580
        drm: Some(drm),
581
        renderer,
582
        layout,
583
        windows,
584
        anim,
585
        borders,
586
        config,
587
        space,
588
        start,
589
        cursor,
590
        cursor_status,
591
        pointer_pos,
592
        ..
593
    } = state
594
    else {
595
        return false;
596
    };
597
    if !drm.session.is_active() {
598
        return false;
599
    }
600
    let Some(surface) = drm.surfaces.get_mut(&crtc) else {
601
        return false;
602
    };
603
    if surface.queued {
604
        return false;
605
    }
606
    surface.cursor_redraw = false;
607
    let want_vrr = surface.vrr_capable
608
        && match config.output.vrr {
609
            Vrr::Off => false,
610
            Vrr::On => true,
611
            Vrr::Fullscreen => layout.arrange(surface.id).fullscreen.is_some(),
612
        };
613
    if want_vrr != surface.vrr_on {
614
        match surface.drm.with_compositor(|c| c.use_vrr(want_vrr)) {
615
            Ok(()) => {
616
                surface.vrr_on = want_vrr;
617
                tracing::info!(
618
                    output = surface.output.name(),
619
                    vrr = want_vrr,
620
                    "adaptive sync"
621
                );
622
            }
623
            Err(e) => {
624
                tracing::warn!("could not switch adaptive sync: {e}");
625
                surface.vrr_capable = false;
626
            }
627
        }
628
    }
629
    let began = std::time::Instant::now();
630
    let now = target;
631
    let Some(area) = layout.output(surface.id).map(|o| o.area) else {
632
        return false;
633
    };
634
    let mut elements = Vec::new();
635
    let local = *pointer_pos - Point::from((area.x, area.y));
636
    if local.x >= 0.0 && local.y >= 0.0 && local.x < area.w && local.y < area.h {
637
        elements.extend(cursor.elements(renderer, cursor_status, local));
638
    }
639
    let scene_began = std::time::Instant::now();
640
    elements.extend(crate::scene::build(
641
        layout,
642
        surface.id,
643
        windows,
644
        anim,
645
        renderer,
646
        now,
647
        &config.decoration,
648
        borders,
649
    ));
650
    let scene_ns = scene_began.elapsed().as_nanos() as u64;
651
    let animating = anim.active(now);
652
    anim.prune(now);
653
    let render_began = std::time::Instant::now();
654
    let cpu_began = thread_cpu_ns();
655
    let mut queue_ns = 0;
656
    match surface
657
        .drm
658
        .render_frame(renderer, &elements, CLEAR, drm.frame_flags)
659
    {
660
        Ok(result) if !result.is_empty => {
661
            let queue_began = std::time::Instant::now();
662
            match surface.drm.queue_frame(()) {
663
                Ok(()) => surface.queued = true,
664
                Err(e) => tracing::warn!("queueing a frame failed: {e}"),
665
            }
666
            queue_ns = queue_began.elapsed().as_nanos() as u64;
667
        }
668
        Ok(_) => {}
669
        Err(e) => tracing::warn!("rendering failed: {e}"),
670
    }
671
    let render_cpu_ns = thread_cpu_ns() - cpu_began;
672
    let render_ns = render_began.elapsed().as_nanos() as u64 - queue_ns;
673
    let took = began.elapsed().as_nanos() as u64;
674
    surface.budget.record(took);
675
    if took > surface.refresh_ns / 2 {
676
        tracing::debug!(
677
            output = surface.output.name(),
678
            took_ns = took,
679
            scene_ns,
680
            render_ns,
681
            render_cpu_ns,
682
            queue_ns,
683
            "slow frame"
684
        );
685
    }
686
    if !surface.queued {
687
        surface.target_ns = None;
688
    }
689
    surface.redraw = animating;
690
    let elapsed = start.elapsed();
691
    let output = surface.output.clone();
692
    for window in space.elements_for_output(&output) {
693
        window.send_frame(&output, elapsed, Some(Duration::ZERO), |_, _| {
694
            Some(output.clone())
695
        });
696
    }
697
    animating && !surface.queued
698
}
699
700
/// CPU time this thread has used, to tell a render that works from one that
701
/// waits or is descheduled.
702
fn thread_cpu_ns() -> u64 {
703
    let t = smithay::reexports::rustix::time::clock_gettime(
704
        smithay::reexports::rustix::time::ClockId::ThreadCPUTime,
705
    );
706
    t.tv_sec as u64 * 1_000_000_000 + t.tv_nsec as u64
707
}
708
709
fn on_input(state: &mut State, event: InputEvent<LibinputInputBackend>) {
710
    match event {
711
        InputEvent::Keyboard { event } => {
712
            let time = event.time();
713
            state.key(event.key_code(), event.state(), time);
714
        }
715
        InputEvent::PointerMotion { event } => {
716
            let pos = clamp(state, state.pointer_pos + event.delta());
717
            state.pointer_to(pos, event.time());
718
            state.pointer_frame();
719
            state.cursor_moved = true;
720
        }
721
        InputEvent::PointerMotionAbsolute { event } => {
722
            let Some(area) = state.layout.outputs().next().map(|(_, o)| o.area) else {
723
                return;
724
            };
725
            let p = event.position_transformed((area.w as i32, area.h as i32).into());
726
            let pos = Point::from((area.x + p.x, area.y + p.y));
727
            state.pointer_to(pos, event.time());
728
            state.pointer_frame();
729
            state.cursor_moved = true;
730
        }
731
        InputEvent::PointerButton { event } => {
732
            let time = event.time();
733
            state.pointer_button(event.button_code(), event.state(), time);
734
            state.pointer_frame();
735
        }
736
        InputEvent::PointerAxis { event } => {
737
            let source = event.source();
738
            let mut frame = AxisFrame::new(event.time()).source(source);
739
            for axis in [Axis::Horizontal, Axis::Vertical] {
740
                if let Some(v120) = event.amount_v120(axis) {
741
                    frame = frame.v120(axis, v120 as i32);
742
                }
743
                let amount = event
744
                    .amount(axis)
745
                    .or_else(|| event.amount_v120(axis).map(|v| v * 15.0 / 120.0));
746
                match amount {
747
                    Some(a) if a != 0.0 => frame = frame.value(axis, a),
748
                    Some(_) if source == AxisSource::Finger => frame = frame.stop(axis),
749
                    _ => {}
750
                }
751
            }
752
            state.pointer_axis(frame);
753
            state.pointer_frame();
754
        }
755
        _ => {}
756
    }
757
}
758
759
/// Keeps the pointer on some output, sliding along the edge it hit.
760
fn clamp(
761
    state: &State,
762
    pos: Point<f64, smithay::utils::Logical>,
763
) -> Point<f64, smithay::utils::Logical> {
764
    let inside = |a: &Rect, p: Point<f64, smithay::utils::Logical>| {
765
        p.x >= a.x && p.y >= a.y && p.x < a.right() && p.y < a.bottom()
766
    };
767
    let areas: Vec<Rect> = state.layout.outputs().map(|(_, o)| o.area).collect();
768
    if areas.iter().any(|a| inside(a, pos)) {
769
        return pos;
770
    }
771
    let Some(a) = areas
772
        .iter()
773
        .find(|a| inside(a, state.pointer_pos))
774
        .or(areas.first())
775
    else {
776
        return pos;
777
    };
778
    Point::from((
779
        pos.x.clamp(a.x, a.right() - 1.0),
780
        pos.y.clamp(a.y, a.bottom() - 1.0),
781
    ))
782
}