HLSL: использование оператора «?» В разделе «Техника» дает филиалы?

Недавно я обнаружил, что вы можете сделать это:

int AnotherShaderConstant = 0;

float4 PixShader(VSOUT input, uniform bool Direction)
{
    float4 color;
    if (Direction)
        color = GoLeft();
    else
        color = GoRight();

    return color;
}

technique First
{
    PixelShader = compile ps_3_0 PixShader(AnotherShaderConstant == 1 ? true: false);
}

И он будет работать правильно, что означает, что вы можете изменить константу шейдера «AnotherShaderConstant» на уровне CPU, и пиксельный шейдер изменит его поведение. Вопрос в том, будет ли такой ход эквивалентен следующему коду, который явно производит ветку:

int AnotherShaderConstant = 0;

float4 PixShader(VSOUT input)
{
    float4 color;
    if (AnotherShaderConstant == 1)
        color = GoLeft();
    else
        color = GoRight();

    return color;
}

technique First
{
    PixelShader = compile ps_3_0 PixShader();
}

То, что я пытаюсь понять, я получу оптимизацию шейдеров, аналогичную тем, которые предоставляют однородные константы, или мой код будет грубо отправлен на GPU и выполнен для каждого пикселя, эффективно приводящего к ветке?

Я имею в виду, что шейдеры Microsoft, оптимизированные для Microsoft, из простых эффектов XNA делают следующее:

int AnotherShaderConstant = 0;

float4 PixShader1(VSOUT input)
{
    float4 color;
    color = GoLeft();

    return color;
}

float4 PixShader2(VSOUT input)
{
    float4 color;
    color = GoRight();

    return color;
}

PixelShader PixelShaders[2] =
{
    compile ps_3_0 PixShader1();
    compile ps_3_0 PixShader2();
}

technique First
{
    PixelShader = PixelShaders[AnotherShaderConstant];
}

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

5 голосов | спросил cubrman 15 FebruaryEurope/MoscowbSat, 15 Feb 2014 13:30:40 +0400000000pmSat, 15 Feb 2014 13:30:40 +040014 2014, 13:30:40

2 ответа


3

Я профилировал тестовое приложение с NSight, и результаты ясно показали, что использование «?» оператор в разделе техники создает ветви.

При использовании этой структуры:

technique First
{
    PixelShader = compile ps_3_0 PixShader(true);
}

technique Second
{
    PixelShader = compile ps_3_0 PixShader(false);
}

шейдер, отправленный на GPU, оптимизирован на уровне ЦП и генерирует ветви НЕТ. У меня было два филиала в моем шейдере: один с 4 инструкциями, а другой с 129. Шейдер, который был отправлен на GPU (его разборка), имел 4 инструкции, когда я использовал First technique и 129, когда я использовал Second.

Этот подход дал равные результаты:

PixelShader pixelShaders[2] = 
{
    compile ps_3_0 PixShader(true),
    compile ps_3_0 PixShader(false),
};

technique First
{
    PixelShader = pixelShaders[AnotherShaderConstant];
}

Однако этот код:

technique First
{
    PixelShader = compile ps_3_0 PixShader(AnotherShaderConstant == 1 ? true: false);
}

Очевидно, создает ветку, поскольку ЛЮБОЙ шейдер, отправленный в GPU, содержит 134 инструкции независимо от значения AnotherShaderConstant. Время, затрачиваемое на обработку вызовов Draw, также варьировалось:

Для модели, которая использовала простую (первую) ветвь, путем указания другого метода NAME или использования массива шейдеров, потребовалось 13946 мс.

Для той же модели, которая использовала простую (первую) ветвь, изменяя параметр AnotherShaderConstant и используя «?» оператор занял 58063 мс. Так было в 4,16 раза дольше.

Число растрированных примитивов и заштрихованных пикселей в обоих случаях было одинаковым.

UPDATE: Вот пример и грязный образец настраиваемого шейдерного процессора для XNA 4.0:

Пользовательский шейдерный процессор XNA 4.0

Он компилирует все возможные предикаты вашего шейдера в зависимости от количества указаний, которые вы указали. Во время выполнения вы можете переключать эти шейдеры, просто изменяя необходимые переменные.

ответил cubrman 16 MarpmSun, 16 Mar 2014 23:12:03 +04002014-03-16T23:12:03+04:0011 2014, 23:12:03
2

Обычно ветви, зависящие от униформы, обрабатываются драйвером. Возможно, что уровень directX обрабатывает это еще до драйвера, но он не документирован и не является чистой спекуляцией.

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

Microsoft также предоставляет предварительные шейдеры, которые обработчик fxc будет обрабатывать, если не будет предоставлен вариант запретить этот этап. (потому что он может с ошибкой в ​​сложных шейдерных кодах) Предварительный шейдер - это небольшая сцена, на которой компилятор ставит все вычисления, которые определенно будут инвариантными для всех пикселей (один и тот же результат для одного растрового прохода), и заставляют карту (или ЦП) выполнять одно время в одном потоке до фактический многопоточный шейдер выполняется на графическом процессоре, но лишен инвариантного кода, и результат передается магией микрософт между 2, безусловно, с использованием постоянных буферов.

с эффектами, вы можете использовать трюк, который должен использовать 2 метода с кодом uniform bool, переданным как параметр вашей функции освещения. В этом случае компилятор будет генерировать две разные скомпилированные функции из одного и того же кода, но без ветки.

существуют реальные динамические ветви, когда условие зависит от какого-то варианта, например, выборки из текстуры с переменной УФ. (УФ берется из-за изменения или расчета). В этом случае карта имеет 2 аппаратных реализации в зависимости от поколения, старшие поколения (шейдер 2/3) будут выполнять обе ветви, один за другим, а пиксели (потоки) будут выбирать результат из ветви, которая им соответствует в конце были выполнены. Поэтому время выполнения - это сумма обеих ветвей. Это должно быть смягчено тем фактом, что блоки потоков (часто что-то вроде зоны 16 × 16 пикселей на экране) будут запускать только одну ветвь, если все пиксели этого блока имеют одинаковое условие. Поэтому вы не теряете слишком большую производительность при очень низких частотах (например, тень с одним жестким краем, а вся левая часть экрана затенена, и вся правая часть горит, в этом случае только центральные блоки будут замедлить).

Второе поколение (шейдер 4/5) имеет фактическое динамическое ветвление, подобное процессору, в этом случае вам все равно.

ответил v.oddou 18 FebruaryEurope/MoscowbTue, 18 Feb 2014 05:22:08 +0400000000amTue, 18 Feb 2014 05:22:08 +040014 2014, 05:22:08

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132