1package vulkan
2
3import (
4 "image"
5 "math"
6 "strconv"
7
8 "github.com/bnema/neferwl/internal/ports"
9)
10
11// sceneWalk turns one scene into quads in paint order, copying new wl_shm
12// content into GPU buffers on the way. It lives for one frame.
13type sceneWalk struct {
14 r *Renderer
15 s ports.Scene
16 contents map[ports.WindowID]ports.SurfaceContent
17 dmg *damageRegion
18 // scale maps the scene's logical pixels to scene-physical ones.
19 scale float64
20 // zoom shrinks the surfaces of the window being placed: an overview
21 // preview (ports.SceneWindow.Preview); 1 otherwise.
22 zoom float64
23 // pulse is the focus effect of the window being placed
24 // (ports.SceneWindow.FocusEffect); 0 otherwise.
25 pulse float32
26 // alpha is the opacity of the window being drawn, 1 - its Fade
27 // (ports.SceneWindow.Fade): its fills, lines and surfaces are scaled by
28 // it (premultiplied); 1 otherwise.
29 alpha float32
30 bounds image.Rectangle
31 draws []draw
32}
33
34// draws walks the scene into quads in paint order.
35func (r *Renderer) draws(s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent, dmg *damageRegion) []draw {
36 clear(r.scratchDraws) // release references from longer earlier scenes
37 // The walk stays in scene-physical space (the target's size, swapped for
38 // a rotated output); orient maps the draws to the target afterwards.
39 sw, sh := r.sceneSize(s.Transform)
40 w := &sceneWalk{r: r, s: s, contents: contents, dmg: dmg, scale: s.Scale, zoom: 1, alpha: 1, bounds: image.Rect(0, 0, sw, sh), draws: r.scratchDraws[:0]}
41 r.scratchCovers = r.scratchCovers[:0]
42 if w.scale <= 0 {
43 w.scale = 1
44 }
45 w.layers(false)
46 outputBounds := w.bounds
47 if s.WorkspaceClip != (ports.Rect{}) {
48 c := s.WorkspaceClip
49 w.bounds = w.bounds.Intersect(w.physRect(c.X, c.Y, c.W, c.H))
50 }
51 w.windows()
52 w.popups(false)
53 w.dropHints()
54 w.bounds = outputBounds
55 w.layers(true)
56 w.popups(true)
57 w.captureIndicators()
58 r.scratchDraws = w.draws
59 return w.draws
60}
61
62// captureIndicators draws the compositor's capture indicator over everything
63// (ports.CaptureIndicator). The border is four fills on the inside edge of the
64// target, so it stays visible on a monitor edge; the pill is a small rounded
65// square. Sizes are logical like the rest of the scene and scale with it. A
66// scene without indicators draws nothing: captures are rendered from scenes
67// that never carry them.
68func (w *sceneWalk) captureIndicators() {
69 if len(w.s.CaptureIndicators) == 0 {
70 return
71 }
72 col := parseColor(ports.CaptureBorderColor)
73 for _, m := range w.s.CaptureIndicators {
74 t := m.Rect
75 if t.W <= 0 || t.H <= 0 {
76 continue
77 }
78 if m.Pill {
79 w.pill(w.physRect(t.X, t.Y, t.W, t.H), col)
80 continue
81 }
82 b := ports.CaptureBorderOf(t.W, t.H)
83 if b <= 0 {
84 // Too thin for a border (core inflates such marks, so this is a
85 // 1-pixel output): a listed mark always draws something.
86 w.fill(w.physRect(t.X, t.Y, t.W, t.H), col)
87 continue
88 }
89 w.fill(w.physRect(t.X, t.Y, t.W, b), col)
90 w.fill(w.physRect(t.X, t.Y+t.H-b, t.W, b), col)
91 w.fill(w.physRect(t.X, t.Y+b, b, t.H-2*b), col)
92 w.fill(w.physRect(t.X+t.W-b, t.Y+b, b, t.H-2*b), col)
93 }
94}
95
96// dropHints fills where a dragged tile would land (ports.Scene.DropHints),
97// in the active border color, over the workspace's windows.
98func (w *sceneWalk) dropHints() {
99 if len(w.s.DropHints) == 0 {
100 return
101 }
102 col := parseColor(w.s.Border.Active)
103 for _, r := range w.s.DropHints {
104 if r.W > 0 && r.H > 0 {
105 w.fill(w.physRect(r.X, r.Y, r.W, r.H), col)
106 }
107 }
108}
109
110// pill fills a rounded square (physical pixels) with three rects: a full
111// middle and two bands narrowed by the corner radius.
112func (w *sceneWalk) pill(r image.Rectangle, c [3]uint8) {
113 rad := min(w.phys(ports.CapturePillRadius), r.Dx()/2, r.Dy()/2)
114 if rad <= 0 {
115 w.fill(r, c)
116 return
117 }
118 cut := (rad + 1) / 2
119 w.fill(image.Rect(r.Min.X+cut, r.Min.Y, r.Max.X-cut, r.Min.Y+rad), c)
120 w.fill(image.Rect(r.Min.X, r.Min.Y+rad, r.Max.X, r.Max.Y-rad), c)
121 w.fill(image.Rect(r.Min.X+cut, r.Max.Y-rad, r.Max.X-cut, r.Max.Y), c)
122}
123
124func (w *sceneWalk) phys(v int) int { return int(math.Round(float64(v) * w.scale)) }
125
126func (w *sceneWalk) physRect(x, y, width, height int) image.Rectangle {
127 return image.Rect(w.phys(x), w.phys(y), w.phys(x+width), w.phys(y+height))
128}
129
130// physRectF is physRect for fractional logical coordinates (zoomed surfaces).
131func (w *sceneWalk) physRectF(x, y, width, height float64) image.Rectangle {
132 p := func(v float64) int { return int(math.Round(v * w.scale)) }
133 return image.Rect(p(x), p(y), p(x+width), p(y+height))
134}
135
136// fill draws a solid rect (physical pixels), at the walk's alpha.
137func (w *sceneWalk) fill(rect image.Rectangle, c [3]uint8) {
138 rect = rect.Intersect(w.bounds)
139 if rect.Empty() {
140 return
141 }
142 dr := w.r.fillDraw(rect, c)
143 if w.alpha < 1 {
144 w.r.fadeSolid(&dr, w.alpha)
145 }
146 w.draws = append(w.draws, dr)
147}
148
149// dim paints a translucent black quad over rect (physical), clipped to the
150// output, at alpha scaled by the walk's alpha. Damage clipping later limits
151// it to the region being repainted, just like other fills.
152func (w *sceneWalk) dim(rect image.Rectangle, alpha float64) {
153 if rect = rect.Intersect(w.bounds); !rect.Empty() {
154 w.draws = append(w.draws, w.r.dimDraw(rect, alpha*float64(w.alpha)))
155 }
156}
157
158// layers draws the layer surfaces below the windows, or above them.
159// The scene holds only the shown ones: core hides them under a fullscreen
160// window.
161func (w *sceneWalk) layers(afterWindows bool) {
162 for _, layer := range w.s.Layers {
163 if (layer.Layer >= ports.LayerTop) != afterWindows || layer.Rect.W <= 0 || layer.Rect.H <= 0 {
164 continue
165 }
166 content := w.contents[layer.ID]
167 w.dmg.window(layer.ID, content, w.physRect(layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H))
168 w.place(layer.ID, &content, layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H)
169 }
170}
171
172// drawsAsWindow reports whether windows() paints win as a window (not
173// hidden, a popup or empty).
174func drawsAsWindow(win ports.SceneWindow) bool {
175 return !win.Hidden && !win.Popup && win.Rect.W > 0 && win.Rect.H > 0
176}
177
178// opensFloats reports whether win, once reached, closes the tiles: the tile
179// lines and the dim veil are painted under it (an overview preview of a
180// float is not one).
181func opensFloats(win ports.SceneWindow) bool {
182 return !win.Below && win.Floating && win.Preview == 0
183}
184
185// floatsStart is the index of the first window that opens the floats in s,
186// or -1 when none does: where windows() splits its paint order.
187func floatsStart(s ports.Scene) int {
188 for i, win := range s.Windows {
189 if drawsAsWindow(win) && opensFloats(win) {
190 return i
191 }
192 }
193 return -1
194}
195
196// windows draws ordered placements with borders. Tile lines go after
197// the column group, before the first float above it; an overview preview
198// of a float is not one. The veil goes under that float, or under every
199// window with DimBehind.
200func (w *sceneWalk) windows() {
201 if w.s.DimBehind && w.s.Dim > 0 {
202 w.dim(w.bounds, w.s.Dim)
203 }
204 tileLines := false
205 for _, win := range w.s.Windows {
206 if !drawsAsWindow(win) {
207 continue
208 }
209 if opensFloats(win) && !tileLines {
210 w.separators(0)
211 tileLines = true
212 if w.s.Dim > 0 && !w.s.DimBehind {
213 w.dim(w.bounds, w.s.Dim)
214 }
215 }
216 if win.Fade >= 1 {
217 // Faded out: it still opens the floats (its lines and veil are
218 // painted under where it would be), but draws nothing.
219 continue
220 }
221 w.alpha = 1 - float32(max(0, win.Fade))
222 // Content sits inside the border; core sized the client to match.
223 b, inset := 0, ports.Sides(0)
224 if !win.Fullscreen {
225 b, inset = w.s.Border.Width, win.Inset
226 }
227 c := win.Rect.Inset(inset, b)
228 body := w.physRect(c.X, c.Y, c.W, c.H)
229 content := w.contents[win.ID]
230 w.dmg.window(win.ID, content, w.physRect(win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H))
231 // Until its first buffer, a window shows the background: no flash.
232 w.fill(body, parseColor(w.s.Background))
233 if !content.Empty() {
234 // An animating frame zooms its content (Zoom); a card draws
235 // at its Preview.
236 if win.Zoom > 0 {
237 w.zoom = win.Zoom
238 } else if win.Preview > 0 {
239 w.zoom = win.Preview
240 }
241 w.pulse = float32(max(0, min(win.FocusEffect, 1)))
242 w.place(win.ID, &content, c.X, c.Y, c.W, c.H)
243 w.zoom, w.pulse = 1, 0
244 }
245 if win.Floating {
246 w.separators(win.ID)
247 }
248 if win.Dim > 0 {
249 // A stashed window peeking in: dimmed with its border.
250 w.dim(w.physRect(win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H), win.Dim)
251 }
252 w.alpha = 1
253 }
254 if !tileLines {
255 w.separators(0)
256 }
257}
258
259// separators draws a window's border lines in order, active lines over
260// inactive ones.
261func (w *sceneWalk) separators(id ports.WindowID) {
262 for _, sep := range w.s.Separators {
263 if sep.Window != id {
264 continue
265 }
266 col := w.s.Border.Inactive
267 if sep.Active {
268 col = w.s.Border.Active
269 }
270 if col != "" {
271 w.fill(w.physRect(sep.Rect.X, sep.Rect.Y, sep.Rect.W, sep.Rect.H), parseColor(col))
272 }
273 }
274}
275
276// popups draws only what the client drew, shadows clipped. Window popups
277// stay with the windows; a layer's go over every layer.
278func (w *sceneWalk) popups(overLayers bool) {
279 for _, win := range w.s.Windows {
280 if win.Popup && win.OverLayers == overLayers && !win.Hidden && win.Rect.W > 0 && win.Rect.H > 0 {
281 content := w.contents[win.ID]
282 w.dmg.window(win.ID, content, w.physRect(win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H))
283 w.place(win.ID, &content, win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H)
284 }
285 }
286}
287
288// place draws a surface tree with its window geometry at (x, y) logical,
289// clipped to width×height logical: client shadows fall outside. Every
290// surface of the tree is keyed by its stable surface identity.
291func (w *sceneWalk) place(id ports.WindowID, content *ports.SurfaceContent, x, y, width, height int) {
292 clip := image.Rect(x, y, x+width, y+height)
293 // Offsets inside the tree shrink with a zoomed (preview) window.
294 z := w.zoom
295 ox, oy := float64(x)-float64(content.Geometry.X)*z, float64(y)-float64(content.Geometry.Y)*z
296 at := func(ch *ports.Subsurface) (float64, float64) { return ox + float64(ch.X)*z, oy + float64(ch.Y)*z }
297 covers := w.opaqueChildren(content, ox, oy, clip)
298 for i := range content.Children {
299 if ch := &content.Children[i]; ch.Below {
300 cx, cy := at(ch)
301 w.surface(&ch.SurfaceContent, cx, cy, clip, shmKey{id, ch.Surface}, ch.Version, covers, false)
302 }
303 }
304 w.surface(content, ox, oy, clip, shmKey{id, content.Surface}, content.Version, covers, true)
305 for i := range content.Children {
306 if ch := &content.Children[i]; !ch.Below {
307 cx, cy := at(ch)
308 w.surface(&ch.SurfaceContent, cx, cy, clip, shmKey{id, ch.Surface}, ch.Version, nil, false)
309 }
310 }
311}
312
313// opaqueChildren gives the physical rects shown opaque by the subsurfaces
314// above the root (e.g. a game's GPU surface over its window's shm buffer):
315// they hide the surfaces below them. A child that is not wholly opaque but
316// has an opaque region (Firefox's page, whose region stops a pixel short of
317// its edges) hides the surfaces below only inside that rect. A child whose
318// buffer will not draw is left out, so the surface below it still shows: a
319// dmabuf that fails to import (opaqueChildren pre-imports it) or an
320// unreadable shm buffer.
321func (w *sceneWalk) opaqueChildren(content *ports.SurfaceContent, ox, oy float64, clip image.Rectangle) []image.Rectangle {
322 covers := w.r.scratchCovers[:0]
323 for i := range content.Children {
324 ch := &content.Children[i]
325 if ch.Below || !ch.Opaque && ch.OpaqueRect.W <= 0 {
326 continue
327 }
328 full, dst, ok := w.surfaceRects(&ch.SurfaceContent, ox+float64(ch.X)*w.zoom, oy+float64(ch.Y)*w.zoom, clip)
329 if !ok || !w.drawable(&ch.SurfaceContent) {
330 continue
331 }
332 if !ch.Opaque {
333 if dst = opaqueCover(&ch.SurfaceContent, full, dst); dst.Empty() {
334 continue
335 }
336 }
337 covers = append(covers, dst)
338 }
339 w.r.scratchCovers = covers
340 return covers
341}
342
343// opaqueCover is the part of dst (physical, what the surface shows of full)
344// that its OpaqueRect maps to. Edges round inward, so every pixel of it is
345// inside the region; an edge on the surface's own edge stays on dst's.
346func opaqueCover(c *ports.SurfaceContent, full, dst image.Rectangle) image.Rectangle {
347 lw, lh := c.LogicalW, c.LogicalH
348 if lw <= 0 || lh <= 0 {
349 lw, lh = c.Width, c.Height
350 }
351 o := c.OpaqueRect
352 if lw <= 0 || lh <= 0 || o.W <= 0 || o.H <= 0 {
353 return image.Rectangle{}
354 }
355 // ceil and floor of full.Min + v*size/n, v >= 0.
356 lo := func(min, v, size, n int) int { return min + (v*size+n-1)/n }
357 hi := func(min, v, size, n int) int { return min + v*size/n }
358 r := image.Rect(lo(full.Min.X, max(o.X, 0), full.Dx(), lw), lo(full.Min.Y, max(o.Y, 0), full.Dy(), lh),
359 hi(full.Min.X, min(o.X+o.W, lw), full.Dx(), lw), hi(full.Min.Y, min(o.Y+o.H, lh), full.Dy(), lh))
360 return r.Intersect(dst)
361}
362
363// drawable reports whether the child's buffer will draw. GPU buffer
364// imports are cached, so pre-importing here costs nothing.
365func (w *sceneWalk) drawable(c *ports.SurfaceContent) bool {
366 if c.Solid != nil {
367 return true
368 }
369 if c.DMABuf != nil {
370 _, err := w.r.importDMABuf(c.DMABuf)
371 return err == nil
372 }
373 b := c.SHM
374 if b == nil {
375 return false
376 }
377 _, err := w.r.shmPixels(b, b.Offset+(c.Height-1)*b.Stride+c.Width*4)
378 return err == nil
379}
380
381// surfaceRects gives the physical rect of a surface buffer with its origin
382// at (x, y) logical, shrunk by the walk's zoom, and the part of it clip
383// (logical) shows. ok is false when there is nothing to draw.
384func (w *sceneWalk) surfaceRects(content *ports.SurfaceContent, x, y float64, clip image.Rectangle) (full, dst image.Rectangle, ok bool) {
385 lw, lh := content.LogicalW, content.LogicalH
386 if lw <= 0 || lh <= 0 {
387 lw, lh = content.Width, content.Height
388 }
389 if !content.HasBuffer() || lw <= 0 || lh <= 0 || content.Width <= 0 || content.Height <= 0 {
390 return full, dst, false
391 }
392 if content.SHM != nil && (content.SHM.Stride < content.Width*4 || content.SHM.Offset < 0) {
393 return full, dst, false
394 }
395 full = w.physRectF(x, y, float64(lw)*w.zoom, float64(lh)*w.zoom)
396 // A buffer drawn at the physical size (fractional-scale clients round
397 // w*scale, we round per edge) is copied 1:1, never resampled for 1px.
398 near := func(a, b int) bool { return a-b <= 1 && b-a <= 1 }
399 sourceW, sourceH := content.Width, content.Height
400 if content.Source[2] > 0 {
401 sourceW, sourceH = int(content.Source[2]), int(content.Source[3])
402 }
403 sourceW, sourceH = content.Transform.Size(sourceW, sourceH)
404 if near(full.Dx(), sourceW) && near(full.Dy(), sourceH) {
405 full.Max = full.Min.Add(image.Pt(sourceW, sourceH))
406 }
407 dst = full.Intersect(w.physRect(clip.Min.X, clip.Min.Y, clip.Dx(), clip.Dy())).Intersect(w.bounds)
408 return full, dst, !dst.Empty()
409}
410
411// surface draws one surface buffer with its origin at (x, y) logical,
412// clipped to clip (logical), unless an opaque surface above it (covers,
413// physical) hides all of it: its buffer is then neither copied nor drawn.
414// A cover over only part of it clips the draw to the rest.
415func (w *sceneWalk) surface(content *ports.SurfaceContent, x, y float64, clip image.Rectangle, key shmKey, seq uint64, covers []image.Rectangle, root bool) {
416 full, dst, ok := w.surfaceRects(content, x, y, clip)
417 if !ok {
418 return
419 }
420 for _, c := range covers {
421 if dst.In(c) {
422 return
423 }
424 }
425 if root {
426 w.dmg.content(key.win, content, full, dst)
427 }
428 n := len(w.draws)
429 w.content(dst, full, content, key, seq, root)
430 if len(w.draws) == n+1 && len(covers) > 0 {
431 // The quad's mapping comes from full: only its rect shrinks.
432 d := w.draws[n]
433 w.draws = w.draws[:n]
434 w.split(d, dst, covers)
435 }
436}
437
438// Splitting a quad around covers is bounded: covers come from the client, and
439// each can cut a quad into four strips, recursively. Past these bounds the
440// quad is drawn whole: the covers above it hide the rest, at the cost of
441// overdraw.
442const (
443 maxSplitCovers = 8 // covers crossing the quad that are still split around
444 maxSplitStrips = 32 // draws one quad may become
445)
446
447// split appends d, a quad over rect, minus the covers, or the whole quad
448// when the covers cut it into too many strips.
449func (w *sceneWalk) split(d draw, rect image.Rectangle, covers []image.Rectangle) {
450 crossing := 0
451 for _, c := range covers {
452 if !rect.Intersect(c).Empty() {
453 crossing++
454 }
455 }
456 n := len(w.draws)
457 if crossing <= maxSplitCovers {
458 w.uncovered(d, rect, covers, n+maxSplitStrips)
459 if len(w.draws) <= n+maxSplitStrips {
460 return
461 }
462 w.draws = w.draws[:n]
463 }
464 w.quad(d, rect)
465}
466
467// uncovered appends d, a quad over rect, minus the covers: the strips of
468// rect around each cover, in turn. d keeps its mapping. It stops once the
469// draws pass limit, for the caller to drop them.
470func (w *sceneWalk) uncovered(d draw, rect image.Rectangle, covers []image.Rectangle, limit int) {
471 if len(w.draws) > limit {
472 return
473 }
474 for i, c := range covers {
475 in := rect.Intersect(c)
476 if in.Empty() {
477 continue
478 }
479 rest := covers[i+1:]
480 w.uncovered(d, image.Rect(rect.Min.X, rect.Min.Y, rect.Max.X, in.Min.Y), rest, limit)
481 w.uncovered(d, image.Rect(rect.Min.X, in.Max.Y, rect.Max.X, rect.Max.Y), rest, limit)
482 w.uncovered(d, image.Rect(rect.Min.X, in.Min.Y, in.Min.X, in.Max.Y), rest, limit)
483 w.uncovered(d, image.Rect(in.Max.X, in.Min.Y, rect.Max.X, in.Max.Y), rest, limit)
484 return
485 }
486 w.quad(d, rect)
487}
488
489// quad appends d over rect, unless it is empty.
490func (w *sceneWalk) quad(d draw, rect image.Rectangle) {
491 if rect.Empty() {
492 return
493 }
494 d.pc.rect = [4]int32{int32(rect.Min.X), int32(rect.Min.Y), int32(rect.Max.X), int32(rect.Max.Y)}
495 w.draws = append(w.draws, d)
496}
497
498// content draws the part of a buffer mapped onto full (physical pixels)
499// that dst shows. key names the surface, seq its content.
500func (w *sceneWalk) content(dst, full image.Rectangle, content *ports.SurfaceContent, key shmKey, seq uint64, root bool) {
501 r := w.r
502 rect := dst
503 if rect.Empty() {
504 return
505 }
506 if content.Solid != nil {
507 dr := r.solidDraw(rect, content)
508 if w.alpha < 1 {
509 r.fadeSolid(&dr, w.alpha)
510 }
511 if dr.pc.color[3] > 0 {
512 w.draws = append(w.draws, dr)
513 }
514 return
515 }
516 if content.DMABuf != nil {
517 im, err := r.importDMABuf(content.DMABuf)
518 if err != nil {
519 // The window shows its background until a buffer imports.
520 return
521 }
522 dr := r.contentDraw(rect, full, content.Width, content.Height, content.Source, content.Transform, modeImage, content.Opaque)
523 r.setContentColor(&dr, content)
524 dr.pc.color[3] *= w.alpha
525 dr.pc.mapy[2] = w.pulse
526 if im.yuv {
527 dr.pc.misc[1] |= flagYUV
528 if content.DMABuf.Format == fourcc('P', '0', '1', '0') {
529 dr.pc.misc[1] |= flagP010
530 }
531 coeff, ran := content.Color.Coefficients, content.Color.Range
532 if coeff == 0 {
533 coeff = 2 /* BT.709 */
534 }
535 if ran == 0 {
536 ran = 2 /* limited */
537 }
538 dr.pc.buf[3] = uint32(coeff) | uint32(ran)<<8 | uint32(content.Color.Chroma)<<16
539 }
540 dr.set, dr.im = im.set, im
541 dr.acquire = content.Acquire
542 w.draws = append(w.draws, dr)
543 return
544 }
545 b := content.SHM
546 st := shmState{w: content.Width, h: content.Height, seq: seq, windowSeq: content.Seq}
547 // The copy of this content, if any, needs no pool: a kept (closing)
548 // window whose pool file is closed still draws.
549 c := r.shmCached(key, st)
550 if c != nil {
551 // As a drawn pool, for Trim; nothing is mapped for it.
552 if m := r.pools[b.Pool]; m != nil {
553 m.last = r.frame
554 }
555 } else {
556 pixels, err := r.shmPixels(b, b.Offset+(content.Height-1)*b.Stride+content.Width*4)
557 if err != nil {
558 return
559 }
560 // Damage history is the root surface's: children copy in full.
561 var damage func(uint64) ([]ports.Rect, bool)
562 if root {
563 damage = content.DamageSince
564 }
565 if c, err = r.shmCopyFor(key, st, pixels, b.Offset, b.Stride, damage); err != nil {
566 return
567 }
568 }
569 dr := r.contentDraw(rect, full, content.Width, content.Height, content.Source, content.Transform, modeBuffer, content.Opaque)
570 r.setContentColor(&dr, content)
571 dr.pc.color[3] *= w.alpha
572 dr.pc.mapy[2] = w.pulse
573 dr.set = c.set
574 dr.pc.buf = [4]uint32{0, uint32(content.Width), uint32(content.Height), 0}
575 w.draws = append(w.draws, dr)
576}
577
578// setContentColor selects only color encodings the protocol accepts, and
579// the surface's fade.
580func (r *Renderer) setContentColor(dr *draw, content *ports.SurfaceContent) {
581 dr.pc.color[3] = 1 - max(0, min(1, content.Fade))
582 c := content.Color
583 if c.IsPQ2020() {
584 dr.pc.misc[1] |= flagPQ
585 } else if c.IsExtendedLinear() {
586 dr.pc.misc[1] |= flagExtendedLinear
587 } else {
588 return
589 }
590 dr.pc.color[0] = float32(r.hdrNits)
591 if dr.pc.color[0] <= 0 {
592 dr.pc.color[0] = 203
593 }
594}
595
596func parseColor(s string) [3]uint8 {
597 var c [3]uint8
598 if len(s) != 7 || s[0] != '#' {
599 return c
600 }
601 for i := range c {
602 v, e := strconv.ParseUint(s[1+i*2:3+i*2], 16, 8)
603 if e != nil {
604 return [3]uint8{}
605 }
606 c[i] = uint8(v)
607 }
608 return c
609}