Novinky / Hry

3 roky metalu - blog Roblox

Před třemi lety jsme přenesli náš renderer na Metal. Netrvalo to dlouho, bylo to úžasné a na iOS to fungovalo skvěle. Napsali jsme tedy článek, ve kterém vysvětlujeme, jak jsme se rozhodli a jak to nakonec dopadlo (spoilery: fakt dobrý!). Většina z původní retrospektivy stále platí, ale dnes je Metal v lepším stavu než kdy jindy – a tak jsme se rozhodli ji znovu publikovat s naší tříletou aktualizací.

Vraťme se tedy v čase, předstírejme, že je prosinec 2016 a právě jsme vydali verzi našeho Metal renderu pro iOS.

Proč kov?

Když Apple oznámil Metal na WWDC v roce 2014, moje první reakce byla ignorovat to. Byl k dispozici pouze na nejnovějším hardwaru, který většina našich uživatelů neměla, a přestože Apple uvedl, že řeší problémy s výkonem CPU, optimalizace pro menší trh by znamenala, že by se propast mezi nejrychlejšími a nejpomalejšími zařízeními ještě více prohloubila. . V té době jsme provozovali OpenGL ES 2 pouze na Apple a také jsme začínali portovat na Android.

Rychle vpřed dva a půl roku, takto vypadá podíl na trhu kovů pro naše uživatele:

Je mnohem atraktivnější než dříve. Stále platí, že implementace Metal nepomáhá starším zařízením, ale trh GL na iOS se neustále zmenšuje a obsah, který provozujeme na těchto starších zařízeních, se často liší od obsahu, který běží na nejnovějších zařízeních, takže má smysl věnovat určité úsilí tomu, aby to bylo rychlejší. Vzhledem k tomu, že váš kód iOS Metal bude fungovat na Macu s velmi malými úpravami, může být dobré ho použít také na Macu, i když jste zaměřeni na mobily (momentálně dodáváme pouze Metal verze na iOS).

Myslím, že stojí za to analyzovat podíl na trhu trochu podrobněji. Na iOS podporujeme Metal pro iOS 8.3+; I když někteří uživatelé nemohou spustit Metal kvůli omezením verzí OS, většina z 25%, kteří stále používají GL, pouze používají starší zařízení s hardwarem SGX. Nemají také funkcionalitu OpenGL ES 3 a my tam pouze provozujeme low-endovou renderovací cestu (ačkoli se nám líbí, že všechna zařízení jdou na Metal – naštěstí to rozdělení GL/Metal nebude. bude jen lepší). Na Macu je Metal API novější a operační systém hraje docela velkou roli – pro používání Metalu musíte používat OSX 10.11+ a polovina našich uživatelů má prostě starší operační systém – to je méně hardwaru než softwaru (95 % naši uživatelé počítačů Mac používají OpenGL 3.2+).

Takže vzhledem k podílu na trhu stále máme možnosti, které nezahrnují portování na Metal. Jedním z nich je právě použití MoltenGL, které by využívalo OpenGL kód, který již máme, ale který má být rychlejší; další je port na Vulkan (pro získání lepšího výkonu na PC a případně Androidu) a použití MoltenVK. Krátce jsem zkontroloval MoltenGL a nebyl jsem s výsledky příliš spokojen - zprovoznit náš kód vyžadovalo určité úsilí, a přestože byl výkon o něco lepší ve srovnání se standardním OpenGL, doufal jsem ve více. Pokud jde o MoltenVK, myslím si, že je špatné pokoušet se implementovat nízkoúrovňové API jako vrstvu na druhé – riskujete, že dojde k nesouladu impedance, což povede k výkonu. suboptimální – možná to bude lepší než vysokoúrovňové API které jste používali dříve, ale je nepravděpodobné, že to bude tak rychlé, jak je to možné, proto nejprve zvolte nízkoúrovňové API! Dalším důležitým aspektem je, že implementace Metalu je mnohem jednodušší než Vulkan - o tom později - takže v jistém smyslu bych preferoval obal Metal -> Vulkan místo Vulkan -> Metal.

Za zmínku také stojí, že na iOS 10 na nejnovějších iPhonech zřejmě není žádný ovladač GL – GL je implementován nad Metal. Což znamená, že používání OpenGL vám ušetří opravdu jen trochu úsilí při vývoji – ne zas tak moc, vzhledem k tomu, že příslib „jednou napsat, spustit kdekoli“, že OpenGL nefunguje, na mobilu opravdu ne.

Dopravné

