release.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close