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 s.contentMu.Lock()
51 h.after = s.contentSeq[window]
52 s.contentMu.Unlock()
53 s.held = append(s.held, h)
54 // Wake the pacer: it releases the buffer on the next output report.
55 select {
56 case s.frameReady <- struct{}{}:
57 default:
58 }
59}
60
61// release gives a buffer back: its release point is signalled when it
62// has one (explicit sync), else wl_buffer.release.
63func (s *Server) release(b *wayland.Buffer, sync syncHold) {
64 if sync.release.set() || sync.acquire != nil {
65 s.releaseSync(sync)
66 return
67 }
68 if b != nil && b.Resource.Alive() {
69 b.SendRelease()
70 }
71}
72
73// releaseBufferSync holds only a buffer's explicit-sync points: the same
74// buffer committed again with new points (clients reuse one buffer).
75func (s *Server) releaseBufferSync(surf *surface, sync syncHold) {
76 if !sync.release.set() && sync.acquire == nil {
77 return
78 }
79 window, name := surf.root().windowID(), s.frameOutput(surf)
80 if window == 0 || name == "" {
81 s.releaseSync(sync)
82 return
83 }
84 s.contentMu.Lock()
85 after := s.contentSeq[window]
86 s.contentMu.Unlock()
87 s.held = append(s.held, heldBuffer{sync: sync, window: window, after: after, at: time.Now()})
88 s.wakePacer()
89}
90
91// releaseHeld records the output reports and releases the buffers no
92// output reads. Every output reads every window's content, so a buffer
93// waits until no live output shows or queues it and all of them have seen
94// a later content of its window (or heldTimeout passed, without child reads).
95// It reports
96// whether buffers are still held.
97func (s *Server) releaseHeld(now time.Time, reports []ports.OutputPresented) bool {
98 for _, r := range reports {
99 s.reports[r.Output] = r
100 }
101 live := make([]ports.OutputPresented, 0, len(s.outputs))
102 for name := range s.reports {
103 if s.outputByNameExact(name) == nil {
104 delete(s.reports, name)
105 }
106 }
107 for _, r := range s.reports {
108 live = append(live, r)
109 }
110 kept := s.held[:0]
111 for _, h := range s.held {
112 if s.bufferReferenced(h.res) || keepHeld(h, live, now) {
113 kept = append(kept, h)
114 continue
115 }
116 s.release(h.res, h.sync)
117 }
118 clear(s.held[len(kept):])
119 s.held = kept
120 return len(s.held) > 0
121}
122
123// bufferReferenced checks queued, cached and pending commits on the owner
124// goroutine. Never return a buffer still promised to a later commit.
125func (s *Server) bufferReferenced(b *wayland.Buffer) bool {
126 if b == nil {
127 return false
128 }
129 for _, surf := range s.surfaces {
130 if surf.next.attached && sameBuffer(surf.next.buffer, b) {
131 return true
132 }
133 for _, u := range surf.queue {
134 if u.attached && sameBuffer(u.buffer, b) {
135 return true
136 }
137 }
138 }
139 return false
140}
141
142// keepHeld reports whether an output may still read a held buffer: one
143// scans it out or queued it, a child renderer still reads its content, or
144// one has not got a later content of its window yet and heldTimeout has
145// not passed. Child reads are exempt from the timeout until the render
146// finishes or its device shuts down.
147func keepHeld(h heldBuffer, reports []ports.OutputPresented, now time.Time) bool {
148 seen := true
149 for _, r := range reports {
150 if h.id != 0 && (r.Shown == h.id || r.Queued == h.id) {
151 return true
152 }
153 if seq, ok := r.ChildReads[h.window]; ok && seq <= h.after {
154 return true
155 }
156 if r.Seen[h.window] <= h.after {
157 seen = false
158 }
159 }
160 if len(reports) == 0 {
161 seen = false
162 }
163 return !seen && now.Sub(h.at) < heldTimeout
164}