main 5d6fa26

gbm.go RawHistoryFolder

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}

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