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