1package surfaces
2
3import (
4 "os"
5 "testing"
6
7 portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
8 "github.com/bnema/neferwl/internal/ports"
9)
10
11func shows(ids ...ports.WindowID) ports.Scene {
12 s := ports.Scene{OutputWidth: 100, OutputHeight: 100}
13 for _, id := range ids {
14 s.Windows = append(s.Windows, ports.SceneWindow{ID: id, Rect: ports.Rect{W: 10, H: 10}})
15 }
16 return s
17}
18
19// An empty content of a window the scene draws keeps the previous one with
20// its Acquire fence dropped; the content goes once a scene no longer lists
21// the window.
22func TestEmptyContentKeptWhileShown(t *testing.T) {
23 tb := New()
24 r, w, err := os.Pipe()
25 if err != nil {
26 t.Fatal(err)
27 }
28 defer r.Close()
29 defer w.Close()
30 full := ports.SurfaceContent{ID: 1, Seq: 4, Width: 10, Height: 10, DMABuf: &ports.DMABuf{ID: 7}, Acquire: r}
31 tb.Update(full, shows(1))
32 tb.Update(ports.SurfaceContent{ID: 1, Seq: 5}, shows(1))
33 kept, ok := tb.Map()[1]
34 if !ok || kept.DMABuf != full.DMABuf || kept.Seq != 4 {
35 t.Fatalf("kept %+v, want the previous content", kept)
36 }
37 if kept.Acquire != nil {
38 t.Fatal("the kept content keeps its Acquire fence")
39 }
40 if !tb.Kept(1) {
41 t.Fatal("Kept false for a content drawn after its empty one")
42 }
43 // A scene still listing it keeps it; one without it drops it.
44 tb.Prune(shows(1, 2))
45 if _, ok := tb.Map()[1]; !ok {
46 t.Fatal("dropped while still listed")
47 }
48 tb.Prune(shows(2))
49 if _, ok := tb.Map()[1]; ok || tb.Kept(1) {
50 t.Fatal("not dropped once unlisted")
51 }
52}
53
54// An empty content of a window the scene does not draw drops it at once.
55func TestEmptyContentOfUnshownWindowDropped(t *testing.T) {
56 tb := New()
57 tb.Update(ports.SurfaceContent{ID: 1, SHM: &ports.SHMBuffer{Pool: 1}}, shows(1))
58 tb.Update(ports.SurfaceContent{ID: 1}, shows(2))
59 if _, ok := tb.Map()[1]; ok || tb.Kept(1) {
60 t.Fatal("content of an unshown window kept")
61 }
62 // Hidden counts as not drawn.
63 tb.Update(ports.SurfaceContent{ID: 3, SHM: &ports.SHMBuffer{Pool: 3}}, shows())
64 hidden := ports.Scene{OutputWidth: 100, OutputHeight: 100, Windows: []ports.SceneWindow{{ID: 3, Hidden: true, Rect: ports.Rect{W: 10, H: 10}}}}
65 tb.Update(ports.SurfaceContent{ID: 3}, hidden)
66 if _, ok := tb.Map()[3]; ok {
67 t.Fatal("content of a hidden window kept")
68 }
69}
70
71// A new content replaces a kept one and the window is no longer kept (the
72// client mapped again).
73func TestNewContentReplacesKept(t *testing.T) {
74 tb := New()
75 tb.Update(ports.SurfaceContent{ID: 1, Seq: 1, SHM: &ports.SHMBuffer{Pool: 1}}, shows(1))
76 tb.Update(ports.SurfaceContent{ID: 1, Seq: 2}, shows(1))
77 tb.Update(ports.SurfaceContent{ID: 1, Seq: 3, SHM: &ports.SHMBuffer{Pool: 2}}, shows(1))
78 if c := tb.Map()[1]; c.Seq != 3 || c.SHM.Pool != 2 || tb.Kept(1) {
79 t.Fatalf("after a new content: %+v kept %t", c, tb.Kept(1))
80 }
81 // The map handed out is the table's own: the renderer sees updates.
82 m := tb.Map()
83 tb.Update(ports.SurfaceContent{ID: 2, Seq: 1, SHM: &ports.SHMBuffer{Pool: 9}}, shows(1, 2))
84 if _, ok := m[2]; !ok {
85 t.Fatal("Map is a copy")
86 }
87}
88
89// Update and Prune allocate nothing in steady state.
90func TestUpdateAllocations(t *testing.T) {
91 tb := New()
92 s := shows(1, 2)
93 c := ports.SurfaceContent{ID: 1, Seq: 1, SHM: &ports.SHMBuffer{Pool: 1}}
94 tb.Update(c, s)
95 if n := testing.AllocsPerRun(100, func() {
96 c.Seq++
97 tb.Update(c, s)
98 tb.Prune(s)
99 }); n != 0 {
100 t.Fatalf("%v allocations per update", n)
101 }
102}
103
104// The kept content's children lose their Acquire fences too, on a copy:
105// wayland's own slice (shared with its publication) is left alone.
106func TestKeptChildrenFencesDroppedOnACopy(t *testing.T) {
107 tb := New()
108 r, w, err := os.Pipe()
109 if err != nil {
110 t.Fatal(err)
111 }
112 defer r.Close()
113 defer w.Close()
114 children := []ports.Subsurface{{SurfaceContent: ports.SurfaceContent{DMABuf: &ports.DMABuf{ID: 2}, Acquire: r}}}
115 full := ports.SurfaceContent{ID: 1, Seq: 1, SHM: &ports.SHMBuffer{Pool: 1}, Children: children}
116 tb.Update(full, shows(1))
117 tb.Update(ports.SurfaceContent{ID: 1, Seq: 2}, shows(1))
118 kept := tb.Map()[1]
119 if len(kept.Children) != 1 || kept.Children[0].Acquire != nil || kept.Children[0].DMABuf != children[0].DMABuf {
120 t.Fatalf("kept children %+v", kept.Children)
121 }
122 if children[0].Acquire != r {
123 t.Fatal("wayland's slice was modified")
124 }
125}
126
127// Both backends report a window shown only when the scene draws it and its
128// content is not kept.
129func TestShowsSnapshotSkipsKept(t *testing.T) {
130 kept := func(id ports.WindowID) bool { return id == 2 }
131 var snap ShowsSnapshot
132 got := snap.FromSeen(shows(1, 2), map[ports.WindowID]uint64{1: 3, 2: 4, 3: 5}, kept)
133 if len(got) != 1 || got[1] != 3 {
134 t.Fatalf("shown from seen %v", got)
135 }
136 var fromContents ShowsSnapshot
137 contents := map[ports.WindowID]ports.SurfaceContent{1: {ID: 1, Seq: 3}, 2: {ID: 2, Seq: 4}, 3: {ID: 3, Seq: 5}}
138 got = fromContents.FromContents(shows(1, 2), contents, kept)
139 if len(got) != 1 || got[1] != 3 {
140 t.Fatalf("shown from contents %v", got)
141 }
142 if !Shown(shows(2), 2, nil) {
143 t.Fatal("nil kept counts nothing as kept")
144 }
145}
146
147// An unchanged snapshot is reused without allocating; a changed one never
148// mutates a map already handed out.
149func TestShowsSnapshotImmutable(t *testing.T) {
150 var snap ShowsSnapshot
151 scene := shows(1)
152 seen := map[ports.WindowID]uint64{1: 1, 2: 2}
153 first := snap.FromSeen(scene, seen, nil)
154 if allocs := testing.AllocsPerRun(100, func() { snap.FromSeen(scene, seen, nil) }); allocs != 0 {
155 t.Errorf("unchanged shows: %.1f allocs, want 0", allocs)
156 }
157 seen[1] = 3
158 second := snap.FromSeen(scene, seen, nil)
159 if first[1] != 1 || second[1] != 3 {
160 t.Fatalf("snapshot mutated: old %v new %v", first, second)
161 }
162}
163
164// Apply takes the batch into the table, advances seen, reports what needs a
165// frame or a report, then resets the batch before giving it back.
166func TestApply(t *testing.T) {
167 tb := New()
168 seen := map[ports.WindowID]uint64{1: 5}
169 b := &ports.ContentBatch{Items: []ports.SurfaceContent{
170 {ID: 1, Seq: 4, SHM: &ports.SHMBuffer{Pool: 1}}, // older than seen
171 {ID: 2, Seq: 1, SHM: &ports.SHMBuffer{Pool: 2}},
172 }}
173 ret := portsmocks.NewMockContentReturn(t)
174 ret.EXPECT().Return(b).Run(func(got *ports.ContentBatch) {
175 if len(got.Items) != 0 {
176 t.Errorf("batch returned before its reset: %d items", len(got.Items))
177 }
178 }).Once()
179 dirty, changed := tb.Apply(b, shows(2), seen, ret)
180 if !dirty || !changed {
181 t.Fatalf("dirty %v changed %v, want both", dirty, changed)
182 }
183 if seen[1] != 5 || seen[2] != 1 {
184 t.Fatalf("seen %v", seen)
185 }
186 if len(tb.Map()) != 2 {
187 t.Fatalf("table %v", tb.Map())
188 }
189
190 // A window no scene draws and no newer Seq: nothing to do.
191 b = &ports.ContentBatch{Items: []ports.SurfaceContent{{ID: 1, Seq: 5, SHM: &ports.SHMBuffer{Pool: 1}}}}
192 ret.EXPECT().Return(b).Once()
193 dirty, changed = tb.Apply(b, shows(2), seen, ret)
194 if dirty || changed {
195 t.Fatalf("dirty %v changed %v, want neither", dirty, changed)
196 }
197}