Стоимость использования нескольких целей рендеринга

Я использую glsl в качестве основы для GPGPU для обработки изображений в реальном времени. В настоящее время я пытаюсь «сбрить» еще несколько миллисекунд, чтобы мое приложение работало в режиме реального времени. Вот базовая настройка:

Я беру входное изображение, вычисляю несколько его преобразований, а затем вывожу результирующее изображение. Например, пусть входное изображение будет I. Затем один фрагментный шейдер вычисляет f(I);, второй вычисляет g(I); и последний вычисляет h(f(I),g(I)).

Мой вопрос касается эффективного расчета f(I),g(I): имеет ли значение, использую ли я 2 отдельных фрагментных шейдера (и, следовательно, 2 прохода рендеринга) или использую один фрагментный шейдер с 2 выходами? Будет ли последний работать быстрее? В основном я встречал обсуждения о том, как это сделать; не о производительности.

Редактировать

Спасибо за ответы. После нескольких замечаний, вот пример моего варианта использования с некоторыми дополнительными подробностями:

Я хочу фильтровать строки изображения I с помощью одномерного фильтра; а также отфильтровать строки изображения в квадрате (каждый пиксель возведен в квадрат). f(I) = filter rows и g(I) = square and filter rows:

shader1: (input image) I --> filter rows --> I_rows (output image)

shader2: (input image) I --> square pixels and filter rows--> I^2_rows (output image)

Вопрос в следующем: будет ли написание одного шейдера, который выполняет обе операции, быстрее, чем запуск этих двух шейдеров один за другим? @derhass предполагает, что ответ положительный, потому что доступ к одним и тем же локациям текстуры и наслаждение местонахождение. Но если бы не локальность текстуры: я бы все еще наслаждался приростом производительности? или рендеринг одного шейдера на два выхода в основном эквивалентен двум проходам рендеринга?


person zuuz    schedule 02.05.2014    source источник
comment
Что вы имеете в виду под шейдером с одним фрагментом и двумя выходами?   -  person zero298    schedule 02.05.2014
comment
Я с @zero298 в том, что я не понимаю, что вы подразумеваете под шейдером с одним фрагментом с двумя выходами. Если я вас правильно понял, то F(I) будет рендериться в одну текстуру, G(I) будет рендериться в другую, а H(F(I), G(I)) будет рендериться на экран. Имейте три FBO, два из которых указывают на привязанность цвета к буферу рендеринга текстуры.   -  person Freddy    schedule 02.05.2014
comment
Просто примечание: когда вы используете I в качестве идентификатора и пишете вопрос, то выделите его, чтобы легче было отличить идентификатор I от я (вы).   -  person t.niese    schedule 02.05.2014
comment
Чтобы пойти немного дальше, в зависимости от того, что вам нужно от вывода, вы можете гипотетически использовать только один фрагментный шейдер, но только если вы можете разместить всю необходимую информацию в одном цветовом канале. Обычно я помещаю данные о глубине в a из rgba, когда мне не нужна прозрачность. Но поскольку вы занимаетесь обработкой изображений, вам, вероятно, понадобится весь цвет, который вы можете получить, и в этом случае рассмотрите предложение @Freddy.   -  person zero298    schedule 02.05.2014


Ответы (2)


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

Насколько я понимаю, и f(I), и g(I) сэмплируют входное изображение I, и если каждый сэмплирует одни и те же (или близко соседние) локации, вы можете значительно извлечь выгоду из кэша текстур между разными операциями - у вас есть сэмплировать входную текстуру только один раз, а не два раза при многопроходном подходе.

Сделав этот подход еще на один шаг вперед: вам вообще нужны промежуточные результаты f(I) и g(I) отдельно? Может быть, вы могли бы просто поместить h(f(I),g(I)) непосредственно в один шейдер, чтобы вам не требовалось ни многократных проходов, ни MRT. Если вы хотите иметь возможность динамически комбинировать свои операции, вы все равно можете использовать этот подход и программно комбинировать различные части кода шейдера динамически для реализации операций (где это возможно) и использовать несколько проходов только там, где это абсолютно необходимо.

ИЗМЕНИТЬ

Поскольку вопрос был обновлен, я думаю, что могу дать более конкретные ответы:

То, что я сказал до сих пор, особенно о размещении h(f(I),g(f(I)) в одном шейдере, является хорошей идеей только в том случае, если h (или f и g) не будут нуждаться в соседних пикселях. nxn ядер фильтра, вам потребуется получить доступ к nxn различным входным текселям, и, поскольку эти входные данные не известны напрямую, вам придется вычислять f и g для каждого из них.Если и f, и h являются ядрами фильтра, эффективный размер фильтра равен составная операция будет больше, и гораздо лучше сначала вычислить промежуточные результаты и использовать несколько проходов.

Глядя на конкретную проблему, которую вы описываете, все сводится к следующему.

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

  1. использовать шейдер1
  2. выберите некоторый выходной цветовой буфер
  3. нарисовать четверной
  4. использовать шейдер2
  5. выберите другой цветовой буфер
  6. нарисовать четверной

Каждый вызов отрисовки имеет свои накладные расходы. GL должен будет выполнить дополнительную проверку. Переключение шейдеров может быть здесь самым дорогостоящим дополнительным шагом по сравнению с подходом с комбинированным шейдером, поскольку это может привести к очистке конвейера графического процессора. Кроме того, для каждого вызова отрисовки у вас есть операции обработки вершин, растеризации и интерполяции атрибутов каждого фрагмента. С помощью всего одного шейдера многие из этих накладных расходов исчезают, а описанные до сих пор вычисления для каждого фрагмента могут быть «разделены» для обоих фильтров.

Но если бы не локальность текстуры: я бы все еще наслаждался приростом производительности?

Из-за того, что я сказал до сих пор, и из-за особенностей шейдеров, которые вы представили, я склонен сказать: да. Но эффект будет очень мал или пренебрежимо мал, если мы здесь проигнорируем доступ к текстурам, особенно если мы предположим изображения с разумным высоким разрешением, так что относительные накладные расходы по сравнению с общим объемом работы кажутся небольшими. По крайней мере, я бы сказал, что использование однопроходной установки MRT не будет медленнее. Однако только бенчмаркинг/профилирование очень конкретной реализации на конкретном графическом процессоре даст окончательный ответ.

Почему я сказал "представленные вами шейдеры". Потому что в обоих случаях вы делаете квадрат изображения в одном шейдере. Вы также можете разделить это на два разных шейдера и рендерпасса. В этом случае вы получите дополнительные накладные расходы (к уже упомянутым) для записи промежуточных результатов и необходимости их чтения. Однако, поскольку вы применяете фильтр к промежуточному результату, вам не нужно возводить входной тексель в квадрат более одного раза, но в комбинированном подходе вы это делаете. Если операция возведения в квадрат достаточно дорогая, а размер вашего фильтра достаточно велик, теоретически вы можете сэкономить больше времени, чем это связано с накладными расходами на несколько проходов. Опять же, только бенчмаркинг/профилирование могут сказать вам, где будет безубыточность.

В прошлом я сам сравнивал MRT с несколькими проходами рендеринга, хотя операции обработки изображений, которые меня интересовали, немного отличаются от ваших. Я обнаружил, что в таких сценариях доступ к текстуре является ключевым фактором, и вы можете скрыть множество других вычислений (например, возведение в квадрат значения цвета) в задержке доступа к текстуре. Я думаю, что ваше «Но если бы не локальность текстуры» немного нереально, поскольку это основной вклад в общее время работы. И дело не только в местоположении, но и в общем количестве обращений к текстуре: с вашим подходом с несколькими шейдерами, изображением размером w*h и 1D-фильтром размера n вы получите 2*w*h*n обращений к текстуре в целом. , в то время как при комбинированном подходе вы просто сократитесь до *w*h*n, и это будет иметь огромное значение в прошлом.

Для AMD FirePro V9800 с размером изображения 1920x1080 и простым копированием пикселей в два выходных буфера путем рендеринга текстурированных quds я получил за два прохода: ~0,320 мс (даже без переключения шейдеров) против 1 прохода MRT: ~0,230 мс. Таким образом, время выполнения было сокращено «всего» на 30%, но это было только с одной выборкой текстуры на вызов шейдера. Я ожидаю, что с ядрами фильтров эта цифра будет приближаться к 50%-му уменьшению с увеличением размера ядра (хотя я этого не измерял).

person derhass    schedule 02.05.2014
comment
спасибо за ваш ответ @derhass. Я немного уточнил свой первоначальный вопрос. - person zuuz; 05.05.2014

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

Размытие по Гауссу на 2D-изображении — это разделяемый фильтр (X и Y можно размыть как гораздо более простую серию одномерных размытий), и вы действительно можете повысить производительность, если разделите горизонтальный и вертикальное нанесение в два прохода.

Рассмотрим сложность двух одномерных размытий по сравнению с одним двумерным размытием в Big O:

Двухпроходное размытие по Гауссу (два одномерных размытия):

Двухпроходный

Однопроходное размытие по Гауссу (Одиночное 2D-размытие):

Один проход

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

Многопроходность — это не всегда плохо, когда она упрощает ваш алгоритм, как в случае с разделяемым фильтром или световым покрытием, часто это хорошо.

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

person Andon M. Coleman    schedule 02.05.2014
comment
Если мы используем линейную выборку в центре 4 пикселей в одном проходе Гаусса, который должен иметь такое же количество выборок для двухпроходного фильтра Гаусса (используя выборку в центре 2 пикселей). - person user1914692; 10.12.2014