Kurz DirectX (47.)

V dnešní lekci si podrobně rozebereme strukturu pixel shaderů jednotlivých verzí od 1.1 po 2.0. V průběhu vývoje pixel shaderů se kromě práce s texturami také změnila instrukční sada. Každá nová verze představuje nové instrukce.

47.1. Pixel shader

Před tím, než se pustím do popisu první generace pixel shaderů, sdělím pár obecných faktů, které platí víceméně pro všechny verze. Jak už jsem minule zmínil, pixel shader (podobně jako vertex shader) je relativně jednoduchá aritmeticko-logická jednotka obklopená různými typy registrů. Jak se PS postupně vyvíjí, tyto registry přibývají co do počtu a vznikají také nové typy, které rozšiřují možnosti shaderu.

Základ pixel shaderů je však pro všechny verze stejný. Na vstup přichází barva pixelu interpolovaná z okolních vertexů. Tato barva se určuje při rasterizaci trojúhelníku. V ALU jednotce je možno tuto barvu libovolně modifikovat (anebo zcela nahradit) a zapsat výsledek do výstupního registru. Tato barva se pak objeví na výsledném trojúhelníku. Nejjednodušší pixel shader pouze kopíruje vstup na výstup a tím se docílí polygonu bez textury, tedy stejného efektu jako kdybychom u fixed pipeline nastavili nulovou texturu. Jednoduchou modifikací programu můžeme zavést texturování. Právě způsob výběru texturových souřadnic a příslušné textury se u jednotlivých verzí výrazně liší.

Stejně jako vertex shader, i pixel shader má k dispozici sadu konstantních registrů, které se nastavují metodou SetPixelShaderConstant() z vašeho programu. Další registry rozebereme až podle konkrétní verze shaderu.

Kromě registrů se poměrně výrazně změnila instrukční sada. Pokud bychom se na tento vývoj dívali z dlouhodobého hlediska, došlo k výraznému zjednodušení, což je trochu paradox, protože například pixel shader 2.0 je mnohem více pružnější než jeho předchůdci. Dalším faktem je, že instrukce jsou víceméně totožné s instrukcemi vertex shaderu (samozřejmě mám na mysli hlavně aritmetické instrukce). I texturové instrukce na "nabírání" texelů jsou zredukovány a kouzla, které se ve verzi 1.3 dělaly pomocí speciální texturové instrukce, se ve vyšších verzích provádí kombinací jednoduché texturové a dalších aritmetických instrukcí.

Každá instrukce zabírá určitý počet slotů a ty jsou v jednotlivých verzích omezeny. Každopádně je třeba si dát pozor, aby program nebyl příliš dlouhý a aby se tedy vešel do pixel shaderu. Další zvláštností PS je, že může "zabíjet" pixely instrukcí texkill. Podrobně se o tom zmíním v příští lekci, ale v tuto chvíli je zajímavá informace, že z pixel shaderu nemusí vystupovat žádný pixel!

Většina registrů PS jsou typu float se čtyřmi složkami x, y, z, w nebo a, r, g, b.

47.2. PS verze 1.1-1.3

V této části si stručně popíšeme strukturu pixel shaderu verzí 1.1 až 1.3, které se od sebe víceméně neliší. Instrukční sada této verze se spíše podobá CISC procesoru, tzn. že obsahuje poměrně velké množství specializovaných instrukcí převážně na nabírání texelu z textury. Podívejme se na schematický obrázek shaderu:

Vstupní registry v0 a v1 a texturové registry t0-t3 jsou plněny z vertex shaderu - ne tedy přímo, ale jejich hodnoty pochází z interpolací okolních vertexů daného polygonu. Na pravé straně jsou vyznačeny konstantní float registry, které jsou plněny z vašeho programu metodou SetPixelShaderConstant() a je jich pouze 8!

Zajímavé jsou dva odkládací registry r0 a r1, kde r0 plní dvě role. Kromě toho, že do něj můžete uložit dočasné hodnoty, musí být do tohoto registru zapsána barva výsledného pixelu!

Nakonec si ještě povíme, jakým způsobem je vybírán texel z textury. K výběru texelu můžete použít celou řadu texturových instrukcí. Jak systém pracuje ukáži na příkladu. Mějme tento jednoduchý program:

ps.1.1
tex t0
mov r0, t0

Instrukce ps pouze deklaruje verzi pixel shaderu. Instrukce tex vybírá texel z textury nastavené ve stupni 0 (stage 0), použije při tom texturové souřadnice uložené v registru t0 a výsledek uloží opět do registru t0. Na závěr barvu texelu překopírujeme na výstup pixel shaderu. Důležité při této operaci je, že instrukce tex bere vždy texturu podle čísla použitého argumentu a použije souřadnice z příslušného registru. Takže si nemůžete říct, že použijete texturové koordináty č. 4 a vyberete ze stupně 1. Stupeň a souřadnice jsou napevno svázány.

Počet slotů této verze je velice omezen. Pouze 4 sloty na texturovací a 8 slotů na aritmetické instrukce.

47.3. PS verze 1.4

Další verze 1.4 už přinesla radikální změny převážně v instrukční sadě a způsobu výběru texelu. Přesto je tato verze plně zpětně kompatibilní se starší řadou. Když se podíváte na následující obrázek, tak zjistíte, že ve struktuře mnoho změn nenastává. Přibyly dva texturové a tři odkládací registry:

Další novinkou je možnost použití dvou fází, což umožňuje použít více instrukcí. Počet slotů je u této verze rozšířen na 6 + 8, ale zároveň můžete použít dvě fáze, takže skutečný počet slotů je vlastně dvojnásobný. K přepínání fází slouží instrukce phase. Pokud není uvedena, program implicitně běží ve druhé fází, která rovněž musí zapsat barevnou hodnotu na výstup. Přesto zde jsou jistá omezení, na které je potřeba si dát při přechodu fází pozor. Vzhledem k tomu, že tuto možnost nebudeme vůbec používat, nebudu se o tom dále rozepisovat.

Další velkou změnou je výběr texelu. Ve verzi 1.4 přibyly tři nové instrukce, které zcela nahradí tu spoustu texturovacích instrukcí z minulé verze. Jak výběr funguje si opět ukážeme na příkladu:

ps.1.4.
texld r1, t2
mov r0, r1

Instrukce texld použije texturu nastavenou ve stage 1, protože jsme použili registr r1. Dále použije texturové koordináty v t2, čili zcela jinou sadu! A výsledek operace, čili barvu texelu, uloží do registru r1. Obecně tedy platí, že se použije stupeň podle prvního argumentu instrukce (registry r0-r5) a texturové souřadnice podle druhého argumentu (t0-t5). Kromě toho může být jako druhý argument i odkládací registr čili zcela vlastní koordináty a ne ty, které přišly z vertex shaderu.

47.3. PS verze 2.0

A konečně se dostáváme k nejrozšířenější verzi pixel shaderu. Zde již najdete mnoho výrazných změn v koncepci. Instrukční sada doznala mnoha změn. Kromě několika nových texturových instrukcí, verze 2.0 přináší aritmetické instrukce podobné těm ve vertex shaderu. Když se podíváte na obrázek zjistíte, že mnoho registrů bylo rozšířeno a přibylo také pár nových typů.

Poznámka: Zde je malá nesrovnalost, kterou najdete v dokumentaci MSDN. Registr p0, který se používá u dynamických skoků, se vyskytuje až ve verzi 2.x, což je jakási rozšiřující verze PS. Bohužel jsem nenašel, na kterých grafických kartách je tato verze implementována. Sám vlastním kartu ATI Radeon X700 a ta statické a dynamické větvení nepodporuje. Přesto si myslím, že některé karty hlásí verzi 2.0 a přesto větvení podporují. Dále se s tímto vylepšením nebudu zabývat, i když je to velká škoda. Ale určitě se v budoucnosti dostaneme k verzi 3.0, která je dnes implementována pouze na nejnovějších kartách firmy nVidia a tam se k nativní podpoře větvení programu ještě dostaneme.

Ale zpět k verzi 2.0. Začneme popisem registrů:

Vstupní registry v0 a v1
Tyto dva registry plní stejnou funkci jako v předchozích verzích. Registr v0 obvykle obsahuje difusní a v1 spekulární složku interpolované barvy z vertex shaderu. Další důležitou vlastností těchto registrů je, že jsou pouze pro čtení a nelze je použít dvakrát v téže instrukci. Tyto registry vyžadují explicitní deklaraci pomocí instrukce dcl.

Odkládací registry r0, r1,...
Počet těchto registrů je různý, ale minimální počet je 12. Skutečný počet registrů zjistíte přečtením struktury D3DPSHADERCAPS2_0. Atribut NumTemps určuje počet těchto registrů (u této verze může být až 32). Registry jsou jak pro zápis tak pro čtení a v jedné instrukci se mohou vyskytovat nejvýše třikrát.

Konstantní registry
Ve verzi 2.0 (v té, kde není větvení programu) naleznete pouze registry c0-c31, které jsou typu float. Tyto registry jsou z pixel shaderu pouze pro čtení. Hodnoty se nastavují z vnějšího prostředí metodami SetPixelShaderConstant{F|I|B}(). Kromě toho mohou být konstantní registry nastaveny na začátku programu pixel shaderu instrukcí def.

Texturové registry
V registrech t0-t7 může být uloženo až 8 sad texturových souřadnic (2D nebo 3D). Tyto registry jsou pouze pro čtení (jsou nastaveny z vertex shaderu) a vyžadují opět deklaraci instrukcí dcl, kde můžeme zároveň vyznačit, které složky budeme používat. Například u 2D souřadnic bude deklarace vypadat takto:

dcl t0.xy

Adresování textur probereme později.

Vzorkovací registry (samplers) s0-s15
Tyto registry nejsou přístupné většině instrukcí, ale používají je pouze texturové instrukce texld a texldp. Představují stupeň, ve které je nastavena textura pomocí metody SetTexture() a výběr texelu je ovlivněn nastavením daného stupně metodou SetTextureStageState(). Těchto registrů je 16, takže můžete v jednom průchodu číst až 16 textur najednou! Registry vyžadují explicitní deklaraci instrukcí dcl. Například:

dcl_2d s0

Nastaví sampler s0 pro 2D texturu.

Jako poslední skupinu uvedu výstupní registry, které ve verzi 2.0 slouží opravdu jen na výstup. Výstupních registrů je pět. Kromě čtyř barevných registrů oC0-oC3, je zde navíc další registr oDepth. Z pixel shaderu verze 2.0 můžete zapisovat až do čtyřech povrchů najednou (myšleno render do textury nebo do frame bufferu). Do těchto registrů lze zapsat pouze instrukcí mov a vždy musí být zapsán alespoň registr oC0 (a pouze jednou).
Registr oDepth je skalární hodnota reprezentující hloubku pixelu pro následný hloubkový test v z-bufferu.

V této verzi byly také rozšířeny sloty na instrukce a to na 32 slotů pro texturové a 64 pro aritmetické instrukce. Rozšířená verze 2.x má pak minimálně 96 obecných slotů.

Na závěr dnešní lekce si ještě povíme o způsobu výběru texelu podle texturových souřadnic a sampleru. Uvedu krátký příklad:

ps_2_0

dcl t0.xy
dcl t1.xy
dcl_2d s0
dcl_2d s1

texld r0, t0, s1
texld r1, t1, s0
add r0, r0, r1
mov oC0, r0

První instrukce texld vybere texel podle texturových souřadnic t0 a ze stupně 1 (s1). Barvu texelu uloží do pomocného registru r0. Druhá instrukce provede totéž, ale použije 2. sadu texturových souřadnic (registr t1) a stupeň 0. Dále jen obě barvy sečteme a zapíšeme na výstup.

Z příkladu je vidět, že texturové souřadnice jsou zcela odděleny od stupně, kde je nastavena skutečná textura. Můžete třeba použít dvě textury a pouze jednu sadu souřadnic.

47.4. Závěr

Doufám, že se Vám dnešní teoretická lekce líbila. Příště si budeme povídat o assembleru verze 2.0 (starší verze již nebudu vůbec zmiňovat). Jako lákadlo Vám může posloužit následující fragment kódu:

ps_2_0

def c5, 2, -1, 0, 0
dcl t0.xyz
dcl t1.xyz
dcl t3.xyz
dcl t4.xyz
dcl t2.xy
dcl_cube s0
dcl_2d s1
dcl_2d s2

texld r0, t2, s1
nrm r1.xyz, t4
mad r2.xyz, c5.x, r0, c5.y
mul r2.xy, r2, c1.x
nrm r0.xyz, r2
mul r2.xyz, r1, r0.y
nrm r1.xyz, t3
mad r2.xyz, r0.x, r1, r2
nrm r1.xyz, t1
mad r1.xyz, r0.z, r1, r2
add r0.xyz, t0, -c0
nrm r2.xyz, r0
dp3 r0.x, r2, r1
add r0.w, r0.x, r0.x
mad r0.xyz, r1, -r0.w, r2
dp3_sat r3.x, r1, -r2
texld r1, r0, s0
texld r0, t2, s2
mul r1.w, r3.x, r3.x
mul r1.w, r1.w, r1.w
mul r1.w, r1.w, r1.w
mul r2.w, r1.w, r1.w
lrp r2.xyz, c3.x, r3.x, r1
mul r2.w, r2.w, r2.w
mad r1.xyz, r1.w, r2.w, r2
mul r1.xyz, r1, c2.x
mov r1.w, c2.x
mad r0, c4.x, r0, r1
mov oC0, r0

Už se jedná o něco složitější pixel shader. Již dnes zde poznáváte některé povinné rysy každého programu. Například poslední instrukce mov zapisuje výsledek do výstupního registru oC0.

Těším se příště nashledanou.

Jiří Formánek