1package wayland
2
3import (
4 "time"
5
6 "github.com/bnema/neferwl/internal/ports"
7 "github.com/bnema/purego-libwayland/protocol/wayland"
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 is released then.
19
20// heldTimeout releases held buffers of an output that stopped reporting.
21const heldTimeout = 100 * time.Millisecond
22
23type heldBuffer struct {
24 res *wayland.Buffer
25 // sync is the buffer's explicit-sync state: its release point is
26 // signalled instead of wl_buffer.release.
27 sync syncHold
28 window ports.WindowID
29 id uint64 // DMABuf ID, 0 for wl_shm
30 after uint64 // the window's content Seq when the buffer was replaced
31 at time.Time
32}
33
34// releaseBuffer releases a replaced buffer, or holds it until the output
35// showing its window no longer reads it.
36func (s *Server) releaseBuffer(surf *surface, b *wayland.Buffer, sync syncHold) {
37 window, name := surf.root().windowID(), s.frameOutput(surf)
38 if !b.Resource.Alive() || window == 0 || name == "" {
39 // Not drawn (unmapped, hidden, a cursor): nothing reads it.
40 s.release(b, sync)
41 return
42 }
43 h := heldBuffer{res: b, sync: sync, window: window, at: time.Now()}
44 if d, ok := s.buffers[b.Resource].(*dmabufBuffer); ok {
45 h.id = d.buf.ID
46 }
47 s.contentMu.Lock()
48 h.after = s.contentSeq[window]
49 s.contentMu.Unlock()
50 s.held = append(s.held, h)
51 // Wake the pacer: it releases the buffer on the next output report.
52 select {
53 case s.frameReady <- struct{}{}:
54 default:
55 }
56}
57
58// release gives a buffer back: its release point is signalled when it
59// has one (explicit sync), else wl_buffer.release.
60func (s *Server) release(b *wayland.Buffer, sync syncHold) {
61 if sync.release.set() || sync.acquire != nil {
62 s.releaseSync(sync)
63 return
64 }
65 if b != nil && b.Resource.Alive() {
66 b.SendRelease()
67 }
68}
69
70// releaseBufferSync holds only a buffer's explicit-sync points: the same
71// buffer committed again with new points (clients reuse one buffer).
72func (s *Server) releaseBufferSync(surf *surface, sync syncHold) {
73 if !sync.release.set() && sync.acquire == nil {
74 return
75 }
76 window := surf.root().windowID()
77 if window == 0 || s.frameOutput(surf) == "" {
78 s.releaseSync(sync)
79 return
80 }
81 s.contentMu.Lock()
82 after := s.contentSeq[window]
83 s.contentMu.Unlock()
84 s.held = append(s.held, heldBuffer{sync: sync, window: window, after: after, at: time.Now()})
85 s.wakePacer()
86}
87
88// releaseHeld records the output reports and releases the buffers no
89// output reads. Every output reads every window's content, so a buffer
90// waits until no live output shows or queues it and all of them have seen
91// a later content of its window (or heldTimeout passed). It reports
92// whether buffers are still held.
93func (s *Server) releaseHeld(now time.Time, reports []ports.OutputPresented) bool {
94 for _, r := range reports {
95 s.reports[r.Output] = r
96 }
97 live := make([]ports.OutputPresented, 0, len(s.outputs))
98 for name := range s.reports {
99 if s.outputByNameExact(name) == nil {
100 delete(s.reports, name)
101 }
102 }
103 for _, r := range s.reports {
104 live = append(live, r)
105 }
106 kept := s.held[:0]
107 for _, h := range s.held {
108 if keepHeld(h, live, now) {
109 kept = append(kept, h)
110 continue
111 }
112 s.release(h.res, h.sync)
113 }
114 clear(s.held[len(kept):])
115 s.held = kept
116 return len(s.held) > 0
117}
118
119// keepHeld reports whether an output may still read a held buffer: one
120// scans it out or queued it, or one has not got a later content of its
121// window yet and heldTimeout has not passed.
122func keepHeld(h heldBuffer, reports []ports.OutputPresented, now time.Time) bool {
123 seen := true
124 for _, r := range reports {
125 if h.id != 0 && (r.Shown == h.id || r.Queued == h.id) {
126 return true
127 }
128 if r.Seen[h.window] <= h.after {
129 seen = false
130 }
131 }
132 if len(reports) == 0 {
133 seen = false
134 }
135 return !seen && now.Sub(h.at) < heldTimeout
136}