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// shmCached returns the GPU buffer already holding a surface's content st,
182// or nil. It needs no client pixels: a kept window whose pool is closed
183// still draws from it.
184func (r *Renderer) shmCached(key shmKey, st shmState) *shmCopy {
185 s := r.shm[key]
186 if s == nil {
187 return nil
188 }
189 s.last = r.frame
190 for _, c := range s.bufs {
191 if c != nil && c.valid && c.holds.w == st.w && c.holds.h == st.h && c.holds.seq == st.seq {
192 c.gpu.last = r.frame
193 return c
194 }
195 }
196 return nil
197}
198
199// shmCopyFor copies a surface's content st from the client pixels (stride
200// bytes per row from offset) into a GPU buffer; the caller found none
201// holding it (shmCached). A buffer holding an older content of the same
202// size gets only what changed since (damage, in buffer pixels; false:
203// everything).
204func (r *Renderer) shmCopyFor(key shmKey, st shmState, pixels []byte, offset, stride int, damage func(since uint64) ([]ports.Rect, bool)) (*shmCopy, error) {
205 s := r.shm[key]
206 if s == nil {
207 s = &shmSurface{}
208 r.shm[key] = s
209 }
210 s.last = r.frame
211 // The older buffer takes the new content.
212 i := 0
213 if s.bufs[0] != nil && (s.bufs[1] == nil || s.bufs[1].gpu.last < s.bufs[0].gpu.last) {
214 i = 1
215 }
216 size := st.w * st.h * 4
217 if size > r.maxRange {
218 return nil, fmt.Errorf("shm buffer of %d bytes past the storage buffer range", size)
219 }
220 c := s.bufs[i]
221 if c != nil && c.gpu.size != size {
222 r.retire(c.gpu.last, func() { r.freeShmCopy(c) })
223 s.bufs[i], c = nil, nil
224 }
225 if c == nil {
226 c = &shmCopy{}
227 if err := r.newGPUBuffer(&c.gpu, size); err != nil {
228 return nil, err
229 }
230 var err error
231 if c.pool, c.set, err = r.newSet(r.compose.dummyView, c.gpu.buffer); err != nil {
232 r.freeGPUBuffer(&c.gpu)
233 return nil, err
234 }
235 s.bufs[i] = c
236 }
237 if err := r.waitFrame(c.gpu.last); err != nil {
238 return nil, err
239 }
240 dst := unsafe.Slice((*byte)(c.gpu.mapped), size)
241 row := st.w * 4
242 rects := []ports.Rect{{W: st.w, H: st.h}}
243 if c.valid && c.holds.w == st.w && c.holds.h == st.h && damage != nil {
244 if d, ok := damage(c.holds.windowSeq); ok {
245 rects = d
246 }
247 }
248 // The client may shrink its pool under the mapping: its window then
249 // shows garbage for this frame, never a crash, and is copied again.
250 c.valid = copyGuarded(func() {
251 for _, rc := range rects {
252 x0, y0 := max(rc.X, 0), max(rc.Y, 0)
253 x1, y1 := min(rc.X+rc.W, st.w), min(rc.Y+rc.H, st.h)
254 for y := y0; y < y1; y++ {
255 d := y*row + x0*4
256 s := offset + y*stride + x0*4
257 n := (x1 - x0) * 4
258 copy(dst[d:d+n], pixels[s:s+n])
259 r.copied += n
260 }
261 }
262 })
263 c.holds, c.gpu.last = st, r.frame
264 return c, nil
265}
266
267func (r *Renderer) freeShmCopy(c *shmCopy) {
268 if c.pool != 0 {
269 r.dd.DestroyDescriptorPool(r.device, c.pool, nil)
270 c.pool = 0
271 }
272 r.freeGPUBuffer(&c.gpu)
273}
274
275// dropShm frees the GPU buffers of surfaces not drawn for importTTL frames.
276func (r *Renderer) dropShm() {
277 for key, s := range r.shm {
278 if r.frame-s.last > importTTL {
279 r.retireShm(s)
280 delete(r.shm, key)
281 }
282 }
283}
284
285// retireShm frees a surface's GPU buffers once the frames reading them
286// completed.
287func (r *Renderer) retireShm(s *shmSurface) {
288 for _, c := range s.bufs {
289 if c != nil {
290 r.retire(c.gpu.last, func() { r.freeShmCopy(c) })
291 }
292 }
293}