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 // Vulkan's A2R10G10B10/A2B10G10R10 packed UNORM formats match
45 // DRM's little-endian 2101010 layout; do not use an sRGB view for PQ.
46 {fourcc('A', 'R', '3', '0'), vk.FormatA2r10g10b10UnormPack32, false},
47 {fourcc('X', 'R', '3', '0'), vk.FormatA2r10g10b10UnormPack32, true},
48 {fourcc('A', 'B', '3', '0'), vk.FormatA2b10g10r10UnormPack32, false},
49 {fourcc('X', 'B', '3', '0'), vk.FormatA2b10g10r10UnormPack32, true},
50 // Little-endian ABGR16161616F is RGBA16F; floating-point values
51 // are linear electrical signals, not sRGB-encoded.
52 {fourcc('A', 'B', '4', 'H'), vk.FormatR16g16b16a16Sfloat, false},
53 {fourcc('X', 'B', '4', 'H'), vk.FormatR16g16b16a16Sfloat, true},
54 {fourcc('N', 'V', '1', '2'), vk.Format(1000156003), true}, // VK_FORMAT_G8_B8R8_2PLANE_420_UNORM
55 {fourcc('P', '0', '1', '0'), vk.Format(1000156013), true}, // VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16
56}
57
58func isYUVFormat(f uint32) bool {
59 return f == fourcc('N', 'V', '1', '2') || f == fourcc('P', '0', '1', '0')
60}
61
62func fourcc(a, b, c, d byte) uint32 {
63 return uint32(a) | uint32(b)<<8 | uint32(c)<<16 | uint32(d)<<24
64}
65
66func vkFormat(f uint32) (vk.Format, bool, bool) {
67 for _, v := range dmabufFormats {
68 if v.fourcc == f {
69 return v.format, v.opaque, true
70 }
71 }
72 return 0, false, false
73}
74
75// extensions lists the device extensions supported by the selected GPU.
76func (r *Renderer) extensions(physical vk.PhysicalDevice) map[string]bool {
77 var n uint32
78 if r.id.EnumerateDeviceExtensionProperties(physical, nil, &n, nil) != vk.Success || n == 0 {
79 return nil
80 }
81 props := make([]vk.ExtensionProperties, n)
82 if r.id.EnumerateDeviceExtensionProperties(physical, nil, &n, &props[0]) != vk.Success {
83 return nil
84 }
85 have := map[string]bool{}
86 for _, p := range props[:n] {
87 have[cstring(p.ExtensionName[:])] = true
88 }
89 return have
90}
91
92func cstring(b []byte) string {
93 if i := slices.Index(b, 0); i >= 0 {
94 b = b[:i]
95 }
96 return string(b)
97}
98
99// probeDMABuf lists the sampled fourcc/modifier pairs supported by the GPU.
100// Multi-planar formats require mutable per-plane views; disjoint binding is
101// optional and only used when the device supports it and planes differ.
102func (r *Renderer) probeDMABuf(physical vk.PhysicalDevice) ports.DMABufSupport {
103 var sup ports.DMABufSupport
104 drm := vk.PhysicalDeviceDrmPropertiesEXT{SType: vk.StructureTypePhysicalDeviceDRMPropertiesEXT}
105 props := vk.PhysicalDeviceProperties2{SType: vk.StructureTypePhysicalDeviceProperties2, Next: unsafe.Pointer(&drm)}
106 r.id.GetPhysicalDeviceProperties2(physical, &props)
107 if drm.HasRender != 0 {
108 sup.Device = unix.Mkdev(uint32(drm.RenderMajor), uint32(drm.RenderMinor))
109 }
110 for _, f := range dmabufFormats {
111 list := vk.DrmFormatModifierPropertiesListEXT{SType: vk.StructureTypeDRMFormatModifierPropertiesListEXT}
112 fp := vk.FormatProperties2{SType: vk.StructureTypeFormatProperties2, Next: unsafe.Pointer(&list)}
113 r.id.GetPhysicalDeviceFormatProperties2(physical, f.format, &fp)
114 if list.DrmFormatModifierCount == 0 {
115 continue
116 }
117 mods := make([]vk.DrmFormatModifierPropertiesEXT, list.DrmFormatModifierCount)
118 list.DrmFormatModifierProperties = &mods[0]
119 r.id.GetPhysicalDeviceFormatProperties2(physical, f.format, &fp)
120 for _, m := range mods[:list.DrmFormatModifierCount] {
121 need := vk.FormatFeatureFlags(vk.FormatFeatureSampledImageBit | vk.FormatFeatureSampledImageFilterLinearBit)
122 planes := uint32(1)
123 if isYUVFormat(f.fourcc) {
124 planes = 2
125 }
126 if m.DrmFormatModifierPlaneCount != planes || m.DrmFormatModifierTilingFeatures&need != need {
127 continue
128 }
129 if r.importable(physical, f.format, m.DrmFormatModifier, isYUVFormat(f.fourcc), false) {
130 sup.Formats = append(sup.Formats, ports.DMABufFormat{Format: f.fourcc, Modifier: m.DrmFormatModifier})
131 }
132 }
133 }
134 return sup
135}
136
137// importable asks whether an image of that format and modifier can be
138// imported from a dmabuf.
139func (r *Renderer) importable(physical vk.PhysicalDevice, format vk.Format, modifier uint64, yuv, disjoint bool) bool {
140 mod := vk.PhysicalDeviceImageDrmFormatModifierInfoEXT{SType: vk.StructureTypePhysicalDeviceImageDRMFormatModifierInfoEXT, DrmFormatModifier: modifier, SharingMode: vk.SharingModeExclusive}
141 ext := vk.PhysicalDeviceExternalImageFormatInfo{SType: vk.StructureTypePhysicalDeviceExternalImageFormatInfo, Next: unsafe.Pointer(&mod), HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
142 flags := vk.ImageCreateFlags(0)
143 if yuv {
144 flags = vk.ImageCreateMutableFormatBit
145 if disjoint {
146 flags |= imageDisjoint
147 // Image-format queries alone do not imply the modifier supports
148 // disjoint plane bindings. Check its tiling features as well.
149 list := vk.DrmFormatModifierPropertiesListEXT{SType: vk.StructureTypeDRMFormatModifierPropertiesListEXT}
150 properties := vk.FormatProperties2{SType: vk.StructureTypeFormatProperties2, Next: unsafe.Pointer(&list)}
151 r.id.GetPhysicalDeviceFormatProperties2(physical, format, &properties)
152 if list.DrmFormatModifierCount == 0 {
153 return false
154 }
155 mods := make([]vk.DrmFormatModifierPropertiesEXT, list.DrmFormatModifierCount)
156 list.DrmFormatModifierProperties = &mods[0]
157 r.id.GetPhysicalDeviceFormatProperties2(physical, format, &properties)
158 found := false
159 for _, m := range mods[:list.DrmFormatModifierCount] {
160 if m.DrmFormatModifier == modifier && m.DrmFormatModifierTilingFeatures&0x00400000 != 0 {
161 found = true
162 break
163 }
164 }
165 if !found {
166 return false
167 }
168 }
169 views := yuvViewFormats(format)
170 formats := imageFormatList{sType: structureImageFormatList, viewFormatCount: 3, viewFormats: &views[0]}
171 mod.Next = unsafe.Pointer(&formats)
172 info := vk.PhysicalDeviceImageFormatInfo2{SType: vk.StructureTypePhysicalDeviceImageFormatInfo2, Next: unsafe.Pointer(&ext), Flags: flags, Format: format, Type: vk.ImageType2d, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: vk.ImageUsageSampledBit}
173 extOut := vk.ExternalImageFormatProperties{SType: vk.StructureTypeExternalImageFormatProperties}
174 out := vk.ImageFormatProperties2{SType: vk.StructureTypeImageFormatProperties2, Next: unsafe.Pointer(&extOut)}
175 return r.id.GetPhysicalDeviceImageFormatProperties2(physical, &info, &out) == vk.Success && extOut.ExternalMemoryProperties.ExternalMemoryFeatures&vk.ExternalMemoryFeatureImportableBit != 0
176 }
177 info := vk.PhysicalDeviceImageFormatInfo2{SType: vk.StructureTypePhysicalDeviceImageFormatInfo2, Next: unsafe.Pointer(&ext), Flags: flags, Format: format, Type: vk.ImageType2d, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: vk.ImageUsageSampledBit}
178 extOut := vk.ExternalImageFormatProperties{SType: vk.StructureTypeExternalImageFormatProperties}
179 out := vk.ImageFormatProperties2{SType: vk.StructureTypeImageFormatProperties2, Next: unsafe.Pointer(&extOut)}
180 if r.id.GetPhysicalDeviceImageFormatProperties2(physical, &info, &out) != vk.Success {
181 return false
182 }
183 return extOut.ExternalMemoryProperties.ExternalMemoryFeatures&vk.ExternalMemoryFeatureImportableBit != 0
184}
185
186// DMABuf reports the formats this renderer imports.
187func (r *Renderer) DMABuf() ports.DMABufSupport { return r.dmabuf }
188
189// imported is a client dmabuf bound to a VkImage and its sampled views.
190type imported struct {
191 image vk.Image
192 memory vk.DeviceMemory
193 view vk.ImageView
194 chromaView vk.ImageView
195 memories [2]vk.DeviceMemory
196 fds [2]int
197 yuv bool
198 // disjoint is available for this format/modifier; required only when
199 // the two planes refer to different underlying DMA-BUF objects.
200 disjoint bool
201 fd int // our duplicate of plane 0, for implicit sync
202 // last is the frame that drew it, for eviction.
203 last uint64
204}
205
206// importTTL is how many frames an import survives undrawn: clients cycle
207// through 2-4 buffers, and a destroyed buffer is freed shortly after.
208const importTTL = 120
209
210// importDMABuf returns the image of a client buffer, importing it once.
211func (r *Renderer) importDMABuf(b *ports.DMABuf) (*imported, error) {
212 if im := r.imports[b.ID]; im != nil {
213 im.last = r.frame
214 return im, nil
215 }
216 if len(r.dmabuf.Formats) == 0 {
217 return nil, fmt.Errorf("dmabuf unsupported")
218 }
219 format, _, ok := vkFormat(b.Format)
220 if !ok || b.Width <= 0 || b.Height <= 0 {
221 return nil, fmt.Errorf("dmabuf format %#x with %d planes unsupported", b.Format, len(b.Planes))
222 }
223 if isYUVFormat(b.Format) {
224 if len(b.Planes) != 2 || b.Width%2 != 0 || b.Height%2 != 0 {
225 return nil, fmt.Errorf("invalid YUV buffer geometry or planes")
226 }
227 return r.importYUV(b, format)
228 }
229 if len(b.Planes) != 1 {
230 return nil, fmt.Errorf("RGB buffer needs one plane")
231 }
232 fd, err := dupFile(b.Planes[0].File)
233 if err != nil {
234 return nil, err
235 }
236 im := &imported{fd: fd, last: r.frame}
237 ok = false
238 defer func() {
239 if !ok {
240 r.release(im)
241 }
242 }()
243 d := r.dd
244 layout := vk.SubresourceLayout{Offset: vk.DeviceSize(b.Planes[0].Offset), RowPitch: vk.DeviceSize(b.Planes[0].Stride)}
245 explicit := vk.ImageDrmFormatModifierExplicitCreateInfoEXT{SType: vk.StructureTypeImageDRMFormatModifierExplicitCreateInfoEXT, DrmFormatModifier: b.Modifier, DrmFormatModifierPlaneCount: 1, PlaneLayouts: &layout}
246 external := vk.ExternalMemoryImageCreateInfo{SType: vk.StructureTypeExternalMemoryImageCreateInfo, Next: unsafe.Pointer(&explicit), HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
247 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}
248 if err := checked("vkCreateImage(dmabuf)", d.CreateImage(r.device, &ii, nil, &im.image)); err != nil {
249 return nil, err
250 }
251 fdProps := vk.MemoryFdPropertiesKHR{SType: vk.StructureTypeMemoryFDPropertiesKHR}
252 if err := checked("vkGetMemoryFdPropertiesKHR", d.GetMemoryFdPropertiesKHR(r.device, vk.ExternalMemoryHandleTypeDMABUFBitEXT, int32(fd), &fdProps)); err != nil {
253 return nil, err
254 }
255 var req vk.MemoryRequirements
256 d.GetImageMemoryRequirements(r.device, im.image, &req)
257 kind, err := r.findMemoryType(req.MemoryTypeBits&fdProps.MemoryTypeBits, 0)
258 if err != nil {
259 return nil, err
260 }
261 // Vulkan owns the fd it imports on success: hand it its own duplicate.
262 memFD, err := unix.FcntlInt(uintptr(fd), unix.F_DUPFD_CLOEXEC, 0)
263 if err != nil {
264 return nil, err
265 }
266 dedicated := vk.MemoryDedicatedAllocateInfo{SType: vk.StructureTypeMemoryDedicatedAllocateInfo, Image: im.image}
267 imp := vk.ImportMemoryFdInfoKHR{SType: vk.StructureTypeImportMemoryFDInfoKHR, Next: unsafe.Pointer(&dedicated), HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT, Fd: int32(memFD)}
268 alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, Next: unsafe.Pointer(&imp), AllocationSize: req.Size, MemoryTypeIndex: kind}
269 if err := checked("vkAllocateMemory(dmabuf)", d.AllocateMemory(r.device, &alloc, nil, &im.memory)); err != nil {
270 unix.Close(memFD)
271 return nil, err
272 }
273 if err := checked("vkBindImageMemory(dmabuf)", d.BindImageMemory(r.device, im.image, im.memory, 0)); err != nil {
274 return nil, err
275 }
276 if im.view, err = r.imageView(im.image, format); err != nil {
277 return nil, err
278 }
279 ok = true
280 r.imports[b.ID] = im
281 return im, nil
282}
283
284// dupFile duplicates the descriptor while the file is held open, so a close
285// on the wayland goroutine cannot swap it for a reused number.
286func dupFile(f *os.File) (int, error) {
287 if f == nil {
288 return -1, fmt.Errorf("dmabuf plane without file")
289 }
290 raw, err := f.SyscallConn()
291 if err != nil {
292 return -1, err
293 }
294 fd, dupErr := -1, error(nil)
295 if err := raw.Control(func(v uintptr) { fd, dupErr = unix.FcntlInt(v, unix.F_DUPFD_CLOEXEC, 0) }); err != nil {
296 return -1, err
297 }
298 return fd, dupErr
299}
300
301func (r *Renderer) release(im *imported) {
302 r.invalidateCompose(resourceKey{im: im})
303 if im.view != 0 {
304 r.dd.DestroyImageView(r.device, im.view, nil)
305 }
306 if im.chromaView != 0 {
307 r.dd.DestroyImageView(r.device, im.chromaView, nil)
308 }
309 if im.image != 0 {
310 r.dd.DestroyImage(r.device, im.image, nil)
311 }
312 if im.memory != 0 {
313 r.dd.FreeMemory(r.device, im.memory, nil)
314 }
315 for i := range im.memories {
316 if im.memories[i] != 0 {
317 r.dd.FreeMemory(r.device, im.memories[i], nil)
318 }
319 }
320 if im.fd >= 0 {
321 unix.Close(im.fd)
322 }
323 if im.yuv {
324 for _, fd := range im.fds {
325 if fd >= 0 {
326 unix.Close(fd)
327 }
328 }
329 }
330}
331
332// dropUnused frees imports not drawn for importTTL frames (the client
333// moved on to other buffers or went away), once the last frame that drew
334// them completed.
335func (r *Renderer) dropUnused() {
336 for id, im := range r.imports {
337 if r.frame-im.last > importTTL {
338 r.retire(im.last, func() { r.release(im) })
339 delete(r.imports, id)
340 }
341 }
342}
343
344// dmaBufSync is struct dma_buf_export_sync_file.
345type dmaBufSync struct {
346 flags uint32
347 fd int32
348}
349
350// DMA_BUF_IOCTL_EXPORT_SYNC_FILE = _IOWR('b', 2, struct dma_buf_export_sync_file)
351const ioctlExportSyncFile = 0xc0086202
352const dmaBufSyncRead = 1
353
354// appendReadFences waits on every distinct backing object's pending writes.
355// Shared YUV planes have one implicit fence; disjoint planes have two.
356func (r *Renderer) appendReadFences(waits []vk.Semaphore, im *imported) []vk.Semaphore {
357 fds := [2]int{im.fd}
358 count := 1
359 if im.yuv {
360 fds = im.fds
361 if im.disjoint {
362 count = 2
363 }
364 }
365 for _, fd := range fds[:count] {
366 arg := dmaBufSync{flags: dmaBufSyncRead, fd: -1}
367 if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), ioctlExportSyncFile, uintptr(unsafe.Pointer(&arg))); errno != 0 || arg.fd < 0 {
368 continue // Older kernels cannot export implicit fences.
369 }
370 if sem := r.importSyncFD(int(arg.fd)); sem != 0 {
371 waits = append(waits, sem)
372 }
373 }
374 return waits
375}
376
377// importFence makes a wait semaphore of a sync file. The file stays the
378// caller's: Vulkan takes a duplicate, made while the file is held open.
379func (r *Renderer) importFence(f *os.File) vk.Semaphore {
380 fd, err := dupFile(f)
381 if err != nil {
382 return 0
383 }
384 return r.importSyncFD(fd)
385}
386
387// importSyncFD imports a sync file fd as a temporary semaphore payload;
388// the fd is Vulkan's on success and closed on failure.
389func (r *Renderer) importSyncFD(fd int) vk.Semaphore {
390 var sem vk.Semaphore
391 si := vk.SemaphoreCreateInfo{SType: vk.StructureTypeSemaphoreCreateInfo}
392 if r.dd.CreateSemaphore(r.device, &si, nil, &sem) != vk.Success {
393 unix.Close(fd)
394 return 0
395 }
396 imp := vk.ImportSemaphoreFdInfoKHR{SType: vk.StructureTypeImportSemaphoreFDInfoKHR, Semaphore: sem, Flags: vk.SemaphoreImportTemporaryBit, HandleType: vk.ExternalSemaphoreHandleTypeSyncFDBit, Fd: int32(fd)}
397 if r.dd.ImportSemaphoreFdKHR(r.device, &imp) != vk.Success {
398 unix.Close(fd)
399 r.dd.DestroySemaphore(r.device, sem, nil)
400 return 0
401 }
402 return sem
403}
404
405// Probe reports the dmabuf formats of the device renderers use, or none
406// when Vulkan or the extensions are missing.
407func Probe() ports.DMABufSupport {
408 r, err := New(1, 1)
409 if err != nil {
410 return ports.DMABufSupport{}
411 }
412 defer r.Close()
413 return r.DMABuf()
414}