v0.1.0 d9218d6

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
14	vk "github.com/bnema/purego-vulkan/vulkan"
15)
16
17// Renderer is owned by one goroutine; its methods must not be called concurrently.
18type Renderer struct {
19	width, height int
20	instance      vk.Instance
21	id            *vk.InstanceDispatch
22	device        vk.Device
23	dd            *vk.DeviceDispatch
24	queue         vk.Queue
25	// own is the target until ExportTargets; targets are exported images
26	// (targets.go), current the one the next frame draws into.
27	own        target
28	targets    []*target
29	current    int
30	renderMods []uint64
31	// last is the target of the last frame; readback copies it into
32	// buffer only when Pixels asks (stale until then).
33	last         *target
34	readBack     bool
35	buffer       vk.Buffer
36	bufferMemory vk.DeviceMemory
37	mapped       unsafe.Pointer
38	pool         vk.CommandPool
39	memory       vk.PhysicalDeviceMemoryProperties
40	family       uint32
41	// Frames in flight (frame.go): frame is the last frame recorded,
42	// submitted the last submitted, completed the last known finished.
43	slots                       [frameSlots]frameSlot
44	frame, submitted, completed uint64
45	deferred                    []deferredFree
46	// syncFD: frames export a sync file (else Render waits for them).
47	syncFD bool
48	// dmabuf is what clients may send; imports are their buffers by ID.
49	dmabuf  ports.DMABufSupport
50	imports map[uint64]*imported
51	// pools are client wl_shm pools mapped by pool ID.
52	pools map[uint64]*mapping
53	// retired are pool mappings replaced this frame, unmapped once the
54	// frame's copies are done.
55	retired [][]byte
56	// shm are the GPU copies of wl_shm surfaces (shm.go); copied counts
57	// the bytes copied into them, for tests.
58	shm    map[shmKey]*shmSurface
59	copied int
60	// redrawn counts the target pixels drawn, for tests.
61	redrawn int
62	// cursors are the exported cursor images (CursorBuffers).
63	cursors [2]*cursorImage
64	// compose draws every frame (compose.go); maxRange bounds one
65	// storage buffer binding.
66	compose  composer
67	maxRange int
68}
69
70func checked(name string, result vk.Result) error {
71	if err := vk.Check(result); err != nil {
72		return fmt.Errorf("%s: %w", name, err)
73	}
74	return nil
75}
76
77func New(width, height int) (r *Renderer, err error) {
78	if width <= 0 || height <= 0 || width > math.MaxUint32 || height > math.MaxUint32 || uint64(width)*uint64(height) > uint64(math.MaxInt/4) {
79		return nil, fmt.Errorf("invalid renderer dimensions %d x %d", width, height)
80	}
81	r = &Renderer{width: width, height: height, imports: map[uint64]*imported{}, pools: map[uint64]*mapping{}, shm: map[shmKey]*shmSurface{}}
82	defer func() {
83		if err != nil {
84			r.Close()
85			r = nil
86		}
87	}()
88	if err = vk.Init(); err != nil {
89		return nil, fmt.Errorf("vk.Init: %w", err)
90	}
91	app := vk.ApplicationInfo{SType: vk.StructureTypeApplicationInfo, ApiVersion: vk.MakeVersion(1, 3, 0)}
92	info := vk.InstanceCreateInfo{SType: vk.StructureTypeInstanceCreateInfo, ApplicationInfo: &app}
93	if err = checked("vkCreateInstance", vk.Global().CreateInstance(&info, nil, &r.instance)); err != nil {
94		return
95	}
96	r.id, err = vk.LoadInstanceDispatch(r.instance)
97	if err != nil {
98		err = fmt.Errorf("LoadInstanceDispatch: %w", err)
99		return
100	}
101	var count uint32
102	if err = checked("vkEnumeratePhysicalDevices(count)", r.id.EnumeratePhysicalDevices(r.instance, &count, nil)); err != nil {
103		return
104	}
105	if count == 0 {
106		err = fmt.Errorf("no Vulkan 1.3 device")
107		return
108	}
109	devices := make([]vk.PhysicalDevice, count)
110	if err = checked("vkEnumeratePhysicalDevices", r.id.EnumeratePhysicalDevices(r.instance, &count, &devices[0])); err != nil {
111		return
112	}
113	index := -1
114	if value, ok := os.LookupEnv("NEFERWL_VK_DEVICE"); ok {
115		index, err = strconv.Atoi(value)
116		if err != nil || index < 0 || index >= int(count) {
117			err = fmt.Errorf("invalid NEFERWL_VK_DEVICE index %q", value)
118			return
119		}
120	}
121	var physical vk.PhysicalDevice
122	var family uint32
123	for i, device := range devices[:count] {
124		if index >= 0 && i != index {
125			continue
126		}
127		var properties vk.PhysicalDeviceProperties
128		r.id.GetPhysicalDeviceProperties(device, &properties)
129		if properties.ApiVersion < vk.MakeVersion(1, 3, 0) {
130			continue
131		}
132		var n uint32
133		r.id.GetPhysicalDeviceQueueFamilyProperties(device, &n, nil)
134		if n == 0 {
135			continue
136		}
137		families := make([]vk.QueueFamilyProperties, n)
138		r.id.GetPhysicalDeviceQueueFamilyProperties(device, &n, &families[0])
139		for j, f := range families[:n] {
140			if f.QueueCount > 0 && f.QueueFlags&vk.QueueGraphicsBit != 0 {
141				physical = device
142				family = uint32(j)
143				break
144			}
145		}
146		if physical != 0 {
147			break
148		}
149	}
150	if physical == 0 {
151		err = fmt.Errorf("no Vulkan 1.3 device with a graphics queue")
152		return
153	}
154	r.id.GetPhysicalDeviceMemoryProperties(physical, &r.memory)
155	var properties vk.PhysicalDeviceProperties
156	r.id.GetPhysicalDeviceProperties(physical, &properties)
157	r.maxRange = int(properties.Limits.MaxStorageBufferRange)
158	r.family = family
159	priority := float32(1)
160	qi := vk.DeviceQueueCreateInfo{SType: vk.StructureTypeDeviceQueueCreateInfo, QueueFamilyIndex: family, QueueCount: 1, QueuePriorities: &priority}
161	dynamicRendering := vk.PhysicalDeviceDynamicRenderingFeatures{SType: vk.StructureTypePhysicalDeviceDynamicRenderingFeatures, DynamicRendering: 1}
162	di := vk.DeviceCreateInfo{SType: vk.StructureTypeDeviceCreateInfo, Next: unsafe.Pointer(&dynamicRendering), QueueCreateInfoCount: 1, QueueCreateInfos: &qi}
163	// dmabuf import needs every extension; without them clients use wl_shm.
164	var extNames [][]byte
165	var extPtrs []*byte
166	if r.hasExtensions(physical) {
167		for _, e := range deviceExtensions {
168			extNames = append(extNames, append([]byte(e), 0))
169			extPtrs = append(extPtrs, &extNames[len(extNames)-1][0])
170		}
171		di.EnabledExtensionCount = uint32(len(extPtrs))
172		di.PpEnabledExtensionNames = &extPtrs[0]
173	}
174	if err = checked("vkCreateDevice", r.id.CreateDevice(physical, &di, nil, &r.device)); err != nil {
175		return
176	}
177	r.dd, err = vk.LoadDeviceDispatch(r.id, r.device)
178	if err != nil {
179		err = fmt.Errorf("LoadDeviceDispatch: %w", err)
180		return
181	}
182	runtime.KeepAlive(extNames)
183	runtime.KeepAlive(extPtrs)
184	if len(extPtrs) > 0 {
185		// Clients need the render node to allocate on the right GPU.
186		if sup := r.probeDMABuf(physical); sup.Device != 0 {
187			r.dmabuf = sup
188			r.probeRenderModifiers(physical)
189		}
190		// VK_KHR_external_semaphore_fd (enabled above): frames export
191		// their fence as a sync file.
192		r.syncFD = r.dd.GetSemaphoreFdKHR != nil
193	}
194	r.dd.GetDeviceQueue(r.device, family, 0, &r.queue)
195	extent := vk.Extent3D{Width: uint32(width), Height: uint32(height), Depth: 1}
196	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, SharingMode: vk.SharingModeExclusive, InitialLayout: vk.ImageLayoutUndefined}
197	if err = checked("vkCreateImage", r.dd.CreateImage(r.device, &ii, nil, &r.own.image)); err != nil {
198		return
199	}
200	var req vk.MemoryRequirements
201	r.dd.GetImageMemoryRequirements(r.device, r.own.image, &req)
202	var kind uint32
203	kind, err = r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyDeviceLocalBit)
204	if err != nil {
205		return
206	}
207	alloc := vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind}
208	if err = checked("vkAllocateMemory(image)", r.dd.AllocateMemory(r.device, &alloc, nil, &r.own.memory)); err != nil {
209		return
210	}
211	if err = checked("vkBindImageMemory", r.dd.BindImageMemory(r.device, r.own.image, r.own.memory, 0)); err != nil {
212		return
213	}
214	size := vk.DeviceSize(width) * vk.DeviceSize(height) * 4
215	bi := vk.BufferCreateInfo{SType: vk.StructureTypeBufferCreateInfo, Size: size, Usage: vk.BufferUsageTransferDstBit, SharingMode: vk.SharingModeExclusive}
216	if err = checked("vkCreateBuffer", r.dd.CreateBuffer(r.device, &bi, nil, &r.buffer)); err != nil {
217		return
218	}
219	r.dd.GetBufferMemoryRequirements(r.device, r.buffer, &req)
220	// Readback (screenshots) reads this buffer: uncached memory is ~100x slower.
221	kind, err = r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyHostVisibleBit|vk.MemoryPropertyHostCoherentBit|vk.MemoryPropertyHostCachedBit)
222	if err != nil {
223		kind, err = r.findMemoryType(req.MemoryTypeBits, vk.MemoryPropertyHostVisibleBit|vk.MemoryPropertyHostCoherentBit)
224	}
225	if err != nil {
226		return
227	}
228	alloc = vk.MemoryAllocateInfo{SType: vk.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: kind}
229	if err = checked("vkAllocateMemory(buffer)", r.dd.AllocateMemory(r.device, &alloc, nil, &r.bufferMemory)); err != nil {
230		return
231	}
232	if err = checked("vkBindBufferMemory", r.dd.BindBufferMemory(r.device, r.buffer, r.bufferMemory, 0)); err != nil {
233		return
234	}
235	if err = checked("vkMapMemory", r.dd.MapMemory(r.device, r.bufferMemory, 0, size, 0, &r.mapped)); err != nil {
236		return
237	}
238	pi := vk.CommandPoolCreateInfo{SType: vk.StructureTypeCommandPoolCreateInfo, Flags: vk.CommandPoolCreateResetCommandBufferBit, QueueFamilyIndex: family}
239	if err = checked("vkCreateCommandPool", r.dd.CreateCommandPool(r.device, &pi, nil, &r.pool)); err != nil {
240		return
241	}
242	if err = r.createSlots(); err != nil {
243		return
244	}
245	if err = r.createView(&r.own); err != nil {
246		return
247	}
248	if err = r.createComposer(); err != nil {
249		return
250	}
251	runtime.KeepAlive(priority)
252	return
253}
254
255func (r *Renderer) findMemoryType(bits uint32, props vk.MemoryPropertyFlags) (uint32, error) {
256	for i := uint32(0); i < r.memory.MemoryTypeCount; i++ {
257		if bits&(uint32(1)<<i) != 0 && r.memory.MemoryTypes[i].PropertyFlags&props == props {
258			return i, nil
259		}
260	}
261	return 0, fmt.Errorf("no Vulkan memory type for bits %#x and properties %#x", bits, props)
262}
263
264func (r *Renderer) Pixels() *image.RGBA {
265	out := image.NewRGBA(image.Rect(0, 0, r.width, r.height))
266	if r.mapped == nil || r.readback() != nil {
267		return out
268	}
269	src := unsafe.Slice((*byte)(r.mapped), len(out.Pix))
270	for i := 0; i < len(src); i += 4 {
271		// The output is opaque: x-format client buffers leave alpha undefined.
272		out.Pix[i], out.Pix[i+1], out.Pix[i+2], out.Pix[i+3] = src[i+2], src[i+1], src[i], 255
273	}
274	return out
275}
276
277// Missing from the bindings: VK_QUEUE_FAMILY_IGNORED, VK_WHOLE_SIZE.
278const (
279	queueFamilyIgnored = ^uint32(0)
280	wholeSize          = ^vk.DeviceSize(0)
281)
282
283// readback copies the last frame into the host buffer, once per frame
284// (screenshots only).
285func (r *Renderer) readback() error {
286	if r.readBack || r.last == nil || r.last.image == 0 {
287		return nil
288	}
289	// The copy reads the frame: wait for it, and keep the ring ordered.
290	if err := r.waitFrame(r.submitted); err != nil {
291		return err
292	}
293	t := r.last
294	err := r.oneShot(func(cmd vk.CommandBuffer) {
295		d := r.dd
296		b := vk.ImageMemoryBarrier{SType: vk.StructureTypeImageMemoryBarrier, DstAccessMask: vk.AccessTransferReadBit, OldLayout: t.layout, NewLayout: vk.ImageLayoutTransferSrcOptimal, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Image: t.image, SubresourceRange: colorRange}
297		if t.exported {
298			b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = vk.QueueFamilyForeignEXT, r.family
299		}
300		d.CmdPipelineBarrier(cmd, vk.PipelineStageTopOfPipeBit, vk.PipelineStageTransferBit, 0, 0, nil, 0, nil, 1, &b)
301		region := vk.BufferImageCopy{ImageSubresource: vk.ImageSubresourceLayers{AspectMask: vk.ImageAspectColorBit, LayerCount: 1}, ImageExtent: vk.Extent3D{Width: uint32(r.width), Height: uint32(r.height), Depth: 1}}
302		d.CmdCopyImageToBuffer(cmd, t.image, vk.ImageLayoutTransferSrcOptimal, r.buffer, 1, &region)
303		// Make the copy visible to host reads of the mapped buffer.
304		hb := vk.BufferMemoryBarrier{SType: vk.StructureTypeBufferMemoryBarrier, SrcAccessMask: vk.AccessTransferWriteBit, DstAccessMask: vk.AccessHostReadBit, SrcQueueFamilyIndex: queueFamilyIgnored, DstQueueFamilyIndex: queueFamilyIgnored, Buffer: r.buffer, Size: wholeSize}
305		d.CmdPipelineBarrier(cmd, vk.PipelineStageTransferBit, vk.PipelineStageHostBit, 0, 0, nil, 1, &hb, 0, nil)
306		if t.exported {
307			b.OldLayout, b.NewLayout = vk.ImageLayoutTransferSrcOptimal, vk.ImageLayoutGeneral
308			b.SrcAccessMask, b.DstAccessMask = vk.AccessTransferReadBit, 0
309			b.SrcQueueFamilyIndex, b.DstQueueFamilyIndex = r.family, vk.QueueFamilyForeignEXT
310			d.CmdPipelineBarrier(cmd, vk.PipelineStageTransferBit, vk.PipelineStageBottomOfPipeBit, 0, 0, nil, 0, nil, 1, &b)
311		}
312	})
313	if err != nil {
314		return err
315	}
316	if !t.exported {
317		t.layout = vk.ImageLayoutTransferSrcOptimal
318	}
319	r.readBack = true
320	return nil
321}
322
323func (r *Renderer) Close() {
324	if r == nil {
325		return
326	}
327	if r.dd != nil {
328		d := r.dd
329		if r.device != 0 {
330			_ = checked("vkDeviceWaitIdle", d.DeviceWaitIdle(r.device))
331			r.idle()
332		}
333		r.dropTargets()
334		r.dropCursors()
335		for id, im := range r.imports {
336			r.release(im)
337			delete(r.imports, id)
338		}
339		r.frame += importTTL + 1
340		r.dropPools()
341		r.dropShm()
342		r.destroySlots()
343		if r.pool != 0 {
344			d.DestroyCommandPool(r.device, r.pool, nil)
345			r.pool = 0
346		}
347		if r.mapped != nil {
348			d.UnmapMemory(r.device, r.bufferMemory)
349			r.mapped = nil
350		}
351		if r.buffer != 0 {
352			d.DestroyBuffer(r.device, r.buffer, nil)
353			r.buffer = 0
354		}
355		if r.bufferMemory != 0 {
356			d.FreeMemory(r.device, r.bufferMemory, nil)
357			r.bufferMemory = 0
358		}
359		r.destroyComposer()
360		r.freeTarget(&r.own)
361		d.DestroyDevice(r.device, nil)
362		r.device = 0
363	} else if r.device != 0 && r.id != nil {
364		if vk.VkDestroyDevice != nil {
365			vk.VkDestroyDevice(r.device, nil)
366		}
367		r.device = 0
368	}
369	if r.instance != 0 {
370		if r.id != nil {
371			r.id.DestroyInstance(r.instance, nil)
372		} else if vk.VkDestroyInstance != nil {
373			vk.VkDestroyInstance(r.instance, nil)
374		}
375		r.instance = 0
376	}
377}

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