1package drm
2
3import "time"
4
5// flipStats are the flip timing counters of one output, reported and
6// reset by the periodic stats entry. They are plain fields owned by the
7// output goroutine. All times are CLOCK_MONOTONIC (kernel flip
8// timestamps and userspace reads), so they tell a late composition from a
9// late read of the event (see accountFlip).
10type flipStats struct {
11 // missedVblanks counts due frame flips whose kernel timestamp is over
12 // 1.5 refresh periods after the previous frame flip (never under VRR,
13 // where the period is not fixed).
14 missedVblanks int
15 // maxInterval is the longest time between two frame flips, of the
16 // frames that were due.
17 maxInterval time.Duration
18 // maxCommitDelay is the longest time a due frame waited between the
19 // moment it was due (wanted, the previous flip landed, or the VRR
20 // flip gap ended) and its commit: late event read, slow composition.
21 maxCommitDelay time.Duration
22 // maxFlipToRead is the longest time between a flip's kernel timestamp
23 // and the output reading its event: the event delivery latency.
24 maxFlipToRead time.Duration
25 // lateFences counts missed vblanks whose fences signalled after the
26 // vblank the frame targeted (never under VRR). Fence times are only
27 // read while flip tracing is on.
28 lateFences int
29 // stateCommits counts state-only commits (cursor, VRR, content type);
30 // stateCarried counts deferred state changes a frame carried instead.
31 // Many state commits while a video plays point at a lost vblank each.
32 stateCommits, stateCarried int
33 // fenceTimeouts counts commits whose CPU fence wait hit its limit
34 // (fencegate.go).
35 fenceTimeouts int
36}
37
38// nominalPeriod is the time between two vblanks at the mode's refresh
39// rate. Under VRR it is the shortest period: the fastest the output
40// flips.
41func (o *Output) nominalPeriod() time.Duration {
42 return time.Duration(int64(time.Second) * 1000 / int64(max(1, o.mode.refreshMilli())))
43}
44
45// refreshPeriod is the time between two vblanks of the current mode (0:
46// not fixed, VRR).
47func (o *Output) refreshPeriod() time.Duration {
48 if o.vrrOn {
49 return 0
50 }
51 return o.nominalPeriod()
52}
53
54// setVRR records the VRR state. The flip interval chain restarts when it
55// changes: the period of the two states differs.
56func (o *Output) setVRR(on bool) {
57 if o.vrrOn != on {
58 o.lastFlipAt = 0
59 }
60 o.vrrOn = on
61}
62
63// accountFlip counts the completion ev of a frame commit made at
64// commitAt for a frame due at wantedAt (0: unknown, the commit time; see
65// pendingFrame.dueAt). All times are CLOCK_MONOTONIC. fenceAt is when the
66// commit's fences signalled (0: none or unknown). It updates lastFlipAt
67// for frame flips and never allocates.
68//
69// An output renders only on change, so a gap after an idle period says
70// nothing about missed vblanks. The interval to the previous flip counts
71// only for a frame that was due: wanted less than one period after the
72// previous flip, so it was meant for the very next vblank, whenever it was
73// committed. A frame that waited for the previous flip's event or for its
74// composition is due as soon as it is wanted. Then:
75//
76// - a flip over 1.5 periods after the previous one missed a vblank
77// (never counted under VRR, where the period is not fixed);
78// - if it missed one and its fences signalled after the vblank it
79// targeted (the previous flip plus one period), the frame was late: a
80// late fence. A fence before that vblank is not;
81// - the commit delay is the time from when the frame was due (wanted,
82// and not before the previous flip) to its commit. A missed vblank
83// with a long commit delay and no late fence points at the commit
84// path: late event read, slow composition on the CPU.
85func (o *Output) accountFlip(ev flipEvent, frame bool, wantedAt, commitAt, fenceAt time.Duration) {
86 if !frame {
87 return
88 }
89 if wantedAt == 0 {
90 wantedAt = commitAt
91 }
92 if prev := o.lastFlipAt; prev != 0 && wantedAt-prev < o.nominalPeriod() {
93 s := &o.flipStats
94 interval := ev.when - prev
95 s.maxInterval = max(s.maxInterval, interval)
96 s.maxCommitDelay = max(s.maxCommitDelay, commitAt-max(wantedAt, prev))
97 if period := o.refreshPeriod(); period != 0 && 2*interval > 3*period {
98 s.missedVblanks++
99 if fenceAt != 0 && fenceAt > prev+period {
100 s.lateFences++
101 }
102 }
103 }
104 o.lastFlipAt = ev.when
105}
106
107// accountRead tracks the delivery latency of the event ev handled at now.
108func (o *Output) accountRead(ev flipEvent, now time.Duration) {
109 o.flipStats.maxFlipToRead = max(o.flipStats.maxFlipToRead, now-ev.when)
110}
111
112// takeFlipStats returns the counters and starts over.
113func (o *Output) takeFlipStats() flipStats {
114 s := o.flipStats
115 o.flipStats = flipStats{}
116 return s
117}