Celkově bylo přenesení na Metal hračkou. Máme mnoho zkušeností s různými grafickými API, od vysokoúrovňových API, jako je Direct3D 9/11, až po nízkoúrovňová API, jako je PS4 GNM. To poskytuje jedinečnou výhodu možnosti pohodlně používat API, jako je Metal, které je zároveň na přiměřeně vysoké úrovni, ale také ponechává některé úkoly, jako je synchronizace CPU-GPU, na vývojáři aplikace.

Jedinou překážkou bylo skutečně kompilace našich shaderů - jakmile to bylo hotovo a nastal čas napsat kód, ukázalo se, že API je tak jednoduché a samovysvětlující, že se kód prakticky napsal sám. Získal jsem port, který udělal většinu věcí suboptimální, během asi 10 hodin za jediný den a další dva týdny jsem strávil čištěním kódu, opravou problémů s ověřováním, profilováním a optimalizací a obecným leštěním. Získání implementace API v té době vypovídá o kvalitě API a sady nástrojů. Věřím, že existuje několik aspektů, které přispívají:

  • Kód můžete vyvíjet postupně, s dobrou zpětnou vazbou na každém kroku. Náš kód začal tím, že ignoroval všechny synchronizace CPU-GPU, byl opravdu sub-optimální v částech nastavení stavu, používal vestavěné sledování benchmarků pro zdroje a nikdy nespouštěl CPU a GPU paralelně, aby nedošlo k selhání. fáze optimalizace/leštění to pak převedla na něco, co bychom mohli dodat, aniž bychom během toho ztratili schopnost vykreslování.
  • Nástroje jsou tu pro vás, fungují a fungují dobře. Pro lidi zvyklé na Direct3D 11 to není až tak velké překvapení – ale toto je poprvé na mobilu, kde jsem měl CPU profiler, GPU profiler, GPU debugger a GPU API validační vrstvu, které všechny fungovaly. zachytit většinu problémů během vývoje a pomoci optimalizovat kód.
  • Ačkoli je API o něco nižší úroveň než Direct3D 11 a ponechává některá klíčová rozhodnutí na nízké úrovni na vývojáři (jako je nastavení průchodu vykreslování nebo synchronizace), stále používá tradiční model zdrojů. kde každý zdroj má určité „metriky využití“. „Byl vytvořen s, ale nevyžaduje bariéry potrubí nebo přechody rozvržení, a tradiční model propojení, kde každý krok shaderu má více míst, na kterých můžete volně alokovat zdroje. Oba jsou známé, snadno srozumitelné a vyžadují velmi omezené množství kódu, abyste mohli rychle začít.

Další věc, která pomohla, je, že naše API bylo připraveno pro API podobná Metalu – je velmi štíhlé, ale odhaluje dostatek detailů (jako jsou renderovací průchody), abychom mohli snadno napsat výkonnou implementaci. V žádném okamžiku naší implementace jsem nepotřeboval ukládat/obnovovat stav (mnoho API trpí, zejména kvůli zacházení s konfigurací cíle vykreslení jako změny stavu a vázání zdroje/trvalého stavu) nebo dělat komplikovaná rozhodnutí o životnosti/synchronizaci zdroje. Jediný „složitý“ kód potřebný pro vykreslování je ten, který vytváří stav vykreslovacího kanálu hašováním bitů, které jsou potřebné k vytvoření jednokanálových stavových objektů, které nejsou součástí naší abstrakce API. I to je docela jednoduché a rychlé. Více o našem API napíšu v samostatném článku.

Takže jeden týden na kompilaci shaderů, dva týdny na získání optimalizované a vylepšené implementace1 – jaké jsou výsledky? Výsledky jsou vynikající - Metal absolutně dodržuje své sliby ohledně výkonu. Na jedné straně je výkon jednovláknového odesílání znatelně lepší než u OpenGL (tím, že se 2-3krát v závislosti na pracovní zátěži zmenší část vykreslování našeho vykreslovacího rámce), a to je vzhledem k tomu, že naše implementace OpenGL je docela dobrá. vyladěn z hlediska redundantní konfigurace stavu a hraní si s ovladačem pomocí rychlých cest. Tím to ale nekončí – multithreading v Metalu se snadno používá, pokud je na to váš renderovací kód připraven. Ještě jsme se nedostali k distribuci tisků s vlákny, ale již převádíme další části, které připravují zdroje, aby se vyskytly z vykreslovacího vlákna, což je na rozdíl od OpenGL v podstatě snadné.

