v0.4.0 b6be3a8

hdr.go RawHistoryFolder

1package drm
2
3import (
4	"math"
5	"unsafe"
6)
7
8// HDMI static metadata type 1, embedded in struct hdr_output_metadata.
9// All coordinates use 0.00002; minimum luminance uses 0.0001 nits.
10type hdmiMetadataType1 struct {
11	EOTF, MetadataType           uint8
12	DisplayPrimaries             [3][2]uint16
13	WhitePoint                   [2]uint16
14	MaxDisplayMasteringLuminance uint16
15	MinDisplayMasteringLuminance uint16
16	MaxCLL, MaxFALL              uint16
17}
18
19type hdrOutputMetadata struct {
20	MetadataType uint32
21	HDMI         hdmiMetadataType1
22}
23
24func hdrMetadata(m Monitor) hdrOutputMetadata {
25	unit := func(v, scale float64) uint16 {
26		return uint16(math.Round(max(0, min(v*scale, math.MaxUint16))))
27	}
28	c, h := m.Chromaticity, m.HDR
29	return hdrOutputMetadata{HDMI: hdmiMetadataType1{
30		EOTF:                         2,
31		DisplayPrimaries:             [3][2]uint16{{unit(c.RedX, 50000), unit(c.RedY, 50000)}, {unit(c.GreenX, 50000), unit(c.GreenY, 50000)}, {unit(c.BlueX, 50000), unit(c.BlueY, 50000)}},
32		WhitePoint:                   [2]uint16{unit(c.WhiteX, 50000), unit(c.WhiteY, 50000)},
33		MaxDisplayMasteringLuminance: unit(h.MaxLuminance, 1),
34		MinDisplayMasteringLuminance: unit(h.MinLuminance, 10000),
35		MaxCLL:                       unit(h.MaxLuminance, 1), MaxFALL: unit(maxFrameAverage(h), 1),
36	}}
37}
38
39func maxFrameAverage(h HDRMetadata) float64 {
40	if h.MaxFrameAverage > 0 {
41		return h.MaxFrameAverage
42	}
43	return h.MaxLuminance
44}
45
46// hdrState is an output's HDR10 signal: what the display can do, what the
47// config asks, and the metadata blob KMS shows. The output goroutine owns it.
48type hdrState struct {
49	cap      hdrCapability
50	props    connectorHDRProps
51	settings HDRSettings
52	// failed disables retries until this Output is replaced; on is the
53	// currently selected signal encoding, including on VT resume.
54	on, failed bool
55	// blob is the live metadata blob and blobData its content. A blob stays
56	// alive while a modeset may still show it.
57	blob     uint32
58	blobData hdrOutputMetadata
59}
60
61// wanted reports whether the next modeset should try HDR10.
62func (h *hdrState) wanted() bool {
63	return h.settings.Enabled && h.cap.Capable && !h.failed
64}
65
66// shown reports whether an HDR modeset may still be on screen: a live blob
67// counts even after a failed SDR fallback (on is then false).
68func (h *hdrState) shown() bool { return h.on || h.blob != 0 }
69
70// hdrBlobSwap is a metadata blob replacement waiting for its modeset.
71type hdrBlobSwap struct {
72	old     uint32
73	meta    hdrOutputMetadata
74	created bool
75}
76
77// prepareBlob makes h.blob carry meta. The live blob is reused when its
78// content matches (VT resume); otherwise a replacement is created and the
79// old one stays alive until the swap is committed or rolled back.
80func (h *hdrState) prepareBlob(k kms, meta hdrOutputMetadata) (hdrBlobSwap, error) {
81	sw := hdrBlobSwap{old: h.blob, meta: meta}
82	if sw.old != 0 && meta == h.blobData {
83		return sw, nil
84	}
85	blob, err := k.createBlob(meta.bytes())
86	if err != nil {
87		return sw, err
88	}
89	h.blob, sw.created = blob, true
90	return sw, nil
91}
92
93// commitBlob runs once KMS accepted the modeset: the old blob is freed.
94func (h *hdrState) commitBlob(k kms, sw hdrBlobSwap) {
95	if !sw.created {
96		return
97	}
98	h.blobData = sw.meta
99	if sw.old != 0 {
100		_ = k.destroyBlob(sw.old)
101	}
102}
103
104// rollbackBlob restores the old blob after a refused modeset: the existing
105// modeset may still use it. Without an old blob the new one stays live.
106func (h *hdrState) rollbackBlob(k kms, sw hdrBlobSwap) {
107	if !sw.created {
108		return
109	}
110	if sw.old != 0 {
111		_ = k.destroyBlob(h.blob)
112		h.blob = sw.old
113		return
114	}
115	h.blobData = sw.meta
116}
117
118// releaseBlob frees the blob once no modeset can show it.
119func (h *hdrState) releaseBlob(k kms) {
120	if h.blob != 0 {
121		_ = k.destroyBlob(h.blob)
122	}
123	h.blob, h.blobData = 0, hdrOutputMetadata{}
124}
125
126// hdrConnectorProps sets the connector's signal metadata on a modeset or restore.
127func (o *Output) hdrConnectorProps(req *atomicReq, enabled bool) {
128	p := o.hdr.props
129	if enabled {
130		req.set(o.conn.id, p.MaxBPC, 10)
131		req.set(o.conn.id, p.Colorspace, p.BT2020Value)
132		req.set(o.conn.id, p.Metadata, uint64(o.hdr.blob))
133		return
134	}
135	req.set(o.conn.id, p.MaxBPC, p.MaxBPCValue)
136	if p.HasDefault {
137		req.set(o.conn.id, p.Colorspace, p.DefaultValue)
138	}
139	req.set(o.conn.id, p.Metadata, 0)
140}
141
142func (m *hdrOutputMetadata) bytes() []byte {
143	return unsafe.Slice((*byte)(unsafe.Pointer(m)), unsafe.Sizeof(*m))
144}

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 25

Tags 10

↑↓ moveenter openesc close