Chapter 18 · 渲染管线

脏矩形与按需渲染

学习 XGE 的按需渲染模式(On-Demand),只在画面变化时才重绘,大幅降低静态界面的功耗。

18

概念:为什么需要按需渲染

游戏通常每帧都重绘整个画面(60 FPS)。但对于工具类应用(编辑器、仪表盘),画面大部分时间是静态的——持续 60 FPS 渲染白白消耗 GPU 和电量。

XGE 的按需渲染(On-Demand)模式:引擎默认不渲染新帧,只有当你调用 xgeRenderRequest() 或 xgeInvalidateRect() 标记"画面需要更新"时,才触发下一帧渲染。

API 速查

函数 / 常量说明
XGE_INIT_ON_DEMAND (0x0040)初始化标志:启用按需渲染模式
void xgeRenderRequest(void)请求渲染下一帧(全屏无效化)
void xgeInvalidateRect(xge_rect_t tRect)标记矩形区域为"脏",触发局部重绘

编译本章示例

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

示例 1:启用按需渲染

int main(void) {
    xge_desc_t d = {0};
    d.iWidth  = 800;
    d.iHeight = 600;
    d.sTitle  = "On-Demand Demo";

    // 关键:添加 ON_DEMAND 标志
    d.iFlags = XGE_INIT_ON_DEMAND;

    xgeInit(&d);
    xgeRun(onFrame, NULL);
    xgeUnit();
    return 0;
}

示例 2:只在交互时重绘

static int g_iCounter = 0;

static int onFrame(void* pUser) {
    // 只有收到 RenderRequest 时才会进入这里
    xgeClear(0x081114FF);

    // 绘制计数器
    // xgeTextDraw(100, 100, "Count: %d", g_iCounter);

    // 检测用户输入
    if (xgeKeyPressed(XGE_KEY_SPACE)) {
        g_iCounter++;
        // 标记需要重绘
        xgeRenderRequest();
    }

    return XGE_OK;
}

// 外部事件(如网络数据到达)也可触发重绘:
void onDataReceived(void) {
    xgeRenderRequest();  // 唤醒渲染
}

示例 3:局部无效化

// 只有状态栏区域发生变化
xge_rect_t statusBar = { 0, 570, 800, 30 };
xgeInvalidateRect(statusBar);

// 引擎只重绘该区域(如果支持局部重绘)
// 其余区域保持上一帧内容

完整代码

将以下代码保存为 ch18_main.c,编译后运行。按空格键增加计数,观察按需渲染的工作方式:

#include "xge.h"

static int g_iCounter = 0;
static int g_iFrameCount = 0;

static int onFrame(void* pUser) {
    (void)pUser;
    g_iFrameCount++;

    // 只有收到 RenderRequest 时才会进入这里
    xgeClear(0x081114FF);

    // 绘制计数器和帧数
    xgeShapeRectFill(200, 200, 400, 200, 0x1A2A30FF);
    xgeShapeCircleFill(400, 300, 40 + g_iCounter * 5, 0x4FD8C2FF);

    // 检测用户输入
    if (xgeKeyPressed(XGE_KEY_SPACE)) {
        g_iCounter++;
        xgeRenderRequest();  // 标记需要重绘
    }

    return XGE_OK;
}

int main(void) {
    xge_desc_t d = {0};
    d.iWidth  = 800;
    d.iHeight = 600;
    d.sTitle  = "ch18 - On-Demand Rendering";
    d.iFlags  = XGE_INIT_ON_DEMAND;  // 关键:按需模式
    xgeInit(&d);
    xgeRun(onFrame, NULL);
    xgeUnit();
    return 0;
}

代码讲解

  • XGE_INIT_ON_DEMAND 标志使引擎进入按需渲染模式,空闲时不调用帧回调。
  • 只有调用 xgeRenderRequest() 后,引擎才会触发下一帧渲染。
  • 按空格键增加计数器并请求重绘,圆形半径随之增大。
  • 空闲时 GPU 完全休眠,功耗极低——适合工具类应用。

按需渲染 vs 持续渲染

模式适用场景功耗
持续渲染(默认)游戏、动画、视频播放高(GPU 满载)
按需渲染编辑器、工具、静态仪表盘低(空闲时 GPU 休眠)

要点与陷阱

  • 初始化时决定:XGE_INIT_ON_DEMAND 必须在 xgeInit 的 flags 中设置,运行时不可切换。
  • 首帧:引擎初始化后会自动渲染第一帧,无需手动 Request。
  • 事件驱动:窗口 Resize、鼠标/键盘事件通常自动触发重绘(取决于实现)。
  • InvalidateRect 是建议:引擎可能合并多个脏矩形或退化为全屏重绘。
  • 不要混用:如果你在 ON_DEMAND 模式下做动画,需要每帧调用 xgeRenderRequest()——此时等同于持续渲染。
  • 调试模式:编译时定义 XGE_DEBUGMODE=1 可用 xgedbgDirtyRectCount/Get/Clear 可视化脏矩形。

进阶练习

  1. 实现一个时钟:每秒调用一次 xgeRenderRequest(),观察 GPU 占用率对比持续渲染。
  2. 用 xgeInvalidateRect 只重绘鼠标 hover 变化的按钮区域。
  3. 在 Debug 模式下用 xgedbgDirtyRectGet 绘制脏矩形边框,观察引擎的无效化策略。
脏矩形与按需渲染效果
图:脏矩形与按需渲染渲染效果