Kromě toho nám Metal umožňuje řešit další problémy s výkonem poskytováním snadno dostupných a spolehlivých nástrojů. Jednou z ústředních částí našeho vykreslovacího kódu je systém, který vypočítává data osvětlení na CPU ve světovém prostoru a stahuje je do oblastí 3D textury (kterou musíme emulovat na hardwaru OpenGL ES 2). Aktualizace jsou částečné, takže nemůžeme duplikovat celou texturu a musíme se na ni spolehnout, nicméně ovladač implementuje glTexSubImage3D. V jednu chvíli jsme se pokusili použít PBO ke zlepšení výkonu aktualizací, ale čelili jsme významným problémům se stabilitou napříč platformou, a to jak na Androidu, tak na iOS. Na Metalu existují dva vestavěné způsoby, jak nahrát region – MTLTexture.replaceRegion, který můžete použít, pokud GPU aktuálně nečte texturu, nebo MTLBlitCommandEncoder (copyFromBufferToTexture nebo copyFromTextureToTexture), který dokáže stáhnout region asynchronně právě včas. GPU začne používat texturovaný GPU.

Obě tyto metody byly pomalejší, než bych si přál – první nebyla ve skutečnosti dostupná, protože jsme museli podporovat účinné dílčí aktualizace, a fungovala pouze na CPU pomocí něčeho, co vypadalo jako implementace překladu d velmi pomalé adresy. Druhý fungoval, ale zdálo se, že k vyplnění 2D textury používá řadu 3D blitů, které byly dostatečně drahé na konfiguraci bočních ovládacích prvků CPU a také z nějakého důvodu měly velmi vysokou režii GPU. Pokud by se jednalo o OpenGL, bylo by to příliš – ve skutečnosti výkon těchto dvou metod zhruba odpovídal pozorovaným nákladům na podobnou aktualizaci v OpenGL. Naštěstí jako Metal má snadný přístup k výpočetním shaderům - a super jednoduchý výpočetní shader nám umožnil provést vyrovnávací paměť -> stahování 3D textury, které bylo velmi rychlé na CPU a GPU a v podstatě to opravilo naše problémy s výkonem v tomto část kódu pro dobro2:

Závěrem lze říci, že údržba kódu Metal je asi stejně snadná – všechny funkce navíc, které jsme dosud museli přidat, bylo snazší přidat než na kterékoli jiné API, které podporujeme, a očekávám, že tento trend bude pokračovat. Byla tu trochu obava, že přidání dalšího API bude vyžadovat neustálou údržbu, ale ve srovnání s OpenGL to opravdu nevyžaduje mnoho práce; ve skutečnosti, protože už nebudeme muset podporovat OpenGL ES 3 na iOS, znamená to, že můžeme zjednodušit i kód OpenGL, který máme.

Stabilita

Dnes na iOS Metal působí velmi stabilně. Nevím, jak vypadala situace při uvedení na trh v roce 2014, nebo jak to vypadá dnes na Macu, ale ovladače a nástroje pro iOS se zdají docela solidní.

Měli jsme problém s ovladačem na iOS 10 související s načítáním shaderů zkompilovaných s Xcode 7 (který jsme opravili upgradem na Xcode 8) a pád ovladače na iOS 9, který se ukázal být důsledkem zneužití NextDrawable API. Kromě toho jsme nezaznamenali žádné chyby v chování nebo pády - pro relativně nový API Metal to bylo velmi solidní.

Kromě toho jsou nástroje, které získáte s Metal, rozmanité a bohaté; konkrétně můžete použít:

  • Poměrně komplexní ověřovací vrstva, která identifikuje běžné problémy s používáním API. Je to v podstatě jako ladění Direct3D - které je známé Direct3D, ale téměř neznámé v terénu OpenGL (teoreticky má tento problém vyřešit ARB_debug_callback, v praxi je většinou nedostupný a když není příliš užitečný)
  • Funkční ladicí program GPU, který zobrazuje všechny odeslané příkazy s jejich stavem, obsahem cílového vykreslení, obsahem textur atd. Nejsem si jistý, jestli má fungující shader debugger, protože jsem ho nikdy nepotřeboval a kontrola vyrovnávací paměti by mohla být o něco jednodušší, ale většinou to zvládne.
  • Funkční profilovač GPU, který zobrazuje statistiky výkonu na průchod (čas, šířka pásma) a také dobu provádění na shader. Vzhledem k tomu, že GPU je dlaždice, nemůžete samozřejmě očekávat synchronizaci s nulovým voláním. Mít tuto úroveň viditelnosti – zejména s ohledem na naprostý nedostatek informací o synchronizaci GPU v grafických rozhraních API na iOS – je skvělé.
  • Funkční časová osa CPU / GPU (Metal System Trace), která ukazuje plánování vykreslování CPU a GPU, podobně jako GPUView, ale ve skutečnosti se snadno používá a moduluje některé zvláštnosti uživatelského rozhraní.
  • Offline kompilátor shaderů, který ověřuje syntaxi vašeho shaderu, někdy vám dává užitečná varování, převádí váš shader na binární blob, který se za běhu načítá velmi rychle, a navíc je přiměřeně dobře optimalizován předem, což zkracuje prostoje. Doba načítání, protože kompilátor ovladače může být rychlejší.

