1package drm
2
3import (
4 "errors"
5 "os"
6
7 "golang.org/x/sys/unix"
8)
9
10// The output side of the plane colour pipelines: per-plane state, commit
11// properties, refusal caching. The pure part (reading and matching
12// pipelines, the maths, planeColor) is in colorop_match.go.
13
14// hasColor reports whether the plane has a COLOR_PIPELINE property.
15func (p *plane) hasColor() bool { return p != nil && p.props[propColorPipeline] != 0 }
16
17// colorStale reports whether mode must be written: the plane applied
18// another one, or nobody knows. A plane without a matched pipeline never
19// leaves Bypass (modesets and Close write it), so it is never stale.
20func (p *plane) colorStale(mode colorMode) bool {
21 return p.hasColor() && p.pipeline != nil && (!p.colorKnown || p.colorApplied != mode)
22}
23
24// colorProps puts mode on p: Bypass selects the plane's Bypass pipeline;
25// colorSDRToPQ selects the matched one with its four operations enabled and
26// every other one bypassed. mult is the S31.32 multiplier and ctm the matrix
27// blob.
28func (p *plane) colorProps(req *atomicReq, mode colorMode, mult uint64, ctm uint32) {
29 if !p.hasColor() {
30 return
31 }
32 prop := p.props[propColorPipeline]
33 cp := p.pipeline
34 if mode != colorSDRToPQ || cp == nil {
35 req.set(p.id, prop, 0)
36 return
37 }
38 req.set(p.id, prop, cp.id)
39 for _, s := range cp.others {
40 req.set(s.op, s.bypass, 1)
41 }
42 for _, s := range [...]struct {
43 stage colorStage
44 val uint64
45 }{{cp.srgb, cp.srgb.curve}, {cp.mult, mult}, {cp.ctm, uint64(ctm)}, {cp.pq, cp.pq.curve}} {
46 req.set(s.stage.op, s.stage.bypass, 0)
47 req.set(s.stage.op, s.stage.prop, s.val)
48 }
49}
50
51// colorVerdict is KMS's answer to a TEST_ONLY with the pipeline on a plane,
52// kept per buffer format and cursor state.
53type colorVerdict struct {
54 format uint32
55 cursor bool
56 ok bool
57}
58
59// verdict returns a cached answer.
60func (p *plane) verdict(format uint32, cursor bool) (ok, known bool) {
61 for _, v := range p.verdicts {
62 if v.format == format && v.cursor == cursor {
63 return v.ok, true
64 }
65 }
66 return false, false
67}
68
69// setVerdict caches KMS's answer for the pipeline on p.
70func (p *plane) setVerdict(format uint32, cursor, ok bool) {
71 for i, v := range p.verdicts {
72 if v.format == format && v.cursor == cursor {
73 p.verdicts[i].ok = ok
74 return
75 }
76 }
77 p.verdicts = append(p.verdicts, colorVerdict{format: format, cursor: cursor, ok: ok})
78}
79
80// cursorShown reports whether a frame committed now shows the hardware cursor.
81func (o *Output) cursorShown() bool { return o.cursor != nil && !o.protected && o.cursor.desired().on }
82
83// primaryColorProps puts mode on the primary plane if it applies another one.
84func (o *Output) primaryColorProps(req *atomicReq, mode colorMode) {
85 if o.primary.colorStale(mode) {
86 o.primary.colorProps(req, mode, o.colorMult, o.ctmBlob)
87 }
88}
89
90// forceBypass sets Bypass on the primary and overlay planes whether or not
91// the applied state is known to differ: for requests that must leave a
92// known state (modeset, close).
93func (o *Output) forceBypass(req *atomicReq) {
94 o.primary.colorProps(req, colorBypass, 0, 0)
95 o.overlay.colorProps(req, colorBypass, 0, 0)
96}
97
98// overlayColorProps puts mode on the overlay plane if it applies another one.
99func (o *Output) overlayColorProps(req *atomicReq, mode colorMode) {
100 if o.overlay.colorStale(mode) {
101 o.overlay.colorProps(req, mode, o.colorMult, o.ctmBlob)
102 }
103}
104
105// colorBypassed records that a commit left both planes on Bypass.
106func (o *Output) colorBypassed() {
107 for _, p := range [...]*plane{o.primary, o.overlay} {
108 if p != nil {
109 p.colorApplied, p.colorKnown = colorBypass, true
110 }
111 }
112}
113
114// forgetColor drops what is known about the planes' colour state: the next
115// frame commit writes it. Every modeset (VT resume and recovery included)
116// calls it, since the planes' state is then not ours to assume. The async
117// probe follows a modeset and needs no call of its own.
118func (o *Output) forgetColor() {
119 for _, p := range [...]*plane{o.primary, o.overlay} {
120 if p != nil {
121 p.colorKnown = false
122 }
123 }
124}
125
126// forgetColorVerdicts drops the cached refusals: after a modeset the driver
127// is asked again.
128func (o *Output) forgetColorVerdicts() {
129 for _, p := range [...]*plane{o.primary, o.overlay} {
130 if p != nil {
131 p.verdicts = p.verdicts[:0]
132 }
133 }
134}
135
136// colorStaleFor reports whether a frame commit with primary colour pc and
137// overlay ov writes colour state: such a commit cannot be async.
138func (o *Output) colorStaleFor(pc colorUse, ov overlayWin) bool {
139 return o.primary.colorStale(pc.mode) || o.overlay.colorStale(ov.color.mode)
140}
141
142// colorCommitted records the colour state a successful synchronous frame
143// commit left.
144func (o *Output) colorCommitted(pc colorUse, ov overlayWin) {
145 if o.primary.hasColor() {
146 o.primary.colorApplied, o.primary.colorKnown, o.primary.colorFormat = pc.mode, true, pc.format
147 }
148 if o.overlay.hasColor() {
149 o.overlay.colorApplied, o.overlay.colorKnown, o.overlay.colorFormat = ov.color.mode, true, ov.color.format
150 }
151}
152
153// errColorRefused: KMS refused a frame with a plane colour pipeline that it
154// takes with Bypass. The caller composes the frame.
155var errColorRefused = errors.New("plane colour pipeline refused")
156
157// colorRefused handles a frame commit that failed with EINVAL while a plane
158// showed the pipeline: when the same frame passes TEST_ONLY with Bypass, and
159// differs from the commit in colour alone, the pipeline is the cause. The refusal is cached for each plane that showed it,
160// its buffer format and the cursor state, and the frame is not retried.
161func (o *Output) colorRefused(err error, fb uint32, fence *os.File, vrr bool, cur cursorState, ov overlayWin, rect planeRect, pc colorUse) bool {
162 if !o.colorUsed(pc, ov) {
163 return false
164 }
165 bypassed := ov
166 bypassed.color = colorUse{}
167 // The same frame as the failed commit, content hint included, with
168 // Bypass as the only difference: if the hint was the cause, this fails
169 // too and the pipeline is not blamed.
170 if !o.frameTest(fb, fence, vrr, cur, bypassed, rect, colorUse{}, true) {
171 return false
172 }
173 if pc.mode != colorBypass {
174 o.primary.setVerdict(pc.format, cur.on, false)
175 }
176 if ov.buf != 0 && ov.color.mode != colorBypass {
177 o.overlay.setVerdict(ov.color.format, cur.on, false)
178 }
179 o.renderLog.Info().Err(err).Str("connector", o.conn.name).Bool("cursor", cur.on).Msg("plane colour pipeline refused")
180 return true
181}
182
183// colorConflict handles a commit without a frame, which turned the cursor
184// on or moved it, refused while a plane applies the pipeline: KMS may refuse
185// that combination although it took each alone. The pipeline is refused for
186// that buffer format with the cursor shown, so the next frame is composed
187// (the cursor then commits with it). It reports whether it did.
188func (o *Output) colorConflict(err error, cursorOn bool) bool {
189 if !cursorOn || !errors.Is(err, unix.EINVAL) {
190 return false
191 }
192 hit := false
193 for _, p := range [...]*plane{o.primary, o.overlay} {
194 if p.hasColor() && p.colorKnown && p.colorApplied == colorSDRToPQ {
195 p.setVerdict(p.colorFormat, true, false)
196 hit = true
197 }
198 }
199 if hit {
200 o.renderLog.Info().Err(err).Str("connector", o.conn.name).Msg("plane colour pipeline refused with the cursor")
201 }
202 return hit
203}
204
205// colorAllowed tests, once per plane, buffer format and cursor state, that
206// KMS takes the SDR to PQ pipeline on p for the request test builds (the frame
207// as it would commit). A refusal is cached: the buffer is composed from then
208// on, never tested each frame.
209func (o *Output) colorAllowed(p *plane, format uint32, test func(req *atomicReq)) bool {
210 cursor := o.cursorShown()
211 if ok, known := p.verdict(format, cursor); known {
212 return ok
213 }
214 req := &o.probeReq
215 req.reset()
216 test(req)
217 p.colorProps(req, colorSDRToPQ, o.colorMult, o.ctmBlob)
218 if cursor {
219 o.cursor.props(req, o.crtc, o.cursor.desired())
220 }
221 err := o.k.commit(req, atomicTestOnly, 0)
222 if err != nil && !refused(err) {
223 // Not an answer about the pipeline (e.g. master lost): ask again.
224 return false
225 }
226 p.setVerdict(format, cursor, err == nil)
227 if err != nil {
228 o.renderLog.Info().Err(err).Uint32("format", format).Uint32("plane", p.id).Bool("cursor", cursor).Str("connector", o.conn.name).Msg("plane colour pipeline refused")
229 }
230 return err == nil
231}
232
233// setupColor makes the output's colour objects: the CTM blob and the
234// multiplier, when HDR can be on and a plane has a matched pipeline. A
235// failure drops the pipelines: SDR content is composed as before.
236func (o *Output) setupColor() {
237 if !o.hdr.wanted() {
238 // The pipelines serve HDR outputs only: SDR stays on Bypass.
239 o.dropPipelines()
240 return
241 }
242 if o.primary.pipeline == nil && (o.overlay == nil || o.overlay.pipeline == nil) {
243 return
244 }
245 blob, err := o.k.createBlob(ctmBytes(bt709ToBT2020))
246 if err != nil {
247 o.log.Warn().Err(err).Str("connector", o.conn.name).Msg("colour matrix blob; plane colour pipelines off")
248 o.dropPipelines()
249 return
250 }
251 o.ctmBlob = blob
252 o.colorMult = s3132(float64(o.hdr.settings.SDRBrightness) / 80)
253}
254
255// dropPipelines leaves the output's planes without a pipeline: SDR content
256// is composed.
257func (o *Output) dropPipelines() {
258 for _, p := range [...]*plane{o.primary, o.overlay} {
259 if p != nil {
260 p.pipeline = nil
261 }
262 }
263}
264
265// readColor finds the planes' SDR to PQ pipelines, once at output creation.
266func (o *Output) readColor() {
267 for _, p := range [...]*plane{o.primary, o.overlay} {
268 if p == nil || !p.hasColor() {
269 continue
270 }
271 cp, why := readColorPipeline(o.k, p)
272 p.pipeline = cp
273 o.log.Info().Str("connector", o.conn.name).Uint32("plane", p.id).Bool("matched", cp != nil).Str("reason", why).Msg("plane colour pipeline")
274 }
275}
276
277// colorUsed reports whether a frame with primary colour pc and overlay ov
278// has a plane show the pipeline.
279func (o *Output) colorUsed(pc colorUse, ov overlayWin) bool {
280 return pc.mode != colorBypass || ov.buf != 0 && ov.color.mode != colorBypass
281}