1package wayland
2
3import (
4 "testing"
5 "time"
6
7 "github.com/bnema/go-wayland-bindings/server/tearingcontrol"
8 "github.com/bnema/go-wayland-bindings/server/wayland"
9 "github.com/bnema/neferwl/internal/ports"
10)
11
12// asyncContent waits for a content of window id with the given hint.
13func asyncContent(t *testing.T, contents <-chan ports.SurfaceContent, id ports.WindowID, async bool) {
14 t.Helper()
15 deadline := time.After(2 * time.Second)
16 for {
17 select {
18 case c := <-contents:
19 if c.ID == id && c.Async == async {
20 return
21 }
22 case <-deadline:
23 t.Fatalf("no content with async=%v", async)
24 }
25 }
26}
27
28// A subsurface carries its own hint in the window's content, even when a
29// commit changes only the hint after the tree was cached (Wine presents
30// the game in a subsurface that the output scans out).
31func TestTearingHintOnSubsurface(t *testing.T) {
32 s, events, _, contents, dir := contentServer(t)
33 c := protocolClient(t, s, dir)
34 w, root, _ := surfaceMapper(t, c, events)()
35 comp := bindProtocol(t, c, "wl_compositor")
36 subc := bindProtocol(t, c, "wl_subcompositor")
37 child := c.AllocateID()
38 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
39 registerProtocol(t, c, child)
40 sub := c.AllocateID()
41 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root)
42 registerProtocol(t, c, sub)
43 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, shmBuffer(t, c), int32(0), int32(0))
44 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
45 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
46 if err := c.Roundtrip(); err != nil {
47 t.Fatal(err)
48 }
49 childAsync := func(want bool) {
50 t.Helper()
51 deadline := time.After(2 * time.Second)
52 for {
53 select {
54 case got := <-contents:
55 if got.ID == w.ID && len(got.Children) == 1 && got.Children[0].Async == want {
56 if got.Async {
57 t.Fatal("child hint leaked to the root")
58 }
59 return
60 }
61 case <-deadline:
62 t.Fatalf("no content with child async=%v", want)
63 }
64 }
65 }
66 childAsync(false)
67
68 manager := bindProtocol(t, c, "wp_tearing_control_manager_v1")
69 control := c.AllocateID()
70 registerProtocol(t, c, control)
71 requestProtocol(t, c, manager, tearingcontrol.WpTearingControlManagerV1RequestGetTearingControl, control, child)
72 requestProtocol(t, c, control, tearingcontrol.WpTearingControlV1RequestSetPresentationHint, uint32(tearingcontrol.WpTearingControlV1PresentationHintAsync))
73 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
74 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
75 if err := c.Roundtrip(); err != nil {
76 t.Fatal(err)
77 }
78 childAsync(true)
79}
80
81func TestTearingHintAppliesOnCommit(t *testing.T) {
82 s, events, _, contents, dir := contentServer(t)
83 c := protocolClient(t, s, dir)
84 w, surf, _ := surfaceMapper(t, c, events)()
85 manager := bindProtocol(t, c, "wp_tearing_control_manager_v1")
86 control := c.AllocateID()
87 registerProtocol(t, c, control)
88 requestProtocol(t, c, manager, tearingcontrol.WpTearingControlManagerV1RequestGetTearingControl, control, surf)
89 requestProtocol(t, c, control, tearingcontrol.WpTearingControlV1RequestSetPresentationHint, uint32(tearingcontrol.WpTearingControlV1PresentationHintAsync))
90 if err := c.Roundtrip(); err != nil {
91 t.Fatal(err)
92 }
93 // Double-buffered: nothing changes before the commit.
94 select {
95 case got := <-contents:
96 if got.Async {
97 t.Fatal("hint applied before commit")
98 }
99 case <-time.After(50 * time.Millisecond):
100 }
101 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
102 if err := c.Roundtrip(); err != nil {
103 t.Fatal(err)
104 }
105 asyncContent(t, contents, w.ID, true)
106
107 // Destroying the control goes back to vsync on the next commit.
108 requestProtocol(t, c, control, tearingcontrol.WpTearingControlV1RequestDestroy)
109 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
110 if err := c.Roundtrip(); err != nil {
111 t.Fatal(err)
112 }
113 asyncContent(t, contents, w.ID, false)
114
115 // A control outliving its surface is inert.
116 inert := c.AllocateID()
117 registerProtocol(t, c, inert)
118 requestProtocol(t, c, manager, tearingcontrol.WpTearingControlManagerV1RequestGetTearingControl, inert, surf)
119 requestProtocol(t, c, surf, wayland.SurfaceRequestDestroy)
120 requestProtocol(t, c, inert, tearingcontrol.WpTearingControlV1RequestSetPresentationHint, uint32(tearingcontrol.WpTearingControlV1PresentationHintAsync))
121 if err := c.Roundtrip(); err != nil {
122 t.Fatal(err)
123 }
124 unmapped(t, events, w.ID)
125
126 // A surface has at most one control.
127 _, other, _ := surfaceMapper(t, c, events)()
128 first, second := c.AllocateID(), c.AllocateID()
129 registerProtocol(t, c, first)
130 registerProtocol(t, c, second)
131 requestProtocol(t, c, manager, tearingcontrol.WpTearingControlManagerV1RequestGetTearingControl, first, other)
132 requestProtocol(t, c, manager, tearingcontrol.WpTearingControlManagerV1RequestGetTearingControl, second, other)
133 expectProtocolError(t, c, manager, uint32(tearingcontrol.WpTearingControlManagerV1ErrorTearingControlExists))
134}