# 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 ``` 构建(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 不再承担计时职责 ## 当前状态与后续 **已完成:** - 可旋转立方体的 3D 渲染(MVP 变换、背面剔除、扫描线填充、深度测试) - 双平台显示后端(SDL2 / Framebuffer) - 离线资源转换工具:PNG sprite -> C++ 头文件,像素字体 -> bitmap atlas/header - 基础 2D sprite、SpriteRegion、bitmap font 文本绘制和 tilemap 视口绘制,当前 demo 显示 FPS 文本、测试 sprite 和小型滚动 tilemap - Gfx 目录规范化,代码收敛到 `src/Gfx/` - `Gfx::DrawContext` 统一绘制入口,封装现有绘制能力 - C++11 兼容代码 - CMake 跨平台构建 **待完成(按优先级):** 1. FrameBuffer 性能优化(`memset` 清屏、去掉 `at()`、定点数/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 对照后端