Chapter 124 · XUI 框架架构

脏标记系统

LAYOUT / STYLE / CACHE / RENDER / TREE 五种脏标记驱动增量更新,避免每帧全量重算。

124

概念

XUI 使用脏标记(Dirty Flag)实现增量更新:
• LAYOUT (0x01):尺寸/位置变化,需重新布局
• STYLE (0x02):样式属性变化,需重新计算外观
• CACHE (0x04):缓存失效,需重建渲染缓存
• RENDER (0x08):需要重绘
• TREE (0x10):子节点增删,需重建渲染树
标记会向父节点传播,布局/渲染阶段只处理脏节点。

API 速查

宏/函数说明
XUI_WIDGET_DIRTY_LAYOUT布局脏 (0x01)
XUI_WIDGET_DIRTY_STYLE样式脏 (0x02)
XUI_WIDGET_DIRTY_CACHE缓存脏 (0x04)
XUI_WIDGET_DIRTY_RENDER渲染脏 (0x08)
XUI_WIDGET_DIRTY_TREE树结构脏 (0x10)
XUI_WIDGET_DIRTY_ALL全部脏 (0x1F)
int xuiInvalidateRect(xui_context pContext, xui_rect_i_t tRect)标记矩形区域脏
int xuiInvalidateAll(xui_context pContext)全部标记脏
int xuiHasDamage(xui_context pContext)是否有脏区域

编译本章示例

gcc -O2 -I. -o ch124.exe ch124_main.c src\xui_*.c build\xge.lib -lm -lws2_32 -liphlpapi -lgdi32 -luser32 -lshell32 -lopengl32 -lole32 -lwinmm -lavrt
.\ch124.exe

示例:理解脏传播

// 修改控件文本 → 自动标记脏
xuiLabelSetText(pLabel, "新文本");
// 内部自动:标记 LAYOUT|STYLE|CACHE|RENDER
// 并向父节点传播 LAYOUT

// 手动标记(自定义控件中)
xuiWidgetInvalidate(pWidget,
    XUI_WIDGET_DIRTY_RENDER);

// 添加子控件 → TREE 脏
xuiWidgetAddChild(pPanel, pNewChild);
// 自动标记 TREE,下帧重建渲染树

// 帧末检查:是否有需要渲染的区域
if (xuiHasDamage(pCtx)) {
    // 仅重绘脏区域
    xuiRender(pCtx, NULL, rects, n);
} else {
    // 无变化,跳过渲染(省电)
}

// 强制全量刷新(如主题切换)
xuiInvalidateAll(pCtx);

完整代码

将以下代码保存为 ch124_main.c,编译后运行即可观察脏标记驱动的按需渲染:

#include "xge.h"
#include "xui.h"
#include <stdio.h>

static xui_context g_pCtx;
static int         g_nFrame = 0;

static int onFrame(void* pUser) {
    (void)pUser;
    xgeClear(0x081114FF);
    xuiUpdate(g_pCtx, xgeGetDelta());
    xuiLayout(g_pCtx);

    // 仅在有脏区域时渲染
    if (xuiHasDamage(g_pCtx)) {
        xuiRender(g_pCtx, NULL, NULL, 0);
        printf("Frame %d: rendered (dirty)\n", g_nFrame);
    } else {
        printf("Frame %d: skipped (clean)\n", g_nFrame);
    }

    // 每 120 帧强制全量刷新
    if (++g_nFrame % 120 == 0) {
        xuiInvalidateAll(g_pCtx);
        printf("  -> InvalidateAll triggered\n");
    }
    return XGE_OK;
}

int main(void) {
    xge_desc_t d = {0};
    d.iWidth  = 800;
    d.iHeight = 600;
    d.sTitle  = "ch124 - Dirty Flags";
    d.iFlags  = XGE_INIT_VSYNC;
    xgeInit(&d);

    xuiCreate(&g_pCtx);
    extern const xui_proxy_t tXgeProxy;
    xui_proxy_t tXgeProxy = xuiProxyXge();
xuiSetProxy(g_pCtx, &tXgeProxy);
    xuiSetViewportSize(g_pCtx, 800, 600);

    xgeRun(onFrame, NULL);

    xuiDestroy(g_pCtx);
    xgeUnit();
    return 0;
}

代码讲解

  • xuiHasDamage:检查是否有脏区域,无变化时返回 0,跳过渲染节省 GPU。
  • xuiRender:仅重绘脏区域,而非全屏重绘。
  • xuiInvalidateAll:强制标记所有区域为脏,下帧全量重绘(如主题切换)。
  • 每 120 帧触发:演示 InvalidateAll 的效果,控制台可观察到 dirty/clean 交替。
  • 性能意义:静态 UI 无交互时不渲染,大幅降低功耗。

要点与陷阱

  • 自动传播:子控件脏 → 父控件相应标记也脏。
  • 合并:同帧多次修改只触发一次重算。
  • STYLE_DIRTY_DEFAULT:样式变化 = STYLE|CACHE|RENDER 组合。
  • 性能关键:避免无意义的 InvalidateAll,精确标记脏区域。

进阶练习

  1. 用 xuiGetRenderStats 统计每帧实际重绘面积。
  2. 对比 InvalidateAll vs 精确标记的帧时间差异。
脏标记系统效果
图:脏标记系统渲染效果