1package drm
2
3import (
4 "errors"
5 "os"
6 "testing"
7
8 portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
9 "github.com/bnema/neferwl/internal/ports"
10 "github.com/stretchr/testify/mock"
11 "golang.org/x/sys/unix"
12)
13
14// gbmBuf is a dmabuf over a closed pipe, as an image buffer hands out.
15func gbmBuf(planes int) ports.DMABuf {
16 b := ports.DMABuf{Modifier: tiledMod}
17 for range planes {
18 f, w, _ := os.Pipe()
19 w.Close()
20 b.Planes = append(b.Planes, ports.DMABufPlane{File: f})
21 }
22 return b
23}
24
25// twoGBMImages makes an allocator that hands out two images of the given
26// plane count; each image closes exactly once, counted in closed.
27func twoGBMImages(t *testing.T, planes int) (a imageAllocator, closed *int) {
28 m := newMockimageAllocator(t)
29 closed = new(int)
30 m.EXPECT().create(200, 100, uint32(fourccXRGB), []uint64{tiledMod}).RunAndReturn(func(int, int, uint32, []uint64) (imageBuffer, error) {
31 img := newMockimageBuffer(t)
32 img.EXPECT().dmabuf().RunAndReturn(func() (ports.DMABuf, error) { return gbmBuf(planes), nil })
33 img.EXPECT().close().Run(func() { *closed++ }).Once()
34 return img, nil
35 }).Twice()
36 m.EXPECT().close().Return().Maybe()
37 return m, closed
38}
39
40func gbmRenderer(t *testing.T) *portsmocks.MockRenderer {
41 r := portsmocks.NewMockRenderer(t)
42 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
43 r.EXPECT().UseTarget(mock.Anything).Return().Maybe()
44 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil).Maybe()
45 return r
46}
47
48// GBM images come first: the renderer imports them, the framebuffers are
49// made from them, and no image is exported.
50func TestShowImagesAllocatesWithGBMFirst(t *testing.T) {
51 o, k, _ := testOutput(t)
52 o.cursor = nil
53 a, closed := twoGBMImages(t, 1)
54 o.openAlloc = func() (imageAllocator, error) { return a, nil }
55 r := gbmRenderer(t)
56 r.EXPECT().TargetModifiers([]uint64(nil), false).Return([]uint64{tiledMod}).Once()
57 r.EXPECT().ImportTargets(mock.MatchedBy(func(b []ports.DMABuf) bool { return len(b) == 2 && b[0].Modifier == tiledMod })).Return(nil).Once()
58 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil).Twice()
59 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
60 k.EXPECT().destroyBlob(mock.Anything).Return(nil)
61 k.EXPECT().rmFB(mock.Anything).Return(nil)
62 if err := o.showImages(r, imagesDriver, nil); err != nil {
63 t.Fatal(err)
64 }
65 if o.kind != imagesDriver || o.imageMod != tiledMod || len(o.images) != 2 || *closed != 0 {
66 t.Fatalf("kind %d modifier %#x images %d closed %d", o.kind, o.imageMod, len(o.images), *closed)
67 }
68 r.EXPECT().ImportTargets([]ports.DMABuf(nil)).Return(nil).Once()
69 o.dropImages(r)
70 if *closed != 2 || o.images != nil || o.fbs != [2]uint32{} {
71 t.Fatalf("closed %d images, %d left, fbs %v", *closed, len(o.images), o.fbs)
72 }
73}
74
75// A GBM failure at any step falls back to the renderer's own images of the same kind.
76func TestShowImagesFallsBackToVulkanWhenGBMFails(t *testing.T) {
77 for name, tc := range map[string]struct {
78 setup func(t *testing.T, o *Output, k *mockkms, r *portsmocks.MockRenderer)
79 }{
80 "gbm unavailable": {func(_ *testing.T, o *Output, _ *mockkms, _ *portsmocks.MockRenderer) {
81 o.openAlloc = func() (imageAllocator, error) { return nil, errors.New("no libgbm") }
82 }},
83 "no common modifier": {func(t *testing.T, o *Output, k *mockkms, r *portsmocks.MockRenderer) {
84 a := newMockimageAllocator(t)
85 a.EXPECT().close().Return().Maybe()
86 o.openAlloc = func() (imageAllocator, error) { return a, nil }
87 r.EXPECT().TargetModifiers(mock.Anything, false).Return(nil).Once()
88 }},
89 "allocation fails": {func(t *testing.T, o *Output, k *mockkms, r *portsmocks.MockRenderer) {
90 a := newMockimageAllocator(t)
91 a.EXPECT().close().Return().Maybe()
92 a.EXPECT().create(200, 100, uint32(fourccXRGB), []uint64{tiledMod}).Return(nil, errors.New("gbm: no buffer")).Once()
93 o.openAlloc = func() (imageAllocator, error) { return a, nil }
94 r.EXPECT().TargetModifiers(mock.Anything, false).Return([]uint64{tiledMod}).Once()
95 }},
96 "second allocation fails": {func(t *testing.T, o *Output, k *mockkms, r *portsmocks.MockRenderer) {
97 a := newMockimageAllocator(t)
98 a.EXPECT().close().Return().Maybe()
99 img := newMockimageBuffer(t)
100 img.EXPECT().dmabuf().Return(gbmBuf(1), nil).Once()
101 img.EXPECT().close().Return().Once() // freed with the failed attempt
102 r.EXPECT().ImportTargets([]ports.DMABuf(nil)).Return(nil).Once()
103 a.EXPECT().create(200, 100, uint32(fourccXRGB), []uint64{tiledMod}).Return(img, nil).Once()
104 a.EXPECT().create(200, 100, uint32(fourccXRGB), []uint64{tiledMod}).Return(nil, errors.New("out of memory")).Once()
105 o.openAlloc = func() (imageAllocator, error) { return a, nil }
106 r.EXPECT().TargetModifiers(mock.Anything, false).Return([]uint64{tiledMod}).Once()
107 }},
108 "import refused": {func(t *testing.T, o *Output, k *mockkms, r *portsmocks.MockRenderer) {
109 a, _ := twoGBMImages(t, 1)
110 o.openAlloc = func() (imageAllocator, error) { return a, nil }
111 r.EXPECT().TargetModifiers(mock.Anything, false).Return([]uint64{tiledMod}).Once()
112 r.EXPECT().ImportTargets(mock.Anything).Return(errors.New("explicit layout refused")).Once()
113 // The images made before the refusal go with a renderer drop.
114 r.EXPECT().ImportTargets([]ports.DMABuf(nil)).Return(nil).Once()
115 }},
116 "framebuffer refused": {func(t *testing.T, o *Output, k *mockkms, r *portsmocks.MockRenderer) {
117 a, _ := twoGBMImages(t, 1)
118 o.openAlloc = func() (imageAllocator, error) { return a, nil }
119 r.EXPECT().TargetModifiers(mock.Anything, false).Return([]uint64{tiledMod}).Once()
120 r.EXPECT().ImportTargets(mock.Anything).Return(nil).Once()
121 r.EXPECT().ImportTargets([]ports.DMABuf(nil)).Return(nil).Once()
122 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(0, unix.EINVAL).Once()
123 }},
124 } {
125 t.Run(name, func(t *testing.T) {
126 o, k, _ := testOutput(t)
127 o.cursor = nil
128 r := gbmRenderer(t)
129 tc.setup(t, o, k, r)
130 r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{gbmBuf(1), gbmBuf(1)}, nil).Once()
131 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil).Maybe()
132 k.EXPECT().rmFB(mock.Anything).Return(nil).Maybe()
133 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
134 k.EXPECT().destroyBlob(mock.Anything).Return(nil)
135 if err := o.showImages(r, imagesDriver, nil); err != nil {
136 t.Fatal(err)
137 }
138 // The renderer's own images: no GBM image is alive.
139 if o.kind != imagesDriver || len(o.images) != 0 || o.fbs[0] == 0 || o.fbs[1] == 0 {
140 t.Fatalf("kind %d images %d fbs %v", o.kind, len(o.images), o.fbs)
141 }
142 })
143 }
144}
145
146// A failed GBM open is remembered: the next kind and the next setup do not
147// open the library again.
148func TestGBMOpenFailureIsRemembered(t *testing.T) {
149 o, k, _ := testOutput(t, unix.EINVAL)
150 o.cursor = nil
151 opens := 0
152 o.openAlloc = func() (imageAllocator, error) { opens++; return nil, errors.New("no libgbm") }
153 r := gbmRenderer(t)
154 r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{gbmBuf(2), gbmBuf(2)}, nil).Once()
155 r.EXPECT().ExportTargets(2, []uint64(nil), true).Return([]ports.DMABuf{gbmBuf(1), gbmBuf(1)}, nil).Once()
156 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil)
157 k.EXPECT().rmFB(mock.Anything).Return(nil)
158 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
159 k.EXPECT().destroyBlob(mock.Anything).Return(nil)
160 if err := o.showImages(r, imagesDriver, nil); err != nil {
161 t.Fatal(err)
162 }
163 if opens != 1 || o.kind != imagesSinglePlane {
164 t.Fatalf("%d opens, kind %d", opens, o.kind)
165 }
166}
167
168// A modeset test that refuses GBM images tries the renderer's images of the
169// same kind before the next kind.
170func TestShowImagesRefusedGBMImagesTryVulkanOfTheSameKind(t *testing.T) {
171 o, k, commits := testOutput(t, unix.EINVAL)
172 o.cursor = nil
173 a, closed := twoGBMImages(t, 1)
174 o.openAlloc = func() (imageAllocator, error) { return a, nil }
175 r := gbmRenderer(t)
176 r.EXPECT().TargetModifiers([]uint64(nil), false).Return([]uint64{tiledMod}).Once()
177 r.EXPECT().ImportTargets(mock.MatchedBy(func(b []ports.DMABuf) bool { return len(b) == 2 })).Return(nil).Once()
178 // The refused GBM images leave no renderer target behind.
179 r.EXPECT().ImportTargets([]ports.DMABuf(nil)).Return(nil).Once()
180 r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{gbmBuf(1), gbmBuf(1)}, nil).Once()
181 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil)
182 k.EXPECT().rmFB(mock.Anything).Return(nil)
183 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
184 k.EXPECT().destroyBlob(mock.Anything).Return(nil)
185 if err := o.showImages(r, imagesDriver, nil); err != nil {
186 t.Fatal(err)
187 }
188 if o.kind != imagesDriver || *closed != 2 || len(o.images) != 0 || len(*commits) != 3 {
189 t.Fatalf("kind %d closed %d images %d commits %d", o.kind, *closed, len(o.images), len(*commits))
190 }
191}
192
193// Closing the output frees the GBM images and closes the allocator once.
194func TestOutputCloseFreesGBMImagesAndAllocator(t *testing.T) {
195 o, k, _ := testOutput(t)
196 o.cursor = nil
197 img := newMockimageBuffer(t)
198 img.EXPECT().close().Return().Twice()
199 alloc := newMockimageAllocator(t)
200 alloc.EXPECT().close().Return().Once()
201 o.images = []imageBuffer{img, img}
202 o.alloc = alloc
203 o.fbs = [2]uint32{70, 71}
204 k.EXPECT().rmFB(uint32(70)).Return(nil).Once()
205 k.EXPECT().rmFB(uint32(71)).Return(nil).Once()
206 o.Close()
207 if o.images != nil || o.alloc != nil || o.fbs != [2]uint32{} {
208 t.Fatalf("images %v alloc %v fbs %v", o.images, o.alloc, o.fbs)
209 }
210}
211
212// A setup that finds images left over frees them first, with the renderer's
213// targets over GBM ones: the renderer never keeps a target over a freed
214// image.
215func TestSetupStartsFromCleanState(t *testing.T) {
216 o, k, _ := testOutput(t)
217 o.cursor = nil
218 img := newMockimageBuffer(t)
219 img.EXPECT().close().Return().Once()
220 o.images = []imageBuffer{img}
221 o.fbs = [2]uint32{70, 71}
222 k.EXPECT().rmFB(uint32(70)).Return(nil).Once()
223 k.EXPECT().rmFB(uint32(71)).Return(nil).Once()
224 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(80, nil)
225 r := gbmRenderer(t)
226 r.EXPECT().ImportTargets([]ports.DMABuf(nil)).Return(nil).Once()
227 r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{gbmBuf(1), gbmBuf(1)}, nil).Once()
228 if err := o.exportImages(r, nil, false); err != nil {
229 t.Fatal(err)
230 }
231 if o.images != nil || o.fbs != [2]uint32{80, 80} {
232 t.Fatalf("images %v fbs %v", o.images, o.fbs)
233 }
234}