Pokud jste z Direct3D nebo ze světa konzolí, můžete každou z nich považovat za samozřejmost – věřte mi, v OpenGL je každá z nich neobvyklá a vzbuzuje vzrušení, zvláště na mobilních zařízeních, kde je máte. „Zvyk řešit občasné selhání ovladače , žádné ověřování, žádný ladicí program GPU, žádný užitečný profilovač GPU, žádná schopnost shromažďovat plánovací data GPU a musí pracovat s shader jazykem založeným na textu, pro který má každý poskytovatel trochu jiný analyzátor.

Metal je skvělé API pro psaní kódu a přepravních aplikací. Snadno se používá, má předvídatelný výkon, má robustní ovladače a solidní sadu nástrojů. Poráží OpenGL ve všech aspektech kromě přenositelnosti, ale realita s OpenGL je taková, že jste jej měli skutečně používat pouze na třech platformách (iOS, Android a Mac), přičemž dvě z nich nyní podporují metal; navíc slib OpenGL ohledně přenositelnosti obvykle není splněn, protože kód, který píšete na jedné platformě, velmi často z různých důvodů nefunguje na jiné.

Pokud používáte engine třetí strany, jako je Unity nebo UE4, Metal je zde již podporován; pokud nejste a máte rádi grafické programování nebo vám hluboce záleží na výkonu a berete iOS nebo Mac vážně, důrazně vás vyzývám, abyste Metal vyzkoušeli. Nebudeš zklamaný.

Metal nyní

Největší změny, které se z našeho pohledu udály v Metalu za poslední tři roky, jsou ve velkém měřítku.

Před třemi lety musela čtvrtina zařízení používat OpenGL. Dnes je toto číslo pro naše publikum kolem 2 % – což znamená, že na našem OpenGL backendu už nezáleží. Stále to udržujeme, ale nebude to trvat dlouho.

Ovladače jsou lepší než kdy jindy – obecně řečeno, v iOS nevidíme problémy s ovladači, a když už ano, často se vyskytují u prvních prototypů, a než se prototypy dostanou do výroby, jsou problémy obvykle vyřešeny.

Strávili jsme také čas vylepšováním našeho Metal backendu se zaměřením na tři oblasti:

Přepracujte řetězec nástrojů kompilace shaderu

Další věcí, která se za poslední tři roky stala, je vydání a vývoj Vulkanu. I když to vypadá, že API jsou zcela odlišná (a jsou), ekosystém Vulkan poskytl komunitě vykreslování fantastickou sadu nástrojů s otevřeným zdrojovým kódem, které, když se zkombinují, vyústí v sadu nástrojů s otevřeným zdrojovým kódem. Snadno použitelné produkční- kvalitní nástroje pro kompilaci.

Použili jsme knihovny k vytvoření kompilačního toolchainu, který dokáže převzít zdrojový kód HLSL (pomocí různých funkcí DX11 včetně výpočetních shaderů), zkompilovat jej do SPIRV, optimalizovat uvedené SPIRV a převést výsledný SPIRV na MSL (Metal Shading Language). Nahrazuje náš předchozí toolchain, který mohl jako vstup používat pouze zdroj DX9 HLSL a měl různé opravy pro složité shadery.

