v0.3.0 cf370a0

damage.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close