Chapter 105 · 精灵批处理与九宫格

SpriteBatch

SpriteBatch 将同一纹理的多个精灵合并为一次 Draw Call,大幅减少渲染状态切换,是 2D 游戏性能的关键。

105

概念

逐个调用 xgeDraw 绘制精灵会产生大量 Draw Call。SpriteBatch 将使用同一纹理的 精灵顶点累积到动态 VBO 中,一次 Flush 完成所有绘制。 典型场景:Tilemap、粒子系统、UI 精灵。

API 速查

函数说明
int xgeSpriteBatchInit(xge_sprite_batch pBatch, xge_texture pTexture, int iCapacity, uint32_t iFlags)初始化批处理器
void xgeSpriteBatchFree(xge_sprite_batch pBatch)释放
void xgeSpriteBatchClear(xge_sprite_batch pBatch)清空累积
int xgeSpriteBatchAdd(xge_sprite_batch pBatch, const xge_draw_t* pDraw)添加精灵
int xgeSpriteBatchFlush(xge_sprite_batch pBatch)提交绘制

编译本章示例

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

示例:Tilemap 批量绘制

// 初始化(容量 1024 个精灵)
xge_sprite_batch_t batch;
xgeSpriteBatchInit(&batch, &tilesetTex,
    1024, 0);

// 每帧:添加所有可见 Tile
xgeSpriteBatchClear(&batch);

for (int y = 0; y < mapH; y++) {
    for (int x = 0; x < mapW; x++) {
        xge_draw_t draw = { 0 };
        draw.tSrc = (xge_rect_t){
            tileId * 32, 0, 32, 32 };
        draw.tDst = (xge_rect_t){
            x * 32, y * 32, 32, 32 };
        xgeSpriteBatchAdd(&batch, &draw);
    }
}

// 一次提交(1 个 Draw Call)
xgeSpriteBatchFlush(&batch);

// 退出时释放
xgeSpriteBatchFree(&batch);

完整代码

将以下代码保存为 ch105_main.c,编译后运行即可看到 SpriteBatch Tilemap 批量绘制效果:

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

#define MAP_W 25
#define MAP_H 18
#define TILE  32

static xge_sprite_batch_t g_batch;
static xge_texture_t g_tileset;
static xge_font g_font = NULL;
static int g_hasTex = 0;

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

    // 每帧清空并添加所有 Tile
    xgeSpriteBatchClear(&g_batch);

    for (int y = 0; y < MAP_H; y++)
        for (int x = 0; x < MAP_W; x++) {
            int tileId = (x + y) % 4;
            xge_draw_t draw = { 0 };
            draw.tSrc = (xge_rect_t){
                tileId * TILE, 0, TILE, TILE };
            draw.tDst = (xge_rect_t){
                x * TILE, y * TILE, TILE, TILE };
            xgeSpriteBatchAdd(&g_batch, &draw);
        }

    // 一次提交(1 个 Draw Call)
    xgeSpriteBatchFlush(&g_batch);

    xgeTextDraw(g_font, "SpriteBatch: 450 tiles, 1 draw call",
        10.0f, 580.0f, 0xFFFFFFFF)

    return XGE_OK;
}

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

    xgeFontLoad(&g_font, "Roboto.ttf", 16.0f);

    // 创建 tileset 纹理(程序生成 4 种色块)
    uint8_t px[128 * 32 * 4];
    memset(px, 0, sizeof(px));
    uint8_t colors[4][3] = {
        {200,60,60}, {60,200,60},
        {60,60,200}, {200,200,60}
    };
    for (int t = 0; t < 4; t++)
        for (int y = 0; y < 32; y++)
            for (int x = 0; x < 32; x++) {
                int i = (y * 128 + t*32 + x) * 4;
                px[i] = colors[t][0];
                px[i+1] = colors[t][1];
                px[i+2] = colors[t][2];
                px[i+3] = 255;
            }
    xgeTextureCreateRGBA(&g_tileset, 128, 32, px);
    g_hasTex = 1;

    // 初始化批处理器
    xgeSpriteBatchInit(&g_batch, &g_tileset,
        1024, 0);

    xgeRun(onFrame, NULL);

    xgeSpriteBatchFree(&g_batch);
    if (g_hasTex) xgeTextureFree(&g_tileset);
    xgeFontFree(g_font);
    xgeUnit();
    return 0;
}

代码讲解

  • SpriteBatchInit:初始化批处理器,绑定一张纹理和容量上限。
  • BatchAdd:每帧添加所有可见 Tile 的源/目标矩形。
  • BatchFlush:一次提交所有精灵,合并为 1 个 Draw Call。
  • 性能:450 个 Tile 只需 1 次 GPU 调用,比逐个绘制快数十倍。

要点与陷阱

  • 单纹理:一个 Batch 绑定一张纹理;多纹理需多个 Batch。
  • Capacity:超出容量时 Add 返回错误,需合理预估。
  • Clear vs Flush:Clear 只清空不绘制;Flush 绘制并清空。
  • 排序:按纹理分组精灵可最大化批处理效率。

进阶练习

  1. 实现粒子系统:每帧 Add 数百个粒子精灵。
  2. 多纹理批处理:按纹理排序后切换 Batch。
SpriteBatch效果
图:SpriteBatch渲染效果