[Concept,v2,06/35] video: truetype: Use pre-allocated buffer for glyph rendering

Message ID 20251210125706.730184-7-sjg@u-boot.org
State New
Headers
Series None |

Commit Message

Simon Glass Dec. 10, 2025, 12:56 p.m. UTC
  From: Simon Glass <simon.glass@canonical.com>

The TrueType console driver calls malloc/free for every character
rendered, which causes significant memory fragmentation and allocation
traffic.

Add CONFIG_CONSOLE_TRUETYPE_GLYPH_BUF to enable a pre-allocated buffer
in the driver's private data. The buffer starts at 4KB and grows via
realloc() as needed. When rendering a glyph, use this buffer to avoid
malloc/free for normal characters.

The buffer is allocated lazily after relocation to avoid consuming
early malloc space before the full heap is available.

Add CONFIG_VIDEO_GLYPH_STATS (default y on sandbox) to track the number
of glyphs rendered. Use 'font info' to view the count.

Co-developed-by: Claude Opus 4 <noreply@anthropic.com>
Signed-off-by: Simon Glass <simon.glass@canonical.com>
---

Changes in v2:
- Rename the Kconfig to just enable the feature: always allocate

 cmd/font.c                        | 24 ++++++++++-
 doc/usage/cmd/font.rst            | 22 +++++++++-
 drivers/video/Kconfig             | 22 ++++++++++
 drivers/video/console_truetype.c  | 69 ++++++++++++++++++++++++++++---
 include/asm-generic/global_data.h | 14 +++++++
 test/cmd/font.c                   | 17 ++++++++
 6 files changed, 159 insertions(+), 9 deletions(-)
  

Patch

diff --git a/cmd/font.c b/cmd/font.c
index 384751e787a..79218779a2d 100644
--- a/cmd/font.c
+++ b/cmd/font.c
@@ -11,6 +11,16 @@ 
 #include <video.h>
 #include <video_console.h>
 
+#if CONFIG_IS_ENABLED(VIDEO_GLYPH_STATS)
+static int do_font_info(struct cmd_tbl *cmdtp, int flag, int argc,
+			char *const argv[])
+{
+	printf("glyphs rendered: %u\n", gd->glyph_count);
+
+	return 0;
+}
+#endif
+
 static int do_font_list(struct cmd_tbl *cmdtp, int flag, int argc,
 			char *const argv[])
 {
@@ -75,12 +85,22 @@  static int do_font_size(struct cmd_tbl *cmdtp, int flag, int argc,
 	return 0;
 }
 
+#if CONFIG_IS_ENABLED(VIDEO_GLYPH_STATS)
+#define FONT_INFO_HELP	"\nfont info - show glyph rendering statistics"
+#define FONT_INFO_SUB	, U_BOOT_SUBCMD_MKENT(info, 1, 1, do_font_info)
+#else
+#define FONT_INFO_HELP
+#define FONT_INFO_SUB
+#endif
+
 U_BOOT_LONGHELP(font,
 	"list       - list available fonts\n"
 	"font select <name> [<size>] - select font to use\n"
-	"font size <size> - select font size to");
+	"font size <size> - select font size to"
+	FONT_INFO_HELP);
 
 U_BOOT_CMD_WITH_SUBCMDS(font, "Fonts", font_help_text,
 	U_BOOT_SUBCMD_MKENT(list, 1, 1, do_font_list),
 	U_BOOT_SUBCMD_MKENT(select, 3, 1, do_font_select),
-	U_BOOT_SUBCMD_MKENT(size, 2, 1, do_font_size));
+	U_BOOT_SUBCMD_MKENT(size, 2, 1, do_font_size)
+	FONT_INFO_SUB);
diff --git a/doc/usage/cmd/font.rst b/doc/usage/cmd/font.rst
index 6e313e70c7a..f7a4897667b 100644
--- a/doc/usage/cmd/font.rst
+++ b/doc/usage/cmd/font.rst
@@ -14,6 +14,7 @@  Synopsis
     font list
     font select [<name> [<size>]]
     font size [<size>]
+    font info
 
 Description
 -----------
@@ -38,6 +39,14 @@  font size
 
 This changes the font size only. With no argument it shows the current size.
 
+font info
+~~~~~~~~~
+
+This shows glyph rendering statistics, specifically the number of glyphs
+rendered since the video console was set up.
+
+This subcommand requires CONFIG_VIDEO_GLYPH_STATS=y.
+
 Examples
 --------
 
