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).
93// The CRTC's DEGAMMA_LUT, CTM and GAMMA_LUT are cleared with them: this
94// compositor never sets them, and a ramp left by another DRM master would
95// apply on top of the PQ output.
96func (o *Output) forceBypass(req *atomicReq) {
97	o.primary.colorProps(req, colorBypass, 0, 0)
98	o.overlay.colorProps(req, colorBypass, 0, 0)
99	for _, name := range [...]string{"DEGAMMA_LUT", "CTM", "GAMMA_LUT"} {
100		req.set(o.crtc, o.crtcProps[name], 0)
101	}
102}
103
104// overlayColorProps puts mode on the overlay plane if it applies another one.
105func (o *Output) overlayColorProps(req *atomicReq, mode colorMode) {
106	if o.overlay.colorStale(mode) {
107		o.overlay.colorProps(req, mode, o.colorMult, o.ctmBlob)
108	}
109}
110
111// colorBypassed records that a commit left both planes on Bypass.
112func (o *Output) colorBypassed() {
113	for _, p := range [...]*plane{o.primary, o.overlay} {
114		if p != nil {
115			p.colorApplied, p.colorKnown = colorBypass, true
116		}
117	}
118}
119
120// forgetColor drops what is known about the planes' colour state: the next
121// frame commit writes it. Every modeset (VT resume and recovery included)
122// calls it, since the planes' state is then not ours to assume. The async
123// probe follows a modeset and needs no call of its own.
124func (o *Output) forgetColor() {
125	for _, p := range [...]*plane{o.primary, o.overlay} {
126		if p != nil {
127			p.colorKnown = false
128		}
129	}
130}
131
132// forgetColorVerdicts drops the cached refusals: after a modeset the driver
133// is asked again.
134func (o *Output) forgetColorVerdicts() {
135	for _, p := range [...]*plane{o.primary, o.overlay} {
136		if p != nil {
137			p.verdicts = p.verdicts[:0]
138		}
139	}
140}
141
142// colorStaleFor reports whether a frame commit with primary colour pc and
143// overlay ov writes colour state: such a commit cannot be async.
144func (o *Output) colorStaleFor(pc colorUse, ov overlayWin) bool {
145	return o.primary.colorStale(pc.mode) || o.overlay.colorStale(ov.color.mode)
146}
147
148// colorCommitted records the colour state a successful synchronous frame
149// commit left.
150func (o *Output) colorCommitted(pc colorUse, ov overlayWin) {
151	if o.primary.hasColor() {
152		o.primary.colorApplied, o.primary.colorKnown, o.primary.colorFormat = pc.mode, true, pc.format
153	}
154	if o.overlay.hasColor() {
155		o.overlay.colorApplied, o.overlay.colorKnown, o.overlay.colorFormat = ov.color.mode, true, ov.color.format
156	}
157}
158
159// errColorRefused: KMS refused a frame with a plane colour pipeline that it
160// takes with Bypass. The caller composes the frame.
161var errColorRefused = errors.New("plane colour pipeline refused")
162
163// colorRefused handles a frame commit that failed with EINVAL while a plane
164// showed the pipeline: when the same frame passes TEST_ONLY with Bypass, and
165// differs from the commit in colour alone, the pipeline is the cause. The refusal is cached for each plane that showed it,
166// its buffer format and the cursor state, and the frame is not retried.
167func (o *Output) colorRefused(err error, fb uint32, fence *os.File, vrr bool, cur cursorState, ov overlayWin, rect planeRect, pc colorUse) bool {
168	if !o.colorUsed(pc, ov) {
169		return false
170	}
171	bypassed := ov
172	bypassed.color = colorUse{}
173	// The same frame as the failed commit, content hint included, with
174	// Bypass as the only difference: if the hint was the cause, this fails
175	// too and the pipeline is not blamed.
176	if !o.frameTest(fb, fence, vrr, cur, bypassed, rect, colorUse{}, true) {
177		return false
178	}
179	if pc.mode != colorBypass {
180		o.primary.setVerdict(pc.format, cur.on, false)
181	}
182	if ov.buf != 0 && ov.color.mode != colorBypass {
183		o.overlay.setVerdict(ov.color.format, cur.on, false)
184	}
185	o.renderLog.Info().Err(err).Str("connector", o.conn.name).Bool("cursor", cur.on).Msg("plane colour pipeline refused")
186	return true
187}
188
189// colorConflict handles a commit without a frame, which turned the cursor
190// on or moved it, refused while a plane applies the pipeline: KMS may refuse
191// that combination although it took each alone. The pipeline is refused for
192// that buffer format with the cursor shown, so the next frame is composed
193// (the cursor then commits with it). It reports whether it did.
194func (o *Output) colorConflict(err error, cursorOn bool) bool {
195	if !cursorOn || !errors.Is(err, unix.EINVAL) {
196		return false
197	}
198	hit := false
199	for _, p := range [...]*plane{o.primary, o.overlay} {
200		if p.hasColor() && p.colorKnown && p.colorApplied == colorSDRToPQ {
201			p.setVerdict(p.colorFormat, true, false)
202			hit = true
203		}
204	}
205	if hit {
206		o.renderLog.Info().Err(err).Str("connector", o.conn.name).Msg("plane colour pipeline refused with the cursor")
207	}
208	return hit
209}
210
211// colorAllowed tests, once per plane, buffer format and cursor state, that
212// KMS takes the SDR to PQ pipeline on p for the request test builds (the frame
213// as it would commit). A refusal is cached: the buffer is composed from then
214// on, never tested each frame.
215func (o *Output) colorAllowed(p *plane, format uint32, test func(req *atomicReq)) bool {
216	cursor := o.cursorShown()
217	if ok, known := p.verdict(format, cursor); known {
218		return ok
219	}
220	req := &o.probeReq
221	req.reset()
222	test(req)
223	p.colorProps(req, colorSDRToPQ, o.colorMult, o.ctmBlob)
224	if cursor {
225		o.cursor.props(req, o.crtc, o.cursor.desired())
226	}
227	err := o.k.commit(req, atomicTestOnly, 0)
228	if err != nil && !refused(err) {
229		// Not an answer about the pipeline (e.g. master lost): ask again.
230		return false
231	}
232	p.setVerdict(format, cursor, err == nil)
233	if err != nil {
234		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")
235	}
236	return err == nil
237}
238
239// setupColor makes the output's colour objects: the CTM blob and the
240// multiplier, when HDR can be on and a plane has a matched pipeline. A
241// failure drops the pipelines: SDR content is composed as before.
242func (o *Output) setupColor() {
243	if !o.hdr.wanted() {
244		// The pipelines serve HDR outputs only: SDR stays on Bypass.
245		o.dropPipelines()
246		return
247	}
248	if o.primary.pipeline == nil && (o.overlay == nil || o.overlay.pipeline == nil) {
249		return
250	}
251	blob, err := o.k.createBlob(ctmBytes(bt709ToBT2020))
252	if err != nil {
253		o.log.Warn().Err(err).Str("connector", o.conn.name).Msg("colour matrix blob; plane colour pipelines off")
254		o.dropPipelines()
255		return
256	}
257	o.ctmBlob = blob
258	o.colorMult = s3132(float64(o.hdr.settings.SDRBrightness) / 80)
259}
260
261// dropPipelines leaves the output's planes without a pipeline: SDR content
262// is composed.
263func (o *Output) dropPipelines() {
264	for _, p := range [...]*plane{o.primary, o.overlay} {
265		if p != nil {
266			p.pipeline = nil
267		}
268	}
269}
270
271// readColor finds the planes' SDR to PQ pipelines, once at output creation.
272func (o *Output) readColor() {
273	for _, p := range [...]*plane{o.primary, o.overlay} {
274		if p == nil || !p.hasColor() {
275			continue
276		}
277		cp, why := readColorPipeline(o.k, p)
278		p.pipeline = cp
279		o.log.Info().Str("connector", o.conn.name).Uint32("plane", p.id).Bool("matched", cp != nil).Str("reason", why).Msg("plane colour pipeline")
280	}
281}
282
283// colorUsed reports whether a frame with primary colour pc and overlay ov
284// has a plane show the pipeline.
285func (o *Output) colorUsed(pc colorUse, ov overlayWin) bool {
286	return pc.mode != colorBypass || ov.buf != 0 && ov.color.mode != colorBypass
287}

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