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// frameDamage reuses the bookkeeping maps. drawn is swapped with the
43// target's previous map after hold, so a target never observes mutations.
44func (r *Renderer) frameDamage(tg *target, s ports.Scene, bounds image.Rectangle) *damageRegion {
45 if r.damageSeen == nil {
46 r.damageSeen = make(map[ports.WindowID]bool)
47 }
48 clear(r.damageSeen)
49 if r.damageDrawn == nil {
50 r.damageDrawn = make(map[ports.WindowID]heldWindow)
51 }
52 clear(r.damageDrawn)
53 r.damage = damageRegion{seen: r.damageSeen, drawn: r.damageDrawn, bound: bounds}
54 if tg.valid && s.Seq != 0 && tg.sceneSeq == s.Seq {
55 r.damage.held = tg
56 }
57 return &r.damage
58}
59
60// all reports whether the whole target is redrawn.
61func (d *damageRegion) all() bool { return d.held == nil }
62
63func (d *damageRegion) add(r image.Rectangle) {
64 if d.held != nil {
65 d.area = d.area.Union(r.Intersect(d.bound))
66 }
67}
68
69// window records a window drawn in rect (physical): if its content changed
70// and no finer damage comes (content), the whole rect is redrawn.
71func (d *damageRegion) window(id ports.WindowID, c ports.SurfaceContent, rect image.Rectangle) {
72 if d.seen[id] {
73 return
74 }
75 d.seen[id] = true
76 d.drawn[id] = heldWindow{seq: c.Seq, rect: rect}
77 if d.held == nil {
78 return
79 }
80 hw, ok := d.held.windows[id]
81 held := hw.seq
82 if ok && held == c.Seq {
83 return
84 }
85 if !ok || c.Empty() || len(c.Children) > 0 {
86 d.add(rect)
87 return
88 }
89 rects, known := c.DamageSince(held)
90 if !known {
91 d.add(rect)
92 return
93 }
94 // Mapped by content, called next: nothing more here.
95 _ = rects
96}
97
98// content maps a root surface's buffer damage onto full (where its w×h
99// buffer is drawn), clipped to dst. Scaled buffers filter linearly: one
100// more pixel around each rect. A transformed buffer redraws all of dst.
101func (d *damageRegion) content(id ports.WindowID, c *ports.SurfaceContent, full, dst image.Rectangle) {
102 if d.held == nil {
103 return
104 }
105 hw, ok := d.held.windows[id]
106 held := hw.seq
107 if !ok || held == c.Seq || len(c.Children) > 0 {
108 return
109 }
110 rects, known := c.DamageSince(held)
111 if !known || c.Width <= 0 || c.Height <= 0 {
112 return
113 }
114 if c.Transform != 0 {
115 d.add(dst)
116 return
117 }
118 // Linear filtering spreads a texel over half a buffer pixel around
119 // it: scale/2 target pixels, plus one for rounding.
120 pad := 0
121 if full.Dx() != c.Width || full.Dy() != c.Height {
122 s := max(float64(full.Dx())/float64(c.Width), float64(full.Dy())/float64(c.Height))
123 pad = int(math.Ceil(s/2)) + 1
124 }
125 for _, r := range rects {
126 x0 := full.Min.X + r.X*full.Dx()/c.Width - pad
127 y0 := full.Min.Y + r.Y*full.Dy()/c.Height - pad
128 x1 := full.Min.X + ((r.X+r.W)*full.Dx()+c.Width-1)/c.Width + pad
129 y1 := full.Min.Y + ((r.Y+r.H)*full.Dy()+c.Height-1)/c.Height + pad
130 d.add(image.Rect(x0, y0, x1, y1).Intersect(dst))
131 }
132}
133
134// finish adds the rects of windows the target holds that this frame does
135// not draw (e.g. a window that moved to an overlay plane and back).
136func (d *damageRegion) finish() {
137 if d.held == nil {
138 return
139 }
140 for id, hw := range d.held.windows {
141 if _, ok := d.drawn[id]; !ok {
142 d.add(hw.rect)
143 }
144 }
145}
146
147// clip limits the draws to the region; draws outside it are dropped.
148func (d *damageRegion) clip(ds []draw) []draw {
149 if d.held == nil {
150 return ds
151 }
152 out := ds[:0]
153 for _, dr := range ds {
154 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)
155 if r.Empty() {
156 continue
157 }
158 dr.pc.rect = [4]int32{int32(r.Min.X), int32(r.Min.Y), int32(r.Max.X), int32(r.Max.Y)}
159 out = append(out, dr)
160 }
161 return out
162}
163
164// hold records what the target holds after this frame: only the windows
165// it drew.
166func (tg *target) hold(s ports.Scene, d *damageRegion) {
167 tg.valid, tg.sceneSeq, tg.windows = true, s.Seq, d.drawn
168}