v0.3.0 cf370a0

dmabuf_test.go RawHistoryFolder

1package vulkan
2
3import (
4	"image"
5	"image/color"
6	"os"
7	"slices"
8	"testing"
9	"unsafe"
10
11	"github.com/bnema/neferwl/internal/ports"
12	"golang.org/x/sys/unix"
13)
14
15// udmabuf turns memfd pages into a real dmabuf: a linear buffer any GPU
16// driver imports, like a client's.
17func udmabuf(t *testing.T, w, h int, fill func(x, y int) [4]byte) *os.File {
18	t.Helper()
19	dev, err := os.OpenFile("/dev/udmabuf", os.O_RDWR, 0)
20	if err != nil {
21		t.Skipf("udmabuf unavailable: %v", err)
22	}
23	defer dev.Close()
24	size := (w*h*4 + os.Getpagesize() - 1) / os.Getpagesize() * os.Getpagesize()
25	mem, err := unix.MemfdCreate("dmabuf-test", unix.MFD_ALLOW_SEALING)
26	if err != nil {
27		t.Fatal(err)
28	}
29	defer unix.Close(mem)
30	if err := unix.Ftruncate(mem, int64(size)); err != nil {
31		t.Fatal(err)
32	}
33	data, err := unix.Mmap(mem, 0, size, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED)
34	if err != nil {
35		t.Fatal(err)
36	}
37	for y := range h {
38		for x := range w {
39			p := fill(x, y)
40			copy(data[(y*w+x)*4:], p[:])
41		}
42	}
43	_ = unix.Munmap(data)
44	if _, err := unix.FcntlInt(uintptr(mem), unix.F_ADD_SEALS, unix.F_SEAL_SHRINK); err != nil {
45		t.Fatal(err)
46	}
47	// struct udmabuf_create { u32 memfd; u32 flags; u64 offset; u64 size; }
48	arg := struct {
49		memfd, flags uint32
50		offset, size uint64
51	}{memfd: uint32(mem), flags: 1 /* CLOEXEC */, size: uint64(size)}
52	const udmabufCreate = 0x40187542 // _IOW('u', 0x42, struct udmabuf_create)
53	fd, _, errno := unix.Syscall(unix.SYS_IOCTL, dev.Fd(), udmabufCreate, uintptr(unsafe.Pointer(&arg)))
54	if errno != 0 {
55		t.Skipf("udmabuf create: %v", errno)
56	}
57	f := os.NewFile(fd, "dmabuf")
58	t.Cleanup(func() { f.Close() })
59	return f
60}
61
62// A linear ARGB8888 dmabuf is imported and drawn like wl_shm pixels, and
63// the import is freed once no frame draws it.
64func TestRendererImportsDMABuf(t *testing.T) {
65	r, err := New(80, 32)
66	if err != nil {
67		t.Skipf("Vulkan unavailable: %v", err)
68	}
69	defer r.Close()
70	linear := ports.DMABufFormat{Format: fourcc('A', 'R', '2', '4'), Modifier: 0}
71	if !slices.Contains(r.DMABuf().Formats, linear) {
72		t.Skipf("linear ARGB8888 not importable: %+v", r.DMABuf())
73	}
74	// Left half red, right half blue (B,G,R,A in memory). 64 pixels wide:
75	// drivers want linear rows aligned (256 bytes on AMD).
76	f := udmabuf(t, 64, 16, func(x, _ int) [4]byte {
77		if x < 32 {
78			return [4]byte{0, 0, 255, 255}
79		}
80		return [4]byte{255, 0, 0, 255}
81	})
82	buf := &ports.DMABuf{ID: 7, Width: 64, Height: 16, Format: linear.Format, Planes: []ports.DMABufPlane{{File: f, Stride: 256}}}
83	scene := ports.Scene{Background: "#000000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{X: 4, Y: 4, W: 64, H: 16}}}}
84	contents := map[ports.WindowID]ports.SurfaceContent{1: {ID: 1, Width: 64, Height: 16, DMABuf: buf}}
85	if err := render(r, scene, contents); err != nil {
86		t.Fatal(err)
87	}
88	px := readPixels(t, r)
89	for _, c := range []struct {
90		x, y int
91		want color.RGBA
92	}{{4, 4, color.RGBA{255, 0, 0, 255}}, {35, 19, color.RGBA{255, 0, 0, 255}}, {36, 4, color.RGBA{0, 0, 255, 255}}, {67, 19, color.RGBA{0, 0, 255, 255}}, {2, 2, color.RGBA{0, 0, 0, 255}}, {70, 10, color.RGBA{0, 0, 0, 255}}} {
93		if got := px.At(c.x, c.y); got != c.want {
94			t.Errorf("At(%d,%d)=%v want %v", c.x, c.y, got, c.want)
95		}
96	}
97	if len(r.imports) != 1 {
98		t.Fatalf("imports %d", len(r.imports))
99	}
100	// Frames without the buffer: its import is released after importTTL.
101	for range importTTL + 1 {
102		if err := render(r, scene, nil); err != nil {
103			t.Fatal(err)
104		}
105	}
106	if len(r.imports) != 0 {
107		t.Fatalf("import kept: %d", len(r.imports))
108	}
109}
110
111// The HDR swapchain's DRM 2101010 formats use packed UNORM views, not
112// automatic sRGB decoding. The same imports work in SDR composition.
113func TestTenBitDMABufFormats(t *testing.T) {
114	for _, tc := range []struct {
115		name   [4]byte
116		opaque bool
117	}{
118		{[4]byte{'A', 'R', '3', '0'}, false},
119		{[4]byte{'X', 'R', '3', '0'}, true},
120		{[4]byte{'A', 'B', '3', '0'}, false},
121		{[4]byte{'X', 'B', '3', '0'}, true},
122	} {
123		format, opaque, ok := vkFormat(fourcc(tc.name[0], tc.name[1], tc.name[2], tc.name[3]))
124		if !ok || format == 0 || opaque != tc.opaque {
125			t.Errorf("%q: format %v opaque %t available %t", tc.name, format, opaque, ok)
126		}
127	}
128}
129
130// A subsurface dmabuf is read after its own acquire fence, not the root's.
131func TestRendererSubsurfaceAcquire(t *testing.T) {
132	r, err := New(80, 32)
133	if err != nil {
134		t.Skipf("Vulkan unavailable: %v", err)
135	}
136	defer r.Close()
137	linear := ports.DMABufFormat{Format: fourcc('A', 'R', '2', '4'), Modifier: 0}
138	if !slices.Contains(r.DMABuf().Formats, linear) {
139		t.Skipf("linear ARGB8888 not importable: %+v", r.DMABuf())
140	}
141	f := udmabuf(t, 64, 16, func(int, int) [4]byte { return [4]byte{0, 0, 255, 255} })
142	fence, w, _ := os.Pipe()
143	defer fence.Close()
144	defer w.Close()
145	child := ports.SurfaceContent{ID: 1, Width: 64, Height: 16, Acquire: fence,
146		DMABuf: &ports.DMABuf{ID: 8, Width: 64, Height: 16, Format: linear.Format, Planes: []ports.DMABufPlane{{File: f, Stride: 256}}}}
147	scene := ports.Scene{Background: "#000000", Windows: []ports.SceneWindow{{ID: 1, Rect: ports.Rect{W: 64, H: 16}}}}
148	contents := map[ports.WindowID]ports.SurfaceContent{1: {ID: 1, Width: 64, Height: 16, Children: []ports.Subsurface{{SurfaceContent: child}}}}
149	ds := r.draws(scene, contents, newDamage(r.target(), scene, image.Rect(0, 0, 80, 32)))
150	for _, d := range ds {
151		if d.im != nil && d.acquire == fence {
152			return
153		}
154	}
155	t.Fatal("subsurface draw has no acquire fence")
156}

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 26

Tags 10

↑↓ moveenter openesc close