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