1package vulkan
2
3import (
4 "image"
5 "math"
6
7 "github.com/bnema/neferwl/internal/ports"
8)
9
10// Damage: an output target keeps what it holds (scene Seq, each window's
11// content Seq). When the scene is the same, only the region changed since
12// then is redrawn: every quad is clipped to it on the CPU and the pass
13// keeps the rest of the image. A changed scene, a target never drawn or
14// a history too short redraws everything.
15
16// damageRegion collects the region of a target to redraw this frame and
17// what the target will hold after it: the windows drawn, their content
18// Seq and rect.
19type damageRegion struct {
20 held *target // nil: redraw everything
21 area image.Rectangle
22 seen map[ports.WindowID]bool
23 bound image.Rectangle
24 drawn map[ports.WindowID]heldWindow
25}
26
27// heldWindow is a window a target holds: its content Seq and rect.
28type heldWindow struct {
29 seq uint64
30 rect image.Rectangle
31}
32
33func newDamage(tg *target, s ports.Scene, bounds image.Rectangle) *damageRegion {
34 d := &damageRegion{seen: map[ports.WindowID]bool{}, bound: bounds, drawn: map[ports.WindowID]heldWindow{}}
35 // Seq 0 is a scene core did not number (setup, tests): redraw all.
36 if tg.valid && s.Seq != 0 && tg.sceneSeq == s.Seq {
37 d.held = tg
38 }
39 return d
40}
41
42// all reports whether the whole target is redrawn.
43func (d *damageRegion) all() bool { return d.held == nil }
44
45func (d *damageRegion) add(r image.Rectangle) {
46 if d.held != nil {
47 d.area = d.area.Union(r.Intersect(d.bound))
48 }
49}
50
51// window records a window drawn in rect (physical): if its content changed
52// and no finer damage comes (content), the whole rect is redrawn.
53func (d *damageRegion) window(id ports.WindowID, c ports.SurfaceContent, rect image.Rectangle) {
54 if d.seen[id] {
55 return
56 }
57 d.seen[id] = true
58 d.drawn[id] = heldWindow{seq: c.Seq, rect: rect}
59 if d.held == nil {
60 return
61 }
62 hw, ok := d.held.windows[id]
63 held := hw.seq
64 if ok && held == c.Seq {
65 return
66 }
67 if !ok || c.Empty() || len(c.Children) > 0 {
68 d.add(rect)
69 return
70 }
71 rects, known := c.DamageSince(held)
72 if !known {
73 d.add(rect)
74 return
75 }
76 // Mapped by content, called next: nothing more here.
77 _ = rects
78}
79
80// content maps a root surface's buffer damage onto full (where its w×h
81// buffer is drawn), clipped to dst. Scaled buffers filter linearly: one
82// more pixel around each rect.
83func (d *damageRegion) content(id ports.WindowID, c *ports.SurfaceContent, full, dst image.Rectangle) {
84 if d.held == nil {
85 return
86 }
87 hw, ok := d.held.windows[id]
88 held := hw.seq
89 if !ok || held == c.Seq || len(c.Children) > 0 {
90 return
91 }
92 rects, known := c.DamageSince(held)
93 if !known || c.Width <= 0 || c.Height <= 0 {
94 return
95 }
96 // Linear filtering spreads a texel over half a buffer pixel around
97 // it: scale/2 target pixels, plus one for rounding.
98 pad := 0
99 if full.Dx() != c.Width || full.Dy() != c.Height {
100 s := max(float64(full.Dx())/float64(c.Width), float64(full.Dy())/float64(c.Height))
101 pad = int(math.Ceil(s/2)) + 1
102 }
103 for _, r := range rects {
104 x0 := full.Min.X + r.X*full.Dx()/c.Width - pad
105 y0 := full.Min.Y + r.Y*full.Dy()/c.Height - pad
106 x1 := full.Min.X + ((r.X+r.W)*full.Dx()+c.Width-1)/c.Width + pad
107 y1 := full.Min.Y + ((r.Y+r.H)*full.Dy()+c.Height-1)/c.Height + pad
108 d.add(image.Rect(x0, y0, x1, y1).Intersect(dst))
109 }
110}
111
112// finish adds the rects of windows the target holds that this frame does
113// not draw (e.g. a window that moved to an overlay plane and back).
114func (d *damageRegion) finish() {
115 if d.held == nil {
116 return
117 }
118 for id, hw := range d.held.windows {
119 if _, ok := d.drawn[id]; !ok {
120 d.add(hw.rect)
121 }
122 }
123}
124
125// clip limits the draws to the region; draws outside it are dropped.
126func (d *damageRegion) clip(ds []draw) []draw {
127 if d.held == nil {
128 return ds
129 }
130 out := ds[:0]
131 for _, dr := range ds {
132 r := image.Rect(int(dr.pc.rect[0]), int(dr.pc.rect[1]), int(dr.pc.rect[2]), int(dr.pc.rect[3])).Intersect(d.area)
133 if r.Empty() {
134 continue
135 }
136 dr.pc.rect = [4]int32{int32(r.Min.X), int32(r.Min.Y), int32(r.Max.X), int32(r.Max.Y)}
137 out = append(out, dr)
138 }
139 return out
140}
141
142// hold records what the target holds after this frame: only the windows
143// it drew.
144func (tg *target) hold(s ports.Scene, d *damageRegion) {
145 tg.valid, tg.sceneSeq, tg.windows = true, s.Seq, d.drawn
146}