1package vulkan
2
3import (
4 "errors"
5 "fmt"
6 "maps"
7 "os"
8 "slices"
9 "unsafe"
10
11 "github.com/bnema/neferwl/internal/ports"
12 vk "github.com/bnema/purego-vulkan/vulkan"
13 "golang.org/x/sys/unix"
14)
15
16// Render targets (ADR 014). A DRM output scans out images the renderer
17// draws into: ExportTargets allocates them with a DRM format modifier the
18// display accepts and exports their memory as dmabufs, which the output
19// turns into KMS framebuffers. Render then composes straight into the
20// selected target and the output flips to it; nothing is copied by the
21// CPU. In HDR an internal fp16 linear image keeps full-range composition,
22// sampled by a full-screen GPU pass into the 10-bit target. Capture/Pixels
23// convert to SDR sRGB only when requested.
24
25// target is an image frames are drawn into.
26type target struct {
27 image vk.Image
28 memory vk.DeviceMemory
29 layout vk.ImageLayout
30 view vk.ImageView // color attachment for blended draws
31 // exported images are shared with the display: ownership goes to the
32 // foreign queue family after each frame, and KMS reads them.
33 exported bool
34 // What the image holds (damage.go): the scene, its Seq and each
35 // window's content Seq; valid once a frame was drawn whole. Scenes are
36 // immutable for core, but output adapters reuse their slices in place
37 // (DRM overlay frames, capture), so the compared slices of scene point
38 // into the target's own buffers, copied by hold.
39 valid bool
40 sceneSeq uint64
41 scene ports.Scene
42 heldWindows []ports.SceneWindow
43 heldSeparators []ports.Separator
44 heldLayers []ports.SceneLayer
45 heldHints []ports.Rect
46 heldIndicators []ports.CaptureIndicator
47 heldLabels []ports.HintLabel
48 windows map[ports.WindowID]heldWindow
49}
50
51// fourccXRGB is DRM_FORMAT_XRGB8888: B8G8R8A8 in memory, alpha ignored.
52const fourccXRGB = 'X' | 'R'<<8 | '2'<<16 | '4'<<24
53
54// fourccXR30 is DRM_FORMAT_XRGB2101010 (A2R10G10B10 packed).
55const fourccXR30 = 'X' | 'R'<<8 | '3'<<16 | '0'<<24
56
57// ExportTargets allocates n exported images of the output size, replacing
58// the previous ones; n = 0 only drops them (back to the internal image).
59// singlePlane keeps only one-plane modifiers (no DCC metadata planes), for
60// displays that refuse the multi-plane framebuffers of the others.
61func (r *Renderer) ExportTargets(n int, modifiers []uint64, singlePlane bool) ([]ports.DMABuf, error) {
62 r.dropTargets()
63 if n > 0 && r.hdrNits > 0 && r.hdrReadback && r.physical != 0 {
64 // Readback targets (GPU tests, headless --screenshot-raw) are also
65 // transfer sources, which changes the format requirements.
66 r.hdrMods = r.probeModifiers(r.physical, vk.FormatA2r10g10b10UnormPack32)
67 }
68 if n > 0 && r.hdrNits > 0 && r.hdr.pipeline == 0 {
69 return nil, fmt.Errorf("HDR transform unavailable: %w", r.hdrError)
70 }
71 if n == 0 {
72 return nil, nil
73 }
74 if !r.dd.HasGetMemoryFdKHR() || !r.dd.HasGetImageDrmFormatModifierPropertiesEXT() || len(r.dmabuf.Formats) == 0 {
75 return nil, errors.New("device cannot export dmabufs")
76 }
77 mods := r.exportModifiers(modifiers, singlePlane)
78 if len(mods) == 0 {
79 if r.hdrNits == 0 {
80 return nil, errors.New("no XRGB8888 modifier both the device and the display accept")
81 }
82 return nil, errors.New("no XRGB2101010 modifier both the device and the display accept")
83 }
84 var out []ports.DMABuf
85 for range n {
86 t, buf, err := r.exportTarget(mods)
87 if err != nil {
88 r.dropTargets()
89 for _, b := range out {
90 for _, p := range b.Planes {
91 p.File.Close()
92 }
93 }
94 return nil, err
95 }
96 r.targets = append(r.targets, t)
97 out = append(out, buf)
98 }
99 r.current = 0
100 return out, nil
101}
102
103// ImportTargets makes the images the display allocated (GBM dmabufs, one
104// memory object for all planes) the render targets, replacing the previous
105// ones; no buffers only drops them. The caller keeps its files: the
106// renderer imports duplicates. Each buffer must have the renderer's size and
107// target format and a modifier from TargetModifiers. A refusal (the driver
108// rejecting the explicit layout) returns an error and leaves no targets.
109func (r *Renderer) ImportTargets(bufs []ports.DMABuf) error {
110 r.dropTargets()
111 if len(bufs) == 0 {
112 return nil
113 }
114 if r.hdrNits > 0 && r.hdr.pipeline == 0 {
115 return fmt.Errorf("HDR transform unavailable: %w", r.hdrError)
116 }
117 if !r.dd.HasGetMemoryFdPropertiesKHR() || !r.dd.HasGetImageDrmFormatModifierPropertiesEXT() || len(r.dmabuf.Formats) == 0 {
118 return errors.New("device cannot import dmabufs")
119 }
120 for i := range bufs {
121 t, err := r.importTarget(&bufs[i])
122 if err != nil {
123 r.dropTargets()
124 return err
125 }
126 r.targets = append(r.targets, t)
127 }
128 r.current = 0
129 return nil
130}
131
132// SetVirtualOutput marks the output as having no display (headless): an
133// empty display modifier list then accepts every exportable HDR modifier,
134// as it does for SDR. DRM never sets it. Call before ExportTargets.
135func (r *Renderer) SetVirtualOutput(on bool) { r.virtual = on }
136
137// UseTarget selects the exported image the next frames draw into.
138func (r *Renderer) UseTarget(i int) {
139 if i >= 0 && i < len(r.targets) {
140 r.current = i
141 }
142}
143
144// target is the image the next frame draws into.
145func (r *Renderer) target() *target {
146 if len(r.targets) > 0 {
147 return r.targets[r.current]
148 }
149 return &r.own
150}
151
152// exportModifiers intersects the selected signal format modifiers with
153// those supported by the display. HDR never assumes an unspecified list,
154// except for a virtual output (no display) or a test readback. singlePlane
155// drops the modifiers with more than one memory plane.
156func (r *Renderer) exportModifiers(display []uint64, singlePlane bool) []uint64 {
157 available := r.renderMods
158 if r.hdrNits > 0 {
159 available = r.hdrMods
160 }
161 return r.intersectModifiers(available, display, singlePlane)
162}
163
164// intersectModifiers keeps the available modifiers the display lists (an
165// empty display list accepts all of them, for SDR and for HDR only on a
166// virtual output or a test readback), without the multi-plane ones when
167// singlePlane.
168func (r *Renderer) intersectModifiers(available, display []uint64, singlePlane bool) []uint64 {
169 var out []uint64
170 for _, m := range available {
171 if singlePlane && r.modPlanes[m] > 1 {
172 continue
173 }
174 if (len(display) == 0 && (r.hdrNits == 0 || r.hdrReadback || r.virtual)) || slices.Contains(display, m) {
175 out = append(out, m)
176 }
177 }
178 return out
179}
180
181// targetFormat is the Vulkan format and fourcc of the targets: XRGB8888, or
182// XRGB2101010 in HDR.
183func (r *Renderer) targetFormat() (vk.Format, uint32) {
184 if r.hdrNits > 0 {
185 return vk.FormatA2r10g10b10UnormPack32, fourccXR30
186 }
187 return vk.FormatB8g8r8a8Unorm, fourccXRGB
188}
189
190// TargetModifiers lists the modifiers an image allocated by the display
191// (GBM) can have to be imported as a target: those the device renders into
192// and imports for the target format, that the display lists (empty: any),
193// without multi-plane ones when singlePlane. Call after SetHDR.
194func (r *Renderer) TargetModifiers(display []uint64, singlePlane bool) []uint64 {
195 if r.physical == 0 || r.dd == nil || !r.dd.HasGetMemoryFdPropertiesKHR() || !r.dd.HasGetImageDrmFormatModifierPropertiesEXT() {
196 return nil
197 }
198 format, _ := r.targetFormat()
199 return r.intersectModifiers(r.probeImportModifiers(r.physical, format), display, singlePlane)
200}
201
202// probeRenderModifiers lists exportable SDR and HDR targets.
203func (r *Renderer) probeRenderModifiers(physical vk.PhysicalDevice) {
204 r.renderMods = r.probeModifiers(physical, vk.FormatB8g8r8a8Unorm)
205 r.hdrMods = r.probeModifiers(physical, vk.FormatA2r10g10b10UnormPack32)
206}
207
208// probeModifiers lists the exportable modifiers of format (one to four memory
209// planes: DCC adds metadata planes; DCC retile modifiers are left out, see
210// retilesDCC) that support the target usage, and
211// records their plane counts in modPlanes. HDR targets need transfer-src only when
212// hdrReadback is set (GPU tests, headless --screenshot-raw; never DRM).
213func (r *Renderer) probeModifiers(physical vk.PhysicalDevice, format vk.Format) []uint64 {
214 return r.probeTargetModifiers(physical, format, vk.ExternalMemoryFeatureExportableBit)
215}
216
217// importKey is what the importable modifiers of a target depend on: its
218// format and whether test readback adds the transfer-source requirement.
219type importKey struct {
220 format vk.Format
221 readback bool
222}
223
224// probeImportModifiers lists the modifiers of format a target can be
225// imported with: probeModifiers with the importable bit, for images the
226// display allocator made. The answer is probed once per key: it does not
227// change while the device lives.
228func (r *Renderer) probeImportModifiers(physical vk.PhysicalDevice, format vk.Format) []uint64 {
229 key := importKey{format, r.hdrReadback}
230 if mods, ok := r.importMods[key]; ok {
231 return mods
232 }
233 mods := r.probeTargetModifiers(physical, format, vk.ExternalMemoryFeatureImportableBit)
234 if r.importMods == nil {
235 r.importMods = make(map[importKey][]uint64)
236 }
237 r.importMods[key] = mods
238 return mods
239}
240
241func (r *Renderer) probeTargetModifiers(physical vk.PhysicalDevice, format vk.Format, feature vk.ExternalMemoryFeatureFlags) []uint64 {
242 list := vk.DrmFormatModifierPropertiesListEXT{SType: vk.StructureTypeDRMFormatModifierPropertiesListEXT}
243 fp := vk.FormatProperties2{SType: vk.StructureTypeFormatProperties2, Next: unsafe.Pointer(&list)}
244 r.id.GetPhysicalDeviceFormatProperties2(physical, format, &fp)
245 if list.DrmFormatModifierCount == 0 {
246 return nil
247 }
248 mods := make([]vk.DrmFormatModifierPropertiesEXT, list.DrmFormatModifierCount)
249 list.DrmFormatModifierProperties = &mods[0]
250 r.id.GetPhysicalDeviceFormatProperties2(physical, format, &fp)
251 need := vk.FormatFeatureFlags(vk.FormatFeatureColorAttachmentBit)
252 if format == vk.FormatB8g8r8a8Unorm {
253 need |= formatFeatureTransferSrc | vk.FormatFeatureColorAttachmentBlendBit
254 } else if r.hdrReadback {
255 need |= formatFeatureTransferSrc
256 }
257 // SDR targets are composed and read back; HDR targets are color
258 // attachments, and transfer sources only with hdrReadback.
259 result, planes := filterModifiers(mods[:list.DrmFormatModifierCount], need, func(m uint64) bool { return r.externalTarget(physical, format, m, feature) })
260 if r.modPlanes == nil {
261 r.modPlanes = make(map[uint64]uint32)
262 }
263 // A modifier has the same plane count for every format (DCC planes
264 // come from the modifier), so SDR and HDR share the map.
265 maps.Copy(r.modPlanes, planes)
266 return result
267}
268
269// maxModifierPlanes is the DRM limit of planes in a framebuffer.
270const maxModifierPlanes = 4
271
272// AMD format modifiers (drm_fourcc.h): the vendor is in bits 56-63, and a DCC
273// modifier with the retile bit keeps a second, display-aligned copy of the
274// DCC metadata.
275const (
276 modVendorShift = 56
277 modVendorAMD = 0x02
278 amdModRetile = 1 << 14 // AMD_FMT_MOD_DCC_RETILE
279)
280
281// retilesDCC reports whether m is an AMD DCC modifier with the retile bit.
282// RADV renders such an image into pipe-aligned DCC and runs a compute pass
283// that rewrites the display copy: an extra full-surface GPU pass per frame,
284// so these modifiers are never offered for targets. Plain DCC (pipe-aligned
285// or not) costs no such pass.
286func retilesDCC(m uint64) bool {
287 return m>>modVendorShift == modVendorAMD && m&amdModRetile != 0
288}
289
290// filterModifiers keeps the modifiers with 1..4 memory planes whose tiling
291// features include need, that are not DCC retile modifiers and that pass
292// exportable, and returns each kept modifier's plane count.
293func filterModifiers(mods []vk.DrmFormatModifierPropertiesEXT, need vk.FormatFeatureFlags, exportable func(uint64) bool) ([]uint64, map[uint64]uint32) {
294 var result []uint64
295 planes := make(map[uint64]uint32, len(mods))
296 for _, m := range mods {
297 if m.DrmFormatModifierPlaneCount < 1 || m.DrmFormatModifierPlaneCount > maxModifierPlanes || m.DrmFormatModifierTilingFeatures&need != need || retilesDCC(m.DrmFormatModifier) || !exportable(m.DrmFormatModifier) {
298 continue
299 }
300 result = append(result, m.DrmFormatModifier)
301 planes[m.DrmFormatModifier] = m.DrmFormatModifierPlaneCount
302 }
303 return result, planes
304}
305
306// externalTarget asks whether a target image of format and modifier with
307// the target usage supports the external memory feature (exportable: the
308// renderer allocates it; importable: the display allocator does).
309func (r *Renderer) externalTarget(physical vk.PhysicalDevice, format vk.Format, modifier uint64, feature vk.ExternalMemoryFeatureFlags) bool {
310 mod := vk.PhysicalDeviceImageDrmFormatModifierInfoEXT{SType: vk.StructureTypePhysicalDeviceImageDRMFormatModifierInfoEXT, DrmFormatModifier: modifier, SharingMode: vk.SharingModeExclusive}
311 ext := vk.PhysicalDeviceExternalImageFormatInfo{SType: vk.StructureTypePhysicalDeviceExternalImageFormatInfo, Next: unsafe.Pointer(&mod), HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
312 info := vk.PhysicalDeviceImageFormatInfo2{SType: vk.StructureTypePhysicalDeviceImageFormatInfo2, Next: unsafe.Pointer(&ext), Format: format, Type: vk.ImageType2d, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: exportUsage(format, r.hdrReadback)}
313 extOut := vk.ExternalImageFormatProperties{SType: vk.StructureTypeExternalImageFormatProperties}
314 out := vk.ImageFormatProperties2{SType: vk.StructureTypeImageFormatProperties2, Next: unsafe.Pointer(&extOut)}
315 if r.id.GetPhysicalDeviceImageFormatProperties2(physical, &info, &out) != vk.Success {
316 return false
317 }
318 return extOut.ExternalMemoryProperties.ExternalMemoryFeatures&feature != 0
319}
320
321// VK_FORMAT_FEATURE_TRANSFER_SRC_BIT (Vulkan 1.1), missing from the
322// generated bindings.
323const formatFeatureTransferSrc = 1 << 14
324
325// Exported SDR targets compose directly; HDR targets receive the final pass.
326const targetUsage = vk.ImageUsageTransferSrcBit | vk.ImageUsageColorAttachmentBit
327
328// Production HDR targets need only a color attachment; test readback opts
329// into transfer source before export and skips if the driver refuses it.
330func exportUsage(format vk.Format, readback bool) vk.ImageUsageFlags {
331 if format == vk.FormatA2r10g10b10UnormPack32 && !readback {
332 return vk.ImageUsageColorAttachmentBit
333 }
334 return targetUsage
335}
336
337// exportTarget creates one exported image with a modifier from mods.
338func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) {
339 d := r.dd
340 t := &target{exported: true, layout: vk.ImageLayoutUndefined}
341 format, fourcc := r.targetFormat()
342 ok := false
343 defer func() {
344 if !ok {
345 r.freeTarget(t)
346 }
347 }()
348 list := vk.ImageDrmFormatModifierListCreateInfoEXT{SType: vk.StructureTypeImageDRMFormatModifierListCreateInfoEXT, DrmFormatModifierCount: uint32(len(mods)), DrmFormatModifiers: &mods[0]}
349 external := vk.ExternalMemoryImageCreateInfo{SType: vk.StructureTypeExternalMemoryImageCreateInfo, Next: unsafe.Pointer(&list), HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
350 ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, Next: unsafe.Pointer(&external), ImageType: vk.ImageType2d, Format: format, Extent: vk.Extent3D{Width: uint32(r.width), Height: uint32(r.height), Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: exportUsage(format, r.hdrReadback), SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined}
351 if err := checked("vkCreateImage(target)", d.CreateImage(r.device, &ii, nil, &t.image)); err != nil {
352 return nil, ports.DMABuf{}, err
353 }
354 var req vk.MemoryRequirements
355 d.GetImageMemoryRequirements(r.device, t.image, &req)
356 kind, err := r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyDeviceLocalBit)
357 if err != nil {
358 return nil, ports.DMABuf{}, err
359 }
360 dedicated := vk.MemoryDedicatedAllocateInfo{SType: vk.StructureTypeMemoryDedicatedAllocateInfo, Image: t.image}
361 export := vk.ExportMemoryAllocateInfo{SType: vk.StructureTypeExportMemoryAllocateInfo, Next: unsafe.Pointer(&dedicated), HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
362 alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, Next: unsafe.Pointer(&export), AllocationSize: req.Size, MemoryTypeIndex: kind}
363 if err := checked("vkAllocateMemory(target)", d.AllocateMemory(r.device, &alloc, nil, &t.memory)); err != nil {
364 return nil, ports.DMABuf{}, err
365 }
366 if err := checked("vkBindImageMemory(target)", d.BindImageMemory(r.device, t.image, t.memory, 0)); err != nil {
367 return nil, ports.DMABuf{}, err
368 }
369 props := vk.ImageDrmFormatModifierPropertiesEXT{SType: vk.StructureTypeImageDRMFormatModifierPropertiesEXT}
370 if err := checked("vkGetImageDrmFormatModifierPropertiesEXT", d.GetImageDrmFormatModifierPropertiesEXT(r.device, t.image, &props)); err != nil {
371 return nil, ports.DMABuf{}, err
372 }
373 n := min(max(r.modPlanes[props.DrmFormatModifier], 1), maxModifierPlanes)
374 var layouts [maxModifierPlanes]vk.SubresourceLayout
375 for i := range n {
376 sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT << i}
377 d.GetImageSubresourceLayout(r.device, t.image, &sub, &layouts[i])
378 }
379 t.view, err = r.imageView(t.image, format)
380 if err != nil {
381 return nil, ports.DMABuf{}, err
382 }
383 var fd int32
384 get := vk.MemoryGetFdInfoKHR{SType: vk.StructureTypeMemoryGetFDInfoKHR, Memory: t.memory, HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
385 if err := checked("vkGetMemoryFdKHR", d.GetMemoryFdKHR(r.device, &get, &fd)); err != nil {
386 return nil, ports.DMABuf{}, err
387 }
388 // Every plane owns its file: the planes share the one memory object,
389 // so planes 1.. are duplicates of the exported fd.
390 planes := make([]ports.DMABufPlane, 0, n)
391 closePlanes := func() {
392 for _, p := range planes {
393 _ = p.File.Close()
394 }
395 }
396 first := os.NewFile(uintptr(fd), "neferwl-target")
397 if first == nil {
398 return nil, ports.DMABuf{}, fmt.Errorf("invalid exported fd %d", fd)
399 }
400 planes = append(planes, ports.DMABufPlane{File: first, Offset: uint32(layouts[0].Offset), Stride: uint32(layouts[0].RowPitch)})
401 for i := uint32(1); i < n; i++ {
402 dup, err := dupFile(first)
403 if err != nil {
404 closePlanes()
405 return nil, ports.DMABuf{}, fmt.Errorf("duplicate exported fd for plane %d: %w", i, err)
406 }
407 planes = append(planes, ports.DMABufPlane{File: os.NewFile(uintptr(dup), "neferwl-target"), Offset: uint32(layouts[i].Offset), Stride: uint32(layouts[i].RowPitch)})
408 }
409 r.log.Debug().Uint64("modifier", props.DrmFormatModifier).Uint32("planes", n).Msg("render target exported")
410 buf := ports.DMABuf{Width: r.width, Height: r.height, Format: fourcc, Modifier: props.DrmFormatModifier, Planes: planes}
411 ok = true
412 return t, buf, nil
413}
414
415func (r *Renderer) freeTarget(t *target) {
416 if t.view != 0 {
417 r.dd.DestroyImageView(r.device, t.view, nil)
418 t.view = 0
419 }
420 if t.image != 0 {
421 r.dd.DestroyImage(r.device, t.image, nil)
422 t.image = 0
423 }
424 if t.memory != 0 {
425 r.dd.FreeMemory(r.device, t.memory, nil)
426 t.memory = 0
427 }
428}
429
430func (r *Renderer) dropTargets() {
431 if len(r.targets) > 0 && r.device != 0 {
432 _ = checked("vkDeviceWaitIdle", r.dd.DeviceWaitIdle(r.device))
433 r.idle()
434 }
435 for _, t := range r.targets {
436 if r.last == t {
437 r.last = nil
438 }
439 r.freeTarget(t)
440 }
441 // New targets hold no HDR frame until the next Render.
442 r.targets, r.current, r.lastHDRTarget = nil, 0, -1
443}
444
445// What ImportTargets refuses an image for, before asking the driver.
446var (
447 errImageGeometry = errors.New("image size or format does not match the target")
448 errImageModifier = errors.New("image modifier is not an importable target modifier")
449 errImagePlanes = errors.New("image plane count does not match its modifier")
450 errImageDMABuf = errors.New("image planes are not in one dmabuf")
451)
452
453// importTarget makes a target of an image allocated outside the renderer.
454// The target is shared with the display like an exported one: frames
455// acquire it from and release it to the foreign queue family.
456func (r *Renderer) importTarget(b *ports.DMABuf) (*target, error) {
457 d := r.dd
458 format, fourcc := r.targetFormat()
459 n := len(b.Planes)
460 want, known := r.modPlanes[b.Modifier]
461 switch {
462 case b.Format != fourcc || b.Width != r.width || b.Height != r.height:
463 return nil, fmt.Errorf("%w: image %dx%d format %#x, target %dx%d format %#x", errImageGeometry, b.Width, b.Height, b.Format, r.width, r.height, fourcc)
464 case !known:
465 return nil, fmt.Errorf("%w: %#x is not a target modifier of the device", errImageModifier, b.Modifier)
466 case n < 1 || n > maxModifierPlanes || want != uint32(n):
467 return nil, fmt.Errorf("%w: modifier %#x: image has %d planes, device expects %d", errImagePlanes, b.Modifier, n, want)
468 case !r.externalTarget(r.physical, format, b.Modifier, vk.ExternalMemoryFeatureImportableBit):
469 return nil, fmt.Errorf("%w: %#x cannot be imported as a target", errImageModifier, b.Modifier)
470 }
471 if err := sameDMABuf(b.Planes); err != nil {
472 return nil, err
473 }
474 fd, err := dupFile(b.Planes[0].File)
475 if err != nil {
476 return nil, err
477 }
478 // Vulkan owns the fd it imports on success: hand it its own duplicate.
479 // Closed here on every path but the successful import.
480 defer func() {
481 if fd >= 0 {
482 _ = unix.Close(fd)
483 }
484 }()
485 t := &target{exported: true, layout: vk.ImageLayoutUndefined}
486 ok := false
487 defer func() {
488 if !ok {
489 r.freeTarget(t)
490 }
491 }()
492 var layouts [maxModifierPlanes]vk.SubresourceLayout
493 for i, p := range b.Planes {
494 layouts[i] = vk.SubresourceLayout{Offset: vk.DeviceSize(p.Offset), RowPitch: vk.DeviceSize(p.Stride)}
495 }
496 explicit := vk.ImageDrmFormatModifierExplicitCreateInfoEXT{SType: vk.StructureTypeImageDRMFormatModifierExplicitCreateInfoEXT, DrmFormatModifier: b.Modifier, DrmFormatModifierPlaneCount: uint32(n), PlaneLayouts: &layouts[0]}
497 external := vk.ExternalMemoryImageCreateInfo{SType: vk.StructureTypeExternalMemoryImageCreateInfo, Next: unsafe.Pointer(&explicit), HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
498 ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, Next: unsafe.Pointer(&external), ImageType: vk.ImageType2d, Format: format, Extent: vk.Extent3D{Width: uint32(r.width), Height: uint32(r.height), Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: exportUsage(format, r.hdrReadback), SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined}
499 if err := checked("vkCreateImage(imported target)", d.CreateImage(r.device, &ii, nil, &t.image)); err != nil {
500 return nil, err
501 }
502 fdProps := vk.MemoryFdPropertiesKHR{SType: vk.StructureTypeMemoryFDPropertiesKHR}
503 if err := checked("vkGetMemoryFdPropertiesKHR", d.GetMemoryFdPropertiesKHR(r.device, vk.ExternalMemoryHandleTypeDMABUFBitEXT, int32(fd), &fdProps)); err != nil {
504 return nil, err
505 }
506 var req vk.MemoryRequirements
507 d.GetImageMemoryRequirements(r.device, t.image, &req)
508 bits := req.MemoryTypeBits & fdProps.MemoryTypeBits
509 kind, err := r.findMemoryType(bits, vk.MemoryPropertyDeviceLocalBit)
510 if err != nil {
511 if kind, err = r.findMemoryType(bits, 0); err != nil {
512 return nil, err
513 }
514 }
515 dedicated := vk.MemoryDedicatedAllocateInfo{SType: vk.StructureTypeMemoryDedicatedAllocateInfo, Image: t.image}
516 imp := vk.ImportMemoryFdInfoKHR{SType: vk.StructureTypeImportMemoryFDInfoKHR, Next: unsafe.Pointer(&dedicated), HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT, Fd: int32(fd)}
517 alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, Next: unsafe.Pointer(&imp), AllocationSize: req.Size, MemoryTypeIndex: kind}
518 if err := checked("vkAllocateMemory(imported target)", d.AllocateMemory(r.device, &alloc, nil, &t.memory)); err != nil {
519 return nil, err
520 }
521 fd = -1 // Vulkan owns it now.
522 if err := checked("vkBindImageMemory(imported target)", d.BindImageMemory(r.device, t.image, t.memory, 0)); err != nil {
523 return nil, err
524 }
525 if t.view, err = r.imageView(t.image, format); err != nil {
526 return nil, err
527 }
528 r.log.Debug().Uint64("modifier", b.Modifier).Int("planes", n).Msg("render target imported")
529 ok = true
530 return t, nil
531}
532
533// sameDMABuf fails unless every plane file is the same dmabuf (same device
534// and inode): the target is imported from the first plane's memory only, so
535// planes in other buffers would read the wrong memory.
536func sameDMABuf(planes []ports.DMABufPlane) error {
537 var first unix.Stat_t
538 for i, p := range planes {
539 fd, err := dupFile(p.File)
540 if err != nil {
541 return fmt.Errorf("plane %d: %w", i, err)
542 }
543 var st unix.Stat_t
544 err = unix.Fstat(fd, &st)
545 _ = unix.Close(fd)
546 if err != nil {
547 return fmt.Errorf("stat plane %d: %w", i, err)
548 }
549 if i == 0 {
550 first = st
551 } else if st.Dev != first.Dev || st.Ino != first.Ino {
552 return fmt.Errorf("%w: plane %d is not in the dmabuf of plane 0", errImageDMABuf, i)
553 }
554 }
555 return nil
556}