Por que isso é condicional no meu shader de fragmentos tão lento?

19

Eu configurei algum código de medição de FPS no WebGL (com base nesta resposta do SO ) e descobri algumas curiosidades com o desempenho do meu shader de fragmento. O código apenas renderiza um único quadrilátero (ou melhor, dois triângulos) sobre uma tela de 1024x1024, para que toda a mágica ocorra no shader de fragmento.

Considere este sombreador simples (GLSL; o sombreador de vértice é apenas uma passagem):

// some definitions

void main() {
    float seed = uSeed;
    float x = vPos.x;
    float y = vPos.y;

    float value = 1.0;

    // Nothing to see here...

    gl_FragColor = vec4(value, value, value, 1.0);
}

Então isso apenas renderiza uma tela branca. A média é de 30 fps na minha máquina.

Agora vamos acelerar o processamento de números e calcular cada fragmento com base em algumas oitavas de ruído dependente da posição:

void main() {
    float seed = uSeed;
    float x = vPos.x;
    float y = vPos.y;

    float value = 1.0;

      float noise;
      for ( int j=0; j<10; ++j)
      {
        noise = 0.0;
        for ( int i=4; i>0; i-- )
        {
            float oct = pow(2.0,float(i));
            noise += snoise(vec2(mod(seed,13.0)+x*oct,mod(seed*seed,11.0)+y*oct))/oct*4.0;
        }
      }

      value = noise/2.0+0.5;

    gl_FragColor = vec4(value, value, value, 1.0);
}

Se você deseja executar o código acima, eu tenho usado esta implementação desnoise .

Isso reduz os fps para algo como 7. Isso faz sentido.

Agora a parte estranha ... vamos calcular apenas um de cada 16 fragmentos como ruído e deixar os outros brancos, envolvendo a computação de ruído na seguinte condicional:

if (int(mod(x*512.0,4.0)) == 0 && int(mod(y*512.0,4.0)) == 0)) {
    // same noise computation
}

Você esperaria que isso fosse muito mais rápido, mas ainda são apenas 7 qps.

Para mais um teste, vamos filtrar os pixels com a seguinte condicional:

if (x > 0.5 && y > 0.5) {
    // same noise computation
}

Isso fornece exatamente o mesmo número de pixels de ruído de antes, mas agora estamos com quase 30 fps.

O que está acontecendo aqui? As duas maneiras de filtrar 16 dos pixels não deveriam fornecer exatamente o mesmo número de ciclos? E por que o mais lento é tão lento quanto renderizar todos os pixels como ruído?

Pergunta bônus: O que posso fazer sobre isso? Existe alguma maneira de contornar o desempenho horrível se eu realmente não quer salpico minha tela com apenas alguns fragmentos caros?

(Só para ter certeza, confirmei que o cálculo real do módulo não afeta a taxa de quadros, tornando todo décimo sexto pixel preto em vez de branco.)

Martin Ender
fonte

Respostas:

22

Os pixels são agrupados em pequenos quadrados (o tamanho depende do hardware) e computados juntos em um único canal SIMD . (estrutura das matrizes tipo de SIMD)

Esse pipeline (que possui vários nomes diferentes, dependendo do fornecedor: warps, wavefronts) executará operações para cada pixel / fragmento na etapa de bloqueio. Isso significa que, se 1 pixel precisar de um cálculo, todos os pixels serão computados e os que não precisam do resultado serão descartados.

Se todos os fragmentos seguirem o mesmo caminho através de um sombreador, os outros ramos não serão executados.

Isso significa que o seu primeiro método de computação a cada 16º pixel será na pior das hipóteses.

Se você quiser diminuir ainda mais o tamanho da imagem, renderize-a em uma textura menor e depois aumente a escala.

catraca arrepiante
fonte
5
Renderizar para uma textura menor e aumentar a amostragem é uma boa maneira de fazê-lo. Mas se, por algum motivo, você realmente precisar gravar em cada 16º pixel da textura grande, usar um sombreador de computação com uma chamada para cada 16º pixel mais carregamento / armazenamento de imagem para dispersar as gravações no destino de renderização pode ser uma boa opção.
Re