state_schedule.go RawHistoryFolder
1package drm
2
3import "time"
4
5// State commit scheduling: a CRTC takes one atomic commit per vblank, so a
6// commit carrying only state (cursor move, VRR toggle, content type) holds
7// the next frame until it lands. Committed as soon as the pointer moves, it
8// pushes a video frame that arrives later in the same refresh period to the
9// following vblank: a visible stutter on every cursor move over the output
10// or across to another one.
11//
12// So a state change waits until stateLead before the next vblank. A frame
13// committed before then carries it (one commit, no lost vblank); otherwise
14// the state commits alone and still lands on that vblank, so the cursor is
15// never later than it would have been.
16//
17// Under VRR there is no fixed vblank to aim at: state commits at once (a
18// game's cursor rides on its frames, see cursorWaits).
19
20// stateLeadMax is how long before the vblank a deferred state commit is
21// made: enough for the kernel to program the planes for that vblank.
22const stateLeadMax = 1500 * time.Microsecond
23
24// vblankStale is how old the last vblank may be to predict the next one: a
25// longer idle period lets the nominal period drift from the panel's.
26const vblankStale = 250 * time.Millisecond
27
28// stateLead is stateLeadMax, at most a quarter of period.
29func stateLead(period time.Duration) time.Duration { return min(stateLeadMax, period/4) }
30
31// stateDelay is how long a state change made at now (CLOCK_MONOTONIC)
32// waits before its own commit: until stateLead before the next vblank, 0
33// when that point has passed or no vblank can be predicted (VRR, no recent
34// flip).
35func (o *Output) stateDelay(now time.Duration) time.Duration {
36 period := o.refreshPeriod()
37 last := o.lastVblankAt
38 if period <= 0 || last <= 0 || now < last || now-last > vblankStale {
39 return 0
40 }
41 next := last + ((now-last)/period+1)*period
42 return max(0, next-stateLead(period)-now)
43}
44
45// stateChanged reports whether a state commit would change anything:
46// cursor, VRR or content type differ from what KMS holds.
47func (o *Output) stateChanged(vrr bool) bool {
48 if o.cursor != nil && o.cursor.desired() != o.cursor.applied {
49 return true
50 }
51 if vrr != o.vrrOn {
52 return true
53 }
54 return o.contentProp != 0 && o.contentWanted != o.contentValue
55}
56
57// stateDue reports whether the state commit may go now. The first call
58// for a change fixes its time (stateAt) and later calls keep it, so a
59// timer that fires late never pushes the commit by another period.
60func (o *Output) stateDue(now time.Time, mono time.Duration) bool {
61 if o.stateAt.IsZero() {
62 o.stateAt = now.Add(o.stateDelay(mono))
63 }
64 if now.Before(o.stateAt) {
65 return false
66 }
67 o.stateAt = time.Time{}
68 return true
69}
70
71// noteVblank records the vblank of a completed vsynced commit (frame or
72// state). A tearing flip's timestamp is not a vblank.
73func (o *Output) noteVblank(f pendingFrame, when time.Duration) {
74 if f.async || when <= 0 {
75 return
76 }
77 o.lastVblankAt = when
78}