v0.10.0 d5d679d

surfaces.go RawHistoryFolder

1// Package surfaces keeps the latest content of every window for an output's
2// renderer, and the last content of a window that closed while the scene
3// still draws it.
4package surfaces
5
6import (
7	"maps"
8
9	"github.com/bnema/neferwl/internal/adapters/outputkit/capture"
10	"github.com/bnema/neferwl/internal/ports"
11)
12
13// Table is what an output passes to Renderer.Render: the latest content per
14// window. Its owner goroutine is the only user.
15//
16// A window that unmaps sends an empty content right after the core learns
17// of it; the scene that follows may keep drawing the window while it fades
18// out (ports.SceneWindow of a leaving window). The table then keeps the
19// window's last content until a scene no longer lists it, so the renderer
20// draws it from the buffer it already imported or copied: no new upload,
21// no copy. Its Acquire fence is dropped: the first frame that drew the
22// content waited on it, and wayland closes it once the buffer is released.
23//
24// Buffer lifetime: wayland releases the buffer to the client once the
25// output reports the empty content as seen, so the client may draw into,
26// destroy or reuse it during the fade. A kept content is therefore only
27// ever composed by the renderer, from what it already holds: the dmabuf
28// import (its own duplicated descriptors keep the memory alive; a reused
29// buffer shows stale pixels at worst) or the GPU copy of the wl_shm buffer
30// (the GPU never reads the pool mapping, and a pool's fd close does not
31// unmap it). A kept content is never scanned out on a plane and never
32// reported as shown: outputs ask Kept. Window capture never reaches it:
33// core resolves a window session through its workspace, which the closed
34// window has left, so the session ends (CaptureReasonWindowGone) before
35// the leaving scene; an output or workspace capture composes the fading
36// window like the screen does.
37// Every Acquire fence of the kept content (root and children) is dropped:
38// the frame that first drew it waited on them, and wayland closes them
39// when the buffers are released.
40type Table struct {
41	m map[ports.WindowID]ports.SurfaceContent
42	// kept are the windows drawn from a content their client withdrew.
43	kept map[ports.WindowID]struct{}
44}
45
46// New returns an empty table.
47func New() *Table {
48	return &Table{m: make(map[ports.WindowID]ports.SurfaceContent), kept: make(map[ports.WindowID]struct{})}
49}
50
51// Map is the contents the renderer draws from. It is the table's own map:
52// read it, never keep it across an Update or Prune.
53func (t *Table) Map() map[ports.WindowID]ports.SurfaceContent { return t.m }
54
55// Update takes a window's new content. An empty one (the window unmapped)
56// keeps the previous content, without its Acquire fence, while scene draws
57// the window; it drops it otherwise.
58func (t *Table) Update(c ports.SurfaceContent, scene ports.Scene) {
59	if !c.Empty() {
60		t.m[c.ID] = c
61		delete(t.kept, c.ID)
62		return
63	}
64	prev, ok := t.m[c.ID]
65	if !ok || !capture.Shows(scene, c.ID) {
66		delete(t.m, c.ID)
67		delete(t.kept, c.ID)
68		return
69	}
70	prev.Acquire = nil
71	if len(prev.Children) > 0 {
72		// The slice is shared with wayland's publication: copy before
73		// zeroing (one allocation per unmap).
74		children := make([]ports.Subsurface, len(prev.Children))
75		copy(children, prev.Children)
76		for i := range children {
77			children[i].Acquire = nil
78		}
79		prev.Children = children
80	}
81	t.m[c.ID] = prev
82	t.kept[c.ID] = struct{}{}
83}
84
85// Prune drops the kept contents of windows scene no longer draws. Outputs
86// call it with every scene they accept.
87func (t *Table) Prune(scene ports.Scene) {
88	for id := range t.kept {
89		if !capture.Shows(scene, id) {
90			delete(t.m, id)
91			delete(t.kept, id)
92		}
93	}
94}
95
96// Kept reports whether id is drawn from a content its client withdrew: a
97// buffer the client may have got back. Outputs compose it only: no plane
98// scans it out, no capture or presentation report counts it as shown.
99func (t *Table) Kept(id ports.WindowID) bool {
100	_, ok := t.kept[id]
101	return ok
102}
103
104// Shown is the presentation rule both output backends report with: id was
105// shown to its client when scene draws it and its content is not kept. A
106// nil kept counts nothing as kept.
107func Shown(scene ports.Scene, id ports.WindowID, kept func(ports.WindowID) bool) bool {
108	return scene.Shows(id) && (kept == nil || !kept(id))
109}
110
111// Apply takes every content of b into the table, as the output's owner does
112// when it receives a batch: Update per item against scene, seen advanced to
113// the highest Seq per window, then the batch reset and handed back through
114// ret. Reset-then-return is the ownership rule of ports.ContentBatch; b is
115// not the caller's after Apply.
116//
117// dirty reports a content of a window scene draws (windows on other outputs
118// or workspaces need no frame). seenChanged reports a window whose seen Seq
119// moved forward: a new report is due.
120func (t *Table) Apply(b *ports.ContentBatch, scene ports.Scene, seen map[ports.WindowID]uint64, ret ports.ContentReturn) (dirty, seenChanged bool) {
121	for i := range b.Items {
122		c := b.Items[i]
123		t.Update(c, scene)
124		if c.Seq > seen[c.ID] {
125			seen[c.ID] = c.Seq
126			seenChanged = true
127		}
128		dirty = dirty || capture.Shows(scene, c.ID)
129	}
130	b.Reset()
131	ret.Return(b)
132	return dirty, seenChanged
133}
134
135// ShowsSnapshot builds the Shows of successive flips. Its owner goroutine is
136// the only user. A flip's Shows map is an immutable snapshot: wayland may
137// still read the one of an earlier flip on another goroutine, so the scratch
138// map is never sent and a snapshot is replaced by a clone only when it
139// changed. The zero value is ready; an unchanged result allocates nothing.
140type ShowsSnapshot struct {
141	scratch, snapshot map[ports.WindowID]uint64
142}
143
144// FromSeen returns the windows of seen that scene showed to their client
145// (Shown), at their seen Seq. The result is shared with earlier flips of the
146// same content: never modify it.
147func (s *ShowsSnapshot) FromSeen(scene ports.Scene, seen map[ports.WindowID]uint64, kept func(ports.WindowID) bool) map[ports.WindowID]uint64 {
148	s.begin()
149	for id, seq := range seen {
150		if Shown(scene, id, kept) {
151			s.scratch[id] = seq
152		}
153	}
154	return s.settle()
155}
156
157// FromContents returns the windows of contents that scene showed to their
158// client (Shown), at their content Seq. The result is shared with earlier
159// flips of the same content: never modify it.
160func (s *ShowsSnapshot) FromContents(scene ports.Scene, contents map[ports.WindowID]ports.SurfaceContent, kept func(ports.WindowID) bool) map[ports.WindowID]uint64 {
161	s.begin()
162	for id, c := range contents {
163		if Shown(scene, id, kept) {
164			s.scratch[id] = c.Seq
165		}
166	}
167	return s.settle()
168}
169
170func (s *ShowsSnapshot) begin() {
171	if s.scratch == nil {
172		s.scratch = make(map[ports.WindowID]uint64)
173	}
174	clear(s.scratch)
175}
176
177func (s *ShowsSnapshot) settle() map[ports.WindowID]uint64 {
178	if s.snapshot == nil || !maps.Equal(s.snapshot, s.scratch) {
179		s.snapshot = maps.Clone(s.scratch)
180	}
181	return s.snapshot
182}

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 23

Tags 10

↑↓ moveenter openesc close