renderer.go RawHistoryFolder

1package vulkan
2
3import (
4	"fmt"
5	"image"
6	"math"
7	"os"
8	"runtime"
9	"strconv"
10	"unsafe"
11
12	"github.com/bnema/neferwl/internal/ports"
13	"github.com/bnema/zerowrap"
14
15	vk "github.com/bnema/purego-vulkan/vulkan"
16)
17
18// Renderer is owned by one goroutine; its methods must not be called concurrently.
19type Renderer struct {
20	width, height int
21	// queuePriority is the global priority the queue got: realtime, high
22	// or default.
23	queuePriority string
24	instance      vk.Instance
25	id            *vk.InstanceDispatch
26	device        vk.Device
27	dd            *vk.DeviceDispatch
28	queue         vk.Queue
29	// own is the SDR composition image; HDR uses hdrOwn and PQ targets.
30	own        target
31	hdrOwn     target // linear BT.709 in units of SDR reference white
32	targets    []*target
33	current    int
34	renderMods []uint64
35	hdrMods    []uint64
36	hdrNits    float64
37	hdr        hdrPass
38	hdrError   error
39	// modPlanes is the memory plane count of each probed target modifier
40	// (SDR and HDR): DCC modifiers have more than one.
41	modPlanes map[uint64]uint32
42	// log reports the chosen target modifier; the zero value discards.
43	log zerowrap.Logger
44	// hdrReadback makes exported HDR targets transfer sources, for GPU tests
45	// and headless --screenshot-raw (SetHDRReadback); never DRM.
46	hdrReadback bool
47	// virtual: the output has no display (headless), so ExportTargets
48	// accepts an empty modifier list for HDR (SetVirtualOutput).
49	virtual  bool
50	physical vk.PhysicalDevice
51	// One converted cursor image is kept; changes replace it in bounded memory.
52	cursorCache cursorConversion
53	// lastHDRTarget is the index in targets of the exported PQ target the
54	// last HDR frame rendered into; -1 when the current targets hold none.
55	lastHDRTarget int
56	// last is the target of the last frame: what captures copy.
57	last *target
58	// captures are the readback slots (capture.go), allocated on the
59	// first capture; capturePass converts HDR frames into captureSDR.
60	captures    []*captureSlot
61	capturePass hdrPass
62	captureSDR  target
63	sync        captureSync
64	pool        vk.CommandPool
65	memory      vk.PhysicalDeviceMemoryProperties
66	family      uint32
67	// Frames in flight (frame.go): frame is the last frame recorded,
68	// submitted the last submitted, completed the last known finished.
69	slots                       [frameSlots]frameSlot
70	frame, submitted, completed uint64
71	deferred                    []deferredFree
72	// syncFD: frames export a sync file (else Render waits for them).
73	syncFD bool
74	// dmabuf is what clients may send; imports are their buffers by ID.
75	dmabuf  ports.DMABufSupport
76	imports map[uint64]*imported
77	// pools are client wl_shm pools mapped by pool ID.
78	pools map[uint64]*mapping
79	// retired are pool mappings replaced this frame, unmapped once the
80	// frame's copies are done.
81	retired [][]byte
82	// shm are the GPU copies of wl_shm surfaces (shm.go); copied counts
83	// the bytes copied into them, for tests.
84	shm    map[shmKey]*shmSurface
85	copied int
86	// shmRects is scratch for shmCopyFor.
87	shmRects []image.Rectangle
88	// marks date the frames for Trim's wall-clock eviction (trim.go).
89	marks []trimMark
90	// redrawn counts the target pixels drawn (TakeRedrawn).
91	redrawn int
92	// cursors are the exported cursor images (CursorBuffers).
93	cursors [2]*cursorImage
94	// compose draws every frame (compose.go); maxRange bounds one
95	// storage buffer binding.
96	compose  composer
97	maxRange int
98	// Scratch belongs to the renderer goroutine; neither slice is published.
99	scratchDraws  []draw
100	scratchCovers []image.Rectangle
101	damage        damageRegion
102	damageSeen    map[ports.WindowID]bool
103	damageDrawn   map[ports.WindowID]heldWindow
104	frameDMAs     []*imported
105	frameAcquires map[*imported]*os.File
106	frameWaits    []vk.Semaphore
107	frameStages   []vk.PipelineStageFlags
108}
109
110// priorityAttempts falls back to an ordinary queue when elevated priority is unavailable.
111func priorityAttempts(supported bool) []vk.QueueGlobalPriority {
112	if supported {
113		return []vk.QueueGlobalPriority{vk.QueueGlobalPriorityRealtime, vk.QueueGlobalPriorityHigh, 0}
114	}
115	return []vk.QueueGlobalPriority{0}
116}
117
118func checked(name string, result vk.Result) error {
119	if err := vk.Check(result); err != nil {
120		return fmt.Errorf("%s: %w", name, err)
121	}
122	return nil
123}
124
125func New(width, height int) (r *Renderer, err error) {
126	if width <= 0 || height <= 0 || width > math.MaxUint32 || height > math.MaxUint32 || uint64(width)*uint64(height) > uint64(math.MaxInt/4) {
127		return nil, fmt.Errorf("invalid renderer dimensions %d x %d", width, height)
128	}
129	r = &Renderer{width: width, height: height, lastHDRTarget: -1, imports: map[uint64]*imported{}, pools: map[uint64]*mapping{}, shm: map[shmKey]*shmSurface{}}
130	defer func() {
131		if err != nil {
132			r.Close()
133			r = nil
134		}
135	}()
136	if err = vk.Init(); err != nil {
137		return nil, fmt.Errorf("vk.Init: %w", err)
138	}
139	app := vk.ApplicationInfo{SType: vk.StructureTypeApplicationInfo, ApiVersion: vk.MakeVersion(1, 3, 0)}
140	info := vk.InstanceCreateInfo{SType: vk.StructureTypeInstanceCreateInfo, ApplicationInfo: &app}
141	if err = checked("vkCreateInstance", vk.Global().CreateInstance(&info, nil, &r.instance)); err != nil {
142		return
143	}
144	r.id, err = vk.LoadInstanceDispatch(r.instance)
145	if err != nil {
146		err = fmt.Errorf("LoadInstanceDispatch: %w", err)
147		return
148	}
149	var count uint32
150	if err = checked("vkEnumeratePhysicalDevices(count)", r.id.EnumeratePhysicalDevices(r.instance, &count, nil)); err != nil {
151		return
152	}
153	if count == 0 {
154		err = fmt.Errorf("no Vulkan 1.3 device")
155		return
156	}
157	devices := make([]vk.PhysicalDevice, count)
158	if err = checked("vkEnumeratePhysicalDevices", r.id.EnumeratePhysicalDevices(r.instance, &count, &devices[0])); err != nil {
159		return
160	}
161	index := -1
162	if value, ok := os.LookupEnv("NEFERWL_VK_DEVICE"); ok {
163		index, err = strconv.Atoi(value)
164		if err != nil || index < 0 || index >= int(count) {
165			err = fmt.Errorf("invalid NEFERWL_VK_DEVICE index %q", value)
166			return
167		}
168	}
169	var physical vk.PhysicalDevice
170	var family uint32
171	for i, device := range devices[:count] {
172		if index >= 0 && i != index {
173			continue
174		}
175		var properties vk.PhysicalDeviceProperties
176		r.id.GetPhysicalDeviceProperties(device, &properties)
177		if properties.ApiVersion < vk.MakeVersion(1, 3, 0) {
178			continue
179		}
180		var n uint32
181		r.id.GetPhysicalDeviceQueueFamilyProperties(device, &n, nil)
182		if n == 0 {
183			continue
184		}
185		families := make([]vk.QueueFamilyProperties, n)
186		r.id.GetPhysicalDeviceQueueFamilyProperties(device, &n, &families[0])
187		for j, f := range families[:n] {
188			if f.QueueCount > 0 && f.QueueFlags&vk.QueueGraphicsBit != 0 {
189				physical = device
190				family = uint32(j)
191				break
192			}
193		}
194		if physical != 0 {
195			break
196		}
197	}
198	if physical == 0 {
199		err = fmt.Errorf("no Vulkan 1.3 device with a graphics queue")
200		return
201	}
202	r.id.GetPhysicalDeviceMemoryProperties(physical, &r.memory)
203	var properties vk.PhysicalDeviceProperties
204	r.id.GetPhysicalDeviceProperties(physical, &properties)
205	r.maxRange = int(properties.Limits.MaxStorageBufferRange)
206	r.family = family
207	priority := float32(1)
208	qi := vk.DeviceQueueCreateInfo{SType: vk.StructureTypeDeviceQueueCreateInfo, QueueFamilyIndex: family, QueueCount: 1, QueuePriorities: &priority}
209	dynamicRendering := vk.PhysicalDeviceDynamicRenderingFeatures{SType: vk.StructureTypePhysicalDeviceDynamicRenderingFeatures, DynamicRendering: 1}
210	di := vk.DeviceCreateInfo{SType: vk.StructureTypeDeviceCreateInfo, Next: unsafe.Pointer(&dynamicRendering), QueueCreateInfoCount: 1, QueueCreateInfos: &qi}
211	// dmabuf import needs every extension; without them clients use wl_shm.
212	var extNames [][]byte
213	var extPtrs []*byte
214	have := r.extensions(physical)
215	dmabufSupported := true
216	for _, e := range deviceExtensions {
217		if !have[e] {
218			dmabufSupported = false
219		}
220	}
221	if dmabufSupported {
222		for _, e := range deviceExtensions {
223			extNames = append(extNames, append([]byte(e), 0))
224			extPtrs = append(extPtrs, &extNames[len(extNames)-1][0])
225		}
226		di.EnabledExtensionCount = uint32(len(extPtrs))
227		di.PpEnabledExtensionNames = &extPtrs[0]
228	}
229	globalExt := ""
230	if have[vk.KHRGlobalPriorityExtensionName] {
231		globalExt = vk.KHRGlobalPriorityExtensionName
232	} else if have[vk.EXTGlobalPriorityExtensionName] {
233		globalExt = vk.EXTGlobalPriorityExtensionName
234	}
235	var global vk.DeviceQueueGlobalPriorityCreateInfo
236	chosen := "default"
237	for _, attempt := range priorityAttempts(globalExt != "") {
238		qi.Next = nil
239		count := len(extPtrs)
240		if attempt != 0 {
241			global = vk.DeviceQueueGlobalPriorityCreateInfo{SType: vk.StructureTypeDeviceQueueGlobalPriorityCreateInfo, GlobalPriority: attempt}
242			qi.Next = unsafe.Pointer(&global)
243			extNames = append(extNames, append([]byte(globalExt), 0))
244			extPtrs = append(extPtrs, &extNames[len(extNames)-1][0])
245			chosen = "realtime"
246			if attempt == vk.QueueGlobalPriorityHigh {
247				chosen = "high"
248			}
249		} else {
250			chosen = "default"
251		}
252		di.EnabledExtensionCount = uint32(len(extPtrs))
253		if len(extPtrs) > 0 {
254			di.PpEnabledExtensionNames = &extPtrs[0]
255		} else {
256			di.PpEnabledExtensionNames = nil
257		}
258		result := r.id.CreateDevice(physical, &di, nil, &r.device)
259		runtime.KeepAlive(global)
260		runtime.KeepAlive(qi)
261		runtime.KeepAlive(extNames)
262		runtime.KeepAlive(extPtrs)
263		extPtrs = extPtrs[:count]
264		extNames = extNames[:count]
265		if result == vk.Success {
266			err = nil
267			break
268		}
269		err = checked("vkCreateDevice", result)
270		if attempt == 0 {
271			return
272		}
273	}
274	r.queuePriority = chosen
275	r.dd, err = vk.LoadDeviceDispatch(r.id, r.device)
276	if err != nil {
277		err = fmt.Errorf("LoadDeviceDispatch: %w", err)
278		return
279	}
280	runtime.KeepAlive(extNames)
281	runtime.KeepAlive(extPtrs)
282	if dmabufSupported {
283		// Clients need the render node to allocate on the right GPU.
284		if sup := r.probeDMABuf(physical); sup.Device != 0 {
285			r.dmabuf = sup
286			r.probeRenderModifiers(physical)
287			r.physical = physical
288		}
289		// VK_KHR_external_semaphore_fd (enabled above): frames export
290		// their fence as a sync file.
291		r.syncFD = r.dd.HasGetSemaphoreFdKHR()
292	}
293	r.dd.GetDeviceQueue(r.device, family, 0, &r.queue)
294	extent := vk.Extent3D{Width: uint32(width), Height: uint32(height), Depth: 1}
295	ii := vk.ImageCreateInfo{SType: vk.StructureTypeImageCreateInfo, ImageType: vk.ImageType2d, Format: vk.FormatB8g8r8a8Unorm, Extent: extent, MipLevels: 1, ArrayLayers: 1, Samples: vk.SampleCount1Bit, Tiling: vk.ImageTilingOptimal, Usage: targetUsage | vk.ImageUsageSampledBit, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined}
296	if err = checked("vkCreateImage", r.dd.CreateImage(r.device, &ii, nil, &r.own.image)); err != nil {
297		return
298	}
299	var req vk.MemoryRequirements
300	r.dd.GetImageMemoryRequirements(r.device, r.own.image, &req)
301	var kind uint32
302	kind, err = r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyDeviceLocalBit)
303	if err != nil {
304		return
305	}
306	alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind}
307	if err = checked("vkAllocateMemory(image)", r.dd.AllocateMemory(r.device, &alloc, nil, &r.own.memory)); err != nil {
308		return
309	}
310	if err = checked("vkBindImageMemory", r.dd.BindImageMemory(r.device, r.own.image, r.own.memory, 0)); err != nil {
311		return
312	}
313	pi := vk.CommandPoolCreateInfo{SType: vk.StructureTypeCommandPoolCreateInfo, Flags: vk.CommandPoolCreateResetCommandBufferBit, QueueFamilyIndex: family}
314	if err = checked("vkCreateCommandPool", r.dd.CreateCommandPool(r.device, &pi, nil, &r.pool)); err != nil {
315		return
316	}
317	if err = r.createSlots(); err != nil {
318		return
319	}
320	if err = r.createView(&r.own); err != nil {
321		return
322	}
323	if err = r.createComposer(); err != nil {
324		return
325	}
326	runtime.KeepAlive(priority)
327	return
328}
329
330func (r *Renderer) findMemoryType(bits uint32, props vk.MemoryPropertyFlags) (uint32, error) {
331	for i := uint32(0); i < r.memory.MemoryTypeCount; i++ {
332		if bits&(uint32(1)<<i) != 0 && r.memory.MemoryTypes[i].PropertyFlags&props == props {
333			return i, nil
334		}
335	}
336	return 0, fmt.Errorf("no Vulkan memory type for bits %#x and properties %#x", bits, props)
337}
338
339// CopiedBytes reports cumulative wl_shm bytes copied into GPU storage.
340// It is diagnostic only and must be read by the renderer's owner goroutine.
341func (r *Renderer) CopiedBytes() int { return r.copied }
342
343// TakeRedrawn returns the target pixels drawn since the last call and
344// resets the count.
345func (r *Renderer) TakeRedrawn() int {
346	n := r.redrawn
347	r.redrawn = 0
348	return n
349}
350
351// SetLogger sets the logger (with its component field) for render target
352// events. Not on the port; the zero logger discards.
353func (r *Renderer) SetLogger(l zerowrap.Logger) { r.log = l }
354
355// QueuePriority is the global priority the queue got: realtime, high or
356// default.
357func (r *Renderer) QueuePriority() string { return r.queuePriority }
358
359// Pixels reads the last frame back through a capture slot, waiting for
360// the GPU on the calling goroutine (slot fence, no sync file needed):
361// headless screenshots and tests only. Nil when no slot is free (both
362// leased to captures) or the copy failed: never a silent black image.
363func (r *Renderer) Pixels() *image.RGBA {
364	cf, err := r.debugCapture()
365	if err != nil {
366		return nil
367	}
368	defer r.EndCapture(cf)
369	out := image.NewRGBA(image.Rect(0, 0, r.width, r.height))
370	if err := cf.Read(image.Rect(0, 0, r.width, r.height), out.Pix, out.Stride); err != nil {
371		return nil
372	}
373	for i := 0; i < len(out.Pix); i += 4 {
374		out.Pix[i], out.Pix[i+2] = out.Pix[i+2], out.Pix[i]
375	}
376	return out
377}
378
379// SetHDRReadback makes the exported HDR targets transfer sources so that
380// HDRPixels can read them (they are not in production: the DRM path never
381// sets it). Call before ExportTargets. Not on the port.
382func (r *Renderer) SetHDRReadback(on bool) { r.hdrReadback = on }
383
384// HDRPixels reads the last HDR frame's XRGB2101010 target back, each 10-bit
385// code scaled to 16 bits (c<<6 | c>>4), alpha opaque. It waits for the GPU:
386// debug only. Nil when the output is SDR, the target is not readable (no
387// SetHDRReadback), no HDR frame was rendered, every slot is leased or the
388// copy failed.
389func (r *Renderer) HDRPixels() *image.RGBA64 {
390	var out *image.RGBA64
391	err := r.readHDRTarget(func(words []uint32) {
392		out = image.NewRGBA64(image.Rect(0, 0, r.width, r.height))
393		for i, w := range words {
394			p := out.Pix[i*8 : i*8+8]
395			for ch, c := range [3]uint32{w >> 20 & 1023, w >> 10 & 1023, w & 1023} {
396				v := uint16(c<<6 | c>>4)
397				p[ch*2], p[ch*2+1] = byte(v>>8), byte(v)
398			}
399			p[6], p[7] = 0xff, 0xff
400		}
401	})
402	if err != nil {
403		return nil
404	}
405	return out
406}
407
408// Missing from the bindings: VK_QUEUE_FAMILY_IGNORED, VK_WHOLE_SIZE.
409const (
410	queueFamilyIgnored = ^uint32(0)
411	wholeSize          = ^vk.DeviceSize(0)
412)
413
414func (r *Renderer) Close() {
415	if r == nil {
416		return
417	}
418	if r.dd != nil {
419		d := r.dd
420		if r.device != 0 {
421			_ = checked("vkDeviceWaitIdle", d.DeviceWaitIdle(r.device))
422			r.idle()
423		}
424		r.dropTargets()
425		r.destroyHDR()
426		r.dropCursors()
427		for id, im := range r.imports {
428			r.release(im)
429			delete(r.imports, id)
430		}
431		r.frame += importTTL + 1
432		r.dropPools()
433		r.dropShm()
434		r.destroySlots()
435		r.destroyCaptures()
436		if r.pool != 0 {
437			d.DestroyCommandPool(r.device, r.pool, nil)
438			r.pool = 0
439		}
440		r.destroyComposer()
441		r.freeTarget(&r.hdrOwn)
442		r.freeTarget(&r.own)
443		d.DestroyDevice(r.device, nil)
444		r.device = 0
445	} else if r.device != 0 && r.id != nil {
446		if vk.VkDestroyDevice != nil {
447			vk.VkDestroyDevice(r.device, nil)
448		}
449		r.device = 0
450	}
451	if r.instance != 0 {
452		if r.id != nil {
453			r.id.DestroyInstance(r.instance, nil)
454		} else if vk.VkDestroyInstance != nil {
455			vk.VkDestroyInstance(r.instance, nil)
456		}
457		r.instance = 0
458	}
459}

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