v0.7.3 bcc9a45

device.go RawHistoryFolder

1//go:build linux
2
3// Package vkdevice selects a Vulkan graphics device by compositor-advertised
4// DRM dev_t. It rejects cross-GPU fallback rather than silently copying images.
5package vkdevice
6
7import (
8	"bytes"
9	"encoding/binary"
10	"fmt"
11	"os"
12	"runtime"
13	"unsafe"
14
15	"github.com/bnema/purego-vulkan/vulkan"
16)
17
18// Identity uses the Linux gnu_dev_major/minor dev_t encoding. A Wayland
19// main_device is an 8-byte native-endian dev_t, not a Vulkan device index.
20type Identity struct{ Major, Minor uint32 }
21
22func DecodeDevice(device []byte) (Identity, error) {
23	if len(device) != 8 {
24		return Identity{}, fmt.Errorf("invalid main_device dev_t length %d", len(device))
25	}
26	d := binary.NativeEndian.Uint64(device)
27	return Identity{Major: uint32((d >> 8) & 0xfff), Minor: uint32((d & 0xff) | ((d >> 12) & 0xffffff00))}, nil
28}
29func DeviceIdentity(device uint64) Identity {
30	return Identity{Major: uint32((device >> 8) & 0xfff), Minor: uint32((device & 0xff) | ((device >> 12) & 0xffffff00))}
31}
32func Matches(main Identity, primary, render Identity, hasPrimary, hasRender bool) bool {
33	return (hasPrimary && main == primary) || (hasRender && main == render)
34}
35
36// RequiredExtensions are mandatory on the matched device. Vulkan 1.3
37// supplies dynamic rendering and synchronization2 as core features.
38var RequiredExtensions = []string{
39	"VK_EXT_physical_device_drm",
40	"VK_KHR_external_memory_fd",
41	"VK_EXT_external_memory_dma_buf",
42	"VK_EXT_image_drm_format_modifier",
43	"VK_KHR_external_semaphore_fd",
44}
45
46type Device struct {
47	Instance         vulkan.Instance
48	InstanceDispatch *vulkan.InstanceDispatch
49	Physical         vulkan.PhysicalDevice
50	Logical          vulkan.Device
51	Dispatch         *vulkan.DeviceDispatch
52	Queue            vulkan.Queue
53	QueueFamily      uint32
54	Render           Identity
55	Validation       *Validation
56	atlas            *glyphAtlas
57	images           *imageCache
58	imageDimension   int // application ceiling clamped to the selected device
59}
60
61func Open(main Identity) (_ *Device, err error) {
62	if err = vulkan.Init(); err != nil {
63		return nil, err
64	}
65	app := append([]byte("NeferGUI"), 0)
66	ai := vulkan.ApplicationInfo{SType: vulkan.StructureTypeApplicationInfo, ApplicationName: &app[0], ApiVersion: (1 << 22) | (3 << 12)}
67	ci := vulkan.InstanceCreateInfo{SType: vulkan.StructureTypeInstanceCreateInfo, ApplicationInfo: &ai}
68	var v *Validation
69	var layer, extension []byte
70	var layerPtr, extensionPtr *byte
71	if os.Getenv("NEFERGUI_VULKAN_VALIDATION") == "1" {
72		v = &Validation{}
73		if _, _, err = v.enabled(); err != nil {
74			return nil, err
75		}
76		layer = append([]byte(validationLayer), 0)
77		extension = append([]byte(debugExtension), 0)
78		layerPtr, extensionPtr = &layer[0], &extension[0]
79		ci.EnabledLayerCount = 1
80		ci.PpEnabledLayerNames = &layerPtr
81		ci.EnabledExtensionCount = 1
82		ci.PpEnabledExtensionNames = &extensionPtr
83	}
84	var instance vulkan.Instance
85	if err = vulkan.Check(vulkan.Global().CreateInstance(&ci, nil, &instance)); err != nil {
86		return nil, fmt.Errorf("create Vulkan 1.3 instance: %w", err)
87	}
88	id, e := vulkan.LoadInstanceDispatch(instance)
89	if e != nil {
90		vulkan.VkDestroyInstance(instance, nil)
91		return nil, e
92	}
93	d := &Device{Instance: instance, InstanceDispatch: id, Validation: v}
94	if v != nil {
95		if e = v.create(id, instance); e != nil {
96			d.Close()
97			return nil, e
98		}
99	}
100	defer func() {
101		if err != nil {
102			d.Close()
103		}
104	}()
105	var count uint32
106	if err = vulkan.Check(id.EnumeratePhysicalDevices(instance, &count, nil)); err != nil {
107		return nil, err
108	}
109	if count == 0 {
110		return nil, fmt.Errorf("no Vulkan physical devices; need DRM main_device %d:%d", main.Major, main.Minor)
111	}
112	physical := make([]vulkan.PhysicalDevice, count)
113	if err = vulkan.Check(id.EnumeratePhysicalDevices(instance, &count, &physical[0])); err != nil {
114		return nil, err
115	}
116	for _, candidate := range physical[:count] {
117		exts, e := extensions(id, candidate)
118		if e != nil {
119			return nil, e
120		}
121		if !exts["VK_EXT_physical_device_drm"] {
122			continue
123		}
124		props := vulkan.PhysicalDeviceProperties2{SType: vulkan.StructureTypePhysicalDeviceProperties2}
125		drm := vulkan.PhysicalDeviceDrmPropertiesEXT{SType: vulkan.StructureTypePhysicalDeviceDRMPropertiesEXT}
126		props.Next = unsafe.Pointer(&drm)
127		id.GetPhysicalDeviceProperties2(candidate, &props)
128		if !Matches(main, Identity{uint32(drm.PrimaryMajor), uint32(drm.PrimaryMinor)}, Identity{uint32(drm.RenderMajor), uint32(drm.RenderMinor)}, drm.HasPrimary != 0, drm.HasRender != 0) {
129			continue
130		}
131		if drm.HasRender == 0 {
132			return nil, fmt.Errorf("vulkan device matching main_device %d:%d has no render node", main.Major, main.Minor)
133		}
134		for _, required := range RequiredExtensions {
135			if !exts[required] {
136				return nil, fmt.Errorf("matching Vulkan device missing %s", required)
137			}
138		}
139		d.Physical = candidate
140		d.imageDimension = int(min(uint32(maxImageDimension), props.Properties.Limits.MaxImageDimension2D))
141		d.Render = Identity{uint32(drm.RenderMajor), uint32(drm.RenderMinor)}
142		break
143	}
144	if d.Physical == 0 {
145		return nil, fmt.Errorf("no Vulkan DRM device matches linux-dmabuf main_device %d:%d (no cross-GPU fallback)", main.Major, main.Minor)
146	}
147	features := vulkan.PhysicalDeviceFeatures2{SType: vulkan.StructureTypePhysicalDeviceFeatures2}
148	timeline := vulkan.PhysicalDeviceTimelineSemaphoreFeatures{SType: vulkan.StructureTypePhysicalDeviceTimelineSemaphoreFeatures}
149	sync2 := vulkan.PhysicalDeviceSynchronization2Features{SType: vulkan.StructureTypePhysicalDeviceSynchronization2Features}
150	dynamic := vulkan.PhysicalDeviceDynamicRenderingFeatures{SType: vulkan.StructureTypePhysicalDeviceDynamicRenderingFeatures}
151	features.Next = unsafe.Pointer(&timeline)
152	timeline.Next = unsafe.Pointer(&sync2)
153	sync2.Next = unsafe.Pointer(&dynamic)
154	id.GetPhysicalDeviceFeatures2(d.Physical, &features)
155	if timeline.TimelineSemaphore == 0 || sync2.Synchronization2 == 0 || dynamic.DynamicRendering == 0 {
156		return nil, fmt.Errorf("matching Vulkan device missing timeline/synchronization2/dynamic rendering feature")
157	}
158	var families uint32
159	id.GetPhysicalDeviceQueueFamilyProperties(d.Physical, &families, nil)
160	if families == 0 {
161		return nil, fmt.Errorf("matching Vulkan device has no graphics queue")
162	}
163	queues := make([]vulkan.QueueFamilyProperties, families)
164	id.GetPhysicalDeviceQueueFamilyProperties(d.Physical, &families, &queues[0])
165	found := false
166	for i, q := range queues[:families] {
167		if q.QueueCount != 0 && q.QueueFlags&vulkan.QueueGraphicsBit != 0 {
168			d.QueueFamily = uint32(i)
169			found = true
170			break
171		}
172	}
173	if !found {
174		return nil, fmt.Errorf("matching Vulkan device has no graphics queue")
175	}
176	priority := float32(1)
177	qci := vulkan.DeviceQueueCreateInfo{SType: vulkan.StructureTypeDeviceQueueCreateInfo, QueueFamilyIndex: d.QueueFamily, QueueCount: 1, QueuePriorities: &priority}
178	// Enable only the extensions actually used; core 1.3 features still need
179	// their feature-chain Boolean set explicitly at vkCreateDevice.
180	names := make([][]byte, 0, len(RequiredExtensions)-1)
181	ptrs := make([]*byte, 0, len(RequiredExtensions)-1)
182	for _, name := range RequiredExtensions {
183		if name == "VK_EXT_physical_device_drm" {
184			continue
185		}
186		names = append(names, append([]byte(name), 0))
187		ptrs = append(ptrs, &names[len(names)-1][0])
188	}
189	dci := vulkan.DeviceCreateInfo{SType: vulkan.StructureTypeDeviceCreateInfo, Next: unsafe.Pointer(&timeline), QueueCreateInfoCount: 1, QueueCreateInfos: &qci, EnabledExtensionCount: uint32(len(ptrs)), PpEnabledExtensionNames: &ptrs[0]}
190	if err = vulkan.Check(id.CreateDevice(d.Physical, &dci, nil, &d.Logical)); err != nil {
191		return nil, fmt.Errorf("create matched Vulkan device: %w", err)
192	}
193	d.Dispatch, err = vulkan.LoadDeviceDispatch(id, d.Logical)
194	if err != nil {
195		vulkan.VkDestroyDevice(d.Logical, nil)
196		d.Logical = 0
197		return nil, err
198	}
199	d.Dispatch.GetDeviceQueue(d.Logical, d.QueueFamily, 0, &d.Queue)
200	runtime.KeepAlive(app)
201	runtime.KeepAlive(layer)
202	runtime.KeepAlive(extension)
203	runtime.KeepAlive(names)
204	runtime.KeepAlive(ptrs)
205	return d, nil
206}
207
208func (d *Device) Close() {
209	if d == nil {
210		return
211	}
212	if d.Logical != 0 {
213		if d.Dispatch != nil {
214			_ = vulkan.Check(d.Dispatch.DeviceWaitIdle(d.Logical))
215			if d.atlas != nil {
216				d.atlas.Close()
217				d.atlas = nil
218			}
219			if d.images != nil {
220				d.images.Close()
221				d.images = nil
222			}
223			d.Dispatch.DestroyDevice(d.Logical, nil)
224		}
225		d.Logical = 0
226	}
227	if d.Instance != 0 {
228		if d.Validation != nil {
229			d.Validation.destroy(d.InstanceDispatch, d.Instance)
230		}
231		d.InstanceDispatch.DestroyInstance(d.Instance, nil)
232		d.Instance = 0
233	}
234}
235
236func extensions(id *vulkan.InstanceDispatch, physical vulkan.PhysicalDevice) (map[string]bool, error) {
237	var count uint32
238	if err := vulkan.Check(id.EnumerateDeviceExtensionProperties(physical, nil, &count, nil)); err != nil {
239		return nil, err
240	}
241	found := map[string]bool{}
242	if count == 0 {
243		return found, nil
244	}
245	props := make([]vulkan.ExtensionProperties, count)
246	if err := vulkan.Check(id.EnumerateDeviceExtensionProperties(physical, nil, &count, &props[0])); err != nil {
247		return nil, err
248	}
249	for _, p := range props[:count] {
250		found[string(bytes.TrimRight(p.ExtensionName[:], "\x00"))] = true
251	}
252	return found, nil
253}

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 1

Tags 12

↑↓ moveenter openesc close