From 52e46fa7a8b0e13836c8b12b23e3e8b631927470 Mon Sep 17 00:00:00 2001 From: Andros Fenollosa Date: Wed, 3 Jun 2026 19:48:02 +0200 Subject: [PATCH] Metal: clear bottom strip when minibuffer height changes (expose substitute) NS repaints uncovered regions through its drawRect: expose path; the Metal overlay has none, so when a multi-line echo message shrank back, stale pixels (e.g. a wrap-arrow shard in the echo fringe) survived. Detect minibuffer pixel-height changes in update_begin and clear the affected bottom strip inside the same update cycle, so the redraw and the clear land in one present without flashing. --- src/mtlterm.h | 5 +++++ src/mtlterm.m | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/mtlterm.h b/src/mtlterm.h index 949b64f5236..63732df5b55 100644 --- a/src/mtlterm.h +++ b/src/mtlterm.h @@ -174,6 +174,11 @@ typedef struct mtl_spring { the whole clear+redraw sequence is shown in one go (no flicker). */ @property (nonatomic, assign) BOOL needsPresent; +/* Expose substitute: last known pixel height of the minibuffer window. When it + changes, update_begin clears the affected bottom strip (NS relies on its + drawRect: expose path for the uncovered pixels; Metal has none). */ +@property (nonatomic, assign) int lastMiniHeight; + /* Main Emacs render cycle (renders to staticTexture) */ - (void)beginFrame; - (void)endFrame; diff --git a/src/mtlterm.m b/src/mtlterm.m index a6c65f9039a..7e27220bab5 100644 --- a/src/mtlterm.m +++ b/src/mtlterm.m @@ -2703,6 +2703,29 @@ mtl_update_begin (struct frame *f) end it cleanly before starting a new frame. */ if (fd.encoder) [fd endFrame]; [fd beginFrame]; + + /* Expose substitute: when the minibuffer (echo area) changes height, the + bottom of the layout shifts but the engine does not repaint every + uncovered pixel — it relies on an expose pass that NS gets via drawRect: + and Metal does not have. (Seen as a stale wrap-arrow shard in the echo + fringe after a multi-line message shrank back.) Clear the affected + bottom strip at the start of this same update; the update then redraws + the real rows on top, all within one present, so nothing flashes. */ + if (WINDOWP (FRAME_MINIBUF_WINDOW (f))) + { + struct window *mini = XWINDOW (FRAME_MINIBUF_WINDOW (f)); + int mh = WINDOW_PIXEL_HEIGHT (mini); + if (fd.lastMiniHeight > 0 && mh != fd.lastMiniHeight) + { + int maxh = mh > fd.lastMiniHeight ? mh : fd.lastMiniHeight; + int y0 = FRAME_PIXEL_HEIGHT (f) - maxh - FRAME_LINE_HEIGHT (f); + if (y0 < 0) y0 = 0; + [fd fillRect:NSMakeRect (0, y0, FRAME_PIXEL_WIDTH (f), + FRAME_PIXEL_HEIGHT (f) - y0) + color:ns_color_to_pixel (FRAME_BACKGROUND_COLOR (f))]; + } + fd.lastMiniHeight = mh; + } } static void