Почему это условие в моем фрагментном шейдере так медленно?

19

Я установил некоторый FPS-измерительный код в WebGL (на основе этого SO-ответа ) и обнаружил некоторые странности в производительности моего фрагментного шейдера. Код просто отображает один квад (или, скорее, два треугольника) на холсте 1024x1024, поэтому вся магия происходит в фрагментном шейдере.

Рассмотрим этот простой шейдер (GLSL; вершинный шейдер - просто сквозной):

// 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);
}

Так что это просто делает белый холст. Это в среднем около 30 кадров в секунду на моей машине.

Теперь давайте увеличим число и вычислим каждый фрагмент на основе нескольких октав зависимого от положения шума:

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);
}

Если вы хотите запустить приведенный выше код, я использовал эту реализациюsnoise .

Это приводит к снижению частоты кадров примерно до 7. Это имеет смысл.

Теперь странная часть ... давайте вычислим только один из каждых 16 фрагментов как шум и оставим остальные белыми, заключив вычисление шума в следующее условие:

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

Вы ожидаете, что это будет намного быстрее, но это все равно всего 7 кадров в секунду.

Для еще одного теста давайте вместо этого отфильтруем пиксели с помощью следующего условия:

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

Это дает точно такое же количество пикселей шума, как и раньше, но теперь мы вернулись почти к 30 кадрам в секунду.

Что здесь происходит? Разве два способа отфильтровать 16-е пиксели не должны дать одинаковое количество циклов? И почему медленнее, чем рендеринг всех пикселей в виде шума?

Бонусный вопрос: что я могу с этим сделать? Есть ли способ , чтобы работать вокруг ужасной производительности , если я на самом деле действительно хочу спекл моего холста с помощью всего несколько дорогих фрагментов?

(Просто чтобы быть уверенным, я подтвердил, что фактические вычисления по модулю не влияют на частоту кадров, отображая каждый 16-й пиксель черным, а не белым.)

Мартин Эндер
источник

Ответы:

22

Пиксели группируются в маленькие квадраты (насколько велико зависит от оборудования) и вычисляются вместе в одном SIMD- конвейере. (структура массивов типа SIMD)

Этот конвейер (который имеет несколько разных имен в зависимости от поставщика: деформации, волновые фронты) будет выполнять операции для каждого пикселя / фрагмента в слоге. Это означает, что если 1 пиксель нуждается в вычислении, то все пиксели будут его вычислять, а те, которые не нуждаются в результате, выбрасывают его.

Если все фрагменты следуют по одному и тому же пути через шейдер, другие ветви не будут выполнены.

Это означает, что ваш первый метод вычисления каждого 16-го пикселя будет наихудшим ветвлением.

Если вы все еще хотите уменьшить размер изображения, просто визуализируйте текстуру меньшего размера, а затем увеличьте ее.

чокнутый урод
источник
5
Рендеринг в меньшую текстуру и повышение частоты дискретизации - хороший способ сделать это. Но если по какой-то причине вам действительно нужно записать каждый 16-й пиксель большой текстуры, хорошим вариантом может быть использование вычислительного шейдера с одним вызовом для каждого 16-го пикселя плюс загрузка / сохранение изображения для разброса записей в цель рендеринга.
Натан Рид