detached 8f14070

perf(drm): keep a float on the overlay plane over the lines painted under it

commit
8f14070c0e59e602e50d9cc12ab7f53f1987acef
author
brice · · 2026-10-08 19:50 UTC
parent
b0baaac
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +50 -2
M internal/adapters/drm/overlay.go +22 -2
@@ -73,6 +73,7 @@ func overlayCandidate(s ports.Scene, surfaces map[ports.WindowID]ports.SurfaceCo
7373 scale = 1
7474 }
7575 var pick *ports.SceneWindow
76+ var pickAt int
7677 var mode colorMode
7778 for i := range s.Windows {
7879 w := &s.Windows[i]
@@ -95,7 +96,7 @@ func overlayCandidate(s ports.Scene, surfaces map[ports.WindowID]ports.SurfaceCo
9596 // lift; an overview preview is drawn smaller than its buffer.
9697 if w.Dim <= 0 && w.Fade <= 0 && w.Zoom <= 0 && w.FocusEffect <= 0 && w.Preview <= 0 && c.DMABuf != nil && !isYUVFormat(c.DMABuf.Format) && c.Opaque && len(c.Children) == 0 && c.Transform == 0 && !cropped(c) && (kept == nil || !kept(w.ID)) {
9798 if m, why := planeColor(c.Color, c.DMABuf.Format, c.Opaque, hdrOn, pipeline); why == "" {
98- pick, mode = w, m
99+ pick, pickAt, mode = w, i, m
99100 continue
100101 }
101102 }
@@ -143,7 +144,7 @@ func overlayCandidate(s ports.Scene, surfaces map[ports.WindowID]ports.SurfaceCo
143144 return ports.SceneWindow{}, ports.SurfaceContent{}, colorBypass, "border"
144145 }
145146 for _, sep := range s.Separators {
146- if sep.Rect.Overlaps(pick.Rect) {
147+ if sep.Rect.Overlaps(pick.Rect) && !linesUnder(s, sep, pick, pickAt) {
147148 // The overlay shows the buffer alone; the lines would go
148149 // under it.
149150 return ports.SceneWindow{}, ports.SurfaceContent{}, colorBypass, "border"
@@ -170,6 +171,25 @@ func overlayCandidate(s ports.Scene, surfaces map[ports.WindowID]ports.SurfaceCo
170171 return *pick, c, mode, ""
171172 }
172173
174+// linesUnder reports whether sep is painted before the candidate at index
175+// pickAt, so stays composed under the plane: the tile lines and the lines
176+// of windows listed before it, when the candidate is a float drawn above
177+// the tiles.
178+func linesUnder(s ports.Scene, sep ports.Separator, pick *ports.SceneWindow, pickAt int) bool {
179+ if !pick.Floating || pick.Below {
180+ return false
181+ }
182+ if sep.Window == 0 {
183+ return true
184+ }
185+ for _, w := range s.Windows[:pickAt] {
186+ if w.ID == sep.Window {
187+ return true
188+ }
189+ }
190+ return false
191+}
192+
173193 // overlayProps puts ov on the overlay plane, or turns it off.
174194 func (o *Output) overlayProps(req *atomicReq, ov overlayWin) {
175195 p := o.overlay
@@ -73,6 +73,7 @@ func overlayCandidate(s ports.Scene, surfaces map[ports.WindowID]ports.SurfaceCo
73 scale = 173 scale = 1
74 }74 }
75 var pick *ports.SceneWindow75 var pick *ports.SceneWindow
76+ var pickAt int
76 var mode colorMode77 var mode colorMode
77 for i := range s.Windows {78 for i := range s.Windows {
78 w := &s.Windows[i]79 w := &s.Windows[i]
@@ -95,7 +96,7 @@ func overlayCandidate(s ports.Scene, surfaces map[ports.WindowID]ports.SurfaceCo
95 // lift; an overview preview is drawn smaller than its buffer.96 // lift; an overview preview is drawn smaller than its buffer.
96 if w.Dim <= 0 && w.Fade <= 0 && w.Zoom <= 0 && w.FocusEffect <= 0 && w.Preview <= 0 && c.DMABuf != nil && !isYUVFormat(c.DMABuf.Format) && c.Opaque && len(c.Children) == 0 && c.Transform == 0 && !cropped(c) && (kept == nil || !kept(w.ID)) {97 if w.Dim <= 0 && w.Fade <= 0 && w.Zoom <= 0 && w.FocusEffect <= 0 && w.Preview <= 0 && c.DMABuf != nil && !isYUVFormat(c.DMABuf.Format) && c.Opaque && len(c.Children) == 0 && c.Transform == 0 && !cropped(c) && (kept == nil || !kept(w.ID)) {
97 if m, why := planeColor(c.Color, c.DMABuf.Format, c.Opaque, hdrOn, pipeline); why == "" {98 if m, why := planeColor(c.Color, c.DMABuf.Format, c.Opaque, hdrOn, pipeline); why == "" {
98- pick, mode = w, m99+ pick, pickAt, mode = w, i, m
99 continue100 continue
100 }101 }
101 }102 }
@@ -143,7 +144,7 @@ func overlayCandidate(s ports.Scene, surfaces map[ports.WindowID]ports.SurfaceCo
143 return ports.SceneWindow{}, ports.SurfaceContent{}, colorBypass, "border"144 return ports.SceneWindow{}, ports.SurfaceContent{}, colorBypass, "border"
144 }145 }
145 for _, sep := range s.Separators {146 for _, sep := range s.Separators {
146- if sep.Rect.Overlaps(pick.Rect) {147+ if sep.Rect.Overlaps(pick.Rect) && !linesUnder(s, sep, pick, pickAt) {
147 // The overlay shows the buffer alone; the lines would go148 // The overlay shows the buffer alone; the lines would go
148 // under it.149 // under it.
149 return ports.SceneWindow{}, ports.SurfaceContent{}, colorBypass, "border"150 return ports.SceneWindow{}, ports.SurfaceContent{}, colorBypass, "border"
@@ -170,6 +171,25 @@ func overlayCandidate(s ports.Scene, surfaces map[ports.WindowID]ports.SurfaceCo
170 return *pick, c, mode, ""171 return *pick, c, mode, ""
171 }172 }
172 173
174+// linesUnder reports whether sep is painted before the candidate at index
175+// pickAt, so stays composed under the plane: the tile lines and the lines
176+// of windows listed before it, when the candidate is a float drawn above
177+// the tiles.
178+func linesUnder(s ports.Scene, sep ports.Separator, pick *ports.SceneWindow, pickAt int) bool {
179+ if !pick.Floating || pick.Below {
180+ return false
181+ }
182+ if sep.Window == 0 {
183+ return true
184+ }
185+ for _, w := range s.Windows[:pickAt] {
186+ if w.ID == sep.Window {
187+ return true
188+ }
189+ }
190+ return false
191+}
192+
173 // overlayProps puts ov on the overlay plane, or turns it off.193 // overlayProps puts ov on the overlay plane, or turns it off.
174 func (o *Output) overlayProps(req *atomicReq, ov overlayWin) {194 func (o *Output) overlayProps(req *atomicReq, ov overlayWin) {
175 p := o.overlay195 p := o.overlay
M internal/adapters/drm/overlay_test.go +28 -0
@@ -271,6 +271,34 @@ func TestOverlayPopupAndBorder(t *testing.T) {
271271 }
272272 }
273273
274+// A float above the tiles covers the tile lines and the lines of windows
275+// listed before it; its own lines and those of later windows still block
276+// the overlay.
277+func TestOverlayFloatOverLines(t *testing.T) {
278+ for _, tc := range []struct {
279+ name string
280+ sep ports.WindowID
281+ below bool
282+ reason string
283+ }{
284+ {"tile line", 0, false, ""},
285+ {"earlier window line", 1, false, ""},
286+ {"own line", 2, false, "border"},
287+ {"later window line", 3, false, "border"},
288+ {"tile line over a below float", 0, true, "border"},
289+ } {
290+ t.Run(tc.name, func(t *testing.T) {
291+ s, c := overlayScene()
292+ s.Windows[1].Floating, s.Windows[1].Below = true, tc.below
293+ s.Windows = append(s.Windows, ports.SceneWindow{ID: 3, Floating: true, Rect: ports.Rect{W: 10, H: 10}})
294+ s.Separators = []ports.Separator{{Rect: ports.Rect{X: 150, W: 2, H: 100}, Window: tc.sep}}
295+ if _, _, _, reason := overlayCandidate(s, c, false, nil, nil); reason != tc.reason {
296+ t.Fatalf("reason %q, want %q", reason, tc.reason)
297+ }
298+ })
299+ }
300+}
301+
274302 // What is drawn elsewhere on screen stays composed on the primary plane: a
275303 // bar, a popup, another window's lines or a window that do not touch the
276304 // candidate leave it on the overlay.
@@ -271,6 +271,34 @@ func TestOverlayPopupAndBorder(t *testing.T) {
271 }271 }
272 }272 }
273 273
274+// A float above the tiles covers the tile lines and the lines of windows
275+// listed before it; its own lines and those of later windows still block
276+// the overlay.
277+func TestOverlayFloatOverLines(t *testing.T) {
278+ for _, tc := range []struct {
279+ name string
280+ sep ports.WindowID
281+ below bool
282+ reason string
283+ }{
284+ {"tile line", 0, false, ""},
285+ {"earlier window line", 1, false, ""},
286+ {"own line", 2, false, "border"},
287+ {"later window line", 3, false, "border"},
288+ {"tile line over a below float", 0, true, "border"},
289+ } {
290+ t.Run(tc.name, func(t *testing.T) {
291+ s, c := overlayScene()
292+ s.Windows[1].Floating, s.Windows[1].Below = true, tc.below
293+ s.Windows = append(s.Windows, ports.SceneWindow{ID: 3, Floating: true, Rect: ports.Rect{W: 10, H: 10}})
294+ s.Separators = []ports.Separator{{Rect: ports.Rect{X: 150, W: 2, H: 100}, Window: tc.sep}}
295+ if _, _, _, reason := overlayCandidate(s, c, false, nil, nil); reason != tc.reason {
296+ t.Fatalf("reason %q, want %q", reason, tc.reason)
297+ }
298+ })
299+ }
300+}
301+
274 // What is drawn elsewhere on screen stays composed on the primary plane: a302 // What is drawn elsewhere on screen stays composed on the primary plane: a
275 // bar, a popup, another window's lines or a window that do not touch the303 // bar, a popup, another window's lines or a window that do not touch the
276 // candidate leave it on the overlay.304 // candidate leave it on the overlay.

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 23

Tags 10

↑↓ moveenter openesc close