# SF32LB52 SDK Bug Report：LV_IMG_CF_INDEXED_8BIT 图像导致 OOM 崩溃

**芯片型号**：SF32LB52
**SDK 版本**：SiFli-SDK（含 LVGL v8 适配层）
**问题分类**：SDK 缺陷 / GPU 适配层与 LVGL 解码器接口不匹配
**严重程度**：致命 —— 系统必然 OOM 崩溃，无法绕过

---

## 一、问题现象

在 SF32LB52 平台上运行 LVGL v8，主页面包含若干 `LV_IMG_CF_INDEXED_8BIT` 格式图像。
系统在主界面停留约 1~5 分钟后，RT-Thread 堆内存被耗尽，触发 assertion 崩溃：

```
Assertion failed at function:lv_mem_buf_get, line number:312 ,(0)
fatal error on thread: LvglTask
```

或：

```
Assertion failed at function:lv_img_decoder_built_in_open, line number:388 ,(0)
fatal error on thread: LvglTask
```

通过串口监控每帧堆内存变化，内存从初始约 **80KB free** 持续线性下降，每秒约 **300~1000 字节**无法回收：

```
[LVGL_TASK] 心跳 #1001, heap free=79280 used=224924
[LVGL_TASK] 心跳 #3001, heap free=78880 used=225324
[LVGL_TASK] 心跳 #5001, heap free=78280 ...
...（持续下降，约 5 分钟后耗尽）
```

---

## 二、根本原因

### 2.1 链路说明

`lv_task_handler()` → `lv_refr_task()` → 绘制 INDEXED_8BIT 图像
→ `lv_img_decoder_built_in_open()` → `my_decode_and_draw()` in `lv_gpu.c`

### 2.2 LVGL 解码器的行为（Bug 根源）

**文件**：`SF32_SDK_Test/external/lvgl_v8/src/draw/lv_img_decoder.c`
**函数**：`lv_img_decoder_built_in_open()`

对于 `LV_IMG_CF_INDEXED_8BIT` 格式，解码器处理逻辑如下：

```c
// lv_img_decoder.c ~Line 380
else if(cf == LV_IMG_CF_INDEXED_1BIT || cf == LV_IMG_CF_INDEXED_2BIT ||
        cf == LV_IMG_CF_INDEXED_4BIT || cf == LV_IMG_CF_INDEXED_8BIT) {
    // 只分配 palette 和 opa 缓冲区
    user_data->palette = lv_mem_alloc(palette_size * sizeof(lv_color_t));
    user_data->opa     = lv_mem_alloc(palette_size * sizeof(lv_opa_t));
    // ... 填充 palette ...
    return LV_RES_OK;   // ← 直接返回，从未设置 dsc->img_data
}
```

**关键缺陷：`dsc->img_data` 对于 INDEXED_8BIT 格式永远不会被设置，保持为 `NULL`。**

这是 LVGL 的设计意图：INDEXED 格式依赖逐像素解码（`lv_img_decoder_built_in_read_line()`），而非直接内存映射。

### 2.3 GPU 适配层的错误判断

**文件**：`SF32_SDK_Test/middleware/lvgl/lv_drivers/lv_gpu.c`
**函数**：`my_decode_and_draw()`

SDK 提供的 GPU 适配层在决定是否走 EPIC GPU 路径时，**仅检查 `img_data != NULL`**：

```c
// lv_gpu.c（修复前原始代码）
else if (cdsc->dec_dsc.img_data)   // ← INDEXED_8BIT 时此条件为 false
{
    // 走 EPIC GPU 路径（draw_img 直接渲染）
    ...
}
// else：落入软件逐行解码路径
```

由于 `img_data == NULL`，所有 INDEXED_8BIT 图像**全部跌落到软件解码路径**，而软件路径的实现为：

```c
// 软件逐行解码路径（每帧每张图像均执行）
for (int y = 0; y < clip_h; y++) {
    uint8_t *buf = lv_mem_buf_get(clip_w * LV_IMG_PX_SIZE_ALPHA_BYTE);
    lv_img_decoder_built_in_read_line(dsc, x, y, clip_w, buf);
    // ... 软件合成像素 ...
    lv_mem_buf_release(buf);
}
```

### 2.4 内存泄漏的实际机制

`lv_mem_buf_get()` 使用一个 **16 个 slot 的全局缓冲池**，每个 slot 只增不缩（`lv_mem_realloc` 扩大后旧内存以碎片形式留在堆中）：

```
每帧渲染 N 张 INDEXED_8BIT 图像：
  → 每张图像每行调用 lv_mem_buf_get(width * 4 字节)
  → 不同图像宽度不同 → buf pool 不断 realloc 到更大尺寸
  → 旧的小块内存成为堆碎片，无法合并回收
  → 每帧净增 ~300-1100 字节碎片
  → 5 分钟后 80KB 堆耗尽 → OOM Assertion
```

实测：7 张 INDEXED_8BIT 图像（宽度 25px），主页面 30fps 渲染，**约 300~500 帧后内存下降趋势不可逆**。

### 2.5 硬件明明支持 L8 格式

**文件**：`SF32_SDK_Test/drivers/Include/bf0_hal_epic.h`

```c
// SF32LB52 非 LB55X 系列均定义了此宏
#define EPIC_SUPPORT_L8
```

`EPIC_SUPPORTED_CF()` 函数明确将 `LV_IMG_CF_INDEXED_8BIT` 列为支持格式：

