» »

Register Combiners vs. Pixel Shaders

Tokrat bo članek nekoliko bolj tehnične narave. Namen tega članka pa je prikazati kontrast med register combinerji (na primeru NVidine implementacije) in pixel shaderji v DirectX 8.x oziroma klasičnim SetTextureStageState mehanizmom v DirectXu. Za vse, ki se bodo ustrašili, da delam preveč reklame za NVidio naj povem, da ima tudi ATI v OpenGLu podoben sistem combinerjev. Pa brez skrbi, če vam ni jasno, kaj je to SetTextureStageState - vse vam bo postalo jasno tekom branja članka.


Zakaj register combinerji?

OpenGL, za razliko od DirectXa, v osnovi nima nobenega mehanizma, ki bi mu omogočal delo v multitexturing načinu. Zato je bilo za OpenGL potrebno razviti razširitve, ki to omogočajo. Register combinerji grafičnemu čipu v bistvu povedo, kakšno zaporedje operacij naj izvede med teksturami in ostalimi parametri, da dobimo želeno barvo končne pike na zaslonu. Imajo pa še to dobro lastnost, da delujejo praktično identično kot deluje sam grafični čip.


Kaj lahko uporabljamo?

Register Combiners SetTextureStageState Pixel Shaders
Število konstant 8 * 1 8 (*)
Število tekstur 2 ali 4 do 8 4 ali 6 (ps 1.4)
Število artitmetičnih ukazov do 8 combinerjev do 8 8 ali 16 (ps 1.4 z dvema fazama)
Število ukazov za naslavljanje tekstur 0 ** 0 4 ali 6 (ps 1.4)
Začasni registri 2 1 ali 2 (DirectX 8.x) 2

* Vendar lahko prvi in drugi ukaz dostopata samo do prve in druge konstante, tretji in četrti ukaz do tretje in četrte konstante,...

** Ukazi za naslavljanje tekstur niso del register combinerjev

Kot lahko vidimo, se specifikacije le malo razlikujejo. Vedeti pa je treba, je število možnih operacij in tekstur v register combinerjih (in tudi SetTextureStageState) precej odvisno od števila teh register combinerjev in, jasno, strojne opreme (GeForce 1 in 2 imata dva register combinerja in dve teksturi, GeForce 3 in 4 Ti pa imata osem register combinerjev in štiri teksture), pixel shaderji pa imajo točno določene zahteve. Če jih strojna oprema ne doseže - pač niso podprti.


Delovanje register combinerjev

Vsak register combiner deluje v osnovi po formuli A*B+C*D, pri čemer imamo (programerji) kontrolo nad tem, ali se bo namesto katerega izmed dveh množenj izvedel skalarni produkt vektorjev (dot product). Kontrolo imamo tudi nad seštevanjem, ki ga lahko nadomestimo ukazom, ki izbere A*B ali C*D glede na alpha kanal prvega začasnega registra (spare0). Sedaj je potrebno nastaviti ustrezne vrednosti spremenljivkam A,B,C in D, kar pa naredimo s funkcijo:

glCombinerInputNV

Prvi parameter funkcije pove, kateri combiner želimo konfigurirati (od GL_COMBINER0_NV do GL_COMBINER8_NV, odvisno od čipa). Drugi parameter pove ali bomo uporabljali RGB ali alpha komponento (GL_RGB ali GL_ALPHA). Tretji parameter pove spremenljivko, ki jo konfiguriramo (npr. GL_VARIABLE_A_NV). Četrti parameter pa pove, kaj priredimo spremenljivki. To je lahko karkoli, tekstura (npr. GL_TEXTURE0_ARB), konstanta,... S petim parametrom nastavljamo morebitne spremembe spremenljivke (negiranje, spremembe obsega,...). Šesti parameter pa nastavi komponento (RGB ali alpha), ki jo bomo postavili v spremenljivko (GL_RGB ali GL_ALPHA).

Sedaj moramo nastaviti še izhod register combinerja:

glCombinerOutputNV

Prvi parameter zopet pove, kateri combiner konfiguriramo (npr. GL_COMBINER0_NV). Drugi parameter pove, katero komponento nastavljamo (GL_RGB ali GL_ALPHA). Tretji parameter pove kam želimo shraniti produkt A*B (če produkta A*B ne potrebujemo izrecno, potem to nastavimo na GL_DISCARD_NV). Četrti parameter počne isto kot tretji, s to razliko da se nanaša na produkt C*D. Peti parameter pove, kam želimo shraniti rezultat (npr. GL_SPARE0_NV). Šesti parameter skrbi za skaliranje (množenje z 1, 2, 4 ali 0.5). Sedmi parameter lahko množi rezultat z -0.5. Osmi in deveti parameter sta vrednosti true, false, ki povesta če želimo namesto običajnega produkta v A*B oziroma C*D skalarni produkt (dot product). Deseti parameter je prav tako vrednost true, false, ki pa pove, če želimo namesto seštevanja izbiro med A*B in C*D (če je alpha kanal manjši od 0.5, vzamemo A*B. Če je večji ali enak od 0.5, vzamemo C*D).

