Chapter 114 · 音频系统

异步资源加载

AsyncRequest 在后台线程加载图像、纹理、字体、音频,通过回调通知完成,避免主线程卡顿。

114

概念

大资源(高清图、大音频)同步加载会阻塞主线程导致卡顿。 XGE 提供异步加载框架:
• 发起请求 → 后台线程执行 I/O + 解码
• 主线程每帧 Poll 或等待回调
• 支持取消、线程开关
可异步加载:Image、Texture、Font、Sound。

API 速查

函数说明
void xgeAsyncRequestInit(xge_async_request pRequest)初始化请求对象
void xgeAsyncRequestFree(xge_async_request pRequest)释放请求对象
int xgeAsyncRequestCancel(xge_async_request pRequest)取消进行中的请求
int xgeAsyncThreadingSet(int bEnabled)启用/禁用多线程
int xgeAsyncThreadingGet(void)查询多线程状态
int xgeAsyncPoll(xge_async_request pRequest)轮询完成状态
int xgeAsyncImageLoad(xge_async_request pRequest, xge_image pImage, const char* sPath, uint32_t iFlags, xge_async_proc onComplete, void* pUser)异步加载图像
int xgeAsyncTextureLoad(xge_async_request pRequest, xge_texture pTexture, const char* sPath, uint32_t iFlags, xge_async_proc onComplete, void* pUser)异步加载纹理
int xgeAsyncFontLoad(xge_async_request pRequest, xge_font pFont, const char* sPath, float fSize, xge_async_proc onComplete, void* pUser)异步加载字体
int xgeAsyncSoundLoad(xge_async_request pRequest, xge_sound pSound, const char* sPath, xge_async_proc onComplete, void* pUser)异步加载音效

编译本章示例

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

示例:异步加载纹理 + 回调

// 回调函数:加载完成后在主线程调用
void onTextureReady(int iResult, void* pUser) {
    if (iResult == 0) {
        xgeLogWrite(XGE_LOG_INFO,
            "纹理加载完成");
    } else {
        xgeLogWrite(XGE_LOG_ERROR,
            "纹理加载失败: %d", iResult);
    }
}

// 启用多线程加载
xgeAsyncThreadingSet(1);

// 发起异步纹理加载
xge_async_request_t req;
xgeAsyncRequestInit(&req);

xge_texture_t texBg;
xgeAsyncTextureLoad(&req, &texBg,
    "textures/background_4k.png",
    0, onTextureReady, NULL);

// 方式 A:回调自动通知(推荐)
// 方式 B:手动轮询
void onFrame(void) {
    if (xgeAsyncPoll(&req)) {
        // 已完成,可以使用 texBg
        xgeDraw(&texBg, 0, 0);
    } else {
        // 显示加载指示器
        drawLoadingSpinner();
    }
}

// 取消(如切换场景)
xgeAsyncRequestCancel(&req);

// 释放
xgeAsyncRequestFree(&req);

完整代码

将以下代码保存为 ch114_main.c,编译后运行即可看到异步加载纹理效果:

#include "xge.h"
#include <math.h>

static xge_async_request_t g_req;
static xge_texture_t g_tex;
static xge_font g_font = NULL;
static int g_loaded = 0;
static float g_time = 0.0f;

static void onTexReady(int iResult, void* pUser) {
    (void)pUser;
    if (iResult == 0) {
        g_loaded = 1;
        xgeLogWrite(XGE_LOG_INFO, "Texture loaded!");
    } else {
        xgeLogWrite(XGE_LOG_ERROR, "Load failed: %d", iResult);
    }
}

static int onFrame(void* pUser) {
    (void)pUser;
    g_time += 0.016f;
    xgeClear(0x081114FF);

    if (g_loaded) {
        // 加载完成,绘制纹理
        xgeDraw(&g_tex, 200, 150);
        xgeTextDraw(g_font, "Texture loaded!",
            50, 50, 0x4FD8C2FF);
    } else {
        // 加载中:显示旋转动画
        float cx = 400, cy = 300;
        for (int i = 0; i < 8; i++) {
            float a = g_time * 4.0f + i * 0.785f;
            float r = 30.0f;
            uint32_t c = (i == 0) ? 0xFFFFFFFF : 0x555555FF;
            xgeShapeCircleFill(
                cx + r*cosf(a), cy + r*sinf(a), 5, c);
        }
        xgeTextDraw(g_font, "Loading...",
            350, 350, 0xFFFFFFFF);
    }
    return XGE_OK;
}

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

    // 启用多线程加载
    xgeAsyncThreadingSet(1);
    xgeAsyncRequestInit(&g_req);

    // 发起异步纹理加载
    xgeAsyncTextureLoad(&g_req, &g_tex,
        "textures/background.png",
        0, onTexReady, NULL);

    g_font = xgeFontLoad("res/font/default.ttf", 24);
    xgeRun(onFrame, NULL);

    xgeAsyncRequestFree(&g_req);
    xgeFontFree(g_font);
    xgeUnit();
    return 0;
}

代码讲解

  • xgeAsyncThreadingSet(1):启用后台线程加载,避免主线程卡顿。
  • xgeAsyncRequestInit:初始化请求对象,每个请求对应一个加载任务。
  • xgeAsyncTextureLoad:发起异步加载,完成后回调 onTexReady。
  • 回调线程安全:回调在主线程执行,可安全调用渲染 API。
  • 加载指示器:未完成时显示动画,提升用户体验。

要点与陷阱

  • 线程安全:回调在主线程执行,可安全调用渲染 API。
  • Threading:禁用多线程时退化为同步加载(调试用)。
  • Cancel:取消后回调仍会触发(iResult != 0)。
  • 一请求一资源:每个 Request 只能关联一个加载任务。
  • 生命周期:Request 必须在资源使用完毕后再 Free。

进阶练习

  1. 实现加载管理器:队列化多个 AsyncRequest,显示总进度。
  2. 场景预加载:切换前异步加载下一场景所有资源。

已验证实现

权威源码为 examples/tutorial_capture/ch114_main1.c。为保证 CI/截图结果确定,它关闭异步线程并调用 xgeAsyncSoundLoad、xgeAsyncPoll,断言请求进入 XGE_ASYNC_READY 后播放声音。生产环境可开启线程,但必须在主线程持续轮询并处理失败与取消。

XGE async sound load polling validation
图:第 114 章真实运行的异步声音请求验证。绿色流程代表请求完成、声音对象就绪并可播放。