Apple Metal API какъв е трикът
На WWDC 2014 всички ни очакваше изненада: обявяването на нов API за 3D графики, наречен Metal. Но този път нямаме работа с нов API на високо ниво върху OpenGL ES (какъвто беше случаят със Scene Kit), а с нов API на ниско ниво за рендиране и изчисления, който може да служи като заместител на OpenGL в игрите. Според Apple, Metal може да бъде до 10 пъти по-бърз от OpenGL ES (по-конкретно, той може да генерира повиквания за изтегляне [повиквания за изтегляне; прехвърляне на данни към графичния процесор] 10 пъти по-бързо) и е наличен само на устройства с iOS с последно поколение A7 процесор.Това съобщение провокира нова вълна от дискусии и дебати за необходимостта от нови графични API, които трябва (или не трябва - кой знае) да заменят OpenGL. Тази публикация няма за цел да участва в тази дискусия - целта й е да обясни как Metal се различава от OpenGL ES, чийто заместител е. За да разберем какво е толкова специално (или обратното, нищо особено) в Metal API, трябва да надникнем малко под „качулката“ на графичния API и GPU.
Как работят GPU и графичните API
Един наивен читател може да приеме, че директното извикване на API прави нещо на GPU или позволява нещо да се случи вътре в GPU. Още по-наивен читател предполага, че графичният процесор е приключил с обработката на това извикване, когато API върне резултат. И двете твърдения са далеч от реалността. Ако драйверът изпълни командите за изобразяване по същото време, когато са създадени, и изчака процесът на изобразяване да завърши, преди да върне резултата към извикването на API, тогава нито CPU, нито GPU могат да работят ефективно, тъй като един от процесорите винаги ще бъде блокиран в полза на другия.
За просто подобрениев работата на GPU този процес трябва да се изпълнява асинхронно; тогава GPU няма да блокира CPU и извикванията на API ще върнат резултата почти мигновено. В този случай GPU може да не се използва на 100%, тъй като може да се наложи да изчака CPU да направи нови извиквания за рендиране (= начало на рамка), докато други команди чакат предишните да завършат. Това е причината, поради която повечето графични драйвери събират всичкиповиквания за чертане(и други задачи, които ще трябва да бъдат извършени на GPU, като промяна на състояния), за да изобразят целия кадър, преди да го изпратят на GPU. След това тези буферирани команди ще бъдат изпратени обратно след получаване на команда за изчертаване на следващия кадър, като се гарантира, че GPU се използва възможно най-ефективно. Разбира се, това ще добави един кадър на забавяне: докато процесорът създава задача за текущия кадър, предишният кадър ще бъде изобразен на GPU. Всъщност можете да буферирате повече от един кадър и по този начин да постигнете по-висока честота на кадрите – за сметка на още повече латентност.
Друга грешка в нашето наивно предположение е, че приемаме какво правят извикванията за промяна на състоянието.
Така че научихме най-малко две важни неща за това какво се случва зад сцената на сътрудничеството на OpenGL с модерни графични процесори: промяната на състоянията може да бъде трудна, ако е необходима нова комбинация от състояния и всички операции на графичния процесор ще бъдат забавени с известно време.
В приложението се генерира един поток от действителни команди за един кадър, който да бъде изпълнен на GPU, и се изпраща на GPU наведнъж (всъщност е малко по-сложно, но нека не навлизаме все още).
Прочетете повече за това как работиМожете да намерите модерен тръбопровод за компютърна графика в поредица от статии на Фабиан Гизенс - „Пътуване по конвейера за графики“.
Защо различен модел на програмиране може да има предимства
Както вече видяхте, огромен брой сложности и хитри трикове са скрити от програмиста (вероятно има дори повече от тях, отколкото споменах), които скриват това, което се случва директно. Някои от тях улесняват живота на простия разработчик, други го карат да търси начини да надхитри драйвера или да „копае“ към страничните ефекти на API извикванията.
Някои графични API днес се опитват да премахнат повечето от тези трикове, разкривайки "заплитането", което крият - и в някои случаи оставят на програмата да разреши всички свързани проблеми. Графичният API на PS3 върви в тази посока, AMD със своя Mantle върви в него, предстоящият DirectX 12 и Apple Metal също вървят там.
Какво се промени?
Командните буфери вече са отворени и приложението трябва да попълни тези буфери и да ги изпрати на опашката с команди, която ще изпълни тези буфери в указания ред на GPI - по този начин приложението ще има пълен контрол върху заданието, изпратено до GPU, и ще определи колко кадъра на забавяне да добави (добавя латентност, но също така увеличава степента на използване на GPU). Буферирането на команди на GPU и изпращането им асинхронно към следващия кадър трябва да се реализира от самото приложение.
Тъй като става ясно, че тези буфери няма да бъдат изпълнени веднага (т.е. в момента на създаване) и че множество буфери могат да бъдат създадени и добавени към опашката за изпълнение в определен ред, приложението може да си позволи да ги изгради на множество нишки паралелно. Също така става по-очевидно за програмиста кой от резултатитеизчисленията вече са налични и кои не са.
Промените в състоянието вече са организирани в обекти на състояние, които могат просто да се превключват, докато създаването на тези обекти ще бъде по-скъпо. Например MTLRenderPipelineState съдържа шейдъри и всички състояния, които се изпълняват чрез тяхното корекция.
Друго предимство на новия API е, че той не трябва да носи тежестта на съвместимостта с предишни версии и следователно няма да бъде толкова консервативен.
Има един нюанс в заточването за A7 - благодарение на него Metal е заточен за работа на системи със споделена памет, т.е. CPU и GPU имат директен достъп до едни и същи данни, без да се налага да ги прехвърлят през PCI шината. Metal дава на програмата директен достъп до буферите от процесора и е отговорност на програмиста да гарантира, че тези данни не се използват едновременно от графичния процесор. Тази полезна функция ви позволява да смесвате продукта от GPU и CPU изчисления.
И как става 10 пъти по-бързо?
Всяко повикване за изтегляне струва известно време на процесора и известно време на графичния процесор. Metal API намалява времето на процесора чрез опростяване на мониторинга на състоянието и по този начин намалява броя на проверките за грешки от драйвера за валидни комбинации от състояния. Предварителното изчисляване на състоянията също помага: не само можете да проверявате за грешки по време на изграждането, но и самата промяна на състоянието ще изисква по-малко извиквания на API. Възможността за паралелно изграждане на буфери за инструкции допълнително увеличава броя на извикванията за изтегляне, когато дадено приложение е обвързано с процесора.
От друга страна, изобразяването на GPU не става по-бързо, приложение, което прави много малко извиквания за рисуване за големи мрежи (мрежае частта от модела, състояща се от върхове на обект], няма да получи никаквиползите от преминаването към Metal.
Може ли същото да се направи в OpenGL?
Тази и други идеи ще изискват допълнително разширяване на OpenGL и нови версии на този API, но голяма част от това може да бъде пренесено в OpenGL ES. Това, което ще загубим, е способността да контролираме директно командните буфери, с всичките му плюсове и минуси.
Каква е вероятността да видите това в бъдеще? Поради поддръжката за обратна съвместимост, може само да се надяваме на набор от функции, които могат да бъдат наречени „модерно ядро“, но най-вероятно ще трябва да се направи съвместимо с всичко до оригиналната функция glBegin (). Това ограничение ще продължи през цялото потенциално бъдеще на OpenGL и ще бъде границата на неговата еволюция, правейки алтернативи като Metal API все по-предпочитани ...
Hardcore conf в C++. Каним само професионалисти.