Skip to content

Commit 27f5567

Browse files
fix(sdl2): pixel-perfect rendering across DPI levels (#2222)
* fix(sdl2): pixel-perfect rendering across DPI levels Fixes several SDL2 rendering artefacts and adds a headless test suite: - Baseline drift: anchor glyphs via cached font ascent/descent (glyph_y = bottom_y - ascent - |descent|) instead of surface height. - AA tail erosion at attribute boundaries: lift the two-pass bg/glyph rendering to span the whole physical line in redraw-physical-line. - Oversized icon/folder/emoji glyphs: plain-text-object-p routes only base text-objects through the per-character path, leaving subclasses their scale-to-fit draw-object methods. - Stepped highlight rectangles: pin text-object height to the stable display-char-height rather than per-string SDL_ttf surface height. - DPI transitions (Retina <-> standard): handle :size-changed / :display-changed by re-deriving scale, re-opening the font, and refreshing per-view textures via *post-display-change-hooks*. Adds lem-sdl2/tests (font + drawing) wired into make test. * fix(sdl2): address contract review feedback - Move *post-display-change-hooks* defvar into the declarations section at the top of display.lisp (file_structure_rule), and reword its docstring to note it is a hook registry analogous to Lem's other *...-hooks* variables. - Export make-letter-object from lem-core/display and reference it unqualified in drawing.lisp instead of reaching through lem-core:: internal access (internal_symbol_rule).
1 parent 9aa9a78 commit 27f5567

10 files changed

Lines changed: 505 additions & 42 deletions

File tree

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ test:
100100
--eval '(asdf:test-system "lem-tests")' \
101101
--eval '(asdf:test-system "lem-vi-mode")' \
102102
--eval '(asdf:test-system "lem-transient")' \
103+
--eval '(asdf:test-system "lem-sdl2/tests")' \
103104
--quit
104105

105106
doc:

frontends/sdl2/display.lisp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
:display-window
1717
:display-char-width
1818
:display-char-height
19+
:display-font-ascent
20+
:display-font-descent
1921
:display-focus-p
2022
:create-view-texture
2123
:display-renderer
@@ -37,6 +39,9 @@
3739
:display-height
3840
:display-window-width
3941
:adapt-high-dpi-font-size
42+
:handle-display-changed
43+
:*post-display-change-hooks*
44+
:add-post-display-change-hook
4045
:change-font
4146
:with-renderer
4247
:with-display-render-target
@@ -47,6 +52,17 @@
4752

4853
(defvar *display*)
4954

55+
(defvar *post-display-change-hooks* '()
56+
"Hook variable (analogous to Lem's other `*...-hooks*' registries): a list
57+
of one-argument functions (DISPLAY) called after a DPI-driven font/char-metrics
58+
change has been applied, before `update-on-display-resized'.
59+
`lem-sdl2/view' registers a hook here to recreate per-view textures
60+
whose pixel sizes depend on `display-char-width' / `display-char-height';
61+
without it, the old view textures — sized at the *old* char metrics —
62+
leak through as scaled/clipped artifacts (most visibly the buffer
63+
occupying only the upper-left quarter of the window when moving from
64+
Retina to a 1x display).")
65+
5066
(defun current-display ()
5167
*display*)
5268

@@ -137,6 +153,16 @@ over `call-with-scratch-rect'."
137153
(defmethod display-braille-font ((display display))
138154
(font:font-braille-font (display-font display)))
139155

156+
(defmethod display-font-ascent ((display display))
157+
"Latin-normal-font ascent in pixels (always positive). Defines the row's
158+
target baseline relative to the cell top."
159+
(font:font-ascent (display-font display)))
160+
161+
(defmethod display-font-descent ((display display))
162+
"Latin-normal-font descent in pixels (SDL_ttf convention: negative number,
163+
e.g. -3 for NotoSansMono at 12pt). |descent| pixels sit below the baseline."
164+
(font:font-descent (display-font display)))
165+
140166
(defmethod display-background-color ((display display))
141167
(or (lem:parse-color lem-if:*background-color-of-drawing-window*)
142168
(slot-value display 'background-color)))
@@ -243,6 +269,15 @@ over `call-with-scratch-rect'."
243269
(* ratio (lem:config :sdl2-font-size (font:default-font-size))))
244270
nil))))
245271

272+
(defun add-post-display-change-hook (function)
273+
"Register FUNCTION (a one-arg fn of DISPLAY) on `*post-display-change-hooks*'.
274+
Idempotent."
275+
(pushnew function *post-display-change-hooks*))
276+
277+
(defun run-post-display-change-hooks (display)
278+
(dolist (hook *post-display-change-hooks*)
279+
(funcall hook display)))
280+
246281
(defmethod notify-required-redisplay ((display display))
247282
(with-renderer (display)
248283
(when (display-redraw-at-least-once-p display)
@@ -254,8 +289,33 @@ over `call-with-scratch-rect'."
254289
(adapt-high-dpi-display-scale display)
255290
#+darwin
256291
(adapt-high-dpi-font-size display)
292+
(run-post-display-change-hooks display)
257293
(lem:update-on-display-resized))))
258294

295+
(defun handle-display-changed (display)
296+
"Handle a backing-size or display change for DISPLAY.
297+
Re-creates the texture at the new drawable size, re-derives the
298+
high-DPI scale, re-opens the font at the new scale, runs
299+
`*post-display-change-hooks*' (which refresh per-view textures), and
300+
relayouts windows. Called when SDL fires `:size-changed' or
301+
`:display-changed' for the window \u2014 i.e. the window moved to a
302+
monitor with a different backing scale (Retina \u2194 standard DPI).
303+
Unlike `notify-required-redisplay' this runs the DPI adaptation
304+
unconditionally, since by the time the event fires the window has
305+
already been rendered at least once on the previous display.
306+
307+
`update-texture' grabs the display mutex non-recursively, so it must
308+
be called outside the `with-renderer' block (matching the `:resized'
309+
event handler pattern in `on-windowevent')."
310+
(update-texture display)
311+
(with-renderer (display)
312+
#+darwin
313+
(adapt-high-dpi-display-scale display)
314+
#+darwin
315+
(adapt-high-dpi-font-size display)
316+
(run-post-display-change-hooks display)
317+
(lem:update-on-display-resized)))
318+
259319
(defmethod render-fill-rect ((display display) x y width height &key color)
260320
(let ((x (* x (display-char-width display)))
261321
(y (* y (display-char-height display)))

0 commit comments

Comments
 (0)