@@ -52,7 +61,7 @@  Examples
     => font select cantoraone_regular 20
     =>
 
-This shows an example of selecting a bitmap font Truetype is active::
+This shows an example of selecting a bitmap font when Truetype is active::
 
     => font list
     8x16
@@ -61,12 +70,23 @@  This shows an example of selecting a bitmap font Truetype is active::
     cantoraone_regular
     => font sel 8x16
 
+This shows glyph rendering statistics::
+
+    => font info
+    glyphs rendered: 32705
+
 
 Configuration
 -------------
 
 The command is only available if CONFIG_CONSOLE_TRUETYPE=y.
 
+CONFIG_CONSOLE_TRUETYPE_GLYPH_BUF enables a pre-allocated buffer for glyph
+rendering, avoiding malloc/free per character. The buffer starts at 4KB and
+grows as needed via realloc().
+
+CONFIG_VIDEO_GLYPH_STATS enables tracking of glyph-rendering statistics.
+
 Return value
 ------------
 
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 446ce51fe27..0f99ba1845b 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -247,6 +247,28 @@  config CONSOLE_TRUETYPE_MAX_METRICS
 	  font metrics which are expensive to regenerate each time the font
 	  size changes.
 
+config CONSOLE_TRUETYPE_GLYPH_BUF
+	bool "TrueType glyph buffer to reduce malloc traffic"
+	depends on CONSOLE_TRUETYPE
+	default y
+	help
+	  Enable a pre-allocated buffer for rendering glyph bitmaps. This
+	  avoids malloc/free for each character rendered, reducing memory
+	  fragmentation and improving performance.
+
+	  The buffer starts at 4KB and grows via realloc() as needed to
+	  accommodate larger glyphs.
+
+config VIDEO_GLYPH_STATS
+	bool "Track glyph rendering statistics"
+	depends on CONSOLE_TRUETYPE
+	default y if SANDBOX
+	help
+	  Track cumulative glyph rendering statistics in global_data, so they
+	  persist across video device rebinds. This allows seeing the total
+	  count of glyphs rendered using the pre-allocated buffer vs. malloc
+	  fallback. Use 'font info' to view the statistics.
+
 config SYS_WHITE_ON_BLACK
 	bool "Display console as white on a black background"
 	default y if ARCH_AT91 || ARCH_EXYNOS || ARCH_ROCKCHIP || ARCH_TEGRA || X86 || ARCH_SUNXI
diff --git a/drivers/video/console_truetype.c b/drivers/video/console_truetype.c
index 7f5a2262b17..6e65f55d598 100644
--- a/drivers/video/console_truetype.c
+++ b/drivers/video/console_truetype.c
@@ -180,6 +180,10 @@  struct console_tt_metrics {
  * @pos_start:	Value of pos_ptr when the cursor is at the start of the text
  *	being entered by the user
  * @pos_count:	Maximum value reached by pos_ptr (initially zero)
+ * @glyph_buf:	Pre-allocated buffer for rendering glyphs. If a glyph fits,
+ *	this avoids malloc/free per character. Allocated lazily after
+ *	relocation to avoid using early malloc space.
+ * @glyph_buf_size: Current size of glyph_buf in bytes
  */
 struct console_tt_priv {
 	struct console_tt_metrics *cur_met;
@@ -190,6 +194,8 @@  struct console_tt_priv {
 	struct video_fontdata *cur_fontdata;
 	int pos_start;
 	int pos_count;
+	u8 *glyph_buf;
+	int glyph_buf_size;
 };
 
 /**
@@ -365,6 +371,7 @@  static int console_truetype_putc_xy(struct udevice *dev, uint x, uint y,
 	int advance;
 	void *start, *end, *line;
 	int row, kern;
+	bool use_buf;
 
 	/* Use fixed font if selected */
 	if (priv->cur_fontdata)
@@ -440,13 +447,61 @@  static int console_truetype_putc_xy(struct udevice *dev, uint x, uint y,
 	 * information into the render, which will return a 8-bit-per-pixel
 	 * image of the character. For empty characters, like ' ', data will
 	 * return NULL;
+	 *
+	 * Use the pre-allocated glyph buffer if large enough, falling back to
+	 * malloc for oversized glyphs. This avoids alloc/free traffic for
+	 * normal characters.
 	 */
-	data = stbtt_GetCodepointBitmapSubpixel(font, met->scale, met->scale,
-						x_shift, 0, cp, &width, &height,
-						&xoff, &yoff);
-	if (!data)
+	{
+		int ix0, iy0, ix1, iy1;
+
+		stbtt_GetCodepointBitmapBoxSubpixel(font, cp, met->scale,
+						    met->scale, x_shift, 0,
+						    &ix0, &iy0, &ix1, &iy1);
+		width = ix1 - ix0;
+		height = iy1 - iy0;
+		xoff = ix0;
+		yoff = iy0;
+	}
+	if (!width || !height)
 		return width_frac;
 
+	/*
+	 * Use the pre-allocated buffer if available and large enough. Allocate
+	 * it lazily, but only after relocation to avoid using early malloc.
+	 * Use realloc() to grow the buffer as needed.
+	 */
+	use_buf = false;
+	if (IS_ENABLED(CONFIG_CONSOLE_TRUETYPE_GLYPH_BUF) &&
+	    xpl_phase() >= PHASE_BOARD_R) {
+		int need_size = width * height;
+
+		if (need_size > priv->glyph_buf_size) {
+			int new_size = SZ_4K;
+
+			/* use the next power of 2 */
+			while (new_size < need_size)
+				new_size <<= 1;
+			priv->glyph_buf = realloc(priv->glyph_buf, new_size);
+			if (priv->glyph_buf)
+				priv->glyph_buf_size = new_size;
+		}
+		if (priv->glyph_buf) {
+			data = priv->glyph_buf;
+			use_buf = true;
+		}
+	}
+	if (!use_buf) {
+		data = malloc(width * height);
+		if (!data)
+			return width_frac;
+	}
+	gd_inc_glyph_count();
+
+	stbtt_MakeCodepointBitmapSubpixel(font, data, width, height, width,
+					  met->scale, met->scale, x_shift, 0,
+					  cp);
+
 	/* Figure out where to write the character in the frame buffer */
 	bits = data;
 	start = vid_priv->fb + y * vid_priv->line_length +
@@ -534,7 +589,8 @@  static int console_truetype_putc_xy(struct udevice *dev, uint x, uint y,
 			break;
 		}
 		default:
-			free(data);
+			if (!use_buf)
+				free(data);
 			return -ENOSYS;
 		}
 
@@ -547,7 +603,8 @@  static int console_truetype_putc_xy(struct udevice *dev, uint x, uint y,
 		     width,
 		     height);
 
-	free(data);
+	if (!use_buf)
+		free(data);
 
 	return width_frac;
 }
diff --git a/include/asm-generic/global_data.h b/include/asm-generic/global_data.h
index cff9066de53..7155f400db1 100644
--- a/include/asm-generic/global_data.h
+++ b/include/asm-generic/global_data.h
@@ -365,6 +365,12 @@  struct global_data {
 	 */
 	ulong video_bottom;
 #endif
+#if CONFIG_IS_ENABLED(VIDEO_GLYPH_STATS)
+	/**
+	 * @glyph_count: number of glyphs rendered
+	 */
+	uint glyph_count;
+#endif
 #ifdef CONFIG_BOOTSTAGE
 	/**
 	 * @bootstage: boot stage information
@@ -637,6 +643,14 @@  static_assert(sizeof(struct global_data) == GD_SIZE);
 #define gd_pager_page_len()	0
 #endif
 
+#if CONFIG_IS_ENABLED(VIDEO_GLYPH_STATS)
+#define gd_glyph_count()		gd->glyph_count
+#define gd_inc_glyph_count()		gd->glyph_count++
+#else
+#define gd_glyph_count()		0
+#define gd_inc_glyph_count()
+#endif
+
 /**
  * enum gd_flags - global data flags
  *
diff --git a/test/cmd/font.c b/test/cmd/font.c
index adfeebe920d..4991608e267 100644
--- a/test/cmd/font.c
+++ b/test/cmd/font.c
@@ -98,3 +98,20 @@  static int font_test_base(struct unit_test_state *uts)
 }
 FONT_TEST(font_test_base, UTF_SCAN_PDATA | UTF_SCAN_FDT | UTF_CONSOLE |
 	  UTF_DM);
+
+/* Test 'font info' command */
+static int font_test_info(struct unit_test_state *uts)
+{
+	int count;
+
+	if (!CONFIG_IS_ENABLED(VIDEO_GLYPH_STATS))
+		return -EAGAIN;
+
+	count = gd_glyph_count();
+	ut_assertok(run_command("font info", 0));
+	ut_assert_nextline("glyphs rendered: %u", count);
+	ut_assert_console_end();
+
+	return 0;
+}
+FONT_TEST(font_test_info, UTF_CONSOLE);