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}