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