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.
This commit is contained in:
Andros Fenollosa 2026-06-03 19:48:02 +02:00
parent 9c68084ac3
commit 52e46fa7a8
2 changed files with 28 additions and 0 deletions

View file

@ -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;

View file

@ -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