main 3a820d6

cursor_images.go RawHistoryFolder

1package drm
2
3import (
4	"errors"
5	"fmt"
6
7	"github.com/bnema/neferwl/internal/adapters/hdrcursor"
8	"github.com/bnema/neferwl/internal/ports"
9)
10
11// Who allocated the cursor images (the "alloc" log field).
12//
13// The renderer's cursor images (allocVulkan, shared with the output images)
14// are the fallback.
15const allocDumb = "dumb"
16
17// cursorImages are the two ARGB8888 images of the cursor plane and their
18// framebuffers. The output goroutine owns them.
19type cursorImages interface {
20	// fb is the framebuffer of image i.
21	fb(i int) uint32
22	// write fills image i with premultiplied ARGB8888 pixels (w×h, w*4 per
23	// row), cropped to the image and cleared around. hdrNits > 0 converts
24	// them to the HDR10 output encoding for that SDR white.
25	write(i int, pixels []byte, w, h int, hdrNits float64) error
26	// close removes the framebuffers and releases the images.
27	close()
28}
29
30// dumbImage is one KMS dumb buffer mapped for the CPU.
31type dumbImage struct {
32	handle, fb uint32
33	pitch      int
34	mem        []byte
35}
36
37// dumbCursor are two KMS dumb buffers: KMS allocates what it scans out, the
38// CPU writes the pixels. Renderer-allocated images are refused by some
39// drivers (nvidia-drm) on the cursor plane.
40type dumbCursor struct {
41	k     kms
42	size  int
43	img   [2]dumbImage
44	cache hdrcursor.Cache
45}
46
47func newDumbCursor(k kms, size int) (*dumbCursor, error) {
48	d := &dumbCursor{k: k, size: size}
49	for i := range d.img {
50		if err := d.alloc(&d.img[i]); err != nil {
51			d.close()
52			return nil, err
53		}
54	}
55	return d, nil
56}
57
58func (d *dumbCursor) alloc(img *dumbImage) error {
59	b, err := d.k.createDumb(d.size, d.size)
60	if err != nil {
61		return fmt.Errorf("create dumb buffer: %w", err)
62	}
63	img.handle, img.pitch = b.handle, int(b.pitch)
64	if img.pitch < d.size*4 {
65		return fmt.Errorf("dumb buffer pitch %d, want at least %d", img.pitch, d.size*4)
66	}
67	if img.mem, err = d.k.mapDumb(b); err != nil {
68		return fmt.Errorf("map dumb buffer: %w", err)
69	}
70	if len(img.mem) < img.pitch*d.size {
71		return fmt.Errorf("dumb buffer mapping %d bytes, want %d", len(img.mem), img.pitch*d.size)
72	}
73	clear(img.mem)
74	if img.fb, err = d.k.addFBHandle(b, d.size, d.size, fourccARGB); err != nil {
75		return fmt.Errorf("dumb buffer framebuffer: %w", err)
76	}
77	return nil
78}
79
80func (d *dumbCursor) fb(i int) uint32 { return d.img[i].fb }
81
82func (d *dumbCursor) write(i int, pixels []byte, w, h int, hdrNits float64) error {
83	if i < 0 || i >= len(d.img) || d.img[i].mem == nil {
84		return errors.New("no cursor image")
85	}
86	if w < 0 || h < 0 || len(pixels) < w*h*4 {
87		return errors.New("short cursor image")
88	}
89	img := &d.img[i]
90	mem := img.mem[:img.pitch*d.size]
91	clear(mem)
92	cw := min(w, d.size) * 4
93	src, stride := pixels, w*4
94	if hdrNits > 0 {
95		// Called only on image changes, never on moves.
96		src, stride = d.cache.Convert(pixels, w, h, d.size, hdrNits), cw
97	}
98	for y := range min(h, d.size) {
99		copy(mem[y*img.pitch:y*img.pitch+cw], src[y*stride:y*stride+cw])
100	}
101	return nil
102}
103
104func (d *dumbCursor) close() {
105	for i := range d.img {
106		img := &d.img[i]
107		if img.fb != 0 {
108			_ = d.k.rmFB(img.fb)
109		}
110		if img.mem != nil {
111			_ = d.k.unmapDumb(img.mem)
112		}
113		if img.handle != 0 {
114			_ = d.k.destroyDumb(img.handle)
115		}
116		*img = dumbImage{}
117	}
118	d.cache.Reset()
119}
120
121// rendererCursor are the renderer's cursor images (linear dmabufs the
122// renderer allocates and writes), imported as framebuffers.
123type rendererCursor struct {
124	k   kms
125	r   ports.Renderer
126	fbs [2]uint32
127}
128
129func newRendererCursor(k kms, r ports.Renderer, size int) (*rendererCursor, error) {
130	bufs, err := r.CursorBuffers(size)
131	if err != nil {
132		return nil, err
133	}
134	c := &rendererCursor{k: k, r: r}
135	for i := range bufs {
136		if err == nil {
137			c.fbs[i], err = k.addFB(&bufs[i], fourccARGB)
138		}
139		for _, p := range bufs[i].Planes {
140			p.File.Close()
141		}
142	}
143	if err != nil {
144		c.close()
145		return nil, err
146	}
147	return c, nil
148}
149
150func (c *rendererCursor) fb(i int) uint32 { return c.fbs[i] }
151
152// write relies on the renderer's own HDR state: hdrNits is not used.
153func (c *rendererCursor) write(i int, pixels []byte, w, h int, _ float64) error {
154	return c.r.WriteCursor(i, pixels, w, h)
155}
156
157func (c *rendererCursor) close() {
158	for i, fb := range c.fbs {
159		if fb != 0 {
160			_ = c.k.rmFB(fb)
161			c.fbs[i] = 0
162		}
163	}
164}

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