main 7a3b92c

device.go RawHistoryFolder

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) }

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 5

Tags 4

↑↓ moveenter openesc close