323 lines
15 KiB
Markdown
323 lines
15 KiB
Markdown
# IMX6U-Game
|
||
|
||
一个从 CPU 软光栅化渲染器向嵌入式游戏图形库迁移的项目。
|
||
|
||
核心目标是在 IMX6U(ARM Cortex-A7,无 GPU 或极弱 GPU)上运行纯 CPU 渲染的 2D/3D 图形。为此,代码保持 C++11 兼容,显示后端可切换(SDL2 窗口 vs Linux Framebuffer),以支持跨平台开发和嵌入式部署。
|
||
|
||
## 编译目标与验证目的
|
||
|
||
本项目支持三套编译目标,分别对应开发流程的不同阶段:
|
||
|
||
| 目标平台 | 显示后端 | 验证目的 |
|
||
|-------------------|-------------------|-------------------------------------|
|
||
| Windows (x86/x64) | SDL2 窗口 | 验证渲染逻辑、算法正确性、快速迭代调试 |
|
||
| Linux x86_64 | SDL2 窗口 | 验证 Linux 兼容性、CMake 构建、SDL2 系统依赖 |
|
||
| ARM Linux (交叉编译) | SDL2 或 `/dev/fb0` | 最终在 IMX6U 开发板上运行的版本;优先目标以后续实际部署后端为准 |
|
||
|
||
**为什么分三套?**
|
||
|
||
- **Windows 编译**:开发主力机通常是 Windows,有 IDE 调试、有图形窗口,渲染算法对不对一眼就能看到。这个阶段完全不关心嵌入式细节。
|
||
- **Linux x86 编译**:验证代码在 GCC/Clang 下有无警告、CMake 配置是否跨平台、系统 SDL2 依赖是否正确。很多嵌入式工具链的问题在 x86 Linux 上就能提前暴露。
|
||
- **ARM 交叉编译**:最终在 IMX6U 上跑。若目标板使用 SDL2,则 SDL2 仅作为显示/输入适配层,时间由独立 `Platform::ITimeSource` 提供,核心渲染仍按 CPU framebuffer + 一次性提交设计;如需极简依赖,也保留 `/dev/fb0` 后端作为对照。
|
||
|
||
|
||
## 开发规范与性能红线
|
||
|
||
IMX6U 运行时性能预算较紧,后续开发必须遵守 `docs/DEVELOPMENT_GUIDELINES.md`。如果目标板使用 SDL2,仍然要把 SDL2 限制在平台适配层,核心逻辑和渲染热路径不直接依赖 SDL:
|
||
|
||
- 核心逻辑和热路径默认不新增 `float` / `double`,需要小数时使用项目统一定点数;只在显示、调试、导入导出等边界层转换成浮点。
|
||
- 主循环、每对象、每三角形/顶点/像素级代码中不得频繁创建 `std::vector` / `std::string` 等动态分配容器,应复用缓冲或使用固定容量结构。
|
||
- PC/SDL 版本用于调试验证,不能把调试便利写法扩散到 ARM release 核心路径。
|
||
- SDL2 后端只做显示、输入和最终 framebuffer 提交;时间源独立于显示后端,不在核心渲染/游戏逻辑中直接调用 SDL API。
|
||
- 新增核心代码前按规范文档中的检查清单自查。
|
||
|
||
## 应用层与图形库拆分
|
||
|
||
项目后续按“两个游戏 + 一个启动器 + 一套底层图形库”组织,详细边界见 `docs/APP_AND_GFX_ARCHITECTURE.md`:
|
||
|
||
- `Gfx` / 底层图形库:只提供 framebuffer、SDL2/fb0 平台适配、独立时间源、基础绘制能力,例如点、线、矩形、四边形、sprite、bitmap font、tilemap。
|
||
- `Apps/Launcher`:负责游戏选择、全局设置和启动流程。
|
||
- `Apps/GameA`、`Apps/GameB`:分别维护自己的游戏规则、状态、资源和渲染调用。
|
||
- `Shared`:只放应用层共享但不属于底层图形库的 UI、存档、配置、资源索引等。
|
||
- 依赖方向必须保持 `Apps -> Shared -> Gfx -> Platform`,底层图形库不能反向依赖具体游戏。
|
||
|
||
初期推荐单进程多应用模式:Launcher、GameA、GameB 共用同一个 SDL2 初始化、framebuffer 和主循环,通过统一 `IApp` 接口切换当前应用。
|
||
|
||
## 构建说明
|
||
|
||
### 通用前置
|
||
|
||
```bash
|
||
# 克隆仓库后,确保子目录完整
|
||
cd IMX6U-Game
|
||
```
|
||
|
||
### Windows(Visual Studio / MSVC)
|
||
|
||
仓库已自带 SDL2 开发库(`libs/Win/SDL2`),无需额外安装。
|
||
|
||
```bash
|
||
cmake -B build-win .
|
||
cmake --build build-win --config Release
|
||
```
|
||
|
||
运行:
|
||
```bash
|
||
./build-win/Release/IMX6U-Game.exe
|
||
```
|
||
|
||
可选帧率档位:
|
||
```bash
|
||
./build-win/Release/IMX6U-Game.exe --fps 30
|
||
./build-win/Release/IMX6U-Game.exe --fps 45
|
||
./build-win/Release/IMX6U-Game.exe --fps 60
|
||
```
|
||
|
||
当前只接受 `30`、`45`、`60` 三档。主循环从 `Platform::ITimeSource` 读取单调整数毫秒时间,由 `Core::Timer` 生成固定步长 tick,并通过 `33/33/34`、`22/22/22/22/23`、`16/17/17` 这类整数节奏逼近对应目标帧率,避免核心时间源依赖 `float` 秒。
|
||
|
||
### Linux x86_64(Ubuntu / WSL2)
|
||
|
||
需要系统 SDL2:
|
||
|
||
```bash
|
||
sudo apt-get install libsdl2-dev cmake g++
|
||
```
|
||
|
||
构建:
|
||
```bash
|
||
cmake -B build-linux .
|
||
cmake --build build-linux
|
||
```
|
||
|
||
运行:
|
||
```bash
|
||
./build-linux/IMX6U-Game
|
||
```
|
||
|
||
### ARM 交叉编译(IMX6U)
|
||
|
||
需要交叉编译工具链:
|
||
|
||
```bash
|
||
sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf
|
||
```
|
||
|
||
本项目 ARM 端保留两类后端:
|
||
|
||
- **SDL2 后端**:目标是后续游戏主路径;SDL2 负责显示、输入和最终 framebuffer 提交,时间源使用独立的 `Platform::ITimeSource`。
|
||
- **Framebuffer 后端**:作为极简依赖和显示通路对照测试。
|
||
|
||
构建(Framebuffer 对照后端):
|
||
```bash
|
||
cmake -B build-arm-fb \
|
||
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-arm-linux-gnueabihf.cmake \
|
||
-DUSE_FRAMEBUFFER=ON .
|
||
cmake --build build-arm-fb
|
||
```
|
||
|
||
说明:单配置生成器(Makefile/Ninja)默认使用 `Release` 构建;ARM / framebuffer 性能测试必须确认 `CMAKE_BUILD_TYPE=Release`,否则逐像素绘制和 `/dev/fb0` 提交会因未优化构建出现数量级偏差。
|
||
|
||
构建(SDL2 后端,要求工具链/sysroot 可找到目标板 SDL2 开发库):
|
||
```bash
|
||
cmake -B build-arm-sdl \
|
||
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-arm-linux-gnueabihf.cmake \
|
||
-DUSE_FRAMEBUFFER=OFF .
|
||
cmake --build build-arm-sdl
|
||
```
|
||
|
||
部署到开发板:
|
||
```bash
|
||
scp build-arm-sdl/IMX6U-Game root@imx6u:/tmp/
|
||
# 或部署 framebuffer 对照版本:
|
||
scp build-arm-fb/IMX6U-Game root@imx6u:/tmp/
|
||
```
|
||
|
||
板子上运行:
|
||
```bash
|
||
/tmp/IMX6U-Game
|
||
```
|
||
|
||
Framebuffer 对照版本可能需要 root 权限访问 `/dev/fb0`。SDL2 版本是否需要额外环境变量或权限,取决于目标板 SDL2 视频驱动和显示栈配置。
|
||
|
||
## 资源转换工具
|
||
|
||
项目运行时不依赖 PNG/TTF 解码库。图片和字体资源在离线阶段转换成 C++ 头文件,运行时直接以 `uint32_t` 数组访问,像素格式统一为:
|
||
|
||
```text
|
||
(R << 24) | (G << 16) | (B << 8) | A
|
||
```
|
||
|
||
当前转换工具位于 `tools/`,需要 Python 和 Pillow:
|
||
|
||
```bash
|
||
pip install pillow
|
||
```
|
||
|
||
### Sprite 转换
|
||
|
||
普通 PNG sprite 使用 `tools/png_to_header.py` 转换:
|
||
|
||
```bash
|
||
python tools/png_to_header.py assets/sprite/test_sprite.png assets/sprite/test_sprite.h test_sprite
|
||
```
|
||
|
||
生成的头文件会包含:
|
||
|
||
```cpp
|
||
test_sprite_width
|
||
test_sprite_height
|
||
test_sprite_pixels
|
||
```
|
||
|
||
透明像素仍保留 alpha;当前 demo 通过 `RenderData::Image(..., 0x00000000)` 把全透明像素作为 color key 跳过。
|
||
|
||
### Bitmap Font 转换
|
||
|
||
像素字体图集使用 `tools/gen_font_atlas.py` 生成:
|
||
|
||
```bash
|
||
python tools/gen_font_atlas.py assets/font
|
||
```
|
||
|
||
脚本默认优先读取:
|
||
|
||
```text
|
||
assets/font/ndrtyyl-prqyys-undertale-hebrew-uppercase.ttf
|
||
```
|
||
|
||
输出:
|
||
|
||
```text
|
||
assets/font/font_atlas.png
|
||
assets/font/font_atlas.h
|
||
```
|
||
|
||
字体范围为 ASCII 32~126,按 16 列排列。生成端会把字体 alpha 阈值化为 0/255,以匹配当前 `DrawContext::draw_text` 的像素风路径;运行时只把非透明像素替换成调用方指定颜色。
|
||
|
||
## 显示后端架构
|
||
|
||
显示层通过抽象接口 `Platform::IDisplay` 与渲染逻辑解耦。后续应用层和图形库拆分后,`Platform` 会收敛到 `Gfx` 的平台适配层:
|
||
|
||
```
|
||
┌──────────────────────────────────────────────┐
|
||
│ Apps:Launcher / GameA / GameB │
|
||
├──────────────────────────────────────────────┤
|
||
│ Shared:UI、存档、配置、资源索引 │
|
||
├──────────────────────────────────────────────┤
|
||
│ Gfx:DrawContext、FrameBuffer、Draw2D、Math │
|
||
├──────────────────────────────────────────────┤
|
||
│ Platform::IDisplay │
|
||
│ - SDLDisplay : SDL2 显示/输入适配 │
|
||
│ - ITimeSource: 单调整数毫秒时间源 │
|
||
│ - FBDisplay : /dev/fb0 对照后端 │
|
||
└──────────────────────────────────────────────┘
|
||
```
|
||
|
||
切换显示后端不应影响应用层和核心绘制逻辑;当前 CMake 通过 `USE_FRAMEBUFFER` 在 SDL2 与 framebuffer 后端间切换。
|
||
|
||
## 目录结构
|
||
|
||
```text
|
||
IMX6U-Game/
|
||
├─ cmake/
|
||
│ └─ toolchain-arm-linux-gnueabihf.cmake # ARM 交叉编译工具链
|
||
├─ libs/
|
||
│ └─ Win/
|
||
│ ├─ SDL2/ # Windows 用 SDL2 库(头文件 + lib + DLL)
|
||
│ └─ SDL_image/ # SDL2_image 库(头文件 + lib + DLL)
|
||
├─ assets/
|
||
│ ├─ font/ # 像素字体源文件、font_atlas.png、font_atlas.h
|
||
│ └─ sprite/ # PNG sprite 源文件及转换后的头文件
|
||
├─ tools/
|
||
│ ├─ gen_font_atlas.py # TTF -> bitmap font atlas/header
|
||
│ └─ png_to_header.py # PNG -> uint32_t RGBA header
|
||
├─ src/
|
||
│ ├─ Gfx/ # 底层图形库:可复用、无具体游戏规则
|
||
│ │ ├─ Draw2D/ # DrawContext 统一绘制入口
|
||
│ │ ├─ Core/ # FrameBuffer、DepthBuffer
|
||
│ │ ├─ Math/ # 向量、矩阵、数学工具
|
||
│ │ ├─ Rasterizer/ # 线段、三角形光栅化
|
||
│ │ ├─ RenderData/ # Color、Triangle 等数据结构
|
||
│ │ ├─ Scene/ # Camera、Transform、Mesh
|
||
│ │ ├─ Shading/ # 着色器(预留)
|
||
│ │ ├─ Platform/ # IDisplay、SDLDisplay、FBDisplay、ITimeSource
|
||
│ │ └─ Asset/ # ObjLoader 等资源加载
|
||
│ ├─ Apps/
|
||
│ │ └─ Demo/ # 当前 3D 立方体 demo 入口
|
||
│ └─ test_fb.cpp # 独立 fb 测试(最小示例)
|
||
├─ docs/
|
||
│ ├─ DEVELOPMENT_GUIDELINES.md # IMX6U 性能红线
|
||
│ ├─ APP_AND_GFX_ARCHITECTURE.md # 应用层与图形库分层
|
||
│ └─ CONVENTIONS.md # 坐标系、矩阵、深度等数学约定
|
||
├─ CMakeLists.txt
|
||
└─ README.md
|
||
```
|
||
|
||
## 模块说明
|
||
|
||
### Draw2D
|
||
- **DrawContext**:统一绘制入口,封装 FrameBuffer、DepthBuffer、Rasterizer、TriangleRasterizer,对外提供 `clear`、`draw_line`、`draw_triangle`、`draw_sprite`、`draw_sprite_region`、`draw_text`、`draw_tilemap`、`present` 接口
|
||
- **SpriteRegion**:描述 atlas 中的子区域,`draw_sprite_region` / `draw_sprite_region_ex` 可直接绘制子图,底层复用 `draw_sprite_ex`
|
||
- **Tilemap**:使用 `uint16_t` tile id 引用 atlas 中的固定大小 tile,`draw_tilemap` 按视口可见范围遍历 tile,并在视口边缘做像素级裁剪
|
||
|
||
### Core
|
||
- **FrameBuffer**:CPU 侧颜色缓冲,渲染结果先写在这里
|
||
- **DepthBuffer**:深度测试用 Z-buffer
|
||
- **Timer**:整数毫秒固定步长 tick 生成器,支持 30/45/60 FPS 档位和每帧剩余时间计算
|
||
|
||
### Math
|
||
- 通用数学类型:`Vector2/3/4`、`Matrix4x4`
|
||
- 纯头文件实现,无动态分配
|
||
|
||
### Rasterizer
|
||
- **Rasterizer**:Bresenham 线段光栅化
|
||
- **TriangleRasterizer**:扫描线三角形填充 + 深度测试
|
||
|
||
### Platform
|
||
- **IDisplay**:显示后端抽象,解耦渲染与输出
|
||
- **SDLDisplay**:SDL2 后端,PC 调试和 IMX6U SDL2 目标路径共用这一类适配思想
|
||
- **FBDisplay**:`/dev/fb0` 对照后端,用于极简显示通路验证
|
||
- **ITimeSource / SteadyTimeSource**:独立时间源接口与单调时钟实现;Linux/IMX6U 使用 `clock_gettime(CLOCK_MONOTONIC)`,Windows 使用 `std::chrono::steady_clock`,Display 不再承担计时职责
|
||
|
||
### Framebuffer 性能说明
|
||
|
||
`FBDisplay` 是直接写 `/dev/fb0` 的对照后端。当前实现会从 CPU 侧 `FrameBuffer` 提交到系统 framebuffer,并针对常见像素格式提供快速路径:
|
||
|
||
- RGB565:使用专用 RGBA -> RGB565 转换;
|
||
- ARGB8888 / XRGB8888 类 32bpp:使用专用通道重排;
|
||
- RGBA8888 且行宽连续时:整块 `memcpy`。
|
||
|
||
板端性能测试必须使用 Release 构建。一次测试中,未优化 ARM 构建曾导致 `Frame:81ms / Present:69ms`,开启 Release 后同一轻量 2D demo 可达到约 76 FPS。该结果说明 `/dev/fb0` 整屏提交仍是关键热点,但构建类型会极大影响结论。后续优化优先级:
|
||
|
||
1. 直接使用与目标 fb0 一致的 backbuffer 像素格式,例如 RGB565,减少提交时转换;
|
||
2. 2D 场景使用 dirty rect / 局部提交,避免每帧整屏写入;
|
||
3. 避免无 3D 内容时清理 depth buffer;
|
||
4. 对 tile/sprite 增加不透明行拷贝、预转换资源或专用批处理路径。
|
||
|
||
## 当前状态与后续
|
||
|
||
**已完成:**
|
||
- 可旋转立方体的 3D 渲染(MVP 变换、背面剔除、扫描线填充、深度测试)
|
||
- 双平台显示后端(SDL2 / Framebuffer)
|
||
- 离线资源转换工具:PNG sprite -> C++ 头文件,像素字体 -> bitmap atlas/header
|
||
- 基础 2D sprite、SpriteRegion、bitmap font 文本绘制和 tilemap 视口绘制,当前 demo 显示 FPS 文本、测试 sprite 和小型滚动 tilemap
|
||
- 当前板端性能 demo 已临时移除旋转正方体,只保留 2D sprite/tilemap/FPS 与 `Frame/Present` 耗时显示,用于测量 framebuffer 提交瓶颈
|
||
- Gfx 目录规范化,代码收敛到 `src/Gfx/`
|
||
- `Gfx::DrawContext` 统一绘制入口,封装现有绘制能力
|
||
- `DrawContext::clear_color()` 支持只清颜色缓冲,避免 2D-only demo 每帧无意义清 depth buffer
|
||
- C++11 兼容代码
|
||
- CMake 跨平台构建
|
||
|
||
**待完成(按优先级):**
|
||
1. FrameBuffer / FBDisplay 性能优化(目标像素格式 backbuffer、dirty rect、专用 tile/sprite 快路径、NEON)
|
||
2. 应用层拆分(Launcher / GameA / GameB / Shared)和统一 `IApp` 主循环
|
||
3. SDL2 输入抽象(键盘/触摸/按键状态快照)
|
||
4. Gfx 基础 2D 绘制接口(矩形、四边形、继续完善 Sprite/Text/Tilemap 的批处理和专用快路径)
|
||
5. 纹理贴图、OBJ 模型加载与完整光照
|
||
|
||
## 说明
|
||
|
||
- 代码标准:**C++11**,确保兼容嵌入式老工具链
|
||
- `test_fb.cpp` 是一个完全独立的 Linux framebuffer 最小测试,不依赖项目其他代码,用于快速验证板子显示通路
|
||
- Windows、Linux x86 和目标板 SDL2 路径共享 `SDLDisplay` 适配思想;`FBDisplay` 保留为 framebuffer 对照后端
|