1package neferclient
2
3import (
4 "errors"
5 "fmt"
6
7 "github.com/bnema/go-wayland-bindings/client/wayland"
8 "github.com/bnema/wlturbo/wl"
9 "golang.org/x/sys/unix"
10)
11
12// importedBuffer is a wl_buffer with the size it was imported at.
13type importedBuffer struct {
14 buf *wayland.Buffer
15 w, h int32
16}
17
18// Plane is one DMA-BUF plane of a [Buffer]. FD stays owned by the caller.
19type Plane struct {
20 FD int
21 Offset uint32
22 Stride uint32
23}
24
25// Buffer describes a DMA-BUF to import as a wl_buffer.
26type Buffer struct {
27 Width, Height int32 // physical pixels
28 FourCC uint32
29 Modifier uint64
30 Planes [4]Plane
31 PlaneCount int
32}
33
34// Present describes one commit: which imported buffer, the explicit-sync
35// timelines and points, and the damage in buffer pixels.
36type Present struct {
37 Buffer uint64
38 AcquireTimeline uint64
39 ReleaseTimeline uint64
40 AcquirePoint uint64
41 ReleasePoint uint64
42 Damage []Rect // nil or empty damages the whole buffer
43 Opaque bool // the whole surface is opaque
44}
45
46// ImportBuffer creates a wl_buffer from b under the caller's id. The plane
47// descriptors are duplicated internally; the caller keeps ownership of its own.
48func (s *Surface) ImportBuffer(id uint64, b *Buffer) (err error) {
49 if s.closed {
50 return ErrClosed
51 }
52 if _, ok := s.buffers[id]; ok {
53 return fmt.Errorf("neferclient: buffer %d already imported", id)
54 }
55 if b == nil || b.Width <= 0 || b.Height <= 0 || b.PlaneCount < 1 || b.PlaneCount > len(b.Planes) {
56 return errors.New("neferclient: invalid buffer description")
57 }
58 dups := [4]int{-1, -1, -1, -1}
59 sent := 0
60 defer func() {
61 for _, fd := range dups[sent:b.PlaneCount] { // not consumed by a request
62 if fd >= 0 {
63 _ = unix.Close(fd)
64 }
65 }
66 }()
67 for i := range b.PlaneCount {
68 fd, err := unix.FcntlInt(uintptr(b.Planes[i].FD), unix.F_DUPFD_CLOEXEC, 0)
69 if err != nil {
70 return fmt.Errorf("neferclient: dup plane %d: %w", i, err)
71 }
72 dups[i] = fd
73 }
74 params, err := s.c.g.dmabuf.CreateParams()
75 if err != nil {
76 return err
77 }
78 defer func() { err = errors.Join(err, params.Destroy()) }()
79 hi, lo := uint32(b.Modifier>>32), uint32(b.Modifier)
80 for i := range b.PlaneCount {
81 // On success the request closes the duplicate; on failure it stays ours.
82 if err = params.Add(dups[i], uint32(i), b.Planes[i].Offset, b.Planes[i].Stride, hi, lo); err != nil {
83 return err
84 }
85 sent++
86 }
87 buf, err := params.CreateImmed(b.Width, b.Height, b.FourCC, 0)
88 if err != nil {
89 return err
90 }
91 s.buffers[id] = importedBuffer{buf: buf, w: b.Width, h: b.Height}
92 return nil
93}
94
95// DestroyBuffer destroys an imported wl_buffer. The compositor may still read
96// it until its release point signals; see the explicit-sync rules.
97func (s *Surface) DestroyBuffer(id uint64) error {
98 buf, ok := s.buffers[id]
99 if !ok {
100 return fmt.Errorf("neferclient: unknown buffer %d", id)
101 }
102 delete(s.buffers, id)
103 return buf.buf.Destroy()
104}
105
106// ImportTimeline imports a DRM syncobj timeline. fd is duplicated internally.
107func (s *Surface) ImportTimeline(id uint64, fd int) error {
108 if s.closed {
109 return ErrClosed
110 }
111 if _, ok := s.timelines[id]; ok {
112 return fmt.Errorf("neferclient: timeline %d already imported", id)
113 }
114 dup, err := unix.FcntlInt(uintptr(fd), unix.F_DUPFD_CLOEXEC, 0)
115 if err != nil {
116 return fmt.Errorf("neferclient: dup timeline: %w", err)
117 }
118 t, err := s.c.g.syncobj.ImportTimeline(dup)
119 if err != nil {
120 _ = unix.Close(dup) // not consumed on error
121 return err
122 }
123 s.timelines[id] = t
124 return nil
125}
126
127// DestroyTimeline destroys an imported timeline object. The id is the one given
128// to [Surface.ImportTimeline]; it may be imported again afterwards. Do not
129// destroy a timeline that a pending commit still references. Surface.Close
130// destroys every remaining timeline.
131func (s *Surface) DestroyTimeline(id uint64) error {
132 t, ok := s.timelines[id]
133 if !ok {
134 return fmt.Errorf("neferclient: unknown timeline %d", id)
135 }
136 delete(s.timelines, id)
137 if err := t.Destroy(); err != nil {
138 return fmt.Errorf("neferclient: destroy timeline %d: %w", id, err)
139 }
140 return nil
141}
142
143// Present commits an imported buffer. It refuses before the first configure,
144// before the first complete dmabuf feedback and while the previous frame
145// callback is pending.
146func (s *Surface) Present(p *Present) error {
147 if s.closed {
148 return ErrClosed
149 }
150 if p == nil {
151 return errors.New("neferclient: nil present")
152 }
153 if !s.configured {
154 return errors.New("neferclient: present before configure")
155 }
156 if !s.fbDone {
157 return errors.New("neferclient: dmabuf feedback incomplete")
158 }
159 if !s.frameReady {
160 return errors.New("neferclient: frame callback pending")
161 }
162 buf, ok := s.buffers[p.Buffer]
163 if !ok {
164 return fmt.Errorf("neferclient: unknown buffer %d", p.Buffer)
165 }
166 if p.AcquireTimeline == p.ReleaseTimeline && p.AcquirePoint >= p.ReleasePoint {
167 return fmt.Errorf("neferclient: acquire point %d must precede release point %d on one timeline", p.AcquirePoint, p.ReleasePoint)
168 }
169 for _, id := range [...]uint64{p.AcquireTimeline, p.ReleaseTimeline} {
170 if _, ok := s.timelines[id]; !ok {
171 return fmt.Errorf("neferclient: unknown timeline %d", id)
172 }
173 }
174 pw, ph, err := s.PhysicalSize()
175 if err != nil {
176 return err
177 }
178 // Without a viewport (and always on lock surfaces) the compositor
179 // requires the buffer to match the surface exactly.
180 if (s.viewport == nil || s.role == roleLock) && (buf.w != pw || buf.h != ph) {
181 return fmt.Errorf("neferclient: buffer %dx%d does not match surface %dx%d", buf.w, buf.h, pw, ph)
182 }
183 r := s.req
184 if s.viewport != nil {
185 if s.sentDestW != s.width || s.sentDestH != s.height {
186 if err = r.setDestination(s.width, s.height); err != nil {
187 return err
188 }
189 s.sentDestW, s.sentDestH = s.width, s.height
190 }
191 } else if s.sentBufScale != int32(s.scale) {
192 if err = r.setBufferScale(int32(s.scale)); err != nil {
193 return err
194 }
195 s.sentBufScale = int32(s.scale)
196 }
197 if p.Opaque {
198 if !s.opaqueSet || s.opaqueW != s.width || s.opaqueH != s.height {
199 if err = r.setOpaqueRegion(s.width, s.height, true); err != nil {
200 return err
201 }
202 s.opaqueSet, s.opaqueW, s.opaqueH = true, s.width, s.height
203 }
204 } else if s.opaqueSet {
205 if err = r.setOpaqueRegion(0, 0, false); err != nil {
206 return err
207 }
208 s.opaqueSet = false
209 }
210 if err = r.setAcquire(p.AcquireTimeline, p.AcquirePoint); err != nil {
211 return err
212 }
213 if err = r.setRelease(p.ReleaseTimeline, p.ReleasePoint); err != nil {
214 return err
215 }
216 if err = r.attach(p.Buffer); err != nil {
217 return err
218 }
219 if len(p.Damage) == 0 {
220 err = r.damageBuffer(Rect{0, 0, pw, ph})
221 }
222 for _, d := range p.Damage {
223 if err != nil {
224 break
225 }
226 err = r.damageBuffer(d)
227 }
228 if err != nil {
229 return err
230 }
231 if err = r.frame(); err != nil {
232 return err
233 }
234 if err = r.commit(); err != nil {
235 return err
236 }
237 s.frameReady, s.presented = false, true
238 return nil
239}
240
241// surfaceRequests is the seam between the Present ordering rules and the
242// wire: the production implementation sends the protocol requests.
243type surfaceRequests interface {
244 setDestination(w, h int32) error
245 setBufferScale(n int32) error
246 setOpaqueRegion(w, h int32, opaque bool) error
247 setAcquire(timeline, point uint64) error
248 setRelease(timeline, point uint64) error
249 attach(buffer uint64) error
250 damageBuffer(r Rect) error
251 frame() error
252 commit() error
253}
254
255// wireRequests sends the requests. skipFrame is set by tests that isolate the
256// request path from the frame callback registration.
257type wireRequests struct {
258 s *Surface
259 skipFrame bool
260}
261
262func (r wireRequests) setDestination(w, h int32) error { return r.s.viewport.SetDestination(w, h) }
263func (r wireRequests) setBufferScale(n int32) error { return r.s.surf.SetBufferScale(n) }
264
265func (r wireRequests) setOpaqueRegion(w, h int32, opaque bool) error {
266 s := r.s
267 if !opaque {
268 return s.surf.SetOpaqueRegion(nil)
269 }
270 region, err := s.c.g.compositor.CreateRegion()
271 if err != nil {
272 return err
273 }
274 if err = region.Add(0, 0, w, h); err == nil {
275 err = s.surf.SetOpaqueRegion(region)
276 }
277 _ = region.Destroy()
278 return err
279}
280
281func (r wireRequests) setAcquire(timeline, point uint64) error {
282 return r.s.sync.SetAcquirePoint(r.s.timelines[timeline], uint32(point>>32), uint32(point))
283}
284
285func (r wireRequests) setRelease(timeline, point uint64) error {
286 return r.s.sync.SetReleasePoint(r.s.timelines[timeline], uint32(point>>32), uint32(point))
287}
288
289func (r wireRequests) attach(buffer uint64) error {
290 return r.s.surf.Attach(r.s.buffers[buffer].buf, 0, 0)
291}
292
293func (r wireRequests) damageBuffer(d Rect) error {
294 return r.s.surf.DamageBuffer(d.X, d.Y, d.Width, d.Height)
295}
296
297// frame requests the frame callback on the surface's single reusable
298// callback object: only one is ever pending.
299func (r wireRequests) frame() error {
300 s := r.s
301 if r.skipFrame {
302 return nil
303 }
304 return s.c.wlctx.RequestArgs(wl.Request{Proxy: s.surf, Opcode: 3, Name: "wl_surface.frame", Child: s.cb}, wl.ArgObject(s.cb))
305}
306
307func (r wireRequests) commit() error { return r.s.surf.Commit() }