Chapter 192 · XUI 高级机制

缓存策略

XUI 渲染缓存:SELF/SUBTREE/TILED/DISPLAY_LIST/AUTO 策略、状态缓存、自定义渲染回调。

192

概念

XUI 缓存策略控制控件渲染结果的复用方式:
• NONE (0):不缓存,每帧重绘
• SELF (1):仅缓存自身绘制,子控件仍重绘
• SUBTREE (2):缓存整棵子树(含子控件)
• SUBTREE_TILED (3):子树分块缓存,大控件局部失效
• DISPLAY_LIST (4):录制绘制指令列表,重放无需重新计算
• AUTO (5):引擎自动选择最优策略
缓存命中时跳过绘制,仅做纹理合成,大幅降低 CPU/GPU 开销。

API 速查

函数说明
int xuiWidgetSetCachePolicy(pW, pPolicy)设置缓存策略
xui_cache_policy_t xuiWidgetGetCachePolicy(pW)获取策略
int xuiWidgetSetCacheRenderCallback(pW, onRender, pUser)自定义缓存渲染
int xuiWidgetSetCacheStateCount(pW, iCount)状态缓存数量
int xuiWidgetSetCacheStateId(pW, iIndex, iStateId)状态缓存 ID

编译本章示例

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

示例:子树缓存 + 状态缓存

// 静态面板:子树缓存
xui_cache_policy_t tPolicy = { 0 };
tPolicy.iPolicy = XUI_CACHE_POLICY_SUBTREE;
xuiWidgetSetCachePolicy(pPanel, &tPolicy);

// 按钮:多状态缓存(Normal / Hover / Pressed)
xui_cache_policy_t tBtnPolicy = { 0 };
tBtnPolicy.iPolicy = XUI_CACHE_POLICY_SELF;
xuiWidgetSetCachePolicy(pButton, &tBtnPolicy);

xuiWidgetSetCacheStateCount(pButton, 3);
xuiWidgetSetCacheStateId(pButton, 0, 0 /* 默认态 */);
xuiWidgetSetCacheStateId(pButton, 1, XUI_WIDGET_STATE_HOVER);
xuiWidgetSetCacheStateId(pButton, 2, XUI_WIDGET_STATE_ACTIVE);

// 大画布:分块缓存
xui_cache_policy_t tTilePolicy = { 0 };
tTilePolicy.iPolicy = XUI_CACHE_POLICY_SUBTREE_TILED;
tTilePolicy.iTileSize = 256;
xuiWidgetSetCachePolicy(pCanvas, &tTilePolicy);

完整代码

将以下代码保存为 ch192_main.c,编译后运行即可看到缓存策略对渲染性能的影响:

#include "xge.h"
#include "xui.h"

static xui_context g_pCtx;

static int onFrame(void* pUser) {
    (void)pUser;
    xgeClear(0x1a1a2e);
    xui_vec2_t pos;
    xgeMouseGet(&pos.x, &pos.y);
    xuiInputPointerMove(g_pCtx, pos.x, pos.y);
    if (xgeMouseDown(XGE_MOUSE_LEFT))
        xuiInputPointerDown(g_pCtx, pos.x, pos.y, 0);
    else
        xuiInputPointerUp(g_pCtx, pos.x, pos.y, 0);
    xuiUpdate(g_pCtx, xgeGetDelta());
    xuiLayout(g_pCtx);
    xuiRender(g_pCtx, NULL, NULL, 0);
    return XGE_OK;
}

int main(void) {
    xge_desc_t d = {0};
    d.iWidth = 640; d.iHeight = 480;
    d.sTitle = "ch192 - Cache Policy";
    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, 640, 480);

    xui_widget pRoot;
xuiPanelCreate(g_pCtx, &pRoot, NULL);
    xuiWidgetSetRect(pRoot, (xui_rect_t){0, 0, 640, 480});

    /* 静态面板:子树缓存 */
    xui_widget pPanel;
xuiPanelCreate(g_pCtx, &pPanel, NULL);
xuiWidgetAddChild(pRoot, pPanel);
    xuiWidgetSetRect(pPanel, (xui_rect_t){40, 40, 260, 200});
    xuiWidgetSetRect(pPanel, (xui_rect_t){0, 0, 260, 200});
    xui_cache_policy_t tSub = {0};
    tSub.iPolicy = XUI_CACHE_POLICY_SUBTREE;
    xuiWidgetSetCachePolicy(pPanel, &tSub);

    xui_widget pLbl;
xuiLabelCreate(g_pCtx, &pLbl, NULL);
xuiWidgetAddChild(pPanel, pLbl);
    xuiLabelSetText(pLbl, "SUBTREE cached panel");
    xuiWidgetSetRect(pLbl, (xui_rect_t){20, 20, 220, 32});
    xuiWidgetSetRect(pLbl, (xui_rect_t){0, 0, 220, 32});

    /* 按钮:多状态缓存 */
    xui_widget pBtn;
xuiButtonCreate(g_pCtx, &pBtn, NULL);
xuiWidgetAddChild(pRoot, pBtn);
    xuiLabelSetText(pBtn, "State Cached");
    xuiWidgetSetRect(pBtn, (xui_rect_t){360, 80, 180, 48});
    xuiWidgetSetRect(pBtn, (xui_rect_t){0, 0, 180, 48});
    xui_cache_policy_t tSelf = {0};
    tSelf.iPolicy = XUI_CACHE_POLICY_SELF;
    xuiWidgetSetCachePolicy(pBtn, &tSelf);
    xuiWidgetSetCacheStateCount(pBtn, 3);
    xuiWidgetSetCacheStateId(pBtn, 0, 0);
    xuiWidgetSetCacheStateId(pBtn, 1, XUI_WIDGET_STATE_HOVER);
    xuiWidgetSetCacheStateId(pBtn, 2, XUI_WIDGET_STATE_ACTIVE);

    /* 信息标签 */
    xui_widget pInfo;
xuiLabelCreate(g_pCtx, &pInfo, NULL);
xuiWidgetAddChild(pRoot, pInfo);
    xuiLabelSetText(pInfo, "Hover button: state cache switches");
    xuiWidgetSetRect(pInfo, (xui_rect_t){340, 160, 260, 32});
    xuiWidgetSetRect(pInfo, (xui_rect_t){0, 0, 260, 32});

    xgeRun(onFrame, NULL);
    xuiDestroy(g_pCtx);
    xgeUnit();
    return 0;
}

代码讲解

  • SUBTREE 缓存:pPanel 及其子控件内容不变时,整个子树只渲染一次并缓存为纹理。
  • SELF + 状态缓存:按钮预渲染 Normal/Hover/Active 三态,状态切换时直接取缓存无需重绘。
  • 失效机制:子控件文本/颜色/布局变化时自动标记脏,下一帧重建缓存。
  • 性能收益:复杂静态面板用 SUBTREE,有限状态控件用多状态缓存,可显著降低 DrawCall。

要点与陷阱

  • SUBTREE:子控件任何变化(文本/颜色/布局)会使缓存失效重建。
  • TILED:适合大尺寸控件,局部脏只重建对应分块。
  • 状态缓存:按钮等有限状态控件,预渲染所有状态避免切换时重建。
  • AUTO:引擎根据控件复杂度和更新频率自动选择,一般场景推荐。
  • 内存:缓存占用显存,大量 SUBTREE 缓存需注意内存预算。

进阶练习

  1. 对比测试:同一面板分别设置 NONE/SUBTREE,观察帧率差异。
  2. 状态缓存:为 Toggle 控件设置 On/Off 两态缓存。
缓存策略效果
图:缓存策略渲染效果