1// Package gpu renders the cell grid with raw Vulkan into DMA-BUF images that
2// the Wayland layer can hand to the compositor.
3package gpu
4
5import (
6 "bytes"
7 "encoding/binary"
8 "fmt"
9 "math"
10 "unsafe"
11
12 "github.com/bnema/purego-vulkan/vulkan"
13)
14
15// requiredExtensions are mandatory on the device that matches the compositor's
16// linux-dmabuf main device.
17var requiredExtensions = []string{
18 "VK_KHR_external_memory_fd",
19 "VK_EXT_external_memory_dma_buf",
20 "VK_EXT_image_drm_format_modifier",
21}
22
23// Device is the Vulkan device chosen by the compositor's main_device.
24type Device struct {
25 instance vulkan.Instance
26 idisp *vulkan.InstanceDispatch
27 phys vulkan.PhysicalDevice
28 logical vulkan.Device
29 disp *vulkan.DeviceDispatch
30 queue vulkan.Queue
31 family uint32
32 // RenderMinor is the minor of the matching /dev/dri/renderD node.
33 RenderMinor uint32
34
35 memProps vulkan.PhysicalDeviceMemoryProperties
36}
37
38func devMajor(d uint64) uint32 { return uint32((d >> 8) & 0xfff) }
39func devMinor(d uint64) uint32 { return uint32((d & 0xff) | ((d >> 12) & 0xffffff00)) }
40
41// Open selects the Vulkan device whose primary or render node matches
42// mainDevice (a dev_t). There is no cross-GPU fallback.
43func Open(mainDevice uint64) (_ *Device, err error) {
44 if err = vulkan.Init(); err != nil {
45 return nil, err
46 }
47 name := []byte("neferbar\x00")
48 ai := vulkan.ApplicationInfo{SType: vulkan.StructureTypeApplicationInfo, ApplicationName: &name[0], ApiVersion: 1<<22 | 3<<12}
49 ci := vulkan.InstanceCreateInfo{SType: vulkan.StructureTypeInstanceCreateInfo, ApplicationInfo: &ai}
50 d := &Device{}
51 if err = vulkan.Check(vulkan.Global().CreateInstance(&ci, nil, &d.instance)); err != nil {
52 return nil, fmt.Errorf("gpu: create Vulkan 1.3 instance: %w", err)
53 }
54 if d.idisp, err = vulkan.LoadInstanceDispatch(d.instance); err != nil {
55 vulkan.VkDestroyInstance(d.instance, nil)
56 return nil, err
57 }
58 defer func() {
59 if err != nil {
60 d.Close()
61 }
62 }()
63 if err = d.pickPhysical(mainDevice); err != nil {
64 return nil, err
65 }
66 if err = d.createLogical(); err != nil {
67 return nil, err
68 }
69 d.idisp.GetPhysicalDeviceMemoryProperties(d.phys, &d.memProps)
70 return d, nil
71}
72
73func (d *Device) pickPhysical(main uint64) error {
74 var count uint32
75 if err := vulkan.Check(d.idisp.EnumeratePhysicalDevices(d.instance, &count, nil)); err != nil {
76 return err
77 }
78 if count == 0 {
79 return fmt.Errorf("gpu: no Vulkan physical device")
80 }
81 all := make([]vulkan.PhysicalDevice, count)
82 if err := vulkan.Check(d.idisp.EnumeratePhysicalDevices(d.instance, &count, &all[0])); err != nil {
83 return err
84 }
85 mj, mn := devMajor(main), devMinor(main)
86 for _, cand := range all[:count] {
87 exts, err := d.deviceExtensions(cand)
88 if err != nil {
89 return err
90 }
91 if !exts["VK_EXT_physical_device_drm"] {
92 continue
93 }
94 props := vulkan.PhysicalDeviceProperties2{SType: vulkan.StructureTypePhysicalDeviceProperties2}
95 drm := vulkan.PhysicalDeviceDrmPropertiesEXT{SType: vulkan.StructureTypePhysicalDeviceDRMPropertiesEXT}
96 props.Next = unsafe.Pointer(&drm)
97 d.idisp.GetPhysicalDeviceProperties2(cand, &props)
98 primary := drm.HasPrimary != 0 && uint32(drm.PrimaryMajor) == mj && uint32(drm.PrimaryMinor) == mn
99 render := drm.HasRender != 0 && uint32(drm.RenderMajor) == mj && uint32(drm.RenderMinor) == mn
100 if !primary && !render {
101 continue
102 }
103 if drm.HasRender == 0 {
104 return fmt.Errorf("gpu: device matching main_device %d:%d has no render node", mj, mn)
105 }
106 for _, need := range requiredExtensions {
107 if !exts[need] {
108 return fmt.Errorf("gpu: matching device lacks %s", need)
109 }
110 }
111 d.phys, d.RenderMinor = cand, uint32(drm.RenderMinor)
112 return nil
113 }
114 return fmt.Errorf("gpu: no Vulkan device matches linux-dmabuf main_device %d:%d", mj, mn)
115}
116
117func (d *Device) deviceExtensions(p vulkan.PhysicalDevice) (map[string]bool, error) {
118 var count uint32
119 if err := vulkan.Check(d.idisp.EnumerateDeviceExtensionProperties(p, nil, &count, nil)); err != nil {
120 return nil, err
121 }
122 out := map[string]bool{}
123 if count == 0 {
124 return out, nil
125 }
126 props := make([]vulkan.ExtensionProperties, count)
127 if err := vulkan.Check(d.idisp.EnumerateDeviceExtensionProperties(p, nil, &count, &props[0])); err != nil {
128 return nil, err
129 }
130 for _, e := range props[:count] {
131 out[string(bytes.TrimRight(e.ExtensionName[:], "\x00"))] = true
132 }
133 return out, nil
134}
135
136func (d *Device) createLogical() error {
137 var families uint32
138 d.idisp.GetPhysicalDeviceQueueFamilyProperties(d.phys, &families, nil)
139 if families == 0 {
140 return fmt.Errorf("gpu: no queue family")
141 }
142 qs := make([]vulkan.QueueFamilyProperties, families)
143 d.idisp.GetPhysicalDeviceQueueFamilyProperties(d.phys, &families, &qs[0])
144 found := false
145 for i, q := range qs[:families] {
146 if q.QueueCount != 0 && q.QueueFlags&vulkan.QueueGraphicsBit != 0 {
147 d.family, found = uint32(i), true
148 break
149 }
150 }
151 if !found {
152 return fmt.Errorf("gpu: no graphics queue")
153 }
154 sync2 := vulkan.PhysicalDeviceSynchronization2Features{SType: vulkan.StructureTypePhysicalDeviceSynchronization2Features, Synchronization2: 1}
155 dyn := vulkan.PhysicalDeviceDynamicRenderingFeatures{SType: vulkan.StructureTypePhysicalDeviceDynamicRenderingFeatures, Next: unsafe.Pointer(&sync2), DynamicRendering: 1}
156 prio := float32(1)
157 qci := vulkan.DeviceQueueCreateInfo{SType: vulkan.StructureTypeDeviceQueueCreateInfo, QueueFamilyIndex: d.family, QueueCount: 1, QueuePriorities: &prio}
158 names := make([][]byte, len(requiredExtensions))
159 ptrs := make([]*byte, len(requiredExtensions))
160 for i, n := range requiredExtensions {
161 names[i] = append([]byte(n), 0)
162 ptrs[i] = &names[i][0]
163 }
164 dci := vulkan.DeviceCreateInfo{SType: vulkan.StructureTypeDeviceCreateInfo, Next: unsafe.Pointer(&dyn), QueueCreateInfoCount: 1, QueueCreateInfos: &qci, EnabledExtensionCount: uint32(len(ptrs)), PpEnabledExtensionNames: &ptrs[0]}
165 if err := vulkan.Check(d.idisp.CreateDevice(d.phys, &dci, nil, &d.logical)); err != nil {
166 return fmt.Errorf("gpu: create device: %w", err)
167 }
168 var err error
169 if d.disp, err = vulkan.LoadDeviceDispatch(d.idisp, d.logical); err != nil {
170 return err
171 }
172 d.disp.GetDeviceQueue(d.logical, d.family, 0, &d.queue)
173 return nil
174}
175
176// memoryType finds a memory type index allowed by bits with all want flags.
177func (d *Device) memoryType(bits uint32, want vulkan.MemoryPropertyFlags) (uint32, error) {
178 for i := uint32(0); i < d.memProps.MemoryTypeCount; i++ {
179 if bits&(1<<i) != 0 && d.memProps.MemoryTypes[i].PropertyFlags&want == want {
180 return i, nil
181 }
182 }
183 return math.MaxUint32, fmt.Errorf("gpu: no memory type for flags %#x", want)
184}
185
186// WaitIdle blocks until the queue has finished all submitted work.
187func (d *Device) WaitIdle() error { return vulkan.Check(d.disp.DeviceWaitIdle(d.logical)) }
188
189// Close destroys the device and the instance.
190func (d *Device) Close() {
191 if d == nil {
192 return
193 }
194 if d.logical != 0 {
195 _ = d.WaitIdle()
196 d.disp.DestroyDevice(d.logical, nil)
197 d.logical = 0
198 }
199 if d.instance != 0 {
200 d.idisp.DestroyInstance(d.instance, nil)
201 d.instance = 0
202 }
203}
204
205// Modifier is a DRM format modifier both the GPU and the compositor accept.
206type Modifier uint64
207
208// ExportableModifiers lists single-plane modifiers of B8G8R8A8 the GPU can
209// render to and export as DMA-BUF.
210func (d *Device) ExportableModifiers() (map[uint64]bool, error) {
211 props := vulkan.FormatProperties2{SType: vulkan.StructureTypeFormatProperties2}
212 list := vulkan.DrmFormatModifierPropertiesList2EXT{SType: vulkan.StructureTypeDRMFormatModifierPropertiesList2EXT}
213 props.Next = unsafe.Pointer(&list)
214 d.idisp.GetPhysicalDeviceFormatProperties2(d.phys, vulkan.FormatB8g8r8a8Unorm, &props)
215 out := map[uint64]bool{}
216 if list.DrmFormatModifierCount == 0 {
217 return out, nil
218 }
219 mods := make([]vulkan.DrmFormatModifierProperties2EXT, list.DrmFormatModifierCount)
220 list.DrmFormatModifierProperties = &mods[0]
221 d.idisp.GetPhysicalDeviceFormatProperties2(d.phys, vulkan.FormatB8g8r8a8Unorm, &props)
222 for _, m := range mods[:list.DrmFormatModifierCount] {
223 if m.DrmFormatModifierPlaneCount != 1 || m.DrmFormatModifierTilingFeatures&vulkan.FormatFeature2ColorAttachmentBit == 0 {
224 continue
225 }
226 mi := vulkan.PhysicalDeviceImageDrmFormatModifierInfoEXT{SType: vulkan.StructureTypePhysicalDeviceImageDRMFormatModifierInfoEXT, DrmFormatModifier: m.DrmFormatModifier, SharingMode: vulkan.SharingModeExclusive}
227 ei := vulkan.PhysicalDeviceExternalImageFormatInfo{SType: vulkan.StructureTypePhysicalDeviceExternalImageFormatInfo, Next: unsafe.Pointer(&mi), HandleType: vulkan.ExternalMemoryHandleTypeDMABUFBitEXT}
228 info := vulkan.PhysicalDeviceImageFormatInfo2{SType: vulkan.StructureTypePhysicalDeviceImageFormatInfo2, Next: unsafe.Pointer(&ei), Format: vulkan.FormatB8g8r8a8Unorm, Type: vulkan.ImageType2d, Tiling: vulkan.ImageTilingDRMFormatModifierEXT, Usage: vulkan.ImageUsageColorAttachmentBit}
229 ext := vulkan.ExternalImageFormatProperties{SType: vulkan.StructureTypeExternalImageFormatProperties}
230 ip := vulkan.ImageFormatProperties2{SType: vulkan.StructureTypeImageFormatProperties2, Next: unsafe.Pointer(&ext)}
231 r := d.idisp.GetPhysicalDeviceImageFormatProperties2(d.phys, &info, &ip)
232 if r == vulkan.ErrorFormatNotSupported {
233 continue
234 }
235 if err := vulkan.Check(r); err != nil {
236 return nil, fmt.Errorf("gpu: query modifier %#x: %w", m.DrmFormatModifier, err)
237 }
238 if ext.ExternalMemoryProperties.ExternalMemoryFeatures&vulkan.ExternalMemoryFeatureExportableBit != 0 {
239 out[m.DrmFormatModifier] = true
240 }
241 }
242 return out, nil
243}
244
245// DRMFormatXRGB8888 is the fourcc of XRGB8888, which maps to B8G8R8A8.
246const DRMFormatXRGB8888 uint32 = 0x34325258
247
248// FormatPair is one DRM format and modifier the compositor accepts.
249type FormatPair struct {
250 FourCC uint32
251 Modifier uint64
252}
253
254// ChooseModifier returns the first compositor-preferred XRGB8888 modifier the
255// GPU can export.
256func ChooseModifier(formats []FormatPair, supported map[uint64]bool) (Modifier, error) {
257 for _, f := range formats {
258 if f.FourCC == DRMFormatXRGB8888 && supported[f.Modifier] {
259 return Modifier(f.Modifier), nil
260 }
261 }
262 return 0, fmt.Errorf("gpu: no XRGB8888 modifier shared by the GPU and the compositor")
263}
264
265func le32(b []byte) uint32 { return binary.LittleEndian.Uint32(b) }