refactor/kvconf 3462c76

colorop.go RawHistoryFolder

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}

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