v0.1.0 d9218d6

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. The internal image is the target until then (headless, tests).
20// Readback into host memory happens only for Pixels (screenshots).
21
22// target is an image frames are drawn into.
23type target struct {
24	image  vk.Image
25	memory vk.DeviceMemory
26	layout vk.ImageLayout
27	view   vk.ImageView // color attachment for blended draws
28	// exported images are shared with the display: ownership goes to the
29	// foreign queue family after each frame, and KMS reads them.
30	exported bool
31	// What the image holds (damage.go): the scene Seq and each window's
32	// content Seq; valid once a frame was drawn whole.
33	valid    bool
34	sceneSeq uint64
35	windows  map[ports.WindowID]heldWindow
36}
37
38// fourccXRGB is DRM_FORMAT_XRGB8888: B8G8R8A8 in memory, alpha ignored.
39const fourccXRGB = 'X' | 'R'<<8 | '2'<<16 | '4'<<24
40
41// ExportTargets allocates n exported images of the output size, replacing
42// the previous ones; n = 0 only drops them (back to the internal image).
43func (r *Renderer) ExportTargets(n int, modifiers []uint64) ([]ports.DMABuf, error) {
44	r.dropTargets()
45	if n == 0 {
46		return nil, nil
47	}
48	if r.dd.GetMemoryFdKHR == nil || r.dd.GetImageDrmFormatModifierPropertiesEXT == nil || len(r.dmabuf.Formats) == 0 {
49		return nil, errors.New("device cannot export dmabufs")
50	}
51	mods := r.exportModifiers(modifiers)
52	if len(mods) == 0 {
53		return nil, errors.New("no XRGB8888 modifier both the device and the display accept")
54	}
55	var out []ports.DMABuf
56	for range n {
57		t, buf, err := r.exportTarget(mods)
58		if err != nil {
59			r.dropTargets()
60			for _, b := range out {
61				for _, p := range b.Planes {
62					p.File.Close()
63				}
64			}
65			return nil, err
66		}
67		r.targets = append(r.targets, t)
68		out = append(out, buf)
69	}
70	r.current = 0
71	return out, nil
72}
73
74// UseTarget selects the exported image the next frames draw into.
75func (r *Renderer) UseTarget(i int) {
76	if i >= 0 && i < len(r.targets) {
77		r.current = i
78	}
79}
80
81// target is the image the next frame draws into.
82func (r *Renderer) target() *target {
83	if len(r.targets) > 0 {
84		return r.targets[r.current]
85	}
86	return &r.own
87}
88
89// exportModifiers are the XRGB8888 modifiers the device renders to and
90// exports with one plane, restricted to the display's when given.
91func (r *Renderer) exportModifiers(display []uint64) []uint64 {
92	var out []uint64
93	for _, m := range r.renderMods {
94		if len(display) == 0 || slices.Contains(display, m) {
95			out = append(out, m)
96		}
97	}
98	return out
99}
100
101// probeRenderModifiers lists the B8G8R8A8 modifiers usable as a transfer
102// destination with a single plane that the device can export.
103func (r *Renderer) probeRenderModifiers(physical vk.PhysicalDevice) {
104	list := vk.DrmFormatModifierPropertiesListEXT{SType: vk.StructureTypeDRMFormatModifierPropertiesListEXT}
105	fp := vk.FormatProperties2{SType: vk.StructureTypeFormatProperties2, Next: unsafe.Pointer(&list)}
106	r.id.GetPhysicalDeviceFormatProperties2(physical, vk.FormatB8g8r8a8Unorm, &fp)
107	if list.DrmFormatModifierCount == 0 {
108		return
109	}
110	mods := make([]vk.DrmFormatModifierPropertiesEXT, list.DrmFormatModifierCount)
111	list.DrmFormatModifierProperties = &mods[0]
112	r.id.GetPhysicalDeviceFormatProperties2(physical, vk.FormatB8g8r8a8Unorm, &fp)
113	need := vk.FormatFeatureFlags(formatFeatureTransferSrc | vk.FormatFeatureColorAttachmentBit | vk.FormatFeatureColorAttachmentBlendBit)
114	// Targets are drawn on (compose.go) and read back for screenshots.
115	for _, m := range mods[:list.DrmFormatModifierCount] {
116		if m.DrmFormatModifierPlaneCount == 1 && m.DrmFormatModifierTilingFeatures&need == need && r.exportable(physical, m.DrmFormatModifier) {
117			r.renderMods = append(r.renderMods, m.DrmFormatModifier)
118		}
119	}
120}
121
122func (r *Renderer) exportable(physical vk.PhysicalDevice, modifier uint64) bool {
123	mod := vk.PhysicalDeviceImageDrmFormatModifierInfoEXT{SType: vk.StructureTypePhysicalDeviceImageDRMFormatModifierInfoEXT, DrmFormatModifier: modifier, SharingMode: vk.SharingModeExclusive}
124	ext := vk.PhysicalDeviceExternalImageFormatInfo{SType: vk.StructureTypePhysicalDeviceExternalImageFormatInfo, Next: unsafe.Pointer(&mod), HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
125	info := vk.PhysicalDeviceImageFormatInfo2{SType: vk.StructureTypePhysicalDeviceImageFormatInfo2, Next: unsafe.Pointer(&ext), Format: vk.FormatB8g8r8a8Unorm, Type: vk.ImageType2d, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: targetUsage}
126	extOut := vk.ExternalImageFormatProperties{SType: vk.StructureTypeExternalImageFormatProperties}
127	out := vk.ImageFormatProperties2{SType: vk.StructureTypeImageFormatProperties2, Next: unsafe.Pointer(&extOut)}
128	if r.id.GetPhysicalDeviceImageFormatProperties2(physical, &info, &out) != vk.Success {
129		return false
130	}
131	return extOut.ExternalMemoryProperties.ExternalMemoryFeatures&vk.ExternalMemoryFeatureExportableBit != 0
132}
133
134// VK_FORMAT_FEATURE_TRANSFER_SRC_BIT (Vulkan 1.1), missing from the
135// generated bindings.
136const formatFeatureTransferSrc = 1 << 14
137
138// Targets are drawn on (compose.go) and copied from for screenshots.
139const targetUsage = vk.ImageUsageTransferSrcBit | vk.ImageUsageColorAttachmentBit
140
141// exportTarget creates one exported image with a modifier from mods.
142func (r *Renderer) exportTarget(mods []uint64) (*target, ports.DMABuf, error) {
143	d := r.dd
144	t := &target{exported: true, layout: vk.ImageLayoutUndefined}
145	ok := false
146	defer func() {
147		if !ok {
148			r.freeTarget(t)
149		}
150	}()
151	list := vk.ImageDrmFormatModifierListCreateInfoEXT{SType: vk.StructureTypeImageDRMFormatModifierListCreateInfoEXT, DrmFormatModifierCount: uint32(len(mods)), DrmFormatModifiers: &mods[0]}
152	external := vk.ExternalMemoryImageCreateInfo{SType: vk.StructureTypeExternalMemoryImageCreateInfo, Next: unsafe.Pointer(&list), HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
153	ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, Next: unsafe.Pointer(&external), ImageType: vk.ImageType2d, Format: vk.FormatB8g8r8a8Unorm, Extent: vk.Extent3D{Width: uint32(r.width), Height: uint32(r.height), Depth: 1}, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingDRMFormatModifierEXT, Usage: targetUsage, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined}
154	if err := checked("vkCreateImage(target)", d.CreateImage(r.device, &ii, nil, &t.image)); err != nil {
155		return nil, ports.DMABuf{}, err
156	}
157	var req vk.MemoryRequirements
158	d.GetImageMemoryRequirements(r.device, t.image, &req)
159	kind, err := r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyDeviceLocalBit)
160	if err != nil {
161		return nil, ports.DMABuf{}, err
162	}
163	dedicated := vk.MemoryDedicatedAllocateInfo{SType: vk.StructureTypeMemoryDedicatedAllocateInfo, Image: t.image}
164	export := vk.ExportMemoryAllocateInfo{SType: vk.StructureTypeExportMemoryAllocateInfo, Next: unsafe.Pointer(&dedicated), HandleTypes: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
165	alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, Next: unsafe.Pointer(&export), AllocationSize: req.Size, MemoryTypeIndex: kind}
166	if err := checked("vkAllocateMemory(target)", d.AllocateMemory(r.device, &alloc, nil, &t.memory)); err != nil {
167		return nil, ports.DMABuf{}, err
168	}
169	if err := checked("vkBindImageMemory(target)", d.BindImageMemory(r.device, t.image, t.memory, 0)); err != nil {
170		return nil, ports.DMABuf{}, err
171	}
172	props := vk.ImageDrmFormatModifierPropertiesEXT{SType: vk.StructureTypeImageDRMFormatModifierPropertiesEXT}
173	if err := checked("vkGetImageDrmFormatModifierPropertiesEXT", d.GetImageDrmFormatModifierPropertiesEXT(r.device, t.image, &props)); err != nil {
174		return nil, ports.DMABuf{}, err
175	}
176	var layout vk.SubresourceLayout
177	sub := vk.ImageSubresource{AspectMask: vk.ImageAspectMemoryPlane0BitEXT}
178	d.GetImageSubresourceLayout(r.device, t.image, &sub, &layout)
179	if err := r.createView(t); err != nil {
180		return nil, ports.DMABuf{}, err
181	}
182	var fd int32
183	get := vk.MemoryGetFdInfoKHR{SType: vk.StructureTypeMemoryGetFDInfoKHR, Memory: t.memory, HandleType: vk.ExternalMemoryHandleTypeDMABUFBitEXT}
184	if err := checked("vkGetMemoryFdKHR", d.GetMemoryFdKHR(r.device, &get, &fd)); err != nil {
185		return nil, ports.DMABuf{}, err
186	}
187	f := os.NewFile(uintptr(fd), "neferwl-target")
188	if f == nil {
189		return nil, ports.DMABuf{}, fmt.Errorf("invalid exported fd %d", fd)
190	}
191	buf := ports.DMABuf{Width: r.width, Height: r.height, Format: fourccXRGB, Modifier: props.DrmFormatModifier, Planes: []ports.DMABufPlane{{File: f, Offset: uint32(layout.Offset), Stride: uint32(layout.RowPitch)}}}
192	ok = true
193	return t, buf, nil
194}
195
196func (r *Renderer) freeTarget(t *target) {
197	if t.view != 0 {
198		r.dd.DestroyImageView(r.device, t.view, nil)
199		t.view = 0
200	}
201	if t.image != 0 {
202		r.dd.DestroyImage(r.device, t.image, nil)
203		t.image = 0
204	}
205	if t.memory != 0 {
206		r.dd.FreeMemory(r.device, t.memory, nil)
207		t.memory = 0
208	}
209}
210
211func (r *Renderer) dropTargets() {
212	if len(r.targets) > 0 && r.device != 0 {
213		_ = checked("vkDeviceWaitIdle", r.dd.DeviceWaitIdle(r.device))
214		r.idle()
215	}
216	for _, t := range r.targets {
217		if r.last == t {
218			r.last = nil
219		}
220		r.freeTarget(t)
221	}
222	r.targets, r.current = nil, 0
223}

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