targets.go RawHistoryFolder

1package vulkan
2
3import (
4	"errors"
5	"fmt"
6	"os"
7	"slices"
8	"unsafe"
9
10	"github.com/bnema/neferwl/internal/ports"
11	vk "github.com/bnema/purego-vulkan/vulkan"
12)
13
14// Render targets (ADR 014). A DRM output scans out images the renderer
15// draws into: ExportTargets allocates them with a DRM format modifier the
16// display accepts and exports their memory as dmabufs, which the output
17// turns into KMS framebuffers. Render then composes straight into the
18// selected target and the output flips to it; nothing is copied by the
19// CPU. In HDR an internal fp16 linear image keeps full-range composition,
20// sampled by a full-screen GPU pass into the 10-bit target. Capture/Pixels
21// convert to SDR sRGB only when requested.
22
23// target is an image frames are drawn into.
24type target struct {
25	image  vk.Image
26	memory vk.DeviceMemory
27	layout vk.ImageLayout
28	view   vk.ImageView // color attachment for blended draws
29	// exported images are shared with the display: ownership goes to the
30	// foreign queue family after each frame, and KMS reads them.
31	exported bool
32	// What the image holds (damage.go): the scene Seq and each window's
33	// content Seq; valid once a frame was drawn whole.
34	valid    bool
35	sceneSeq uint64
36	windows  map[ports.WindowID]heldWindow
37}
38
39// fourccXRGB is DRM_FORMAT_XRGB8888: B8G8R8A8 in memory, alpha ignored.
40const fourccXRGB = 'X' | 'R'<<8 | '2'<<16 | '4'<<24
41
42// fourccXR30 is DRM_FORMAT_XRGB2101010 (A2R10G10B10 packed).
43const fourccXR30 = 'X' | 'R'<<8 | '3'<<16 | '0'<<24
44
45// ExportTargets allocates n exported images of the output size, replacing
46// the previous ones; n = 0 only drops them (back to the internal image).
47func (r *Renderer) ExportTargets(n int, modifiers []uint64) ([]ports.DMABuf, error) {
48	r.dropTargets()
49	if n > 0 && r.hdrNits > 0 && r.hdrReadback && r.physical != 0 {
50		// Test-only transfer-source targets have different format requirements.
51		r.hdrMods = r.probeModifiers(r.physical, vk.FormatA2r10g10b10UnormPack32)
52	}
53	if n > 0 && r.hdrNits > 0 && r.hdr.pipeline == 0 {
54		return nil, fmt.Errorf("HDR transform unavailable: %w", r.hdrError)
55	}
56	if n == 0 {
57		return nil, nil
58	}
59	if !r.dd.HasGetMemoryFdKHR() || !r.dd.HasGetImageDrmFormatModifierPropertiesEXT() || len(r.dmabuf.Formats) == 0 {
60		return nil, errors.New("device cannot export dmabufs")
61	}
62	mods := r.exportModifiers(modifiers)
63	if len(mods) == 0 {
64		if r.hdrNits == 0 {
65			return nil, errors.New("no XRGB8888 modifier both the device and the display accept")
66		}
67		return nil, errors.New("no XRGB2101010 modifier both the device and the display accept")
68	}
69	var out []ports.DMABuf
70	for range n {
71		t, buf, err := r.exportTarget(mods)
72		if err != nil {
73			r.dropTargets()
74			for _, b := range out {
75				for _, p := range b.Planes {
76					p.File.Close()
77				}
78			}
79			return nil, err
80		}
81		r.targets = append(r.targets, t)
82		out = append(out, buf)
83	}
84	r.current = 0
85	return out, nil
86}
87
88// UseTarget selects the exported image the next frames draw into.
89func (r *Renderer) UseTarget(i int) {
90	if i >= 0 && i < len(r.targets) {
91		r.current = i
92	}
93}
94
95// target is the image the next frame draws into.
96func (r *Renderer) target() *target {
97	if len(r.targets) > 0 {
98		return r.targets[r.current]
99	}
100	return &r.own
101}
102
103// exportModifiers intersects the selected signal format modifiers with
104// those supported by the display. HDR never assumes an unspecified list.
105func (r *Renderer) exportModifiers(display []uint64) []uint64 {
106	var out []uint64
107	available := r.renderMods
108	if r.hdrNits > 0 {
109		available = r.hdrMods
110	}
111	for _, m := range available {
112		if (len(display) == 0 && (r.hdrNits == 0 || r.hdrReadback)) || slices.Contains(display, m) {
113			out = append(out, m)
114		}
115	}
116	return out
117}
118
119// probeRenderModifiers lists single-plane, exportable SDR and HDR targets.
120func (r *Renderer) probeRenderModifiers(physical vk.PhysicalDevice) {
121	r.renderMods = r.probeModifiers(physical, vk.FormatB8g8r8a8Unorm)
122	r.hdrMods = r.probeModifiers(physical, vk.FormatA2r10g10b10UnormPack32)
123}
124
125func (r *Renderer) probeModifiers(physical vk.PhysicalDevice, format vk.Format) []uint64 {
126	list := vk.DrmFormatModifierPropertiesListEXT{SType: vk.StructureTypeDRMFormatModifierPropertiesListEXT}
127	fp := vk.FormatProperties2{SType: vk.StructureTypeFormatProperties2, Next: unsafe.Pointer(&list)}
128	r.id.GetPhysicalDeviceFormatProperties2(physical, format, &fp)
129	if list.DrmFormatModifierCount == 0 {
130		return nil
131	}
132	mods := make([]vk.DrmFormatModifierPropertiesEXT, list.DrmFormatModifierCount)
133	list.DrmFormatModifierProperties = &mods[0]
134	r.id.GetPhysicalDeviceFormatProperties2(physical, format, &fp)
135	need := vk.FormatFeatureFlags(vk.FormatFeatureColorAttachmentBit)
136	if format == vk.FormatB8g8r8a8Unorm {
137		need |= formatFeatureTransferSrc | vk.FormatFeatureColorAttachmentBlendBit
138	} else if r.hdrReadback {
139		need |= formatFeatureTransferSrc
140	}
141	var result []uint64
142	// SDR targets are composed and read back; HDR targets are color attachments.
143	for _, m := range mods[:list.DrmFormatModifierCount] {
144		if m.DrmFormatModifierPlaneCount == 1 && m.DrmFormatModifierTilingFeatures&need == need && r.exportable(physical, format, m.DrmFormatModifier) {
145			result = append(result, m.DrmFormatModifier)
146		}
147	}
148	return result
149}
150
151func (r *Renderer) exportable(physical vk.PhysicalDevice, format vk.Format, modifier uint64) bool {
152	mod := vk.PhysicalDeviceImageDrmFormatModifierInfoEXT{SType: vk.StructureTypePhysicalDeviceImageDRMFormatModifierInfoEXT, DrmFormatModifier: modifier, SharingMode: vk.SharingModeExclusive}
153	ext := vk.PhysicalDeviceExternalImageFormatInfo{SType: vk.StructureTypePhysicalDeviceExternalImageFormatInfo, Next: unsafe.Pointer(&mod), HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
154	info := vk.PhysicalDeviceImageFormatInfo2{SType: vk.StructureTypePhysicalDeviceImageFormatInfo2, Next: unsafe.Pointer(&ext), Format: format, Type: vk.ImageType2d, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: exportUsage(format, r.hdrReadback)}
155	extOut := vk.ExternalImageFormatProperties{SType: vk.StructureTypeExternalImageFormatProperties}
156	out := vk.ImageFormatProperties2{SType: vk.StructureTypeImageFormatProperties2, Next: unsafe.Pointer(&extOut)}
157	if r.id.GetPhysicalDeviceImageFormatProperties2(physical, &info, &out) != vk.Success {
158		return false
159	}
160	return extOut.ExternalMemoryProperties.ExternalMemoryFeatures&vk.ExternalMemoryFeatureExportableBit != 0
161}
162
163// VK_FORMAT_FEATURE_TRANSFER_SRC_BIT (Vulkan 1.1), missing from the
164// generated bindings.
165const formatFeatureTransferSrc = 1 << 14
166
167// Exported SDR targets compose directly; HDR targets receive the final pass.
168const targetUsage = vk.ImageUsageTransferSrcBit | vk.ImageUsageColorAttachmentBit
169
170// Production HDR targets need only a color attachment; test readback opts
171// into transfer source before export and skips if the driver refuses it.
172func exportUsage(format vk.Format, readback bool) vk.ImageUsageFlags {
173	if format == vk.FormatA2r10g10b10UnormPack32 && !readback {
174		return vk.ImageUsageColorAttachmentBit
175	}
176	return targetUsage
177}
178
179// exportTarget creates one exported image with a modifier from mods.
180func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) {
181	d := r.dd
182	t := &target{exported: true, layout: vk.ImageLayoutUndefined}
183	format, fourcc := vk.Format(vk.FormatB8g8r8a8Unorm), uint32(fourccXRGB)
184	if r.hdrNits > 0 {
185		format, fourcc = vk.FormatA2r10g10b10UnormPack32, fourccXR30
186	}
187	ok := false
188	defer func() {
189		if !ok {
190			r.freeTarget(t)
191		}
192	}()
193	list := vk.ImageDrmFormatModifierListCreateInfoEXT{SType: vk.StructureTypeImageDRMFormatModifierListCreateInfoEXT, DrmFormatModifierCount: uint32(len(mods)), DrmFormatModifiers: &mods[0]}
194	external := vk.ExternalMemoryImageCreateInfo{SType: vk.StructureTypeExternalMemoryImageCreateInfo, Next: unsafe.Pointer(&list), HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
195	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}
196	if err := checked("vkCreateImage(target)", d.CreateImage(r.device, &ii, nil, &t.image)); err != nil {
197		return nil, ports.DMABuf{}, err
198	}
199	var req vk.MemoryRequirements
200	d.GetImageMemoryRequirements(r.device, t.image, &req)
201	kind, err := r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyDeviceLocalBit)
202	if err != nil {
203		return nil, ports.DMABuf{}, err
204	}
205	dedicated := vk.MemoryDedicatedAllocateInfo{SType: vk.StructureTypeMemoryDedicatedAllocateInfo, Image: t.image}
206	export := vk.ExportMemoryAllocateInfo{SType: vk.StructureTypeExportMemoryAllocateInfo, Next: unsafe.Pointer(&dedicated), HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
207	alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, Next: unsafe.Pointer(&export), AllocationSize: req.Size, MemoryTypeIndex: kind}
208	if err := checked("vkAllocateMemory(target)", d.AllocateMemory(r.device, &alloc, nil, &t.memory)); err != nil {
209		return nil, ports.DMABuf{}, err
210	}
211	if err := checked("vkBindImageMemory(target)", d.BindImageMemory(r.device, t.image, t.memory, 0)); err != nil {
212		return nil, ports.DMABuf{}, err
213	}
214	props := vk.ImageDrmFormatModifierPropertiesEXT{SType: vk.StructureTypeImageDRMFormatModifierPropertiesEXT}
215	if err := checked("vkGetImageDrmFormatModifierPropertiesEXT", d.GetImageDrmFormatModifierPropertiesEXT(r.device, t.image, &props)); err != nil {
216		return nil, ports.DMABuf{}, err
217	}
218	var layout vk.SubresourceLayout
219	sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT}
220	d.GetImageSubresourceLayout(r.device, t.image, &sub, &layout)
221	t.view, err = r.imageView(t.image, format)
222	if err != nil {
223		return nil, ports.DMABuf{}, err
224	}
225	var fd int32
226	get := vk.MemoryGetFdInfoKHR{SType: vk.StructureTypeMemoryGetFDInfoKHR, Memory: t.memory, HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
227	if err := checked("vkGetMemoryFdKHR", d.GetMemoryFdKHR(r.device, &get, &fd)); err != nil {
228		return nil, ports.DMABuf{}, err
229	}
230	f := os.NewFile(uintptr(fd), "neferwl-target")
231	if f == nil {
232		return nil, ports.DMABuf{}, fmt.Errorf("invalid exported fd %d", fd)
233	}
234	buf := ports.DMABuf{Width: r.width, Height: r.height, Format: fourcc, Modifier: props.DrmFormatModifier, Planes: []ports.DMABufPlane{{File: f, Offset: uint32(layout.Offset), Stride: uint32(layout.RowPitch)}}}
235	ok = true
236	return t, buf, nil
237}
238
239func (r *Renderer) freeTarget(t *target) {
240	if t.view != 0 {
241		r.dd.DestroyImageView(r.device, t.view, nil)
242		t.view = 0
243	}
244	if t.image != 0 {
245		r.dd.DestroyImage(r.device, t.image, nil)
246		t.image = 0
247	}
248	if t.memory != 0 {
249		r.dd.FreeMemory(r.device, t.memory, nil)
250		t.memory = 0
251	}
252}
253
254func (r *Renderer) dropTargets() {
255	if len(r.targets) > 0 && r.device != 0 {
256		_ = checked("vkDeviceWaitIdle", r.dd.DeviceWaitIdle(r.device))
257		r.idle()
258	}
259	for _, t := range r.targets {
260		if r.last == t {
261			r.last = nil
262		}
263		r.freeTarget(t)
264	}
265	r.targets, r.current = nil, 0
266}

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 26

Tags 10

↑↓ moveenter openesc close