1package capture
2
3import (
4 "fmt"
5 "image"
6 "math"
7 "time"
8
9 "github.com/bnema/neferwl/internal/ports"
10)
11
12// The indicator fence. A capture is requested straight from wayland to the
13// output owner, while core learns of it (CaptureFrameTaken) on its own
14// channel and only then publishes a scene that carries the capture indicator.
15// An output that served the request from the scene it already had would copy
16// pixels before the border or pill was ever on screen. So the output owner
17// holds every request until its current scene shows the mark the request
18// needs, and serves it from that scene: the indicator is composed in the same
19// frame as the capture or in an earlier one, never after it.
20//
21// Wayland stamps every capture it requests with Indicate. The mark needed is
22// derived from the request alone:
23// - OffScreen (a workspace rendered for capture only): a pill;
24// - any other: a border whose rectangle covers the request's region.
25//
26// A mark that draws nothing (an empty rectangle, a border that would be 0
27// wide, a pill not fully inside the output: ports.CaptureIndicator.DrawableIn) never counts as shown. Core
28// inflates thin targets so their mark is drawable.
29//
30// Delivery is gated on the display (Pipeline.BeginGate, EndGate). The frame
31// of a capture is rendered without the indicator and copied before the
32// indicated frame, but its pixels are written and its reply sent only after
33// the owner committed (DRM) or presented (headless) the frame that carries
34// the indicator. When that frame fails to render or to commit, or its path
35// ends in any other way, the capture fails with ErrIndicatorMissing (and the
36// cause): a capture is never delivered for an indicator that was not shown.
37// A scene without an indicator gates nothing and costs nothing.
38//
39// A request whose mark does not come (the target vanished, so core has
40// nothing to mark) fails with ErrIndicatorMissing after HoldFor: nothing is
41// ever served without its indicator. The fence costs nothing while no request
42// is pending.
43
44// HoldFor bounds how long a request waits for its indicator.
45const HoldFor = 250 * time.Millisecond
46
47// ErrIndicatorMissing fails a request whose indicator was not on screen in
48// time.
49var ErrIndicatorMissing = fmt.Errorf("%w: capture indicator is not on screen", ports.ErrCaptureTransient)
50
51// IndicatorShown reports whether scene s shows the indicator request q needs.
52// A request that does not ask for one (Indicate false) needs none.
53func IndicatorShown(s ports.Scene, q ports.CaptureRequest) bool {
54 if !q.Indicate {
55 return true
56 }
57 for _, m := range s.CaptureIndicators {
58 if !m.DrawableIn(s.OutputWidth, s.OutputHeight) {
59 // Defense in depth: a mark whose border would be empty shows
60 // nothing, so it vouches for no capture.
61 continue
62 }
63 if q.OffScreen {
64 if m.Pill {
65 return true
66 }
67 continue
68 }
69 if !m.Pill && covers(s, m.Rect, q.Region) {
70 return true
71 }
72 }
73 return false
74}
75
76// covers reports whether the border of logical rect m, drawn over a target
77// region, covers the physical region r: m is projected to physical pixels the
78// way wayland projects a logical target (rounded outwards). An edge of m on an
79// output edge extends to it: the output size is not always a multiple of the
80// scale.
81func covers(s ports.Scene, m ports.Rect, r image.Rectangle) bool {
82 scale := s.Scale
83 if scale <= 0 {
84 scale = 1
85 }
86 x0, y0 := int(math.Floor(float64(m.X)*scale)), int(math.Floor(float64(m.Y)*scale))
87 x1, y1 := int(math.Ceil(float64(m.X+m.W)*scale)), int(math.Ceil(float64(m.Y+m.H)*scale))
88 if m.X <= 0 {
89 x0 = math.MinInt
90 }
91 if m.Y <= 0 {
92 y0 = math.MinInt
93 }
94 if m.X+m.W >= s.OutputWidth {
95 x1 = math.MaxInt
96 }
97 if m.Y+m.H >= s.OutputHeight {
98 y1 = math.MaxInt
99 }
100 return r.Min.X >= x0 && r.Min.Y >= y0 && r.Max.X <= x1 && r.Max.Y <= y1
101}
102
103// expired reports whether q has waited for its indicator for HoldFor.
104func expired(q ports.CaptureRequest, now time.Time) bool {
105 return now.Sub(q.Since) >= HoldFor
106}
107
108// Expire fails the requests that waited HoldFor without their indicator
109// being shown by s, and returns the others, in order, in place (the entries
110// past them are zeroed). wait is how long until the next one expires, 0 when
111// none waits for its indicator. No allocation.
112func (p *Pipeline) Expire(s ports.Scene, reqs []ports.CaptureRequest, now time.Time) (kept []ports.CaptureRequest, wait time.Duration) {
113 n := 0
114 for _, q := range reqs {
115 if !IndicatorShown(s, q) {
116 if expired(q, now) {
117 Fail(p.ctx, q, ErrIndicatorMissing, p.replies)
118 continue
119 }
120 if d := HoldFor - now.Sub(q.Since); wait == 0 || d < wait {
121 wait = d
122 }
123 }
124 reqs[n] = q
125 n++
126 }
127 clear(reqs[n:])
128 return reqs[:n], wait
129}
130
131// Hold splits the requests of one output frame by the indicator of s: ready
132// is the prefix whose indicator s shows (they go to Split), held the requests
133// that keep waiting for a scene that shows theirs. Expired ones are failed
134// first (see Expire). ready and held are consecutive in reqs; the entries
135// past them are zeroed. No allocation.
136func (p *Pipeline) Hold(s ports.Scene, reqs []ports.CaptureRequest, now time.Time) (ready, held []ports.CaptureRequest) {
137 reqs, _ = p.Expire(s, reqs, now)
138 j := 0
139 for i := range reqs {
140 if IndicatorShown(s, reqs[i]) {
141 reqs[i], reqs[j] = reqs[j], reqs[i]
142 j++
143 }
144 }
145 return reqs[:j], reqs[j:]
146}
147
148// Waiting keeps, in place, the requests of a frame that still wait for the
149// indicator of s: the ones neither handed over nor failed, whose indicator s
150// does not show. The entries past them are zeroed. The capacity is kept, so
151// the result replaces the owner's request list.
152func Waiting(reqs []ports.CaptureRequest, s ports.Scene) []ports.CaptureRequest {
153 n := 0
154 for _, q := range reqs {
155 if !Handed(q) && !IndicatorShown(s, q) {
156 reqs[n] = q
157 n++
158 }
159 }
160 clear(reqs[n:])
161 return reqs[:n]
162}