1package drm
2
3import (
4 "context"
5 "os"
6 "testing"
7 "testing/synctest"
8
9 portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
10 "github.com/bnema/neferwl/internal/ports"
11 "github.com/stretchr/testify/mock"
12)
13
14// A scene with Off turns the CRTC inactive and draws nothing; the next
15// scene without Off modesets (ACTIVE=1) and draws a frame.
16func TestRunOutputPower(t *testing.T) { synctest.Test(t, runOutputPower) }
17
18func runOutputPower(t *testing.T) {
19 o, k, commits, commitMu := testOutputMu(t)
20 o.tearing, o.cursor = false, nil
21 flips := make(chan flipEvent, 1)
22 o.flipped = flips
23 r := portsmocks.NewMockRenderer(t)
24 buf := func() ports.DMABuf {
25 f, w, _ := os.Pipe()
26 w.Close()
27 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
28 }
29 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
30 r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
31 r.EXPECT().UseTarget(mock.Anything).Return()
32 renders := make(chan struct{}, 8)
33 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
34 renders <- struct{}{}
35 return nil, nil
36 })
37 r.EXPECT().Close().Return().Once()
38 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil).Twice()
39 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
40 k.EXPECT().destroyBlob(mock.Anything).Return(nil).Maybe()
41 k.EXPECT().rmFB(mock.Anything).Return(nil).Maybe()
42 snapshot := func() []commitRec {
43 commitMu.Lock()
44 defer commitMu.Unlock()
45 return append([]commitRec(nil), *commits...)
46 }
47 active := func(c commitRec) (uint64, bool) { return c.req.value(tCrtc, pActive) }
48 scenes := make(chan ports.Scene, 1)
49 vt := make(chan bool)
50 ctx, cancel := context.WithCancel(context.Background())
51 done := make(chan error, 1)
52 go func() {
53 done <- o.Run(ctx, func(int, int) (ports.Renderer, error) { return r, nil }, nil, vt, scenes, nil, nil, nil, discardSink(t), nil, nil)
54 }()
55 on := ports.Scene{OutputWidth: 200, OutputHeight: 100, Scale: 1}
56 scenes <- on
57 <-renders
58 var frame commitRec
59 waitFor(t, func() bool {
60 for _, c := range snapshot() {
61 if c.user&3 == userFrame {
62 frame = c
63 return true
64 }
65 }
66 return false
67 })
68 flips <- eventOf(frame)
69
70 off := on
71 off.Off = true
72 scenes <- off
73 waitFor(t, func() bool {
74 cs := snapshot()
75 v, ok := active(cs[len(cs)-1])
76 return ok && v == 0
77 })
78 n := len(snapshot())
79 // Scenes while off draw nothing (renders before the power off drained).
80 for len(renders) > 0 {
81 <-renders
82 }
83 scenes <- off
84 synctest.Wait()
85 select {
86 case <-renders:
87 t.Fatal("rendered while off")
88 default:
89 }
90 if len(snapshot()) != n {
91 t.Fatal("committed while off")
92 }
93
94 // A VT switch back modesets while off: the CRTC stays inactive.
95 vt <- false
96 vt <- true
97 waitFor(t, func() bool { return len(snapshot()) > n })
98 synctest.Wait()
99 for _, c := range snapshot()[n:] {
100 if v, ok := active(c); ok && v != 0 && c.flags&atomicTestOnly == 0 {
101 t.Fatal("VT resume turned the display on")
102 }
103 }
104 n = len(snapshot())
105
106 scenes <- on
107 <-renders
108 waitFor(t, func() bool {
109 for _, c := range snapshot()[n:] {
110 if v, ok := active(c); ok && v == 1 {
111 return true
112 }
113 }
114 return false
115 })
116 cancel()
117 if err := <-done; err != nil {
118 t.Fatal(err)
119 }
120}
121
122// An output started off (it reconnected while turned off) never activates
123// its CRTC before a scene turns it on; its tearing probe, refused by an
124// inactive CRTC, waits until then.
125func TestRunOutputStartOff(t *testing.T) { synctest.Test(t, runOutputStartOff) }
126
127func runOutputStartOff(t *testing.T) {
128 o, k, commits, commitMu := testOutputMu(t)
129 o.tearing, o.cursor = true, nil
130 o.StartOff = true
131 ready := make(chan error, 1)
132 o.ready = ready
133 r := portsmocks.NewMockRenderer(t)
134 buf := func() ports.DMABuf {
135 f, w, _ := os.Pipe()
136 w.Close()
137 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
138 }
139 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
140 r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
141 r.EXPECT().UseTarget(mock.Anything).Return().Maybe()
142 renders := make(chan struct{}, 8)
143 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
144 renders <- struct{}{}
145 return nil, nil
146 }).Maybe()
147 r.EXPECT().Close().Return().Once()
148 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil).Twice()
149 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
150 k.EXPECT().destroyBlob(mock.Anything).Return(nil).Maybe()
151 k.EXPECT().rmFB(mock.Anything).Return(nil).Maybe()
152 snapshot := func() []commitRec {
153 commitMu.Lock()
154 defer commitMu.Unlock()
155 return append([]commitRec(nil), *commits...)
156 }
157 lit := func(cs []commitRec) bool {
158 for _, c := range cs {
159 if v, ok := c.req.value(tCrtc, pActive); ok && v != 0 && c.flags&atomicTestOnly == 0 {
160 return true
161 }
162 }
163 return false
164 }
165 probed := func(cs []commitRec) bool {
166 for _, c := range cs {
167 if c.flags == atomicTestOnly|flipAsyncFlag {
168 return true
169 }
170 }
171 return false
172 }
173 scenes := make(chan ports.Scene, 1)
174 ctx, cancel := context.WithCancel(context.Background())
175 done := make(chan error, 1)
176 go func() {
177 done <- o.Run(ctx, func(int, int) (ports.Renderer, error) { return r, nil }, nil, make(chan bool), scenes, nil, nil, nil, discardSink(t), nil, nil)
178 }()
179 if err := <-ready; err != nil {
180 t.Fatal(err)
181 }
182 for len(renders) > 0 { // the images are cleared at setup
183 <-renders
184 }
185 off := ports.Scene{OutputWidth: 200, OutputHeight: 100, Scale: 1, Off: true}
186 scenes <- off
187 synctest.Wait()
188 if lit(snapshot()) {
189 t.Fatal("a display started off lit up")
190 }
191 if probed(snapshot()) {
192 t.Fatal("tearing probed while off")
193 }
194 if len(renders) > 0 {
195 t.Fatal("rendered while off")
196 }
197 on := off
198 on.Off = false
199 scenes <- on
200 waitFor(t, func() bool { return lit(snapshot()) && probed(snapshot()) })
201 cancel()
202 if err := <-done; err != nil {
203 t.Fatal(err)
204 }
205}