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