1package wayland
2
3import (
4 "testing"
5 "time"
6
7 "github.com/bnema/go-wayland-bindings/server/wayland"
8 "github.com/bnema/go-wayland-bindings/server/xdgshell"
9 "github.com/bnema/neferwl/internal/ports"
10 "golang.org/x/sys/unix"
11)
12
13// Damage reaches the renderer in buffer pixels, per content, with the
14// window's recent history; a new buffer without damage is a full change.
15func TestDamageHistory(t *testing.T) {
16 s, events, _, contents, dir := contentServer(t)
17 c := protocolClient(t, s, dir)
18 comp := bindVersion(t, c, "wl_compositor", 4)
19 wm := bindProtocol(t, c, "xdg_wm_base")
20 shm := bindProtocol(t, c, "wl_shm")
21 registerProtocol(t, c, shm)
22 surf, xdg, top := c.AllocateID(), c.AllocateID(), c.AllocateID()
23 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, surf)
24 registerProtocol(t, c, surf)
25 requestProtocol(t, c, wm, xdgshell.WmBaseRequestGetXdgSurface, xdg, surf)
26 serials := make(chan uint32, 8)
27 xp := &configureProxy{serial: serials}
28 xp.SetID(xdg)
29 registerWireProxy(c, xp)
30 registerProtocol(t, c, top)
31 requestProtocol(t, c, xdg, xdgshell.SurfaceRequestGetToplevel, top)
32 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
33 if err := c.Roundtrip(); err != nil {
34 t.Fatal(err)
35 }
36 requestProtocol(t, c, xdg, xdgshell.SurfaceRequestAckConfigure, <-serials)
37 fd, err := unix.MemfdCreate("damage", 0)
38 if err != nil {
39 t.Fatal(err)
40 }
41 defer unix.Close(fd)
42 if err := unix.Ftruncate(fd, 2*16*16*4); err != nil {
43 t.Fatal(err)
44 }
45 pool := c.AllocateID()
46 registerProtocol(t, c, pool)
47 if err := wireRequest(c, shm, uint16(wayland.ShmRequestCreatePool), []int{fd}, pool, int32(2*16*16*4)); err != nil {
48 t.Fatal(err)
49 }
50 bufs := [2]uint32{c.AllocateID(), c.AllocateID()}
51 for i, b := range bufs {
52 registerProtocol(t, c, b)
53 requestProtocol(t, c, pool, wayland.ShmPoolRequestCreateBuffer, b, int32(i*16*16*4), int32(16), int32(16), int32(64), uint32(0))
54 }
55 next := func() ports.SurfaceContent {
56 t.Helper()
57 if err := c.Roundtrip(); err != nil {
58 t.Fatal(err)
59 }
60 deadline := time.After(2 * time.Second)
61 for {
62 select {
63 case got := <-contents:
64 if got.Width == 16 {
65 return got
66 }
67 case <-deadline:
68 t.Fatal("no content")
69 }
70 }
71 }
72 // A new size: full.
73 requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, bufs[0], int32(0), int32(0))
74 requestProtocol(t, c, surf, wayland.SurfaceRequestDamageBuffer, int32(0), int32(0), int32(4), int32(4))
75 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
76 mapped(t, events, 2*time.Second)
77 first := next()
78 if h := first.DamageHistory; len(h) == 0 || h[len(h)-1].Seq != first.Seq || !h[len(h)-1].Full {
79 t.Fatalf("resize: %+v", h)
80 }
81 // Buffer and surface damage, clipped to the buffer.
82 requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, bufs[1], int32(0), int32(0))
83 requestProtocol(t, c, surf, wayland.SurfaceRequestDamageBuffer, int32(2), int32(3), int32(4), int32(5))
84 requestProtocol(t, c, surf, wayland.SurfaceRequestDamage, int32(14), int32(14), int32(1<<30), int32(1<<30))
85 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
86 second := next()
87 got, ok := second.DamageSince(first.Seq)
88 want := []ports.Rect{{X: 2, Y: 3, W: 4, H: 5}, {X: 14, Y: 14, W: 2, H: 2}}
89 if !ok || len(got) != 2 || got[0] != want[0] || got[1] != want[1] {
90 t.Fatalf("damage %v %v", got, ok)
91 }
92 // Same buffer size, new logical size (buffer scale): full.
93 requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, bufs[0], int32(0), int32(0))
94 requestProtocol(t, c, surf, wayland.SurfaceRequestSetBufferScale, int32(2))
95 requestProtocol(t, c, surf, wayland.SurfaceRequestDamageBuffer, int32(0), int32(0), int32(1), int32(1))
96 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
97 scaled := next()
98 if _, ok := scaled.DamageSince(second.Seq); ok {
99 t.Fatal("logical resize kept partial damage")
100 }
101 second = scaled
102 // No damage with a new buffer: full.
103 requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, bufs[0], int32(0), int32(0))
104 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
105 third := next()
106 if _, ok := third.DamageSince(second.Seq); ok {
107 t.Fatal("undamaged new buffer taken as unchanged")
108 }
109 // A subsurface with no buffer draws nothing: once its layout is
110 // applied (one full change), the root keeps its damage.
111 subc := bindProtocol(t, c, "wl_subcompositor")
112 child, sub := c.AllocateID(), c.AllocateID()
113 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
114 registerProtocol(t, c, child)
115 registerProtocol(t, c, sub)
116 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, surf)
117 requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, bufs[1], int32(0), int32(0))
118 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
119 third = next()
120 requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, bufs[0], int32(0), int32(0))
121 requestProtocol(t, c, surf, wayland.SurfaceRequestDamageBuffer, int32(1), int32(1), int32(2), int32(2))
122 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
123 fourth := next()
124 if got, ok := fourth.DamageSince(third.Seq); !ok || len(got) != 1 || got[0] != (ports.Rect{X: 1, Y: 1, W: 2, H: 2}) {
125 t.Fatalf("empty subsurface: damage %v %v", got, ok)
126 }
127 // A synced child drawn, then hidden by a null attach the parent's
128 // commit applies: its old area must be redrawn, both times.
129 rootCommit := func(what string, wantChildren int) {
130 t.Helper()
131 prev := fourth
132 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
133 fourth = next()
134 if len(fourth.Children) != wantChildren {
135 t.Fatalf("%s: %d children, want %d", what, len(fourth.Children), wantChildren)
136 }
137 if got, ok := fourth.DamageSince(prev.Seq); ok {
138 t.Fatalf("%s kept partial damage %v", what, got)
139 }
140 }
141 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, bufs[1], int32(0), int32(0))
142 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
143 rootCommit("child appearing", 1)
144 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, uint32(0), int32(0), int32(0))
145 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
146 rootCommit("child hidden", 0)
147 // Destroying a child that draws nothing changes nothing on screen.
148 prev := fourth
149 requestProtocol(t, c, sub, wayland.SubsurfaceRequestDestroy)
150 fourth = next()
151 if got, ok := fourth.DamageSince(prev.Seq); !ok || len(got) != 0 {
152 t.Fatalf("empty child destroyed: damage %v %v", got, ok)
153 }
154 prev = fourth
155 requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, bufs[0], int32(0), int32(0))
156 requestProtocol(t, c, surf, wayland.SurfaceRequestDamageBuffer, int32(1), int32(1), int32(2), int32(2))
157 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
158 fourth = next()
159 if got, ok := fourth.DamageSince(prev.Seq); !ok || len(got) != 1 {
160 t.Fatalf("after empty child: damage %v %v", got, ok)
161 }
162 // Destroying a drawn child redraws its area.
163 child2, sub2 := c.AllocateID(), c.AllocateID()
164 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child2)
165 registerProtocol(t, c, child2)
166 registerProtocol(t, c, sub2)
167 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub2, child2, surf)
168 requestProtocol(t, c, child2, wayland.SurfaceRequestAttach, bufs[1], int32(0), int32(0))
169 requestProtocol(t, c, child2, wayland.SurfaceRequestCommit)
170 rootCommit("second child appearing", 1)
171 prev = fourth
172 requestProtocol(t, c, sub2, wayland.SubsurfaceRequestDestroy)
173 fourth = next()
174 if got, ok := fourth.DamageSince(prev.Seq); ok || len(fourth.Children) != 0 {
175 t.Fatalf("drawn child destroyed: damage %v %v, %d children", got, ok, len(fourth.Children))
176 }
177}