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}