# 性能调优指南 ## 概述 EmbeddedGUI 的性能调优主要围绕三个维度展开:PFB(局部帧缓冲)配置、SPI 传输策略、以及绘图 API 的选择。本文基于 QEMU 实测数据,给出各场景下的优化建议。 ## PFB 尺寸选择策略 PFB 是 EmbeddedGUI 的核心设计:屏幕被划分为多个小块,逐块渲染后传输到显示器。PFB 尺寸直接影响内存占用和渲染效率。 ### 尺寸与性能的关系 根据 PFB 矩阵测试数据,不同尺寸的性能表现差异显著: | PFB 配置 | 内存 (RGB565) | 分块数 | 适用场景 | |----------|-------------|--------|---------| | 30x30 (default) | 1.8 KB | 64 | RAM < 4KB 的极低资源 MCU | | 60x30 (medium) | 3.6 KB | 32 | 通用嵌入式场景,性价比最优 | | 120x60 (large) | 14.4 KB | 8 | RAM 充裕时的高性能选择 | | 240x15 (fullwidth) | 7.2 KB | 16 | 行扫描优化,适合文本密集界面 | | 240x240 (fullscreen) | 112.5 KB | 1 | 仅限 RAM > 128KB 的高端 MCU | ### 选择建议 对于大多数图元绘制(矩形、圆形、圆弧等),PFB 尺寸对 CPU 计算耗时的影响不大(差异 < 10%)。但以下场景差异明显: 1. 文本渲染:PFB 越大,字体缓存命中率越高。`fullscreen` 模式下 TEXT 耗时仅 0.6ms,而 `default` 模式需要 1.9ms 2. 填充操作:更大的 PFB 减少了跨块边界的重复计算。CIRCLE_FILL 从 2.9ms (default) 降到 2.3ms (fullscreen) 3. 渐变和阴影:这类逐像素计算的操作受 PFB 尺寸影响较小 推荐策略: - RAM < 8KB:使用 30x30 默认配置 - RAM 8-32KB:使用 60x30,兼顾内存和性能 - RAM > 32KB:使用 120x60 或 240x15(全宽条带) - RAM > 128KB:可考虑全屏缓冲,但需评估是否值得 配置方法(在 `app_egui_config.h` 中): ```c #define EGUI_CONFIG_PFB_WIDTH 60 #define EGUI_CONFIG_PFB_HEIGHT 30 ``` 注意:PFB 宽度和高度建议优先选成屏幕尺寸的整数约数。例如 240x240 屏幕,PFB 宽度可选 1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 16, 20, 24, 30, 40, 48, 60, 80, 120, 240;如果不能整除,框架也会处理边界块。 ## 双缓冲与三缓冲 ### SPI 传输与缓冲策略 SPI 传输是大多数场景下的帧率瓶颈,详见下文 [SPI 速度的决定性影响](#spi-bottleneck)。双缓冲通过让 CPU 和 SPI/DMA 并行工作来缓解这一问题。 根据 [SPI 矩阵测试报告](spi_matrix_report.md) 的实测数据: | 策略 | 说明 | 效果 | |------|------|------| | 单缓冲 (buf=1) | CPU 渲染完等 SPI 传输完再渲染下一块 | 帧时间 = CPU + SPI,最慢 | | 双缓冲 (buf=2) | CPU 渲染块 N+1 的同时 SPI 传输块 N | CPU 和 SPI 并行,帧时间 = max(CPU, SPI) | | 三缓冲 (buf=3) | 额外一个缓冲区吸收 CPU/SPI 速度波动 | 理论上更平滑,实测与双缓冲差异很小 | 实测数据显示,从单缓冲切换到双缓冲后,大部分测试项的帧时间从 40-65ms 降到 38.4-39ms,接近 SPI 传输的理论下限。三缓冲和四缓冲相比双缓冲几乎没有额外收益,但多占用一个 PFB 大小的 RAM。 建议:优先使用双缓冲。三缓冲仅在 CPU 渲染时间波动很大时才有意义。 配置方法: ```c #define EGUI_CONFIG_PFB_BUFFER_COUNT 2 ``` (spi-bottleneck)= ## SPI 速度的决定性影响 ### 为什么 SPI 才是真正的瓶颈 在实际硬件上,帧刷新时间由 CPU 渲染和 SPI 传输两部分组成。启用双缓冲后,两者可以并行,帧时间取决于较慢的一方。根据 [SPI 矩阵测试报告](spi_matrix_report.md) 的实测数据,**绝大多数绘制操作的 CPU 耗时远低于 SPI 传输时间**,这意味着 SPI 速度才是决定帧率的关键因素。 ![SPI Matrix Chart](images/spi_matrix_report.png) 下表对比了典型图元在纯 CPU 渲染、SPI 16MHz 双缓冲、SPI 64MHz 双缓冲三种条件下的帧时间(单位 ms,PFB 30x30,屏幕 240x240): | 测试用例 | CPU only | SPI 16MHz buf=2 | SPI 64MHz buf=2 | CPU 占比 (16MHz) | |---------|----------|-----------------|-----------------|-----------------| | RECTANGLE_FILL | 1.132 | 57.600 | 19.200 | 2.0% | | CIRCLE_FILL | 1.960 | 57.600 | 19.200 | 3.4% | | IMAGE_565 | 0.855 | 57.600 | 19.200 | 1.5% | | TEXT | 2.456 | 58.200 | 19.800 | 4.3% | | ARC_FILL | 5.491 | 59.300 | 20.900 | 9.5% | | CIRCLE | 4.560 | 58.300 | 19.900 | 7.9% | | GRADIENT_RECT | 2.122 | 57.600 | 19.200 | 3.7% | | SHADOW_ROUND | 7.676 | 60.800 | 22.300 | 12.6% | | GRADIENT_CIRCLE | 16.355 | 64.700 | 24.300 | 25.3% | | TEXT_ROTATE | 45.469 | 74.000 | 48.300 | 61.4% | | GRADIENT_ROUND_RECT_RING | 54.944 | 81.500 | 55.200 | 67.4% | 关键发现: 1. **SPI 16MHz 下,超过 85% 的测试项帧时间被钉死在 57.6ms**。CPU 渲染只用了不到 5ms,剩余 52ms 都在等 SPI 传输完成。CPU 大部分时间处于空闲状态 2. **SPI 从 16MHz 提升到 64MHz,帧时间从 57.6ms 降到 19.2ms**,提升 3 倍。而同样的 CPU 渲染优化(比如把 CIRCLE_FILL 从 2ms 优化到 1ms)对帧时间几乎没有影响 3. **只有极少数重计算操作**(旋转文字、复杂渐变)的 CPU 耗时超过 SPI 传输时间,此时 CPU 才成为瓶颈 ### 不同屏幕尺寸下的 SPI 瓶颈分析 SPI 传输时间与屏幕总像素数成正比。下表列出常见屏幕在不同 SPI 速率下的全屏传输时间(RGB565,PFB 30x30 双缓冲,脏区域为全屏): | 屏幕分辨率 | 总字节数 | SPI 16MHz | SPI 64MHz | |-----------|---------|-----------|-----------| | 128x128 | 32 KB | ~18 ms | ~6 ms | | 240x240 | 112.5 KB | 57.6 ms (实测) | 19.2 ms (实测) | | 320x480 | 300 KB | ~153 ms | ~51 ms | 对比 CPU 渲染耗时(大多数常规图元 < 10ms),可以看出: - **128x128 小屏**:SPI 64MHz 以上时传输约 6ms,CPU 和 SPI 耗时接近,两者都需要优化 - **240x240 中屏**:SPI 16MHz 下帧时间被钉死在 57.6ms(约 17 FPS),即使 64MHz 也需要 19.2ms,远超大多数 CPU 渲染时间。SPI 是绝对瓶颈 - **320x480 大屏**:SPI 16MHz 下全屏刷新约 153ms(仅 6.5 FPS),大屏必须依赖脏矩形减少传输量,或使用并行接口(8080/RGB) ### 实际优化建议 既然 SPI 是瓶颈,优化策略的优先级应该调整: 1. **提高 SPI 时钟频率**:这是最直接有效的手段。从 16MHz 提升到 64MHz,帧率直接提升 3 倍。大多数 SPI LCD 控制器支持 40-80MHz 2. **启用双缓冲**:让 CPU 渲染和 SPI 传输并行。实测数据显示,双缓冲后帧时间从 CPU+SPI 降到 max(CPU, SPI),对于 SPI 瓶颈场景收益巨大 3. **减少脏区域面积**:SPI 只传输脏区域覆盖的 PFB 块。如果只有 1/4 屏幕需要更新,SPI 传输时间也降为 1/4 4. **CPU 优化仅在特定场景有意义**:只有当 CPU 渲染时间超过 SPI 传输时间时(如旋转文字、复杂渐变),CPU 优化才能降低帧时间。对于常规 UI(按钮、文本、图片),优化 CPU 渲染几乎不影响帧率 > **经验法则**:如果你的 UI 不包含旋转文字或复杂渐变,帧率几乎完全由 `屏幕像素数 / SPI 带宽` 决定。先算这个数字,再决定是否需要优化 CPU 渲染。 详细的 SPI 矩阵测试数据见 [SPI 矩阵测试报告](spi_matrix_report.md)。 ## 脏矩形优化 EmbeddedGUI 使用脏矩形(dirty rectangle)机制:只有被标记为"脏"的区域才会重新渲染。 如果后续需要继续压缩控件内部的局部刷新范围,可参考[脏矩形细粒度优化指南](dirty_region_tuning.md);其中包含自定义子区域、PFB 早裁剪和统计验证的完整做法。 ### 减少 invalidate 范围 每次调用 `egui_view_invalidate()` 会将整个 view 的区域标记为脏。优化要点: 1. 精确 invalidate:只对真正变化的 view 调用 invalidate,避免对父容器调用 2. 避免全屏刷新:不要在定时器回调中对根 view 调用 invalidate 3. 动画优化:动画框架会自动 invalidate 目标 view,不需要手动调用 ```c // 不推荐:刷新整个页面 egui_view_invalidate(EGUI_VIEW_OF(&root_layout)); // 推荐:只刷新变化的控件 egui_view_invalidate(EGUI_VIEW_OF(&label_value)); ``` ### 布局技巧 将频繁更新的控件和静态控件分开放置,避免脏矩形覆盖不必要的区域: - 把动态数值 label 和静态标题 label 放在不同的区域 - 避免大面积半透明叠加,因为底层控件也需要重绘 ## 普通 vs HQ 抗锯齿 EmbeddedGUI 提供两套绘图 API:普通版和 HQ(高质量抗锯齿)版。 ### 性能对比 | 图元 | 普通 (ms) | HQ (ms) | 倍率 | |------|----------|---------|------| | CIRCLE | 6.19 | 9.23 | 1.5x | | CIRCLE_FILL | 4.48 | 4.40 | 1.0x | | ARC | 2.69 | 6.83 | 2.5x | | ARC_FILL | 6.92 | 18.18 | 2.6x | | LINE | 8.79 | 11.28 | 1.3x | 关键发现: - 描边操作(CIRCLE, ARC, LINE)的 HQ 版本开销明显,约 1.3-2.6 倍 - 填充操作(CIRCLE_FILL)的 HQ 版本几乎无额外开销 - ARC_FILL_HQ 开销最大(2.6 倍),因为需要对弧形边缘做亚像素混合 建议: - 大尺寸图元(半径 > 30px)使用 HQ 版本,锯齿感明显 - 小尺寸图元(半径 < 15px)使用普通版本,肉眼难以分辨 - 填充操作可以放心使用 HQ 版本 - 动画中的高频重绘优先使用普通版本 ## 图片 vs 矢量绘制 ### 性能对比 | 方式 | 耗时 | 内存 | |------|------|------| | IMAGE_565 (位图) | 9.1ms | 图片大小 (WxHx2 bytes) | | CIRCLE + RECTANGLE (矢量) | ~8.6ms | 几乎为零 | | IMAGE_565_8 (8x 压缩) | 0.19ms | 图片大小 / 8 | | GRADIENT_ROUND_RECT (矢量渐变) | 7.3ms | 几乎为零 | 关键发现: - 压缩图片(IMAGE_565_1/2/4/8)的绘制速度极快(< 0.2ms),因为解压后直接 memcpy - 未压缩的全屏图片绘制约 9ms,与复杂矢量图形相当 - 渐变矢量图形(GRADIENT_ROUND_RECT)耗时 7.3ms,比之前的 155ms 大幅优化 建议: - 复杂图形(图标、照片)使用压缩位图,速度最快 - 简单几何形状使用矢量绘制,节省 ROM 空间 - 渐变背景优先使用矢量渐变 API,避免存储大尺寸渐变位图 ## 文本渲染优化 文本渲染的性能与 PFB 尺寸强相关: | PFB 配置 | TEXT (ms) | TEXT_RECT (ms) | |----------|----------|---------------| | 15x15 | 7.09 | 7.25 | | 30x30 | 2.46 | 2.50 | | 240x1 | 16.13 | 16.28 | | 240x240 | 0.39 | 0.39 | 优化建议: 1. 增大 PFB 尺寸:文本渲染从中受益最大,因为减少了字形的重复解码 2. 使用 TEXT_RECT 裁剪:当文本可能超出显示区域时,TEXT_RECT 通过裁剪避免无效像素写入 3. 字体大小选择:较小的字体(12-16px)渲染更快,因为每个字形的像素更少 4. 静态文本缓存:对于不变的文本,避免每帧重新设置 `egui_view_label_set_text()` ## 总结 性能调优的优先级排序: 1. 提高 SPI 时钟频率(对帧率影响最大,大多数场景下是决定性因素) 2. 启用双缓冲(让 CPU 和 SPI 并行,消除串行等待) 3. 精确控制脏矩形范围(减少 SPI 传输量和 CPU 重绘量) 4. 选择合适的 PFB 尺寸(影响 CPU 渲染效率,尤其是文本和填充操作) 5. 根据场景选择普通/HQ 绘图 API 6. 合理使用压缩位图 vs 矢量绘制