git: 6b9da4e86ed9 - stable/14 - vt(4): New bitblt_text variant making a copy before unlocking vt_buf

From: Vladimir Kondratyev <wulf_at_FreeBSD.org>
Date: Sat, 17 Feb 2024 21:32:30 UTC
The branch stable/14 has been updated by wulf:

URL: https://cgit.FreeBSD.org/src/commit/?id=6b9da4e86ed961f297040bb9a5355e6c941f161b

commit 6b9da4e86ed961f297040bb9a5355e6c941f161b
Author:     Jean-Sébastien Pédron <dumbbell@FreeBSD.org>
AuthorDate: 2023-11-24 17:30:34 +0000
Commit:     Vladimir Kondratyev <wulf@FreeBSD.org>
CommitDate: 2024-02-17 20:58:34 +0000

    vt(4): New bitblt_text variant making a copy before unlocking vt_buf
    
    [Why]
    In the DRM drivers and the integration with vt(4), we need to execute
    DRM code outside of the vtbuf_lock. The reason is that this DRM code
    acquires locks which can't be acquired when vtbuf_lock, an MTX_SPIN
    mutex, is already held.
    
    [How]
    A vt(4) backend can now set the `vd_bitblt_after_vtbuf_unlock` flag to
    true if it wants to be called outside of vt_buf_lock.
    
    In this case, vt(4) uses an internal version of bitblt_text that uses
    the `vd_drawn` arrays, plus a new `vd_pos_to_flush` array, to track
    characters to draw/refresh. This internal version then uses the
    backend's bitblt_bmp callback to draw the characters after vt_buf has
    been unlocked.
    
    Drawing borders and CPU logos is also deferred after the vt_buf lock is
    released for the same reason.
    
    We introduce another lock (a default mutex), only used when the
    `vd_bitblt_after_vtbuf_unlock` flag is set, to replace part the role of
    the vt_buf lock and manage concurrent calls to vt_flush().
    
    The `SC_NO_CONSDRAWN` define is dropped because we now always need the
    `vd_drawn` arrays.
    
    Reviewed by:    manu
    Approved by:    manu
    Differential Revision:  https://reviews.freebsd.org/D42057
    
    (cherry picked from commit 162a2b858854656ddd6b75d290be8a640ba8f324)
---
 sys/dev/vt/vt.h      |  19 ++++++
 sys/dev/vt/vt_core.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 185 insertions(+), 14 deletions(-)

diff --git a/sys/dev/vt/vt.h b/sys/dev/vt/vt.h
index a21ecea999ec..56a28c0420c7 100644
--- a/sys/dev/vt/vt.h
+++ b/sys/dev/vt/vt.h
@@ -165,6 +165,9 @@ struct vt_device {
 	term_char_t		*vd_drawn;	/* (?) Most recent char drawn. */
 	term_color_t		*vd_drawnfg;	/* (?) Most recent fg color drawn. */
 	term_color_t		*vd_drawnbg;	/* (?) Most recent bg color drawn. */
+
+	struct mtx		 vd_flush_lock;	/* (?) vt_flush() lock. */
+	bool			*vd_pos_to_flush;/* (?) Positions to flush. */
 };
 
 #define	VD_PASTEBUF(vd)	((vd)->vd_pastebuf.vpb_buf)