Da pridemo do željenega rezultata, moramo nastaviti še vse spremenljivke končnega combinerja:

glFinalCombinerInputNV

Ta funkcija uporablja samo štiri parametre, kjer prvi predstavlja spremenljivko, ki jo konfiguriramo. Drugi parameter pove iz katerega registra želimo vrednost naložiti. Tretji parameter skrbi za morebitne spremembe spremenljivke (negiranje,...). Četrti pa pove, katero barvno komponento želimo (GL_RGB ali GL_ALPHA). Končni combiner pa ima poleg spremenljivk A,B,C in D še E,F in G, pa tudi formula je nekoliko spremenjena. Sedaj se barva računa po formuli A*B+(1-A)*C+D in je ne moremo spreminjati. Spremenljivka G je vedno samo alpha kompoenta barve in se prepiše direktno v končno barvo. E in F pa se uporabljata za meglo in specular barvo.

Sedaj moramo samo še povedati, koliko combinerjev potrebujemo:

glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV,X);

kjer X pač pove, koliko combinerjev potrevujemo (na GeForce 1 in 2 lahko dobimo 2, na GeForce 3 in 4 Ti pa 8).

 


Prikaz usmerjene luči (directional light)

Spodaj so vam prikazane tri različne poti za dosego istega kočnega produkta:

Register Combiners:

glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV,1);
 
glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV,
		  GL_TEXTURE0_ARB, GL_EXPAND_NORMAL_NV, GL_RGB);
glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV,
 		  GL_CONSTANT_COLOR0_NV, GL_EXPAND_NORMAL_NV, GL_RGB);
glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_C_NV,
		  GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV,
		  GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
 
glCombinerOutputNV(GL_COMBINER0_NV, GL_RGB, GL_DISCARD_NV, GL_DISCARD_NV, GL_SPARE0_NV,
                   GL_NONE, GL_NONE,GL_TRUE,GL_FALSE,GL_FALSE);
 
glFinalCombinerInputNV(GL_VARIABLE_A_NV, GL_PRIMARY_COLOR_NV, 
		       GL_UNSIGNED_IDENTITY_NV,GL_RGB);
glFinalCombinerInputNV(GL_VARIABLE_B_NV, GL_SPARE0_NV,
		       GL_UNSIGNED_IDENTITY_NV,GL_RGB);
glFinalCombinerInputNV(GL_VARIABLE_C_NV, GL_ZERO,
		       GL_UNSIGNED_IDENTITY_NV,GL_RGB);
glFinalCombinerInputNV(GL_VARIABLE_D_NV, GL_ZERO,
		       GL_UNSIGNED_IDENTITY_NV,GL_RGB);
 
glEnable(GL_REGISTER_COMBINERS_NV);


SetTextureStageState

lpDevice->SetTextureStageState(0,D3DTSS_COLORARG1,D3DTA_TEXTURE);
lpDevice->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_DOTPRODUCT3);
lpDevice->SetTextureStageState(0,D3DTSS_COLORARG2,D3DTA_TFACTOR);
 
lpDevice->SetTextureStageState(1,D3DTSS_COLORARG1,D3DTA_CURRENT);
lpDevice->SetTextureStageState(1,D3DTSS_COLOROP,D3DTOP_MODULATE);
lpDevice->SetTextureStageState(1,D3DTSS_COLORARG2,D3DTA_DIFFUSE);


Pixel Shader:

ps.1.0
tex t0
dp3_sat r0, t0_bx2, c0_bx2
mul r0, r0, v0

Očitno je koda register combinerjev najdaljša in najbolj komplicirana, vendar pa za uporabo Pixel Shaderja potrebujemo z DirectX 8.0 združljivo grafično kartico. Klasičen SetTextureStageState pa potrebuje dva prehoda.


Linearna interpolacija dveh tekstur

Tudi tukaj so vam prikazane tri različne poti za dosego istega produkta:

Register Combiners:

glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV,1);
 
glCombinerInputNV(GL_COMBINER0_NV, GL_RGB,GL_VARIABLE_A_NV, 
		  GL_TEXTURE0_ARB,GL_UNSIGNED_IDENTITY_NV,GL_RGB);
glCombinerInputNV(GL_COMBINER0_NV, GL_RGB,GL_VARIABLE_B_NV,
		  GL_CONSTANT_COLOR0_NV,GL_UNSIGNED_IDENTITY_NV,GL_RGB);
