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}