shm.go RawHistoryFolder

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

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