Chapter 19 · 渲染管线

渲染线程

了解 XGE 的多线程渲染架构,学习如何启用独立渲染线程、查询线程能力,以及 EGL 上下文配置。

19

概念:单线程 vs 多线程渲染

默认情况下,XGE 在主线程上执行所有操作:事件轮询、逻辑更新、GL 绘制、SwapBuffers。这对大多数应用足够。

但对于高负载场景(大量绘制命令、复杂 Shader),主线程可能被 GPU 提交阻塞。启用渲染线程后,绘制命令的提交和 Present 在独立线程执行,主线程可以更早开始下一帧的逻辑计算。

高级功能:渲染线程涉及 GL 上下文跨线程共享,配置不当会导致崩溃。大多数应用不需要启用。

API 速查

函数说明
int xgeRenderThreadSet(int bEnabled)启用(1)/ 禁用(0)渲染线程
int xgeRenderThreadGet(void)查询渲染线程是否启用
int xgeRenderThreadCapsGet(xge_render_thread_caps_t* pCaps)查询渲染线程能力
int xgeRenderThreadEGLSet(const xge_egl_desc_t* pDesc)设置渲染线程的 EGL 上下文参数

编译本章示例

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

xge_render_thread_caps_t 结构

typedef struct xge_render_thread_caps_t {
    int bSupported;              // 平台是否支持渲染线程
    int bEnabled;                // 当前是否已启用
    int bWorkerDrain;            // 工作线程是否支持 drain
    int bGLContextOwned;         // GL 上下文是否由引擎拥有
    int bAsyncFlush;             // 是否支持异步 flush
    int bCanUseWithCurrentContext; // 能否与现有上下文共存
} xge_render_thread_caps_t;

xge_egl_desc_t 结构

typedef struct xge_egl_desc_t {
    void* pNativeDisplay;   // 原生显示连接
    void* pNativeWindow;    // 原生窗口句柄
    int   iWidth;           // 缓冲宽度
    int   iHeight;          // 缓冲高度
    int   bPBuffer;         // 使用 PBuffer 离屏
    int   bSurfaceless;     // 无表面模式
    int   bBoardLinux;      // 嵌入式 Linux 标识
    void* pUser;            // 用户数据
} xge_egl_desc_t;

示例 1:查询能力并启用

// 先查询平台是否支持
xge_render_thread_caps_t caps = {0};
xgeRenderThreadCapsGet(&caps);

if (caps.bSupported) {
    xgeLogWrite(XGE_LOG_INFO, "Render thread supported, enabling...");
    xgeRenderThreadSet(1);
} else {
    xgeLogWrite(XGE_LOG_WARN, "Render thread not supported on this platform");
}

// 验证状态
int bEnabled = xgeRenderThreadGet();  // 1 = 已启用

示例 2:配置 EGL 上下文(嵌入式 Linux)

// 在嵌入式 Linux 上,渲染线程需要独立的 EGL 上下文
xge_egl_desc_t egl = {0};
egl.pNativeDisplay = myDisplay;   // EGLDisplay
egl.pNativeWindow  = myWindow;    // EGLNativeWindowType
egl.iWidth  = 1920;
egl.iHeight = 1080;
egl.bPBuffer = 0;  // 使用窗口表面

xgeRenderThreadEGLSet(&egl);
xgeRenderThreadSet(1);  // 启用

线程模型示意

// 单线程模式(默认):
// [Main Thread] Poll → Update → Draw → Flush → Present → Sleep
//
// 多线程模式:
// [Main Thread]   Poll → Update → Draw(录制命令) → 提交
// [Render Thread]                          接收命令 → GL 执行 → Present

完整代码

将以下代码保存为 ch19_main.c,编译后运行即可查询当前平台的渲染线程能力:

#include "xge.h"

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

    // 绘制简单场景(无论是否启用渲染线程,绘制代码相同)
    xgeShapeRectFill(200, 150, 400, 300, 0x1A2A30FF);
    xgeShapeCircleFill(400, 300, 80, 0x4FD8C2FF);

    return XGE_OK;
}

int main(void) {
    // 查询渲染线程能力
    xge_render_thread_caps_t caps = {0};
    xgeRenderThreadCapsGet(&caps);

    xgeLogWrite(XGE_LOG_INFO, "Render thread supported: %d", caps.bSupported);
    xgeLogWrite(XGE_LOG_INFO, "Async flush: %d", caps.bAsyncFlush);

    // 如果支持则启用
    if (caps.bSupported) {
        xgeRenderThreadSet(1);
        xgeLogWrite(XGE_LOG_INFO, "Render thread enabled");
    }

    xge_desc_t d = {0};
    d.iWidth  = 800;
    d.iHeight = 600;
    d.sTitle  = "ch19 - Render Thread";
    d.iFlags  = XGE_INIT_VSYNC;
    xgeInit(&d);
    xgeRun(onFrame, NULL);
    xgeUnit();
    return 0;
}

代码讲解

  • xgeRenderThreadCapsGet(&caps) 查询平台是否支持渲染线程,应在 xgeInit 之前调用。
  • xgeRenderThreadSet(1) 启用渲染线程,绘制命令在独立线程执行。
  • 启用后应用代码不能直接调用 GL 函数,必须通过 XGE API。
  • 大多数应用不需要启用渲染线程,仅在 GPU 提交成为瓶颈时考虑。

要点与陷阱

  • 默认关闭——除非有明确的性能瓶颈,否则不要启用渲染线程。
  • 平台支持:并非所有平台都支持(Web/小程序不支持)。务必先查询 xgeRenderThreadCapsGet。
  • GL 调用限制:启用渲染线程后,应用代码中不能直接调用 GL 函数——必须通过 XGE API。
  • 时序问题:渲染线程有一帧延迟。对延迟敏感的应用(VR)需要评估影响。
  • EGL 配置:仅在嵌入式 Linux / Android 上需要手动配置 EGL。桌面平台自动处理。
  • 必须在 xgeInit 之前设置:xgeRenderThreadSet 和 xgeRenderThreadEGLSet 应在初始化前调用。

进阶练习

  1. 查询当前平台的渲染线程能力,打印所有 caps 字段。
  2. 对比启用/禁用渲染线程时的帧率差异(用大量绘制命令压测)。
  3. 研究 bAsyncFlush 的含义,理解命令缓冲区的提交时机。
渲染线程效果
图:渲染线程渲染效果