1. 字体渲染基础:像素与矢量字体的区别、TouchGFX字体引擎架构、字体缓存机制
做嵌入式UI,绕不开字体。你想想看,一个界面好不好看,字体占了半壁江山。我刚开始用TouchGFX时,就踩过字体的坑——显示模糊、内存爆掉、切换卡顿。后来才明白,根子在于没搞懂字体渲染的基本原理。
这一节,咱们就把字体渲染的底裤扒干净。从像素字体和矢量字体的区别讲起,再深入TouchGFX的字体引擎,最后聊聊缓存机制。嗯,都是实战中必须啃下的硬骨头。
1.1 像素字体 vs 矢量字体:本质区别在哪?
说白了,像素字体就是点阵图。每个字符对应一张固定大小的位图。比如一个16x16的汉字,就是256个像素点,哪个点亮哪个不亮,写死了。
矢量字体呢?存的是数学曲线。用贝塞尔曲线描述字符轮廓,渲染时再计算哪些像素该填色。TTF、OTF都是矢量格式。
| 对比维度 | 像素字体(点阵) | 矢量字体(轮廓) |
|---|---|---|
| 存储方式 | 每个字号存一张位图 | 存数学曲线,渲染时计算 |
| 缩放效果 | 放大后锯齿严重 | 任意缩放不失真 |
| 内存占用 | 字号越多,体积越大 | 一套数据,多字号共用 |
| 渲染速度 | 直接拷贝,极快 | 需要计算,较慢 |
| 适用场景 | 小屏、低资源MCU | 大屏、高性能平台 |
核心结论:TouchGFX默认使用像素字体(.bdf/.bin格式),但支持通过外部库引入矢量字体渲染。选哪个,取决于你的硬件资源。
我在一个STM32F429项目里用过矢量字体,结果发现渲染一个汉字要花3ms,界面帧率直接掉到20fps。后来换成预渲染的像素字体,1ms搞定。嗯,这就是现实——不是技术越先进越好,得看平台扛不扛得住。
1.2 TouchGFX字体引擎架构
TouchGFX的字体引擎,我习惯把它拆成三层:
- 字体数据层:管理字体文件(.bdf/.bin/.ttf),提供字形轮廓或位图数据。
- 渲染引擎层:负责将字形数据转换成像素,处理抗锯齿、颜色、透明度。
- 缓存管理层:缓存已渲染的字形,避免重复计算。
这三层配合起来,流程是这样的:
- 应用层请求渲染字符'A',字号16,颜色黑色。
- 字体引擎先查缓存——有没有现成的?有就直接用。
- 没有?去字体数据层拿'A'的轮廓或位图数据。
- 渲染引擎根据字号、颜色、抗锯齿参数,生成像素图。
- 把结果塞进缓存,同时返回给调用方。
下面这张图,把整个流程串起来了:
我的经验:调试字体渲染性能时,先看缓存命中率。如果命中率低于80%,说明缓存太小或策略有问题。我曾经在一个项目里把缓存从2KB调到8KB,帧率直接翻倍。
1.3 字体缓存机制:为什么需要它?
你想想看,一个界面可能有几十个文本控件,每个控件显示不同的字符。如果每次渲染都去解析字体文件、计算轮廓、生成位图,CPU根本扛不住。
缓存机制就是为了解决这个问题。TouchGFX内部维护了一个字形缓存(GlyphCache),用LRU(最近最少使用)策略管理。
具体来说:
- 缓存以字形索引+字号+样式为键值存储。
- 新渲染的字形插入到缓存头部。
- 缓存满了,淘汰尾部最久未使用的字形。
- 每个字形缓存包含:位图数据、宽高、基线偏移、步进值。
注意:缓存不是越大越好。我见过有人把缓存设到64KB,结果内存不够,系统频繁触发OOM。一般建议缓存大小 = 界面同时显示的字符数 × 每个字符平均位图大小 × 1.5。
举个例子。一个16x16的汉字,带4级抗锯齿,每个像素占4位(半字节),那么一个字形的位图大小是:16×16×4÷8 = 128字节。如果界面同时显示50个汉字,缓存至少需要50×128 = 6.4KB。加上管理开销,8KB比较合理。
1.4 避坑指南:我踩过的三个坑
做字体适配这么多年,有些坑我记忆犹新:
- 坑一:用矢量字体时没开缓存。结果每次渲染都重新解析TTF文件,一个字符要5ms。后来开了缓存,降到0.1ms。
- 坑二:像素字体字号太多。一个应用用了10种字号,每种字号单独生成.bin文件,Flash直接爆了。后来只保留3种字号,其他用缩放,虽然有点模糊,但能接受。
- 坑三:缓存键值没包含抗锯齿级别。同一个字符,不同抗锯齿级别渲染结果不同,但缓存返回了旧的。显示出来边缘发虚。排查了半天才发现是缓存没区分抗锯齿参数。
总结一下:字体渲染优化的核心就三点——选对字体类型、配好缓存大小、确保缓存键值唯一。搞定了这些,你的TouchGFX界面就能跑得又快又好看。