1package wayland
2
3import (
4 "math"
5
6 cm "github.com/bnema/go-wayland-bindings/server/colormanagement"
7 "github.com/bnema/go-wayland-bindings/server/wayland"
8 "github.com/bnema/purego-libwayland/server"
9)
10
11// SurfaceColor is the committed color encoding of a client surface. Set is false
12// when no explicit description was committed; the default is compositor-defined.
13type SurfaceColor struct {
14 Set bool
15 TF, Primaries, MaxCLL, MaxFALL uint32
16}
17
18func (s *surface) ColorDescription() SurfaceColor { return s.color }
19
20type colorDescription struct {
21 SurfaceColor
22 min, max, white, targetMin, targetMax uint32
23 cllSet, fallSet bool
24 identity uint64
25}
26
27const (
28 srgb = uint32(cm.WpColorManagerV1PrimariesSrgb)
29 bt2020 = uint32(cm.WpColorManagerV1PrimariesBt2020)
30 gamma22 = uint32(cm.WpColorManagerV1TransferFunctionGamma22)
31 pq = uint32(cm.WpColorManagerV1TransferFunctionSt2084Pq)
32 extLinear = uint32(cm.WpColorManagerV1TransferFunctionExtLinear)
33)
34
35func (s *Server) nextColorID() uint64 { s.colorID++; return s.colorID }
36func (s *Server) outputColor(o *output) colorDescription {
37 d := colorDescription{SurfaceColor: SurfaceColor{Set: true, TF: uint32(cm.WpColorManagerV1TransferFunctionSrgb), Primaries: srgb}, min: 2000, max: 80, white: 80, targetMin: 2000, targetMax: 80}
38 if o != nil {
39 if h, ok := s.hdrOutputs[o.name()]; ok {
40 d.TF, d.Primaries, d.white = pq, bt2020, 203
41 d.min = uint32(math.Max(0, math.Round(h.MinLuminance*10000)))
42 d.max = d.min/10000 + 10000 // PQ primary volume is fixed by ST 2084.
43 d.targetMin, d.targetMax = d.min, uint32(math.Max(1, math.Round(h.MaxLuminance)))
44 d.MaxCLL = d.targetMax
45 d.MaxFALL = uint32(math.Max(0, math.Round(h.MaxFrameAverage)))
46 d.cllSet, d.fallSet = true, true
47 }
48 }
49 // Identity changes only when the description changes, not on each query.
50 if o != nil {
51 if s.colorIdentity == nil {
52 s.colorIdentity = map[string]uint64{}
53 }
54 d.identity = s.colorIdentity[o.name()]
55 if d.identity == 0 {
56 d.identity = s.nextColorID()
57 s.colorIdentity[o.name()] = d.identity
58 }
59 }
60 return d
61}
62func registerColorManagement(d *server.Display, s *Server) error {
63 return cm.NewWpColorManagerV1Global(d, 3, func(c server.Client, v, id uint32) {
64 r, err := cm.NewWpColorManagerV1(c, int32(v), id, colorManager{s})
65 if err != nil {
66 return
67 }
68 r.SendSupportedIntent(uint32(cm.WpColorManagerV1RenderIntentPerceptual))
69 r.SendSupportedFeature(uint32(cm.WpColorManagerV1FeatureParametric))
70 if v >= 3 {
71 r.SendSupportedFeature(uint32(cm.WpColorManagerV1FeatureWindowsBt2100))
72 }
73 for _, tf := range []uint32{pq, extLinear, uint32(cm.WpColorManagerV1TransferFunctionSrgb)} {
74 r.SendSupportedTfNamed(tf)
75 }
76
77 for _, p := range []uint32{srgb, bt2020} {
78 r.SendSupportedPrimariesNamed(p)
79 }
80 r.SendDone()
81 })
82}
83
84type colorManager struct{ s *Server }
85
86func (colorManager) Destroy(*cm.WpColorManagerV1) {}
87func (m colorManager) GetOutput(r *cm.WpColorManagerV1, id uint32, out *wayland.Output) {
88 h := &colorOutput{s: m.s, o: m.s.outputOf(out)}
89 res, err := cm.NewWpColorManagementOutputV1(r.Client(), r.Version(), id, h)
90 if err != nil {
91 return
92 }
93 h.res = res
94 m.s.colorOutputs[h] = struct{}{}
95 res.OnDestroy = func() { delete(m.s.colorOutputs, h) }
96}
97func (m colorManager) GetSurface(r *cm.WpColorManagerV1, id uint32, wl *wayland.Surface) {
98 var surf *surface
99 if wl != nil {
100 surf = m.s.surfaces[wl.Resource]
101 }
102 if surf != nil && surf.colorControl != nil {
103 r.PostError(uint32(cm.WpColorManagerV1ErrorSurfaceExists), "surface already has color management")
104 return
105 }
106 h := &colorSurface{surf: surf}
107 res, err := cm.NewWpColorManagementSurfaceV1(r.Client(), r.Version(), id, h)
108 if err != nil {
109 return
110 }
111 if surf != nil {
112 surf.colorControl = h
113 res.OnDestroy = func() {
114 if surf.colorControl == h {
115 surf.colorControl = nil
116 surf.next.color = SurfaceColor{}
117 }
118 }
119 }
120}
121func (m colorManager) GetSurfaceFeedback(r *cm.WpColorManagerV1, id uint32, wl *wayland.Surface) {
122 var surf *surface
123 if wl != nil {
124 surf = m.s.surfaces[wl.Resource]
125 }
126 h := &colorFeedback{s: m.s, surf: surf}
127 res, err := cm.NewWpColorManagementSurfaceFeedbackV1(r.Client(), r.Version(), id, h)
128 if err != nil {
129 return
130 }
131 h.res = res
132 m.s.colorFeedbacks[h] = struct{}{}
133 res.OnDestroy = func() { delete(m.s.colorFeedbacks, h) }
134}
135func (colorManager) CreateIccCreator(r *cm.WpColorManagerV1, _ uint32) {
136 r.PostError(uint32(cm.WpColorManagerV1ErrorUnsupportedFeature), "ICC unsupported")
137}
138func (m colorManager) CreateParametricCreator(r *cm.WpColorManagerV1, id uint32) {
139 _, _ = cm.NewWpImageDescriptionCreatorParamsV1(r.Client(), r.Version(), id, &colorParams{s: m.s})
140}
141func (colorManager) CreateWindowsScrgb(r *cm.WpColorManagerV1, _ uint32) {
142 r.PostError(uint32(cm.WpColorManagerV1ErrorUnsupportedFeature), "scRGB unsupported")
143}
144
145// CreateWindowsBt2100 is BT.2020 primaries with the PQ transfer function,
146// as Windows drives HDR screens (DXGI HDR10 swapchains through Wine or
147// DXVK). Its reference white is BT.2408's 203 cd/m²; it has no
148// get_information.
149func (m colorManager) CreateWindowsBt2100(r *cm.WpColorManagerV1, id uint32) {
150 d := colorDescription{SurfaceColor: SurfaceColor{TF: pq, Primaries: bt2020, Set: true}, min: 50, max: 10000, white: 203}
151 d.targetMin, d.targetMax = d.min, d.max
152 m.s.createColorDescription(r.Client(), r.Version(), id, d, 0, false)
153}
154func (colorManager) GetImageDescription(r *cm.WpColorManagerV1, _ uint32, _ *cm.WpImageDescriptionReferenceV1) {
155 r.PostError(uint32(cm.WpColorManagerV1ErrorUnsupportedFeature), "references unsupported")
156}
157
158type colorOutput struct {
159 s *Server
160 o *output
161 res *cm.WpColorManagementOutputV1
162}
163
164func (*colorOutput) Destroy(*cm.WpColorManagementOutputV1) {}
165func (h *colorOutput) GetImageDescription(r *cm.WpColorManagementOutputV1, id uint32) {
166 if h.o == nil || h.s.outputByNameExact(h.o.name()) != h.o {
167 h.s.newColorDescription(r.Client(), r.Version(), id, colorDescription{}, uint32(cm.WpImageDescriptionV1CauseNoOutput))
168 return
169 }
170 h.s.newColorDescription(r.Client(), r.Version(), id, h.s.outputColor(h.o), 0)
171}
172
173type colorSurface struct{ surf *surface }
174
175func (h *colorSurface) Destroy(*cm.WpColorManagementSurfaceV1) {
176 if h.surf != nil && !h.surf.destroyed {
177 h.surf.next.color = SurfaceColor{}
178 }
179}
180func (h *colorSurface) SetImageDescription(r *cm.WpColorManagementSurfaceV1, desc *cm.WpImageDescriptionV1, intent uint32) {
181 if h.surf == nil || h.surf.destroyed {
182 r.PostError(uint32(cm.WpColorManagementSurfaceV1ErrorInert), "surface destroyed")
183 return
184 }
185 if intent != 0 {
186 r.PostError(uint32(cm.WpColorManagementSurfaceV1ErrorRenderIntent), "unsupported intent")
187 return
188 }
189 if desc == nil {
190 r.PostError(uint32(cm.WpColorManagementSurfaceV1ErrorImageDescription), "missing description")
191 return
192 }
193 d, ok := h.surf.server.colorDescriptions[desc.Resource]
194 if !ok {
195 r.PostError(uint32(cm.WpColorManagementSurfaceV1ErrorImageDescription), "description not ready")
196 return
197 }
198 h.surf.next.color = d.SurfaceColor
199 h.surf.server.log.Debug().Str("component", "wayland").Uint32("tf", d.TF).Uint32("primaries", d.Primaries).Msg("surface color description set")
200}
201func (h *colorSurface) UnsetImageDescription(r *cm.WpColorManagementSurfaceV1) {
202 if h.surf == nil || h.surf.destroyed {
203 r.PostError(uint32(cm.WpColorManagementSurfaceV1ErrorInert), "surface destroyed")
204 return
205 }
206 h.surf.next.color = SurfaceColor{}
207 h.surf.server.log.Debug().Str("component", "wayland").Msg("surface color description unset")
208}
209
210type colorFeedback struct {
211 s *Server
212 surf *surface
213 res *cm.WpColorManagementSurfaceFeedbackV1
214 last *output
215}
216
217func (*colorFeedback) Destroy(*cm.WpColorManagementSurfaceFeedbackV1) {}
218func (h *colorFeedback) preferred(r *cm.WpColorManagementSurfaceFeedbackV1, id uint32) {
219 if h.surf == nil || h.surf.destroyed {
220 r.PostError(uint32(cm.WpColorManagementSurfaceFeedbackV1ErrorInert), "surface destroyed")
221 return
222 }
223 h.s.newColorDescription(r.Client(), r.Version(), id, h.s.outputColor(h.s.outputOfSurface(h.surf)), 0)
224}
225func (h *colorFeedback) GetPreferred(r *cm.WpColorManagementSurfaceFeedbackV1, id uint32) {
226 h.preferred(r, id)
227}
228func (h *colorFeedback) GetPreferredParametric(r *cm.WpColorManagementSurfaceFeedbackV1, id uint32) {
229 h.preferred(r, id)
230}
231func (s *Server) notifyColorFeedback() {
232 for h := range s.colorFeedbacks {
233 if h.surf == nil || h.surf.destroyed {
234 continue
235 }
236 o := s.outputOfSurface(h.surf)
237 if o != h.last {
238 h.last = o
239 h.changed(s.outputColor(o).identity)
240 }
241 }
242}
243func (h *colorFeedback) changed(id uint64) {
244 if h.res.Version() >= 2 {
245 h.res.SendPreferredChanged2(uint32(id>>32), uint32(id))
246 } else {
247 h.res.SendPreferredChanged(uint32(id))
248 }
249}
250func (s *Server) colorOutputChanged(name string) {
251 if s.outputByNameExact(name) == nil {
252 return
253 }
254 if s.colorIdentity == nil {
255 s.colorIdentity = map[string]uint64{}
256 }
257 s.colorIdentity[name] = s.nextColorID()
258 for h := range s.colorOutputs {
259 if h.o != nil && h.o.name() == name && s.outputByNameExact(name) == h.o {
260 h.res.SendImageDescriptionChanged()
261 for _, r := range h.o.resources {
262 if r.Client() == h.res.Client() && r.Version() >= 2 {
263 r.SendDone()
264 }
265 }
266 }
267 }
268 for h := range s.colorFeedbacks {
269 if h.surf != nil && !h.surf.destroyed {
270 if o := s.outputOfSurface(h.surf); o != nil && o.name() == name {
271 h.changed(s.colorIdentity[name])
272 }
273 }
274 }
275}
276func (s *Server) newColorDescription(c server.Client, v int32, id uint32, d colorDescription, failure uint32) {
277 s.createColorDescription(c, v, id, d, failure, true)
278}
279func (s *Server) createColorDescription(c server.Client, v int32, id uint32, d colorDescription, failure uint32, information bool) {
280 h := &colorImage{s: s, d: d, ready: failure == 0, information: information}
281 res, err := cm.NewWpImageDescriptionV1(c, v, id, h)
282 if err != nil {
283 return
284 }
285 if failure != 0 {
286 msg := "output unavailable"
287 if failure == uint32(cm.WpImageDescriptionV1CauseUnsupported) {
288 msg = "unsupported transfer function and primaries combination"
289 }
290 res.SendFailed(failure, msg)
291 return
292 }
293 if d.identity == 0 {
294 d.identity = s.nextColorID()
295 h.d.identity = d.identity
296 }
297 s.colorDescriptions[res.Resource] = d
298 res.OnDestroy = func() { delete(s.colorDescriptions, res.Resource) }
299 if v >= 2 {
300 res.SendReady2(uint32(d.identity>>32), uint32(d.identity))
301 } else {
302 res.SendReady(uint32(d.identity))
303 }
304}
305
306type colorImage struct {
307 s *Server
308 d colorDescription
309 ready bool
310 information bool
311}
312
313func (*colorImage) Destroy(*cm.WpImageDescriptionV1) {}
314func (h *colorImage) GetInformation(r *cm.WpImageDescriptionV1, id uint32) {
315 if !h.ready {
316 r.PostError(uint32(cm.WpImageDescriptionV1ErrorNotReady), "description not ready")
317 return
318 }
319 if !h.information {
320 r.PostError(uint32(cm.WpImageDescriptionV1ErrorNoInformation), "information not allowed")
321 return
322 }
323 info, err := cm.NewWpImageDescriptionInfoV1(r.Client(), r.Version(), id, colorInfo{})
324 if err != nil {
325 return
326 }
327 d := h.d
328 coords := srgbCoords
329 if d.Primaries == bt2020 {
330 coords = bt2020Coords
331 }
332 info.SendPrimaries(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5], coords[6], coords[7])
333 info.SendPrimariesNamed(d.Primaries)
334 info.SendTfNamed(d.TF)
335 if d.min != 0 || d.max != 0 {
336 info.SendLuminances(d.min, d.max, d.white)
337 }
338 info.SendTargetPrimaries(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5], coords[6], coords[7])
339 info.SendTargetLuminance(d.targetMin, d.targetMax)
340 if d.cllSet {
341 info.SendTargetMaxCll(d.MaxCLL)
342 }
343 if d.fallSet {
344 info.SendTargetMaxFall(d.MaxFALL)
345 }
346 info.SendDone()
347 info.Destroy()
348}
349
350type colorInfo struct{}
351
352func (colorInfo) Destroy(*cm.WpImageDescriptionInfoV1) {}
353
354var srgbCoords = [8]int32{640000, 330000, 300000, 600000, 150000, 60000, 312700, 329000}
355var bt2020Coords = [8]int32{708000, 292000, 170000, 797000, 131000, 46000, 312700, 329000}
356
357type colorParams struct {
358 s *Server
359 d colorDescription
360 tfSet, primSet bool
361}
362
363func (h *colorParams) fail(r *cm.WpImageDescriptionCreatorParamsV1, code cm.WpImageDescriptionCreatorParamsV1Error, msg string) {
364 r.PostError(uint32(code), msg)
365}
366func (h *colorParams) Create(r *cm.WpImageDescriptionCreatorParamsV1, id uint32) {
367 if !h.tfSet || !h.primSet {
368 h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorIncompleteSet, "transfer and primaries required")
369 return
370 }
371 if h.d.cllSet && h.d.fallSet && h.d.MaxFALL > h.d.MaxCLL {
372 h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorInvalidLuminance, "MaxFALL exceeds MaxCLL")
373 return
374 }
375 if r.Version() == 1 {
376 for _, x := range []struct {
377 set bool
378 value uint32
379 }{{h.d.cllSet, h.d.MaxCLL}, {h.d.fallSet, h.d.MaxFALL}} {
380 if x.set && (uint64(x.value)*10000 <= uint64(h.d.targetMin) || x.value > h.d.targetMax) {
381 h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorInvalidLuminance, "content luminance outside mastering range")
382 return
383 }
384 }
385 }
386 // Advertised transfer functions and primaries do not imply that every
387 // cross-product can be rendered. Reject unsupported combinations as an
388 // image-description failure, not a protocol error.
389 validSDR := h.d.Primaries == srgb && (h.d.TF == extLinear || h.d.TF == uint32(cm.WpColorManagerV1TransferFunctionSrgb))
390 if !validSDR && !(h.d.Primaries == bt2020 && h.d.TF == pq) {
391 h.s.createColorDescription(r.Client(), r.Version(), id, h.d, uint32(cm.WpImageDescriptionV1CauseUnsupported), false)
392 return
393 }
394 h.s.createColorDescription(r.Client(), r.Version(), id, h.d, 0, false)
395}
396func (h *colorParams) SetTfNamed(r *cm.WpImageDescriptionCreatorParamsV1, tf uint32) {
397 if h.tfSet {
398 h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorAlreadySet, "TF already set")
399 return
400 }
401 if tf == gamma22 {
402 h.tfSet = true
403 h.d.TF = tf
404 h.d.Set = true
405 return // Create reports failed(unsupported) for unsupported named TFs.
406 }
407 if tf != pq && tf != extLinear && tf != uint32(cm.WpColorManagerV1TransferFunctionSrgb) {
408 h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorInvalidTf, "unsupported TF")
409 return
410 }
411 h.tfSet = true
412 h.d.TF = tf
413 h.d.Set = true
414 if tf == pq {
415 h.d.min, h.d.max, h.d.white = 50, 10000, 203
416 } else if tf == extLinear {
417 // Protocol defaults: sRGB primary volume, 80 cd/m² reference white.
418 h.d.min, h.d.max, h.d.white = 2000, 80, 80
419 } else {
420 h.d.min, h.d.max, h.d.white = 100, 100, 100
421 }
422 h.d.targetMin, h.d.targetMax = h.d.min, h.d.max
423}
424func (h *colorParams) SetTfPower(r *cm.WpImageDescriptionCreatorParamsV1, _ uint32) {
425 h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorUnsupportedFeature, "TF power unsupported")
426}
427func (h *colorParams) SetPrimariesNamed(r *cm.WpImageDescriptionCreatorParamsV1, p uint32) {
428 if h.primSet {
429 h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorAlreadySet, "primaries already set")
430 return
431 }
432 if p != srgb && p != bt2020 {
433 h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorInvalidPrimariesNamed, "unsupported primaries")
434 return
435 }
436 h.primSet = true
437 h.d.Primaries = p
438}
439func (h *colorParams) SetPrimaries(r *cm.WpImageDescriptionCreatorParamsV1, _, _, _, _, _, _, _, _ int32) {
440 h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorUnsupportedFeature, "custom primaries unsupported")
441}
442func (h *colorParams) SetLuminances(r *cm.WpImageDescriptionCreatorParamsV1, min, max, white uint32) {
443 h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorUnsupportedFeature, "custom luminances unsupported")
444}
445func (h *colorParams) SetMasteringDisplayPrimaries(r *cm.WpImageDescriptionCreatorParamsV1, _, _, _, _, _, _, _, _ int32) {
446 h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorUnsupportedFeature, "mastering primaries unsupported")
447}
448func (h *colorParams) SetMasteringLuminance(r *cm.WpImageDescriptionCreatorParamsV1, _, _ uint32) {
449 h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorUnsupportedFeature, "mastering luminance unsupported")
450}
451func (h *colorParams) SetMaxCll(_ *cm.WpImageDescriptionCreatorParamsV1, v uint32) {
452 h.d.MaxCLL = v
453 h.d.cllSet = true
454}
455func (h *colorParams) SetMaxFall(_ *cm.WpImageDescriptionCreatorParamsV1, v uint32) {
456 h.d.MaxFALL = v
457 h.d.fallSet = true
458}