1package vulkan
2
3import (
4 "fmt"
5 "os"
6 "slices"
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// dmabuf import (linux-dmabuf clients): each client buffer becomes a
15// VkImage bound to its imported memory, cached by buffer ID while the
16// client keeps attaching it, and sampled by the compose shader. Sync is
17// implicit: before sampling, the renderer waits on the fences the client's
18// driver attached to the buffer (DMA_BUF_IOCTL_EXPORT_SYNC_FILE), imported
19// as a semaphore.
20
21// deviceExtensions are enabled when all present; without them the renderer
22// has no dmabuf support and clients fall back to wl_shm.
23var deviceExtensions = []string{
24 vk.KHRExternalMemoryFDExtensionName,
25 vk.EXTExternalMemoryDMABUFExtensionName,
26 vk.EXTImageDRMFormatModifierExtensionName,
27 vk.EXTQueueFamilyForeignExtensionName,
28 vk.KHRExternalSemaphoreFDExtensionName,
29 // The render node advertised to clients in dmabuf feedback.
30 vk.EXTPhysicalDeviceDRMExtensionName,
31}
32
33// fourcc → Vulkan format. DRM formats name the packed pixel from the high
34// byte, little-endian: ARGB8888 is B,G,R,A in memory, like B8G8R8A8.
35var dmabufFormats = []struct {
36 fourcc uint32
37 format vk.Format
38 opaque bool
39}{
40 {fourcc('A', 'R', '2', '4'), vk.FormatB8g8r8a8Unorm, false},
41 {fourcc('X', 'R', '2', '4'), vk.FormatB8g8r8a8Unorm, true},
42 {fourcc('A', 'B', '2', '4'), vk.FormatR8g8b8a8Unorm, false},
43 {fourcc('X', 'B', '2', '4'), vk.FormatR8g8b8a8Unorm, true},
44}
45
46func fourcc(a, b, c, d byte) uint32 {
47 return uint32(a) | uint32(b)<<8 | uint32(c)<<16 | uint32(d)<<24
48}
49
50func vkFormat(f uint32) (vk.Format, bool, bool) {
51 for _, v := range dmabufFormats {
52 if v.fourcc == f {
53 return v.format, v.opaque, true
54 }
55 }
56 return 0, false, false
57}
58
59// hasExtensions reports whether the device offers every dmabuf extension.
60func (r *Renderer) hasExtensions(physical vk.PhysicalDevice) bool {
61 var n uint32
62 if r.id.EnumerateDeviceExtensionProperties(physical, nil, &n, nil) != vk.Success || n == 0 {
63 return false
64 }
65 props := make([]vk.ExtensionProperties, n)
66 if r.id.EnumerateDeviceExtensionProperties(physical, nil, &n, &props[0]) != vk.Success {
67 return false
68 }
69 have := map[string]bool{}
70 for _, p := range props[:n] {
71 have[cstring(p.ExtensionName[:])] = true
72 }
73 for _, e := range deviceExtensions {
74 if !have[e] {
75 return false
76 }
77 }
78 return true
79}
80
81func cstring(b []byte) string {
82 if i := slices.Index(b, 0); i >= 0 {
83 b = b[:i]
84 }
85 return string(b)
86}
87
88// probeDMABuf lists the fourcc/modifier pairs the device imports as
89// single-plane sampled images with linear filtering, and the render node.
90func (r *Renderer) probeDMABuf(physical vk.PhysicalDevice) ports.DMABufSupport {
91 var sup ports.DMABufSupport
92 drm := vk.PhysicalDeviceDrmPropertiesEXT{SType: vk.StructureTypePhysicalDeviceDRMPropertiesEXT}
93 props := vk.PhysicalDeviceProperties2{SType: vk.StructureTypePhysicalDeviceProperties2, Next: unsafe.Pointer(&drm)}
94 r.id.GetPhysicalDeviceProperties2(physical, &props)
95 if drm.HasRender != 0 {
96 sup.Device = unix.Mkdev(uint32(drm.RenderMajor), uint32(drm.RenderMinor))
97 }
98 for _, f := range dmabufFormats {
99 list := vk.DrmFormatModifierPropertiesListEXT{SType: vk.StructureTypeDRMFormatModifierPropertiesListEXT}
100 fp := vk.FormatProperties2{SType: vk.StructureTypeFormatProperties2, Next: unsafe.Pointer(&list)}
101 r.id.GetPhysicalDeviceFormatProperties2(physical, f.format, &fp)
102 if list.DrmFormatModifierCount == 0 {
103 continue
104 }
105 mods := make([]vk.DrmFormatModifierPropertiesEXT, list.DrmFormatModifierCount)
106 list.DrmFormatModifierProperties = &mods[0]
107 r.id.GetPhysicalDeviceFormatProperties2(physical, f.format, &fp)
108 for _, m := range mods[:list.DrmFormatModifierCount] {
109 // Multi-plane modifiers (compression metadata) are left to a
110 // later step.
111 need := vk.FormatFeatureFlags(vk.FormatFeatureSampledImageBit | vk.FormatFeatureSampledImageFilterLinearBit)
112 if m.DrmFormatModifierPlaneCount != 1 || m.DrmFormatModifierTilingFeatures&need != need {
113 continue
114 }
115 if r.importable(physical, f.format, m.DrmFormatModifier) {
116 sup.Formats = append(sup.Formats, ports.DMABufFormat{Format: f.fourcc, Modifier: m.DrmFormatModifier})
117 }
118 }
119 }
120 return sup
121}
122
123// importable asks whether an image of that format and modifier can be
124// imported from a dmabuf.
125func (r *Renderer) importable(physical vk.PhysicalDevice, format vk.Format, modifier uint64) bool {
126 mod := vk.PhysicalDeviceImageDrmFormatModifierInfoEXT{SType: vk.StructureTypePhysicalDeviceImageDRMFormatModifierInfoEXT, DrmFormatModifier: modifier, SharingMode: vk.SharingModeExclusive}
127 ext := vk.PhysicalDeviceExternalImageFormatInfo{SType: vk.StructureTypePhysicalDeviceExternalImageFormatInfo, Next: unsafe.Pointer(&mod), HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
128 info := vk.PhysicalDeviceImageFormatInfo2{SType: vk.StructureTypePhysicalDeviceImageFormatInfo2, Next: unsafe.Pointer(&ext), Format: format, Type: vk.ImageType2d, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: vk.ImageUsageSampledBit}
129 extOut := vk.ExternalImageFormatProperties{SType: vk.StructureTypeExternalImageFormatProperties}
130 out := vk.ImageFormatProperties2{SType: vk.StructureTypeImageFormatProperties2, Next: unsafe.Pointer(&extOut)}
131 if r.id.GetPhysicalDeviceImageFormatProperties2(physical, &info, &out) != vk.Success {
132 return false
133 }
134 return extOut.ExternalMemoryProperties.ExternalMemoryFeatures&vk.ExternalMemoryFeatureImportableBit != 0
135}
136
137// DMABuf reports the formats this renderer imports.
138func (r *Renderer) DMABuf() ports.DMABufSupport { return r.dmabuf }
139
140// imported is a client dmabuf bound to a VkImage, with the view and set
141// the compose shader samples it through.
142type imported struct {
143 image vk.Image
144 memory vk.DeviceMemory
145 view vk.ImageView
146 pool vk.DescriptorPool
147 set vk.DescriptorSet
148 fd int // our duplicate of plane 0, for implicit sync
149 // last is the frame that drew it, for eviction.
150 last uint64
151}
152
153// importTTL is how many frames an import survives undrawn: clients cycle
154// through 2-4 buffers, and a destroyed buffer is freed shortly after.
155const importTTL = 120
156
157// importDMABuf returns the image of a client buffer, importing it once.
158func (r *Renderer) importDMABuf(b *ports.DMABuf) (*imported, error) {
159 if im := r.imports[b.ID]; im != nil {
160 im.last = r.frame
161 return im, nil
162 }
163 if len(r.dmabuf.Formats) == 0 {
164 return nil, fmt.Errorf("dmabuf unsupported")
165 }
166 format, _, ok := vkFormat(b.Format)
167 if !ok || len(b.Planes) != 1 || b.Width <= 0 || b.Height <= 0 {
168 return nil, fmt.Errorf("dmabuf format %#x with %d planes unsupported", b.Format, len(b.Planes))
169 }
170 fd, err := dupFile(b.Planes[0].File)
171 if err != nil {
172 return nil, err
173 }
174 im := &imported{fd: fd, last: r.frame}
175 ok = false
176 defer func() {
177 if !ok {
178 r.release(im)
179 }
180 }()
181 d := r.dd
182 layout := vk.SubresourceLayout{Offset: vk.DeviceSize(b.Planes[0].Offset), RowPitch: vk.DeviceSize(b.Planes[0].Stride)}
183 explicit := vk.ImageDrmFormatModifierExplicitCreateInfoEXT{SType: vk.StructureTypeImageDRMFormatModifierExplicitCreateInfoEXT, DrmFormatModifier: b.Modifier, DrmFormatModifierPlaneCount: 1, PlaneLayouts: &layout}
184 external := vk.ExternalMemoryImageCreateInfo{SType: vk.StructureTypeExternalMemoryImageCreateInfo, Next: unsafe.Pointer(&explicit), HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
185 ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, Next: unsafe.Pointer(&external), ImageType: vk.ImageType2d, Format: format, Extent: vk.Extent3D{Width: uint32(b.Width), Height: uint32(b.Height), Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: vk.ImageUsageSampledBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined}
186 if err := checked("vkCreateImage(dmabuf)", d.CreateImage(r.device, &ii, nil, &im.image)); err != nil {
187 return nil, err
188 }
189 fdProps := vk.MemoryFdPropertiesKHR{SType: vk.StructureTypeMemoryFDPropertiesKHR}
190 if err := checked("vkGetMemoryFdPropertiesKHR", d.GetMemoryFdPropertiesKHR(r.device, vk.ExternalMemoryHandleTypeDMABUFBitEXT, int32(fd), &fdProps)); err != nil {
191 return nil, err
192 }
193 var req vk.MemoryRequirements
194 d.GetImageMemoryRequirements(r.device, im.image, &req)
195 kind, err := r.findMemoryType(req.MemoryTypeBits&fdProps.MemoryTypeBits, 0)
196 if err != nil {
197 return nil, err
198 }
199 // Vulkan owns the fd it imports on success: hand it its own duplicate.
200 memFD, err := unix.FcntlInt(uintptr(fd), unix.F_DUPFD_CLOEXEC, 0)
201 if err != nil {
202 return nil, err
203 }
204 dedicated := vk.MemoryDedicatedAllocateInfo{SType: vk.StructureTypeMemoryDedicatedAllocateInfo, Image: im.image}
205 imp := vk.ImportMemoryFdInfoKHR{SType: vk.StructureTypeImportMemoryFDInfoKHR, Next: unsafe.Pointer(&dedicated), HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT, Fd: int32(memFD)}
206 alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, Next: unsafe.Pointer(&imp), AllocationSize: req.Size, MemoryTypeIndex: kind}
207 if err := checked("vkAllocateMemory(dmabuf)", d.AllocateMemory(r.device, &alloc, nil, &im.memory)); err != nil {
208 unix.Close(memFD)
209 return nil, err
210 }
211 if err := checked("vkBindImageMemory(dmabuf)", d.BindImageMemory(r.device, im.image, im.memory, 0)); err != nil {
212 return nil, err
213 }
214 if im.view, err = r.imageView(im.image, format); err != nil {
215 return nil, err
216 }
217 if im.pool, im.set, err = r.newSet(im.view, r.compose.dummyBuffer.buffer); err != nil {
218 return nil, err
219 }
220 ok = true
221 r.imports[b.ID] = im
222 return im, nil
223}
224
225// dupFile duplicates the descriptor while the file is held open, so a close
226// on the wayland goroutine cannot swap it for a reused number.
227func dupFile(f *os.File) (int, error) {
228 if f == nil {
229 return -1, fmt.Errorf("dmabuf plane without file")
230 }
231 raw, err := f.SyscallConn()
232 if err != nil {
233 return -1, err
234 }
235 fd, dupErr := -1, error(nil)
236 if err := raw.Control(func(v uintptr) { fd, dupErr = unix.FcntlInt(v, unix.F_DUPFD_CLOEXEC, 0) }); err != nil {
237 return -1, err
238 }
239 return fd, dupErr
240}
241
242func (r *Renderer) release(im *imported) {
243 if im.pool != 0 {
244 r.dd.DestroyDescriptorPool(r.device, im.pool, nil)
245 }
246 if im.view != 0 {
247 r.dd.DestroyImageView(r.device, im.view, nil)
248 }
249 if im.image != 0 {
250 r.dd.DestroyImage(r.device, im.image, nil)
251 }
252 if im.memory != 0 {
253 r.dd.FreeMemory(r.device, im.memory, nil)
254 }
255 if im.fd >= 0 {
256 unix.Close(im.fd)
257 }
258}
259
260// dropUnused frees imports not drawn for importTTL frames (the client
261// moved on to other buffers or went away), once the last frame that drew
262// them completed.
263func (r *Renderer) dropUnused() {
264 for id, im := range r.imports {
265 if r.frame-im.last > importTTL {
266 r.retire(im.last, func() { r.release(im) })
267 delete(r.imports, id)
268 }
269 }
270}
271
272// dmaBufSync is struct dma_buf_export_sync_file.
273type dmaBufSync struct {
274 flags uint32
275 fd int32
276}
277
278// DMA_BUF_IOCTL_EXPORT_SYNC_FILE = _IOWR('b', 2, struct dma_buf_export_sync_file)
279const ioctlExportSyncFile = 0xc0086202
280const dmaBufSyncRead = 1
281
282// readFence returns a semaphore signalled when the client's pending writes
283// to the buffer finish, or 0 when the kernel cannot export them (older than
284// 5.20): the buffer is then read as is.
285func (r *Renderer) readFence(im *imported) vk.Semaphore {
286 arg := dmaBufSync{flags: dmaBufSyncRead, fd: -1}
287 if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(im.fd), ioctlExportSyncFile, uintptr(unsafe.Pointer(&arg))); errno != 0 || arg.fd < 0 {
288 return 0
289 }
290 return r.importSyncFD(int(arg.fd))
291}
292
293// importFence makes a wait semaphore of a sync file. The file stays the
294// caller's: Vulkan takes a duplicate, made while the file is held open.
295func (r *Renderer) importFence(f *os.File) vk.Semaphore {
296 fd, err := dupFile(f)
297 if err != nil {
298 return 0
299 }
300 return r.importSyncFD(fd)
301}
302
303// importSyncFD imports a sync file fd as a temporary semaphore payload;
304// the fd is Vulkan's on success and closed on failure.
305func (r *Renderer) importSyncFD(fd int) vk.Semaphore {
306 var sem vk.Semaphore
307 si := vk.SemaphoreCreateInfo{SType: vk.StructureTypeSemaphoreCreateInfo}
308 if r.dd.CreateSemaphore(r.device, &si, nil, &sem) != vk.Success {
309 unix.Close(fd)
310 return 0
311 }
312 imp := vk.ImportSemaphoreFdInfoKHR{SType: vk.StructureTypeImportSemaphoreFDInfoKHR, Semaphore: sem, Flags: vk.SemaphoreImportTemporaryBit, HandleType: vk.ExternalSemaphoreHandleTypeSyncFDBit, Fd: int32(fd)}
313 if r.dd.ImportSemaphoreFdKHR(r.device, &imp) != vk.Success {
314 unix.Close(fd)
315 r.dd.DestroySemaphore(r.device, sem, nil)
316 return 0
317 }
318 return sem
319}
320
321// Probe reports the dmabuf formats of the device renderers use, or none
322// when Vulkan or the extensions are missing.
323func Probe() ports.DMABufSupport {
324 r, err := New(1, 1)
325 if err != nil {
326 return ports.DMABufSupport{}
327 }
328 defer r.Close()
329 return r.DMABuf()
330}