v0.1.0 d9218d6

fifo.go RawHistoryFolder

1package wayland
2
3import (
4	"time"
5
6	"github.com/bnema/neferwl/internal/ports"
7	"github.com/bnema/purego-libwayland/protocol/committiming"
8	"github.com/bnema/purego-libwayland/protocol/contenttype"
9	"github.com/bnema/purego-libwayland/protocol/fifo"
10	"github.com/bnema/purego-libwayland/protocol/presentationtime"
11	"github.com/bnema/purego-libwayland/protocol/wayland"
12	"github.com/bnema/purego-libwayland/server"
13	"golang.org/x/sys/unix"
14)
15
16// Presentation constraints on content updates (games, Vulkan FIFO):
17//
18//   - wp_fifo_v1: a commit with wait_barrier waits while the barrier set
19//     by an earlier commit stands; the barrier clears at the output's
20//     next refresh (its flip, or a deadline when it does not flip).
21//   - wp_commit_timing_v1: a commit waits until the refresh that shows it
22//     is not before its timestamp (CLOCK_MONOTONIC).
23//
24// A commit that must wait is taken out of the surface's pending state and
25// queued; the pacer (frames.go) applies queued updates in order when they
26// are ready. wp_content_type_v1 is recorded and logged only.
27
28func registerPresentationConstraints(d *server.Display, s *Server) error {
29	if err := fifo.NewWpFifoManagerV1Global(d, 1, func(c server.Client, v, id uint32) {
30		_, _ = fifo.NewWpFifoManagerV1(c, int32(v), id, fifoManager{s})
31	}); err != nil {
32		return err
33	}
34	if err := committiming.NewWpCommitTimingManagerV1Global(d, 1, func(c server.Client, v, id uint32) {
35		_, _ = committiming.NewWpCommitTimingManagerV1(c, int32(v), id, timingManager{s})
36	}); err != nil {
37		return err
38	}
39	return contenttype.NewWpContentTypeManagerV1Global(d, 1, func(c server.Client, v, id uint32) {
40		_, _ = contenttype.NewWpContentTypeManagerV1(c, int32(v), id, contentTypeManager{s})
41	})
42}
43
44// fifo_v1
45
46type fifoManager struct{ server *Server }
47
48func (fifoManager) Destroy(*fifo.WpFifoManagerV1) {}
49
50func (m fifoManager) GetFifo(r *fifo.WpFifoManagerV1, id uint32, surf *wayland.Surface) {
51	state := m.server.surfaceOf(surf)
52	if state != nil && state.fifo != nil {
53		r.PostError(uint32(fifo.WpFifoManagerV1ErrorAlreadyExists), "surface already has a fifo")
54		return
55	}
56	h := &fifoHandler{surface: state}
57	res, err := fifo.NewWpFifoV1(r.Client(), r.Version(), id, h)
58	if err != nil || state == nil {
59		return
60	}
61	state.fifo = h
62	res.OnDestroy = func() {
63		if state.fifo == h {
64			state.fifo = nil
65		}
66	}
67}
68
69type fifoHandler struct{ surface *surface }
70
71func (h *fifoHandler) live(r *fifo.WpFifoV1) bool {
72	if h.surface == nil || h.surface.destroyed {
73		r.PostError(uint32(fifo.WpFifoV1ErrorSurfaceDestroyed), "surface destroyed")
74		return false
75	}
76	return true
77}
78func (h *fifoHandler) SetBarrier(r *fifo.WpFifoV1) {
79	if h.live(r) {
80		h.surface.pendingBarrier = true
81	}
82}
83func (h *fifoHandler) WaitBarrier(r *fifo.WpFifoV1) {
84	if h.live(r) {
85		h.surface.pendingWait = true
86	}
87}
88func (*fifoHandler) Destroy(*fifo.WpFifoV1) {}
89
90// commit_timing_v1
91
92type timingManager struct{ server *Server }
93
94func (timingManager) Destroy(*committiming.WpCommitTimingManagerV1) {}
95
96func (m timingManager) GetTimer(r *committiming.WpCommitTimingManagerV1, id uint32, surf *wayland.Surface) {
97	state := m.server.surfaceOf(surf)
98	if state != nil && state.timer != nil {
99		r.PostError(uint32(committiming.WpCommitTimingManagerV1ErrorCommitTimerExists), "surface already has a commit timer")
100		return
101	}
102	h := &timerHandler{surface: state}
103	res, err := committiming.NewWpCommitTimerV1(r.Client(), r.Version(), id, h)
104	if err != nil || state == nil {
105		return
106	}
107	state.timer = h
108	res.OnDestroy = func() {
109		if state.timer == h {
110			state.timer = nil
111		}
112	}
113}
114
115type timerHandler struct{ surface *surface }
116
117func (h *timerHandler) SetTimestamp(r *committiming.WpCommitTimerV1, secHi, secLo, nsec uint32) {
118	switch {
119	case h.surface == nil || h.surface.destroyed:
120		r.PostError(uint32(committiming.WpCommitTimerV1ErrorSurfaceDestroyed), "surface destroyed")
121	case nsec >= 1e9:
122		r.PostError(uint32(committiming.WpCommitTimerV1ErrorInvalidTimestamp), "tv_nsec out of range")
123	case !h.surface.pendingTime.IsZero():
124		r.PostError(uint32(committiming.WpCommitTimerV1ErrorTimestampExists), "timestamp already set")
125	default:
126		h.surface.pendingTime = monotonicTime(uint64(secHi)<<32|uint64(secLo), int64(nsec))
127	}
128}
129func (*timerHandler) Destroy(*committiming.WpCommitTimerV1) {}
130
131// maxTimestampAhead bounds how far a commit timestamp can hold a commit;
132// a later one applies early (a deviation from "not before").
133const maxTimestampAhead = time.Second
134
135// monotonicTime converts a CLOCK_MONOTONIC time to wall time, at most
136// maxTimestampAhead from now: a far timestamp must not stall the surface.
137func monotonicTime(sec uint64, nsec int64) time.Time {
138	var now unix.Timespec
139	if unix.ClockGettime(unix.CLOCK_MONOTONIC, &now) != nil {
140		return time.Now()
141	}
142	ahead := time.Duration(nsec - now.Nsec)
143	switch n := uint64(now.Sec); {
144	case sec >= n+2:
145		ahead = maxTimestampAhead
146	case sec+2 <= n:
147		ahead = 0 // in the past: apply now
148	default:
149		ahead += time.Duration(int64(sec)-now.Sec) * time.Second
150	}
151	return time.Now().Add(min(max(ahead, 0), maxTimestampAhead))
152}
153
154// content_type_v1
155
156type contentTypeManager struct{ server *Server }
157
158func (contentTypeManager) Destroy(*contenttype.WpContentTypeManagerV1) {}
159
160func (m contentTypeManager) GetSurfaceContentType(r *contenttype.WpContentTypeManagerV1, id uint32, surf *wayland.Surface) {
161	state := m.server.surfaceOf(surf)
162	if state != nil && state.contentType != nil {
163		r.PostError(uint32(contenttype.WpContentTypeManagerV1ErrorAlreadyConstructed), "surface already has a content type")
164		return
165	}
166	h := &contentTypeHandler{surface: state}
167	res, err := contenttype.NewWpContentTypeV1(r.Client(), r.Version(), id, h)
168	if err != nil || state == nil {
169		return
170	}
171	state.contentType = h
172	res.OnDestroy = func() {
173		// Destroying it sets the type back to none, double-buffered.
174		if state.contentType == h {
175			state.contentType = nil
176			state.pendingKind = 0
177		}
178	}
179}
180
181type contentTypeHandler struct{ surface *surface }
182
183func (h *contentTypeHandler) SetContentType(_ *contenttype.WpContentTypeV1, kind uint32) {
184	if h.surface != nil && !h.surface.destroyed && h.surface.contentType == h {
185		h.surface.pendingKind = kind
186	}
187}
188func (*contentTypeHandler) Destroy(*contenttype.WpContentTypeV1) {}
189
190func (s *Server) surfaceOf(w *wayland.Surface) *surface {
191	if w == nil {
192		return nil
193	}
194	return s.surfaces[w.Resource]
195}
196
197// Content update queue
198
199// update is a surface's pending state taken out by a commit that waits.
200type update struct {
201	attached  bool
202	buffer    *wayland.Buffer
203	scale     int
204	async     bool
205	kind      uint32
206	callbacks []*wayland.Callback
207	vp        *viewport
208	vpW, vpH  int32
209	vpSet     bool
210	moves     []childMove
211	xdg       *xdgSurface
212	geometry  ports.Rect
213	cons      *constraint
214	region    *ports.Rect
215	layer     *layerSurface
216	layerNext layerState
217	barrier   bool
218	wait      bool
219	at        time.Time
220	feedback  []*presentationtime.WpPresentationFeedback
221	sync      *commitSync
222	damage    []ports.Rect
223	bufDamage []ports.Rect
224}
225
226type childMove struct {
227	child *surface
228	x, y  int
229}
230
231// takePending moves the pending state into an update: one-shot state is
232// cleared, sticky state (scale, hints, viewport, geometry, layer) kept.
233func (s *surface) takePending() update {
234	u := update{attached: s.attached, buffer: s.pending, scale: s.pendingScale, async: s.pendingAsync, kind: s.pendingKind, callbacks: s.callbacks, barrier: s.pendingBarrier, wait: s.pendingWait, at: s.pendingTime, damage: s.pendingDamage, bufDamage: s.pendingBufDamage, feedback: s.pendingFeedback, sync: s.pendingSync}
235	s.attached, s.pending, s.callbacks = false, nil, nil
236	s.pendingFeedback, s.pendingSync = nil, nil
237	s.pendingDamage, s.pendingBufDamage = nil, nil
238	s.pendingBarrier, s.pendingWait, s.pendingTime = false, false, time.Time{}
239	if v := s.viewport; v != nil {
240		u.vp, u.vpW, u.vpH, u.vpSet = v, v.pendingW, v.pendingH, v.pendingSet
241	}
242	for _, ch := range s.sub.children {
243		if ch.sub.moved {
244			u.moves = append(u.moves, childMove{ch, ch.sub.pendX, ch.sub.pendY})
245			ch.sub.moved = false
246		}
247	}
248	if s.xdg != nil {
249		u.xdg, u.geometry = s.xdg, s.xdg.pendingGeometry
250	}
251	if c := s.server.constraints[s]; c != nil && c.pending != nil {
252		u.cons, u.region, c.pending = c, c.pending, nil
253	}
254	if s.layer != nil {
255		u.layer, u.layerNext = s.layer, s.layer.pending
256	}
257	return u
258}
259
260// putPending makes an update the surface's pending state again. Parts
261// whose object is gone (viewport, child, role, constraint) are dropped.
262func (s *surface) putPending(u update) {
263	s.attached, s.pending, s.callbacks = u.attached, u.buffer, u.callbacks
264	s.pendingScale, s.pendingAsync, s.pendingKind = u.scale, u.async, u.kind
265	s.pendingBarrier, s.pendingWait, s.pendingTime = u.barrier, u.wait, u.at
266	s.pendingDamage, s.pendingBufDamage = u.damage, u.bufDamage
267	s.pendingFeedback, s.pendingSync = u.feedback, u.sync
268	if u.vp != nil && s.viewport == u.vp {
269		u.vp.pendingW, u.vp.pendingH, u.vp.pendingSet = u.vpW, u.vpH, u.vpSet
270	}
271	for _, m := range u.moves {
272		if m.child.sub.parent == s {
273			m.child.sub.pendX, m.child.sub.pendY, m.child.sub.moved = m.x, m.y, true
274		}
275	}
276	if u.xdg != nil && s.xdg == u.xdg {
277		u.xdg.pendingGeometry = u.geometry
278	}
279	if u.cons != nil && s.server.constraints[s] == u.cons {
280		u.cons.pending = u.region
281	}
282	if u.layer != nil && s.layer == u.layer {
283		u.layer.pending = u.layerNext
284	}
285}
286
287// mustWait reports whether the pending commit cannot apply now.
288func (s *surface) mustWait(now time.Time) bool {
289	return len(s.queue) > 0 || (s.pendingWait && s.barrier) || s.tooEarly(s.pendingTime, now) || !s.syncReady(s.pendingSync)
290}
291
292// tooEarly reports whether content applied now would show before at:
293// it reaches the screen one refresh later.
294func (s *surface) tooEarly(at, now time.Time) bool {
295	return !at.IsZero() && at.After(s.server.nextRefresh(s.server.fifoOutput(s), now))
296}
297
298// nextRefresh is when the output's next frame reaches the screen: the
299// vblank after now counted from its last flip (kernel time, refresh
300// period), or one period from now without flips or while VRR is on.
301func (s *Server) nextRefresh(name string, now time.Time) time.Time {
302	p := s.outputPeriod(name)
303	f, ok := s.flips[name]
304	if !ok || f.When <= 0 || f.Refresh <= 0 {
305		return now.Add(p)
306	}
307	mono := monotonic(now)
308	since := mono - f.When
309	if since < 0 || since > time.Second {
310		return now.Add(p)
311	}
312	n := since/f.Refresh + 1
313	return now.Add(f.When + n*f.Refresh - mono)
314}
315
316// queueUpdate queues the pending commit until it can apply.
317func (s *surface) queueUpdate() {
318	s.queue = append(s.queue, s.takePending())
319	s.server.fifoSurfaces[s] = struct{}{}
320	s.server.wakePacer()
321}
322
323// applyUpdate applies a queued update, keeping the requests made since.
324func (s *surface) applyUpdate(u update) {
325	// A buffer destroyed while queued (a swapchain resize) is skipped: the
326	// surface keeps its content, and is not unmapped behind the client.
327	if u.buffer != nil && !u.buffer.Resource.Alive() {
328		u.buffer, u.attached = nil, false
329	}
330	later := s.takePending()
331	s.putPending(u)
332	s.applyCommit()
333	s.putPending(later)
334}
335
336// dropQueue discards the queued updates of a destroyed surface or role.
337// A live surface still gets its queued frame callbacks.
338func (s *surface) dropQueue() {
339	released := map[*server.Resource]bool{}
340	for _, u := range s.queue {
341		if s.destroyed {
342			for _, cb := range u.callbacks {
343				cb.Destroy()
344			}
345		} else if len(u.callbacks) > 0 {
346			s.server.queueFrames(s.server.frameOutput(s), u.callbacks)
347		}
348		for _, fb := range u.feedback {
349			discard(fb)
350		}
351		s.dropSync(u.sync)
352		if u.sync != nil {
353			continue // explicit sync: the release point replaced release
354		}
355		if u.buffer != nil && !sameBuffer(u.buffer, s.current) && !released[u.buffer.Resource] && u.buffer.Resource.Alive() {
356			released[u.buffer.Resource] = true
357			u.buffer.SendRelease()
358		}
359	}
360	s.queue = nil
361	delete(s.server.fifoSurfaces, s)
362}
363
364// setBarrier applies a committed set_barrier.
365func (s *surface) setBarrier(now time.Time) {
366	s.barrier, s.barrierAt = true, now
367	s.server.fifoSurfaces[s] = struct{}{}
368	s.server.wakePacer()
369}
370
371// fifoOutput is the output pacing a surface, "" when none does.
372func (s *Server) fifoOutput(surf *surface) string {
373	name := s.frameOutput(surf)
374	if name != "" && s.outputByNameExact(name) == nil {
375		return ""
376	}
377	return name
378}
379
380// outputPeriod is one refresh of an output ("": 60 Hz).
381func (s *Server) outputPeriod(name string) time.Duration {
382	if o := s.outputByNameExact(name); o != nil && name != "" {
383		return framePeriod(o.place.Info.RefreshMilli)
384	}
385	return defaultFramePeriod
386}
387
388// tickFifo clears the barriers whose latching deadline passed and applies
389// the queued updates now ready. A flip of the surface's output is its
390// deadline; outputs that do not flip (headless, switched away, no output)
391// clear it after one refresh, or 1.5 when they usually flip. It returns
392// the time to the next deadline and whether any surface still waits.
393func (s *Server) tickFifo(now time.Time, flipped map[string]bool) (time.Duration, bool) {
394	for name, f := range flipped {
395		if f {
396			s.lastFlip[name] = now
397		}
398	}
399	for name := range s.lastFlip {
400		if s.outputByNameExact(name) == nil {
401			delete(s.lastFlip, name)
402		}
403	}
404	wait := defaultFramePeriod
405	for surf := range s.fifoSurfaces {
406		name := s.fifoOutput(surf)
407		p := s.outputPeriod(name)
408		// The deadline of outputs that usually flip only catches a missed flip.
409		deadline := surf.barrierAt.Add(p)
410		if name != "" && now.Sub(s.lastFlip[name]) < time.Second {
411			deadline = deadline.Add(p / 2)
412		}
413		if surf.barrier && ((flipped[name] && name != "" && s.lastFlip[name].After(surf.barrierAt)) || !now.Before(deadline)) {
414			surf.barrier = false
415		}
416		for len(surf.queue) > 0 && !surf.destroyed {
417			u := surf.queue[0]
418			if u.wait && surf.barrier {
419				break
420			}
421			if !surf.syncReady(u.sync) {
422				break
423			}
424			if surf.tooEarly(u.at, now) {
425				wait = min(wait, u.at.Sub(s.nextRefresh(name, now))+time.Millisecond)
426				break
427			}
428			surf.queue = surf.queue[1:]
429			surf.applyUpdate(u)
430		}
431		if surf.barrier {
432			wait = min(wait, deadline.Sub(now))
433		}
434		if !surf.barrier && len(surf.queue) == 0 {
435			delete(s.fifoSurfaces, surf)
436		}
437	}
438	return max(wait, time.Millisecond), len(s.fifoSurfaces) > 0
439}
440
441// sameBuffer reports whether two wrappers are the same wl_buffer.
442func sameBuffer(a, b *wayland.Buffer) bool {
443	return a != nil && b != nil && a.Resource == b.Resource
444}
445
446// wakePacer makes the pacer look at its queues now.
447func (s *Server) wakePacer() {
448	select {
449	case s.frameReady <- struct{}{}:
450	default:
451	}
452}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 23

Tags 10

↑↓ moveenter openesc close