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 o.fenceProp(req, o.primary, done)
28 }
29 err = o.k.commit(req, atomicTestOnly|flipAsyncFlag, 0)
30 // With CPU waits no fence is in a commit, async or not.
31 o.asyncFence = err == nil && (done != nil || o.cpuFences)
32 o.log.Info().Err(err).Str("connector", o.conn.name).Bool("async_fence", o.asyncFence).Msg("tearing probe")
33}
34
35// wantVRR is the VRR state the next frame commit sets: on while game (a
36// buffer is scanned out or a fullscreen window covers the output), off
37// after vrrHold without it (each toggle may flicker, so short breaks keep
38// it).
39func (o *Output) wantVRR(game bool) bool {
40 if o.vrrProp == 0 {
41 return false
42 }
43 o.vrrGame = game
44 if game {
45 o.composedSince = time.Time{}
46 return true
47 }
48 if o.composedSince.IsZero() {
49 o.composedSince = time.Now()
50 }
51 return o.vrrOn && time.Since(o.composedSince) <= vrrHold
52}
53
54// stateVRR is the VRR state of a commit without a new frame (cursor,
55// vrrOff timer): the last frame's. A shown buffer is not enough: it may
56// be a tiled window on the overlay plane.
57func (o *Output) stateVRR() bool { return o.wantVRR(o.vrrGame) }
58
59// contentProps changes the connector hint only when it differs from the applied value.
60func (o *Output) contentProps(req *atomicReq) {
61 if o.contentProp != 0 && o.contentWanted != o.contentValue {
62 req.set(o.conn.id, o.contentProp, o.contentWanted)
63 }
64}
65
66// wantContent chooses a content hint for the only visible fullscreen window.
67func (o *Output) wantContent(s ports.Scene, surfaces map[ports.WindowID]ports.SurfaceContent) {
68 o.contentWanted = o.contentValues[0]
69 var full *ports.SceneWindow
70 for i := range s.Windows {
71 w := &s.Windows[i]
72 if !s.Draws(*w) {
73 continue
74 }
75 if !w.Fullscreen || !covers(&s, w) || full != nil {
76 return
77 }
78 full = w
79 }
80 if full == nil {
81 return
82 }
83 switch surfaces[full.ID].ContentType {
84 case ports.ContentPhoto:
85 o.contentWanted = o.contentValues[2]
86 case ports.ContentVideo:
87 o.contentWanted = o.contentValues[3]
88 case ports.ContentGame:
89 o.contentWanted = o.contentValues[4]
90 }
91}
92
93// vrrHold is how long composition runs before VRR turns off.
94const vrrHold = 500 * time.Millisecond
95
96// setAsync logs when the output starts or stops tearing.
97func (o *Output) setAsync(on bool) {
98 if on != o.async {
99 o.async = on
100 o.log.Info().Bool("tearing", on).Str("connector", o.conn.name).Msg("tearing")
101 }
102}