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}