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 seq := srv.contentSeq[win]
85 for i := range srv.feedbacks {
86 if w := &srv.feedbacks[i]; w.surf == s && w.replaced == 0 && seq > w.seq {
87 w.replaced = seq
88 }
89 }
90 }
91 if len(pending) == 0 {
92 return
93 }
94 output := srv.frameOutput(s)
95 // A tree only a capture session draws gets no presentation feedback: no
96 // display presents it.
97 if win == 0 || output == "" || output == suspendedFrameQueue || srv.invisible(s) {
98 for _, fb := range pending {
99 discard(fb)
100 }
101 return
102 }
103 seq := srv.contentSeq[win]
104 for _, fb := range pending {
105 srv.feedbacks = append(srv.feedbacks, feedbackWait{fb: fb, surf: s, win: win, seq: seq, at: time.Now()})
106 }
107 srv.wakePacer() // an unanswered feedback times out: the pacer must keep ticking
108}
109
110func discard(fb *presentationtime.WpPresentationFeedback) {
111 if fb.Resource.Alive() {
112 fb.SendDiscarded()
113 fb.Destroy()
114 }
115}
116
117// presentFlip answers the feedbacks a flip of output answers: a window
118// content at or after a feedback's is shown; the feedback is presented,
119// or discarded when its surface committed again by then.
120func (s *Server) presentFlip(output string, f *ports.FlipInfo) {
121 if f == nil || len(s.feedbacks) == 0 {
122 return
123 }
124 o := s.outputByNameExact(output)
125 kept := s.feedbacks[:0]
126 for _, w := range s.feedbacks {
127 shown, ok := f.Shows[w.win]
128 switch {
129 case !w.fb.Resource.Alive():
130 case w.surf.destroyed:
131 discard(w.fb)
132 case s.invisible(w.surf):
133 discard(w.fb)
134 case s.frameOutput(w.surf) != output || !ok || shown < w.seq:
135 kept = append(kept, w)
136 case f.Merged > 0:
137 // The reader fell behind: which flip showed it is unknown.
138 discard(w.fb)
139 default:
140 // Shown, unless the surface's next commit is shown too.
141 s.sendPresented(w, o, f, w.replaced == 0 || shown < w.replaced)
142 }
143 }
144 clear(s.feedbacks[len(kept):])
145 s.feedbacks = kept
146}
147
148// sendPresented sends presented for the flip; exact is false when a
149// later commit of the surface replaced it before the flip (then it is
150// discarded).
151func (s *Server) sendPresented(w feedbackWait, o *output, f *ports.FlipInfo, exact bool) {
152 if !exact {
153 discard(w.fb)
154 return
155 }
156 if o != nil {
157 for _, r := range o.resources {
158 if r.Resource.Alive() && r.Client() == w.fb.Client() {
159 w.fb.SendSyncOutput(r)
160 }
161 }
162 }
163 flags := uint32(0)
164 // A software clock (headless) claims nothing about the display.
165 if !f.Async && f.HardwareClock {
166 flags |= uint32(presentationtime.WpPresentationFeedbackKindVsync)
167 }
168 if f.HardwareClock {
169 flags |= uint32(presentationtime.WpPresentationFeedbackKindHwClock | presentationtime.WpPresentationFeedbackKindHwCompletion)
170 }
171 if f.ZeroCopy != 0 && f.ZeroCopy == w.win {
172 flags |= uint32(presentationtime.WpPresentationFeedbackKindZeroCopy)
173 }
174 sec, nsec := uint64(f.When/time.Second), uint32(f.When%time.Second)
175 w.fb.SendPresented(uint32(sec>>32), uint32(sec), nsec, uint32(f.Refresh), uint32(f.Seq>>32), uint32(f.Seq), flags)
176 w.fb.Destroy()
177}
178
179// feedbackTimeout discards a feedback no flip answered: its window is
180// hidden, on an output that does not flip (headless, switched away), or
181// was never drawn.
182const feedbackTimeout = time.Second
183
184// dropFeedbacks discards the feedbacks of destroyed surfaces, of outputs
185// that are gone, and those older than feedbackTimeout.
186func (s *Server) dropFeedbacks(now time.Time) {
187 kept := s.feedbacks[:0]
188 for _, w := range s.feedbacks {
189 if !w.fb.Resource.Alive() {
190 continue
191 }
192 if w.surf.destroyed || s.invisible(w.surf) || s.frameOutput(w.surf) == "" || now.Sub(w.at) > feedbackTimeout {
193 discard(w.fb)
194 continue
195 }
196 kept = append(kept, w)
197 }
198 clear(s.feedbacks[len(kept):])
199 s.feedbacks = kept
200}