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