v0.1.0 d9218d6

scene.go RawHistoryFolder

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 the target's physical ones.
19	scale      float64
20	bounds     image.Rectangle
21	fullscreen bool
22	draws      []draw
23}
24
25// draws walks the scene into quads in paint order.
26func (r *Renderer) draws(s ports.Scene, contents map[ports.WindowID]ports.SurfaceContent, dmg *damageRegion) []draw {
27	w := &sceneWalk{r: r, s: s, contents: contents, dmg: dmg, scale: s.Scale, bounds: image.Rect(0, 0, r.width, r.height)}
28	if w.scale <= 0 {
29		w.scale = 1
30	}
31	for _, win := range s.Windows {
32		w.fullscreen = w.fullscreen || (!win.Hidden && win.Fullscreen)
33	}
34	w.layers(false)
35	w.windows()
36	w.popups(false)
37	w.layers(true)
38	w.popups(true)
39	return w.draws
40}
41
42func (w *sceneWalk) phys(v int) int { return int(math.Round(float64(v) * w.scale)) }
43
44func (w *sceneWalk) physRect(x, y, width, height int) image.Rectangle {
45	return image.Rect(w.phys(x), w.phys(y), w.phys(x+width), w.phys(y+height))
46}
47
48// fill draws a solid rect (physical pixels).
49func (w *sceneWalk) fill(rect image.Rectangle, c [3]uint8) {
50	rect = rect.Intersect(w.bounds)
51	if rect.Empty() {
52		return
53	}
54	w.draws = append(w.draws, w.r.fillDraw(rect, c))
55}
56
57// layers draws the layer surfaces below the windows, or above them.
58// A fullscreen window hides the bottom and top layers.
59func (w *sceneWalk) layers(afterWindows bool) {
60	for _, layer := range w.s.Layers {
61		if (layer.Layer >= ports.LayerTop) != afterWindows ||
62			(w.fullscreen && (layer.Layer == ports.LayerBottom || layer.Layer == ports.LayerTop)) ||
63			layer.Rect.W <= 0 || layer.Rect.H <= 0 {
64			continue
65		}
66		content := w.contents[layer.ID]
67		w.dmg.window(layer.ID, content, w.physRect(layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H))
68		w.place(layer.ID, &content, layer.Rect.X, layer.Rect.Y, layer.Rect.W, layer.Rect.H)
69	}
70}
71
72// windows draws the tiled then floating windows with their borders.
73// Tile lines (Window 0) go over the tiles, a float's border with it.
74func (w *sceneWalk) windows() {
75	tileLines := false
76	for _, win := range w.s.Windows {
77		if win.Floating && !tileLines {
78			w.separators(0)
79			tileLines = true
80		}
81		if win.Hidden || win.Popup || win.Rect.W <= 0 || win.Rect.H <= 0 {
82			continue
83		}
84		// Content sits inside the border; core sized the client to match.
85		b, inset := 0, ports.Sides(0)
86		if !win.Fullscreen {
87			b, inset = w.s.Border.Width, win.Inset
88		}
89		c := win.Rect.Inset(inset, b)
90		body := w.physRect(c.X, c.Y, c.W, c.H)
91		content := w.contents[win.ID]
92		w.dmg.window(win.ID, content, w.physRect(win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H))
93		if content.Empty() {
94			w.fill(body, windowColor(win.ID))
95		} else {
96			w.fill(body, parseColor(w.s.Background))
97			w.place(win.ID, &content, c.X, c.Y, c.W, c.H)
98		}
99		if win.Floating {
100			w.separators(win.ID)
101		}
102	}
103	if !tileLines {
104		w.separators(0)
105	}
106}
107
108// separators draws a window's border lines in order, active lines over
109// inactive ones.
110func (w *sceneWalk) separators(id ports.WindowID) {
111	for _, sep := range w.s.Separators {
112		if sep.Window != id {
113			continue
114		}
115		col := w.s.Border.Inactive
116		if sep.Active {
117			col = w.s.Border.Active
118		}
119		if col != "" {
120			w.fill(w.physRect(sep.Rect.X, sep.Rect.Y, sep.Rect.W, sep.Rect.H), parseColor(col))
121		}
122	}
123}
124
125// popups draws only what the client drew, shadows clipped. Window popups
126// stay with the windows; a layer's go over every layer.
127func (w *sceneWalk) popups(overLayers bool) {
128	for _, win := range w.s.Windows {
129		if win.Popup && win.OverLayers == overLayers && !win.Hidden && win.Rect.W > 0 && win.Rect.H > 0 {
130			content := w.contents[win.ID]
131			w.dmg.window(win.ID, content, w.physRect(win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H))
132			w.place(win.ID, &content, win.Rect.X, win.Rect.Y, win.Rect.W, win.Rect.H)
133		}
134	}
135}
136
137// place draws a surface tree with its window geometry at (x, y) logical,
138// clipped to width×height logical: client shadows fall outside. Every
139// surface of the tree is keyed by its window and place in it; the tree's
140// Seq changes with any of them.
141func (w *sceneWalk) place(id ports.WindowID, content *ports.SurfaceContent, x, y, width, height int) {
142	clip := image.Rect(x, y, x+width, y+height)
143	ox, oy := x-content.Geometry.X, y-content.Geometry.Y
144	covers := w.opaqueChildren(content, ox, oy, clip)
145	for i := range content.Children {
146		if ch := &content.Children[i]; ch.Below {
147			w.surface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip, shmKey{id, i + 1}, content.Seq, covers)
148		}
149	}
150	w.surface(content, ox, oy, clip, shmKey{id, 0}, content.Seq, covers)
151	for i := range content.Children {
152		if ch := &content.Children[i]; !ch.Below {
153			w.surface(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip, shmKey{id, i + 1}, content.Seq, nil)
154		}
155	}
156}
157
158// opaqueChildren gives the physical rects shown by the opaque subsurfaces
159// above the root (e.g. a game's GPU surface over its window's shm buffer):
160// they hide the surfaces below them. A child whose buffer will not draw is
161// left out, so the surface below it still shows: a dmabuf that fails to
162// import (opaqueChildren pre-imports it) or an unreadable shm buffer.
163func (w *sceneWalk) opaqueChildren(content *ports.SurfaceContent, ox, oy int, clip image.Rectangle) []image.Rectangle {
164	var covers []image.Rectangle
165	for i := range content.Children {
166		ch := &content.Children[i]
167		if ch.Below || !ch.Opaque {
168			continue
169		}
170		_, dst, ok := w.surfaceRects(&ch.SurfaceContent, ox+ch.X, oy+ch.Y, clip)
171		if !ok || !w.drawable(&ch.SurfaceContent) {
172			continue
173		}
174		covers = append(covers, dst)
175	}
176	return covers
177}
178
179// drawable reports whether the child's buffer will draw. GPU buffer
180// imports are cached, so pre-importing here costs nothing.
181func (w *sceneWalk) drawable(c *ports.SurfaceContent) bool {
182	if c.DMABuf != nil {
183		_, err := w.r.importDMABuf(c.DMABuf)
184		return err == nil
185	}
186	b := c.SHM
187	if b == nil {
188		return false
189	}
190	_, err := w.r.shmPixels(b, b.Offset+(c.Height-1)*b.Stride+c.Width*4)
191	return err == nil
192}
193
194// surfaceRects gives the physical rect of a surface buffer with its origin
195// at (x, y) logical, and the part of it clip (logical) shows. ok is false
196// when there is nothing to draw.
197func (w *sceneWalk) surfaceRects(content *ports.SurfaceContent, x, y int, clip image.Rectangle) (full, dst image.Rectangle, ok bool) {
198	lw, lh := content.LogicalW, content.LogicalH
199	if lw <= 0 || lh <= 0 {
200		lw, lh = content.Width, content.Height
201	}
202	if content.SHM == nil && content.DMABuf == nil || lw <= 0 || lh <= 0 || content.Width <= 0 || content.Height <= 0 {
203		return full, dst, false
204	}
205	if content.DMABuf == nil && (content.SHM.Stride < content.Width*4 || content.SHM.Offset < 0) {
206		return full, dst, false
207	}
208	full = w.physRect(x, y, lw, lh)
209	// A buffer drawn at the physical size (fractional-scale clients round
210	// w*scale, we round per edge) is copied 1:1, never resampled for 1px.
211	near := func(a, b int) bool { return a-b <= 1 && b-a <= 1 }
212	if near(full.Dx(), content.Width) && near(full.Dy(), content.Height) {
213		full.Max = full.Min.Add(image.Pt(content.Width, content.Height))
214	}
215	dst = full.Intersect(w.physRect(clip.Min.X, clip.Min.Y, clip.Dx(), clip.Dy()))
216	return full, dst, !dst.Empty()
217}
218
219// surface draws one surface buffer with its origin at (x, y) logical,
220// clipped to clip (logical), unless an opaque surface above it (covers,
221// physical) hides all of it: its buffer is then neither copied nor drawn.
222func (w *sceneWalk) surface(content *ports.SurfaceContent, x, y int, clip image.Rectangle, key shmKey, seq uint64, covers []image.Rectangle) {
223	full, dst, ok := w.surfaceRects(content, x, y, clip)
224	if !ok {
225		return
226	}
227	for _, c := range covers {
228		if dst.In(c) {
229			return
230		}
231	}
232	if key.index == 0 {
233		w.dmg.content(key.win, content, full, dst)
234	}
235	w.content(dst, full, content, key, seq)
236}
237
238// content draws the part of a buffer mapped onto full (physical pixels)
239// that dst shows. key names the surface, seq its content.
240func (w *sceneWalk) content(dst, full image.Rectangle, content *ports.SurfaceContent, key shmKey, seq uint64) {
241	r := w.r
242	rect := dst.Intersect(w.bounds)
243	if rect.Empty() {
244		return
245	}
246	if content.DMABuf != nil {
247		im, err := r.importDMABuf(content.DMABuf)
248		if err != nil {
249			// The window shows its background until a buffer imports.
250			return
251		}
252		dr := r.contentDraw(rect, full, content.Width, content.Height, modeImage, content.Opaque)
253		dr.set, dr.im = im.set, im
254		dr.acquire = content.Acquire
255		w.draws = append(w.draws, dr)
256		return
257	}
258	b := content.SHM
259	pixels, err := r.shmPixels(b, b.Offset+(content.Height-1)*b.Stride+content.Width*4)
260	if err != nil {
261		return
262	}
263	st := shmState{w: content.Width, h: content.Height, seq: seq}
264	// Damage history is the root surface's: children copy in full.
265	var damage func(uint64) ([]ports.Rect, bool)
266	if key.index == 0 {
267		damage = content.DamageSince
268	}
269	c, err := r.shmCopyFor(key, st, pixels, b.Offset, b.Stride, damage)
270	if err != nil {
271		return
272	}
273	dr := r.contentDraw(rect, full, content.Width, content.Height, modeBuffer, content.Opaque)
274	dr.set = c.set
275	dr.pc.buf = [4]uint32{0, uint32(content.Width), uint32(content.Height), 0}
276	w.draws = append(w.draws, dr)
277}
278
279func parseColor(s string) [3]uint8 {
280	var c [3]uint8
281	if len(s) != 7 || s[0] != '#' {
282		return c
283	}
284	for i := range c {
285		v, e := strconv.ParseUint(s[1+i*2:3+i*2], 16, 8)
286		if e != nil {
287			return [3]uint8{}
288		}
289		c[i] = uint8(v)
290	}
291	return c
292}
293
294// windowColor is the placeholder color of a window with no content yet.
295func windowColor(id ports.WindowID) [3]uint8 {
296	h := math.Mod(float64(id)*0.618034, 1) * 6
297	sector := int(h)
298	f := h - float64(sector)
299	p, q, t := 0.4, 0.8*(1-0.5*f), 0.8*(1-0.5*(1-f))
300	var a, b, c float64
301	switch sector {
302	case 0:
303		a, b, c = 0.8, t, p
304	case 1:
305		a, b, c = q, 0.8, p
306	case 2:
307		a, b, c = p, 0.8, t
308	case 3:
309		a, b, c = p, q, 0.8
310	case 4:
311		a, b, c = t, p, 0.8
312	default:
313		a, b, c = 0.8, p, q
314	}
315	return [3]uint8{uint8(a * 255), uint8(b * 255), uint8(c * 255)}
316}

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 26

Tags 10

↑↓ moveenter openesc close