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