1package wayland
2
3import (
4 "sync"
5 "time"
6
7 "github.com/bnema/go-wayland-bindings/server/committiming"
8 "github.com/bnema/go-wayland-bindings/server/contenttype"
9 "github.com/bnema/go-wayland-bindings/server/fifo"
10 "github.com/bnema/go-wayland-bindings/server/wayland"
11 "github.com/bnema/neferwl/internal/ports"
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// Every commit captures its pending state into an update; the pacer (frames.go)
25// applies queued updates in order when they are ready. Requests arriving after
26// capture stay in live pending state. wp_content_type_v1 is 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.next.barrier = true
81 }
82}
83func (h *fifoHandler) WaitBarrier(r *fifo.WpFifoV1) {
84 if h.live(r) {
85 h.surface.next.wait = 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.next.at.IsZero():
124 r.PostError(uint32(committiming.WpCommitTimerV1ErrorTimestampExists), "timestamp already set")
125 default:
126 h.surface.next.at = 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.next.kind = 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.next.kind = 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 owns a captured commit until it applies or is discarded.
200type update struct {
201 // pendingCommit is the wl_surface state captured by the commit.
202 pendingCommit
203 vp *viewport
204 vpW, vpH int32
205 vpSet bool
206 vpSrc [4]server.Fixed
207 vpCrop bool
208 layout []childLayout
209 deps []*update
210 owner *surface
211 implicit [4]*syncWait
212 prev *update
213 synced, bound bool
214 xdg *xdgSurface
215 geometry ports.Rect
216 cons *constraint
217 region *ports.Rect
218 layer *layerSurface
219 layerNext layerState
220 lockAck lockConfigure
221 readyGeneration uint64
222 readyResult bool
223 // refs counts incoming deps and prev links. Retired updates remain readable
224 // until their last incoming link goes away, even after dropQueue.
225 refs int
226 retired bool
227 failed bool // acquire observation failed: discard, never publish
228}
229
230// updatePool contains only retired updates with no incoming graph links. A
231// popped update is NOT reusable just because it left its owner's queue: a
232// parent's queued deps can still point to it (including after role teardown).
233// The display owner goroutine changes graph links; sync.Pool only shares dead,
234// zeroed storage between independent displays.
235var updatePool sync.Pool
236
237func (u *update) releaseRef() {
238 u.refs--
239 if u.refs == 0 && u.retired {
240 deps := u.deps[:0]
241 *u = update{deps: deps}
242 updatePool.Put(u)
243 }
244}
245
246// retireUpdate severs outgoing links only after the update is no longer used
247// by applyUpdate/dropQueue. Incoming links keep its identity until consumed.
248func (u *update) retireUpdate() {
249 u.retired = true
250 prev, deps := u.prev, u.deps
251 u.prev, u.deps = nil, deps[:0]
252 if prev != nil {
253 prev.releaseRef()
254 }
255 for i, dep := range deps {
256 deps[i] = nil // the pooled backing array must not retain old dependencies
257 dep.releaseRef()
258 }
259 if u.refs == 0 {
260 *u = update{deps: u.deps}
261 updatePool.Put(u)
262 }
263}
264
265type childLayout struct {
266 child *surface
267 x, y int
268 below bool
269}
270
271func removeLayout(layout []childLayout, child *surface) []childLayout {
272 layout = append([]childLayout(nil), layout...)
273 for i := 0; i < len(layout); i++ {
274 if layout[i].child == child {
275 layout = append(layout[:i:i], layout[i+1:]...)
276 i--
277 }
278 }
279 return layout
280}
281
282func sameLayout(a, b []childLayout) bool {
283 if len(a) != len(b) {
284 return false
285 }
286 for i := range a {
287 if a[i] != b[i] {
288 return false
289 }
290 }
291 return true
292}
293
294// takePending moves the pending state into an update: one-shot state is
295// cleared, sticky state (scale, hints, viewport, geometry, layer) kept.
296func (s *surface) takePending() update {
297 u := update{pendingCommit: s.next}
298 // One-shot requests apply once; sticky ones carry over.
299 s.next = pendingCommit{scale: s.next.scale, transform: s.next.transform, async: s.next.async, color: s.next.color, representation: s.next.representation, kind: s.next.kind, fade: s.next.fade}
300 if v := s.viewport; v != nil {
301 u.vp, u.vpW, u.vpH, u.vpSet, u.vpSrc, u.vpCrop = v, v.pendingW, v.pendingH, v.pendingSet, v.pendingSrc, v.pendingCrop
302 }
303 u.layout = s.sub.pendingLayout
304 if s.xdg != nil {
305 u.xdg, u.geometry = s.xdg, s.xdg.pendingGeometry
306 }
307 if c := s.server.constraints[s]; c != nil && c.pending != nil {
308 u.cons, u.region, c.pending = c, c.pending, nil
309 }
310 if s.layer != nil {
311 u.layer, u.layerNext = s.layer, s.layer.pending
312 }
313 if s.lock != nil {
314 u.lockAck = s.lock.ack
315 }
316 return u
317}
318
319// tooEarly reports whether content applied now would show before at:
320// it reaches the screen one refresh later.
321func (s *surface) tooEarly(at, now time.Time) bool {
322 return !at.IsZero() && at.After(s.server.nextRefresh(s.server.fifoOutput(s), now))
323}
324
325// nextRefresh is when the output's next frame reaches the screen: the
326// vblank after now counted from its last flip (kernel time, refresh
327// period), or one period from now without flips or while VRR is on.
328func (s *Server) nextRefresh(name string, now time.Time) time.Time {
329 p := s.outputPeriod(name)
330 f, ok := s.flips[name]
331 if !ok || f.When <= 0 || f.Refresh <= 0 {
332 return now.Add(p)
333 }
334 mono := monotonic(now)
335 since := mono - f.When
336 if since < 0 || since > time.Second {
337 return now.Add(p)
338 }
339 n := since/f.Refresh + 1
340 return now.Add(f.When + n*f.Refresh - mono)
341}
342
343// queueUpdate queues the pending commit until it can apply.
344func (s *surface) queueUpdate() {
345 u, _ := updatePool.Get().(*update)
346 if u == nil {
347 u = new(update)
348 }
349 deps := u.deps[:0]
350 *u = s.takePending()
351 u.deps = deps
352 u.owner, u.synced = s, s.effectivelySynced()
353 if s.server.syncWait != nil && u.attached && u.buffer != nil && u.sync == nil {
354 if b, ok := s.server.buffers[u.buffer.Resource].(*dmabufBuffer); ok {
355 err := s.server.syncWait.watchImplicit(b.buf, &u.implicit)
356 if err != nil {
357 // Partial registrations were cancelled; discard this update.
358 u.failed = true
359 s.server.log.Warn().Str("component", "wayland").Err(err).Msg("implicit buffer wait")
360 }
361 }
362 }
363 for _, ch := range s.sub.children {
364 for i := len(ch.queue) - 1; i >= 0; i-- {
365 candidate := ch.queue[i]
366 if candidate.synced && !candidate.bound {
367 candidate.bound = true
368 u.deps = append(u.deps, candidate)
369 candidate.refs++
370 break
371 }
372 }
373 }
374 if len(s.queue) > 0 {
375 u.prev = s.queue[len(s.queue)-1]
376 u.prev.refs++
377 }
378 s.queue = append(s.queue, u)
379 s.server.fifoSurfaces[s] = struct{}{}
380 s.server.wakePacer()
381}
382
383// applyUpdate applies only the captured commit; later requests remain pending.
384func (s *surface) applyUpdate(u *update) {
385 // A buffer destroyed while queued (a swapchain resize) is skipped: the
386 // surface keeps its content, and is not unmapped behind the client.
387 s.commitSkipped = false
388 s.queuedScale = u.scale
389 s.queuedBuffer = 0
390 if u.buffer != nil {
391 s.queuedBuffer = u.buffer.ID()
392 }
393 if u.buffer != nil && !u.buffer.Resource.Alive() {
394 s.commitSkipped = true
395 // No reader takes this buffer. A release point is safe to signal
396 // only if the acquire has already signalled; dropSync owns that rule.
397 s.dropSync(u.sync)
398 u.sync = nil
399 u.buffer, u.attached = nil, false
400 // Scale, transform and crop describe the same retained buffer.
401 u.scale = s.bufferScale
402 u.transform = s.transform
403 // The viewport change was made for that buffer: keep the crop that
404 // matches the retained content instead of validating a mismatch.
405 if u.vp != nil && (s.lock != nil || u.vp.resource != nil && u.vp.resource.Alive()) {
406 v := &s.committedViewport
407 u.vpW, u.vpH, u.vpSet, u.vpSrc, u.vpCrop = v.destW, v.destH, v.dest, v.src, v.crop
408 }
409 }
410 s.applyCommit(u)
411 s.commitSkipped = false
412}
413
414// dropQueue discards the queued updates of a destroyed surface or role.
415// A live surface still gets its queued frame callbacks.
416func (s *surface) dropQueue() {
417 if s.kind == roleSessionLock {
418 s.dropLockDeps()
419 }
420 s.dropQueuePrefix(len(s.queue))
421}
422
423// dropQueuePrefix retires only the first n updates. Leave the preserved suffix
424// visible to release checks: a buffer shared with it is still client-owned work.
425func (s *surface) dropQueuePrefix(n int) {
426 queue := s.queue[:n]
427 s.queue = s.queue[n:]
428 released := map[*server.Resource]bool{}
429 for _, u := range queue {
430 if s.destroyed {
431 for _, cb := range u.callbacks {
432 cb.Destroy()
433 }
434 } else if len(u.callbacks) > 0 {
435 s.server.queueFrames(s, u.callbacks)
436 }
437 for _, fb := range u.feedback {
438 discard(fb)
439 }
440 s.dropSync(u.sync)
441 for _, w := range u.implicit {
442 s.server.syncWait.cancel(w)
443 }
444 if u.sync != nil {
445 continue // explicit sync: the release point replaced release
446 }
447 if u.buffer != nil && !sameBuffer(u.buffer, s.current) && !released[u.buffer.Resource] && u.buffer.Resource.Alive() && !s.bufferQueuedElsewhere(u.buffer) {
448 released[u.buffer.Resource] = true
449 u.buffer.SendRelease()
450 }
451 }
452 for i, u := range queue {
453 queue[i] = nil // do not retain retired objects in the queue backing array
454 u.retireUpdate()
455 }
456 if len(s.queue) == 0 {
457 s.queue = queue[:0] // preserve steady-state capacity when all were retired
458 delete(s.server.fifoSurfaces, s)
459 }
460}
461
462func (s *surface) bufferQueuedElsewhere(b *wayland.Buffer) bool {
463 for _, other := range s.server.surfaces {
464 if sameBuffer(other.next.buffer, b) {
465 return true
466 }
467 if other != s && sameBuffer(other.current, b) {
468 return true
469 }
470 for _, u := range other.queue {
471 if sameBuffer(u.buffer, b) {
472 return true
473 }
474 }
475 }
476 return false
477}
478
479// setBarrier applies a committed set_barrier.
480func (s *surface) setBarrier(now time.Time) {
481 s.barrier, s.barrierAt = true, now
482 s.server.fifoSurfaces[s] = struct{}{}
483 s.server.wakePacer()
484}
485
486// fifoOutput is the output pacing a surface, "" when none does.
487func (s *Server) fifoOutput(surf *surface) string {
488 name := s.frameOutput(surf)
489 if name != "" && name != suspendedFrameQueue && s.outputByNameExact(name) == nil {
490 return ""
491 }
492 return name
493}
494
495// outputPeriod is one refresh of an output ("": 60 Hz).
496func (s *Server) outputPeriod(name string) time.Duration {
497 if name == suspendedFrameQueue {
498 return suspendedFramePeriod
499 }
500 if o := s.outputByNameExact(name); o != nil && name != "" {
501 return framePeriod(o.place.Info.RefreshMilli)
502 }
503 return defaultFramePeriod
504}
505
506// tickFifo clears the barriers whose latching deadline passed and applies
507// the queued updates now ready. A flip of the surface's output is its
508// deadline; outputs that do not flip (headless, switched away, no output)
509// clear it after one refresh, or 1.5 when they usually flip. It returns
510// the time to the next deadline and whether any surface still waits.
511func (s *Server) tickFifo(now time.Time, flipped map[string]bool) (time.Duration, bool) {
512 for name, f := range flipped {
513 if f {
514 s.lastFlip[name] = now
515 }
516 }
517 for name := range s.lastFlip {
518 if s.outputByNameExact(name) == nil {
519 delete(s.lastFlip, name)
520 }
521 }
522 wait := suspendedFramePeriod
523 s.readinessGeneration++
524 for surf := range s.fifoSurfaces {
525 name := s.fifoOutput(surf)
526 p := s.outputPeriod(name)
527 // The deadline of outputs that usually flip only catches a missed flip.
528 deadline := surf.barrierAt.Add(p)
529 if name != "" && now.Sub(s.lastFlip[name]) < time.Second {
530 deadline = deadline.Add(p / 2)
531 }
532 if surf.barrier && ((flipped[name] && name != "" && s.lastFlip[name].After(surf.barrierAt)) || !now.Before(deadline)) {
533 surf.barrier = false
534 }
535 for len(surf.queue) > 0 && !surf.destroyed {
536 u := surf.queue[0]
537 if u.synced || !u.graphReady(now) {
538 break
539 }
540 s.applyingGraph = true
541 s.graphDrawn = false
542 s.graphFeedback = s.graphFeedback[:0]
543 u.applyGraph()
544 s.applyingGraph = false
545 // Applying a graph changes queues and readiness within this tick.
546 s.readinessGeneration++
547 if s.graphDrawn {
548 surf.redraw()
549 }
550 // Only the last commit per surface can be sampled from this
551 // publication. Earlier callbacks were already queued on apply.
552 for i, fb := range s.graphFeedback {
553 superseded := false
554 for j := i + 1; j < len(s.graphFeedback); j++ {
555 if s.graphFeedback[j].surf == fb.surf && s.graphFeedback[j].fresh {
556 superseded = true
557 break
558 }
559 }
560 if superseded {
561 for _, pending := range fb.pending {
562 discard(pending)
563 }
564 continue
565 }
566 fb.surf.commitFeedback(fb.pending, fb.fresh)
567 }
568 s.graphFeedback = s.graphFeedback[:0]
569 }
570 if surf.barrier {
571 wait = min(wait, deadline.Sub(now))
572 }
573 if len(surf.queue) > 0 {
574 wait = min(wait, p)
575 }
576 if !surf.barrier && len(surf.queue) == 0 {
577 delete(s.fifoSurfaces, surf)
578 }
579 }
580 return max(wait, time.Millisecond), len(s.fifoSurfaces) > 0
581}
582
583func (s *surface) effectivelySynced() bool {
584 for p := s; p != nil; p = p.sub.parent {
585 if p.sub.parent != nil && p.sub.synced {
586 return true
587 }
588 }
589 return false
590}
591
592func (s *surface) flushDesync() {
593 if !s.effectivelySynced() {
594 for _, u := range s.queue {
595 if u.synced && !u.bound {
596 u.synced = false
597 }
598 }
599 }
600 for _, ch := range s.sub.children {
601 ch.flushDesync()
602 }
603 s.server.wakePacer()
604}
605
606func (u *update) graphReady(now time.Time) bool {
607 if u.retired {
608 return true // applied or dropped; a queued parent cannot replay it
609 }
610 s := u.owner
611 gen := s.server.readinessGeneration
612 if gen != 0 && u.readyGeneration == gen {
613 return u.readyResult
614 }
615 if gen != 0 {
616 u.readyGeneration, u.readyResult = gen, false
617 }
618 if u.prev != nil && !u.prev.graphReady(now) {
619 return false
620 }
621 if u.wait && s.barrier || s.tooEarly(u.at, now) {
622 return false
623 }
624 // A dead attachment is skipped by applyUpdate; waiting for its fence
625 // would leave this update and every later one stuck behind it.
626 if u.buffer != nil && !u.buffer.Resource.Alive() {
627 for i, w := range u.implicit {
628 if w != nil {
629 s.server.syncWait.cancel(w)
630 u.implicit[i] = nil
631 }
632 }
633 } else if !u.failed {
634 if !s.syncReady(u.sync) {
635 return false
636 }
637 if u.sync != nil && u.sync.wait != nil && u.sync.wait.failed {
638 u.failed = true
639 }
640 waiting := false
641 for _, w := range u.implicit {
642 if w == nil {
643 continue
644 }
645 if !s.server.syncWait.fired(w) {
646 waiting = true
647 } else if w.failed {
648 u.failed = true
649 }
650 }
651 if waiting && !u.failed {
652 return false
653 }
654 }
655 if u.failed {
656 u.readyResult = true // ready only to retire, never to apply
657 return true
658 }
659 for _, dep := range u.deps {
660 if !dep.owner.destroyed && !dep.graphReady(now) {
661 return false
662 }
663 }
664 u.readyResult = true
665 return true
666}
667
668func (u *update) applyGraph() {
669 if u.retired {
670 return
671 }
672 s := u.owner
673 if !u.failed && u.sync != nil && (u.buffer == nil || u.buffer.Alive()) && !s.prepareSync(u.sync) {
674 u.failed = true
675 }
676 if u.failed {
677 // Earlier commits still apply in order; only this update is discarded.
678 for len(s.queue) > 0 && s.queue[0] != u {
679 s.queue[0].applyGraph()
680 }
681 // Retire captured children too, cancelling their waits without replay.
682 u.dropFailedDeps()
683 for i, queued := range s.queue {
684 if queued == u {
685 s.dropQueuePrefix(i + 1)
686 break
687 }
688 }
689 return
690 }
691 for len(s.queue) > 0 && s.queue[0] != u {
692 s.queue[0].applyGraph()
693 }
694 for _, dep := range u.deps {
695 if !dep.owner.destroyed {
696 dep.applyGraph()
697 }
698 }
699 if len(s.queue) == 0 || s.queue[0] != u {
700 return
701 }
702 s.queue[0] = nil
703 if len(s.queue) == 1 {
704 s.queue = s.queue[:0] // retain capacity for the next commit
705 } else {
706 s.queue = s.queue[1:]
707 }
708 if len(s.queue) > 0 {
709 next := s.queue[0]
710 next.prev = nil
711 u.releaseRef() // next's prev no longer points to u
712 }
713 s.applyUpdate(u)
714 u.retireUpdate()
715}
716
717// dropFailedDeps retires just the child prefixes owned by a rejected commit.
718// Incoming graph refs retain their storage until the failed parent retires.
719func (u *update) dropFailedDeps() {
720 for _, dep := range u.deps {
721 if dep.retired {
722 continue
723 }
724 // Every predecessor in the retired prefix can own a different
725 // dependency graph. Traverse it before severing any incoming refs.
726 for _, prefix := range dep.owner.queue {
727 if !prefix.retired {
728 prefix.dropFailedDeps()
729 }
730 if prefix == dep {
731 break
732 }
733 }
734 for i, queued := range dep.owner.queue {
735 if queued == dep {
736 dep.owner.dropQueuePrefix(i + 1)
737 break
738 }
739 }
740 }
741}
742
743// sameBuffer reports whether two wrappers are the same wl_buffer.
744func sameBuffer(a, b *wayland.Buffer) bool {
745 return a != nil && b != nil && a.Resource == b.Resource
746}
747
748// wakePacer makes the pacer look at its queues now.
749func (s *Server) wakePacer() {
750 select {
751 case s.frameReady <- struct{}{}:
752 default:
753 }
754}