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 and the set binding it.
168type shmCopy struct {
169 gpu gpuBuffer
170 pool vk.DescriptorPool
171 set vk.DescriptorSet
172 holds shmState
173 valid bool
174}
175
176// shmSurface is the GPU copy of one surface's wl_shm content.
177type shmSurface struct {
178 bufs [2]*shmCopy
179 last uint64 // frame that drew it
180}
181
182// shmCached returns the GPU buffer already holding a surface's content st,
183// or nil. It needs no client pixels: a kept window whose pool is closed
184// still draws from it.
185func (r *Renderer) shmCached(key shmKey, st shmState) *shmCopy {
186 s := r.shm[key]
187 if s == nil {
188 return nil
189 }
190 s.last = r.frame
191 for _, c := range s.bufs {
192 if c != nil && c.valid && c.holds.w == st.w && c.holds.h == st.h && c.holds.seq == st.seq {
193 c.gpu.last = r.frame
194 return c
195 }
196 }
197 return nil
198}
199
200// shmCopyFor copies a surface's content st from the client pixels (stride
201// bytes per row from offset) into a GPU buffer; the caller found none
202// holding it (shmCached). A buffer holding an older content of the same
203// size gets only what changed since (damage, in buffer pixels; false:
204// everything).
205func (r *Renderer) shmCopyFor(key shmKey, st shmState, pixels []byte, offset, stride int, damage func(since uint64) ([]ports.Rect, bool)) (*shmCopy, error) {
206 s := r.shm[key]
207 if s == nil {
208 s = &shmSurface{}
209 r.shm[key] = s
210 }
211 s.last = r.frame
212 // The older buffer takes the new content.
213 i := 0
214 if s.bufs[0] != nil && (s.bufs[1] == nil || s.bufs[1].gpu.last < s.bufs[0].gpu.last) {
215 i = 1
216 }
217 size := st.w * st.h * 4
218 if size > r.maxRange {
219 return nil, fmt.Errorf("shm buffer of %d bytes past the storage buffer range", size)
220 }
221 c := s.bufs[i]
222 if c != nil && c.gpu.size != size {
223 r.retire(c.gpu.last, func() { r.freeShmCopy(c) })
224 s.bufs[i], c = nil, nil
225 }
226 if c == nil {
227 c = &shmCopy{}
228 if err := r.newGPUBuffer(&c.gpu, size); err != nil {
229 return nil, err
230 }
231 var err error
232 if c.pool, c.set, err = r.newSet(r.compose.dummyView, c.gpu.buffer); err != nil {
233 r.freeGPUBuffer(&c.gpu)
234 return nil, err
235 }
236 s.bufs[i] = c
237 }
238 if err := r.waitFrame(c.gpu.last); err != nil {
239 return nil, err
240 }
241 dst := unsafe.Slice((*byte)(c.gpu.mapped), size)
242 row := st.w * 4
243 rects := []ports.Rect{{W: st.w, H: st.h}}
244 if c.valid && c.holds.w == st.w && c.holds.h == st.h && damage != nil {
245 if d, ok := damage(c.holds.windowSeq); ok {
246 rects = d
247 }
248 }
249 // The client may shrink its pool under the mapping: its window then
250 // shows garbage for this frame, never a crash, and is copied again.
251 // Damage of the frames since this buffer was written can overlap: each
252 // pixel is copied once.
253 r.shmRects = disjointRects(r.shmRects[:0], rects, st.w, st.h)
254 c.valid = copyGuarded(func() {
255 for _, rc := range r.shmRects {
256 for y := rc.Min.Y; y < rc.Max.Y; y++ {
257 d := y*row + rc.Min.X*4
258 s := offset + y*stride + rc.Min.X*4
259 n := rc.Dx() * 4
260 copy(dst[d:d+n], pixels[s:s+n])
261 r.copied += n
262 }
263 }
264 })
265 c.holds, c.gpu.last = st, r.frame
266 return c, nil
267}
268
269func (r *Renderer) freeShmCopy(c *shmCopy) {
270 if c.pool != 0 {
271 r.dd.DestroyDescriptorPool(r.device, c.pool, nil)
272 c.pool = 0
273 }
274 r.freeGPUBuffer(&c.gpu)
275}
276
277// dropShm frees the GPU buffers of surfaces not drawn for importTTL frames.
278func (r *Renderer) dropShm() {
279 for key, s := range r.shm {
280 if r.frame-s.last > importTTL {
281 r.retireShm(s)
282 delete(r.shm, key)
283 }
284 }
285}
286
287// retireShm frees a surface's GPU buffers once the frames reading them
288// completed.
289func (r *Renderer) retireShm(s *shmSurface) {
290 for _, c := range s.bufs {
291 if c != nil {
292 r.retire(c.gpu.last, func() { r.freeShmCopy(c) })
293 }
294 }
295}
296
297// maxDisjointRects bounds the pieces disjointRects cuts the damage into: the
298// rects come from the client, and each cut can add three pieces.
299const maxDisjointRects = 64
300
301// disjointRects appends to dst the parts of rects inside a w×h buffer, with
302// no pixel in two of them. Past maxDisjointRects pieces it appends their
303// bounding box instead: some undamaged pixels are copied again. It reuses
304// dst's capacity.
305func disjointRects(dst []image.Rectangle, rects []ports.Rect, w, h int) []image.Rectangle {
306 bounds := image.Rect(0, 0, w, h)
307 start := len(dst)
308 for _, rc := range rects {
309 if len(dst)-start > maxDisjointRects {
310 var box image.Rectangle
311 for _, rc := range rects {
312 box = box.Union(image.Rect(rc.X, rc.Y, rc.X+rc.W, rc.Y+rc.H).Intersect(bounds))
313 }
314 return append(dst[:start], box)
315 }
316 pending := len(dst)
317 dst = append(dst, image.Rect(rc.X, rc.Y, rc.X+rc.W, rc.Y+rc.H).Intersect(bounds))
318 // Cut the new rect by each earlier one; the pieces it leaves are
319 // appended after it and cut in turn.
320 for i := pending; i < len(dst); i++ {
321 if dst[i].Empty() {
322 dst[i] = dst[len(dst)-1]
323 dst = dst[:len(dst)-1]
324 i--
325 continue
326 }
327 for _, o := range dst[:pending] {
328 in := dst[i].Intersect(o)
329 if in.Empty() {
330 continue
331 }
332 r := dst[i]
333 dst[i] = image.Rect(r.Min.X, r.Min.Y, r.Max.X, in.Min.Y)
334 dst = append(dst,
335 image.Rect(r.Min.X, in.Max.Y, r.Max.X, r.Max.Y),
336 image.Rect(r.Min.X, in.Min.Y, in.Min.X, in.Max.Y),
337 image.Rect(in.Max.X, in.Min.Y, r.Max.X, in.Max.Y))
338 i--
339 break
340 }
341 }
342 }
343 return dst
344}