1// Package gbm allocates dmabuf images with libgbm, loaded at runtime with
2// purego. Display drivers know best how to lay out a buffer they will scan
3// out (NVIDIA's block-linear layouts in particular), so the DRM output asks
4// GBM for its images and the renderer imports them.
5package gbm
6
7import (
8 "errors"
9 "fmt"
10 "os"
11 "runtime"
12 "slices"
13 "sync"
14 "unsafe"
15
16 "github.com/bnema/neferwl/internal/ports"
17 "github.com/bnema/purego"
18 "golang.org/x/sys/unix"
19)
20
21// Usage is a set of GBM_BO_USE_* flags.
22type Usage uint32
23
24const (
25 // UseScanout (GBM_BO_USE_SCANOUT): the buffer can be shown by KMS.
26 UseScanout Usage = 1 << 0
27 // UseRendering (GBM_BO_USE_RENDERING): a GPU can render into it.
28 UseRendering Usage = 1 << 2
29 // useLinear (GBM_BO_USE_LINEAR): the layout is linear.
30 useLinear Usage = 1 << 4
31)
32
33const (
34 // modLinear is DRM_FORMAT_MOD_LINEAR.
35 modLinear = 0
36 // modInvalid is DRM_FORMAT_MOD_INVALID: an implicit layout.
37 modInvalid = 1<<56 - 1
38 // maxPlanes is the DRM limit of planes in a framebuffer.
39 maxPlanes = 4
40)
41
42var (
43 loadOnce sync.Once
44 loadErr error
45
46 createDevice func(fd int32) uintptr
47 destroyDevice func(dev uintptr)
48 createWithModifiers2 func(dev uintptr, w, h, format uint32, mods unsafe.Pointer, count uint32, flags uint32) uintptr
49 createPlain func(dev uintptr, w, h, format uint32, flags uint32) uintptr
50 boModifier func(bo uintptr) uint64
51 boPlaneCount func(bo uintptr) int32
52 boFDForPlane func(bo uintptr, plane int32) int32
53 boStrideForPlane func(bo uintptr, plane int32) uint32
54 boOffset func(bo uintptr, plane int32) uint32
55 boDestroy func(bo uintptr)
56)
57
58// load opens libgbm once. Without it, or with one that lacks a function
59// below (gbm_bo_create_with_modifiers2 and gbm_bo_get_fd_for_plane came with
60// Mesa 21.1), the allocator is unavailable and callers fall back to another
61// way to make images.
62func load() error {
63 loadOnce.Do(func() {
64 lib, err := purego.Dlopen("libgbm.so.1", purego.RTLD_NOW|purego.RTLD_LOCAL)
65 if err != nil {
66 loadErr = fmt.Errorf("open libgbm.so.1: %w", err)
67 return
68 }
69 defer func() {
70 if r := recover(); r != nil {
71 loadErr = fmt.Errorf("libgbm: %v", r)
72 }
73 }()
74 reg := func(fn any, name string) { purego.RegisterLibFunc(fn, lib, name) }
75 reg(&createDevice, "gbm_create_device")
76 reg(&destroyDevice, "gbm_device_destroy")
77 reg(&createWithModifiers2, "gbm_bo_create_with_modifiers2")
78 reg(&createPlain, "gbm_bo_create")
79 reg(&boModifier, "gbm_bo_get_modifier")
80 reg(&boPlaneCount, "gbm_bo_get_plane_count")
81 reg(&boFDForPlane, "gbm_bo_get_fd_for_plane")
82 reg(&boStrideForPlane, "gbm_bo_get_stride_for_plane")
83 reg(&boOffset, "gbm_bo_get_offset")
84 reg(&boDestroy, "gbm_bo_destroy")
85 })
86 return loadErr
87}
88
89// Device is a GBM device on a duplicate of a DRM device fd. Close its
90// buffers before the device.
91type Device struct {
92 dev uintptr
93 fd int
94}
95
96// Open creates a GBM device on a duplicate of fd, which stays the caller's.
97func Open(fd int) (*Device, error) {
98 if err := load(); err != nil {
99 return nil, err
100 }
101 dup, err := unix.FcntlInt(uintptr(fd), unix.F_DUPFD_CLOEXEC, 0)
102 if err != nil {
103 return nil, fmt.Errorf("duplicate drm fd: %w", err)
104 }
105 dev := createDevice(int32(dup))
106 if dev == 0 {
107 _ = unix.Close(dup)
108 return nil, errors.New("gbm_create_device failed")
109 }
110 return &Device{dev: dev, fd: dup}, nil
111}
112
113// Close destroys the device and its fd.
114func (d *Device) Close() {
115 if d == nil || d.dev == 0 {
116 return
117 }
118 destroyDevice(d.dev)
119 _ = unix.Close(d.fd)
120 d.dev = 0
121}
122
123// Buffer is an allocated image. It owns one fd per plane and the GBM
124// object, kept alive for as long as the image is used.
125type Buffer struct {
126 bo uintptr
127 width, height int
128 format uint32
129 modifier uint64
130 planes []plane
131}
132
133type plane struct {
134 fd int
135 offset, stride uint32
136}
137
138// Create allocates an image whose modifier is one of modifiers (a
139// non-empty list). A list holding only linear falls back to a plain
140// linear allocation when GBM refuses the modifier call, and accepts an
141// implicit layout (DRM_FORMAT_MOD_INVALID) as linear. The
142// result must be one memory object, because the renderer imports one
143// dmabuf for all planes.
144func (d *Device) Create(width, height int, format uint32, modifiers []uint64, usage Usage) (*Buffer, error) {
145 if d == nil || d.dev == 0 {
146 return nil, errors.New("gbm device closed")
147 }
148 if width <= 0 || height <= 0 || len(modifiers) == 0 {
149 return nil, fmt.Errorf("gbm: bad request %dx%d with %d modifiers", width, height, len(modifiers))
150 }
151 bo := createWithModifiers2(d.dev, uint32(width), uint32(height), format, unsafe.Pointer(&modifiers[0]), uint32(len(modifiers)), uint32(usage))
152 runtime.KeepAlive(modifiers)
153 onlyLinear := len(modifiers) == 1 && modifiers[0] == modLinear
154 if bo == 0 && onlyLinear {
155 bo = createPlain(d.dev, uint32(width), uint32(height), format, uint32(usage|useLinear))
156 }
157 if bo == 0 {
158 return nil, fmt.Errorf("gbm: no %dx%d buffer of format %#x with modifiers %#x", width, height, format, modifiers)
159 }
160 b, err := readBuffer(bo, width, height, format, acceptedModifiers(modifiers))
161 if err != nil {
162 boDestroy(bo)
163 return nil, err
164 }
165 if onlyLinear && b.modifier == modInvalid {
166 b.modifier = modLinear
167 }
168 return b, nil
169}
170
171// acceptedModifiers is what an allocation may report for a request: the
172// request itself, plus an implicit layout when only linear was asked for.
173func acceptedModifiers(requested []uint64) []uint64 {
174 if len(requested) == 1 && requested[0] == modLinear {
175 return []uint64{modLinear, modInvalid}
176 }
177 return requested
178}
179
180// readBuffer reads the layout of bo and validates it against the request.
181// It does not destroy bo on error.
182func readBuffer(bo uintptr, width, height int, format uint32, modifiers []uint64) (*Buffer, error) {
183 b := &Buffer{bo: bo, width: width, height: height, format: format, modifier: boModifier(bo)}
184 if err := checkModifier(modifiers, b.modifier); err != nil {
185 return nil, err
186 }
187 n := int(boPlaneCount(bo))
188 if n < 1 || n > maxPlanes {
189 return nil, fmt.Errorf("gbm: buffer has %d planes", n)
190 }
191 for i := range n {
192 fd := boFDForPlane(bo, int32(i))
193 if fd < 0 {
194 b.closeFDs()
195 return nil, fmt.Errorf("gbm: no fd for plane %d", i)
196 }
197 b.planes = append(b.planes, plane{fd: int(fd), offset: boOffset(bo, int32(i)), stride: boStrideForPlane(bo, int32(i))})
198 }
199 fds := make([]int, len(b.planes))
200 for i, p := range b.planes {
201 fds[i] = p.fd
202 }
203 if err := checkOneObject(fds); err != nil {
204 b.closeFDs()
205 return nil, err
206 }
207 b.bo = bo
208 return b, nil
209}
210
211// checkModifier fails when the allocator returned a modifier that was not
212// asked for (including DRM_FORMAT_MOD_INVALID, an implicit layout the
213// renderer cannot import).
214func checkModifier(requested []uint64, got uint64) error {
215 if !slices.Contains(requested, got) {
216 return fmt.Errorf("gbm: modifier %#x not in the requested list %#x", got, requested)
217 }
218 return nil
219}
220
221// checkOneObject fails when the plane fds are not all the same dmabuf:
222// disjoint planes cannot be imported as one memory object.
223func checkOneObject(fds []int) error {
224 var first unix.Stat_t
225 for i, fd := range fds {
226 var st unix.Stat_t
227 if err := unix.Fstat(fd, &st); err != nil {
228 return fmt.Errorf("gbm: stat plane %d: %w", i, err)
229 }
230 if i == 0 {
231 first = st
232 continue
233 }
234 if st.Dev != first.Dev || st.Ino != first.Ino {
235 return fmt.Errorf("gbm: plane %d is a different dmabuf than plane 0", i)
236 }
237 }
238 return nil
239}
240
241func (b *Buffer) closeFDs() {
242 for _, p := range b.planes {
243 _ = unix.Close(p.fd)
244 }
245 b.planes = nil
246}
247
248// DMABuf returns the image as a dmabuf with fresh descriptors (the caller
249// closes them); the buffer keeps its own.
250func (b *Buffer) DMABuf() (ports.DMABuf, error) {
251 if b == nil || b.bo == 0 {
252 return ports.DMABuf{}, errors.New("gbm buffer closed")
253 }
254 out := ports.DMABuf{Width: b.width, Height: b.height, Format: b.format, Modifier: b.modifier}
255 for _, p := range b.planes {
256 fd, err := unix.FcntlInt(uintptr(p.fd), unix.F_DUPFD_CLOEXEC, 0)
257 if err != nil {
258 for _, q := range out.Planes {
259 _ = q.File.Close()
260 }
261 return ports.DMABuf{}, fmt.Errorf("duplicate gbm plane fd: %w", err)
262 }
263 out.Planes = append(out.Planes, ports.DMABufPlane{File: os.NewFile(uintptr(fd), "neferwl-gbm"), Offset: p.offset, Stride: p.stride})
264 }
265 return out, nil
266}
267
268// Close frees the buffer and its descriptors.
269func (b *Buffer) Close() {
270 if b == nil || b.bo == 0 {
271 return
272 }
273 b.closeFDs()
274 boDestroy(b.bo)
275 b.bo = 0
276}