feat/data-dnd e05cb15

shm.go RawHistoryFolder

1package vulkan
2
3import (
4	"fmt"
5	"image"
6	"runtime/debug"
7	"unsafe"
8
9	"github.com/bnema/neferwl/internal/ports"
10	vk "github.com/bnema/purego-vulkan/vulkan"
11	"golang.org/x/sys/unix"
12)
13
14// wl_shm buffers: each client pool is mapped once, read only. A surface's
15// pixels are copied once per new content into a persistently mapped,
16// host-visible GPU buffer (device-local when the GPU exposes it, ReBAR)
17// that the fragment shader reads directly: one memcpy per row, no staging
18// copy, no resample, no alpha fix-up on the CPU. Each surface has two GPU
19// buffers used in turn, so the CPU never writes one a frame in flight
20// reads. Mappings and buffers are dropped after importTTL frames unused,
21// or idleTTL of wall-clock time (Trim).
22
23// mapping is a client pool mapped read-only.
24type mapping struct {
25	data []byte
26	last uint64
27}
28
29// shmPixels returns the pool bytes of a buffer, mapping the pool once and
30// again when it grew past the mapping.
31func (r *Renderer) shmPixels(b *ports.SHMBuffer, end int) ([]byte, error) {
32	if m := r.pools[b.Pool]; m != nil {
33		if end <= len(m.data) {
34			m.last = r.frame
35			return m.data, nil
36		}
37		// Draws collected earlier this frame may still read the old
38		// mapping: it is unmapped once the frame is recorded.
39		r.retired = append(r.retired, m.data)
40		delete(r.pools, b.Pool)
41	}
42	fd, err := dupFile(b.File)
43	if err != nil {
44		return nil, err
45	}
46	defer unix.Close(fd)
47	var st unix.Stat_t
48	if err := unix.Fstat(fd, &st); err != nil {
49		return nil, err
50	}
51	if int64(end) > st.Size {
52		return nil, fmt.Errorf("shm pool of %d bytes, buffer ends at %d", st.Size, end)
53	}
54	data, err := unix.Mmap(fd, 0, int(st.Size), unix.PROT_READ, unix.MAP_SHARED)
55	if err != nil {
56		return nil, err
57	}
58	r.pools[b.Pool] = &mapping{data: data, last: r.frame}
59	return data, nil
60}
61
62// dropPools unmaps replaced mappings and pools not drawn for importTTL
63// frames. The GPU never reads a mapping: this runs once the CPU copies of
64// a frame are done.
65func (r *Renderer) dropPools() {
66	for _, data := range r.retired {
67		_ = unix.Munmap(data)
68	}
69	r.retired = r.retired[:0]
70	for id, m := range r.pools {
71		if r.frame-m.last > importTTL {
72			_ = unix.Munmap(m.data)
73			delete(r.pools, id)
74		}
75	}
76}
77
78// copyGuarded runs copy with page faults turned into a recoverable panic:
79// a client may shrink its pool file under the mapping (SIGBUS). It reports
80// false when the client broke its buffer.
81func copyGuarded(copy func()) (ok bool) {
82	defer debug.SetPanicOnFault(debug.SetPanicOnFault(true))
83	defer func() {
84		if recover() != nil {
85			ok = false
86		}
87	}()
88	copy()
89	return true
90}
91
92// gpuBuffer is a host-visible storage buffer, mapped for its whole life.
93type gpuBuffer struct {
94	buffer vk.Buffer
95	memory vk.DeviceMemory
96	mapped unsafe.Pointer
97	size   int
98	// last is the frame that last read it: it is written or freed only
99	// once that frame completed.
100	last uint64
101}
102
103func (r *Renderer) newGPUBuffer(b *gpuBuffer, size int) error {
104	d := r.dd
105	ok := false
106	defer func() {
107		if !ok {
108			r.freeGPUBuffer(b)
109		}
110	}()
111	info := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: vk.DeviceSize(size), Usage: vk.BufferUsageStorageBufferBit, SharingMode: vk.SharingModeExclusive}
112	if err := checked("vkCreateBuffer(shm)", d.CreateBuffer(r.device, &info, nil, &b.buffer)); err != nil {
113		return err
114	}
115	var req vk.MemoryRequirements
116	d.GetBufferMemoryRequirements(r.device, b.buffer, &req)
117	host := vk.MemoryPropertyFlags(vk.MemoryPropertyHostVisibleBit | vk.MemoryPropertyHostCoherentBit)
118	kind, err := r.findMemoryType(req.MemoryTypeBits, host|vk.MemoryPropertyDeviceLocalBit)
119	if err != nil {
120		kind, err = r.findMemoryType(req.MemoryTypeBits, host)
121	}
122	if err != nil {
123		return err
124	}
125	alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind}
126	if err := checked("vkAllocateMemory(shm)", d.AllocateMemory(r.device, &alloc, nil, &b.memory)); err != nil {
127		return err
128	}
129	if err := checked("vkBindBufferMemory(shm)", d.BindBufferMemory(r.device, b.buffer, b.memory, 0)); err != nil {
130		return err
131	}
132	if err := checked("vkMapMemory(shm)", d.MapMemory(r.device, b.memory, 0, vk.DeviceSize(size), 0, &b.mapped)); err != nil {
133		return err
134	}
135	b.size, ok = size, true
136	return nil
137}
138
139func (r *Renderer) freeGPUBuffer(b *gpuBuffer) {
140	d := r.dd
141	if b.mapped != nil {
142		d.UnmapMemory(r.device, b.memory)
143	}
144	if b.buffer != 0 {
145		d.DestroyBuffer(r.device, b.buffer, nil)
146	}
147	if b.memory != 0 {
148		d.FreeMemory(r.device, b.memory, nil)
149	}
150	*b = gpuBuffer{}
151}
152
153// shmKey identifies a surface independently of its position in a tree.
154type shmKey struct {
155	win     ports.WindowID
156	surface uint64
157}
158
159// shmState is what a GPU buffer holds: a w×h client buffer at a content
160// Seq.
161type shmState struct {
162	w, h      int
163	seq       uint64
164	windowSeq uint64 // root damage history uses window publications
165}
166
167// shmCopy is one of a surface's two GPU buffers.
168type shmCopy struct {
169	gpu   gpuBuffer
170	holds shmState
171	valid bool
172}
173
174// shmSurface is the GPU copy of one surface's wl_shm content.
175type shmSurface struct {
176	bufs [2]*shmCopy
177	last uint64 // frame that drew it
178}
179
180// shmCached returns the GPU buffer already holding a surface's content st,
181// or nil. It needs no client pixels: a kept window whose pool is closed
182// still draws from it.
183func (r *Renderer) shmCached(key shmKey, st shmState) *shmCopy {
184	s := r.shm[key]
185	if s == nil {
186		return nil
187	}
188	s.last = r.frame
189	for _, c := range s.bufs {
190		if c != nil && c.valid && c.holds.w == st.w && c.holds.h == st.h && c.holds.seq == st.seq {
191			c.gpu.last = r.frame
192			return c
193		}
194	}
195	return nil
196}
197
198// shmCopyFor copies a surface's content st from the client pixels (stride
199// bytes per row from offset) into a GPU buffer; the caller found none
200// holding it (shmCached). A buffer holding an older content of the same
201// size gets only what changed since (damage, in buffer pixels; false:
202// everything).
203func (r *Renderer) shmCopyFor(key shmKey, st shmState, pixels []byte, offset, stride int, damage func(since uint64) ([]ports.Rect, bool)) (*shmCopy, error) {
204	s := r.shm[key]
205	if s == nil {
206		s = &shmSurface{}
207		r.shm[key] = s
208	}
209	s.last = r.frame
210	// The older buffer takes the new content.
211	i := 0
212	if s.bufs[0] != nil && (s.bufs[1] == nil || s.bufs[1].gpu.last < s.bufs[0].gpu.last) {
213		i = 1
214	}
215	size := st.w * st.h * 4
216	if size > r.maxRange {
217		return nil, fmt.Errorf("shm buffer of %d bytes past the storage buffer range", size)
218	}
219	c := s.bufs[i]
220	if c != nil && c.gpu.size != size {
221		r.retire(c.gpu.last, func() { r.freeShmCopy(c) })
222		s.bufs[i], c = nil, nil
223	}
224	if c == nil {
225		c = &shmCopy{}
226		if err := r.newGPUBuffer(&c.gpu, size); err != nil {
227			return nil, err
228		}
229		s.bufs[i] = c
230	}
231	if err := r.waitFrame(c.gpu.last); err != nil {
232		return nil, err
233	}
234	dst := unsafe.Slice((*byte)(c.gpu.mapped), size)
235	row := st.w * 4
236	rects := []ports.Rect{{W: st.w, H: st.h}}
237	if c.valid && c.holds.w == st.w && c.holds.h == st.h && damage != nil {
238		if d, ok := damage(c.holds.windowSeq); ok {
239			rects = d
240		}
241	}
242	// The client may shrink its pool under the mapping: its window then
243	// shows garbage for this frame, never a crash, and is copied again.
244	// Damage of the frames since this buffer was written can overlap: each
245	// pixel is copied once.
246	r.shmRects = disjointRects(r.shmRects[:0], rects, st.w, st.h)
247	c.valid = copyGuarded(func() {
248		for _, rc := range r.shmRects {
249			for y := rc.Min.Y; y < rc.Max.Y; y++ {
250				d := y*row + rc.Min.X*4
251				s := offset + y*stride + rc.Min.X*4
252				n := rc.Dx() * 4
253				copy(dst[d:d+n], pixels[s:s+n])
254				r.copied += n
255			}
256		}
257	})
258	c.holds, c.gpu.last = st, r.frame
259	return c, nil
260}
261
262func (r *Renderer) freeShmCopy(c *shmCopy) {
263	r.invalidateCompose(resourceKey{shm: c})
264	r.freeGPUBuffer(&c.gpu)
265}
266
267// dropShm frees the GPU buffers of surfaces not drawn for importTTL frames.
268func (r *Renderer) dropShm() {
269	for key, s := range r.shm {
270		if r.frame-s.last > importTTL {
271			r.retireShm(s)
272			delete(r.shm, key)
273		}
274	}
275}
276
277// retireShm frees a surface's GPU buffers once the frames reading them
278// completed.
279func (r *Renderer) retireShm(s *shmSurface) {
280	for _, c := range s.bufs {
281		if c != nil {
282			r.retire(c.gpu.last, func() { r.freeShmCopy(c) })
283		}
284	}
285}
286
287// maxDisjointRects bounds the pieces disjointRects cuts the damage into: the
288// rects come from the client, and each cut can add three pieces.
289const maxDisjointRects = 64
290
291// disjointRects appends to dst the parts of rects inside a w×h buffer, with
292// no pixel in two of them. Past maxDisjointRects pieces it appends their
293// bounding box instead: some undamaged pixels are copied again. It reuses
294// dst's capacity.
295func disjointRects(dst []image.Rectangle, rects []ports.Rect, w, h int) []image.Rectangle {
296	bounds := image.Rect(0, 0, w, h)
297	start := len(dst)
298	for _, rc := range rects {
299		if len(dst)-start > maxDisjointRects {
300			var box image.Rectangle
301			for _, rc := range rects {
302				box = box.Union(image.Rect(rc.X, rc.Y, rc.X+rc.W, rc.Y+rc.H).Intersect(bounds))
303			}
304			return append(dst[:start], box)
305		}
306		pending := len(dst)
307		dst = append(dst, image.Rect(rc.X, rc.Y, rc.X+rc.W, rc.Y+rc.H).Intersect(bounds))
308		// Cut the new rect by each earlier one; the pieces it leaves are
309		// appended after it and cut in turn.
310		for i := pending; i < len(dst); i++ {
311			if dst[i].Empty() {
312				dst[i] = dst[len(dst)-1]
313				dst = dst[:len(dst)-1]
314				i--
315				continue
316			}
317			for _, o := range dst[:pending] {
318				in := dst[i].Intersect(o)
319				if in.Empty() {
320					continue
321				}
322				r := dst[i]
323				dst[i] = image.Rect(r.Min.X, r.Min.Y, r.Max.X, in.Min.Y)
324				dst = append(dst,
325					image.Rect(r.Min.X, in.Max.Y, r.Max.X, r.Max.Y),
326					image.Rect(r.Min.X, in.Min.Y, in.Min.X, in.Max.Y),
327					image.Rect(in.Max.X, in.Min.Y, r.Max.X, in.Max.Y))
328				i--
329				break
330			}
331		}
332	}
333	return dst
334}

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