v0.3.0 da0887c

renderer.go RawHistoryFolder

1package gpu
2
3import (
4	"encoding/binary"
5	"errors"
6	"fmt"
7	"math"
8	"unsafe"
9
10	"github.com/bnema/purego-vulkan/vulkan"
11	"golang.org/x/sys/unix"
12
13	"github.com/bnema/neferbar/internal/glyph"
14	"github.com/bnema/neferbar/internal/gpu/shaders"
15	"github.com/bnema/neferbar/internal/syncobj"
16)
17
18const (
19	atlasSize = 2048
20	// SlotCount is the number of DMA-BUF images: one on screen, one being
21	// shown or queued, one being drawn.
22	SlotCount = 3
23	// maxUploads bounds the glyphs rasterized by one Draw.
24	maxUploads = 256
25)
26
27// Cell is one grid cell as the renderer consumes it.
28type Cell struct {
29	Rune   rune
30	FG, BG [3]uint8
31	Style  glyph.Style
32}
33
34// instance is the per-cell vertex data (64 bytes, four vec4).
35type instance struct {
36	Rect [4]float32 // x, y, w, h in pixels
37	UV   [4]float32 // atlas u0, v0, u1, v1
38	FG   [4]float32
39	BG   [4]float32
40}
41
42type glyphKey struct {
43	r     rune
44	style glyph.Style
45}
46
47type slotState uint8
48
49const (
50	slotFree slotState = iota
51	slotShown
52)
53
54// slot is one DMA-BUF image with everything needed to draw and present it.
55type slot struct {
56	img   *Image
57	pool  vulkan.CommandPool
58	cmd   vulkan.CommandBuffer
59	fence vulkan.Fence
60
61	instBuf, stageBuf vulkan.Buffer
62	instMem, stageMem vulkan.DeviceMemory
63	inst              []instance // mapped
64	stage             []byte     // mapped
65
66	releaseHandle uint32
67	releaseFD     int // syncobj fd, exported once
68	eventFD       int // readable when releasePoint signals
69	releasePoint  uint64
70	presented     bool
71	state         slotState
72
73	// Scratch argument structs: the bindings heap-allocate pointer arguments,
74	// so a call built from locals allocates on every frame.
75	begin    vulkan.CommandBufferBeginInfo
76	barrier  [1]vulkan.ImageMemoryBarrier2
77	dep      vulkan.DependencyInfo
78	color    vulkan.RenderingAttachmentInfo
79	render   vulkan.RenderingInfo
80	cmdInfo  vulkan.CommandBufferSubmitInfo
81	submit   vulkan.SubmitInfo2
82	offset   vulkan.DeviceSize
83	push     [2]float32
84	regions  []vulkan.BufferImageCopy
85	sets     [1]vulkan.DescriptorSet
86	atlasSub vulkan.ImageSubresourceRange
87}
88
89// Frame describes one drawn slot for the Wayland layer. Fields stay valid
90// until the next Draw.
91type Frame struct {
92	Slot            int
93	NewBuffer       bool // import Image as a wl_buffer
94	Image           *Image
95	AcquirePoint    uint64
96	ReleasePoint    uint64
97	ReleaseEventFD  int // readable when ReleasePoint signals; stable per slot
98	ReleaseTimeline int // syncobj fd of the slot's release timeline; valid with NewBuffer
99}
100
101// Renderer draws a row of cells into a pool of DMA-BUF images.
102type Renderer struct {
103	dev  *Device
104	node *syncobj.Node
105	face *glyph.Face
106
107	W, H          int32
108	cols, maxCols int
109	yOff          float32
110
111	slots [SlotCount]slot
112
113	acquireHandle uint32
114	acquireFD     int
115	acquirePoint  uint64
116
117	// atlas
118	atlasImg    vulkan.Image
119	atlasMem    vulkan.DeviceMemory
120	atlasView   vulkan.ImageView
121	sampler     vulkan.Sampler
122	setLayout   vulkan.DescriptorSetLayout
123	descPool    vulkan.DescriptorPool
124	descSet     vulkan.DescriptorSet
125	pipeLayout  vulkan.PipelineLayout
126	pipeline    vulkan.Pipeline
127	atlasCols   int
128	atlasRows   int
129	glyphs      map[glyphKey]uint32
130	nextGlyph   uint32
131	atlasInited bool
132
133	uploadKeys []glyphKey // glyphs added during the current Draw
134	uploadIdx  []uint32
135}
136
137// NewRenderer builds the pipeline, the atlas and the image pool for a w x h
138// target. node must be the render node of dev.
139func NewRenderer(dev *Device, node *syncobj.Node, mod Modifier, face *glyph.Face, w, h int32) (r *Renderer, err error) {
140	if face.CellH > int(h) {
141		return nil, fmt.Errorf("gpu: cell height %d exceeds bar height %d", face.CellH, h)
142	}
143	r = &Renderer{dev: dev, node: node, face: face, W: w, H: h, acquireFD: -1,
144		glyphs: make(map[glyphKey]uint32, 256), yOff: float32((int(h) - face.CellH) / 2)}
145	r.cols = (int(w) + face.CellW - 1) / face.CellW
146	r.maxCols = r.cols
147	for i := range r.slots {
148		r.slots[i].releaseFD, r.slots[i].eventFD = -1, -1
149	}
150	defer func() {
151		if err != nil {
152			r.Close()
153			r = nil
154		}
155	}()
156	if err = r.newAtlas(); err != nil {
157		return nil, err
158	}
159	if err = r.newPipeline(); err != nil {
160		return nil, err
161	}
162	if r.acquireHandle, err = node.Create(); err != nil {
163		return nil, err
164	}
165	if r.acquireFD, err = node.Export(r.acquireHandle); err != nil {
166		return nil, err
167	}
168	for i := range r.slots {
169		if err = r.newSlot(i, mod); err != nil {
170			return nil, err
171		}
172	}
173	// Glyph 0 is the blank cell. Printable ASCII is assigned now and uploaded
174	// with the first frame.
175	r.glyphs[glyphKey{' ', 0}] = 0
176	r.nextGlyph = 1
177	for c := rune(0x21); c < 0x7f; c++ {
178		r.glyphs[glyphKey{c, 0}] = r.nextGlyph
179		r.nextGlyph++
180	}
181	r.uploadKeys = make([]glyphKey, 0, maxUploads)
182	r.uploadIdx = make([]uint32, 0, maxUploads)
183	return r, nil
184}
185
186func (r *Renderer) buffer(size vulkan.DeviceSize, usage vulkan.BufferUsageFlags) (vulkan.Buffer, vulkan.DeviceMemory, unsafe.Pointer, error) {
187	d := r.dev
188	var buf vulkan.Buffer
189	ci := vulkan.BufferCreateInfo{SType: vulkan.StructureTypeBufferCreateInfo, Size: size, Usage: usage, SharingMode: vulkan.SharingModeExclusive}
190	if err := vulkan.Check(d.disp.CreateBuffer(d.logical, &ci, nil, &buf)); err != nil {
191		return 0, 0, nil, err
192	}
193	var req vulkan.MemoryRequirements
194	d.disp.GetBufferMemoryRequirements(d.logical, buf, &req)
195	idx, err := d.memoryType(req.MemoryTypeBits, vulkan.MemoryPropertyHostVisibleBit|vulkan.MemoryPropertyHostCoherentBit)
196	if err != nil {
197		d.disp.DestroyBuffer(d.logical, buf, nil)
198		return 0, 0, nil, err
199	}
200	var mem vulkan.DeviceMemory
201	ai := vulkan.MemoryAllocateInfo{SType: vulkan.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: idx}
202	if err = vulkan.Check(d.disp.AllocateMemory(d.logical, &ai, nil, &mem)); err != nil {
203		d.disp.DestroyBuffer(d.logical, buf, nil)
204		return 0, 0, nil, err
205	}
206	fail := func(err error) (vulkan.Buffer, vulkan.DeviceMemory, unsafe.Pointer, error) {
207		d.disp.FreeMemory(d.logical, mem, nil)
208		d.disp.DestroyBuffer(d.logical, buf, nil)
209		return 0, 0, nil, err
210	}
211	if err = vulkan.Check(d.disp.BindBufferMemory(d.logical, buf, mem, 0)); err != nil {
212		return fail(err)
213	}
214	var ptr unsafe.Pointer
215	if err = vulkan.Check(d.disp.MapMemory(d.logical, mem, 0, size, 0, &ptr)); err != nil {
216		return fail(err)
217	}
218	return buf, mem, ptr, nil
219}
220
221func (r *Renderer) newSlot(i int, mod Modifier) (err error) {
222	d := r.dev
223	s := &r.slots[i]
224	if s.img, err = d.NewImage(r.W, r.H, mod); err != nil {
225		return err
226	}
227	pi := vulkan.CommandPoolCreateInfo{SType: vulkan.StructureTypeCommandPoolCreateInfo, QueueFamilyIndex: d.family, Flags: vulkan.CommandPoolCreateResetCommandBufferBit}
228	if err = vulkan.Check(d.disp.CreateCommandPool(d.logical, &pi, nil, &s.pool)); err != nil {
229		return err
230	}
231	ai := vulkan.CommandBufferAllocateInfo{SType: vulkan.StructureTypeCommandBufferAllocateInfo, CommandPool: s.pool, Level: vulkan.CommandBufferLevelPrimary, CommandBufferCount: 1}
232	if err = vulkan.Check(d.disp.AllocateCommandBuffers(d.logical, &ai, &s.cmd)); err != nil {
233		return err
234	}
235	fi := vulkan.FenceCreateInfo{SType: vulkan.StructureTypeFenceCreateInfo}
236	if err = vulkan.Check(d.disp.CreateFence(d.logical, &fi, nil, &s.fence)); err != nil {
237		return err
238	}
239	instBytes := vulkan.DeviceSize(r.maxCols) * vulkan.DeviceSize(unsafe.Sizeof(instance{}))
240	var p unsafe.Pointer
241	if s.instBuf, s.instMem, p, err = r.buffer(instBytes, vulkan.BufferUsageVertexBufferBit); err != nil {
242		return err
243	}
244	s.inst = unsafe.Slice((*instance)(p), r.maxCols)
245	stageBytes := maxUploads * r.face.CellW * r.face.CellH
246	if s.stageBuf, s.stageMem, p, err = r.buffer(vulkan.DeviceSize(stageBytes), vulkan.BufferUsageTransferSrcBit); err != nil {
247		return err
248	}
249	s.stage = unsafe.Slice((*byte)(p), stageBytes)
250	s.regions = make([]vulkan.BufferImageCopy, 0, maxUploads)
251	s.atlasSub = vulkan.ImageSubresourceRange{AspectMask: vulkan.ImageAspectColorBit, LevelCount: 1, LayerCount: 1}
252	if s.releaseHandle, err = r.node.Create(); err != nil {
253		return err
254	}
255	if s.releaseFD, err = r.node.Export(s.releaseHandle); err != nil {
256		return err
257	}
258	if s.eventFD, err = unix.Eventfd(0, unix.EFD_CLOEXEC|unix.EFD_NONBLOCK); err != nil {
259		return err
260	}
261	return nil
262}
263
264func (r *Renderer) newAtlas() error {
265	d := r.dev
266	r.atlasCols = atlasSize / r.face.CellW
267	r.atlasRows = atlasSize / r.face.CellH
268	if r.atlasCols < 1 || r.atlasRows < 1 {
269		return fmt.Errorf("gpu: cell %dx%d does not fit the atlas", r.face.CellW, r.face.CellH)
270	}
271	ci := vulkan.ImageCreateInfo{
272		SType: vulkan.StructureTypeImageCreateInfo, ImageType: vulkan.ImageType2d, Format: vulkan.FormatR8Unorm,
273		Extent:    vulkan.Extent3D{Width: atlasSize, Height: atlasSize, Depth: 1},
274		MipLevels: 1, ArrayLayers: 1, Samples: vulkan.SampleCount1Bit, Tiling: vulkan.ImageTilingOptimal,
275		Usage:       vulkan.ImageUsageSampledBit | vulkan.ImageUsageTransferDstBit,
276		SharingMode: vulkan.SharingModeExclusive, InitialLayout: vulkan.ImageLayoutUndefined,
277	}
278	if err := vulkan.Check(d.disp.CreateImage(d.logical, &ci, nil, &r.atlasImg)); err != nil {
279		return fmt.Errorf("gpu: create atlas: %w", err)
280	}
281	var req vulkan.MemoryRequirements
282	d.disp.GetImageMemoryRequirements(d.logical, r.atlasImg, &req)
283	idx, err := d.memoryType(req.MemoryTypeBits, vulkan.MemoryPropertyDeviceLocalBit)
284	if err != nil {
285		return err
286	}
287	ai := vulkan.MemoryAllocateInfo{SType: vulkan.StructureTypeMemoryAllocateInfo, AllocationSize: req.Size, MemoryTypeIndex: idx}
288	if err = vulkan.Check(d.disp.AllocateMemory(d.logical, &ai, nil, &r.atlasMem)); err != nil {
289		return err
290	}
291	if err = vulkan.Check(d.disp.BindImageMemory(d.logical, r.atlasImg, r.atlasMem, 0)); err != nil {
292		return err
293	}
294	vi := vulkan.ImageViewCreateInfo{
295		SType: vulkan.StructureTypeImageViewCreateInfo, Image: r.atlasImg, ViewType: vulkan.ImageViewType2d, Format: vulkan.FormatR8Unorm,
296		SubresourceRange: vulkan.ImageSubresourceRange{AspectMask: vulkan.ImageAspectColorBit, LevelCount: 1, LayerCount: 1},
297	}
298	if err = vulkan.Check(d.disp.CreateImageView(d.logical, &vi, nil, &r.atlasView)); err != nil {
299		return err
300	}
301	si := vulkan.SamplerCreateInfo{
302		SType: vulkan.StructureTypeSamplerCreateInfo, MagFilter: vulkan.FilterNearest, MinFilter: vulkan.FilterNearest,
303		MipmapMode:   vulkan.SamplerMipmapModeNearest,
304		AddressModeU: vulkan.SamplerAddressModeClampToEdge, AddressModeV: vulkan.SamplerAddressModeClampToEdge, AddressModeW: vulkan.SamplerAddressModeClampToEdge,
305	}
306	if err = vulkan.Check(d.disp.CreateSampler(d.logical, &si, nil, &r.sampler)); err != nil {
307		return err
308	}
309	b := vulkan.DescriptorSetLayoutBinding{DescriptorType: vulkan.DescriptorTypeCombinedImageSampler, DescriptorCount: 1, StageFlags: vulkan.ShaderStageFragmentBit}
310	li := vulkan.DescriptorSetLayoutCreateInfo{SType: vulkan.StructureTypeDescriptorSetLayoutCreateInfo, BindingCount: 1, Bindings: &b}
311	if err = vulkan.Check(d.disp.CreateDescriptorSetLayout(d.logical, &li, nil, &r.setLayout)); err != nil {
312		return err
313	}
314	ps := vulkan.DescriptorPoolSize{Type: vulkan.DescriptorTypeCombinedImageSampler, DescriptorCount: 1}
315	pi := vulkan.DescriptorPoolCreateInfo{SType: vulkan.StructureTypeDescriptorPoolCreateInfo, MaxSets: 1, PoolSizeCount: 1, PoolSizes: &ps}
316	if err = vulkan.Check(d.disp.CreateDescriptorPool(d.logical, &pi, nil, &r.descPool)); err != nil {
317		return err
318	}
319	ds := vulkan.DescriptorSetAllocateInfo{SType: vulkan.StructureTypeDescriptorSetAllocateInfo, DescriptorPool: r.descPool, DescriptorSetCount: 1, SetLayouts: &r.setLayout}
320	if err = vulkan.Check(d.disp.AllocateDescriptorSets(d.logical, &ds, &r.descSet)); err != nil {
321		return err
322	}
323	info := vulkan.DescriptorImageInfo{Sampler: r.sampler, ImageView: r.atlasView, ImageLayout: vulkan.ImageLayoutShaderReadOnlyOptimal}
324	w := vulkan.WriteDescriptorSet{SType: vulkan.StructureTypeWriteDescriptorSet, DstSet: r.descSet, DescriptorCount: 1, DescriptorType: vulkan.DescriptorTypeCombinedImageSampler, ImageInfo: &info}
325	d.disp.UpdateDescriptorSets(d.logical, 1, &w, 0, nil)
326	return nil
327}
328
329func (r *Renderer) shader(code []byte) (vulkan.ShaderModule, error) {
330	if len(code) < 20 || len(code)%4 != 0 || binary.LittleEndian.Uint32(code) != 0x07230203 {
331		return 0, errors.New("gpu: invalid embedded SPIR-V")
332	}
333	words := make([]uint32, len(code)/4)
334	for i := range words {
335		words[i] = le32(code[i*4:])
336	}
337	ci := vulkan.ShaderModuleCreateInfo{SType: vulkan.StructureTypeShaderModuleCreateInfo, CodeSize: uintptr(len(code)), Code: &words[0]}
338	var m vulkan.ShaderModule
339	if err := vulkan.Check(r.dev.disp.CreateShaderModule(r.dev.logical, &ci, nil, &m)); err != nil {
340		return 0, err
341	}
342	return m, nil
343}
344
345func (r *Renderer) newPipeline() error {
346	d := r.dev
347	pc := vulkan.PushConstantRange{StageFlags: vulkan.ShaderStageVertexBit, Size: 8}
348	li := vulkan.PipelineLayoutCreateInfo{SType: vulkan.StructureTypePipelineLayoutCreateInfo, SetLayoutCount: 1, SetLayouts: &r.setLayout, PushConstantRangeCount: 1, PushConstantRanges: &pc}
349	if err := vulkan.Check(d.disp.CreatePipelineLayout(d.logical, &li, nil, &r.pipeLayout)); err != nil {
350		return err
351	}
352	vs, err := r.shader(shaders.CellVertex)
353	if err != nil {
354		return err
355	}
356	defer d.disp.DestroyShaderModule(d.logical, vs, nil)
357	fs, err := r.shader(shaders.CellFragment)
358	if err != nil {
359		return err
360	}
361	defer d.disp.DestroyShaderModule(d.logical, fs, nil)
362	entry := []byte("main\x00")
363	stages := [2]vulkan.PipelineShaderStageCreateInfo{
364		{SType: vulkan.StructureTypePipelineShaderStageCreateInfo, Stage: vulkan.ShaderStageVertexBit, Module: vs, Name: &entry[0]},
365		{SType: vulkan.StructureTypePipelineShaderStageCreateInfo, Stage: vulkan.ShaderStageFragmentBit, Module: fs, Name: &entry[0]},
366	}
367	var attrs [4]vulkan.VertexInputAttributeDescription
368	for i := range attrs {
369		attrs[i] = vulkan.VertexInputAttributeDescription{Location: uint32(i), Format: vulkan.FormatR32g32b32a32Sfloat, Offset: uint32(i) * 16}
370	}
371	bind := vulkan.VertexInputBindingDescription{Stride: uint32(unsafe.Sizeof(instance{})), InputRate: vulkan.VertexInputRateInstance}
372	vin := vulkan.PipelineVertexInputStateCreateInfo{SType: vulkan.StructureTypePipelineVertexInputStateCreateInfo, VertexBindingDescriptionCount: 1, VertexBindingDescriptions: &bind, VertexAttributeDescriptionCount: 4, VertexAttributeDescriptions: &attrs[0]}
373	asm := vulkan.PipelineInputAssemblyStateCreateInfo{SType: vulkan.StructureTypePipelineInputAssemblyStateCreateInfo, Topology: vulkan.PrimitiveTopologyTriangleList}
374	vp := vulkan.Viewport{Width: float32(r.W), Height: float32(r.H), MaxDepth: 1}
375	sc := vulkan.Rect2D{Extent: vulkan.Extent2D{Width: uint32(r.W), Height: uint32(r.H)}}
376	vps := vulkan.PipelineViewportStateCreateInfo{SType: vulkan.StructureTypePipelineViewportStateCreateInfo, ViewportCount: 1, Viewports: &vp, ScissorCount: 1, Scissors: &sc}
377	ras := vulkan.PipelineRasterizationStateCreateInfo{SType: vulkan.StructureTypePipelineRasterizationStateCreateInfo, PolygonMode: vulkan.PolygonModeFill, CullMode: vulkan.CullModeNone, FrontFace: vulkan.FrontFaceCounterClockwise, LineWidth: 1}
378	ms := vulkan.PipelineMultisampleStateCreateInfo{SType: vulkan.StructureTypePipelineMultisampleStateCreateInfo, RasterizationSamples: vulkan.SampleCount1Bit}
379	att := vulkan.PipelineColorBlendAttachmentState{ColorWriteMask: vulkan.ColorComponentRBit | vulkan.ColorComponentGBit | vulkan.ColorComponentBBit | vulkan.ColorComponentABit}
380	blend := vulkan.PipelineColorBlendStateCreateInfo{SType: vulkan.StructureTypePipelineColorBlendStateCreateInfo, AttachmentCount: 1, Attachments: &att}
381	format := vulkan.Format(vulkan.FormatB8g8r8a8Unorm)
382	rend := vulkan.PipelineRenderingCreateInfo{SType: vulkan.StructureTypePipelineRenderingCreateInfo, ColorAttachmentCount: 1, ColorAttachmentFormats: &format}
383	gi := vulkan.GraphicsPipelineCreateInfo{
384		SType: vulkan.StructureTypeGraphicsPipelineCreateInfo, Next: unsafe.Pointer(&rend), StageCount: 2, Stages: &stages[0],
385		VertexInputState: &vin, InputAssemblyState: &asm, ViewportState: &vps, RasterizationState: &ras,
386		MultisampleState: &ms, ColorBlendState: &blend, Layout: r.pipeLayout,
387	}
388	if err = vulkan.Check(d.disp.CreateGraphicsPipelines(d.logical, 0, 1, &gi, nil, &r.pipeline)); err != nil {
389		return fmt.Errorf("gpu: create pipeline: %w", err)
390	}
391	return nil
392}
393
394// queueGlyph schedules k for upload to atlas index idx in the current Draw.
395func (r *Renderer) queueGlyph(k glyphKey, idx uint32) {
396	r.uploadKeys = append(r.uploadKeys, k)
397	r.uploadIdx = append(r.uploadIdx, idx)
398}
399
400// glyph returns the atlas index of k, assigning one (and queueing its upload)
401// on first use. An exhausted atlas maps to the blank glyph.
402func (r *Renderer) glyph(k glyphKey) uint32 {
403	if idx, ok := r.glyphs[k]; ok {
404		return idx
405	}
406	if int(r.nextGlyph) >= r.atlasCols*r.atlasRows || len(r.uploadKeys) >= maxUploads {
407		return 0
408	}
409	idx := r.nextGlyph
410	r.nextGlyph++
411	r.glyphs[k] = idx
412	r.queueGlyph(k, idx)
413	return idx
414}
415
416// pickSlot returns a free slot index, or -1.
417func (r *Renderer) pickSlot() int {
418	for i := range r.slots {
419		if r.slots[i].state == slotFree {
420			return i
421		}
422	}
423	return -1
424}
425
426// Draw renders cells (len(cells) <= cols) into a free slot, waits for the GPU
427// and signals the acquire point. It reports false when every image is still
428// held by the compositor. clear is the background for pixels outside cells.
429func (r *Renderer) Draw(cells []Cell, clear [3]uint8, out *Frame) (bool, error) {
430	n := r.pickSlot()
431	if n < 0 {
432		return false, nil
433	}
434	s := &r.slots[n]
435	if len(cells) > r.maxCols {
436		cells = cells[:r.maxCols]
437	}
438	r.uploadKeys, r.uploadIdx = r.uploadKeys[:0], r.uploadIdx[:0]
439	if !r.atlasInited {
440		r.queueGlyph(glyphKey{' ', 0}, 0)
441		for c := rune(0x21); c < 0x7f; c++ {
442			r.queueGlyph(glyphKey{c, 0}, r.glyphs[glyphKey{c, 0}])
443		}
444	}
445	cw, ch := float32(r.face.CellW), float32(r.face.CellH)
446	inv := float32(1) / atlasSize
447	for i := range cells {
448		c := &cells[i]
449		gi := r.glyph(glyphKey{c.Rune, c.Style})
450		gx, gy := float32(int(gi)%r.atlasCols)*cw, float32(int(gi)/r.atlasCols)*ch
451		in := &s.inst[i]
452		in.Rect = [4]float32{float32(i) * cw, r.yOff, cw, ch}
453		in.UV = [4]float32{gx * inv, gy * inv, (gx + cw) * inv, (gy + ch) * inv}
454		in.FG = [4]float32{float32(c.FG[0]) / 255, float32(c.FG[1]) / 255, float32(c.FG[2]) / 255, 1}
455		in.BG = [4]float32{float32(c.BG[0]) / 255, float32(c.BG[1]) / 255, float32(c.BG[2]) / 255, 1}
456	}
457	cellBytes := r.face.CellW * r.face.CellH
458	s.regions = s.regions[:0]
459	for i, k := range r.uploadKeys {
460		off := i * cellBytes
461		r.face.Rasterize(k.r, k.style, s.stage[off:off+cellBytes])
462		gi := int(r.uploadIdx[i])
463		s.regions = append(s.regions, vulkan.BufferImageCopy{
464			BufferOffset:     vulkan.DeviceSize(off),
465			BufferRowLength:  uint32(r.face.CellW),
466			ImageSubresource: vulkan.ImageSubresourceLayers{AspectMask: vulkan.ImageAspectColorBit, LayerCount: 1},
467			ImageOffset:      vulkan.Offset3D{X: int32((gi % r.atlasCols) * r.face.CellW), Y: int32((gi / r.atlasCols) * r.face.CellH)},
468			ImageExtent:      vulkan.Extent3D{Width: uint32(r.face.CellW), Height: uint32(r.face.CellH), Depth: 1},
469		})
470	}
471	if err := r.record(s, len(cells), clear); err != nil {
472		return false, err
473	}
474	d := r.dev
475	s.cmdInfo = vulkan.CommandBufferSubmitInfo{SType: vulkan.StructureTypeCommandBufferSubmitInfo, CommandBuffer: s.cmd}
476	s.submit = vulkan.SubmitInfo2{SType: vulkan.StructureTypeSubmitInfo2, CommandBufferInfoCount: 1, CommandBufferInfos: &s.cmdInfo}
477	if err := vulkan.Check(d.disp.QueueSubmit2(d.queue, 1, &s.submit, s.fence)); err != nil {
478		return false, fmt.Errorf("gpu: submit: %w", err)
479	}
480	// The draw is tiny: wait for it here, then signal the acquire point from
481	// the CPU. No sync-file import is needed.
482	if err := vulkan.Check(d.disp.WaitForFences(d.logical, 1, &s.fence, 1, math.MaxUint64)); err != nil {
483		return false, fmt.Errorf("gpu: wait fence: %w", err)
484	}
485	if err := vulkan.Check(d.disp.ResetFences(d.logical, 1, &s.fence)); err != nil {
486		return false, err
487	}
488	r.atlasInited = true
489	r.acquirePoint++
490	if err := r.node.Signal(r.acquireHandle, r.acquirePoint); err != nil {
491		return false, err
492	}
493	s.releasePoint++
494	if err := r.node.EventFD(s.releaseHandle, s.releasePoint, s.eventFD); err != nil {
495		return false, err
496	}
497	s.state = slotShown
498	*out = Frame{
499		Slot: n, NewBuffer: !s.presented, Image: s.img,
500		AcquirePoint: r.acquirePoint, ReleasePoint: s.releasePoint,
501		ReleaseEventFD: s.eventFD, ReleaseTimeline: s.releaseFD,
502	}
503	s.presented = true
504	return true, nil
505}
506
507func (r *Renderer) record(s *slot, count int, clearRGB [3]uint8) error {
508	d := r.dev
509	disp := d.disp
510	if err := vulkan.Check(disp.ResetCommandBuffer(s.cmd, 0)); err != nil {
511		return err
512	}
513	s.begin = vulkan.CommandBufferBeginInfo{SType: vulkan.StructureTypeCommandBufferBeginInfo, Flags: vulkan.CommandBufferUsageOneTimeSubmitBit}
514	if err := vulkan.Check(disp.BeginCommandBuffer(s.cmd, &s.begin)); err != nil {
515		return err
516	}
517	s.dep = vulkan.DependencyInfo{SType: vulkan.StructureTypeDependencyInfo, ImageMemoryBarrierCount: 1, ImageMemoryBarriers: &s.barrier[0]}
518	barrier := &s.barrier[0]
519	if len(s.regions) > 0 || !r.atlasInited {
520		old := vulkan.ImageLayout(vulkan.ImageLayoutShaderReadOnlyOptimal)
521		srcStage, srcAccess := vulkan.PipelineStageFlags2(vulkan.PipelineStage2FragmentShaderBit), vulkan.AccessFlags2(vulkan.Access2ShaderSampledReadBit)
522		if !r.atlasInited {
523			old, srcStage, srcAccess = vulkan.ImageLayoutUndefined, vulkan.PipelineStage2None, 0
524		}
525		*barrier = vulkan.ImageMemoryBarrier2{
526			SType: vulkan.StructureTypeImageMemoryBarrier2, SrcStageMask: srcStage, SrcAccessMask: srcAccess,
527			DstStageMask: vulkan.PipelineStage2AllTransferBit, DstAccessMask: vulkan.Access2TransferWriteBit,
528			OldLayout: old, NewLayout: vulkan.ImageLayoutTransferDstOptimal,
529			SrcQueueFamilyIndex: ^uint32(0), DstQueueFamilyIndex: ^uint32(0), Image: r.atlasImg, SubresourceRange: s.atlasSub,
530		}
531		disp.CmdPipelineBarrier2(s.cmd, &s.dep)
532		if !r.atlasInited {
533			var zero vulkan.ClearColorValue
534			disp.CmdClearColorImage(s.cmd, r.atlasImg, vulkan.ImageLayoutTransferDstOptimal, &zero, 1, &s.atlasSub)
535			barrier.SrcStageMask, barrier.SrcAccessMask = vulkan.PipelineStage2AllTransferBit, vulkan.Access2TransferWriteBit
536			barrier.OldLayout = vulkan.ImageLayoutTransferDstOptimal
537			disp.CmdPipelineBarrier2(s.cmd, &s.dep)
538		}
539		if len(s.regions) > 0 {
540			disp.CmdCopyBufferToImage(s.cmd, s.stageBuf, r.atlasImg, vulkan.ImageLayoutTransferDstOptimal, uint32(len(s.regions)), &s.regions[0])
541		}
542		barrier.SrcStageMask, barrier.SrcAccessMask = vulkan.PipelineStage2AllTransferBit, vulkan.Access2TransferWriteBit
543		barrier.DstStageMask, barrier.DstAccessMask = vulkan.PipelineStage2FragmentShaderBit, vulkan.Access2ShaderSampledReadBit
544		barrier.OldLayout, barrier.NewLayout = vulkan.ImageLayoutTransferDstOptimal, vulkan.ImageLayoutShaderReadOnlyOptimal
545		disp.CmdPipelineBarrier2(s.cmd, &s.dep)
546	}
547	old := vulkan.ImageLayout(vulkan.ImageLayoutGeneral)
548	if !s.img.used {
549		old = vulkan.ImageLayoutUndefined
550	}
551	*barrier = vulkan.ImageMemoryBarrier2{
552		SType: vulkan.StructureTypeImageMemoryBarrier2, SrcStageMask: vulkan.PipelineStage2None,
553		DstStageMask: vulkan.PipelineStage2ColorAttachmentOutputBit, DstAccessMask: vulkan.Access2ColorAttachmentWriteBit,
554		OldLayout: old, NewLayout: vulkan.ImageLayoutColorAttachmentOptimal,
555		SrcQueueFamilyIndex: ^uint32(0), DstQueueFamilyIndex: ^uint32(0), Image: s.img.image, SubresourceRange: s.atlasSub,
556	}
557	disp.CmdPipelineBarrier2(s.cmd, &s.dep)
558	var cv vulkan.ClearValue
559	cv[0], cv[1], cv[2], cv[3] = math.Float32bits(float32(clearRGB[0])/255), math.Float32bits(float32(clearRGB[1])/255), math.Float32bits(float32(clearRGB[2])/255), math.Float32bits(1)
560	s.color = vulkan.RenderingAttachmentInfo{
561		SType: vulkan.StructureTypeRenderingAttachmentInfo, ImageView: s.img.view, ImageLayout: vulkan.ImageLayoutColorAttachmentOptimal,
562		LoadOp: vulkan.AttachmentLoadOpClear, StoreOp: vulkan.AttachmentStoreOpStore, ClearValue: cv,
563	}
564	s.render = vulkan.RenderingInfo{
565		SType:      vulkan.StructureTypeRenderingInfo,
566		RenderArea: vulkan.Rect2D{Extent: vulkan.Extent2D{Width: uint32(r.W), Height: uint32(r.H)}},
567		LayerCount: 1, ColorAttachmentCount: 1, ColorAttachments: &s.color,
568	}
569	disp.CmdBeginRendering(s.cmd, &s.render)
570	disp.CmdBindPipeline(s.cmd, vulkan.PipelineBindPointGraphics, r.pipeline)
571	s.sets[0] = r.descSet
572	disp.CmdBindDescriptorSets(s.cmd, vulkan.PipelineBindPointGraphics, r.pipeLayout, 0, 1, &s.sets[0], 0, nil)
573	s.push = [2]float32{float32(r.W), float32(r.H)}
574	disp.CmdPushConstants(s.cmd, r.pipeLayout, vulkan.ShaderStageVertexBit, 0, 8, unsafe.Pointer(&s.push))
575	s.offset = 0
576	disp.CmdBindVertexBuffers(s.cmd, 0, 1, &s.instBuf, &s.offset)
577	if count > 0 {
578		disp.CmdDraw(s.cmd, 6, uint32(count), 0, 0)
579	}
580	disp.CmdEndRendering(s.cmd)
581	// Leave the image in GENERAL for the compositor.
582	barrier.SrcStageMask, barrier.SrcAccessMask = vulkan.PipelineStage2ColorAttachmentOutputBit, vulkan.Access2ColorAttachmentWriteBit
583	barrier.DstStageMask, barrier.DstAccessMask = vulkan.PipelineStage2AllCommandsBit, vulkan.Access2MemoryReadBit
584	barrier.OldLayout, barrier.NewLayout = vulkan.ImageLayoutColorAttachmentOptimal, vulkan.ImageLayoutGeneral
585	disp.CmdPipelineBarrier2(s.cmd, &s.dep)
586	s.img.used = true
587	return vulkan.Check(disp.EndCommandBuffer(s.cmd))
588}
589
590// Released handles a readable release eventfd of slot: when the compositor's
591// release point has signaled, the image may be drawn again.
592func (r *Renderer) Released(slotIdx int) error {
593	if slotIdx < 0 || slotIdx >= SlotCount {
594		return nil
595	}
596	s := &r.slots[slotIdx]
597	var counter [8]byte
598	if _, err := unix.Read(s.eventFD, counter[:]); err != nil && !errors.Is(err, unix.EAGAIN) {
599		return fmt.Errorf("gpu: read release eventfd: %w", err)
600	}
601	if s.state != slotShown {
602		return nil
603	}
604	point, err := r.node.Query(s.releaseHandle)
605	if err != nil {
606		return err
607	}
608	if point >= s.releasePoint {
609		s.state = slotFree
610	}
611	return nil
612}
613
614// AcquireTimelineFD is the syncobj fd of the shared acquire timeline.
615func (r *Renderer) AcquireTimelineFD() int { return r.acquireFD }
616
617// Cols is the number of cells that fit the width, rounding up.
618func (r *Renderer) Cols() int { return r.cols }
619
620// Close waits for the GPU and frees everything. The compositor's imports keep
621// their own kernel references.
622func (r *Renderer) Close() {
623	if r == nil {
624		return
625	}
626	d := r.dev
627	_ = d.WaitIdle()
628	for i := range r.slots {
629		s := &r.slots[i]
630		if s.eventFD >= 0 {
631			_ = unix.Close(s.eventFD)
632		}
633		if s.releaseFD >= 0 {
634			_ = unix.Close(s.releaseFD)
635		}
636		if s.releaseHandle != 0 {
637			_ = r.node.Destroy(s.releaseHandle)
638		}
639		for _, b := range [...]struct {
640			buf vulkan.Buffer
641			mem vulkan.DeviceMemory
642		}{{s.instBuf, s.instMem}, {s.stageBuf, s.stageMem}} {
643			if b.mem != 0 {
644				d.disp.UnmapMemory(d.logical, b.mem)
645				d.disp.FreeMemory(d.logical, b.mem, nil)
646			}
647			if b.buf != 0 {
648				d.disp.DestroyBuffer(d.logical, b.buf, nil)
649			}
650		}
651		if s.fence != 0 {
652			d.disp.DestroyFence(d.logical, s.fence, nil)
653		}
654		if s.pool != 0 {
655			d.disp.DestroyCommandPool(d.logical, s.pool, nil)
656		}
657		s.img.Close()
658	}
659	if r.acquireFD >= 0 {
660		_ = unix.Close(r.acquireFD)
661	}
662	if r.acquireHandle != 0 {
663		_ = r.node.Destroy(r.acquireHandle)
664	}
665	if r.pipeline != 0 {
666		d.disp.DestroyPipeline(d.logical, r.pipeline, nil)
667	}
668	if r.pipeLayout != 0 {
669		d.disp.DestroyPipelineLayout(d.logical, r.pipeLayout, nil)
670	}
671	if r.descPool != 0 {
672		d.disp.DestroyDescriptorPool(d.logical, r.descPool, nil)
673	}
674	if r.setLayout != 0 {
675		d.disp.DestroyDescriptorSetLayout(d.logical, r.setLayout, nil)
676	}
677	if r.sampler != 0 {
678		d.disp.DestroySampler(d.logical, r.sampler, nil)
679	}
680	if r.atlasView != 0 {
681		d.disp.DestroyImageView(d.logical, r.atlasView, nil)
682	}
683	if r.atlasImg != 0 {
684		d.disp.DestroyImage(d.logical, r.atlasImg, nil)
685	}
686	if r.atlasMem != 0 {
687		d.disp.FreeMemory(d.logical, r.atlasMem, nil)
688	}
689}

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