v0.1.0 d9218d6

scanout_test.go RawHistoryFolder

1package drm
2
3import (
4	"testing"
5	"time"
6
7	"github.com/bnema/neferwl/internal/ports"
8	"github.com/stretchr/testify/mock"
9)
10
11func TestScanoutCandidate(t *testing.T) {
12	full := ports.SceneWindow{ID: 1, Rect: ports.Rect{W: 100, H: 50}, Fullscreen: true}
13	scene := ports.Scene{OutputWidth: 100, OutputHeight: 50, Windows: []ports.SceneWindow{full}}
14	buf := &ports.DMABuf{ID: 9, Width: 200, Height: 100, Format: fourccARGB, Planes: []ports.DMABufPlane{{}}}
15	game := ports.SurfaceContent{ID: 1, Width: 200, Height: 100, LogicalW: 100, LogicalH: 50, DMABuf: buf}
16	for _, tc := range []struct {
17		name   string
18		scene  func(s *ports.Scene)
19		cont   func(c *ports.SurfaceContent)
20		reason string
21	}{
22		{"scanout", nil, nil, ""},
23		{"hidden others ignored", func(s *ports.Scene) {
24			s.Windows = append(s.Windows, ports.SceneWindow{ID: 2, Hidden: true})
25		}, nil, ""},
26		{"no window", func(s *ports.Scene) { s.Windows = nil }, nil, "no_fullscreen"},
27		{"tiled", func(s *ports.Scene) { s.Windows[0].Fullscreen = false }, nil, "other_windows"},
28		{"float above", func(s *ports.Scene) {
29			s.Windows = append(s.Windows, ports.SceneWindow{ID: 2, Rect: ports.Rect{W: 10, H: 10}})
30		}, nil, "other_windows"},
31		{"not covering", func(s *ports.Scene) { s.Windows[0].Rect.W = 90 }, nil, "other_windows"},
32		{"overlay layer", func(s *ports.Scene) {
33			s.Layers = []ports.SceneLayer{{ID: 3, Layer: ports.LayerOverlay, Rect: ports.Rect{W: 10, H: 10}}}
34		}, nil, "overlay_surface"},
35		{"top layer covered", func(s *ports.Scene) {
36			s.Layers = []ports.SceneLayer{{ID: 3, Layer: ports.LayerTop, Rect: ports.Rect{W: 10, H: 10}}}
37		}, nil, ""},
38		{"shm", nil, func(c *ports.SurfaceContent) { c.DMABuf, c.SHM = nil, &ports.SHMBuffer{} }, "not_dmabuf"},
39		{"subsurface", nil, func(c *ports.SurfaceContent) { c.Children = []ports.Subsurface{{}} }, "subsurfaces"},
40		{"scaled", nil, func(c *ports.SurfaceContent) { c.Width = 100 }, "size_mismatch"},
41		{"cropped", nil, func(c *ports.SurfaceContent) { c.Geometry = ports.Rect{X: 5, W: 90, H: 50} }, "geometry_crop"},
42		{"client ignores scale", nil, func(c *ports.SurfaceContent) { c.LogicalW = 200 }, "logical_mismatch"},
43		{"whole geometry", nil, func(c *ports.SurfaceContent) { c.Geometry = ports.Rect{W: 100, H: 50} }, ""},
44	} {
45		s := scene
46		s.Windows = append([]ports.SceneWindow(nil), scene.Windows...)
47		c := game
48		if tc.scene != nil {
49			tc.scene(&s)
50		}
51		if tc.cont != nil {
52			tc.cont(&c)
53		}
54		if _, reason := scanoutCandidate(s, map[ports.WindowID]ports.SurfaceContent{1: c}, 200, 100); reason != tc.reason {
55			t.Errorf("%s: reason %q, want %q", tc.name, reason, tc.reason)
56		}
57	}
58}
59
60// A buffer scans out when the primary plane lists its format (or the
61// opaque variant) with its modifier; the tranche offers only formats the
62// renderer also composes.
63func TestScanoutFormatFollowsInFormats(t *testing.T) {
64	const tiled = 0x0200000000000001
65	o, k, _ := testOutput(t)
66	o.primary.formats = []ports.DMABufFormat{{Format: fourccXRGB, Modifier: tiled}, {Format: fourccXRGB, Modifier: 0}, {Format: fourccXR30, Modifier: tiled}}
67	nv12 := uint32('N' | 'V'<<8 | '1'<<16 | '2'<<24)
68	for _, tc := range []struct {
69		name   string
70		format uint32
71		mod    uint64
72		as     uint32
73		ok     bool
74	}{
75		{"argb as xrgb", fourccARGB, tiled, fourccXRGB, true},
76		{"xrgb linear", fourccXRGB, 0, fourccXRGB, true},
77		{"10 bit alpha as opaque", fourccAR30, tiled, fourccXR30, true},
78		{"modifier not listed", fourccXRGB, 0x0200000000000002, 0, false},
79		{"format not listed", nv12, tiled, 0, false},
80	} {
81		got, ok := o.scanoutFormat(&ports.DMABuf{Format: tc.format, Modifier: tc.mod})
82		if ok != tc.ok || ok && got != tc.as {
83			t.Errorf("%s: %#x %v", tc.name, got, ok)
84		}
85	}
86	sampled := []ports.DMABufFormat{{Format: fourccARGB, Modifier: tiled}, {Format: fourccARGB, Modifier: 0x0200000000000002}, {Format: nv12, Modifier: tiled}}
87	if got := o.scanoutFormats(sampled); len(got) != 1 || got[0] != sampled[0] {
88		t.Fatalf("tranche %v", got)
89	}
90	// A buffer not in IN_FORMATS is refused before any import.
91	o.clientFBs = map[uint64]*clientFB{}
92	if fb, reason := o.scanoutFB(&ports.DMABuf{ID: 4, Format: nv12, Modifier: tiled}, time.Now()); fb != 0 || reason != "format" {
93		t.Fatalf("fb %d reason %q", fb, reason)
94	}
95	// A listed one is imported with every plane (addFB builds ADDFB2).
96	k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(88, nil).Once()
97	if fb, reason := o.scanoutFB(&ports.DMABuf{ID: 5, Format: fourccARGB, Modifier: tiled, Planes: []ports.DMABufPlane{{}, {}}}, time.Now()); fb != 88 || reason != "" {
98		t.Fatalf("fb %d reason %q", fb, reason)
99	}
100}
101
102// Each modeset reports the formats clients may allocate for scanout;
103// with direct scanout off the list is empty.
104func TestModesetSendsScanoutFormats(t *testing.T) {
105	const tiled = 0x0200000000000001
106	for _, on := range []bool{true, false} {
107		o, k, _ := testOutput(t)
108		o.cursor = nil
109		o.scanout, o.device = on, 9
110		o.primary.formats = []ports.DMABufFormat{{Format: fourccXRGB, Modifier: tiled}}
111		o.sampled = []ports.DMABufFormat{{Format: fourccARGB, Modifier: tiled}, {Format: fourccARGB, Modifier: 0}}
112		ch := make(chan ports.OutputFormats, 1)
113		o.formats = ch
114		k.EXPECT().createBlob(mock.Anything).Return(99, nil).Once()
115		if err := o.modeset(); err != nil {
116			t.Fatal(err)
117		}
118		f := <-ch
119		if f.Output != "DP-1" || f.Device != 9 {
120			t.Fatalf("report %+v", f)
121		}
122		if on != (len(f.Formats) == 1 && f.Formats[0] == o.sampled[0]) || !on && len(f.Formats) != 0 {
123			t.Fatalf("scanout=%v formats %v", on, f.Formats)
124		}
125	}
126}
127
128// A plane without IN_FORMATS (driver without modifiers) scans out its
129// GETPLANE formats, linear.
130func TestPlaneWithoutInFormatsIsLinear(t *testing.T) {
131	k := newMockkms(t)
132	k.EXPECT().planes().Return([]planeRes{{id: 1, possible: 1, formats: []uint32{fourccXRGB}}}, nil)
133	k.EXPECT().objProps(uint32(1), uint32(objPlane)).Return(map[string][2]uint64{"type": {20, planePrimary}}, nil)
134	ps, err := readPlanes(k, 0)
135	if err != nil || len(ps) != 1 {
136		t.Fatalf("%v %v", ps, err)
137	}
138	o := &Output{primary: ps[0]}
139	if f, ok := o.scanoutFormat(&ports.DMABuf{Format: fourccARGB}); !ok || f != fourccXRGB {
140		t.Fatalf("linear argb: %#x %v", f, ok)
141	}
142	if _, ok := o.scanoutFormat(&ports.DMABuf{Format: fourccARGB, Modifier: 0x0200000000000001}); ok {
143		t.Fatal("tiled accepted without IN_FORMATS")
144	}
145}
146
147// A flip shows only the windows its scene draws: presentation feedback of
148// a window on another workspace or hidden is discarded, not presented.
149func TestShownByScene(t *testing.T) {
150	s := ports.Scene{Windows: []ports.SceneWindow{{ID: 1}, {ID: 2, Hidden: true}}, Layers: []ports.SceneLayer{{ID: 5}}}
151	got := shownBy(s, map[ports.WindowID]uint64{1: 3, 2: 4, 5: 6, 9: 7})
152	if len(got) != 2 || got[1] != 3 || got[5] != 6 {
153		t.Fatalf("shows %v", got)
154	}
155}

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