加入离线 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
+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 或计时日志,至少拆分统计: