Разходи за използване на множество цели за изобразяване

Използвам glsl като рамка за GPGPU за обработка на изображения в реално време. В момента се опитвам да „избръсна“ още няколко милисекунди, за да направя приложението си в реално време. Ето основната настройка:

Взимам входно изображение, изчислявам няколко негови трансформации и след това извеждам резултатно изображение. Например, нека входното изображение е I. Тогава единият фрагментен шейдър изчислява f(I);, вторият изчислява g(I); и последният изчислява h(f(I),g(I)).

Въпросът ми е относно ефективното изчисляване на f(I),g(I): има ли значение дали използвам 2 отделни фрагментни шейдъра (и следователно 2 пропуска за изобразяване), или ако използвам единичен фрагментен шейдър с 2 изхода? Ще работи ли последният по-бързо? Намерих предимно дискусии за "как да"; не за представянето.

редактиране

Благодаря за отговорите досега. След няколко забележки, ето пример за моя случай на употреба с някои повече подробности:

Искам да филтрирам редовете на изображение I с 1-d филтър; и също така филтрирайте редовете на изображението в квадрат (всеки пиксел е в квадрат). 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
Как имате предвид един фрагментен шейдър с 2 изхода?   -  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) няма да се нуждаят от съседни пиксели. Ако h е nxn филтърно ядро, ще трябва да получите достъп до nxn различни входни тексела и тъй като тези входове не са директно известни, ще трябва да изчислите f и g за всеки от тях. Ако и f, и h са филтърни ядра, ефективният размер на филтъра на комбинираната операция ще бъде по-голяма и е много по-добре първо да се изчислят междинните резултати и да се използват множество преминавания.

Разглеждайки конкретния проблем, който описвате, се свежда до това.

Ако използвате два отделни шейдъра по най-наивния начин, рендирането ви ще изглежда така.

  1. използвайте shader1
  2. изберете някакъв изходен цветен буфер
  3. начертайте четворка
  4. използвай shader2
  5. изберете някакъв различен цветови буфер
  6. начертайте четворка

Всяко повикване за теглене има своите допълнителни разходи. GL ще трябва да направи допълнително валидиране. Превключването на шейдърите може да е най-скъпата допълнителна стъпка тук в сравнение с комбинирания подход на шейдъри, тъй като може да наложи промиване на конвейера на GPU. Освен това за всяко извикване на чертеж имате операции за обработка на върхове, растеризация и интеролация на атрибут на фрагмент. Само с един шейдър голяма част от тези допълнителни разходи изчезват и описаните досега изчисления на фрагмент могат да бъдат „споделени“ и за двата филтъра.

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

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

Защо казах "шейдърите, които представихте". Защото и в двата случая правите квадратурата на изображението в един шейдър. Бихте могли да го разделите на два различни шейдъра и рендерпасове. В този случай ще получите допълнителни разходи (в допълнение към вече споменатите) за писане на междинните резултати и трябва да ги прочетете обратно. Въпреки това, тъй като изпълнявате филтър върху междинния резултат, не е необходимо да повдигате всеки входен тексел повече от веднъж, но при комбинирания подход го правите. Ако операцията за повдигане на квадрат е достатъчно скъпа и размерът на вашия филтър е достатъчно голям, на теория бихте могли да спестите повече време, отколкото се въвежда от режийните разходи на множество преминавания. Отново, само сравнителният анализ/профилирането може да ви каже къде ще бъде безпроблемността.

В миналото съм правил някои сравнителни анализи с MRT срещу многобройни рендиращи преминавания, въпреки че операциите за обработка на изображения, които ме интересуваха, са малко по-различни от вашите. Това, което открих е, че в такива сценарии достъпът до текстурата е ключовият фактор и можете да скриете много други изчисления (като повдигане на стойност на цвят) в латентността на достъпа до текстурата. Мисля, че вашето „Но ако не беше локалността на текстурата“ е малко нереалистично, тъй като това е основният принос към общото време на работа. И това не е само местоположението, но и общият брой достъпи до текстури: С вашия подход с множество шейдъри, изображение с размер w*h и 1D филтър с размер n, ще получите като цяло 2*w*h*n достъпа до текстури , докато с комбинирания подход просто ще намалите до *w*h*n и това ще направи огромна разлика в миналото.

За AMD FirePro V9800, размер на изображението 1920x1080 и просто копиране на пикселите в два изходни буфера чрез изобразяване на текстурирани quds, получих с две преминавания: ~0,320ms (дори без превключване на шейдъри) срещу 1 преминаване MRT: ~0,230ms. Така че времето за изпълнение беше намалено с "само" 30%, но това беше само с едно извличане на текстура на извикване на шейдър. С филтърните ядра бих очаквал тази цифра да се доближава до 50% намаление с увеличаване на размера на ядрото (но все пак не съм го измерил).

person derhass    schedule 02.05.2014
comment
благодаря за отговора @derhass. Изясних малко първоначалния си въпрос. - person zuuz; 05.05.2014

Нека пренебрегнем всички потенциални ползи от специфични за хардуера неща като кеш на данни, повторна употреба на регистри и т.н., които могат да възникнат, ако направите целия си алгоритъм в едно извикване на шейдър и се съсредоточите изцяло върху сложността на алгоритъма за минута.

Замъгляване по Гаус върху 2D изображение е разделим филтър (X и Y могат да бъдат замъглени като много по-проста поредица от 1D замъглявания) и всъщност можете да получите по-добра производителност, ако разделите хоризонталата и вертикално нанасяне в два прохода.

Помислете за сложността на две 1D замъглявания срещу едно 2D замъгляване в Big O:

Гаусово замъгляване с две преминавания (Две 1D замъглявания):

Двупреминаване

Размазване по Гаус с едно преминаване (Едно 2D замъгляване):

Едно преминаване

Отложеното засенчване е друг пример. Вместо един масивен кръг върху всички светлини в едно преминаване, много реализации ще направят едно преминаване на светлина, засенчвайки само областта на екрана, която всяка отделна светлина всъщност покрива.

Многопроходността не винаги е нещо лошо, когато опростява вашия алгоритъм, както в случай на разделим филтър или светлинно покритие, често е добро.

Вашите резултати може да варират, но ако можете да покажете значителна разлика в сложността на алгоритъма в нотация Big O, като използвате единия подход спрямо другия, струва си да проучите производителността по време на изпълнение на двете реализации.

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