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