1package wayland
2
3import (
4 "time"
5
6 "github.com/bnema/neferwl/internal/ports"
7 "github.com/bnema/purego-libwayland/protocol/committiming"
8 "github.com/bnema/purego-libwayland/protocol/contenttype"
9 "github.com/bnema/purego-libwayland/protocol/fifo"
10 "github.com/bnema/purego-libwayland/protocol/presentationtime"
11 "github.com/bnema/purego-libwayland/protocol/wayland"
12 "github.com/bnema/purego-libwayland/server"
13 "golang.org/x/sys/unix"
14)
15
16// Presentation constraints on content updates (games, Vulkan FIFO):
17//
18// - wp_fifo_v1: a commit with wait_barrier waits while the barrier set
19// by an earlier commit stands; the barrier clears at the output's
20// next refresh (its flip, or a deadline when it does not flip).
21// - wp_commit_timing_v1: a commit waits until the refresh that shows it
22// is not before its timestamp (CLOCK_MONOTONIC).
23//
24// A commit that must wait is taken out of the surface's pending state and
25// queued; the pacer (frames.go) applies queued updates in order when they
26// are ready. wp_content_type_v1 is recorded and logged only.
27
28func registerPresentationConstraints(d *server.Display, s *Server) error {
29 if err := fifo.NewWpFifoManagerV1Global(d, 1, func(c server.Client, v, id uint32) {
30 _, _ = fifo.NewWpFifoManagerV1(c, int32(v), id, fifoManager{s})
31 }); err != nil {
32 return err
33 }
34 if err := committiming.NewWpCommitTimingManagerV1Global(d, 1, func(c server.Client, v, id uint32) {
35 _, _ = committiming.NewWpCommitTimingManagerV1(c, int32(v), id, timingManager{s})
36 }); err != nil {
37 return err
38 }
39 return contenttype.NewWpContentTypeManagerV1Global(d, 1, func(c server.Client, v, id uint32) {
40 _, _ = contenttype.NewWpContentTypeManagerV1(c, int32(v), id, contentTypeManager{s})
41 })
42}
43
44// fifo_v1
45
46type fifoManager struct{ server *Server }
47
48func (fifoManager) Destroy(*fifo.WpFifoManagerV1) {}
49
50func (m fifoManager) GetFifo(r *fifo.WpFifoManagerV1, id uint32, surf *wayland.Surface) {
51 state := m.server.surfaceOf(surf)
52 if state != nil && state.fifo != nil {
53 r.PostError(uint32(fifo.WpFifoManagerV1ErrorAlreadyExists), "surface already has a fifo")
54 return
55 }
56 h := &fifoHandler{surface: state}
57 res, err := fifo.NewWpFifoV1(r.Client(), r.Version(), id, h)
58 if err != nil || state == nil {
59 return
60 }
61 state.fifo = h
62 res.OnDestroy = func() {
63 if state.fifo == h {
64 state.fifo = nil
65 }
66 }
67}
68
69type fifoHandler struct{ surface *surface }
70
71func (h *fifoHandler) live(r *fifo.WpFifoV1) bool {
72 if h.surface == nil || h.surface.destroyed {
73 r.PostError(uint32(fifo.WpFifoV1ErrorSurfaceDestroyed), "surface destroyed")
74 return false
75 }
76 return true
77}
78func (h *fifoHandler) SetBarrier(r *fifo.WpFifoV1) {
79 if h.live(r) {
80 h.surface.pendingBarrier = true
81 }
82}
83func (h *fifoHandler) WaitBarrier(r *fifo.WpFifoV1) {
84 if h.live(r) {
85 h.surface.pendingWait = true
86 }
87}
88func (*fifoHandler) Destroy(*fifo.WpFifoV1) {}
89
90// commit_timing_v1
91
92type timingManager struct{ server *Server }
93
94func (timingManager) Destroy(*committiming.WpCommitTimingManagerV1) {}
95
96func (m timingManager) GetTimer(r *committiming.WpCommitTimingManagerV1, id uint32, surf *wayland.Surface) {
97 state := m.server.surfaceOf(surf)
98 if state != nil && state.timer != nil {
99 r.PostError(uint32(committiming.WpCommitTimingManagerV1ErrorCommitTimerExists), "surface already has a commit timer")
100 return
101 }
102 h := &timerHandler{surface: state}
103 res, err := committiming.NewWpCommitTimerV1(r.Client(), r.Version(), id, h)
104 if err != nil || state == nil {
105 return
106 }
107 state.timer = h
108 res.OnDestroy = func() {
109 if state.timer == h {
110 state.timer = nil
111 }
112 }
113}
114
115type timerHandler struct{ surface *surface }
116
117func (h *timerHandler) SetTimestamp(r *committiming.WpCommitTimerV1, secHi, secLo, nsec uint32) {
118 switch {
119 case h.surface == nil || h.surface.destroyed:
120 r.PostError(uint32(committiming.WpCommitTimerV1ErrorSurfaceDestroyed), "surface destroyed")
121 case nsec >= 1e9:
122 r.PostError(uint32(committiming.WpCommitTimerV1ErrorInvalidTimestamp), "tv_nsec out of range")
123 case !h.surface.pendingTime.IsZero():
124 r.PostError(uint32(committiming.WpCommitTimerV1ErrorTimestampExists), "timestamp already set")
125 default:
126 h.surface.pendingTime = monotonicTime(uint64(secHi)<<32|uint64(secLo), int64(nsec))
127 }
128}
129func (*timerHandler) Destroy(*committiming.WpCommitTimerV1) {}
130
131// maxTimestampAhead bounds how far a commit timestamp can hold a commit;
132// a later one applies early (a deviation from "not before").
133const maxTimestampAhead = time.Second
134
135// monotonicTime converts a CLOCK_MONOTONIC time to wall time, at most
136// maxTimestampAhead from now: a far timestamp must not stall the surface.
137func monotonicTime(sec uint64, nsec int64) time.Time {
138 var now unix.Timespec
139 if unix.ClockGettime(unix.CLOCK_MONOTONIC, &now) != nil {
140 return time.Now()
141 }
142 ahead := time.Duration(nsec - now.Nsec)
143 switch n := uint64(now.Sec); {
144 case sec >= n+2:
145 ahead = maxTimestampAhead
146 case sec+2 <= n:
147 ahead = 0 // in the past: apply now
148 default:
149 ahead += time.Duration(int64(sec)-now.Sec) * time.Second
150 }
151 return time.Now().Add(min(max(ahead, 0), maxTimestampAhead))
152}
153
154// content_type_v1
155
156type contentTypeManager struct{ server *Server }
157
158func (contentTypeManager) Destroy(*contenttype.WpContentTypeManagerV1) {}
159
160func (m contentTypeManager) GetSurfaceContentType(r *contenttype.WpContentTypeManagerV1, id uint32, surf *wayland.Surface) {
161 state := m.server.surfaceOf(surf)
162 if state != nil && state.contentType != nil {
163 r.PostError(uint32(contenttype.WpContentTypeManagerV1ErrorAlreadyConstructed), "surface already has a content type")
164 return
165 }
166 h := &contentTypeHandler{surface: state}
167 res, err := contenttype.NewWpContentTypeV1(r.Client(), r.Version(), id, h)
168 if err != nil || state == nil {
169 return
170 }
171 state.contentType = h
172 res.OnDestroy = func() {
173 // Destroying it sets the type back to none, double-buffered.
174 if state.contentType == h {
175 state.contentType = nil
176 state.pendingKind = 0
177 }
178 }
179}
180
181type contentTypeHandler struct{ surface *surface }
182
183func (h *contentTypeHandler) SetContentType(_ *contenttype.WpContentTypeV1, kind uint32) {
184 if h.surface != nil && !h.surface.destroyed && h.surface.contentType == h {
185 h.surface.pendingKind = kind
186 }
187}
188func (*contentTypeHandler) Destroy(*contenttype.WpContentTypeV1) {}
189
190func (s *Server) surfaceOf(w *wayland.Surface) *surface {
191 if w == nil {
192 return nil
193 }
194 return s.surfaces[w.Resource]
195}
196
197// Content update queue
198
199// update is a surface's pending state taken out by a commit that waits.
200type update struct {
201 attached bool
202 buffer *wayland.Buffer
203 scale int
204 async bool
205 kind uint32
206 callbacks []*wayland.Callback
207 vp *viewport
208 vpW, vpH int32
209 vpSet bool
210 moves []childMove
211 xdg *xdgSurface
212 geometry ports.Rect
213 cons *constraint
214 region *ports.Rect
215 layer *layerSurface
216 layerNext layerState
217 barrier bool
218 wait bool
219 at time.Time
220 feedback []*presentationtime.WpPresentationFeedback
221 sync *commitSync
222 damage []ports.Rect
223 bufDamage []ports.Rect
224}
225
226type childMove struct {
227 child *surface
228 x, y int
229}
230
231// takePending moves the pending state into an update: one-shot state is
232// cleared, sticky state (scale, hints, viewport, geometry, layer) kept.
233func (s *surface) takePending() update {
234 u := update{attached: s.attached, buffer: s.pending, scale: s.pendingScale, async: s.pendingAsync, kind: s.pendingKind, callbacks: s.callbacks, barrier: s.pendingBarrier, wait: s.pendingWait, at: s.pendingTime, damage: s.pendingDamage, bufDamage: s.pendingBufDamage, feedback: s.pendingFeedback, sync: s.pendingSync}
235 s.attached, s.pending, s.callbacks = false, nil, nil
236 s.pendingFeedback, s.pendingSync = nil, nil
237 s.pendingDamage, s.pendingBufDamage = nil, nil
238 s.pendingBarrier, s.pendingWait, s.pendingTime = false, false, time.Time{}
239 if v := s.viewport; v != nil {
240 u.vp, u.vpW, u.vpH, u.vpSet = v, v.pendingW, v.pendingH, v.pendingSet
241 }
242 for _, ch := range s.sub.children {
243 if ch.sub.moved {
244 u.moves = append(u.moves, childMove{ch, ch.sub.pendX, ch.sub.pendY})
245 ch.sub.moved = false
246 }
247 }
248 if s.xdg != nil {
249 u.xdg, u.geometry = s.xdg, s.xdg.pendingGeometry
250 }
251 if c := s.server.constraints[s]; c != nil && c.pending != nil {
252 u.cons, u.region, c.pending = c, c.pending, nil
253 }
254 if s.layer != nil {
255 u.layer, u.layerNext = s.layer, s.layer.pending
256 }
257 return u
258}
259
260// putPending makes an update the surface's pending state again. Parts
261// whose object is gone (viewport, child, role, constraint) are dropped.
262func (s *surface) putPending(u update) {
263 s.attached, s.pending, s.callbacks = u.attached, u.buffer, u.callbacks
264 s.pendingScale, s.pendingAsync, s.pendingKind = u.scale, u.async, u.kind
265 s.pendingBarrier, s.pendingWait, s.pendingTime = u.barrier, u.wait, u.at
266 s.pendingDamage, s.pendingBufDamage = u.damage, u.bufDamage
267 s.pendingFeedback, s.pendingSync = u.feedback, u.sync
268 if u.vp != nil && s.viewport == u.vp {
269 u.vp.pendingW, u.vp.pendingH, u.vp.pendingSet = u.vpW, u.vpH, u.vpSet
270 }
271 for _, m := range u.moves {
272 if m.child.sub.parent == s {
273 m.child.sub.pendX, m.child.sub.pendY, m.child.sub.moved = m.x, m.y, true
274 }
275 }
276 if u.xdg != nil && s.xdg == u.xdg {
277 u.xdg.pendingGeometry = u.geometry
278 }
279 if u.cons != nil && s.server.constraints[s] == u.cons {
280 u.cons.pending = u.region
281 }
282 if u.layer != nil && s.layer == u.layer {
283 u.layer.pending = u.layerNext
284 }
285}
286
287// mustWait reports whether the pending commit cannot apply now.
288func (s *surface) mustWait(now time.Time) bool {
289 return len(s.queue) > 0 || (s.pendingWait && s.barrier) || s.tooEarly(s.pendingTime, now) || !s.syncReady(s.pendingSync)
290}
291
292// tooEarly reports whether content applied now would show before at:
293// it reaches the screen one refresh later.
294func (s *surface) tooEarly(at, now time.Time) bool {
295 return !at.IsZero() && at.After(s.server.nextRefresh(s.server.fifoOutput(s), now))
296}
297
298// nextRefresh is when the output's next frame reaches the screen: the
299// vblank after now counted from its last flip (kernel time, refresh
300// period), or one period from now without flips or while VRR is on.
301func (s *Server) nextRefresh(name string, now time.Time) time.Time {
302 p := s.outputPeriod(name)
303 f, ok := s.flips[name]
304 if !ok || f.When <= 0 || f.Refresh <= 0 {
305 return now.Add(p)
306 }
307 mono := monotonic(now)
308 since := mono - f.When
309 if since < 0 || since > time.Second {
310 return now.Add(p)
311 }
312 n := since/f.Refresh + 1
313 return now.Add(f.When + n*f.Refresh - mono)
314}
315
316// queueUpdate queues the pending commit until it can apply.
317func (s *surface) queueUpdate() {
318 s.queue = append(s.queue, s.takePending())
319 s.server.fifoSurfaces[s] = struct{}{}
320 s.server.wakePacer()
321}
322
323// applyUpdate applies a queued update, keeping the requests made since.
324func (s *surface) applyUpdate(u update) {
325 // A buffer destroyed while queued (a swapchain resize) is skipped: the
326 // surface keeps its content, and is not unmapped behind the client.
327 if u.buffer != nil && !u.buffer.Resource.Alive() {
328 u.buffer, u.attached = nil, false
329 }
330 later := s.takePending()
331 s.putPending(u)
332 s.applyCommit()
333 s.putPending(later)
334}
335
336// dropQueue discards the queued updates of a destroyed surface or role.
337// A live surface still gets its queued frame callbacks.
338func (s *surface) dropQueue() {
339 released := map[*server.Resource]bool{}
340 for _, u := range s.queue {
341 if s.destroyed {
342 for _, cb := range u.callbacks {
343 cb.Destroy()
344 }
345 } else if len(u.callbacks) > 0 {
346 s.server.queueFrames(s.server.frameOutput(s), u.callbacks)
347 }
348 for _, fb := range u.feedback {
349 discard(fb)
350 }
351 s.dropSync(u.sync)
352 if u.sync != nil {
353 continue // explicit sync: the release point replaced release
354 }
355 if u.buffer != nil && !sameBuffer(u.buffer, s.current) && !released[u.buffer.Resource] && u.buffer.Resource.Alive() {
356 released[u.buffer.Resource] = true
357 u.buffer.SendRelease()
358 }
359 }
360 s.queue = nil
361 delete(s.server.fifoSurfaces, s)
362}
363
364// setBarrier applies a committed set_barrier.
365func (s *surface) setBarrier(now time.Time) {
366 s.barrier, s.barrierAt = true, now
367 s.server.fifoSurfaces[s] = struct{}{}
368 s.server.wakePacer()
369}
370
371// fifoOutput is the output pacing a surface, "" when none does.
372func (s *Server) fifoOutput(surf *surface) string {
373 name := s.frameOutput(surf)
374 if name != "" && s.outputByNameExact(name) == nil {
375 return ""
376 }
377 return name
378}
379
380// outputPeriod is one refresh of an output ("": 60 Hz).
381func (s *Server) outputPeriod(name string) time.Duration {
382 if o := s.outputByNameExact(name); o != nil && name != "" {
383 return framePeriod(o.place.Info.RefreshMilli)
384 }
385 return defaultFramePeriod
386}
387
388// tickFifo clears the barriers whose latching deadline passed and applies
389// the queued updates now ready. A flip of the surface's output is its
390// deadline; outputs that do not flip (headless, switched away, no output)
391// clear it after one refresh, or 1.5 when they usually flip. It returns
392// the time to the next deadline and whether any surface still waits.
393func (s *Server) tickFifo(now time.Time, flipped map[string]bool) (time.Duration, bool) {
394 for name, f := range flipped {
395 if f {
396 s.lastFlip[name] = now
397 }
398 }
399 for name := range s.lastFlip {
400 if s.outputByNameExact(name) == nil {
401 delete(s.lastFlip, name)
402 }
403 }
404 wait := defaultFramePeriod
405 for surf := range s.fifoSurfaces {
406 name := s.fifoOutput(surf)
407 p := s.outputPeriod(name)
408 // The deadline of outputs that usually flip only catches a missed flip.
409 deadline := surf.barrierAt.Add(p)
410 if name != "" && now.Sub(s.lastFlip[name]) < time.Second {
411 deadline = deadline.Add(p / 2)
412 }
413 if surf.barrier && ((flipped[name] && name != "" && s.lastFlip[name].After(surf.barrierAt)) || !now.Before(deadline)) {
414 surf.barrier = false
415 }
416 for len(surf.queue) > 0 && !surf.destroyed {
417 u := surf.queue[0]
418 if u.wait && surf.barrier {
419 break
420 }
421 if !surf.syncReady(u.sync) {
422 break
423 }
424 if surf.tooEarly(u.at, now) {
425 wait = min(wait, u.at.Sub(s.nextRefresh(name, now))+time.Millisecond)
426 break
427 }
428 surf.queue = surf.queue[1:]
429 surf.applyUpdate(u)
430 }
431 if surf.barrier {
432 wait = min(wait, deadline.Sub(now))
433 }
434 if !surf.barrier && len(surf.queue) == 0 {
435 delete(s.fifoSurfaces, surf)
436 }
437 }
438 return max(wait, time.Millisecond), len(s.fifoSurfaces) > 0
439}
440
441// sameBuffer reports whether two wrappers are the same wl_buffer.
442func sameBuffer(a, b *wayland.Buffer) bool {
443 return a != nil && b != nil && a.Resource == b.Resource
444}
445
446// wakePacer makes the pacer look at its queues now.
447func (s *Server) wakePacer() {
448 select {
449 case s.frameReady <- struct{}{}:
450 default:
451 }
452}