```c
bool EPIC_SUPPORTED_CF(lv_img_cf_t cf) {
    switch (cf) {
#ifdef EPIC_SUPPORT_L8
    case LV_IMG_CF_INDEXED_8BIT:  // ← 硬件支持
#endif
        ret = true;
        break;
    default:
        ret = false;
    }
}
```

**EPIC GPU 硬件支持 L8（INDEXED_8BIT）格式，但 SDK 的 `lv_gpu.c` 适配层因错误的 `img_data` 判断，导致永远无法走到 GPU 路径。**

---

## 三、影响范围

| 项目 | 详情 |
|------|------|
| 受影响芯片 | SF32LB52（及所有定义 `EPIC_SUPPORT_L8` 的型号） |
| 受影响图像格式 | `LV_IMG_CF_INDEXED_8BIT`（调色板索引 8-bit）|
| 触发条件 | 使用 INDEXED_8BIT 图像的页面持续渲染（首页/常驻页面）|
| 崩溃时间 | 约 1~10 分钟（取决于图像数量和帧率）|
| 内存配置 | `LV_MEM_CUSTOM=1`（使用 `rt_malloc`），LVGL 堆 = RT-Thread 堆 |
| 图像缓存 | `LV_IMG_CACHE_DEF_SIZE=0`（无缓存，每帧重新 open/close 解码器）|

---

## 四、修复方案

**文件**：`SF32_SDK_Test/middleware/lvgl/lv_drivers/lv_gpu.c`
**函数**：`my_decode_and_draw()`

在 GPU 路径判断条件中，增加对 INDEXED_8BIT 变量图像的特殊处理：

```c
// 修复后
else if (cdsc->dec_dsc.img_data ||
         (cdsc->dec_dsc.src_type == LV_IMG_SRC_VARIABLE &&
          cdsc->dec_dsc.header.cf == LV_IMG_CF_INDEXED_8BIT))
{
    // INDEXED_8BIT 变量图像：解码器不设置 img_data，手动指向原始数据
    if (cdsc->dec_dsc.img_data == NULL &&
        cdsc->dec_dsc.header.cf == LV_IMG_CF_INDEXED_8BIT)
    {
        const lv_img_dsc_t *img_src = (const lv_img_dsc_t *)cdsc->dec_dsc.src;
        // draw_img() 约定：src->data 起始为 256 项调色板（1024字节），后跟像素数据
        cdsc->dec_dsc.img_data = img_src->data;
    }
    // 继续走 GPU draw_img() 路径
    ...
}
```

**修复效果**：内存稳定，free 维持在约 **79000 字节**，连续运行 23000+ 帧（约 23 分钟）无下降。

---

## 五、原厂应修复的点

### 缺陷 1（必须修复）：`lv_gpu.c` 的 GPU 路径判断缺少 INDEXED_8BIT 处理

**文件**：`middleware/lvgl/lv_drivers/lv_gpu.c`

- `my_decode_and_draw()` 的 GPU 路径判断仅检查 `img_data != NULL`
- INDEXED_8BIT 格式由于 LVGL 解码器设计，`img_data` 永远为 NULL
- 导致所有 INDEXED_8BIT 图像走软件解码路径，产生不可回收的堆碎片
- **EPIC 硬件明确支持 L8 格式（`EPIC_SUPPORT_L8` 已定义），此路径应被正确激活**

### 缺陷 2（建议修复）：`lv_img_decoder.c` 对 INDEXED_8BIT 未设置 `img_data`

**文件**：`external/lvgl_v8/src/draw/lv_img_decoder.c`

- LVGL 上游对 INDEXED 格式的设计是依赖逐像素 `read_line` 接口
- 但 SiFli GPU 适配层期望通过 `img_data` 直接访问像素数据
- 两者接口约定不一致，SDK 层应在 `lv_gpu.c` 中做适配（如上修复）
- 或在 SiFli 自定义的解码器初始化中，对 INDEXED_8BIT 额外设置 `img_data`

### 缺陷 3（建议修复）：`LV_IMG_CACHE_DEF_SIZE=0` 下无保护

- 当图像缓存为 0 时，每帧都会重复调用 `lv_img_decoder_built_in_open()`
- 软件解码路径 + 无缓存 = 每帧必然产生 `lv_mem_buf` realloc
- 建议在文档/示例中说明：使用 INDEXED 格式图像时，应启用图像缓存（`LV_IMG_CACHE_DEF_SIZE >= 图像数量`）或确保 GPU 路径正确激活

---

## 六、复现步骤

1. SF32LB52 开发板，LVGL v8，`LV_IMG_CACHE_DEF_SIZE=0`
2. 在常驻显示页面（如主页）放置 ≥1 张 `LV_IMG_CF_INDEXED_8BIT` 图像
3. 运行系统，监控串口 RT-Thread 堆内存
4. 约 1~10 分钟后，可观察到堆内存持续线性下降并最终触发 `lv_mem_buf_get` assertion 崩溃

---

## 七、附：串口日志片段（崩溃前）

```
[LVGL_TASK] 心跳 #1001, heap free=79280 used=224924
[MEM_LEAK] frame #1149: used 227992->229056 (+1064), free=76996
[MEM_LEAK] frame #1379: used 229188->230280 (+1092), free=75772
...
[MEM_LEAK] frame #5043: used 300920->301984 (+1064), free=4076
[MEM_LEAK] frame #5131: used 302396->303460 (+1064), free=2600
Assertion failed at function:lv_mem_buf_get, line number:312 ,(0)
fatal error on thread: LvglTask
```

---

*报告日期：2026-03-17*
*项目：SF32_JBD013_LVGL（潜水电脑 UI）*