@@ -175,6 +178,14 @@ struct vt_device {
 #define	VT_UNLOCK(vd)	mtx_unlock(&(vd)->vd_lock)
 #define	VT_LOCK_ASSERT(vd, what)	mtx_assert(&(vd)->vd_lock, what)
 
+#define	VT_FLUSH_LOCK(vd)	\
+    if ((vd)->vd_driver->vd_bitblt_after_vtbuf_unlock) \
+	    mtx_lock(&(vd)->vd_flush_lock)
+
+#define	VT_FLUSH_UNLOCK(vd)	\
+    if ((vd)->vd_driver->vd_bitblt_after_vtbuf_unlock) \
+	    mtx_unlock(&(vd)->vd_flush_lock)
+
 void vt_resume(struct vt_device *vd);
 void vt_resume_flush_timer(struct vt_window *vw, int ms);
 void vt_suspend(struct vt_device *vd);
@@ -376,6 +387,14 @@ struct vt_driver {
 #define	VD_PRIORITY_DUMB	10
 #define	VD_PRIORITY_GENERIC	100
 #define	VD_PRIORITY_SPECIFIC	1000
+
+	/*
+	 * Should vd_bitblt_text() be called after unlocking vtbuf? If true,
+	 * characters are copied before vd_bitblt_bmp() is called.
+	 *
+	 * This is only valid when the default bitblt_text callback is used.
+	 */
+	bool		vd_bitblt_after_vtbuf_unlock;
 };
 
 /*
diff --git a/sys/dev/vt/vt_core.c b/sys/dev/vt/vt_core.c
index 6d44c81181a3..3e4db9a1ba30 100644
--- a/sys/dev/vt/vt_core.c
+++ b/sys/dev/vt/vt_core.c
@@ -202,11 +202,10 @@ SET_DECLARE(vt_drv_set, struct vt_driver);
 
 static struct terminal	vt_consterm;
 static struct vt_window	vt_conswindow;
-#ifndef SC_NO_CONSDRAWN
 static term_char_t vt_consdrawn[PIXEL_HEIGHT(VT_FB_MAX_HEIGHT) * PIXEL_WIDTH(VT_FB_MAX_WIDTH)];
 static term_color_t vt_consdrawnfg[PIXEL_HEIGHT(VT_FB_MAX_HEIGHT) * PIXEL_WIDTH(VT_FB_MAX_WIDTH)];
 static term_color_t vt_consdrawnbg[PIXEL_HEIGHT(VT_FB_MAX_HEIGHT) * PIXEL_WIDTH(VT_FB_MAX_WIDTH)];
-#endif
+static bool vt_cons_pos_to_flush[PIXEL_HEIGHT(VT_FB_MAX_HEIGHT) * PIXEL_WIDTH(VT_FB_MAX_WIDTH)];
 struct vt_device	vt_consdev = {
 	.vd_driver = NULL,
 	.vd_softc = NULL,
@@ -228,11 +227,11 @@ struct vt_device	vt_consdev = {
 	.vd_mcursor_bg = TC_BLACK,
 #endif
 
-#ifndef SC_NO_CONSDRAWN
 	.vd_drawn = vt_consdrawn,
 	.vd_drawnfg = vt_consdrawnfg,
 	.vd_drawnbg = vt_consdrawnbg,
-#endif
+
+	.vd_pos_to_flush = vt_cons_pos_to_flush,
 };
 static term_char_t vt_constextbuf[(_VTDEFW) * (VBF_DEFAULT_HISTORY_SIZE)];
 static term_char_t *vt_constextbufrows[VBF_DEFAULT_HISTORY_SIZE];
@@ -292,6 +291,7 @@ vt_update_static(void *dummy)
 		printf("VT: init without driver.\n");
 
 	mtx_init(&main_vd->vd_lock, "vtdev", NULL, MTX_DEF);
+	mtx_init(&main_vd->vd_flush_lock, "vtdev flush", NULL, MTX_DEF);
 	cv_init(&main_vd->vd_winswitch, "vtwswt");
 }
 
@@ -1348,6 +1348,122 @@ vt_set_border(struct vt_device *vd, const term_rect_t *area,
 		    vd->vd_height - 1, 1, c);
 }
 
+static void
+vt_flush_to_buffer(struct vt_device *vd,
+    const struct vt_window *vw, const term_rect_t *area)
+{
+	unsigned int col, row;
+	term_char_t c;
+	term_color_t fg, bg;
+	size_t z;
+
+	for (row = area->tr_begin.tp_row; row < area->tr_end.tp_row; ++row) {
+		for (col = area->tr_begin.tp_col; col < area->tr_end.tp_col;
+		    ++col) {
+			z = row * PIXEL_WIDTH(VT_FB_MAX_WIDTH) + col;
+			if (z >= PIXEL_HEIGHT(VT_FB_MAX_HEIGHT) *
+			    PIXEL_WIDTH(VT_FB_MAX_WIDTH))
+				continue;
+
+			c = VTBUF_GET_FIELD(&vw->vw_buf, row, col);
+			vt_determine_colors(c,
+			    VTBUF_ISCURSOR(&vw->vw_buf, row, col), &fg, &bg);
+
+			if (vd->vd_drawn && (vd->vd_drawn[z] == c) &&
+			    vd->vd_drawnfg && (vd->vd_drawnfg[z] == fg) &&
+			    vd->vd_drawnbg && (vd->vd_drawnbg[z] == bg)) {
+				vd->vd_pos_to_flush[z] = false;
+				continue;
+			}
+
+			vd->vd_pos_to_flush[z] = true;
+
+			if (vd->vd_drawn)
+				vd->vd_drawn[z] = c;
+			if (vd->vd_drawnfg)
+				vd->vd_drawnfg[z] = fg;
+			if (vd->vd_drawnbg)
+				vd->vd_drawnbg[z] = bg;
+		}
+	}
+}
+
+static void
+vt_bitblt_buffer(struct vt_device *vd, const struct vt_window *vw,
+    const term_rect_t *area)
+{
+	unsigned int col, row, x, y;
+	struct vt_font *vf;
+	term_char_t c;
+	term_color_t fg, bg;
+	const uint8_t *pattern;
+	size_t z;
+
+	vf = vw->vw_font;
+
+	for (row = area->tr_begin.tp_row; row < area->tr_end.tp_row; ++row) {
+		for (col = area->tr_begin.tp_col; col < area->tr_end.tp_col;
+		    ++col) {
+			x = col * vf->vf_width +
+			    vw->vw_draw_area.tr_begin.tp_col;
+			y = row * vf->vf_height +
+			    vw->vw_draw_area.tr_begin.tp_row;
+
+			z = row * PIXEL_WIDTH(VT_FB_MAX_WIDTH) + col;
+			if (z >= PIXEL_HEIGHT(VT_FB_MAX_HEIGHT) *
+			    PIXEL_WIDTH(VT_FB_MAX_WIDTH))
+				continue;
+			if (!vd->vd_pos_to_flush[z])
+				continue;
+
+			c = vd->vd_drawn[z];
+			fg = vd->vd_drawnfg[z];
+			bg = vd->vd_drawnbg[z];
+
+			pattern = vtfont_lookup(vf, c);
+			vd->vd_driver->vd_bitblt_bmp(vd, vw,
+			    pattern, NULL, vf->vf_width, vf->vf_height,
+			    x, y, fg, bg);
+		}
+	}
+
+#ifndef SC_NO_CUTPASTE
+	if (!vd->vd_mshown)
+		return;
+
+	term_rect_t drawn_area;
+
+	drawn_area.tr_begin.tp_col = area->tr_begin.tp_col * vf->vf_width;
+	drawn_area.tr_begin.tp_row = area->tr_begin.tp_row * vf->vf_height;
+	drawn_area.tr_end.tp_col = area->tr_end.tp_col * vf->vf_width;
+	drawn_area.tr_end.tp_row = area->tr_end.tp_row * vf->vf_height;
+
+	if (vt_is_cursor_in_area(vd, &drawn_area)) {
+		vd->vd_driver->vd_bitblt_bmp(vd, vw,
+		    vd->vd_mcursor->map, vd->vd_mcursor->mask,
+		    vd->vd_mcursor->width, vd->vd_mcursor->height,
+		    vd->vd_mx_drawn + vw->vw_draw_area.tr_begin.tp_col,
+		    vd->vd_my_drawn + vw->vw_draw_area.tr_begin.tp_row,
+		    vd->vd_mcursor_fg, vd->vd_mcursor_bg);
+	}
+#endif
+}
+
+static void
+vt_draw_decorations(struct vt_device *vd)
+{
+	struct vt_window *vw;
+	const teken_attr_t *a;
+
+	vw = vd->vd_curwindow;
+
+	a = teken_get_curattr(&vw->vw_terminal->tm_emulator);
+	vt_set_border(vd, &vw->vw_draw_area, a->ta_bgcolor);
+
+	if (vt_draw_logo_cpus)
+		vtterm_draw_cpu_logos(vd);
+}
+
 static int
 vt_flush(struct vt_device *vd)
 {
@@ -1357,6 +1473,7 @@ vt_flush(struct vt_device *vd)
 #ifndef SC_NO_CUTPASTE
 	int cursor_was_shown, cursor_moved;
 #endif
+	bool needs_refresh;
 
 	if (inside_vt_flush && KERNEL_PANICKED())
 		return (0);
@@ -1372,8 +1489,9 @@ vt_flush(struct vt_device *vd)
 	if (((vd->vd_flags & VDF_TEXTMODE) == 0) && (vf == NULL))
 		return (0);
 
-	vtbuf_lock(&vw->vw_buf);
+	VT_FLUSH_LOCK(vd);
 
+	vtbuf_lock(&vw->vw_buf);
 	inside_vt_flush = true;
 
 #ifndef SC_NO_CUTPASTE
@@ -1417,29 +1535,63 @@ vt_flush(struct vt_device *vd)
 	vtbuf_undirty(&vw->vw_buf, &tarea);
 
 	/* Force a full redraw when the screen contents might be invalid. */
+	needs_refresh = false;
 	if (vd->vd_flags & (VDF_INVALID | VDF_SUSPENDED)) {
-		const teken_attr_t *a;
-
+		needs_refresh = true;
 		vd->vd_flags &= ~VDF_INVALID;
 
-		a = teken_get_curattr(&vw->vw_terminal->tm_emulator);
-		vt_set_border(vd, &vw->vw_draw_area, a->ta_bgcolor);
 		vt_termrect(vd, vf, &tarea);
 		if (vd->vd_driver->vd_invalidate_text)
 			vd->vd_driver->vd_invalidate_text(vd, &tarea);
-		if (vt_draw_logo_cpus)
-			vtterm_draw_cpu_logos(vd);
 	}
 
 	if (tarea.tr_begin.tp_col < tarea.tr_end.tp_col) {
-		vd->vd_driver->vd_bitblt_text(vd, vw, &tarea);
-		inside_vt_flush = false;
-		vtbuf_unlock(&vw->vw_buf);
+		if (vd->vd_driver->vd_bitblt_after_vtbuf_unlock) {
+			/*
+			 * When `vd_bitblt_after_vtbuf_unlock` is set to true,
+			 * we first remember the characters to redraw. They are
+			 * already copied to the `vd_drawn` arrays.
+			 *
+			 * We then unlock vt_buf and proceed with the actual
+			 * drawing using the backend driver.
+			 */
+			vt_flush_to_buffer(vd, vw, &tarea);
+			vtbuf_unlock(&vw->vw_buf);
+			vt_bitblt_buffer(vd, vw, &tarea);
+
+			if (needs_refresh)
+				vt_draw_decorations(vd);
+
+			/*
+			 * We can reset `inside_vt_flush` after unlocking vtbuf
+			 * here because we also hold vt_flush_lock in this code
+			 * path.
+			 */
+			inside_vt_flush = false;
+		} else {
+			/*
+			 * When `vd_bitblt_after_vtbuf_unlock` is false, we use
+			 * the backend's `vd_bitblt_text` callback directly.
+			 */
+			vd->vd_driver->vd_bitblt_text(vd, vw, &tarea);
+
+			if (needs_refresh)
+				vt_draw_decorations(vd);
+
+			inside_vt_flush = false;
+			vtbuf_unlock(&vw->vw_buf);
+		}
+
+		VT_FLUSH_UNLOCK(vd);
+
 		return (1);
 	}
 
 	inside_vt_flush = false;
 	vtbuf_unlock(&vw->vw_buf);
+
+	VT_FLUSH_UNLOCK(vd);
+
 	return (0);
 }