1package drm
2
3import (
4 "context"
5 "errors"
6 "os"
7 "testing"
8 "time"
9
10 portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
11 "github.com/bnema/neferwl/internal/ports"
12 "github.com/stretchr/testify/mock"
13 "golang.org/x/sys/unix"
14)
15
16// An event queued before a modeset (or for an earlier output on the CRTC)
17// must not complete the commit that follows.
18func TestStaleEventDoesNotCompleteNewCommit(t *testing.T) {
19 o, _, commits := testOutput(t)
20 if err := o.commitFrame(70, nil, false, false, pendingFrame{}); err != nil {
21 t.Fatal(err)
22 }
23 old := eventOf((*commits)[0])
24 o.frame.endPending() // a modeset dropped the commit; its event is queued
25 if err := o.commitFrame(71, nil, false, false, pendingFrame{}); err != nil {
26 t.Fatal(err)
27 }
28 if o.completed(old, nil) || !o.frame.pendingCommit() {
29 t.Fatal("stale event completed the new commit")
30 }
31 if !o.completed(eventOf((*commits)[1]), nil) || o.frame.pendingCommit() {
32 t.Fatal("own event did not complete the commit")
33 }
34}
35
36// After EBUSY no commit of ours is known: the next event of the CRTC ends
37// the wait, then the frame commits.
38func TestBusyFrameCommitsAfterEvent(t *testing.T) {
39 o, _, commits := testOutput(t, unix.EBUSY)
40 enabled := true
41 if err := o.commitFrame(70, nil, false, false, pendingFrame{}); !o.commitFailed(err, &enabled) {
42 t.Fatalf("EBUSY not handled: %v", err)
43 }
44 if !o.completed(flipEvent{crtc: tCrtc, user: 12345 << userKindBits}, nil) || o.frame.pendingCommit() {
45 t.Fatal("EBUSY wait not ended by the CRTC's event")
46 }
47 if err := o.commitFrame(70, nil, false, false, pendingFrame{}); err != nil || len(*commits) != 2 || !o.frame.pendingCommit() {
48 t.Fatalf("frame after EBUSY: %v, %d commits", err, len(*commits))
49 }
50}
51
52// A cursor image change writes only an image the plane neither shows nor
53// is about to show; with both busy it waits for the event.
54func TestCursorImageNeverOverwritesScreen(t *testing.T) {
55 o, _, _ := testOutput(t)
56 c := o.cursor
57 r := portsmocks.NewMockRenderer(t)
58 var written []int
59 r.EXPECT().WriteCursor(mock.Anything, mock.Anything, 1, 1).RunAndReturn(func(i int, _ []byte, _, _ int) error {
60 written = append(written, i)
61 return nil
62 })
63 px := []byte{1, 2, 3, 4}
64 // Image 0 on screen: two changes before a commit both go to image 1.
65 c.screen, c.cur, c.image = 90, 0, true
66 for range 2 {
67 if err := c.setImage(r, px, 1, 1, 0, 0); err != nil {
68 t.Fatal(err)
69 }
70 }
71 if len(written) != 2 || written[0] != 1 || written[1] != 1 {
72 t.Fatalf("written %v", written)
73 }
74 // Image 1 in flight, image 0 on screen: nothing is free.
75 c.committed(cursorState{on: true, fb: 91})
76 if err := c.setImage(r, px, 1, 1, 0, 0); err != nil || len(written) != 2 || c.later == nil {
77 t.Fatalf("wrote a busy image: %v %v", err, written)
78 }
79 // The flip frees image 0.
80 c.landed()
81 if changed, err := c.flushLater(r); !changed || err != nil || len(written) != 3 || written[2] != 0 || c.cur != 0 {
82 t.Fatalf("after flip: %v %v %v cur %d", changed, err, written, c.cur)
83 }
84}
85
86// KMS refusing the cursor plane turns the hardware cursor off and the
87// frame commits without it; the output keeps running.
88func TestCursorRefusedFrameCommitsWithout(t *testing.T) {
89 o, _, commits := testOutput(t, unix.EINVAL, unix.EINVAL)
90 o.cursor.image = true
91 o.cursor.Move(5, 5)
92 if err := o.commitFrame(70, nil, false, false, pendingFrame{}); err != nil {
93 t.Fatal(err)
94 }
95 if !o.cursor.off || len(*commits) != 3 || (*commits)[1].flags != atomicTestOnly {
96 t.Fatalf("off=%v commits %d", o.cursor.off, len(*commits))
97 }
98 if v, _ := (*commits)[2].req.value(tCursor, pFB); v != 0 {
99 t.Fatalf("retry shows cursor fb %d", v)
100 }
101}
102
103// A refused cursor is not blamed on the client buffer.
104func TestCursorRefusedDoesNotBlameScanoutBuffer(t *testing.T) {
105 o, _, _ := testOutput(t, unix.EINVAL, unix.EINVAL)
106 o.cursor.image = true
107 o.cursor.Move(5, 5)
108 o.clientFBs[9] = &clientFB{fbID: 80}
109 direct, err := o.commitScanout(80, ports.SurfaceContent{DMABuf: &ports.DMABuf{ID: 9}}, pendingFrame{})
110 if err != nil || !direct || o.clientFBs[9].failed != "" {
111 t.Fatalf("direct=%v err=%v failed=%q", direct, err, o.clientFBs[9].failed)
112 }
113}
114
115// tiledMod stands for a non-linear driver modifier in the image tests.
116const tiledMod = 0x200000000401b03
117
118// showImages validates the images with a TEST_ONLY modeset, retries with
119// single-plane driver images, then falls back to linear images when KMS
120// refuses both.
121func TestShowImagesTestsBeforeModeset(t *testing.T) {
122 o, k, commits := testOutput(t, unix.EINVAL, unix.EINVAL)
123 o.cursor = nil
124 r := portsmocks.NewMockRenderer(t)
125 buf := func() ports.DMABuf {
126 var b ports.DMABuf
127 for range 2 {
128 f, w, _ := os.Pipe()
129 w.Close()
130 b.Planes = append(b.Planes, ports.DMABufPlane{File: f})
131 }
132 b.Modifier = tiledMod
133 return b
134 }
135 linear := func() ports.DMABuf {
136 f, w, _ := os.Pipe()
137 w.Close()
138 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
139 }
140 tiled1 := func() ports.DMABuf {
141 b := linear()
142 b.Modifier = tiledMod
143 return b
144 }
145 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
146 r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
147 r.EXPECT().ExportTargets(2, []uint64(nil), true).Return([]ports.DMABuf{tiled1(), tiled1()}, nil).Once()
148 r.EXPECT().ExportTargets(2, []uint64{0}, false).Return([]ports.DMABuf{linear(), linear()}, nil).Once()
149 r.EXPECT().UseTarget(mock.Anything).Return()
150 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
151 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil)
152 k.EXPECT().rmFB(mock.Anything).Return(nil)
153 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
154 k.EXPECT().destroyBlob(mock.Anything).Return(nil)
155 if err := o.showImages(r, imagesDriver, nil); err != nil {
156 t.Fatal(err)
157 }
158 want := []uint32{atomicTestOnly | atomicAllowModes, atomicTestOnly | atomicAllowModes, atomicTestOnly | atomicAllowModes, atomicAllowModes}
159 if len(*commits) != len(want) {
160 t.Fatalf("%d commits", len(*commits))
161 }
162 for i, f := range want {
163 if (*commits)[i].flags != f {
164 t.Fatalf("commit %d flags %#x want %#x", i, (*commits)[i].flags, f)
165 }
166 }
167 if o.kind != imagesLinear {
168 t.Fatalf("kind %d", o.kind)
169 }
170}
171
172// A refused multi-plane (DCC) driver image is retried with single-plane
173// driver images before linear; every plane file of every buffer is closed
174// once the framebuffers exist.
175func TestShowImagesRetriesSinglePlaneBeforeLinear(t *testing.T) {
176 o, k, _ := testOutput(t, unix.EINVAL)
177 o.cursor = nil
178 var files []*os.File
179 buf := func(planes int) ports.DMABuf {
180 b := ports.DMABuf{Modifier: tiledMod}
181 for range planes {
182 f, w, _ := os.Pipe()
183 w.Close()
184 files = append(files, f)
185 b.Planes = append(b.Planes, ports.DMABufPlane{File: f})
186 }
187 return b
188 }
189 r := portsmocks.NewMockRenderer(t)
190 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
191 r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{buf(3), buf(3)}, nil).Once()
192 r.EXPECT().ExportTargets(2, []uint64(nil), true).Return([]ports.DMABuf{buf(1), buf(1)}, nil).Once()
193 r.EXPECT().UseTarget(mock.Anything).Return()
194 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
195 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil)
196 k.EXPECT().rmFB(mock.Anything).Return(nil)
197 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
198 k.EXPECT().destroyBlob(mock.Anything).Return(nil)
199 if err := o.showImages(r, imagesDriver, nil); err != nil {
200 t.Fatal(err)
201 }
202 if o.kind != imagesSinglePlane {
203 t.Fatalf("kind %d", o.kind)
204 }
205 if len(files) != 8 {
206 t.Fatalf("%d plane files", len(files))
207 }
208 for i, f := range files {
209 if err := f.Close(); !errors.Is(err, os.ErrClosed) {
210 t.Fatalf("plane file %d left open (close: %v)", i, err)
211 }
212 }
213}
214
215// SDR images are asked for the modifiers the primary plane lists for
216// XRGB8888, in IN_FORMATS order, never the renderer's own choice: RADV's
217// preferred DCC variant (pipe-aligned) is not one amdgpu lists, and KMS
218// refuses a framebuffer with a modifier the plane does not advertise
219// (ADDFB2: EINVAL). Other formats' modifiers are not offered.
220func TestShowImagesOffersThePrimaryPlanesXRGBModifiers(t *testing.T) {
221 const dccListed, tiled, other = 0x200000000563b03, 0x200000000401b03, 0x200000000401903
222 o, k, _ := testOutput(t)
223 o.cursor = nil
224 o.primary.formats = []ports.DMABufFormat{
225 {Format: fourccXRGB, Modifier: dccListed}, {Format: fourccXR30, Modifier: other},
226 {Format: fourccXRGB, Modifier: tiled}, {Format: fourccXRGB, Modifier: 0},
227 }
228 buf := func() ports.DMABuf {
229 f, w, _ := os.Pipe()
230 w.Close()
231 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
232 }
233 r := portsmocks.NewMockRenderer(t)
234 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
235 r.EXPECT().ExportTargets(2, []uint64{dccListed, tiled, 0}, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
236 r.EXPECT().UseTarget(mock.Anything).Return()
237 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
238 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil)
239 k.EXPECT().rmFB(mock.Anything).Return(nil).Maybe()
240 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
241 k.EXPECT().destroyBlob(mock.Anything).Return(nil)
242 if err := o.showImages(r, imagesDriver, nil); err != nil {
243 t.Fatal(err)
244 }
245 if o.kind != imagesDriver {
246 t.Fatalf("kind %d", o.kind)
247 }
248}
249
250// When the renderer has no modifier in common with the plane, the images
251// fall back to single-plane ones with the same list, then to linear.
252func TestShowImagesNoCommonModifierFallsBackWithThePlanesList(t *testing.T) {
253 const listed = 0x200000000563b03
254 o, k, _ := testOutput(t)
255 o.cursor = nil
256 o.primary.formats = []ports.DMABufFormat{{Format: fourccXRGB, Modifier: listed}, {Format: fourccXRGB, Modifier: 0}}
257 buf := func() ports.DMABuf {
258 f, w, _ := os.Pipe()
259 w.Close()
260 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
261 }
262 r := portsmocks.NewMockRenderer(t)
263 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
264 none := errors.New("no XRGB8888 modifier both the device and the display accept")
265 r.EXPECT().ExportTargets(2, []uint64{listed, 0}, false).Return(nil, none).Once()
266 r.EXPECT().ExportTargets(2, []uint64{listed, 0}, true).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
267 r.EXPECT().UseTarget(mock.Anything).Return()
268 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
269 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil)
270 k.EXPECT().rmFB(mock.Anything).Return(nil).Maybe()
271 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
272 k.EXPECT().destroyBlob(mock.Anything).Return(nil)
273 if err := o.showImages(r, imagesDriver, nil); err != nil {
274 t.Fatal(err)
275 }
276 if o.kind != imagesSinglePlane {
277 t.Fatalf("kind %d", o.kind)
278 }
279}
280
281// A plane without IN_FORMATS lists every format with modifier 0 only
282// (readPlanes): the images are linear straight away, with no driver or
283// single-plane attempt KMS would refuse.
284func TestShowImagesLinearOnlyPlaneExportsLinearFirst(t *testing.T) {
285 o, k, commits := testOutput(t)
286 o.cursor = nil
287 o.primary.formats = []ports.DMABufFormat{{Format: fourccXRGB, Modifier: 0}}
288 r := portsmocks.NewMockRenderer(t)
289 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
290 // No ExportTargets(..., true) and no second call: the mock fails on them.
291 r.EXPECT().ExportTargets(2, []uint64{0}, false).RunAndReturn(func(int, []uint64, bool) ([]ports.DMABuf, error) {
292 return []ports.DMABuf{linearBuf(), linearBuf()}, nil
293 }).Once()
294 r.EXPECT().UseTarget(mock.Anything).Return()
295 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
296 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil)
297 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
298 k.EXPECT().destroyBlob(mock.Anything).Return(nil)
299 if err := o.showImages(r, imagesDriver, nil); err != nil {
300 t.Fatal(err)
301 }
302 if o.kind != imagesDriver || o.imageMod != 0 || len(*commits) != 2 {
303 t.Fatalf("kind %d, modifier %#x, %d commits", o.kind, o.imageMod, len(*commits))
304 }
305}
306
307// KMS refusing images that are already linear ends the attempt with that
308// error: the single-plane and linear kinds would export the same images.
309func TestShowImagesRefusedLinearImagesAreNotExportedAgain(t *testing.T) {
310 o, k, commits := testOutput(t, unix.EINVAL)
311 o.cursor = nil
312 o.primary.formats = []ports.DMABufFormat{{Format: fourccXRGB, Modifier: 0}}
313 r := portsmocks.NewMockRenderer(t)
314 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
315 r.EXPECT().ExportTargets(2, []uint64{0}, false).RunAndReturn(func(int, []uint64, bool) ([]ports.DMABuf, error) {
316 return []ports.DMABuf{linearBuf(), linearBuf()}, nil
317 }).Once()
318 r.EXPECT().UseTarget(mock.Anything).Return()
319 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
320 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil)
321 k.EXPECT().rmFB(mock.Anything).Return(nil)
322 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
323 k.EXPECT().destroyBlob(mock.Anything).Return(nil)
324 err := o.showImages(r, imagesDriver, nil)
325 if !errors.Is(err, unix.EINVAL) {
326 t.Fatalf("error %v, want the KMS refusal", err)
327 }
328 if len(*commits) != 1 || o.fbs != [2]uint32{} {
329 t.Fatalf("%d commits, fbs %v", len(*commits), o.fbs)
330 }
331}
332
333// linearBuf is a one-plane linear dmabuf over a closed pipe.
334func linearBuf() ports.DMABuf {
335 f, w, _ := os.Pipe()
336 w.Close()
337 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
338}
339
340// Refused driver images that already had one plane are not retried as
341// single-plane images: the next export is linear.
342func TestShowImagesSkipsSinglePlaneWhenDriverImagesHadOnePlane(t *testing.T) {
343 o, k, commits := testOutput(t, unix.EINVAL)
344 o.cursor = nil
345 buf := func() ports.DMABuf {
346 f, w, _ := os.Pipe()
347 w.Close()
348 return ports.DMABuf{Modifier: tiledMod, Planes: []ports.DMABufPlane{{File: f}}}
349 }
350 linear := func() ports.DMABuf {
351 f, w, _ := os.Pipe()
352 w.Close()
353 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
354 }
355 r := portsmocks.NewMockRenderer(t)
356 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
357 r.EXPECT().ExportTargets(2, []uint64(nil), false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
358 r.EXPECT().ExportTargets(2, []uint64{0}, false).Return([]ports.DMABuf{linear(), linear()}, nil).Once()
359 r.EXPECT().UseTarget(mock.Anything).Return()
360 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
361 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil)
362 k.EXPECT().rmFB(mock.Anything).Return(nil)
363 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
364 k.EXPECT().destroyBlob(mock.Anything).Return(nil)
365 if err := o.showImages(r, imagesDriver, nil); err != nil {
366 t.Fatal(err)
367 }
368 if o.kind != imagesLinear || len(*commits) != 3 {
369 t.Fatalf("kind %d, %d commits", o.kind, len(*commits))
370 }
371}
372
373// Enabling an output whose images were not made yet (started switched
374// away) tests them and modesets once.
375func TestEnableAfterStartAwayModesetsOnce(t *testing.T) {
376 o, k, commits, commitMu := testOutputMu(t)
377 o.cursor, o.tearing, o.fbs = nil, false, [2]uint32{}
378 r := portsmocks.NewMockRenderer(t)
379 buf := func() ports.DMABuf {
380 f, w, _ := os.Pipe()
381 w.Close()
382 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
383 }
384 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
385 r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
386 r.EXPECT().UseTarget(mock.Anything).Return()
387 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
388 r.EXPECT().Close().Return().Once()
389 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil).Twice()
390 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
391 k.EXPECT().destroyBlob(mock.Anything).Return(nil)
392 k.EXPECT().rmFB(mock.Anything).Return(nil).Maybe()
393 active := make(chan bool, 1)
394 active <- false
395 ctx, cancel := context.WithCancel(context.Background())
396 done := make(chan error, 1)
397 go func() {
398 done <- o.Run(ctx, func(int, int) (ports.Renderer, error) { return r, nil }, nil, active, nil, nil, nil, make(chan ports.OutputPresented, 1), nil, nil)
399 }()
400 count := func(flags uint32) int {
401 commitMu.Lock()
402 defer commitMu.Unlock()
403 n := 0
404 for _, c := range *commits {
405 if c.flags == flags {
406 n++
407 }
408 }
409 return n
410 }
411 time.Sleep(20 * time.Millisecond)
412 if n := count(atomicAllowModes); n != 0 {
413 t.Fatalf("%d modesets while away", n)
414 }
415 active <- true
416 waitFor(t, func() bool { return count(atomicAllowModes) == 1 })
417 cancel()
418 if err := <-done; err != nil {
419 t.Fatal(err)
420 }
421 if n := count(atomicTestOnly | atomicAllowModes); n != 1 {
422 t.Fatalf("%d test modesets", n)
423 }
424}
425
426// Close without a mode to restore turns the CRTC off.
427func TestCloseWithoutSavedModeDisablesCrtc(t *testing.T) {
428 o, k, commits := testOutput(t)
429 k.EXPECT().rmFB(mock.Anything).Return(nil)
430 o.Close()
431 c := (*commits)[0]
432 for _, p := range []struct{ obj, prop uint32 }{{tCrtc, pActive}, {tCrtc, pMode}, {tPrimary, pFB}, {tConn, pConnCrtc}} {
433 if v, ok := c.req.value(p.obj, p.prop); !ok || v != 0 {
434 t.Fatalf("object %d prop %d = %d %v", p.obj, p.prop, v, ok)
435 }
436 }
437}
438
439// Close restores the saved mode and gives the connector its CRTC back.
440func TestCloseRestoresSavedMode(t *testing.T) {
441 o, k, commits := testOutput(t)
442 o.saved = modeCrtc{crtcID: tCrtc, fbID: 5, modeValid: 1, mode: modeInfo{HDisplay: 200, VDisplay: 100}}
443 k.EXPECT().createBlob(mock.Anything).Return(98, nil).Once()
444 k.EXPECT().destroyBlob(uint32(98)).Return(nil).Once()
445 k.EXPECT().rmFB(mock.Anything).Return(nil)
446 o.Close()
447 c := (*commits)[0]
448 if v, _ := c.req.value(tConn, pConnCrtc); v != tCrtc {
449 t.Fatalf("connector crtc %d", v)
450 }
451 if v, _ := c.req.value(tPrimary, pFB); v != 5 {
452 t.Fatalf("primary fb %d", v)
453 }
454}
455
456// With the cursor off no image is loaded (no warning per change).
457func TestCursorOffSkipsLoads(t *testing.T) {
458 o, _, _ := testOutput(t)
459 o.cursor.off = true
460 loaded := false
461 o.setCursor(nil, func(ports.CursorChange, float64, ports.BufferTransform, int) (ports.CursorImage, error) {
462 loaded = true
463 return ports.CursorImage{}, nil
464 }, ports.CursorChange{}, 1, 0)
465 if loaded || o.cursor.desired().on {
466 t.Fatal("cursor used while off")
467 }
468}
469
470// Content read while only a cursor commit is in flight is reported when
471// that commit's event arrives, not held until a later frame.
472func TestRunReportsSeenAfterCursorCommit(t *testing.T) {
473 o, k, commits, commitMu := testOutputMu(t)
474 o.tearing, o.cursor.fbs = false, [2]uint32{}
475 flips := make(chan flipEvent, 1)
476 o.flipped = flips
477 r := portsmocks.NewMockRenderer(t)
478 buf := func() ports.DMABuf {
479 f, w, _ := os.Pipe()
480 w.Close()
481 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
482 }
483 r.EXPECT().CursorBuffers(64).Return([2]ports.DMABuf{buf(), buf()}, nil).Once()
484 r.EXPECT().WriteCursor(mock.Anything, mock.Anything, 1, 1).Return(nil)
485 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
486 r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
487 r.EXPECT().UseTarget(mock.Anything).Return()
488 r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil)
489 r.EXPECT().Close().Return().Once()
490 k.EXPECT().addFB(mock.Anything, uint32(fourccARGB)).Return(90, nil).Once()
491 k.EXPECT().addFB(mock.Anything, uint32(fourccARGB)).Return(91, nil).Once()
492 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil).Twice()
493 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
494 k.EXPECT().destroyBlob(mock.Anything).Return(nil).Maybe()
495 k.EXPECT().rmFB(mock.Anything).Return(nil).Maybe()
496 load := func(ports.CursorChange, float64, ports.BufferTransform, int) (ports.CursorImage, error) {
497 return ports.CursorImage{Pixels: []byte{1, 2, 3, 4}, W: 1, H: 1}, nil
498 }
499 last := func(kind uint64) (commitRec, bool) {
500 commitMu.Lock()
501 defer commitMu.Unlock()
502 for i := len(*commits) - 1; i >= 0; i-- {
503 if c := (*commits)[i]; c.user&3 == kind {
504 return c, true
505 }
506 }
507 return commitRec{}, false
508 }
509 scenes := make(chan ports.Scene, 1)
510 contents := make(chan ports.SurfaceContent, 1)
511 presented := make(chan ports.OutputPresented, 8)
512 ctx, cancel := context.WithCancel(context.Background())
513 done := make(chan error, 1)
514 go func() {
515 done <- o.Run(ctx, func(int, int) (ports.Renderer, error) { return r, nil }, load, make(chan bool), scenes, contents, nil, presented, nil, nil)
516 }()
517 scenes <- ports.Scene{OutputWidth: 200, OutputHeight: 100, Scale: 1}
518 var frame commitRec
519 waitFor(t, func() bool { var ok bool; frame, ok = last(userFrame); return ok })
520 flips <- eventOf(frame)
521 <-presented // the frame's flip
522 o.cursor.Move(3, 3)
523 var cur commitRec
524 waitFor(t, func() bool { var ok bool; cur, ok = last(userState); return ok })
525 // A window this output does not show commits while the cursor flies.
526 contents <- ports.SurfaceContent{ID: 7, Seq: 2, SHM: &ports.SHMBuffer{}}
527 time.Sleep(20 * time.Millisecond)
528 flips <- eventOf(cur)
529 select {
530 case p := <-presented:
531 if p.Seen[7] != 2 {
532 t.Fatalf("report %+v", p)
533 }
534 case <-time.After(time.Second):
535 t.Fatal("Seen not reported after the cursor commit")
536 }
537 cancel()
538 if err := <-done; err != nil {
539 t.Fatal(err)
540 }
541}
542
543// Frame fences are closed on every path: flipped, EBUSY (re-rendered
544// after the event) and lost master (re-rendered after the modeset). The
545// fd count is unchanged after many frames.
546func TestRunClosesFrameFences(t *testing.T) {
547 fds := func() int {
548 ents, err := os.ReadDir("/proc/self/fd")
549 if err != nil {
550 t.Skip(err)
551 }
552 return len(ents)
553 }
554 const rounds = 30
555 // Commit results: modeset test and modeset, then per round
556 // EBUSY + flip, or lost master + modeset + flip, or a plain flip.
557 errs := []error{nil, nil}
558 for i := range rounds {
559 switch i % 3 {
560 case 0:
561 errs = append(errs, unix.EBUSY, nil)
562 case 1:
563 errs = append(errs, unix.EACCES, nil, nil)
564 default:
565 errs = append(errs, nil)
566 }
567 }
568 o, k, commits, commitMu := testOutputMu(t, errs...)
569 o.cursor, o.tearing, o.fbs = nil, false, [2]uint32{}
570 flips := make(chan flipEvent, 1)
571 o.flipped = flips
572 r := portsmocks.NewMockRenderer(t)
573 buf := func() ports.DMABuf {
574 f, w, _ := os.Pipe()
575 w.Close()
576 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
577 }
578 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
579 r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
580 r.EXPECT().UseTarget(mock.Anything).Return()
581 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
582 f, w, _ := os.Pipe()
583 _, _ = w.Write([]byte{1})
584 w.Close()
585 return f, nil
586 })
587 r.EXPECT().Close().Return().Once()
588 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil).Twice()
589 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
590 k.EXPECT().destroyBlob(mock.Anything).Return(nil)
591 k.EXPECT().rmFB(mock.Anything).Return(nil).Maybe()
592 before := fds()
593 active := make(chan bool, 1)
594 scenes := make(chan ports.Scene, 1)
595 presented := make(chan ports.OutputPresented, 4*rounds)
596 ctx, cancel := context.WithCancel(context.Background())
597 done := make(chan error, 1)
598 go func() {
599 done <- o.Run(ctx, func(int, int) (ports.Renderer, error) { return r, nil }, nil, active, scenes, nil, nil, presented, nil, nil)
600 }()
601 count := func() int {
602 commitMu.Lock()
603 defer commitMu.Unlock()
604 return len(*commits)
605 }
606 last := func() commitRec {
607 commitMu.Lock()
608 defer commitMu.Unlock()
609 return (*commits)[len(*commits)-1]
610 }
611 waitFor(t, func() bool { return count() == 2 })
612 for i := range rounds {
613 n := count()
614 scenes <- ports.Scene{Seq: uint64(i)}
615 switch i % 3 {
616 case 0:
617 waitFor(t, func() bool { return count() == n+1 })
618 flips <- flipEvent{crtc: tCrtc} // ends the EBUSY wait
619 waitFor(t, func() bool { return count() == n+2 })
620 case 1:
621 waitFor(t, func() bool { return count() == n+1 })
622 active <- true
623 waitFor(t, func() bool { return count() == n+3 })
624 default:
625 waitFor(t, func() bool { return count() == n+1 })
626 }
627 flips <- eventOf(last())
628 }
629 cancel()
630 if err := <-done; err != nil {
631 t.Fatal(err)
632 }
633 if after := fds(); after > before {
634 t.Fatalf("fds %d -> %d", before, after)
635 }
636}