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