Commit: patch 9.2.0189: MS-Windows: opacity popups flicker during redraw in the console

0 views
Skip to first unread message

Christian Brabandt

unread,
Mar 17, 2026, 5:02:04 PM (7 hours ago) Mar 17
to vim...@googlegroups.com
patch 9.2.0189: MS-Windows: opacity popups flicker during redraw in the console

Commit: https://github.com/vim/vim/commit/019c53b37f1b2cf16825cc6f1fa38c25a6f7a9bd
Author: Yasuhiro Matsumoto <matt...@gmail.com>
Date: Tue Mar 17 20:51:22 2026 +0000

patch 9.2.0189: MS-Windows: opacity popups flicker during redraw in the console

Problem: When using transparent popups in the Win32 console, redrawing
background windows causes flickering. This happens because
the background is drawn opaquely before the popup blends
and draws on top.
Solution: Implement a Z-index mask to suppress screen_char() output for
cells covered by an opacity popup. Disable the Clear-to-EOL
(T_CE) optimization for lines overlapping these popups to
prevent accidental erasure (Yasuhiro Matsumoto).

closes: #19697
Signed-off-by: Yasuhiro Matsumoto <matt...@gmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/src/popupwin.c b/src/popupwin.c
index 23588c7ca..fe06777f7 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -4256,6 +4256,41 @@ popup_need_position_adjust(win_T *wp)
return wp->w_cursor.lnum != wp->w_popup_last_curline;
}

+// Cached array with max zindex of opacity popups covering each cell.
+// Allocated in may_update_popup_mask() when opacity popups exist.
+static short *opacity_zindex = NULL;
+static int opacity_zindex_rows = 0;
+static int opacity_zindex_cols = 0;
+
+/*
+ * Mark cells covered by opacity popup "wp" in opacity_zindex[].
+ * Stores the maximum zindex so that lower popups can be suppressed too.
+ */
+ static void
+popup_mark_opacity_zindex(win_T *wp)
+{
+ int width;
+ int height;
+ int r, c;
+
+ if (!(wp->w_popup_flags & POPF_OPACITY) || wp->w_popup_blend <= 0
+ || (wp->w_popup_flags & POPF_HIDDEN))
+ return;
+
+ width = popup_width(wp);
+ height = popup_height(wp);
+ for (r = wp->w_winrow;
+ r < wp->w_winrow + height && r < screen_Rows; ++r)
+ for (c = wp->w_wincol;
+ c < wp->w_wincol + width - wp->w_popup_leftoff
+ && c < screen_Columns; ++c)
+ {
+ int off = r * screen_Columns + c;
+ if (wp->w_zindex > opacity_zindex[off])
+ opacity_zindex[off] = wp->w_zindex;
+ }
+}
+
/*
* Force background windows to redraw rows under an opacity popup.
*/
@@ -4278,16 +4313,55 @@ redraw_win_under_opacity_popup(win_T *wp)
win_T *twp;

twp = mouse_find_win(&line_cp, &col_cp, IGNORE_POPUP);
- if (twp != NULL && line_cp < twp->w_height)
+ if (twp != NULL)
{
- linenr_T lnum;
+ if (line_cp < twp->w_height)
+ {
+ linenr_T lnum;

- (void)mouse_comp_pos(twp, &line_cp, &col_cp, &lnum, NULL);
- redrawWinline(twp, lnum);
+ (void)mouse_comp_pos(twp, &line_cp, &col_cp, &lnum, NULL);
+ redrawWinline(twp, lnum);
+ }
+ else if (line_cp == twp->w_height)
+ // Status bar line: mark for redraw to prevent
+ // opacity blend accumulation.
+ twp->w_redr_status = TRUE;
}
}
}

