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}