targets.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 23

Tags 10

↑↓ moveenter openesc close