Chapter 08 · 引擎基础

帧统计与调试

获取每帧的 DrawCall、批次、帧时间等性能数据,使用 Debug 版 DLL 的扩展统计 API 深入诊断。

08

概念:性能可观测性

图形应用的性能瓶颈通常在 GPU 提交(DrawCall 过多)或帧时间尖峰。XGE 提供了帧统计结构体,让你实时监控渲染管线的健康状况。

xge_frame_stats_t 结构体

typedef struct xge_frame_stats_t {
    int   iFrameCount;       // 累计帧数
    int   iDrawCallCount;    // 本帧 DrawCall 数
    int   iBatchCount;       // 本帧批次数(合批后)
    int   iDirtyRectCount;   // 脏矩形数量
    float fFrameTimeMs;      // 本帧耗时(ms)
    float fFrameTimeAvgMs;   // 平均帧时间(ms)
    float fFrameTimeMaxMs;   // 最大帧时间(ms)
} xge_frame_stats_t;

API 速查

函数说明
xge_frame_stats_t xgeFrameStatsGet(void)获取当前帧统计数据
void xgeFrameStatsReset(void)重置累计统计(帧计数、最大帧时间)

Debug 版扩展 API

注意:以下 API 仅在 Debug 版(xgedbg.dll,编译时 -DXGE_DEBUGMODE=1)中可用。Release 版中这些符号不存在。
函数说明
int xgeDebugGetStats(xge_debug_stats_t*)获取扩展统计(纹理数/内存、字体数、音频数、GL 错误)
int xgeDebugDumpCaps(char* buf, int size)将 GPU/平台能力导出为文本
int xgedbgDirtyRectCount(void)当前脏矩形数量
int xgedbgDirtyRectGet(int idx, xge_rect_t*)获取指定脏矩形
void xgedbgDirtyRectClear(void)清除脏矩形记录

编译本章示例

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

示例:HUD 性能面板

static int onFrame(void* pUser) {
    xgeClear(0x081114FF);

    // ... 绘制场景 ...

    // 获取帧统计
    xge_frame_stats_t s = xgeFrameStatsGet();

    // 在标题栏显示性能数据
    char hud[128];
    snprintf(hud, sizeof(hud),
        "FPS:%d | DC:%d | Batch:%d | %.2fms (avg %.2f, max %.2f)",
        xgeGetFPS(),
        s.iDrawCallCount,
        s.iBatchCount,
        s.fFrameTimeMs,
        s.fFrameTimeAvgMs,
        s.fFrameTimeMaxMs);
    xgeSetTitle(hud);

    return XGE_OK;
}

示例:Debug 版资源统计

#if XGE_HAS_DEBUGMODE
static void printDebugStats(void) {
    xge_debug_stats_t ds;
    xgeDebugGetStats(&ds);

    printf("=== Debug Stats ===\n");
    printf("Textures: %d (%.2f MB)\n",
           ds.iTextureCount,
           ds.iTextureMemoryBytes / (1024.0 * 1024.0));
    printf("Fonts: %d\n", ds.iFontCount);
    printf("Audio: %d\n", ds.iAudioCount);
    printf("Last GL Error: %d\n", ds.iLastGLError);
}
#endif

要点与陷阱

  • DrawCall vs Batch:DrawCall 是你发出的绘制命令数,Batch 是引擎合批后实际提交到 GPU 的次数。Batch << DrawCall 说明合批有效。
  • FrameTimeMax 不会自动重置——它记录的是整个运行期间的最大帧时间。调用 xgeFrameStatsReset() 可清零。
  • Debug API 的链接:使用 xgeDebugGetStats 时必须链接 xgedbg.dll 而非 xge.dll,否则链接错误。
  • 条件编译保护:用 #if XGE_HAS_DEBUGMODE 包裹 Debug API 调用,确保 Release 构建不报错。

进阶练习

  1. 绘制 1000 个随机圆形,观察 DrawCall 和 Batch 的关系。
  2. 用 Debug 版运行,每 5 秒打印一次纹理内存占用。
  3. 实现一个帧时间图表:记录最近 120 帧的 fFrameTimeMs,用折线绘制在屏幕角落。

完整代码与可观察结果

权威源码为 examples/tutorial_capture/ch08_main1.c。它构造一个小型场景并绘制帧时间柱状图与 16.6 ms 参考线;生产 HUD 应读取 xgeFrameStatsGet() 返回的真实统计数据。

XGE performance HUD visualization
图:第 8 章真实离屏输出。场景图元与 HUD 帧时间柱状图同时绘制,用于说明性能数据应当可见、可比较。