加入离线 sprite 与位图字体转换流程

Demo 现在通过生成的 C++ 头文件使用测试 sprite 和像素风 bitmap font,避免在运行时解码 PNG/TTF。同步补充资源转换文档,明确源资源、生成脚本和生成头文件需要一起提交,保证后续可追溯和可重新生成。
Constraint: IMX6U 运行时路径应尽量轻量,避免图片/字体解码开销
Constraint: 生成像素沿用当前 framebuffer 的 uint32_t RGBA 格式
Rejected: 运行时加载 PNG/TTF | 会增加解码依赖和运行时成本
Confidence: high
Scope-risk: moderate
Directive: 修改 sprite 或字体时,必须同步提交源资源、转换脚本和生成头文件
Tested: cmake -B build-win .; cmake --build build-win --config Release
Not-tested: 尚未在 IMX6U 真机上验证 ARM framebuffer/SDL 后端
This commit is contained in:
SepComet
2026-06-06 23:55:27 +08:00
parent 3e735e27b0
commit 213fa7e961
17 changed files with 1968 additions and 16 deletions
+19 -3
View File
@@ -29,10 +29,15 @@ IMX6U-Game/
│ ├─ UI/ # 启动器和游戏共用 UI 组件
│ └─ Assets/ # 应用层资源索引、资源命名约定
├─ assets/
│ ├─ font/ # 共享像素字体源文件与生成的 font_atlas
│ ├─ sprite/ # 当前 demo/test sprite 源文件与生成头文件
│ ├─ launcher/
│ ├─ game_a/
│ ├─ game_b/
│ └─ shared/
├─ tools/
│ ├─ gen_font_atlas.py # TTF -> bitmap font atlas/header
│ └─ png_to_header.py # PNG -> uint32_t RGBA header
└─ docs/
```
@@ -50,6 +55,7 @@ IMX6U-Game/
- 提供基础绘制接口:点、线、矩形、四边形、三角形、sprite、tile、简单文本等。
- 提供颜色、矩形、定点数、纹理、裁剪区域等基础数据结构。
- 封装 SDL2 / framebuffer 显示提交、输入轮询、时间源。
- 使用离线转换后的紧凑资源数据(例如 PNG 转头文件、bitmap font atlas),运行时不直接依赖 PNG/TTF 解码。
- 只关心“怎么画得快、怎么提交到屏幕”,不关心具体游戏规则。
禁止:
@@ -58,6 +64,7 @@ IMX6U-Game/
- 不包含具体关卡、角色、菜单流程、游戏状态机。
- 不直接读取某个游戏专属资源路径。
- 不在核心绘制接口中暴露 SDL2 类型。
- 不在每帧热路径中执行图片/字体解码;资源转换应在构建前或工具阶段完成。
### 2.2 Apps/GameA 与 Apps/GameB:两个游戏
@@ -178,16 +185,17 @@ namespace Gfx
void fill_rect(RectI rect, Color32 color);
void draw_quad(PointI p0, PointI p1, PointI p2, PointI p3, Color32 color);
void fill_quad(PointI p0, PointI p1, PointI p2, PointI p3, Color32 color);
void draw_sprite(int32_t x, int32_t y, const RenderData::Image& image);
void draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,
RenderData::Color color, const char* text);
};
}
```
后续再扩展:
- `draw_sprite`
- `draw_sprite_region`
- `draw_tilemap`
- `draw_text_bitmap_font`
- `set_clip_rect`
- `set_camera_2d`
- `batch sprite/tile`
@@ -210,7 +218,7 @@ namespace Gfx
1. ~~先抽出统一 `IApp` 和 `AppManager`,让当前 demo 成为一个 app。~~
2. ~~把 SDL2 初始化、输入、present 固定在平台层,应用层不直接碰 SDL。~~
3. ~~建立 `Gfx::DrawContext`,先封装 clear、pixel、line、rect、quad。~~ **已完成**(`Gfx::DrawContext` 封装了 clear、draw_line、draw_triangle、present)
3. ~~建立 `Gfx::DrawContext`,先封装 clear、pixel、line、rect、quad。~~ **已完成**(`Gfx::DrawContext` 封装了 clear、draw_line、draw_triangle、draw_sprite、draw_text、present)
4. ~~底层代码迁移到 `src/Gfx/`,Demo 入口迁移到 `src/Apps/Demo/`。~~ **已完成**
5. 新增 Launcher app,只做最小菜单和应用切换。
6. 新增 GameA/GameB 空壳,验证三应用切换。
@@ -225,3 +233,11 @@ namespace Gfx
- Launcher 不应常驻消耗大量纹理/音频资源;进入游戏后可释放非必要启动器资源。
- Gfx 的绘制函数要保持小而直接,优先内联和连续内存写入。
- UI 控件层可以面向对象;像素/quad/sprite/tile 绘制层不要过度抽象。
## 9. 资源转换约定
- 运行时资源优先使用离线转换后的简单数组,不在 IMX6U 运行时解码 PNG/TTF。
- `tools/png_to_header.py` 将 PNG 转为 `uint32_t` RGBA 数组,适用于 sprite、小图标、测试纹理等。
- `tools/gen_font_atlas.py` 将共享像素字体转为 ASCII bitmap atlas,并输出同名 PNG 预览和 C++ 头文件。
- 生成头文件、源 PNG/TTF 和转换脚本应一起纳入仓库,保证资源可追溯、可再生成。
- 生成数据目前面向简单直接的调试/小型游戏资源;后续如果资源体积增长,应再评估 1-bit/8-bit mask、RLE 或自定义资源包格式。
+22 -10
View File
@@ -113,7 +113,18 @@
- 断言用于捕捉开发期错误,但不能替代运行时边界处理。
- Debug 检查和 Release 快路径应可区分;不要为了调试便利让最终路径长期承担检查成本。
## 8. 新代码提交前检查清单
## 8. 资源转换与运行时资源规范
- 图片、字体等外部资源应在离线阶段转换为运行时直接可用的数据格式,避免在 IMX6U 热路径或启动关键路径中引入 PNG/TTF 解码成本。
- 当前工具约定:
- `tools/png_to_header.py`:PNG -> `uint32_t` RGBA 头文件。
- `tools/gen_font_atlas.py`:像素字体 TTF -> ASCII bitmap font atlas/header。
- 生成像素格式统一为 `(R << 24) | (G << 16) | (B << 8) | A`,与 `RenderData::Color::to_rgba()` 和 `Core::FrameBuffer` 当前格式保持一致。
- 源资源、生成脚本和生成头文件应同时提交,保证资源可追溯、可复现。
- 运行时绘制 sprite/font 时只做裁剪、透明判断、颜色替换或必要的像素拷贝;不要在绘制函数内做文件 IO、图片解码、字体栅格化或动态分配。
- 字体资源当前走像素风路径,生成端会把 alpha 阈值化为 0/255;如果未来要恢复抗锯齿字体,必须同步设计 framebuffer alpha blending,而不能只在绘制端把所有非 0 alpha 当作实心像素。
## 9. 新代码提交前检查清单
新增或修改核心代码前,至少检查:
@@ -123,10 +134,11 @@
- [ ] 是否有隐藏的临时大对象拷贝?
- [ ] 是否把平台/显示层 API 类型泄漏进核心逻辑?
- [ ] 是否能在 PC 调试版和 ARM release 版分别关闭调试开销?
- [ ] 新增图片/字体资源是否已离线转换,且源资源、转换脚本、生成头文件一起提交?
- [ ] 是否保留 C++11 兼容?
- [ ] 是否需要同步更新 `docs/CONVENTIONS.md` 中的坐标/矩阵/深度等约定?
## 9. 推荐的代码结构方向
## 10. 推荐的代码结构方向
后续如果继续推进性能优化,优先建立这些基础设施:
@@ -136,7 +148,7 @@
4. ARM release 配置下的性能开关,关闭日志、调试绘制和昂贵检查。
5. 简单基准测试或计时工具,用于比较清屏、光栅化、深度测试等关键路径。
## 10. 与现有代码的关系
## 11. 与现有代码的关系
当前项目仍有历史浮点实现(如数学、相机、深度缓冲等)。本文档不是要求一次性重写,而是作为后续开发红线:
@@ -144,18 +156,18 @@
- 修改已有热路径时,优先向定点、复用缓冲、连续内存方向收敛。
- 每次性能相关重构都应保持行为可验证,避免一次性大改导致渲染结果难以回归。
## 11. IMX6U + SDL2 运行后端规范
## 12. IMX6U + SDL2 运行后端规范
如果最终版本在 IMX6U 上使用 SDL2,而不是直接写 framebuffer,需要额外注意:SDL2 只是显示、输入、计时和窗口/屏幕适配层,不能把 SDL 渲染 API 当成主要性能来源。项目仍然应以 CPU 侧 framebuffer 为核心渲染结果,再以最少拷贝提交给 SDL2。
### 11.1 SDL2 边界
### 12.1 SDL2 边界
- SDL2 类型和调用只允许出现在 `src/Gfx/Platform/` 以及明确的平台适配层中。
- `src/Gfx/Core`、`src/Gfx/Math`、`src/Gfx/Rasterizer`、`src/Gfx/RenderData`、`src/Gfx/Scene`、`src/Gfx/Draw2D` 不应直接包含 `SDL.h`。
- 游戏逻辑不直接处理 `SDL_Event`,应转换为项目自己的输入状态结构。
- 时间源可以来自 SDL,但核心逻辑使用整数 tick / fixed timestep,不直接依赖 float 秒数。
### 11.2 提交帧策略
### 12.2 提交帧策略
- 每帧只提交一次最终 framebuffer,避免在一帧内多次 `SDL_RenderPresent()`。
- 优先使用固定尺寸 streaming texture;初始化时创建,运行时复用。
@@ -164,14 +176,14 @@
- 如果使用 `SDL_UpdateTexture` 成为瓶颈,优先评估 `SDL_LockTexture` 直接写入 texture 缓冲,减少一次额外拷贝。
- 只更新 dirty rect 的策略可以用于 2D UI/地图类画面;但全屏软光栅 3D 通常仍是整帧提交,重点在降低 CPU 侧绘制成本。
### 11.3 SDL Renderer 选择
### 12.3 SDL Renderer 选择
- IMX6U 上不要假设 `SDL_RENDERER_ACCELERATED` 一定更快;实际可能走软件或受驱动限制。
- 需要在开发板上对比 `SDL_RENDERER_ACCELERATED`、`SDL_RENDERER_SOFTWARE`、默认 renderer 的帧时间和稳定性。
- 一旦确定目标板最快/最稳配置,应在代码或 CMake 选项中固定,不要依赖默认行为。
- 如果 SDL2 后端只是搬运 CPU framebuffer,重点关注 texture update、copy、present 的总耗时,而不是复杂 SDL 绘图 API。
### 11.4 分辨率与帧率预算
### 12.4 分辨率与帧率预算
- 先确定目标分辨率和目标 FPS,再决定渲染功能;不要默认使用屏幕原生高分辨率。
- IMX6U 上优先考虑低分辨率内部渲染,再由 SDL/显示层整数倍放大。
@@ -179,14 +191,14 @@
- 性能档应限制:最大三角形数、最大 sprite 数、最大粒子数、最大动态光源数、最大纹理尺寸。
- 所有预算都应以实测帧时间为准,不能只按 PC 表现推断。
### 11.5 输入、音频和资源
### 12.5 输入、音频和资源
- SDL 输入事件应每帧集中轮询一次,转换为当前帧输入快照;不要在逻辑各处直接轮询 SDL。
- 音频回调中禁止分配内存、加锁等待或做复杂逻辑。
- 图片、音频、地图等资源必须在加载阶段解码;运行中避免临时解码和格式转换。
- 大纹理/图片进入运行时前应转换成目标像素格式和目标尺寸。
### 11.6 必须建立的性能观测
### 12.6 必须建立的性能观测
在 IMX6U 上接入 SDL2 后,应尽快加入轻量 profiler 或计时日志,至少拆分统计: