Hlavní navigace

Grafické karty a grafické akcelerátory (22)

3. 8. 2005
Doba čtení: 10 minut

Sdílet

V dnešním pokračování seriálu o grafických kartách a grafických akcelerátorech bude podrobněji popsán způsob programování pixel a vertex shaderů pomocí k tomu specializovaných programovacích jazyků.

Obsah

1. Postupy programování vykreslovacího řetězce grafických akcelerátorů
2. Programování grafických akcelerátorů na nižší úrovni
3. Programování grafických akcelerátorů na vyšší úrovni
4. Programovací jazyk Cg
5. Základní vlastnosti jazyka Cg
6. Datové typy jazyka Cg
7. Obsah dalšího pokračování tohoto seriálu

1. Postupy programování vykreslovacího řetězce grafických akcelerátorů

S postupným (a pro programátory poněkud překotným) vývojem grafických akcelerátorů a možnostmi programovatelnosti jejich grafických procesorů (GPU) se také vyvíjely metody a postupy pro jejich programování. V dnešní době tak existuje několik vzájemně rozdílných metod, kterými je možné grafické akcelerátory programovat. Tyto metody se liší jak svými vyjadřovacími schopnostmi, tak i „složitostí“ resp. vlastní pracností programování.

Obecně lze říci, že programovatelnost grafického akcelerátoru je poskytována na dvou úrovních:

  • Nižší úroveň programování GPU využívá programovací jazyk, který se v mnohém podobá jazyku symbolických adres (assembleru), jež je používán u běžných mikroprocesorů (CPU). V tomto případě jsou vertexové a pixelové procesory striktně odděleny a oba programy jsou na sobě zcela nezávislé. Lze tedy libovolně kombinovat pevný/programo­vatelný pixel shader a pevný/programo­vatelný vertex shader. Program napsaný v takto pojatém jazyce je z velké části nepřenositelný na jiné typy grafických procesorů.
  • Vyšší úroveň programování grafického procesoru využívá programovací jazyk, který se svými vyjadřovacími schopnostmi, datovými typy a operátory podobá programovacímu jazyku C, samozřejmě s určitými omezeními, jako je nedostupnost všech iteračních příkazů, nemožnost práce s řetězci nebo omezení množství lokálních proměnných. Program napsaný v tomto jazyce je již částečně nebo úplně přenositelný na jiné typy grafických procesorů, samozřejmě s vědomím jejich funkčních rozdílů.

2. Programování grafických akcelerátorů na nižší úrovni

Pro programování pixel shaderu a vertex shaderu na nižší úrovni (jak jsme si již výše napsali, jedná se o programovací jazyk nacházející se zhruba na úrovni jazyka symbolických adres – assembleru) existuje několik způsobů zadání programu a jeho nahrání do paměti grafického procesoru. Všechny tyto způsoby jsou podporovány ve stávajících grafických knihovnách (OpenGL, DirectX) jako takzvané rozšíření – extension.

Tato rozšíření se liší podle toho, kterou verzi vertex shaderu a pixel shaderu grafický procesor podporuje a o jaký typ (a také výrobce) grafického procesoru se jedná. Například v grafické knihovně OpenGL byla koncem roku 2003 dostupná následující rozšíření pro programování vertexového shaderu (je důležité mít na paměti, že dnes je již situace v oblasti shaderů odlišná, níže uvedené tabulky pouze demonstrují postupný vývoj v této oblasti):

Rozšíření pro programování vertexového shaderu
Jméno rozšíření Verze vertex shaderu Podpora GPU
GL_NV_vertex_pro­gram vertex shader verze 1.0 firma nVidia
GL_NV_vertex_pro­gram1_1 vertex shader verze 1.1 firma nVidia
GL_NV_vertex_pro­gram2 vertex shader verze 2.0 firma nVidia
GL_ARB_vertex_pro­gram vertex shader verze 1.0 a vyšší firma nVidia
GL_EXT_vertex_sha­der vertex shader verze 1.0 firma ATI
GL_ARB_vertex_pro­gram vertex shader verze 1.0 a vyšší firma ATI

Jak je z předchozí tabulky patrné, existovala mezi implementacemi vertex shaderů shoda mezi GPU firem ATI a nVidia pouze u rozšíření GL_ARB_vertex_pro­gram. Při programování vertex shaderu pomocí tohoto rozšíření však ztrácíme některé možnosti, které jsou součástí standardního grafického vykreslovacího řetězce, zejména transformaci vertexů (vrcholů) pomocí matic ModelView a Projection, per-vertex osvětlování, generování souřadnic do textury, výpočet mlhy a automatickou normalizaci normálových vektorů. Všechny tyto činnosti je tedy nutné naprogramovat, samozřejmě v případě, že se při vykreslování využijí.

Při programování pixel shaderu máme opět k dispozici více použitelných rozšíření, kterých je dokonce ještě větší počet než u vertex shaderů. To je způsobeno zejména skutečností, že se pixel shadery začaly používat v dřívější době, nejprve jako tzv. register combiners, což však byla velmi jednoduchá náhrada skutečných shaderů. Jak je vidět z následujícího seznamu, jediné společné rozšíření je extenze navržená konsorciem ARB, které je však díky podpoře pixel shaderu 2.0 dostupné až v nejnovějších grafických procesorech řady ATI Radeon 9500 a GeForce FX.

Rozšíření pro programování pixel shaderu
Jméno rozšíření Verze pixel shaderu Podpora GPU
GL_NV_register_com­biners register combiners firma nVidia
GL_NV_register_com­biners2 register combiners firma nVidia
GL_NV_texture_sha­der pixel shader verze 1.0 firma nVidia
GL_NV_texture_sha­der2 pixel shader verze 1.0 firma nVidia
GL_NV_texture_sha­der3 pixel shader verze 1.0 firma nVidia
GL_NV_fragmen­t_program pixel shader verze 2.0 firma nVidia
GL_ARB_fragmen­t_program pixel shader verze 2.0 firma nVidia
GL_ATI_fragmen­t_shader pixel shader verze 1.4 firma ATI
GL_ARB_fragmen­t_program pixel shader verze 2.0 firma ATI

Program pro vertex shader nebo pixel shader se do grafického akcelerátoru (resp. do části paměti vyhrazené pro shadery) nahrává a spouští podobně jako textury, tj. nejprve se nahraje do paměti grafického akcelerátoru, kde je mu přiděleno identifikační číslo, a poté se pomocí tohoto čísla může kdykoliv vybrat a spustit. Programů může být v akcelerátoru uloženo více, jejich konkrétní počet a celková délka závisí na možnostech použitého grafického procesoru.

Zajímavé je, že v programech pro vertex shader i pixel shader jsou základní proměnné typu čtyřsložkový vektor. U vertex shaderu je tento typ nasnadě, protože se využívá homogenních souřadnic (x,y,z,w), u pixel shaderů je každá barva také reprezentována čtyřsložkovým vektorem, což umožňuje definovat vektorové (SIMD) operace, které jsou ve výsledku rychlejší než skalární operace.

Složky vektorů jsou většinou reprezentovány hodnotou ve formátu pohyblivé řádové tečky (floating point), což určitým způsobem zjednodušuje programování, na úkor složitějších aritmetických jednotek na grafickém akcelerátoru. Většinou se však nejedná o čísla s plným rozsahem a přesností definovanou dle IEEE, protože mají menší bitovou šířku, typicky 16 nebo 24 bitů, což je pro většinu používaných operací dostačující (je však zapotřebí brát v úvahu, že například více kumulativních operací násobení již může vykazovat značné odchylky od správných výsledků).

3. Programování grafických akcelerátorů na vyšší úrovni

I do oblasti programování grafických akcelerátorů postupně začíná pronikat model programování ve vyšších programovacích jazycích. Jedná se zejména o programovací jazyk Cg podporovaný firmou nVidia, HLSL od firmy Microsoft a GL2 Shading Language navržený firmou 3DLabs.

Tyto jazyky jsou si navzájem velmi podobné, což vyplývá z jejich vcelku přirozené orientace na programátory pracující v programovacím jazyce C. Syntaxe těchto jazyků vychází právě z programovacího jazyka C, přičemž se v nich definují některé nové datové typy (vektory a matice) a operace nad těmito typy.

Také způsob práce v těchto jazycích se podobá klasickému programování. Program pro vertex shader nebo pixel shader se vytvoří v některém z výše uvedených jazyků a kompilátor zdrojový tvar programu převede do kódu v jazyku nižší úrovně (assembleru), který je posléze uložen do paměti grafického akcelerátoru a spuštěn.

4. Programovací jazyk Cg

Jazyk Cg byl vyvinut firmou nVidia ve spolupráci s firmou Microsoft. V současnosti je možné v tomto poměrně jednoduchém a přitom mocném programovacím jazyce vytvářet programy pro vertex shaderpixel shader, přičemž jsou podporovány dvě v současnosti nejrozšířenější grafické knihovny – jak OpenGL, tak i DirectX. Kompilátor jazyka Cg generuje hned několik verzí kódu pro OpenGL i DirectX, jazyk je tedy částečně nezávislý na dostupných verzích vertex i pixel shaderů. V současné době (rok 2005) jsou na běžně dostupných grafických kartách podporovány shadery verze 1.0 i 2.0.

Program v jazyce Cg je možné předkompilovat a v aplikaci použít pouze výsledný kód v jazyce nižší úrovně, a to jak pro vertex, tak i pixel shader.

Další možností je použití run-time (běhového) překladače jazyka Cg, kdy kompilace programu ze zdrojového kódu probíhá při každém spuštění programu. Kompilátor jazyka Cg je uložen v dynamicky linkované knihovně (.dll, .so), kterou je zapotřebí přiložit k samotné aplikaci, jež jazyk Cg pro svůj běh využívá.

Druhá možnost má tu výhodu, že je umožněn překlad na téměř jakémkoliv používaném i v budoucnu vyrobeném grafickém akcelerátoru, na kterém je aplikace spuštěna. Programátor aplikace může při inicializaci otestovat typ grafické karty a podle výsledku testu nastavit požadovaný výstup překladače. Tímto postupem je také umožněna optimalizace pro daný grafický akcelerátor – vše v tomto případě závisí na dodavatelích ovladače grafického akcelerátoru.

Jak již z názvu tohoto jazyka vyplývá, jde o jazyk syntakticky velmi podobný programovacímu jazyku C. Podobně jako v jazyku C, i v Cg lze používat funkce, které jsou však překladačem chápány jako inline funkce, tj. jsou přímo při překladu rozbaleny. Program může používat pomocné proměnné, konstanty apod. Teoreticky sice není počet instrukcí ani pomocných proměnných v programu omezen, v praxi však k omezování dochází, a to nepříjemně často – současné pixel a vertex shadery využívají velmi omezené „grafické systémové“ zdroje. Pro běžné grafické akcelerátory lze uvažovat situaci, kdy pixel shader může mít například pouze 32 instrukcí a vertex shader 256 instrukcí. Výjimkou však nejsou ani čipy s mnohem menším rozsahem instrukcí (například osm a 128).

Základním datovým typem programovacího jazyka Cg je číslo reprezentované v pohyblivé řádové čárce, vektor tří a čtyř číselných hodnot (zkráceně trojice a čtveřice) a matice velikosti 4×4 prvky. Pomocí vektorů jsou vyjádřeny barvy, polohy vrcholů plošek v prostoru, normálové vektory plošek či vrcholů a také souřadnice do textury. Matice slouží především pro vyjádření transformací vrcholů a pro úpravu barev.

Významnou výhodou použití jazyka Cg je fakt, že se nemusí pracovat přímo s jednotlivými rozšířeními, stačí pouze nastavit, které rozšíření se má použít, a další práce probíhá víceméně automaticky. Další výhodou je snadná přenositelnost programů pro vertex a pixel shadery na různé grafické akcelerátory a platformy. Nezanedbatelná je také rychlost vývoje programů a optimální rychlost (samozřejmě, že pomocí ručních úprav lze program pro vertex a pixel shadery na jednotlivé typy grafických akcelerátorů optimalizovat, výhodou Cg však je, že jednou napsaná aplikace poběží optimálně i na budoucích akcelerátorech s jinou vnitřní strukturou, kterou programátor v době vývoje aplikace nemohl znát) na všech podporovaných i ještě nevytvořených grafických akcelerátorech.

S jazykem Cg je spjat i vývoj jazyka HLSL, který je sice syntakticky velmi podobný jazyku Cg, ale je určen pouze pro programové rozhraní DirectX. Oběma těmto jazykům je podobný také programovací jazyk GL2 Shading Language, který je naopak určen pouze pro programové rozhraní OpenGL (konkrétně verze 2.0).

5. Základní vlastnosti jazyka Cg

Jazyk Cg se v mnohém podobá běžným imperativním programovacím jazykům, jako jsou C, C++ a Java. Z pohledu programátora patří mezi základní vlastnosti tohoto jazyka:

  • Koncept proměnných, které mají stejný význam jako v dalších programovacích jazycích.
  • Rozšířené datové typy:
    1. skalární hodnoty
    2. struktury
    3. vektory
    4. pole
    5. matice
  • Koncept uživatelem definovaných funkcí – tyto funkce mohou vracet libovolný výše zmíněný datový typ.
  • Standardizované knihovní funkce – ty programátorovi umožňují přistupovat k jednotlivým modulům GPU.
  • Přetěžování funkcí.
  • Možnost vytvářet podmínky.
  • Možnost vytváření jednoduchých cyklů.

6. Datové typy jazyka Cg

V předchozích kapitolách byly naznačeny také některé vlastnosti jazyka Cg, které ho poněkud odlišují od běžných programovacích jazyků. Velké odlišnosti se nacházejí v podporovaných datových typech. Skalární hodnoty mohou být uloženy v proměnných typu:

  • bool – booleovská hodnota pravda/nepravda.
  • float – hodnota uložená v pohyblivé řádové čárce s „plným“ rozsahem (ten se však liší od normy IEEE), typicky to bývá 24 či 32 bitů.
  • half – hodnota uložená v pohyblivé řádové čárce s „polovičním“ rozsahem, typicky je uložena na šestnácti bitech.
  • fixed – hodnota uložená v systému pevné řádové čárky (fixed point).

Zajímavé jistě je, že se zde nenachází celočíselný datový typ. Je to logické, protože při grafických výpočtech se prakticky vždy pracuje obecně s reálnými hodnotami, proto by zavedení celých čísel celý jazyk a zejména jeho implementaci zbytečně zkomplikovalo.

Mnohem zajímavější je zápis vektorů a matic. Vektory a matice nemohou mít libovolnou velikost, v praxi jsme omezeni na maximální délku vektoru čtyři prvky a maximální velikost matice 4×4 prvky. Opět je to dáno praktickými požadavky, protože vektory se používají pro úschovu hodnot typu barva (R,B,B,α), souřadnice do textury (u,v) či souřadnice vrcholu (x,y,z) resp. v homogenním prostoru (x,y,z,w). Matice se naproti tomu typicky používají pro provádění různých transformací, ať již ve „světovém“ prostoru, tak i v prostoru textur či v barvovém prostoru RGB. Zápis datových typů založených na vektorech a maticích je následující:

  • float4 – vektor o čtyřech prvcích typu float
  • float4×4 – matice 4×4 prvky typu float
  • half3 – vektor o dvou prvcích typu half
  • bool3×3 – matice 3×3 prvky typu bool

Ke složkám vektoru se přistupuje, podobně jako k datovým složkám struktur, pomocí operátoru tečka. Složky se podle funkce vektoru nazývají buď x, y, z a w, nebo r, g, b a a. Se složkami lze provádět velkou řadu operací, jako je jejich permutace apod. O podrobnostech se zmíníme v dalším pokračování tohoto seriálu. Zde si pouze připomeneme, že veškeré operace s vektory a maticemi (včetně násobení matic, vektorového součinu, výpočtu determinantu atd.) jsou velmi rychlé, neboť se provádějí přímo na specializovaných blocích GPU. Šikovným použitím těchto operací lze dosáhnout působivých a přitom rychlých grafických efektů.

root_podpora

7. Obsah dalšího pokračování tohoto seriálu

V dalším pokračování tohoto seriálu se opět budeme věnovat programovacímu jazyku Cg. Zaměříme se na jeho pokročilejší vlastnosti, jako je vytváření nových funkcí, využívání knihovních funkcí, tvorba programových smyček atd. Nebudou chybět ani ukázky jednoduchých pixel a vertex shaderů naprogramovaných v tomto jazyce.

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.