拆分时间源以稳定 IMX6U 固定步长主循环

将计时职责从显示后端中移出,新增 Platform::ITimeSource 和 Core::Timer,使主循环基于单调整数毫秒生成固定步长 tick。
这样 SDL2/fb0 只负责显示和输入,游戏逻辑不再通过 Display 获取时间,也避免用 float 秒作为核心时间源。

同时增加 30/45/60 FPS 命令行档位,并用整数余数累计生成 33/33/34、22/22/22/22/23、16/17/17 这类毫秒节奏,便于在 IMX6U 上按性能预算选择目标帧率。

Constraint: IMX6U 运行时应避免核心逻辑依赖 float deltaSeconds
Constraint: SDL2 只能作为显示/输入适配层,不能扩散到核心时间逻辑
Rejected: 继续让 IDisplay::get_time_ms 提供时间 | 显示后端职责过宽,SDL/fb0 切换会影响时间语义
Rejected: 直接固定 33ms 实现 30 FPS | 长时间运行会产生节拍漂移
Confidence: high
Scope-risk: narrow
Directive: 后续 Launcher/GameA/GameB 应通过 ITimeSource + Timer 获取 fixed_delta_ms,不要重新引入 Display 计时
Tested: cmake --build build-win --config Release
Not-tested: IMX6U 实机 clock_gettime(CLOCK_MONOTONIC) 帧时间稳定性
This commit is contained in:
SepComet
2026-06-07 10:09:07 +08:00
parent 20d2422650
commit 777ff96602
11 changed files with 259 additions and 34 deletions
+5 -5
View File
@@ -17,7 +17,7 @@ IMX6U-Game/
│ │ ├─ RenderData/ # Color、Triangle 等数据结构(✅ 已实现)
│ │ ├─ Scene/ # Camera、Transform(✅ 已实现)
│ │ ├─ Shading/ # 着色器(预留)
│ │ ├─ Platform/ # SDL2 / fb0 平台适配(✅ 已实现)
│ │ ├─ Platform/ # SDL2 / fb0 显示适配与独立时间源(✅ 已实现)
│ │ └─ Asset/ # 资源加载(✅ 已实现)
│ ├─ Apps/
│ │ ├─ Demo/ # 当前 3D 立方体 demo(✅ 已实现)
@@ -54,7 +54,7 @@ IMX6U-Game/
- 管理 framebuffer、depthbuffer、渲染上下文。
- 提供基础绘制接口:点、线、矩形、四边形、三角形、sprite、SpriteRegion、tilemap、简单文本等。
- 提供颜色、矩形、定点数、纹理、裁剪区域等基础数据结构。
- 封装 SDL2 / framebuffer 显示提交、输入轮询、时间源。
- 封装 SDL2 / framebuffer 显示提交、输入轮询,并通过独立 `ITimeSource` 提供单调整数毫秒时间。
- 使用离线转换后的紧凑资源数据(例如 PNG 转头文件、bitmap font atlas),运行时不直接依赖 PNG/TTF 解码。
- 只关心“怎么画得快、怎么提交到屏幕”,不关心具体游戏规则。
@@ -119,7 +119,7 @@ Shared 可以依赖 Gfx;Gfx 不能依赖 Shared。
```text
Apps/GameA ─┐
Apps/GameB ─┼─> Shared ─> Gfx ─> Platform(SDL2/fb0)
Apps/GameB ─┼─> Shared ─> Gfx ─> Platform(SDL2/fb0/ITimeSource)
Launcher ─┘
Apps/GameA ─┐
@@ -160,7 +160,7 @@ public:
poll input -> update current app -> render current app -> present framebuffer
```
这样三个应用层共用同一个主循环、同一套 SDL2 初始化和 framebuffer 提交流程。
这样三个应用层共用同一个主循环、同一套 SDL2 初始化、framebuffer 提交流程和独立时间源。
注意:接口可以先保留虚函数,因为它只在每帧应用级调用,不在像素/顶点热路径中调用。像 `draw_rect`、`draw_quad`、`set_pixel_fast` 这类热路径函数不要虚化。
@@ -232,7 +232,7 @@ namespace Gfx
## 8. 性能注意事项
- 应用切换不应重复销毁/创建 SDL window、renderer、texture。
- 三个应用共用 framebuffer、输入状态和时间源。
- 三个应用共用 framebuffer、输入状态和 `Platform::ITimeSource` 时间源;Display 不承担计时职责。
- 每个应用可以有自己的资源缓存,但必须有上限和释放策略。
- Launcher 不应常驻消耗大量纹理/音频资源;进入游戏后可释放非必要启动器资源。
- Gfx 的绘制函数要保持小而直接,优先内联和连续内存写入。
+4 -3
View File
@@ -159,14 +159,14 @@
## 12. IMX6U + SDL2 运行后端规范
如果最终版本在 IMX6U 上使用 SDL2,而不是直接写 framebuffer,需要额外注意:SDL2 只是显示、输入、计时和窗口/屏幕适配层,不能把 SDL 渲染 API 当成主要性能来源。项目仍然应以 CPU 侧 framebuffer 为核心渲染结果,再以最少拷贝提交给 SDL2。
如果最终版本在 IMX6U 上使用 SDL2,而不是直接写 framebuffer,需要额外注意:SDL2 只是显示、输入和窗口/屏幕适配层,不能把 SDL 渲染 API 当成主要性能来源;时间源应通过独立 `Platform::ITimeSource` 提供。项目仍然应以 CPU 侧 framebuffer 为核心渲染结果,再以最少拷贝提交给 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 秒数。
- 时间源不挂在 `IDisplay` 上;核心逻辑从 `Platform::ITimeSource` 读取单调整数毫秒,并使用 `Core::Timer` 生成整数 tick / fixed timestep,不直接依赖 float 秒数。
### 12.2 提交帧策略
@@ -188,7 +188,8 @@
- 先确定目标分辨率和目标 FPS,再决定渲染功能;不要默认使用屏幕原生高分辨率。
- IMX6U 上优先考虑低分辨率内部渲染,再由 SDL/显示层整数倍放大。
- 目标建议至少维护两个档位:开发调试档、IMX6U 性能档。
- 目标帧率当前维护 `30/45/60 FPS` 三档,通过命令行 `--fps 30|45|60` 选择;IMX6U 默认优先 30 FPS,45/60 FPS 需要以实测帧时间确认。
- 固定步长 tick 使用整数毫秒余数累计,例如 30 FPS 为 `33/33/34 ms` 节奏,45 FPS 为 `22/22/22/22/23 ms` 节奏,60 FPS 为 `16/17/17 ms` 节奏。
- 性能档应限制:最大三角形数、最大 sprite 数、最大粒子数、最大动态光源数、最大纹理尺寸。
- 所有预算都应以实测帧时间为准,不能只按 PC 表现推断。