Autor Tema: Fix 5.2 Skill Birds (Chaotic Diseier do Dark Lord)  (Leído 8 veces)

Estava investigando uma queda muito pesada de FPS na Skill Birds (Chaotic Diseier / Dark Lord). Em algumas situações, essa skill praticamente derrubava o jogo sozinho.

Inicialmente parecia que o problema poderia ser a quantidade de partículas ou a geometria dos efeitos, mas não era nada disso.
O problema estava na forma como o sistema estava enviando as partículas para renderização.

Basicamente, o sistema estava fazendo um "flush" no batch praticamente a cada quad renderizado.

Para explicar de forma simples: o batch serve justamente para juntar vários desenhos parecidos e enviar tudo de uma vez para a placa de vídeo. Só que, nesse caso, o estado de blend estava sendo alterado várias vezes sem necessidade.

Então acontecia algo parecido com isso para uma única partícula:

EnableAlphaBlend();       // muda o estado e gera flush
EnableAlphaBlendMinus();  // muda novamente e gera outro flush

Ou seja, o próprio código se desfazia dentro da mesma

A solução foi identificar qual blend cada partícula realmente vai usar antes de desenhá-la.

Depois disso, as partículas passaram a ser organizadas primeiro pelo tipo de blend e, em seguida, pelos outros estados que também influenciam a renderização, como tipo, subtipo e textura.

Com isso, em vez de ficar alternando:

Blend normal
Blend minus
Blend normal
Blend minus
Blend normal


o sistema consegue agrupar melhor:

Todas com Blend normal
Todas com Blend minus
Todas com Blend3

Isso reduziu drasticamente a quantidade de flushes e draw calls.

Vou deixar abaixo o código que usei para fazer a correção, caso alguém queira acoplar na sua source

A alteração é basicamente no ZzzEffectParticle.cpp.

A ideia é identificar o tipo de blend que cada partícula realmente vai utilizar antes de renderizar. Com isso, o sistema consegue ordenar as partículas de uma forma que evita ficar trocando o blend e dando flush no batch a cada quad.

1. Adicione essa função antes do ParticleSortClass
enum
{
    PBLEND_DEFAULT = 0,
    PBLEND_BLEND,
    PBLEND_MINUS,
    PBLEND_BLEND3
};

static int ParticleFinalBlend(const PARTICLE* o)
{
    switch (o->Type)
    {
    case BITMAP_CLUD64:
        return (o->SubType == 0 ||
                o->SubType == 5 ||
                o->SubType == 11)
            ? PBLEND_MINUS
            : PBLEND_DEFAULT;

    case BITMAP_TWINTAIL_WATER:
        return PBLEND_BLEND;

    case BITMAP_SMOKE:
        return (o->SubType == 2  ||
                o->SubType == 5  ||
                o->SubType == 12 ||
                o->SubType == 14 ||
                o->SubType == 15 ||
                o->SubType == 20 ||
                o->SubType == 21 ||
                o->SubType == 29 ||
                o->SubType == 37 ||
                o->SubType == 38 ||
                o->SubType == 59)
            ? PBLEND_MINUS
            : PBLEND_DEFAULT;

    case BITMAP_SMOKE + 1:
    case BITMAP_SMOKE + 4:
        return PBLEND_BLEND3;

    case BITMAP_SMOKE + 3:
        return (o->SubType == 3 ||
                o->SubType == 4)
            ? PBLEND_MINUS
            : PBLEND_DEFAULT;

    case BITMAP_LIGHT + 2:
        return (o->SubType == 3 ||
                o->SubType == 4 ||
                o->SubType == 6)
            ? PBLEND_MINUS
            : PBLEND_DEFAULT;

    case BITMAP_SPARK:
        return (o->SubType == 10)
            ? PBLEND_MINUS
            : PBLEND_DEFAULT;

    case BITMAP_SMOKELINE2:
        return (o->SubType == 3)
            ? PBLEND_MINUS
            : PBLEND_DEFAULT;

    case BITMAP_CLOUD:
        return (o->SubType == 7 ||
                o->SubType == 14 ||
                o->SubType == 16)
            ? PBLEND_MINUS
            : PBLEND_DEFAULT;
    }

    return PBLEND_DEFAULT;
}
2. Altere a ordenação das partículas

No lugar da ordenação antiga, use o blend como primeira prioridade:

struct ParticleSortPred
{
    bool operator()(int ia, int ib) const
    {
        const PARTICLE& A = Particles[ia];
        const PARTICLE& B = Particles[ib];

        const int ba = ParticleFinalBlend(&A);
        const int bb = ParticleFinalBlend(&B);

        if (ba != bb)
            return ba < bb;

        const int ca = ParticleSortClass(&A);
        const int cb = ParticleSortClass(&B);

        if (ca != cb)
            return ca < cb;

        if (A.Type != B.Type)
            return A.Type < B.Type;

        if (A.SubType != B.SubType)
            return A.SubType < B.SubType;

        return A.TexType < B.TexType;
    }
};
3. Altere o início do RenderParticles

Se atualmente estiver assim:

if (pBitmap->Components == 3)
{
    EnableAlphaBlend();
}
else
{
    EnableAlphaTest(false);
}

troque por:

switch (ParticleFinalBlend(o))
{
case PBLEND_BLEND:
    EnableAlphaBlend();
    break;

case PBLEND_MINUS:
    EnableAlphaBlendMinus();
    break;

case PBLEND_BLEND3:
    EnableAlphaBlend3();
    break;

default:
    if (pBitmap->Components == 3)
        EnableAlphaBlend();
    else
        EnableAlphaTest(false);
    break;
}

Importante: se o seu Main tiver Type/SubType customizados, confira os case do seu próprio RenderParticles e adicione eles no ParticleFinalBlend(). A lógica é a mesma: o blend que a partícula realmente usa precisa ser conhecido antes da renderização.

Se alguém quiser aplicar a mesma ideia em outros sistemas de efeito, vale procurar por situações onde o código fica alternando EnableAlphaBlend, EnableAlphaBlendMinus, EnableAlphaBlend3, etc. dentro do loop de renderização. É aí que normalmente começa esse tipo de problema de flush.

Gracias:


 

.