+
+/*
+ * Return TRUE if cell (row, col) is covered by a higher-zindex opacity popup.
+ */
+ int
+popup_is_under_opacity(int row, int col)
+{
+ if (opacity_zindex == NULL
+ || row < 0 || row >= opacity_zindex_rows
+ || col < 0 || col >= opacity_zindex_cols)
+ return FALSE;
+ return opacity_zindex[row * opacity_zindex_cols + col] > screen_zindex;
+}
+
+/*
+ * Return TRUE if any cell in row "row" from "start_col" to "end_col"
+ * (exclusive) is covered by a higher-zindex opacity popup.
+ */
+ int
+popup_is_under_opacity_range(int row, int start_col, int end_col)
+{
+ int col;
+
+ if (opacity_zindex == NULL
+ || row < 0 || row >= opacity_zindex_rows)
+ return FALSE;
+ for (col = start_col; col < end_col && col < opacity_zindex_cols; ++col)
+ if (opacity_zindex[row * opacity_zindex_cols + col] > screen_zindex)
+ return TRUE;
+ return FALSE;
+}
+
/*
* Update "popup_mask" if needed.
* Also recomputes the popup size and positions.
@@ -4330,10 +4404,58 @@ may_update_popup_mask(int type)
// wouldn't normally be redrawn. Without this, ScreenAttrs retains
// blended values from the previous cycle, causing blend accumulation.
// This must run every cycle, not just when popup_mask_refresh is set.
- FOR_ALL_POPUPWINS(wp)
- redraw_win_under_opacity_popup(wp);
- FOR_ALL_POPUPWINS_IN_TAB(curtab, wp)
- redraw_win_under_opacity_popup(wp);
+ //
+ // Also build the opacity_zindex array used by screen_char() to suppress
+ // output for cells under opacity popups during background draw.
+ {
+ int has_opacity = FALSE;
+
+ FOR_ALL_POPUPWINS(wp)
+ {
+ redraw_win_under_opacity_popup(wp);
+ if ((wp->w_popup_flags & POPF_OPACITY)
+ && wp->w_popup_blend > 0
+ && !(wp->w_popup_flags & POPF_HIDDEN))
+ has_opacity = TRUE;
+ }
+ FOR_ALL_POPUPWINS_IN_TAB(curtab, wp)
+ {
+ redraw_win_under_opacity_popup(wp);
+ if ((wp->w_popup_flags & POPF_OPACITY)
+ && wp->w_popup_blend > 0
+ && !(wp->w_popup_flags & POPF_HIDDEN))
+ has_opacity = TRUE;
+ }
+
+ if (!has_opacity)
+ {
+ VIM_CLEAR(opacity_zindex);
+ opacity_zindex_rows = 0;
+ opacity_zindex_cols = 0;
+ }
+ else
+ {
+ if (opacity_zindex_rows != screen_Rows
+ || opacity_zindex_cols != screen_Columns)
+ {
+ vim_free(opacity_zindex);
+ opacity_zindex = LALLOC_MULT(short,
+ screen_Rows * screen_Columns);
+ opacity_zindex_rows = screen_Rows;
+ opacity_zindex_cols = screen_Columns;
+ }
+ if (opacity_zindex != NULL)
+ {
+ vim_memset(opacity_zindex, 0,
+ (size_t)screen_Rows * screen_Columns * sizeof(short));
+
+ FOR_ALL_POPUPWINS(wp)
+ popup_mark_opacity_zindex(wp);
+ FOR_ALL_POPUPWINS_IN_TAB(curtab, wp)
+ popup_mark_opacity_zindex(wp);
+ }
+ }
+ }

if (!popup_mask_refresh)
return;
diff --git a/src/proto/popupwin.pro b/src/proto/popupwin.pro
index ee8efbb14..d6ac2b72a 100644
--- a/src/proto/popupwin.pro
+++ b/src/proto/popupwin.pro
@@ -52,6 +52,8 @@ win_T *find_next_popup(int lowest, int handled_flag);
int popup_do_filter(int c);
int popup_no_mapping(void);
void popup_check_cursor_pos(void);
+int popup_is_under_opacity(int row, int col);
+int popup_is_under_opacity_range(int row, int start_col, int end_col);
void may_update_popup_mask(int type);
void may_update_popup_position(void);
int popup_get_base_screen_cell(int row, int col, schar_T *linep, int *attrp, u8char_T *ucp);
diff --git a/src/screen.c b/src/screen.c
index acef4d38b..48261009f 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -600,7 +600,13 @@ screen_line(
{
ScreenLines[off_to - 1] = ' ';
ScreenLinesUC[off_to - 1] = 0;
- screen_char(off_to - 1, row, col + coloff - 1);
+ // Skip screen output when drawing an opacity popup: the
+ // background draw already output this cell, and outputting
+ // a space here would briefly erase it causing flicker.
+# ifdef FEAT_PROP_POPUP
+ if (screen_opacity_popup == NULL)
+# endif
+ screen_char(off_to - 1, row, col + coloff - 1);
}
#endif

@@ -1004,7 +1010,13 @@ skip_opacity:
ScreenLines[off_to] = ' ';
if (enc_utf8)
ScreenLinesUC[off_to] = 0;
- screen_char(off_to, row, col + coloff);
+ // Skip screen output when drawing an opacity popup: the
+ // background already has this cell, outputting a space here
+ // would briefly erase it causing flicker.
+#ifdef FEAT_PROP_POPUP
+ if (screen_opacity_popup == NULL)
+#endif
+ screen_char(off_to, row, col + coloff);
}

if (clear_width > 0
@@ -2224,6 +2236,23 @@ screen_char(unsigned off, int row, int col)
if (row >= screen_Rows || col >= screen_Columns)
return;

+#ifdef FEAT_PROP_POPUP
+ // If this cell is under a higher-zindex opacity popup, suppress
+ // output to prevent flicker. The higher popup's redraw will
+ // output the final blended result.
+ // Also suppress if this is a wide character whose second cell
+ // is under an opacity popup.
+ if (popup_is_under_opacity(row, col)
+ || (enc_utf8 && ScreenLinesUC[off] != 0
+ && utf_char2cells(ScreenLinesUC[off]) == 2
+ && col + 1 < screen_Columns
+ && popup_is_under_opacity(row, col + 1)))
+ {
+ screen_cur_col = 9999;
+ return;
+ }
+#endif
+
// Outputting a character in the last cell on the screen may scroll the
// screen up. Only do it when the "xn" termcap property is set, otherwise
// mark the character invalid (update it when scrolled up).
@@ -2331,6 +2360,15 @@ screen_char_2(unsigned off, int row, int col)
return;
}

+#ifdef FEAT_PROP_POPUP
+ // If under a higher-zindex opacity popup, suppress output.
+ if (popup_is_under_opacity(row, col))
+ {
+ screen_cur_col = 9999;
+ return;
+ }
+#endif
+
// Output the first byte normally (positions the cursor), then write the
// second byte directly.
screen_char(off, row, col);
@@ -2498,7 +2536,15 @@ screen_fill(
&& (attr == 0
|| (norm_term
&& attr <= HL_ALL
- && ((attr & ~(HL_BOLD | HL_ITALIC)) == 0))))
+ && ((attr & ~(HL_BOLD | HL_ITALIC)) == 0)))
+#ifdef FEAT_PROP_POPUP
+ // Do not use T_CE optimization if any cell in the
+ // range is under an opacity popup. The clear-to-eol
+ // command would erase the popup area on screen.
+ && !popup_is_under_opacity_range(row,
+ start_col, end_col)
+#endif
+ )
{
/*
* check if we really need to clear something
@@ -3461,6 +3507,17 @@ windgoto(int row, int col)
break;
}
}
+#ifdef FEAT_PROP_POPUP
+ // Don't output characters over opacity popup cells, it
+ // would show unblended background values.
+ if (cost < 999)
+ for (i = wouldbe_col; i < col; ++i)
+ if (popup_is_under_opacity(row, i))
+ {
+ cost = 999;
+ break;
+ }
+#endif
}

/*
diff --git a/src/testdir/dumps/Test_popupwin_opacity_wide_1.dump b/src/testdir/dumps/Test_popupwin_opacity_wide_1.dump
index 9ec6d5ba6..be464f772 100644
--- a/src/testdir/dumps/Test_popupwin_opacity_wide_1.dump
+++ b/src/testdir/dumps/Test_popupwin_opacity_wide_1.dump
@@ -1,8 +1,8 @@
>い*0&#ffffff0|え|ー@15|い|!+&| |1| @3
|い*&|え|ー@15|い|!+&| |2| @3
|い*&|え|ー@15|い|!+&| |3| @3
-|い*&|え*0#ffffff16#e000002|ー@6| |ー*0#0000000#ffffff0@7|い|!+&| |4| @3
-|い*&| +0#ffffff16#e000002|カ*&|ラ|フ|ル|な| +&|ー*&@1| |ー*0#0000000#ffffff0@7|い|!+&| |5| @3
+|い*&|え*0#ffffff16#e000002|ー@6| +&| +0#0000000#ffffff0|ー*&@7|い|!+&| |4| @3
+|い*&| +0#ffffff16#e000002|カ*&|ラ|フ|ル|な| +&|ー*&@1| +&| +0#0000000#ffffff0|ー*&@7|い|!+&| |5| @3
|い*&| +0#ffffff16#e000002|ポ*&|ッ|プ|ア|ッ|プ|で|─+&|╮| +0#0000000#ffffff0|ー*&@7|い|!+&| |6| @3
|い*&| +0#ffffff16#e000002|最*&|上|川| +&|ぼ*&|赤|い|な|│+&| +0#0000000#ffffff0|ー*&@7|い|!+&| |7| @3
|い*&| +0#ffffff16#e000002|│|あ*&|い|う|え|お|ー@1|│+&| +0#0000000#ffffff0|ー*&@7|い|!+&| |8| @3
diff --git a/src/version.c b/src/version.c
index 295f8ed0b..d7c480a67 100644
--- a/src/version.c
+++ b/src/version.c
@@ -734,6 +734,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 189,
/**/
188,
/**/
Reply all
Reply to author
Forward
0 new messages