vrr.go RawHistoryFolder
1package drm
2
3import (
4 "time"
5
6 "github.com/bnema/neferwl/internal/ports"
7)
8
9// Tearing, variable refresh and content type of frame commits.
10
11// probeAsync checks once whether async commits may carry IN_FENCE_FD. An
12// inactive CRTC refuses async flips: the probe waits until it is on.
13func (o *Output) probeAsync(r ports.Renderer) {
14 if o.asyncProbed || !o.tearing || o.off {
15 return
16 }
17 o.asyncProbed = true
18 r.UseTarget(o.back)
19 done, err := r.Render(ports.Scene{Background: "#000000"}, nil)
20 if err != nil {
21 return
22 }
23 req := &atomicReq{}
24 req.set(o.primary.id, o.primary.prop("FB_ID"), uint64(o.fbs[o.back]))
25 if done != nil {
26 defer done.Close()
27 req.set(o.primary.id, o.primary.prop("IN_FENCE_FD"), uint64(done.Fd()))
28 }
29 err = o.k.commit(req, atomicTestOnly|flipAsyncFlag, 0)
30 o.asyncFence = err == nil && done != nil
31 o.log.Info().Err(err).Str("connector", o.conn.name).Bool("async_fence", o.asyncFence).Msg("tearing probe")
32}
33
34// wantVRR is the VRR state the next frame commit sets: on while game (a
35// buffer is scanned out or a fullscreen window covers the output), off
36// after vrrHold without it (each toggle may flicker, so short breaks keep
37// it).
38func (o *Output) wantVRR(game bool) bool {
39 if o.vrrProp == 0 {
40 return false
41 }
42 o.vrrGame = game
43 if game {
44 o.composedSince = time.Time{}
45 return true
46 }
47 if o.composedSince.IsZero() {
48 o.composedSince = time.Now()
49 }
50 return o.vrrOn && time.Since(o.composedSince) <= vrrHold
51}
52
53// stateVRR is the VRR state of a commit without a new frame (cursor,
54// vrrOff timer): the last frame's. A shown buffer is not enough: it may
55// be a tiled window on the overlay plane.
56func (o *Output) stateVRR() bool { return o.wantVRR(o.vrrGame) }
57
58// contentProps changes the connector hint only when it differs from the applied value.
59func (o *Output) contentProps(req *atomicReq) {
60 if o.contentProp != 0 && o.contentWanted != o.contentValue {
61 req.set(o.conn.id, o.contentProp, o.contentWanted)
62 }
63}
64
65// wantContent chooses a content hint for the only visible fullscreen window.
66func (o *Output) wantContent(s ports.Scene, surfaces map[ports.WindowID]ports.SurfaceContent) {
67 o.contentWanted = o.contentValues[0]
68 var full *ports.SceneWindow
69 for i := range s.Windows {
70 w := &s.Windows[i]
71 if !s.Draws(*w) {
72 continue
73 }
74 if !w.Fullscreen || !covers(&s, w) || full != nil {
75 return
76 }
77 full = w
78 }
79 if full == nil {
80 return
81 }
82 switch surfaces[full.ID].ContentType {
83 case ports.ContentPhoto:
84 o.contentWanted = o.contentValues[2]
85 case ports.ContentVideo:
86 o.contentWanted = o.contentValues[3]
87 case ports.ContentGame:
88 o.contentWanted = o.contentValues[4]
89 }
90}
91
92// vrrHold is how long composition runs before VRR turns off.
93const vrrHold = 500 * time.Millisecond
94
95// setAsync logs when the output starts or stops tearing.
96func (o *Output) setAsync(on bool) {
97 if on != o.async {
98 o.async = on
99 o.log.Info().Bool("tearing", on).Str("connector", o.conn.name).Msg("tearing")
100 }
101}