1//go:build linux
2
3package wayland
4
5import (
6 "encoding/binary"
7 "io"
8 "net"
9 "testing"
10 "time"
11
12 "github.com/bnema/nefergui/internal/presentation/buffers"
13 "github.com/bnema/wlturbo"
14 "github.com/bnema/wlturbo/protocol/core"
15 "github.com/bnema/wlturbo/protocol/drmsyncobj"
16 "github.com/bnema/wlturbo/wl"
17)
18
19// A socket peer records real wire requests (not a mocked Present implementation).
20// Every frame must carry both sync points and a buffer on the same commit.
21func TestPresentWireSequence(t *testing.T) {
22 client, server := net.Pipe()
23 defer server.Close()
24 // ConnectFromConn sends get_registry before it returns: drain the pipe first.
25 type request struct {
26 id uint32
27 opcode uint16
28 args []byte
29 }
30 requests := make(chan request, 64)
31 errs := make(chan error, 1)
32 go func() {
33 for {
34 header := make([]byte, 8)
35 if _, e := io.ReadFull(server, header); e != nil {
36 errs <- e
37 return
38 }
39 n := int(binary.LittleEndian.Uint32(header[4:]) >> 16)
40 data := make([]byte, n-8)
41 if _, e := io.ReadFull(server, data); e != nil {
42 errs <- e
43 return
44 }
45 requests <- request{binary.LittleEndian.Uint32(header), binary.LittleEndian.Uint16(header[4:]), data}
46 }
47 }()
48 d, err := wlturbo.ConnectFromConn(client)
49 if err != nil {
50 t.Fatal(err)
51 }
52 defer d.Close()
53 ctx := d.Context()
54 surface := core.NewSurface(ctx)
55 syncSurface := drmsyncobj.NewWpLinuxDrmSyncobjSurface(ctx)
56 acquire := drmsyncobj.NewWpLinuxDrmSyncobjTimeline(ctx)
57 release := drmsyncobj.NewWpLinuxDrmSyncobjTimeline(ctx)
58 buffer := core.NewBuffer(ctx)
59 for i, p := range []wl.Proxy{surface, syncSurface, acquire, release, buffer} {
60 p.SetID(uint32(20 + i))
61 ctx.Register(p)
62 }
63 w := &Window{Surface: surface, SyncSurface: syncSurface, Width: 40, Height: 30, Scale: 1, Configured: true, FeedbackDone: true, FrameReady: true, Transparent: true}
64 p, _ := buffers.New(1)
65 // The fake server receives a finite number of requests; no dispatch loop/GPU.
66 for frame := 0; frame < 2; frame++ {
67 b, e := p.Begin()
68 if e != nil {
69 t.Fatal(e)
70 }
71 a, e := p.Acquire(b)
72 if e != nil {
73 t.Fatal(e)
74 }
75 z, e := p.Commit(b)
76 if e != nil {
77 t.Fatal(e)
78 }
79 if e = w.Present(buffer, acquire, release, a, z); e != nil {
80 t.Fatal(e)
81 }
82 if e = p.Own(b); e != nil {
83 t.Fatal(e)
84 }
85 // Observe the generated protocol sequence up to commit. Ignore ancillary
86 // viewport/opaque/damage/frame requests, but ensure they do not replace the buffer.
87 var events []string
88 var attached, acquired, released bool
89 deadline := time.After(3 * time.Second)
90 loop:
91 for {
92 select {
93 case q := <-requests:
94 switch {
95 case q.id == syncSurface.ID() && q.opcode == 1:
96 acquired = true
97 events = append(events, "acquire")
98 case q.id == syncSurface.ID() && q.opcode == 2:
99 released = true
100 events = append(events, "release")
101 case q.id == surface.ID() && q.opcode == 1:
102 attached = binary.LittleEndian.Uint32(q.args) == buffer.ID()
103 events = append(events, "attach")
104 case q.id == surface.ID() && q.opcode == 6:
105 events = append(events, "commit")
106 break loop
107 }
108 case e := <-errs:
109 t.Fatal(e)
110 case <-deadline:
111 t.Fatal("missing commit")
112 }
113 }
114 if !attached || !acquired || !released || len(events) != 4 || events[0] != "acquire" || events[1] != "release" || events[2] != "attach" || events[3] != "commit" {
115 t.Fatalf("frame %d wire sequence %v attached=%v", frame, events, attached)
116 }
117 if e = p.Release(b); e != nil {
118 t.Fatal(e)
119 }
120 if _, e = p.Begin(); e != buffers.ErrNoBuffer {
121 t.Fatalf("wl_buffer.release reused buffer: %v", e)
122 }
123 if e = p.Signal(b, z); e != nil {
124 t.Fatal(e)
125 }
126 if e = p.Reuse(b, true); e != nil {
127 t.Fatal(e)
128 }
129 w.FrameReady = true // synthetic frame callback
130 }
131}