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// DestroyImports destroys every imported buffer and timeline of the surface,
144// so a renderer built from older dmabuf feedback can be replaced by one that
145// imports its buffers again under any ids (the old ids are free afterwards).
146// It is safe while a frame is pending and when nothing is imported.
147//
148// Present refuses the old ids from now on, so nothing is sent on a destroyed
149// object. A frame callback already requested still fires and is delivered as
150// usual; the next Present waits for it as always. The compositor keeps the
151// content and the DMA-BUF references of a commit it already received: destroying
152// a wl_buffer or a timeline never withdraws a commit, and the descriptors were
153// duplicated at import, so none is closed twice. The caller may drop its own
154// references to the storage (close the renderer) at once, but must not write
155// to or recycle it before the release points of the commits that used it
156// signal (the explicit-sync rule).
157//
158// Every object is destroyed exactly once, even if a request fails; the errors
159// are joined. It returns [ErrClosed] after Surface.Close or Conn.Close. See
160// [Feedback.Equal] and the package documentation.
161func (s *Surface) DestroyImports() error {
162 if s.closed || s.c.closed {
163 return ErrClosed
164 }
165 var errs []error
166 for id, b := range s.buffers {
167 delete(s.buffers, id)
168 if err := b.buf.Destroy(); err != nil {
169 errs = append(errs, fmt.Errorf("neferclient: destroy buffer %d: %w", id, err))
170 }
171 }
172 for id, t := range s.timelines {
173 delete(s.timelines, id)
174 if err := t.Destroy(); err != nil {
175 errs = append(errs, fmt.Errorf("neferclient: destroy timeline %d: %w", id, err))
176 }
177 }
178 return errors.Join(errs...)
179}
180
181// Present commits an imported buffer. It refuses before the first configure,
182// before the first complete dmabuf feedback and while the previous frame
183// callback is pending.
184func (s *Surface) Present(p *Present) error {
185 if s.closed {
186 return ErrClosed
187 }
188 if p == nil {
189 return errors.New("neferclient: nil present")
190 }
191 if !s.configured {
192 return errors.New("neferclient: present before configure")
193 }
194 if !s.fbDone {
195 return errors.New("neferclient: dmabuf feedback incomplete")
196 }
197 if !s.frameReady {
198 return errors.New("neferclient: frame callback pending")
199 }
200 buf, ok := s.buffers[p.Buffer]
201 if !ok {
202 return fmt.Errorf("neferclient: unknown buffer %d", p.Buffer)
203 }
204 if p.AcquireTimeline == p.ReleaseTimeline && p.AcquirePoint >= p.ReleasePoint {
205 return fmt.Errorf("neferclient: acquire point %d must precede release point %d on one timeline", p.AcquirePoint, p.ReleasePoint)
206 }
207 for _, id := range [...]uint64{p.AcquireTimeline, p.ReleaseTimeline} {
208 if _, ok := s.timelines[id]; !ok {
209 return fmt.Errorf("neferclient: unknown timeline %d", id)
210 }
211 }
212 pw, ph, err := s.PhysicalSize()
213 if err != nil {
214 return err
215 }
216 // Without a viewport (and always on lock surfaces) the compositor
217 // requires the buffer to match the surface exactly.
218 if (s.viewport == nil || s.role == roleLock) && (buf.w != pw || buf.h != ph) {
219 return fmt.Errorf("neferclient: buffer %dx%d does not match surface %dx%d", buf.w, buf.h, pw, ph)
220 }
221 r := s.req
222 if s.viewport != nil {
223 if s.sentDestW != s.width || s.sentDestH != s.height {
224 if err = r.setDestination(s.width, s.height); err != nil {
225 return err
226 }
227 s.sentDestW, s.sentDestH = s.width, s.height
228 }
229 } else if s.sentBufScale != int32(s.scale) {
230 if err = r.setBufferScale(int32(s.scale)); err != nil {
231 return err
232 }
233 s.sentBufScale = int32(s.scale)
234 }
235 if p.Opaque {
236 if !s.opaqueSet || s.opaqueW != s.width || s.opaqueH != s.height {
237 if err = r.setOpaqueRegion(s.width, s.height, true); err != nil {
238 return err
239 }
240 s.opaqueSet, s.opaqueW, s.opaqueH = true, s.width, s.height
241 }
242 } else if s.opaqueSet {
243 if err = r.setOpaqueRegion(0, 0, false); err != nil {
244 return err
245 }
246 s.opaqueSet = false
247 }
248 if err = r.setAcquire(p.AcquireTimeline, p.AcquirePoint); err != nil {
249 return err
250 }
251 if err = r.setRelease(p.ReleaseTimeline, p.ReleasePoint); err != nil {
252 return err
253 }
254 if err = r.attach(p.Buffer); err != nil {
255 return err
256 }
257 if len(p.Damage) == 0 {
258 err = r.damageBuffer(Rect{0, 0, pw, ph})
259 }
260 for _, d := range p.Damage {
261 if err != nil {
262 break
263 }
264 err = r.damageBuffer(d)
265 }
266 if err != nil {
267 return err
268 }
269 if err = r.frame(); err != nil {
270 return err
271 }
272 if err = r.commit(); err != nil {
273 return err
274 }
275 s.frameReady, s.presented = false, true
276 return nil
277}
278
279// surfaceRequests is the seam between the Present ordering rules and the
280// wire: the production implementation sends the protocol requests.
281type surfaceRequests interface {
282 setDestination(w, h int32) error
283 setBufferScale(n int32) error
284 setOpaqueRegion(w, h int32, opaque bool) error
285 setAcquire(timeline, point uint64) error
286 setRelease(timeline, point uint64) error
287 attach(buffer uint64) error
288 damageBuffer(r Rect) error
289 frame() error
290 commit() error
291}
292
293// wireRequests sends the requests. skipFrame is set by tests that isolate the
294// request path from the frame callback registration.
295type wireRequests struct {
296 s *Surface
297 skipFrame bool
298}
299
300func (r wireRequests) setDestination(w, h int32) error { return r.s.viewport.SetDestination(w, h) }
301func (r wireRequests) setBufferScale(n int32) error { return r.s.surf.SetBufferScale(n) }
302
303func (r wireRequests) setOpaqueRegion(w, h int32, opaque bool) error {
304 s := r.s
305 if !opaque {
306 return s.surf.SetOpaqueRegion(nil)
307 }
308 region, err := s.c.g.compositor.CreateRegion()
309 if err != nil {
310 return err
311 }
312 if err = region.Add(0, 0, w, h); err == nil {
313 err = s.surf.SetOpaqueRegion(region)
314 }
315 _ = region.Destroy()
316 return err
317}
318
319func (r wireRequests) setAcquire(timeline, point uint64) error {
320 return r.s.sync.SetAcquirePoint(r.s.timelines[timeline], uint32(point>>32), uint32(point))
321}
322
323func (r wireRequests) setRelease(timeline, point uint64) error {
324 return r.s.sync.SetReleasePoint(r.s.timelines[timeline], uint32(point>>32), uint32(point))
325}
326
327func (r wireRequests) attach(buffer uint64) error {
328 return r.s.surf.Attach(r.s.buffers[buffer].buf, 0, 0)
329}
330
331func (r wireRequests) damageBuffer(d Rect) error {
332 return r.s.surf.DamageBuffer(d.X, d.Y, d.Width, d.Height)
333}
334
335// frame requests the frame callback on the surface's single reusable
336// callback object: only one is ever pending.
337func (r wireRequests) frame() error {
338 s := r.s
339 if r.skipFrame {
340 return nil
341 }
342 return s.c.wlctx.RequestArgs(wl.Request{Proxy: s.surf, Opcode: 3, Name: "wl_surface.frame", Child: s.cb}, wl.ArgObject(s.cb))
343}
344
345func (r wireRequests) commit() error { return r.s.surf.Commit() }