Chapter 06 · 引擎基础

时间与帧率

掌握 Delta Time、FPS 查询、高精度计时器,让你的动画在任何帧率下都保持匀速。

06

概念:为什么需要 Delta Time

不同设备的帧率差异巨大——60Hz 显示器每帧 16.6ms,144Hz 每帧 6.9ms。如果动画逻辑写死"每帧移动 2 像素",在 144Hz 屏幕上物体会快 2.4 倍。Delta Time(帧间隔)让速度与帧率解耦。

API 速查

函数返回值说明
float xgeGetDelta(void)秒上一帧到当前帧的时间间隔
int xgeGetFPS(void)整数当前帧率(每秒帧数,滑动平均)
double xgeTimer(void)秒引擎启动以来的高精度时间
int64_t xgeTimeNow(void)纳秒系统单调时钟(纳秒精度)
void xgeSleep(uint32_t ms)—当前线程休眠指定毫秒

编译本章示例

gcc -O2 -I. -o ch06.exe ch06_main.c build\xge.lib -lm -lws2_32 -liphlpapi -lgdi32 -luser32 -lshell32 -lopengl32 -lole32 -lwinmm -lavrt
.\ch06.exe

示例 1:帧率无关的匀速动画

#include "xge.h"

static float g_fX = 0.0f;

static int onFrame(void* pUser) {
    float dt = xgeGetDelta();  // 帧间隔(秒)
    int w = xgeGetWidth();

    // 每秒移动 200 像素,无论帧率多少
    g_fX += 200.0f * dt;
    if (g_fX > w) g_fX -= w;  // 循环

    xgeClear(0x081114FF);
    xgeShapeCircleFill(g_fX, 300, 30, 0xFF6B5EFF);

    return XGE_OK;
}

示例 2:用 xgeTimer 实现秒表

static int onFrame(void* pUser) {
    double elapsed = xgeTimer();  // 启动以来的秒数

    char title[64];
    snprintf(title, sizeof(title),
             "运行时间: %.1fs | FPS: %d",
             elapsed, xgeGetFPS());
    xgeSetTitle(title);

    xgeClear(0x0C181CFF);
    return XGE_OK;
}

示例 3:高精度计时(性能测量)

// 测量一段代码的执行时间
int64_t t0 = xgeTimeNow();

// ... 执行耗时操作 ...
xgeShapeExDraw(pShape);  // 复杂矢量渲染

int64_t t1 = xgeTimeNow();
double ms = (t1 - t0) / 1000000.0;
printf("渲染耗时: %.3f ms\n", ms);

帧率限制策略

方式配置效果
VSyncXGE_INIT_VSYNC帧率锁定为显示器刷新率(60/144 Hz)
目标帧率d.iTargetFPS = 30引擎内部 sleep 限制上限
手动 sleepxgeSleep(16)自行控制,适合 OFFSCREEN 模式
不限制默认(无 VSync,targetFPS=0)尽可能快渲染(可能 1000+ FPS)

要点与陷阱

  • 第一帧 Delta 可能异常大——初始化后的第一帧 Delta 包含窗口创建时间。建议忽略第一帧或 clamp 到合理范围。
  • Delta 为 0 的情况:在极快帧率下(>1000 FPS),float 精度可能导致 Delta 为 0。用 if (dt < 0.0001f) dt = 0.0001f; 保护。
  • xgeTimer vs xgeTimeNow:Timer 返回 double 秒(方便),TimeNow 返回 int64 纳秒(精确)。性能测量用 TimeNow。
  • 不要用 sleep 做精确定时——xgeSleep(1) 实际可能休眠 1-15ms(取决于 OS 调度粒度)。

进阶练习

  1. 实现一个帧率计数器:每秒统计一次帧数并打印,而非使用 xgeGetFPS()。
  2. 用 xgeTimer() 实现一个正弦波动画:y = sin(timer * 2π) * 100。
  3. 设置 iTargetFPS = 30,验证 xgeGetDelta() 约为 0.033s。

完整代码与可观察结果

权威源码为 examples/tutorial_capture/ch06_main1.c。为让截图可重复,示例将时间点固定为教学快照;实际动画请用 xgeGetDelta() 更新位置,而不要依赖每帧固定像素位移。

XGE frame-rate independent motion snapshot
图:第 6 章真实离屏输出。轨迹残影、当前位置和方向箭头共同表示以时间为基准的运动状态。