glCombinerInputNV(GL_COMBINER0_NV, GL_RGB,GL_VARIABLE_C_NV,
		  GL_TEXTURE1_ARB,GL_UNSIGNED_IDENTITY_NV,GL_RGB);
glCombinerInputNV(GL_COMBINER0_NV, GL_RGB,GL_VARIABLE_D_NV,
		  GL_CONSTANT_COLOR0_NV,GL_UNSIGNED_INVERT_NV,GL_RGB);
 
glCombinerOutputNV(GL_COMBINER0_NV,GL_RGB,GL_DISCARD_NV,GL_DISCARD_NV,GL_SPARE0_NV,
                   GL_NONE,GL_NONE,GL_FALSE,GL_FALSE,GL_FALSE);
 
glFinalCombinerInputNV(GL_VARIABLE_A_NV, GL_SPARE0_NV,
		       GL_UNSIGNED_IDENTITY_NV, GL_RGB);
glFinalCombinerInputNV(GL_VARIABLE_B_NV, GL_ZERO,
		       GL_UNSIGNED_INVERT_NV, GL_RGB);
glFinalCombinerInputNV(GL_VARIABLE_C_NV, GL_ZERO,
		       GL_UNSIGNED_IDENTITY_NV, GL_RGB);
glFinalCombinerInputNV(GL_VARIABLE_D_NV, GL_ZERO,
		       GL_UNSIGNED_IDENTITY_NV, GL_RGB);
 
glEnable(GL_REGISTER_COMBINERS_NV);


SetTextureStageState

lpDevice->SetTextureStageState(0,D3DTSS_COLORARG1,D3DTA_TEXTURE);
lpDevice->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_SELECTARG1);
 
lpDevice->SetTextureStageState(1,D3DTSS_COLORARG0,D3DTA_TFACTOR);
lpDevice->SetTextureStageState(1,D3DTSS_COLORARG1,D3DTA_CURRENT);
lpDevice->SetTextureStageState(1,D3DTSS_COLOROP,D3DTOP_LERP);
lpDevice->SetTextureStageState(1,D3DTSS_COLORARG2,D3DTA_TEXTURE);


SetTextureStageState

ps.1.0
tex t0
tex t1
lrp r0, t0, t1, c0

Tukaj je predstavljen še en primer, kjer je uporaba register combinerjev najbolj zapletena pot, vendar zopet videz vara, saj SetTextureStageState tudi tokrat zapolni oba razpoložljiva "stage"-a na GeForce 2, tako da o kakšnih nadaljnih popravkih ni govora. Pri register combinerjih pa nam ostane na razpolago še en combiner, ki ga lahko porabimo za kakšne popravke (na primer dodajanje barve). Pixel shaderji pa zopet potrebujejo DirectX 8.0 kompatibilno grafično kartico...


Zaključek

Register combinerji so podprti samo kot razširitev v OpenGLu in omogočajo, da programerji iz grafičnih kartic iztisnejo še nekoliko več kot običajno. Velikokrat pa so register combinerji tudi hitrejši, saj se da narediti več stvari istočasno. Pixel shaderji se znotraj gonilnikov še vedno pretvarjajo v register combinerje in tako bo po vsej verjetnosti še nekaj časa tudi ostalo. Obenem pa imajo pixel shaderji trenutno še to hibo, da jih podpirajo samo najnovejše grafične kartice. Če vas zanima še kaj več na temo register combinerjev, pa kar pišite.

Čudežno popotovanje skozi grafični cevovod II

Čudežno popotovanje skozi grafični cevovod II

V prvem delu smo si ogledali strukturo sodobne grafične kartice. Med drugim smo omenili shaderje, ki so zaradi svoje programabilnosti, nekakšno srce in duša sodobnih GPU-jev. Različni shaderji so odgovorni za različne strukture v grafiki (v Direct3D 10 so to oglišča, ...

Preberi cel članek »

DirectX 9.0 - Osnove programiranja

DirectX 9.0 - Osnove programiranja

V prejšnjih dveh člankih smo si ogledali vse novosti, ki jih je Microsoft uvedel v DirectX. Ker verjamem, da mnogi med vami niso ravno programerji, vas pa vseeno zanima, kako izgleda življenje na drugi strani, sem se odločil, da vam to vsaj malo približam. V tem članku bo ...

Preberi cel članek »

Novosti DirectXa 9 - 1. del

Novosti DirectXa 9 - 1. del

No pa smo vendarle dočakali novo, dolgo pričakovano, različico knjižnice DirectX, ki jo je zlobni Microsoft, bolj ali manj skrbno skrival pred radovednimi očmi javnosti. V približno enaki tajnosti sta nastala tudi ta dva članka, ki vam bosta, vsaj upam tako, uspela ...

Preberi cel članek »