1package wayland
2
3import (
4 "time"
5
6 "github.com/bnema/neferwl/internal/ports"
7 "github.com/bnema/purego-libwayland/protocol/presentationtime"
8 "github.com/bnema/purego-libwayland/protocol/wayland"
9 "github.com/bnema/purego-libwayland/server"
10 "golang.org/x/sys/unix"
11)
12
13// wp_presentation: a client learns when each commit reached the screen.
14// A feedback belongs to the commit it was requested before; on commit it
15// waits for the flip that shows its window's content (FlipInfo.Shows)
16// and gets that flip's kernel timestamp, vblank counter and refresh.
17// A content replaced before any flip showed it, a destroyed surface or a
18// surface on no output is discarded.
19
20const presentationVersion = 2
21
22// feedbackWait is a committed feedback: presented once a flip on the
23// surface's output shows content seq of window win.
24type feedbackWait struct {
25 fb *presentationtime.WpPresentationFeedback
26 surf *surface
27 win ports.WindowID
28 seq uint64
29 at time.Time
30 // replaced is the window content Seq of the surface's next commit (0:
31 // none yet): a flip showing it or later shows the replacement.
32 replaced uint64
33}
34
35func registerPresentation(d *server.Display, s *Server) error {
36 return presentationtime.NewWpPresentationGlobal(d, presentationVersion, func(c server.Client, v, id uint32) {
37 r, err := presentationtime.NewWpPresentation(c, int32(v), id, presentation{s})
38 if err == nil {
39 r.SendClockId(unix.CLOCK_MONOTONIC)
40 }
41 })
42}
43
44type presentation struct{ server *Server }
45
46func (presentation) Destroy(*presentationtime.WpPresentation) {}
47
48// Feedback attaches a feedback to the surface's next commit.
49func (p presentation) Feedback(r *presentationtime.WpPresentation, surf *wayland.Surface, id uint32) {
50 fb, err := presentationtime.NewWpPresentationFeedback(r.Client(), r.Version(), id, feedbackHandler{})
51 if err != nil {
52 return
53 }
54 state := p.server.surfaceOf(surf)
55 if state == nil || state.destroyed {
56 fb.SendDiscarded()
57 fb.Destroy()
58 return
59 }
60 state.pendingFeedback = append(state.pendingFeedback, fb)
61}
62
63type feedbackHandler struct{}
64
65// Destroy is not a request of wp_presentation_feedback; the handler
66// satisfies the generated interface.
67func (feedbackHandler) Destroy(*presentationtime.WpPresentationFeedback) {}
68
69// commitFeedback moves a surface's pending feedbacks to its window's
70// content just emitted, or discards them when the surface shows nowhere.
71// A commit with a new buffer (fresh) replaces the surface's earlier
72// waiting feedbacks.
73func (s *surface) commitFeedback(pending []*presentationtime.WpPresentationFeedback, fresh bool) {
74 srv := s.server
75 root := s.root()
76 win := root.windowID()
77 if win != 0 && fresh {
78 srv.contentMu.Lock()
79 seq := srv.contentSeq[win]
80 srv.contentMu.Unlock()
81 for i := range srv.feedbacks {
82 if w := &srv.feedbacks[i]; w.surf == s && w.replaced == 0 && seq > w.seq {
83 w.replaced = seq
84 }
85 }
86 }
87 if len(pending) == 0 {
88 return
89 }
90 output := srv.frameOutput(s)
91 if win == 0 || output == "" {
92 for _, fb := range pending {
93 discard(fb)
94 }
95 return
96 }
97 srv.contentMu.Lock()
98 seq := srv.contentSeq[win]
99 srv.contentMu.Unlock()
100 for _, fb := range pending {
101 srv.feedbacks = append(srv.feedbacks, feedbackWait{fb: fb, surf: s, win: win, seq: seq, at: time.Now()})
102 }
103}
104
105func discard(fb *presentationtime.WpPresentationFeedback) {
106 if fb.Resource.Alive() {
107 fb.SendDiscarded()
108 fb.Destroy()
109 }
110}
111
112// presentFlip answers the feedbacks a flip of output answers: a window
113// content at or after a feedback's is shown; the feedback is presented,
114// or discarded when its surface committed again by then.
115func (s *Server) presentFlip(output string, f *ports.FlipInfo) {
116 if f == nil || len(s.feedbacks) == 0 {
117 return
118 }
119 o := s.outputByNameExact(output)
120 kept := s.feedbacks[:0]
121 for _, w := range s.feedbacks {
122 shown, ok := f.Shows[w.win]
123 switch {
124 case !w.fb.Resource.Alive():
125 case w.surf.destroyed:
126 discard(w.fb)
127 case s.frameOutput(w.surf) != output || !ok || shown < w.seq:
128 kept = append(kept, w)
129 case f.Merged > 0:
130 // The reader fell behind: which flip showed it is unknown.
131 discard(w.fb)
132 default:
133 // Shown, unless the surface's next commit is shown too.
134 s.sendPresented(w, o, f, w.replaced == 0 || shown < w.replaced)
135 }
136 }
137 clear(s.feedbacks[len(kept):])
138 s.feedbacks = kept
139}
140
141// sendPresented sends presented for the flip; exact is false when a
142// later commit of the surface replaced it before the flip (then it is
143// discarded).
144func (s *Server) sendPresented(w feedbackWait, o *output, f *ports.FlipInfo, exact bool) {
145 if !exact {
146 discard(w.fb)
147 return
148 }
149 if o != nil {
150 for _, r := range o.resources {
151 if r.Resource.Alive() && r.Client() == w.fb.Client() {
152 w.fb.SendSyncOutput(r)
153 }
154 }
155 }
156 flags := uint32(0)
157 // A software clock (headless) claims nothing about the display.
158 if !f.Async && f.HardwareClock {
159 flags |= uint32(presentationtime.WpPresentationFeedbackKindVsync)
160 }
161 if f.HardwareClock {
162 flags |= uint32(presentationtime.WpPresentationFeedbackKindHwClock | presentationtime.WpPresentationFeedbackKindHwCompletion)
163 }
164 if f.ZeroCopy != 0 && f.ZeroCopy == w.win {
165 flags |= uint32(presentationtime.WpPresentationFeedbackKindZeroCopy)
166 }
167 sec, nsec := uint64(f.When/time.Second), uint32(f.When%time.Second)
168 w.fb.SendPresented(uint32(sec>>32), uint32(sec), nsec, uint32(f.Refresh), uint32(f.Seq>>32), uint32(f.Seq), flags)
169 w.fb.Destroy()
170}
171
172// feedbackTimeout discards a feedback no flip answered: its window is
173// hidden, on an output that does not flip (headless, switched away), or
174// was never drawn.
175const feedbackTimeout = time.Second
176
177// dropFeedbacks discards the feedbacks of destroyed surfaces, of outputs
178// that are gone, and those older than feedbackTimeout.
179func (s *Server) dropFeedbacks(now time.Time) {
180 kept := s.feedbacks[:0]
181 for _, w := range s.feedbacks {
182 if !w.fb.Resource.Alive() {
183 continue
184 }
185 if w.surf.destroyed || s.frameOutput(w.surf) == "" || now.Sub(w.at) > feedbackTimeout {
186 discard(w.fb)
187 continue
188 }
189 kept = append(kept, w)
190 }
191 clear(s.feedbacks[len(kept):])
192 s.feedbacks = kept
193}