Je poněkud ironické, že Apple s tím nemá nic společného, ​​ale jsme tady. Mnohokrát děkujeme přispěvatelům a správcům glslang (https://github.com/KhronosGroup/glslang), spirv-opt (https://github.com/KhronosGroup/SPIRV-Tools) a SPIRV-Cross (https: / / github.com/KhronosGroup/SPIRV-Cross). Poskytli jsme sadu oprav pro tyto knihovny, které nám pomohou dodat také nový toolchain a použít jej k přesměrování našich shaderů na Vulkan, Metal a OpenGL API.

podpora MacOS

Port pro macOS byl vždy možností, ale nebyl to pro nás velký problém, dokud jsme nezačali postrádat některé funkce. Rozhodli jsme se tedy investovat do Metal na macOS, abychom získali rychlejší vykreslování a odemkli některé možnosti do budoucna.

Z realizačního hlediska to nebylo vůbec těžké. Většina API je úplně stejná; Kromě správy oken byla jedinou oblastí, která potřebovala větší úpravy, alokace paměti. Na mobilu je sdílený paměťový prostor pro vyrovnávací paměti a textury, zatímco na desktopu API předpokládá vyhrazený GPU s vlastní videopamětí.

To je možné rychle obejít pomocí spravovaných zdrojů, kde se o kopírování dat za vás stará runtime Metal. Takto jsme dodali naši první verzi, ale poté jsme implementaci přepracovali, abychom explicitněji kopírovali data ze zdrojů pomocí pracovních vyrovnávacích pamětí, abychom minimalizovali režii systémové paměti.

Největší rozdíl mezi macOS a iOS byla stabilita. Na iOS jsme měli co do činění s jediným dodavatelem ovladačů na jedné architektuře, zatímco na macOS jsme museli podporovat všechny tři dodavatele (Intel, AMD, NVidia). Navíc na iOS jsme - naštěstí! - ignoroval * první * verzi iOS, kde byl k dispozici Metal, iOS 8, a na macOS to bylo nepraktické, protože bychom v té době měli příliš málo uživatelů na používání Metal. Kvůli kombinaci těchto problémů jsme narazili na mnohem více problémů s ovladači v relativně neškodných a relativně nejasných oblastech API na macOS.

Stále podporujeme všechny verze macOS Metal (10.11+), i když jsme začali odstraňovat podporu a přecházet na starší backend OpenGL pro některé verze se známými chybami kompilátoru shaderu, které je těžké obejít. , například 10.11 nyní potřebujeme macOS 10.11.6 pro Metal fungovat.

Výkonnostní přínosy byly v souladu s našimi očekáváními; pokud jde o podíl na trhu, nyní jsme na ~ 25 % uživatelů OpenGL a ~ 75 % uživatelů Metal na platformě macOS, což je docela zdravé rozdělení. To znamená, že v určitém okamžiku v budoucnu pro nás může být výhodné přestat podporovat desktopové OpenGL, protože žádná jiná platforma, kterou podporujeme, ho nepoužívá, což je skvělé. Z hlediska možnosti zaměřit se na API, která se snadněji podporují a dosáhnout dobrého výkonu s.

Iterace výkonu a spotřeby paměti

Historicky jsme byli dost konzervativní, pokud jde o funkce grafického API, které používáme, a Metal není výjimkou. Metal v průběhu let získal několik velkých aktualizací funkcí, včetně vylepšených rozhraní API pro alokaci zdrojů s explicitními hromadami, shadery dlaždic s Metal 2, vyrovnávací paměti argumentů a generování příkazů. Strana GPU atd.

Většinu času nepoužíváme žádnou z nových funkcí. Doposud byl výkon přiměřený a rádi bychom se zaměřili na vylepšení, která platí plošně, takže něco jako dlaždicové shadery, které vyžadují implementaci velmi speciální podpory během vykreslování. a jsou dostupné pouze na novějších materiálech, je méně zajímavé.

To znamená, že strávíme nějaký čas laděním různých částí backendu, aby běžely * rychleji * - pomocí zcela asynchronního stahování textur ke snížení zadrhávání při načítání úrovní, což bylo zcela bezbolestné, provedením výše uvedených optimalizací paměti v macOS, optimalizací rozložení CPU do různých místa v backendu snižující chyby mezipaměti atd. a - jedna z mála novějších funkcí, pro kterou máme výslovnou podporu - používáním bezpamětového úložiště textur, pokud je k dispozici, k dramatickému snížení paměti potřebné pro náš nový systém duchů.

Budoucnost

Celkově vzato je skutečnost, že jsme nestrávili příliš mnoho času vylepšováním kovu, vlastně dobrá věc – kód, který byl napsán před 3 lety, celkově funguje a je rychlý a stabilní. , což je skvělý znak vyspělé API. Přenesení na kov byla skvělá investice vzhledem k času, který to trvalo, a neustálým výhodám, které nám a našim uživatelům přináší.

Neustále přehodnocujeme rovnováhu mezi množstvím práce, kterou děláme pro různá API – je velmi pravděpodobné, že u některých budoucích renderovacích projektů budeme muset proniknout hlouběji do modernějších částí Metal API; pokud se tak stane, určitě o tom napíšeme další příspěvek!


  1. Jo, dobře, a možná týden na opravu některých chyb objevených během testování ↩
  2. Čísla odpovídají 128 kB aktualizovaných dat na snímek (dvě oblasti 32x16x32 RGBA8) na A10 ↩