1package drm
2
3import (
4 "errors"
5 "os"
6
7 "github.com/bnema/neferwl/internal/ports"
8 "golang.org/x/sys/unix"
9)
10
11// Overlay plane: a window drawn from one opaque dmabuf, with nothing but
12// the cursor above it, goes on an overlay plane at its place on screen;
13// the rest of the scene is composed into the primary plane without it.
14// The client buffer is shown with no GPU pass, like direct scanout, but
15// the window need not cover the output. A refusal (TEST_ONLY) keeps the
16// window composed and is cached per buffer.
17
18// pickOverlay takes an overlay plane above the primary one (zpos), when
19// the output has one.
20func (o *Output) pickOverlay() {
21 pz, ok := o.primary.zpos()
22 if !ok {
23 return
24 }
25 for _, p := range o.stray {
26 if p.typ != planeOverlay {
27 continue
28 }
29 if z, ok := p.zpos(); ok && z > pz {
30 o.overlay = p
31 return
32 }
33 }
34}
35
36// zpos is the plane's zpos value when known.
37func (p *plane) zpos() (uint64, bool) {
38 if p.props["zpos"] == 0 {
39 return 0, false
40 }
41 return p.zposValue, true
42}
43
44// overlayWin is the window on the overlay plane: its buffer and where it
45// shows, in physical pixels.
46type overlayWin struct {
47 id ports.WindowID
48 fb uint32
49 buf uint64
50 rect ports.Rect // CRTC rect
51 w, h int // buffer size
52 // acquire is the client's explicit-sync fence, a duplicate owned by
53 // the frame loop (closed after the commit); nil without one.
54 acquire *os.File
55}
56
57// overlayCandidate finds the one window the overlay can show: an opaque
58// dmabuf at integer physical coordinates, drawn 1:1, with no other window
59// or layer above it. reason is why none.
60func overlayCandidate(s ports.Scene, surfaces map[ports.WindowID]ports.SurfaceContent) (ports.SceneWindow, ports.SurfaceContent, string) {
61 scale := s.Scale
62 if scale <= 0 {
63 scale = 1
64 }
65 var pick *ports.SceneWindow
66 for i := range s.Windows {
67 w := &s.Windows[i]
68 if w.Hidden || w.Rect.W <= 0 || w.Rect.H <= 0 {
69 continue
70 }
71 if w.Popup {
72 // Popups draw after every window, wherever they are listed.
73 return ports.SceneWindow{}, ports.SurfaceContent{}, "window_above"
74 }
75 c := surfaces[w.ID]
76 // A viewport crop or PQ content is composed: the plane would show
77 // the whole buffer, and raw PQ values on this SDR output. A dimmed
78 // window (a peeking stashed one) needs the veil drawn over it, and
79 // one with the focus effect needs its lift; an overview preview is
80 // drawn smaller than its buffer.
81 if w.Dim <= 0 && w.FocusEffect <= 0 && w.Preview <= 0 && c.DMABuf != nil && !isYUVFormat(c.DMABuf.Format) && c.Opaque && len(c.Children) == 0 && c.Transform == 0 && !cropped(c) && !c.Color.IsPQ2020() {
82 pick = w
83 continue
84 }
85 if pick != nil {
86 // Something is drawn above the candidate.
87 return ports.SceneWindow{}, ports.SurfaceContent{}, "window_above"
88 }
89 }
90 if pick == nil {
91 return ports.SceneWindow{}, ports.SurfaceContent{}, "no_candidate"
92 }
93 if s.Dim > 0 {
94 // The renderer draws the veil under the first float it composes: a
95 // float on the plane would take the veil with it.
96 return ports.SceneWindow{}, ports.SurfaceContent{}, "dim"
97 }
98 if len(s.DropHints) > 0 {
99 // Drag hints are drawn over the windows.
100 return ports.SceneWindow{}, ports.SurfaceContent{}, "drop_hint"
101 }
102 for _, l := range s.Layers {
103 if l.Layer >= ports.LayerTop && l.Rect.W > 0 && l.Rect.H > 0 {
104 return ports.SceneWindow{}, ports.SurfaceContent{}, "layer_above"
105 }
106 }
107 if !pick.Fullscreen && len(s.Separators) > 0 {
108 // The overlay shows the buffer alone; the lines would go with it.
109 return ports.SceneWindow{}, ports.SurfaceContent{}, "border"
110 }
111 if clip := s.WorkspaceClip; clip != (ports.Rect{}) &&
112 (pick.Rect.X < clip.X || pick.Rect.Y < clip.Y || pick.Rect.X+pick.Rect.W > clip.X+clip.W || pick.Rect.Y+pick.Rect.H > clip.Y+clip.H) {
113 // The plane cannot apply the workspace's logical viewport crop.
114 return ports.SceneWindow{}, ports.SurfaceContent{}, "workspace_clip"
115 }
116 c := surfaces[pick.ID]
117 pw, ph := float64(pick.Rect.W)*scale, float64(pick.Rect.H)*scale
118 if float64(c.Width) != pw || float64(c.Height) != ph || c.Geometry != (ports.Rect{}) && (c.Geometry.X != 0 || c.Geometry.Y != 0) {
119 return ports.SceneWindow{}, ports.SurfaceContent{}, "scaled"
120 }
121 x, y := float64(pick.Rect.X)*scale, float64(pick.Rect.Y)*scale
122 if x != float64(int(x)) || y != float64(int(y)) {
123 return ports.SceneWindow{}, ports.SurfaceContent{}, "fractional_position"
124 }
125 return *pick, c, ""
126}
127
128// overlayProps puts ov on the overlay plane, or turns it off.
129func (o *Output) overlayProps(req *atomicReq, ov overlayWin) {
130 p := o.overlay
131 if p == nil {
132 return
133 }
134 if ov.fb == 0 {
135 req.set(p.id, p.prop("FB_ID"), 0)
136 req.set(p.id, p.prop("CRTC_ID"), 0)
137 return
138 }
139 req.set(p.id, p.prop("FB_ID"), uint64(ov.fb))
140 req.set(p.id, p.prop("CRTC_ID"), uint64(o.crtc))
141 req.set(p.id, p.prop("SRC_X"), 0)
142 req.set(p.id, p.prop("SRC_Y"), 0)
143 req.set(p.id, p.prop("SRC_W"), uint64(ov.w)<<16)
144 req.set(p.id, p.prop("SRC_H"), uint64(ov.h)<<16)
145 req.set(p.id, p.prop("CRTC_X"), uint64(int64(ov.rect.X)))
146 req.set(p.id, p.prop("CRTC_Y"), uint64(int64(ov.rect.Y)))
147 req.set(p.id, p.prop("CRTC_W"), uint64(ov.rect.W))
148 req.set(p.id, p.prop("CRTC_H"), uint64(ov.rect.H))
149 if ov.acquire != nil {
150 // KMS waits for the client's GPU work before scanning it out.
151 req.set(p.id, p.prop("IN_FENCE_FD"), uint64(ov.acquire.Fd()))
152 }
153}
154
155// close releases the frame's duplicate of the acquire fence.
156func (ov overlayWin) close() {
157 if ov.acquire != nil {
158 ov.acquire.Close()
159 }
160}
161
162// testOverlay asks KMS whether it takes the frame with ov on the overlay,
163// the cursor at its current place included. A refusal is cached on the
164// buffer.
165func (o *Output) testOverlay(fb uint32, ov overlayWin) bool {
166 req := &atomicReq{}
167 o.primaryProps(req, fb)
168 o.overlayProps(req, ov)
169 if o.cursor != nil {
170 o.cursor.props(req, o.crtc, o.cursor.desired())
171 }
172 err := o.k.commit(req, atomicTestOnly, 0)
173 if err == nil {
174 return true
175 }
176 if cfb := o.clientFBs[ov.buf]; cfb != nil && refused(err) {
177 cfb.overlayFailed = "overlay_refused"
178 }
179 o.setOverlayReason("overlay_refused")
180 o.log.Info().Str("component", "render").Err(err).Str("connector", o.conn.name).Msg("overlay refused")
181 return false
182}
183
184// errOverlayDropped: a commit was refused with the overlay on; the frame is
185// composed again without it.
186var errOverlayDropped = errors.New("overlay dropped")
187
188// overlayConflict handles a commit refused with buffer buf on the overlay:
189// the buffer leaves the overlay for good (e.g. the cursor moved where the
190// plane forbids it) and the frame is composed. It reports whether it did.
191func (o *Output) overlayConflict(err error, buf uint64) bool {
192 if buf == 0 || !errors.Is(err, unix.EINVAL) {
193 return false
194 }
195 if cfb := o.clientFBs[buf]; cfb != nil {
196 cfb.overlayFailed = "cursor_conflict"
197 }
198 o.setOverlayReason("cursor_conflict")
199 return true
200}