1package wayland
2
3import (
4 "time"
5
6 "github.com/bnema/go-wayland-bindings/server/wayland"
7 "github.com/bnema/neferwl/internal/ports"
8)
9
10// Buffer release. Outputs read client buffers in place: the renderer
11// samples them when it draws, and an output scanning out a client buffer
12// (ADR 006) shows it until the next flip. A replaced wl_buffer therefore
13// goes back to the client (release) only when the output showing its
14// window reports (ports.OutputPresented) that it has a later content of
15// that window and neither shows nor queues the buffer. Outputs keep only
16// the latest content of a window, so they never draw an older buffer
17// again. heldTimeout guards against an output that stopped reporting
18// (switched away, stalled): a buffer it does not scan out or read on a
19// separate child device is released then.
20
21// heldTimeout releases held buffers of an output that stopped reporting.
22const heldTimeout = 100 * time.Millisecond
23
24type heldBuffer struct {
25 res *wayland.Buffer
26 // sync is the buffer's explicit-sync state: its release point is
27 // signalled instead of wl_buffer.release.
28 sync syncHold
29 window ports.WindowID
30 id uint64 // DMABuf ID, 0 for wl_shm
31 after uint64 // the window's content Seq when the buffer was replaced
32 at time.Time
33}
34
35// releaseBuffer releases a replaced buffer, or holds it until the output
36// showing its window no longer reads it.
37func (s *Server) releaseBuffer(surf *surface, b *wayland.Buffer, sync syncHold) {
38 // An invisible window is held like a shown one: a scanout plane may
39 // still read its buffer until the flip that hides it.
40 window, name := surf.root().windowID(), s.frameOutput(surf)
41 if !b.Resource.Alive() || window == 0 || name == "" {
42 // Not drawn (unmapped, a cursor): nothing reads it.
43 s.release(b, sync)
44 return
45 }
46 h := heldBuffer{res: b, sync: sync, window: window, at: time.Now()}
47 if d, ok := s.buffers[b.Resource].(*dmabufBuffer); ok {
48 h.id = d.buf.ID
49 }
50 h.after = s.contentSeq[window]
51 s.held = append(s.held, h)
52 // Wake the pacer: it releases the buffer on the next output report.
53 s.wakePacer()
54}
55
56// release gives a buffer back: its release point is signalled when it
57// has one (explicit sync), else wl_buffer.release.
58func (s *Server) release(b *wayland.Buffer, sync syncHold) {
59 if sync.release.set() || sync.acquire != nil {
60 s.releaseSync(sync)
61 return
62 }
63 if b != nil && b.Resource.Alive() {
64 b.SendRelease()
65 }
66}
67
68// releaseBufferSync holds only a buffer's explicit-sync points: the same
69// buffer committed again with new points (clients reuse one buffer).
70func (s *Server) releaseBufferSync(surf *surface, sync syncHold) {
71 if !sync.release.set() && sync.acquire == nil {
72 return
73 }
74 window, name := surf.root().windowID(), s.frameOutput(surf)
75 if window == 0 || name == "" {
76 s.releaseSync(sync)
77 return
78 }
79 after := s.contentSeq[window]
80 s.held = append(s.held, heldBuffer{sync: sync, window: window, after: after, at: time.Now()})
81 s.wakePacer()
82}
83
84// releaseHeld records the output reports and releases the buffers no
85// output reads. Every output reads every window's content, so a buffer
86// waits until no live output shows or queues it and all of them have seen
87// a later content of its window (or heldTimeout passed, without child reads).
88// It reports
89// whether buffers are still held.
90func (s *Server) releaseHeld(now time.Time, reports []ports.OutputPresented) bool {
91 for _, r := range reports {
92 s.reports[r.Output] = r
93 }
94 live := s.heldLive[:0]
95 for name := range s.reports {
96 if s.outputByNameExact(name) == nil {
97 delete(s.reports, name)
98 }
99 }
100 for _, r := range s.reports {
101 live = append(live, r)
102 }
103 // keepHeld only reads live: the scratch holds nothing across calls.
104 s.heldLive = live
105 kept := s.held[:0]
106 for _, h := range s.held {
107 if s.bufferReferenced(h.res) || keepHeld(h, live, now) {
108 kept = append(kept, h)
109 continue
110 }
111 s.release(h.res, h.sync)
112 }
113 clear(s.held[len(kept):])
114 s.held = kept
115 clear(live) // the reports carry maps: do not retain them
116 return len(s.held) > 0
117}
118
119// bufferReferenced checks queued, cached and pending commits on the owner
120// goroutine. Never return a buffer still promised to a later commit.
121func (s *Server) bufferReferenced(b *wayland.Buffer) bool {
122 if b == nil {
123 return false
124 }
125 for _, surf := range s.surfaces {
126 if surf.next.attached && sameBuffer(surf.next.buffer, b) {
127 return true
128 }
129 for _, u := range surf.queue {
130 if u.attached && sameBuffer(u.buffer, b) {
131 return true
132 }
133 }
134 }
135 return false
136}
137
138// keepHeld reports whether an output may still read a held buffer: one
139// scans it out or queued it, a child renderer still reads its content, or
140// one has not got a later content of its window yet and heldTimeout has
141// not passed. Child reads are exempt from the timeout until the render
142// finishes or its device shuts down.
143func keepHeld(h heldBuffer, reports []ports.OutputPresented, now time.Time) bool {
144 seen := true
145 for _, r := range reports {
146 if h.id != 0 && (r.Shown == h.id || r.Queued == h.id) {
147 return true
148 }
149 if seq, ok := r.ChildReads[h.window]; ok && seq <= h.after {
150 return true
151 }
152 if r.Seen[h.window] <= h.after {
153 seen = false
154 }
155 }
156 if len(reports) == 0 {
157 seen = false
158 }
159 return !seen && now.Sub(h.at) < heldTimeout
160}