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