Tutoriál programování grafických procesorů

Podobné dokumenty
Co je grafický akcelerátor

Fakulta informačních technologíı. IZG cvičení 6. - Zobrazování 3D scény a základy OpenGL 1 / 38

Sběrnicová struktura PC Procesory PC funkce, vlastnosti Interní počítačové paměti PC

PŘEDSTAVENÍ GRAFICKÉHO PROCESORU NVIDIA G200

Gymnázium Vysoké Mýto nám. Vaňorného 163, Vysoké Mýto

Gymnázium Vysoké Mýto nám. Vaňorného 163, Vysoké Mýto

Architektura počítačů

Geekovo Minimum. Počítačové Grafiky. Nadpis 1 Nadpis 2 Nadpis 3. Božetěchova 2, Brno

OPS Paralelní systémy, seznam pojmů, klasifikace

Nvidia CUDA Paralelní programování na GPU

Pokročilé architektury počítačů

Úvod do GPGPU J. Sloup, I. Šimeček

Přednáška. Správa paměti II. Katedra počítačových systémů FIT, České vysoké učení technické v Praze Jan Trdlička, 2012

Typy souborů ve STATISTICA. Tento článek poslouží jako přehled hlavních typů souborů v programu

GIS Geografické informační systémy

Přednášky o výpočetní technice. Hardware teoreticky. Adam Dominec 2010

Zobrazovací a zvuková soustava počítače

Vektorové grafické formáty

Operační systémy. Jednoduché stránkování. Virtuální paměť. Příklad: jednoduché stránkování. Virtuální paměť se stránkování. Memory Management Unit

Programovací jazyky. imperativní (procedurální) neimperativní (neprocedurální) assembler (jazyk symbolických instrukcí)

GIS Geografické informační systémy

ARCHITEKTURA AMD PUMA

PROCESOR. Typy procesorů

Přehled paralelních architektur. Dělení paralelních architektur Flynnova taxonomie Komunikační modely paralelních architektur

ZÁKLADY PROGRAMOVÁNÍ. Mgr. Vladislav BEDNÁŘ /14

Představení a vývoj architektur vektorových procesorů

Pokročilá architektura počítačů

2.8 Procesory. Střední průmyslová škola strojnická Vsetín. Ing. Martin Baričák. Název šablony Název DUMu. Předmět Druh učebního materiálu

VYSOKÁ ŠKOLA BÁŇSKÁ TECHNICKÁ UNIVERZITA OSTRAVA FAKULTA STROJNÍ DATABÁZOVÉ SYSTÉMY ARCHITEKTURA DATABÁZOVÝCH SYSTÉMŮ. Ing. Lukáš OTTE, Ph.D.

Architektura grafických ip pro Xbox 360 a PS3

AGP - Accelerated Graphics Port

C2115 Praktický úvod do superpočítání

Řízení IO přenosů DMA řadičem

Grafické karty. Autor: Kulhánek Zdeněk

Grafické karty s podporou DirectX 11 Quynh Trang Dao Dao007

Grafická karta nebo také videoadaptér je součást počítače, která se stará o grafický výstup na monitor, TV obrazovku či jinou zobrazovací jednotku.

GRAFICKÉ ADAPTÉRY. Pracovní režimy grafické karty

Programovací jazyky. imperativní (procedurální) neimperativní (neprocedurální) assembler (jazyk symbolických instrukcí)

Princip funkce počítače

Architektura Intel Atom

Sběrnicová struktura PC Procesory PC funkce, vlastnosti Interní počítačové paměti PC

Z čeho se sběrnice skládá?

Počítač jako elektronické, Číslicové zařízení

Základy 3D modelování a animace v CGI systémech Cinema 4D C4D

VÝUKOVÝ MATERIÁL. Bratislavská 2166, Varnsdorf, IČO: tel Číslo projektu

2D transformací. červen Odvození transformačního klíče vybraných 2D transformací Metody vyrovnání... 2

HW počítače co se nalézá uvnitř počítačové skříně

Profilová část maturitní zkoušky 2013/2014

Architektura počítače

Profilová část maturitní zkoušky 2017/2018

8 Třídy, objekty, metody, předávání argumentů metod

Architektury počítačů a procesorů

1. lekce. do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme:

Vyhodnocení 2D rychlostního pole metodou PIV programem Matlab (zpracoval Jan Kolínský, dle programu ing. Jana Novotného)

Přednáška. Vstup/Výstup. Katedra počítačových systémů FIT, České vysoké učení technické v Praze Jan Trdlička, 2012

Zobrazování terénu. Abstrakt. 1. Úvod. 2. Vykreslování terénu

G R A F I C K É K A R T Y

Programujeme v softwaru Statistica

DUM č. 14 v sadě. 31. Inf-7 Technické vybavení počítačů

Informatika pro 8. ročník. Hardware

Algoritmizace a programování

Karel Johanovský Michal Bílek SPŠ-JIA GRAFICKÉ KARTY

13 Barvy a úpravy rastrového

Programátorská dokumentace

Pokročilé architektury počítačů

architektura mostů severní / jižní most (angl. north / south bridge) 1. Čipové sady s architekturou severního / jižního mostu

4. Úvod do paralelismu, metody paralelizace

Zobrazovací jednotky a monitory

Práce s texty, Transformace rastru, Připojení GPS

Čtvrtek 3. listopadu. Makra v Excelu. Obecná definice makra: Spouštění makra: Druhy maker, způsoby tvorby a jejich ukládání

5.15 INFORMATIKA A VÝPOČETNÍ TECHNIKA

Osobní počítač. Zpracoval: ict Aktualizace:

Pohled do nitra mikroprocesoru Josef Horálek

NSWI /2011 ZS. Principy cpypočítačůčů aoperačních systémů ARCHITEKTURA

2. přednáška z předmětu GIS1 Data a datové modely

Android OpenGL. Pokročilé shadery

Hardware. Z čeho se skládá počítač

Vyučovací hodina. 1vyučovací hodina: 2vyučovací hodiny: Opakování z minulé hodiny. Procvičení nové látky

Gymnázium a Střední odborná škola, Rokycany, Mládežníků 1115

Využijte plný výkon procesorů s více jádry v LabVIEW 8.5

5 Přehled operátorů, příkazy, přetypování

ANOTACE vytvořených/inovovaných materiálů

Rotace, transpozice a zrcadlení matice hodnot

Algoritmy a algoritmizace

Procesy a vlákna (Processes and Threads)

PHP tutoriál (základy PHP snadno a rychle)

Nová architektura od ATI (Radeon HD 4800) Datum: Vypracoval: Bc. Radek Stromský

Základní deska (1) Označována také jako mainboard, motherboard. Deska plošného spoje tvořící základ celého počítače Zpravidla obsahuje:

Hardware - komponenty počítačů Von Neumannova koncepce počítače. Von Neumannova koncepce počítače

Obecné výpočty na GPU v jazyce CUDA. Jiří Filipovič

Zpráva o průběhu přijímacího řízení na vysokých školách dle Vyhlášky MŠMT č. 343/2002 a její změně 276/2004 Sb.

Programování v jazyku LOGO - úvod

Algoritmizace. 1. Úvod. Algoritmus

Hierarchický model Josef Pelikán CGG MFF UK Praha. 1 / 16

1 Strukturované programování

2.9 Vnitřní paměti. Střední průmyslová škola strojnická Vsetín. Ing. Martin Baričák. Název šablony Název DUMu. Předmět Druh učebního materiálu

1. lekce. do souboru main.c uložíme následující kód a pomocí F9 ho zkompilujeme a spustíme:

Xbox 360 Cpu = IBM Xenon

KAPITOLA 1 - ZÁKLADNÍ POJMY INFORMAČNÍCH A KOMUNIKAČNÍCH TECHNOLOGIÍ

Další aspekty architektur CISC a RISC Aktuálnost obsahu registru

Transkript:

České vysoké učení technické v Praze Fakulta elektrotechnická Bakalářská práce Tutoriál programování grafických procesorů Jana Žďárská Vedoucí práce: Ing. Roman Berka, Ph.D. Studijní program: Elektrotechnika a informatika, strukturovaný bakalářský Obor: Informatika a výpočetní technika srpen 2007 i

ii

Poděkování Na tomto místě bych chtěla velmi poděkovat všem, kdo mě po celou dobu práce podporovali a pomáhali mi. Můj dík patří především vedoucímu práce panu Ing. Romanu Berkovi Ph.D. za nekonečnou trpělivost a spoustu cenných rad. Dále pak Ing. Janu Štalmachovi za pomoc při hledání informačních zdrojů. iii

iv

Prohlášení Prohlašuji, že jsem svou bakalářskou práci vypracovala samostatně a použila jsem pouze podklady uvedené v přiloženém seznamu. Nemám závažný důvod proti užití tohoto školního díla ve smyslu 60 Zákona č.121/2000 Sb., o právu autorském, o právech souvisejících s právem autorským a o změně některých zákonů (autorský zákon). V Praze dne 14.8. 2007... v

vi

Abstract This work deals with issue of graphics processing units. It describes principles of their work and possibilities of their application. There is also an introduction to the Cg programming language and to the BrookGPU tool. Both of them enable work with the graphics pipeline. The text should give you a skill of thinking in graphics processor logic and skill of writting basic programs for graphics hardware. Abstrakt Tato práce se zabývá problematikou grafických procesorů. Rozebírá principy jejich fungování a zasvěcuje čtenáře do možností jejich využití. Zároveň poskytuje seznámení s programovacím jazykem Cg a s nástrojem BrookGPU pomocí nichž lze s procesory pracovat. Díky této práci by měl být čtenář schopen uvažovat v logice použité těmito procesory a zároveň by měl získat základní dovednosti v psaní programů pro grafický hardware. vii

viii

Obsah Seznam obrázků... xiii Seznam tabulek... xiv Seznam matematických vzorců... xiv Seznam ukázek kódu... xiv 1. Úvod... 1 2. Trendy vývoje grafických procesorů... 2 2.1. Technologické trendy v navrhování procesorů... 2 2.1.1. Výpočet versus komunikace... 3 2.1.2. Latence versus šířka pásma... 3 2.1.3. Výkon... 3 2.2. Metody dosažení vysokého výpočetního výkonu... 4 2.2.1. Metody efektivního výpočtu... 4 2.2.2. Metody efektivní komunikace... 5 2.3. Rozdíl v návrhu CPU a GPU... 5 3. Princip programování grafických procesorů... 6 3.1. Proudový programovací model... 6 3.1.1. Proud... 6 3.1.2. Výpočetní jádro... 7 3.1.3. Efektivita výpočtů... 8 3.1.4. Efektivita komunikace... 9 3.2. Proudový procesor... 9 3.2.1. Režie proudového procesoru... 9 3.2.2. Rozdíl mezi vektorovým a proudovým procesorem... 10 4. Základní grafická pipeline... 11 4.1. Historický vývoj grafické pipeline... 11 4.1.1. 1.generace... 11 4.1.2. 2.generace... 12 4.1.3. 3.generace... 12 4.1.4. 4.generace... 13 4.2. Průchod grafickou pipeline... 15 4.2.1. Transformace vrcholů... 15 ix

4.2.2. Skládání a rasterizace... 15 4.2.3. Interpolace, texturování a barvení... 16 4.2.4. Rasterové operace... 16 5. Tutoriál programování vertex a fragment procesorů v jazyce Cg... 17 5.1. Úvod do programování grafických procesorů... 17 5.1.1. Základní pojmy... 17 5.1.2. Programovací jazyk... 18 5.1.3. Reálné využití... 19 5.2. Lekce č.1: Základy jazyka I... 21 5.3. Lekce č.2: Základy jazyka II... 25 5.4. Lekce č.3: Transformace... 28 5.5. Lekce č.4: Základní osvětlení... 33 6. Negrafické programování vertex a fragment procesorů... 37 6.1. Jazyk BrookGPU... 38 6.2. Lineární algebra pomocí grafického procesoru... 43 6.2.1. Maticová reprezentace... 43 6.2.2. Základní operace... 44 6.2.2.1.Vektorové počty... 45 6.2.2.2.Součin matice-vektor... 45 6.2.2.3.Vektorová redukce... 46 6.2.3. Řídké matice... 47 6.2.3.1.Páskovaná matice... 47 6.2.3.2.Náhodně řídké matice... 47 6.3. Rychlá Fourierova transformace pomocí grafického procesoru... 48 6.3.1. Algoritmus Cooley-Tukey... 49 6.3.2. Implementace pomocí grafického procesoru... 51 6.3.2.1.Řešení s častým spouštěním fragment programu... 52 6.4. Databázové operace pomocí grafického procesoru... 54 6.4.1. Základní databázové operace pomocí grafického procesoru... 54 6.4.1.1.Implementace výrazu... 55 6.4.1.2.Implementace boolean kombinace... 56 7. Aktuální modely grafických procesorů a jejich charakteristiky... 59 7.1. nvidia... 59 7.1.1. GeForce 8800 Ultra... 59 7.1.2. Technologie využívané řadou GeForce 8800... 60 x

7.1.2.1.Unifikovaná architektura shaderů... 60 7.1.2.2.Lumenex engine... 60 7.1.2.3.PureVideo HD... 61 7.1.2.4.SLI technologie... 61 7.2. ATI Radeon... 61 7.2.1. HD 2900 XT... 61 7.2.2. Technologie využívané řadou HD 2900... 62 7.2.2.1.Unifikovaná architektura superskalárních shaderů... 62 7.2.2.2.Crossfire... 62 7.2.2.3.ATI Avivo HD video a zobrazovací platforma... 62 8. Závěr... 64 9. Použitá literatura... 65 10. Příloha A... 67 10.1. Zdrojové kódy k příkladu první lekce tutoriálu Cg... 67 10.2. Zdrojové kódy k prvnímu příkladu druhé lekce tutoriálu Cg... 68 10.3. Zdrojové kódy k druhému příkladu druhé lekce tutoriálu Cg... 69 10.4. Zdrojové kódy k příkladu třetí lekce tutoriálu Cg... 69 10.5. Zdrojové kódy k prvnímu příkladu čtvrté lekce tutoriálu Cg... 70 10.6. Zdrojové kódy k druhému příkladu čtvrté lekce tutoriálu Cg... 71 11. Obsah CD... 73 xi

xii

Seznam obrázků 3.1. Princip práce s proudy... 8 3.2. Rozložení jednotek proudového procesoru, obrázek z [3]... 9 4.1. Pipeline 1.generace podle [7]... 11 4.2. Pipeline 2.generace podle [7]... 12 4.3. Pipeline 3.generace podle [7]... 13 4.4. Pipeline 4.generace podle [7]... 14 4.5. Průchod grafickou pipeline podle [4]... 15 5.1. Výstup prvního programu v Cg... 24 5.2. Výstup druhého programu v Cg... 26 5.3. Výstup třetího programu v Cg... 27 5.4. Transformace a transformační prostory podle [4]... 28 5.5. Schéma transformací pro funkci lookat... 32 5.6. Výstup čtvrtého programu v Cg... 33 5.7. Emisivní složka světla podle [4]... 33 5.8. Ambientní složka světla podle [4]... 34 5.9. Difusní složka světla podle [4]... 34 5.10. Spekulární složka světla podle [4]... 34 5.11. Výstup pátého programu v Cg... 36 5.12. Výstup šestého programu v Cg... 36 6.1. Maticová reprezentace z [15]... 44 6.2. Průběh algoritmu Cooley-Tukey z [19]... 51 xiii

Seznam tabulek 4.1. Parametry grafických procesorů různých generací z [8][9]... 14 5.1. Přehled samplerů pro jazyk Cg z [4]... 26 Seznam matematických vzorců Matematický vzorec 5.1.: Transformační matice podle [12]... 31 Matematický vzorec 6.1.: Diskrétní Fourierova transformace z [11]... 48 Matematický vzorec 6.2.: Rozpůlení výpočtu DFT z [11]... 49 Matematický vzorec 6.3.: Základ motýlkového výpočtu z [11]... 49 Matematický vzorec 6.4.: Motýlkový výpočet z [11]... 50 Matematický vzorec 6.5.: Semilineární dotaz z [20]... 56 Seznam ukázek kódu Ukázka kódu 6.1.: Násobení matice a vektoru fragment program... 46 Ukázka kódu 6.2.: Fragment program pro FFT z [18]... 52 Ukázka kódu 6.3.: Pseudokód vyhodnocení CNF z [20]... 57 Ukázka kódu 10.1.: Vertex program Pr01v... 67 Ukázka kódu 10.2.: Fragment program Pr01f... 67 Ukázka kódu 10.3.: Vertex program Pr02v... 68 Ukázka kódu 10.4.: Fragment program Pr02f... 68 Ukázka kódu 10.5.: Vertex program Pr03v... 69 Ukázka kódu 10.6.: Fragment program Pr03f... 69 Ukázka kódu 10.7.: Vertex program Pr04v... 69 Ukázka kódu 10.8.: Vertex program Pr05v... 70 Ukázka kódu 10.9.: Vertex program Pr06v... 71 Ukázka kódu 10.10.: Fragment program Pr06f... 71 xiv

xv

xvi

1. Úvod Grafické procesory čili GPU se staly velmi silným nástrojem a přesáhly tak rámec použití výhradně pro grafické účely. Díky jejich programovatelnosti, přesnosti(v současnosti 128bit FP po celé pipeline) a síle se staly předmětem zájmu i v naprosto negrafických aplikacích. Motivace k jejich využití byla prostá. Jsou nejen mnohem rychlejší, ale jejich rychlost roste značně strměji, než rychlost klasických procesorů. Tento znak je důsledkem jejich specifického použití, které umožňuje naprosto paralelní zpracování. Jelikož byly původně určeny čistě pro oblast zpracovaní obrazu, uvažují absolutní datovou nezávislost(jednotlivé pixely lze počítat najednou aniž by se mohlo stát, že bude výsledek v jednom pixelu ovlivněn pixelem jiným). Díky této vlastnosti se pak vývojáři zbavili věčného problému, který provází klasické procesory. Navíc je vývoj v této oblasti podporován velkým komerčním zájmem o lepší a lepší počítačové hry a grafické aplikace vůbec. Takže je vývoj rychle tlačen vpřed. Paralelnost zpracování je ovšem stejně dobře uplatnitelná i v jiných oblastech, kam pomalu GPU pronikají. V negrafických oblastech se využívají plně programovatelné grafické procesory a pro jejich univerzálnost se nazývají GPGPU čili General-Purpose Graphics Processing Units (často se lze setkat i se zkratkami GPGP a GP^2). Cílem se tedy v současnosti stává zpřístupnění GPU vývojářům jako druhu koprocesoru, který se bude doplňovat s CPU. Překážku tvoří rozdílný přístup k věci, tedy neobvyklý programovací model. Další nepříjemnost je způsob programování. Jde to buď v assembleru nebo ve speciálních jazycích, které jsou ovšem stavěny s ohledem na jejich použití v ryze grafických aplikacích. Nově proto vznikají jazyky pro negrafické programování. V současnosti jsou to BrookGPU a Sh. Také architektura skýtá nemilá překvapení. To, že poskytuje plně paralelní zpracování, nemusí být vždy zrovna výhodou. Navíc se velmi rychle vyvíjí a to tak, že se mění i naprosté základy. Povzbudivé není ani to, že samotná architektura je vysoce chráněným výrobním tajemstvím. 1

2. Trendy vývoje grafických procesorů 2.1. Technologické trendy v navrhování procesorů Dnešní procesory se skládají z miliónů propojených tranzistorů. S postupným vývojem se zmenšuje plocha potřebná pro jejich osazení, takže jich lze vložit více a více. Velmi dobře známý je v tomto ohledu Moorův zákon. V roce 1965 Gordon Moore předpověděl, že se počet tranzistorů na jednom procesoru může každý rok a půl zdvojnásobit. Tato tendence se neustále potvrzuje až do dnešních dní. Z původních padesáti tranzistorů z roku 1965 jsme se tak roku 2005 dostali na stovky miliónů kusů[1]. Na stále novějších čipech se ovšem zlepšuje víc než jen počet tranzistorů. Zároveň je tu i jiný trend. Tím je zmenšování jejich velikosti, které má za následek jejich rychlejší činnost. Podíváme-li se v časové ose zpět, zjistíme, že se každý rok jednotlivé tranzistory zmenší zhruba o 15%. Jako uživatelé můžeme tuto změnu sledovat na zrychlování hodin procesoru. Sloučíme-li dohromady ukazatele, kterými jsou zmíněný vzrůst počtu tranzistorů a zrychlování hodin procesoru, zjistíme, že celková schopnost procesorů roste každoročně o 71%[2]. Výpočetní paměť, která se zakládá na zcela odlišných principech výroby než procesorová logika, se taktéž postupně vyvíjí. Předpokládá se, že v oblasti DRAM(Dynamic randomaccess memory) dojde každoročně ke zdvojnásobení kapacity. Výkonost této paměti se dá měřit s ohledem na dva parametry. Prvním je šířka pásma(bandwidth) která referuje o množství dat přenesených za jednu vteřinu. Druhým parametrem je pak latence, tedy čas, který uplyne od doby vyslání požadavku do doby doručení odpovídajících dat. Výkonnost DRAM ovšem zdaleka nestoupá tak rychle, jak stoupá schopnost logické části procesorů. Šířka pásma ročně vzroste zhruba o čtvrtinu, zatímco latence se zlepší o pouhých 5 procent[2]. Na první pohled je hezké, jak se procesory každoročně vyvíjí, na druhý pohled zde ovšem vyvstává problém. Tím je stále se zvětšující propast mezi výpočetní schopností a rychlostí obsluhy paměti. Rychlost jejich zlepšování je totiž zásadně odlišná, jak jsme si před chvílí 2

uvedli na konkrétních číslech. Proto je nutné s každým dalším posunem znovu zvážit možnosti designu. Rozeznáváme tři hlavní problematiky, které jsou hlavními faktory v řízení vývoje GPU. Jsou jimi: Výpočet versus komunikace, latence versus šířka pásma a výkon[2]. Teď si o nich něco málo povíme. 2.2.1. Výpočet versus komunikace S trendem neustálého zrychlování hodin a rozměrů čipu, roste i počet cyklů hodin potřebný k přenosu signálu z jedné strany čipu na druhou. Tento trend lze charakterizovat jako narůstání ceny komunikace v porovnání s výpočetním výkonem. V důsledku se dá tedy tvrdit, že návrháři budou čím dál více používat levnější tranzistory aby odstranili nevýhodu drahé komunikace. Dalším dopadem je vzrůstající počet výpočtů použitelných na slovo z paměťové šířky pásma. Příkladem může být srovnání tří vlajkových lodí nvidie. Model roku 2002 GeForce FX 5800 mohl využít 2 floating-point operace na každé slovo, zatímco u modelu roku 2003 GeForce FX 5950 už to bylo 2.66 operací a roku 2004 na GeForce 6800 téměř 6 floating-point operací[2]. 2.1.2. Latence versus šířka pásma Rozdíl v trendech vývoje latence a šířky pásma je dalším velmi důležitým hybatelem v návrhu architektur. Jelikož se latence zmenšuje mnohem pomaleji, než jak se rozšiřuje šířka pásma, je nutné implementovat řešení, které toleruje zvětšující se latenci a je schopné vykonávat během čekání užitečnou práci. 2.1.3. Výkon Čím dál menší tranzistory potřebují méně energie, takže by se mohlo zdát, že je vše v pořádku a není třeba zvyšovat výkon procesorů. To ovšem není pravda. 3

Důvodem je, že energie potřebná k uspokojení narůstajícího počtu tranzistorů je mnohem větší, než energie ušetřená jejich zmenšováním se. Proto se u procesorů výkon stále zvyšuje. To ovšem nejde dělat do nekonečna. Je zde hranice výkonu, kterou nemůžeme překročit kvůli teplotě s ním spojené. Proto se budoucnost nebude ubírat směrem k zvyšování počtu operací za sekundu, ale spíše ke zvyšování počtu operací za sekundu za watt. 2.2. Metody dosažení vysokého výpočetního výkonu Jak jsme si právě ukázali, moderní technologie umožňuje využívat každoroční zlepšení. To je ale nutné správně využít. Proto je nutné zaměřit se na dva hlavní cíle. Prvním je správná organizace výpočetních zdrojů, která bude ideálně využitá v oblasti aplikace, kterou se zabýváme. Je tedy nutné správně zaměstnat všechny jednotky a rozvrhnout jejich práci tak, aby byla nabytá technologická zlepšení efektivně využita. Druhým cílem musí být efektivní komunikace těchto jednotek. Pokud jí totiž nevyřešíme dostatečně dobře, může doba potřebná k předání informací zcela smazat náskok získaný efektivním výpočtem. 2.2.1 Metody efektivního výpočtu Ačkoli nám moderní technologie poskytují obrovské množství tranzistorů k použití na jediném čipu, naše zdroje nejsou neomezené. V první řadě je nutné tento počet rozdělit do tří hlavních skupin. Kontrolní, která bude směrovat výpočet do správných jednotek. Výpočetní, která bude provádět samotný výpočet. A nakonec úložná, kterou bude náš hardware používat k ukládání dat. Čím lépe si rozvrhneme organizaci, tím větší podíl nám zbude na samotné výpočty. V nich můžeme využít výhod datového nebo instrukčního paralelismu. Datový paralelismus znamená, že provádíme výpočet najednou na více datech. Instrukční paralelismus pak značí, že můžeme vykonávat několik instrukcí najednou[2][3]. 4

Otázku, jaké úlohy budou jednotlivé výpočetní jednotky plnit lze jednoduše přejít jejich plnou programovatelností. To by ovšem nebylo zrovna nejefektivnější řešení, protože jak známo, specializované jednotky pracují ve své specializaci mnohem lépe, než ty, které jsou určeny pro obecné použití[2][4]. Proto máme například pevně daný průběh rasterizace. Je totiž velice efektivní a jeho programovatelnost by mu jen uškodila. 2.2.2. Metody efektivní komunikace Řešením stále se zvětšující propastí mezi rychlostí procesoru a rychlostí dostupnosti dat mimo čip může být například uložení některých dat přímo na čipu. Je to jedna z verzí ke které se lze uchýlit. Mimo čip se pak ukládají pouze globální data[2][5]. Dalším pomocníkem je bezesporu cache. Jde o menší paměť, ke které máme ale mnohem rychlejší přístup. Nejpoužívanější data si tedy můžeme uložit sem a získat drahý čas. Posledním možným řešením je komprese. Data, která chceme ukládat mimo čip nejprve zkomprimujeme a potom teprve pošleme k uložení. Tato varianta ovšem potřebuje i hardwarovou podporu v podobě komprimačních/dekomprimačních jednotek[2]. 2.3. Rozdíl v návrhu CPU a GPU Zde je dobré se pozastavit nad rozdílem v navrhování CPU a GPU. CPU je navrhováno tak, aby uspokojilo velmi obecné a různorodé požadavky aplikované na rozmanitá data. Je zde kladen mnohem větší důraz na řídící část, zatímco podpora paralelismu je v ústraní kvůli převládající sériové formě dat. Ve výsledku pak vypadá velmi odlišně rozložení sil jednotlivých typů jednotek. V naprosté převaze jsou totiž u CPU řídící. Naopak výpočetní síla zůstává ve značné menšině. Částečný podíl na tom má i větší potřeba ukládání dat mimo čip, která vede k další důležité odlišnosti těchto dvou procesorů. CPU se kvůli časté transformaci dat více zaměřuje na zmenšení latence. Z toho důvodu se zde zavádí i několik úrovní cache pamětí. Ty jsou ale pro některá data velmi neefektivní. Jde například právě o grafická data, která jsou přistupována pouze jednou[2]. U GPU je tedy pozornost zaměřena spíše na šířku pásma. 5

Poslední zásadní odlišností je úzká specializace grafického procesoru, která mu dovoluje použít pevně dané neprogramovatelné části pipeline. Jednoznačným určením použití dané části tak velmi zásadně zvyšuje její rychlost oproti programovatelné části pracující na stejném úkolu. 3. Princip programování grafických procesorů Před tím, než začnete psát svůj první shader je nutné pochopit, jak vlastně grafický procesor funguje. Jeho architektura je totiž diametrálně odlišná od architektury CPU. Zatím co na CPU nedosahuje plocha určená pro ALU jednotky ani deseti procent plochy čipu (6.2 na Itaniu 2) a počet ALU jednotek se též drží při zemi (12 pro pevnou a 2 pro plovoucí desetinou čárku na Itaniu 2), pracuje GPU se stovky ALU jednotek[3]. Důvodem, proč si to grafické čipy mohou dovolit je odlišné řešení režie mezi jednotkami. Ze známých modelů se toto řešení nejvíce podobá Stream procesoru, ale i od něj je zde nejedna odchylka. Konkrétní detaily o architektuře jsou přísně tajeným know-how jednotlivých firem. V tomto článku budeme stavět na vysoké podobnosti se zmíněným Stream procesorem. Ten už umí využít 26 procent své plochy pro ALU jednotky. 3.1. Proudový programovací model Hlavním důvodem proč CPU neumí paralelně zpracovávat data jako to umí GPU je jeho sériový programovací model. GPU má naproti tomu proudový programovací model který tuto výhodu umožňuje. 3.1.1. Proud V tomto modelu jsou všechna data reprezentována ve formě proudu. 6

Proud je seřazená sada dat stejného typu. Může jít jak o jednoduché datové typy, jakými jsou celá či desetinná čísla, nebo se může jednat o komplexní datové typy mezi které lze řadit body, trojúhelníky či transformační matice[3]. Proudy mohou mít libovolnou délku. čím jsou ale delší, tím efektivněji se na nich operace provedou. Povolené operace proveditelné na proudech mohou být kopírování, derivování podmnožin proudu, indexování či provádění nejrůznějších výpočtů pomocí výpočetních jader[2]. 3.1.2. Výpočetní jádro Výpočetní jádro je funkce, která má na vstupu i výstupu jeden či více proudů. Nejtypičtější použití jádra je vyhodnocení funkce pro každý elementu proudu, tedy mapovací operace. Například transformační jádro převede každý element proudu do jiného souřadného systému. Dalším typem použití je expanze, kdy je pomocí jednoho vstupního proudového elementu produkováno několik výstupních proudových elementů. Opakem toho je pak použití pro účely redukce, kde je několik vstupních elementů kombinováno do jediného výstupního. Posledním typem je pak filtrace, která má za výstup podmnožinu vstupu určenou na základě zadaných podmínek[2]. Výstupní parametr jádra je vždy pouze funkcí parametru vstupního, kdy není možná závislost na dalších elementech proudů. Toto omezení nám přináší možnost řadit sériově výpočty využívající paralelní zpracovávání dat. V proudovém programovacím modelu tedy řetězíme několik výpočetních jader pomocí jejich vstupních a výstupních parametrů. Ukázkovým příkladem je samotná grafická pipeline, která zahrnuje několik částí (například vertex program, triangulační část, výřezovou část, rasterizaci). Tyto části tvoří dohromady jeden řetězec, kde výstupní data jednoho jádra vstupují do dalšího jako jeho vstupní data[3]. V důsledku to pak znamená, že data vypočtená jedním jádrem mohou být ihned použita pro další výpočet a nemusí se čekat na dopočítání celého proudu. 7

3.1.3. Efektivita výpočtů Obrázek 3.1.: Princip práce s proudy Proudový programovací model značně zvyšuje efektivitu výpočtu na grafickém hardwaru. Největší podíl na tom má vysoká možnost paralelních výpočtů, která je zajištěna tím, že jednotlivá jádra pracují na celém proudu nezávislých dat a je tedy možné jednotlivé elementy vypočítávat na datově paralelním hardwaru[2]. Čím větší proudy, tím více paralelních výpočtů můžeme provádět a ušetříme tím více času. Kromě datového paralelismu se zde využívá i instrukčního paralelismu, kdy se jedna aplikace skládá z několika jader, která mohou být pipelinovaná. Díky rozdělení problému na jednotlivá jádra lze použít specializovaný hardware pro vykonávání právě jednoho konkrétního úkolu. Můžeme tedy část řetězce pevně zadat a tím zvýšit efektivitu jeho výpočtu a část ponechat programovatelnou[2]. U GPU se tedy setkáváme s programovatelnými vertex a fragment shadery, zatímco například rasterizaci a triangulaci máme pevně danou velmi úzce specializovaným a tedy i velmi efektivním hardwarem[4]. Posledním vylepšením výpočetních schopností GPU je omezená možnost použití kontroly datového toku. Díky tomu můžeme věnovat výpočetní části mnohem větší plochu čipu, než si můžeme dovolit u CPU, kde je kontrola toku vysoce podporována a je tedy nutné vyhradit adekvátní plochu čipu pro řídící část hardwaru. 8

3.1.4. Efektivita komunikace Zefektivnění komunikace na tomto programovacím modelu je dáno především jeho vysokou pipelinovatelností, kdy se vypočítaná data z jedné části nemusí ukládat mimo čip a dokonce ani na pomocné paměti čipu, ale rovnou se postupují dalšímu výpočetnímu jádru. Tím se velmi omezí časově náročný přenos dat. Díky tomu, že už při obdržení prvních výsledků předchozího jádra může následující jádro začít s výpočtem, se velmi efektivně využije čas vzniklý čekáním na výsledky užitečnou prací - tedy dalším výpočtem. K rychlejší komunikaci přispívá i samotná struktura proudu. Ten díky velkému počtu elementů požadovaných v jeden okamžik minimalizuje dopad časové ztráty, kterou získáme inicializací každého požadavku na data uložená mimo čip. Z toto tedy vyplývá, že při návrhu GPU se zaměřujeme na zvyšování šířky pásma na úkor latence[2]. 3.2. Proudový procesor Obrázek 3.2.: Rozložení jednotek proudového procesoru, obrázek z [3] 3.2.1. Režie proudového procesoru Abychom mohli využít co největší plochu pro výpočetní jednotky, je třeba velmi efektivně řešit režii dat na čipu. Dobře se osvědčila následující hierarchie[3]. 9

Data uložená mimo čip vstupují do procesu ve formě dříve popsaných proudů. Ty jsou na čipu zachyceny do Stream Register File (SRF)[6]. Jde o úložiště celých proudů dat, ze kterého se jednotlivé elementy posílají do Local Register Files(LRF) na základě vytížení příslušných ALU jednotek, tak aby došlo k co nejefektivnějšímu rozložení práce[6]. Přesun mezi těmito vrstvami, tedy mezi SRF a LRF je optimalizovaný na vysokou rychlost. Local Register Files si lze představit jako balíček vstupních parametrů pro konkrétní ALU jednotky. Každá ALU jednotka má tedy svůj LRF, ze kterého bere operandy pro svou práci. 3.2.2. Rozdíl mezi vektorovým a proudovým procesorem V předchozím popisu jste mohli narazit na nejednu podobnost proudového procesoru s procesorem vektorovým. Je proto více než vhodné na tomto místě popsat nejdůležitější rozdíly. Prvním velmi zásadním rozdílem je velikost proudu a vektoru. Zatímco u vektoru je velikost přesně známá, proud může mít libovolný rozsah[6]. Dalším rozdílem je více vrstev u proudového procesoru. Ten dělí Vector register file(vrf) Vektorového procesoru na dvě hierarchické vrstvy. Jde o již zmíněné Local Register File a Stream Register File, kde LRF obsahuje pouze omezenou část dat, se kterou následně operují jednotlivé ALU jednotky, zatímco SRF pojme více a stará se o rozdělování dat[6]. Samotné zpracování proudu a vektoru je také odlišné. Každý prvek proudu je zpracován celý ve chvíli, kdy přijde na řadu. Jinak řečeno, dokud se na něm neprovedou všechny operace, nepřejde procesor na další prvek. Tím se odstraní okamžité proměnné a tedy i nároky na jejich režii, což v důsledku umožňuje použít více ALU jednotek než právě u vektor procesoru[6]. 10

4. Základní grafická pipeline 4.1. Historický vývoj grafické pipeline Pipeline grafických procesorů prošla dosud 4 generacemi vývoje. Ze začátku šlo o výhradně fixed-function pipline, tedy o takovou, která má přesně a pevně dáno, jak jí data proplouvají a co se s nimi děje. Teprve časem se začaly objevovat první možnosti, jak tento průchod přizpůsobit. S vývojem se ovšem neměnila pouze programovatelnost, ale také i typ použité sběrnice. 4.1.1. 1.generace Typickým reprezentantem je karta 3dfx Voodoo z roku 1996, která byla vůbec první 3d hrací kartou. Karty této generace byly schopné provádět rasterizaci, aplikovaly až 2 textury implementovaly DirectX 6 a podporovaly z-buffering. Jejich velkým omezením ovšem bylo, že neuměly provádět transformaci vertexů. Ta stále zůstávala v režii CPU. Druhým významným omezením první generace byl malý rozsah použitelných matematických operací, jejichž pomocí se daly kombinovat textury a vypočítávat barvy. Propojení CPU a GPU probíhalo zásadně pomocí sběrnice PCI[4][7]. Obrázek 4.1.: Pipeline 1.generace podle [7] 11

4.1.2. 2.generace V druhé generaci už bodují dnešní největší výrobci nvidia a ATI. nvidia v roce 1999 předvádí svůj model GeForce 256 DDR, zatím co ATI přichází v roce 2001 s modelem Radeon 7000 AGP. Do stejné generace patří i S3 Savage3D. Na GPU se v této době přesouvá výpočet osvětlení a transformací, které můžete najít pod označením T&L(transformation and lighting), což je nejvýznamnější změna. Díky paralelnímu výpočtu na GPU jde o velké zefektivnění a tedy i zrychlení práce. Transformaci vrcholů zde podporuje jak OpenGL tak DirectX 7. Možnosti matematických operací oproti předchozí generaci značně narostly. Jsou zde zahrnuty například texturovací krychle. I přes značné rozšíření jsou ale tyto operace příčinou zásadního omezení práce. Druhá generace má vyšší možnosti konfigurovatelnosti, ale o programovatelnosti GPU zatím nemůže být řeč. Od první generace se změnila i sběrnice, kterou je nyní rychlejší AGP[4][7]. 4.1.3. 3.generace Obrázek 4.2.: Pipeline 2. generace podle [7] Zde konečně dostává slovo první programovatelný shader. Zatím lze programovat pouze velmi omezeně, ale i to znamená velký průlom, který takto vybavené GPU posouvá do nové generace. Na tomto místě by bylo dobré vysvětlit pojem shader. Jde o malý program, který je určen ke zpracování přímo na grafické kartě a stává se tak součástí pipeline. Vstupy a výstupy takového programu jsou tedy předem přesně dané, podle toho co předchází/následuje v posloupnosti zpracování[4]. 12

Ale zpět k charakteristice 3.generace GPU. Kromě programovatelného vertex shaderu (shader zpracovávající jednotlivé vrcholy scény) jsou zde rozšířeny možnosti konfigurace operací prováděných na jednotlivých pixelech. Kromě podpory OpenGL zde lze nalézt i podporu DirectX 8 a objevuje se zde možnost provádět volume texturing a multi-sampling (antialiasing). Mezi představitele této generace se řadí GeForce 3, Radeon 8500 ale i Microsoft Xbox. Sběrnice se od předchozí řady nemění a zůstává tak stále AGP[4][7]. Obrázek 4.3.: Pipeline 3. generace podle [7] 4.1.4. 4.generace Zatím se o ní stále mluví jako o generaci poslední, ačkoli už není nejmladší. Její zástupci se objevují poprvé v roce 2002 a jde například o Radeon 9700 nebo GeForceFX 5600. Je zde již samozřejmostí programovatelný vertex shader ale i fragment shader(někdy též nazývaný pixel shader), který je prováděn na každém jednotlivém pixelu scény zvlášt. Diky tomu získávají GPUs velkou variabilitu využití a začíná se hovořit o GPGPU čili GPU pro univerzální použití(tedy ne pouze v oblasti počítačové grafiky). Velký prostor pro širší využití poskytuje i nově zavedená podpora control flow, tedy podpora smyček a podmínek uvnitř shaderů. Ze začátku se zde používá sběrnice AGP, která je ale v současnosti nahrazena lepší PCIe[7]. 13

Obrázek 4.4: Pipeline 4.generace podle [4] V současnosti trhu dominují GPU firem nvidia a ATI. Jejich poslední modely byly představeny koncem roku 2006. To nejlepší co nám můžou nabídnout je GeForce 8800 GTX od nvidia a X1950 XTX od ATI. V jejich útrobách se nachází stovky milionů tranzistorů což je ve srovnání s prvním zmiňovaným VooDoo 1, který měl zhruba 1 milion tranzistorů dost rychlý rozmach na rozmezí 10 let. Tabulka parametrů zmiňovaných GPU Type Core Memory Memory speed Size Type Memory Memory DX Vertex/ Speed bandwidth Fragment shader Voodoo 1 50 Mhz 2/4 Mb EDO 64 Bits 50 Mhz 0,40 Gb/s 5.0 -/- Radeon 7000 AGP 183 Mhz 32 Mb DDR 64 Bits 183 Mhz 2,9 Gb/s 7.0 -/- Geforce 256 DDR 120 Mhz 32/64 Mb DDR 128 Bits 150 Mhz 4,8 Gb/s 7.0 -/- Radeon 8500 275 Mhz 64/128 Mb DDR 128 Bits 275 Mhz 8,8 Gb/s 8.100 1.1/1.4 Geforce 3 200 Mhz 32/64 Mb DDR 128 Bits 230 Mhz 7,36 Gb/s 8.0 1.1/1.1 Radeon 9700 275 Mhz 128 Mb DDR 256 Bits 270 Mhz 17,3 Gb/s 9.0 2.0/2.0 Geforce Fx 5600 325 Mhz 128/256 DDR 128 Bits 325 Mhz 10,4 Gb/s 9.0 2.0+/2.0+ X1950 XTX 648 Mhz 512 MB DDR4 256 Bits 999 Mhz 63,9 Gb/s 10.0 3.0/3.0 Geforce 8800 GTX 575 Mhz 768 MB DDR3 384 Bits 900 Mhz 86,4 Gb/s 10.0 4.0 Voodoo 1 1x1 0,50µ no 50 1 Million 1996 Radeon 7000 AGP 1x3 0,18µ yes 550 30 Milions 2001 Geforce 256 DDR 4x1 0,22µ yes 480 23 Milions 1999 Radeon 8500 4x2 0,15µ yes 2200 60 Milions 2001 Geforce 3 4x2 0,15µ yes 2000 25 Milions 2001 Radeon 9700 8x1 0,15µ programable 2200 107 Milions 2002 Geforce Fx 5600 4x1 0,13µ programable 1300 80 Milions 2003 X1950 XTX 8x48 1,4n programable 10 360 278 Milions 2006 Geforce 8800 GTX 129 uni 1,1n programable 36 864 681 Milions 2006 Tabulka 4.1.: Parametry grafických procesorů různých generací z [8][9] 14

4.2. Průchod grafickou pipeline Nyní už známe vývoj grafické pipeline a můžeme se tedy podívat na jednotlivé její části. Pipeline je sekvence výpočetních jednotek, které pracují paralelně a zároveň je přesně dané jejich pořadí. Každá jednotka obdrží z předchozí své vstupní parametry, které zpracuje a jejich výstup zašle další jednotce v pořadí. 3D aplikace zašle do GPU sekvenci vrcholů, které náleží některému geometrickému primitivu. Každý vrchol pak má specifikovanou pozici a obvykle i další atributy, jakými jsou barva, popřípadě druhá barva, texturovací souřadnice a normálový vektor. Obrázek 4.5.: Průchod grafickou pipeline podle [4] 4.2.1. Transformace vrcholů S těmito vlastnostmi vstupuje vrchol do první fáze zpracování. Tou je transformace vrcholů(vertex Transformation). V této fázi se provede několik matematických operací nad každým vrcholem. Může jít o změnu pozice vrcholu, generování texturovacích souřadnic či výpočet osvětlení, které dodá vrcholu výslednou barvu[4]. 4.2.2. Skládání a rasterizace Transformované vrcholy podstupují další zpracování v jednotce skládání a rasterizace(primitive Assembly and Rasterization). Kde se nejdřív jednotlivé vrcholy složí do formy geometrických primitiv za pomoci doplňujících informací o těchto geometriích. V dalším kroku se jednotlivé trojúhelníky, linie a body podrobí ořezání(clipping) a odstranění neviditelných stran(culling). 15

Při ořezání se z dalšího zpracování vyřadí ty části, které by se ve výsledku zobrazily mimo plochu monitoru, tedy ty, které by svým výpočtem zabíraly jen cenný čas a ve výsledku by nebyly vidět. Při odstranění neviditelných stran se vyhneme výpočtu stran polygonů odvrácených od pozorovatele a ušetříme další čas. Tuto možnost ovšem musíme povolit, jinak se neprovede. Takto sestavené a ořezané geometrie postupují do procesu rasterizace. Zde se rozeznává, kterému pixelu náleží která geometrie. Na výstupu rasterizace je pak sada pixelů uložených ve frame bufferu. Ty jsou pro další práci reprezentovány fragmenty. Fragment lze charakterizovat jako potenciální stav pixelu, podle kterého se tento pixel může aktualizovat. Každý fragment má své atributy. Můžou jimi být pozice, hodnota hloubky, barva, sekundární barva a jedna či více texturovacích souřadnic. Všechny tyto parametry jsou odvozeny od vrcholů tvořících danou geometrii[4]. 4.2.3. Interpolace, texturování a barvení Po vytvoření jednotlivých fragmentů proběhne proces interpolace, texturování a barvení, kde jednotlivé fragmenty získají své finální vlastnosti. Interpolací barev vrcholů dané geometrie získají jednotlivé fragmenty barvu, další matematické operace zajistí správné mapování textury a případně lze změnit i informaci o hloubce. V tomto stádiu si může grafická pipeline dokonce vyžádat nové nahrání fragmentu z frame bufferu. Dosáhne toho tak, že odpovídající fragment nezpracuje a zahodí[4]. 4.2.4 Rasterové operace Jednotlivé fragmenty se správnými vlastnostmi postupují poslední fázi zpracování, která předchází obnovení frame bufferu. Jde o rasterové operace, které jsou standardní součástí OpenGL a Direct3D. Odstraňují se zde například skryté plochy zjištěné pomocí informace o hloubce fragmentu. Během průchodu touto částí se jednotlivé fragmenty podrobí řadě testů(scissor, alpha, stencil a depth). Pokud byť jen jedním z nich neprojdou, jsou odstraněny. Narozdíl od předchozí fáze se zde odstraněné fragmenty znovu nenahrávají. 16

Poté co zůstanou jen fragmenty odpovídající všem testům, proběhne míšení barev fragmentů na dané pozici. Výslednou barvou se pak nahradí barva odpovídajícího pixelu ve frame bufferu[4]. 5. Tutoriál programování vertex a fragment procesorů v jazyce Cg 5.1. Úvod do programování grafických procesorů 5.1.1 Základní pojmy Cg neboli "C for graphic" je jazyk vycházející syntaxí z jazyka C. Jeho pomocí lze programovat přímo obě programovatelné části programovatelných GPU. První část je Vertex procesor a druhá Fragment procesor (někdy také nazývaná pixel procesor). V tomto tutoriálu naleznete příklady psané pomocí OpenGL. Pokud chcete používat DirectX nemusíte se bát, rozdíly jsou pouze syntaktické a ekvivalenty použitých funkcí není složité dohledat v dokumentaci k Cg. Ke každé lekci naleznete v příloze A jak vertex (PrČÍSLO_LEKCEv.cg) tak fragment program (PrČÍSLO_LEKCEf.cg). Vertex procesor nabízí možnost manipulace s vrcholy. Program, který se zde provádí, bude vykonán tolikrát, kolik má daná geometrie vrcholů. Tedy pro každý vrchol zvlášť[4][7]. Vždy když bude program prováděn, bude mít k dispozici vlastnosti toho konkrétního vrcholu, který je právě na řadě. Jde například o pozici a barvu. Tyto informace můžeme, ale nemusíme využívat. Postupným zpracováváním jednotlivých vrcholů odpadá nejeden for cyklus, který bychom použily v případě programování CPU. 17

Vstupní vlastnosti zde samozřejmě neslouží jen jako informace, ale lze s nimi libovolně manipulovat a poté označit za výstupní informaci. Lze je ovšem také zcela ignorovat a pevně stanovit, že výstupní vrchol bude mít za každé situace například zelenou barvu. Váš vertex program bude ve výsledku článkem řetězce, ke kterému máte k dispozici známé úchyty vlevo(tedy na vstupu) i vpravo(tedy na výstupu) a je jen a jen na Vás, kolik z nich použijete a k čemu je přichytíte. Fragment procesor zpracovává každý jeden obrazový prvek zvlášť a hodí se tedy pro veškeré per pixel operace. Nejednu operaci budete schopni napsat pro vertex i fragment procesor. Použijete-li při tom jazyk Cg budou výsledné programy vypadat téměř shodně[4]. Důležité hledisko při rozhodování, ve které fázi operaci provést potom bude požadovaná rychlost(té docílíte zasazením do vertex procesoru) versus požadovaná přesnost výsledku(té dosáhnete dosazením do fragment procesoru). Je například zcela nelogické počítat transformace ve fragment procesoru, protože tím žádného zlepšení skutečně nedocílíte. Naopak je to, pokud budete programovat osvětlovací model pro geometrii složenou z větších plošek. Zde je rozdíl mezi interpolací, kterou použijete ve vertex programu a přesným výpočtem každého pixelu skutečně znatelný. Ovšem znatelné je i zpoždění výpočtu při použití fragment programu. Univerzální pravidlo při rozhodování mezi vertex a fragment programem říká, že co je možné udělat ve vertex programu, to se tam má skutečně dostat. Což ovšem nemusí být vždy nejlepší a je nutné obě možnosti důkladně zvážit[7]. Stejné pravidlo se dá aplikovat i na rozhodování mezi tím, co napsat pro CPU a co již ve vertex programu. V tomto případě je skutečně hodně důležité veškeré konstantní parametry předávat v jejich finální podobě. Na CPU se totiž jejich výpočet provede pouze jednou, zatímco na GPU tolikrát, kolik máme ve scéně vrcholů/pixelů, což bývá skutečně velmi znatelný rozdíl. 5.1.2 Programovací jazyk Jazyk Cg vznikl ve spolupráci firem nvidia a Microsoft, ale není závislý na operačním systému ani na výrobci GPU. Lze ho použít ve spolupráci jak s DirectX tak s OpenGL. Nejde ovšem o jazyk jehož pomocí lze naprogramovat celou aplikaci. 18

Jde o jazyk, kterým lze programovat pouze vertex a fragment procesory. Vlastní aplikaci lze napsat v jazyce C či C++. Poté je třeba přidat do hotového programu knihovnu Cg a pomocí jejích funkcí přilepit své programy v tomto jazyce. Co by mělo být obsahem hlavního programu na CPU a co by se mělo přenechat GPU je otázkou benchmarků a praxe. Při dobrém zvládnutí takovéhoto přerozdělení práce je možné celý výpočet značně zefektivnit. Nástroje nutné pro používání tohoto jazyka je možné získat na adrese http://developer.nvidia.com/object/cg_toolkit.html. Vzhledem k tomu, že každý používáme různá programovací prostředí, dovolím si vynechat popis, jak je naučit Cg používat. Přehledný popis je poskytnut v dokumentaci. Je nutné zmínit, že Cg není jediný jazyk pro programovatelné GPU. Dalšími možnostmi jsou například BrookGPU, Sh, HLSL, GLSL nebo SlabOps a každý z nich má svá pro a proti[7]. O BrookGPU si můžete přečíst v kapitole 6.1. této práce. Spolu s jazykem Sh patří do skupiny pro negrafické využití. Zbylé jmenované jazyky spolu s jazykem Cg jsou určeny speciálně pro využití v grafických aplikacích. Kromě jmenovaných jazyků, jste se v souvislosti se shadery(čili vertex a fragment programy) mohli dovědět i o Rendermanovi. Ten ovšem není plnohodnotným jazykem, ale jakýmsi překladačem. Svým uživatelům poskytuje jazyk, kterým se s ním lze dorozumívat. Ten ovšem sám ve výsledku převede na jazyk jiný. Což vede ke zhoršeným možnostem optimalizace, kdy nevíme co přesně jsme daným kódem procesoru poručily[4]. Cg má oproti rendermanovi své funkce mapované přímo na příkazy v assembleru, což je při optimalizaci znatelně výhodnější[4]. 5.1.3. Reálné využití Nyní přichází ta pravá chvíle začít se zabývat reálnou aplikací shaderů. Tedy osvětlením situace, k čemu a jak jazyk Cg použít. Jeho možnosti jsou skutečně široké. My si tu pokusíme popsat alespoň několik praktických využití. Asi nejjednodušší případ použití je přesunutí výpočtu transformací z CPU a GPU. Tento krok není jen užitečný, ale dokonce nutný. Díky použití vlastního článku pipeline, tedy shaderu(vertex a fragment program) ztrácíme možnost využívat funkce OpenGL pro transformace. Ty totiž ve svém výsledku s pipeline pracují, ale zrovna s částí, kterou svým 19

shaderem nahrazujeme. Je tedy nutné oprášit znalosti jednotlivých prostorů a náležitých transformací a aplikovat je. K tomuto tématu více v kapitole 5.4. Ze stejného důvodu z jakého bylo nutné přeprogramovat funkce jednotlivých transformací, je nutné znovu zavést i osvětlovací model. Možností na výběr v současnosti existuje celá řada. V kapitole 5.5 si ukážeme alespoň tu nejzákladnější. Tím jsme uzavřeli kapitolu, ve které jsme pouze doháněli ztráty způsobené luxusem vlastní části pipeline. Nyní už se můžeme zaměřit na nová témata. Jedním z nich je animace. V jejím rámci se lze zabývat například těmito obory: Pulsující objekty - zde jde o rozpohybování jednotlivých vrcholů ve směru jejich normál za použití funkcí sinu či cosinu a jednotek času. Jde o velmi snadný a rychlý výpočet[4]. Particle systémy - pomocí textur a funkcí popisujících chování jednotlivých částí lze generovat novou výchozí texturu. Zde se může objevit problém readbacku, čili znovunačítání textury, které je poměrně časově náročné[4]. Key-Frame interpolace - sérii obrázků lze "rozpohybovat" pomocí interpolačních funkcí. Základní jsou již součástí jazyka[4]. Vertex skinning - jde o soubor transformačních matic, pomocí kterých lze pohybovat různými soubory vertexů. Ve výsledku se pak dá hovořit o kostech. Každý soubor tvoří jednu kost daného modelu. Nemusíme tedy vytvářet různé pózy objektu a posléze je interpolovat. stačí vytvořit jednu základní pozici a rozdělit ji na části. V každé části pak lze jednotlivým vrcholům přiřadit různou váhu, která určí jak silně na něj bude transformace této skupiny působit. Pomocí této techniky vytvoříme kostru celého modelu a pak s ním už velmi snadno manipulujeme[4]. Dalším možným oborem působnosti shaderů napsaných pomocí jazyka Cg jsou různé techniky mapování. Jde například o tyto: Enviroment Mapping, Reflective Enviroment Mapping, Refractive Enviroment Mapping či Fresnelův efekt a chromatické rozptýlení[4]. A to není zdaleka vše. Ulevit CPU můžete i v oborech jakými jsou: bump mapping, mlha, nefotorealistický rendering, projektivní texturování, mapování stínů či skládání výsledné kompozice obrazu[4]. 20

5.2. Lekce č.1: Základy jazyka I Nejdřív by se slušelo vysvětlit, co lze od programu v Cg očekávat a co nelze. Rozhodně nejde o program, který by byl schopný vykreslovat sám o sobě. Je třeba nejdřív vytvořit základní scénu pomocí OpenGL nebo DirectX, kterou chcete zobrazit nebo dál zpracovat. Nepřipojíte-li žádný vertex ani fragment program, podstoupí Vaše scéna standardní zobrazovací proces. Pomocí vertex a fragment programů, které jsou součástí zobrazovací pipeline GPU, lze tento proces přizpůsobit konkrétním požadavkům. Lze vytvářet různé efekty, textury ale i optimalizovat výpočet. Důležité je, uvědomit si, že jeden vertex program bude vykonán na každém vertexu Vaší scény a fragment program na každém fragmentu. Díky vysoké schopnosti paralelního zpracování na GPU jde o efektivnější cestu než kdybychom dané operace prováděli v cyklech. O tom co vše lze přizpůsobením cesty ovlivnit a jak, se dozvíte právě v tomto tutoriálu, ale začněme od nejjednoduššího. Náš první program vytváří pomocí OpenGL jednoduchý trojúhelník bílé barvy na černém pozadí. My mu chceme přiřadit zelenou barvu. Podívejte se nejdřív na vertex program, který zařizuje změnu barvy na zelenou a předává pozici vrcholu v nezměněné podobě. Potom si prohledněte i fragment program, který je zde jen na ukázku a nic užitečného nedělá, pouze vezme barvu ze vstupu a zkopíruje jí na výstup. Každý program má svou vstupní funkci. Jde o funkci, která bude ekvivalentní funkci main v jazyce C. Její pojmenování závisí čistě na Vás. Tato volnost pojmenování je dána tím, že při různých spuštěních shaderu můžete za main dosadit jinou funkci. Tuto možnost uplatníte například při různé volbě osvětlovacího modelu pro různá tělesa scény[4]. To která funkce je aktuální main zadáte v hlavním programu jako jeden z parametrů spuštění shaderu. V tomto tutoriálu se vstupní funkce vertex programů jmenují PrCv_ukol u fragment programů jsou to PrCf_ukol, kde C je číslo lekce a ukol je účel programu. Způsob zápisu vychází z jazyka C, takže by nemělo být těžké ho pochopit. 21

Dalším důležitým pojmem, se kterým se seznámíme je sémantika. Jde o označení vstupních a výstupních parametrů, které získáváme z předchozí fáze pipeline, nebo je předáváme fázi následující[4]. Tyto parametry jsou vlastnostmi jednotlivých vrcholů/fragmentů, které putují společně se svými nositeli celou pipeline a podstupují zde řadu změn až nakonec dostanou finální formu a stanou se vlastnostmi pixelu. Těchto parametrů existuje jen omezený počet a je nutné je mít jednotně označené, aby procesor jednoznačně určil, co po něm chceme a píší se zásadně velkými písmeny. Parametrům zastoupeným sémantikami říkáme proměnné parametry. To je odvozeno z faktu, že každý jednotlivý vrchol či fragment mají svou jedinečnou hodnotu tohoto parametru, tudíž se s jednotlivými výpočty mění[4]. Během průběhu našeho programu můžeme samozřejmě hodnotu takového parametru změnit a připojit ho k libovolnému výstupu stejného datového typu. Proměnné parametry jsou vyjádřeny svým datovým typem názvem a sémantikou. Příkladem může být: float2 position:position Tady by Vás mohl zarazit datový typ float2. Jde o vektor o dvou prvcích typu float. V jazyce Cg existují ještě jeho další varianty float3 a float4. Zjednodušeně si tento datový typ lze představit jako pole. Má ovšem odlišnou implementaci a možnosti zacházení. Podívejte se ve vertex shaderu na řádek s kódem: OUT.position = float4(position, 0, 1);. Zde definujeme polohu která bude vystupovat z našeho programu. Zápis značí, že první dva prvky odpovídají dvouprvkovému vektoru position a zbylé jsou dané hodnotou. Takto by jste standardní pole rozhodně nedefinovali. Podobných možností poskytují vektory více, ale o nich si povíme v další lekci. Zde je důležité si pouze uvědomit, že jde o uspořádanou posloupnost přesného počtu prvků daného typu[10]. Kromě určování vektorů pozice se velmi dobře uplatní i při určování barvy. 22

Když už víme co program přijímá, řekněme si, co takový program vrací. V našem případě je to struktura Pr01v_Output, která obsahuje pozici a barvu vrcholu, ty pomocí sémantik POSITION a COLOR předává k dalšímu zpracování pipeline. Struktura v Cg funguje obdobně jako struktury v C. Zápis OUT.color = float4(0, 1, 0, 1); tedy znamená přiřazení hodnoty do členské proměnné struktury OUT. Hodnoty přiřazovaného vektoru náleží složkám R, G, B a A Pro úplnost je nutné říct si něco málo o profilech. Profily jsou omezení, které musíme zvolit při volání shaderu. Definují podskupinu schopností Cg, kterou můžeme využívat. Podle naší volby pak buď můžeme používat pokročilejší funkce Cg jako jsou například cykly, nebo můžeme výsledný program spustit i na starších grafických kartách, které takové možnosti nemají[4][10]. Existuje jedna sada profilů pro vertex programy a druhá sada pro fragment programy. Důvodem jejich existence je nekompatibilnost jednotlivých GPU. Starší neumí vše co umí novější. Je tedy nutné zvolit profil takový, aby v něm Vaše GPU umělo komunikovat. Je samozřejmě možné neustále používat nejstarší model, ale přicházíte tím o hodně možností. Další kritérium výběru profilu je to, zda používáte DirectX či OpenGL. V našem příkladu používáme základní profily pro OpenGL a karty nvidia tedy: vp20 a fp20. Profil vyberete v hlavním programu a použijete ho při kompilaci. V tomto příkladu: CGprofile VertexProfile = CG_PROFILE_VP20; Přehled možných profilů najdete zde: http://developer.nvidia.com/object/cg-toolkit- 15#profiles Poslední věc, kterou se v první lekci naučíte je, jak vytvořené Cg programy připojit k Vašemu hlavnímu programu[4][10]. Nejdříve je třeba připojit knihovny Cg. To uděláme pomocí #include < Cg/cg.h>a #include < Cg/cgGL.h >. Druhým krokem by mělo být vytvoření kontextu, což je úložiště jednotlivých vertex a fragment programů. 23

CGcontext context = cgcreatecontext(); Programy přidáme do kontextu a zároveň kompilujeme(nejsou-li předkompilovány): CGprogram program = cgcreateprogram(context, CG_SOURCE, programstring, profile, "main", args);, kde context je uložiště ve kterém chceme program mít, CG_SOURCE nám říká, že program není předkompilovaný a bude zadán polem programstring. Alternativně lze zadat CG_OBJECT, který vyžaduje předkompilovaný program, obecně je ale lepší kompilovat za běhu. Profile nám říká pod jakým profilem chceme program kompilovat. parametr "main" vyžaduje název vstupní funkce programu, napíšete-li null, předpokládá se název "main". Poslední je args, který určuje možnosti kompilace. Prozatím ho vynecháme. Program je před použitím třeba nahrát: cgglloadprogram(program); Povolit profil, který využívá: cgglenableprofile(profile); A připojit program: cgglbindprogram(program); Po dokončení vykreslovací části kódu ukončíme program zakázáním profilu: cggldisableprofile(profile); A úplně nakonec program zničíme: cgdestroyprogram(program); A zničíme i kontext: cgdestroycontext(context); Obrazek 5.1.: Výstup prvního programu v Cg 24

5.3. Lekce č.2: Základy jazyka II Jednoduché mapování textury Vertex program V minulém příkladu jsme měli vertex program, který nastavoval barvu všech vrcholů stejně, to se ale moc nehodí. Nový vertex program generuje různé barvy pro různé vrcholy, tím, že předá ke zpracování barvu předanou od OpenGL. V podstatě tedy slouží pouze jako propojovací prvek, který nic nedělá. Objevila se nám zde nová sémantika a to TEXCOORD0. Jde o parametr předávající texturovací souřadnici vrcholu a jak naznačuje nula na konci, lze používat více textur a tedy i více souřadnic pro každý vrchol, ale i pro každý fragment. Konkrétní počet se liší s typem GPU[10]. Fragment program Program přijímá texturovací souřadnice vrcholu a sampler textury. Vrací pak barvu, kterou získá ze sampleru na dané souřadnici. Vznikají dvě otázky. Co je to sampler a co znamená specifikátor uniform. Uniform označuje uniformní proměnnou. To je taková, která není přijatá přímo z pipeline, ale je předaná z externího prostředí (tedy pomocí OpenGL resp. DirectX). V našem případě si necháváme předat parametr typu sampler2d. Pozor! Uniform v některých jiných jazycích značí parametr, který nelze v programu měnit. U Cg tomu tak není, tzn. s parametrem můžete libovolně pracovat[4]. Sampler znamená v Cg externí objekt, ze kterého může brát vzorky. sampler2d tedy znamená, že chci brát vzorek z dvourozměrného objektu. To provedu funkcí tex2d(decal, texcoord);, která vrací barvu odebranou ze sampleru decal v místě, které po převedení měřítka odpovídá pozici texcoord. Poté už stačí výslednou barvu předat výstupní struktuře a máme otexturovaný objekt[4][10]. 25

sampler1d jednorozměrná textura jednorozměrné funkce sampler2d sampler3d dvojrozměrná textura třírozměrná textura samplercube krychlová textura samplerrect Hlavní program 2D textury s rozměry neodpovídajícími mocninám dvou a nemipmapovatelné 2D textury Tabulka 5.1.: Přehled samplerů pro jazyk Cg z [4] potisk, normálové mapy, mapy stínů apod. objemová data, 3D útlumové funkce mapy prostředí video, fotografie, pomocné buffery Do hlavního programu zavítáme na delší dobu naposledy. Poslední věc, kterou jsme si zde neukázali je totiž to, jak lze předat uniformní parametry. Nejprve musíme parametr odchytit. To uděláme v našem případě pomocí CGparameter image = cggetnamedparameter(fragmentprogram, "decal"); tím vytvoříme ukazatel image a přiřadíme mu parametr se jménem decal z programu Fragment program. Nyní můžeme image měnit. To provedeme pomocí cgglsettextureparameter(image, texture);, kde texture je požadovaná textura. Teď už by pro Vás neměl být problém podobný program vytvořit samostatně. Obrázek 5.2.: Výstup druhého programu v Cg 26

Dvakrát mapovaná textura CALL-BY-RESULT Dosud jsme v našich programech vraceli předem vytvořenou strukturu. To je ovšem jen jedna z možností. Druhou si teď popíšeme a Vy si jí můžete prohlédnout v následujícím příkladu, který mapuje texturu dvakrát jinak posunutou a výsledek je lineární interpolací obou. Nenajdete zde žádnou návratovou hodnotu, ale najdete u některých parametrů slůvko out. tyto parametry reprezentují výstupní hodnotu. Lze použít nejen out, ale i in, což je implicitní hodnota a značí vstupní parametr, nebo inout, který označuje parametr vstupní a výstupní zároveň. Je tedy zcela na Vás jakou formu si zvolíte. Zda si oblíbíte struktury nebo právě popsaný způsob call-by-result. Příklad není v žádném směru nový a proto se skvěle hodí pro procvičení znalostí. Zkuste si ho naprogramovat sami[4]. Malou nápovědou Vám může být funkce lerp(a, b, w), která lineárně interpoluje dva stejně veliké vektory a a b vahou w. Výsledkem pak je (1-w)*a + w*b. Jako druhou nápovědu uvedu způsob, jak nastavit parametr typu float2. Jde o funkci cgglsetparameter2fv(parametr, hodnota). Obrázek 5.3.: Výstup třetího příkladu v Cg 27

5.4. Lekce č.3: Transformace Vertex program může mít několik úloh, co ale udělat musí, je transformování souřadnic a my se v této lekci naučíme jak na to. Nejprve si ale povíme něco o uspořádání a významu jednotlivých transformací a prostorů. Obrázek znázorňuje schéma jejich obvyklého průběhu. Obrázek 5.4.: Transformace a transformační prostory podle [4] Object Space neboli také model space, je prostor se kterým pracujeme v rámci aplikace. Jeho pomocí modelujeme jednotlivé objekty. V rámci scény je obvykle více objektů a každý z nich má svůj vlastní Object Space, který nic nevypovídá o vztazích mezi těmito modely. Pracujeme zde s homogeními souřadnicemi x, y, z a w[4][12]. Word Space Tento prostor dodává vztahy mezi jednotlivé modely. Jde o jediný souřadný systém, vůči kterému jsou modely umisťovány. Teprve zde je možné určit uspořádání scény a zkoordinovat dosud individuální modely v jednotnou scénu[4][12]. Modeling Transform Slouží k manipulaci jednotlivých samostatných modelů tedy pro manipulaci v rámci Object Space. Pro lepší představu uveďme příklad. V místnosti je stůl, skříň a dvě židle. Chci-li zvednout židli na stůl, použiju k tomu její Object Space. To znamená že, budu hýbat pouze touto židlí, nikoli celou místností (Word Space).[4][12] 28

Eye Space Další prostor definuje odkud se na scénu díváme. Oko pozorovatele je zde umístěno v počátku souřadného systému. Scéna je umístěna tak, aby došlo k požadovanému pohledu[4][12]. View Transform Jde o transformaci, která převádí Word Space do Eye Space. Nejprve se přesune "oko" do počátku souřadného systému, pak se adekvátně posune a natočí celá scéna[4][12]. Modelview Matrix Kombinace matic reprezentujících modelovací a pohledovou transformaci. Kombinace probíhá jednoduchým vynásobením[4][12]. Clip Space Jakmile máme pozice v pohledovém prostoru, potřebujeme určit, která místa budou a která nebudou vidět ve výsledné scéně. Tím se dostaneme do Clip Space. V tomto prostoru souřadnice opouští vertex program. To znamená, že výstupní sémantika POSITION značí polohu právě v tomto prostoru[4][12]. Projection Transform touto transformací docílíme pohledu Clip Space. Definuje pohledový jehlan (view frustum) reprezentující prostor viditelný definovaným okem[4][12]. Projection Matrix Matice 4x4 vyjadřující Projection Transform[12]. Perspective Divide Realizuje se vydělením souřadnic x, y a z souřadnicí w. Výsledné souřadnice se nazývají Normalized Device Coordinates. Výsledkem je, že všechna zobrazovaná data leží v krychli se souřadnicemi mezi <-1, -1, -1> a <1, 1, 1> pro OpenGL a <-1, -1, 0> až <1, 1, 1> pro Direct3D[4]. 29

Window Coordinates Posledním krokem je převést normalizované souřadnice do skutečných souřadnic okna měřených v pixelech ve směrech x a y. Proces se nazývá Viewport Transform a je vykonáván na GPU při rasterizaci[4][12]. Depth Range Transform mění hodnotu souřadnice z jednotlivých vrcholů, tak aby ležela v rozsahu použitelném depth bufferem[4]. Vertex program Vertex program přijímá pozici, barvu a transformační matici. Barvu pouze předá dál, ta nás v této lekci nezajímá. O to víc nás bude zajímat změna pozice. Každou vstupní pozici vynásobíme transformační maticí, kterou si vytvoříme v hlavním programu. Je to velice jednoduchý program, o to nás však bude čekat víc práce při vytváření transformační matice v hlavním programu. Fragment program Použijeme jednoduchý program z prvního příkladu. Tedy necháme data pouze proplout. Hlavní program Zde potřebujeme doplnit funkce, které nám umožní snadno vytvořit transformační matici. Tu předáme jako parametr našemu vertex programu. Půjde nám o to, jak nahradit funkce, které nám jinak poskytuje OpenGL. Konkrétně funkce gltranslate, glrotate, glscale, glulookat a glortho. Zacházet budeme s maticemi 4x4, které před použitím nastavíme na jednotkovou matici pomocí funkce initmatrix(matice). Každá transformace pak znamená násobení aktuální matice maticí dané transformace. Zde nabízím přehled základních matic umožňujících posunutí, rotaci a změnu měřítka: 30

Matematický vzorec 5.1.: Transformační matice podle [12] Samotné násobení pak provedu jednoduchou funkcí multmatrix(cil, levamatice, pravamatice). Teď si ukážeme, jak bez pomoci vzorové matice dojdeme k vyjádření pohledové transformace lookat(transformacnimatice, okox, okoy, okoz, cilx, cily, cilz, stropx, stropy, stropz). okox, okoy a okoz nám říkají, kde chceme mít počátek souřadnicové soustavy. Nabízí se tedy použití transformace posunutí o opačné hodnoty. Nesmíme zapomenout stejným způsobem změnit i zbylé parametry. Jako druhý krok jsem zvolila nasměrování pohledu na cilx, cily a cilz. Jde o to, aby zadaný bod ležel na kladné části osy Z. Jak nám naznačuje následující nákres, jde o provedení dvou rotací. První kolem osy X sklopí bod do roviny Y=0 a druhá kolem osy Y dopraví bod na osu Z což je náš cíl. 31

Obrázek 5.6.: Schéma transformací pro funkci lookat Ještě je nutné otočit se kolem osy Z tak, abychom měli bod stropx, stropy, stropz nahoře. Před samotnou rotací nesmíme zapomenout bod přepočítat podle již provedených rotací a jedné translace. Ve výsledném souhrnu máme tedy 4 matice, které navzájem násobíme a vznikne nám výsledný pohled. Při jejich použití je důležité uvědomit si, že transformace na posledním řádku bude provedena první! Poslední funkce, kterou budeme potřebovat, je projekční zobrazení. V našem případě funkci ortho(transformacnimatice, leva, prava, dole, nahore, blizko, daleko) Je v ní potřeba aplikovat změnu měřítka tak, aby zadané rozměry určovaly jednotkovou matici. Nesmíme zapomenout ani na posun celého prostoru nejsou-li protilehlé rozměry symetricky vyvážené. Pro určení souřadnic okna můžeme směle použít funkci glviewport. Tato transformace totiž leží mimo programovatelnou část pipeline[4]. 32

Obrázek 5.6.: Výstup čtvrtého programu v Cg 5.5. Lekce č.4: Základní osvětlení Základní světelný model Základní světelný model lze sestavit zkombinováním 4 složek světla, které si nyní popíšeme. surfacecolor = emissive + ambient + diffuse + specular Emisivní složka Reprezentuje světlo, které vyzařuje samotný objekt a lze jí vyjádřit barvou. Nesmíme si plést tuto složku se samotným zdrojem světla, tak opravdu nefunguje[4]. Obrázek 5.7.: Emisivní složka světla podle [4] 33

Ambientní složka Jde o část světla, která se vyskytuje všude. Celý prostor jí je rovnoměrně zaplněný a nemá tedy žádný zjevný zdroj. Její vyjádření je závislé na schopnosti materiálu toto světlo odrážet a na samotné barvě světla[4]. Obrázek 5.8.: Ambientní složka světla podle [4] Difusní složka Tato složka zastupuje směrové světlo odražené z povrchu rovnoměrně do všech stran. To znamená, že materiál odražející difusní světlo, je hrubý a má tedy velmi malé plošky natočené do všech stran. Množství takto odraženého světla závisí na úhlu dopadu světla na objekt[4]. Obrázek 5.9.: Difusní složka světla podle [4] Spekulární složka Vyjadřuje světlo odražené od povrchu v blízkém úhlu k zrdcadlově odraženému paprsku. Je to velice podstatná část všech lesklých a hladkých materiálů. Narozdíl od ostatních složek závisí i na pozici pozorovatele. Také jí ovlivní odrazivost materiálu[4]. Obrázek 5.10.: Spekulární složka světla podle [4] 34

Základní osvětlovací model Vertex program Vertex program základního modelu spočívá v dosazení výše uvedených rovnic od programu z předchozí lekce. Předpokládáme zde zadání souřadnic světla v Object Space. Z dosud neznámých funkcí se zde objevují[10]: normalize(vektor)... fce normalizující vektor obecné velikosti max(hodnotaa, hodnotab)... fce vracející větší ze dvou hodnot dot(vektora, vektorb)... fce vracející součin 2 vektorů pow(hodnotaa, hodnotab)... fce vracející hodnotaa^hodnotab Dále by se slušelo uvést problematiku swizzling a masking[4]. Jde o další možné využití datového typu vektor. Swizzling dovoluje změnu pořadí jednotlivých složek vektoru různým uspořádáním atributů x, y, z a w resp. r, g, b a a. např.: float4 vektor1 = float4(4.0, -2.0, 5.0, 3.0); float2 vektor2 = vektor1.yx; //vektor2 = (-2.0, 4.0) Masking dovoluje definovat, které složky vektoru chceme nahradit. Ostatních si nevšímá. např.: float4 vektor1 = float4(4.0, -2.0, 5.0, 3.0); float2 vektor3 = float2(1.0, 0.0); vektor1.xw = vektor3; //vektor1 = (1.0, -2.0, 5.0, 0.0) Fragment program Použijeme jednoduchý program z prvního příkladu. Tedy necháme data pouze proplout. 35

Obrázek 5.11.: Výstup pátého programu v Cg Per Fragment osvětlovací model Vertex program V tomto případě zde zpracujeme transformace a předáme normálové vektory, abychom je mohli později interpolovat. Fragment program Fragment program pro základní osvětlení bude vypadat téměř identicky jako vertex program v předchozím příkladě. Jediný rozdíl je v nutnosti normalizovat normály, které interpolací v části pipeline mezi vertex a fragment programem změnily velikost. Obrázek 5.12.: Výstup šestého programu v Cg 36

6. Negrafické programování vertex a fragment procesorů V této sekci se dozvíte o tom, jak lze grafické procesory využít pro programování zcela negrafických aplikací. Pro takové účely samozřejmě lze použít stejné jazyky, jaké používáme pro grafické programování (v kontextu této práce jde o jazyk Cg), ale to by byla pro mnohé velice krkolomná možnost. Ne každý programátor musí mít povědomí o ryze grafických pojmech, které se zde používají. Proto byl vytvořen jazyk nový, který umožní programovat GPU bez znalosti grafického pozadí. Jazyk sám obstará obsazení texturovacích pamětí a tak si nemusíte lámat hlavu s tím k čemu tato vymoženost je a jak jí efektivně využít. V této kategorii se v současné době nachází 2 jazyky. Jde o jazyk BrookGPU, který zde více rozvedu, a o jazyk Sh, který byl vyvinut na univerzitě ve Waterloo. Jazyk BrookGPU je projektem Stanford University. Je založený na proudovém programovacím modelu a lze k němu bezplatně stáhnout překladač a běhovou knihovnu. K této sadě obdržíte i ukázkové kódy, takže můžete směle začít s učením. Cílem projektu je zpřístupnění grafického procesoru jako pomocné síly pro hlavní procesor. Pokud si osvojíte techniky v tomto jazyce, můžete velmi zefektivnit Vaše výpočty díky vysoké schopnosti paralelismu na GPU. V tomto prostředku se skrývá velký potenciál například pro náročné fyzikální simulace. Projekt neustále běží a prochází tak zajímavým vývojem. Pokud si chcete práci pomocí BrookGPU vyzkoušet, je dobré si stáhnout aktuální kopii projektu a nikoli poslední vydanou verzi. Díky neustálé práci tvůrců tak můžete získat v mnohém vylepšenou verzi. Tuto možnost poskytují autoři na stránkách http://www.sf.net/projects/brook. Brook je rozšířením standardu ANSI C a je zkonstruován tak, aby zahrnul myšlenky paralelního výpočtu dat a výpočetní intenzity do známého a efektivního jazyka. Toho dosahuje pomocí hlavního výpočetního proudového modelu, který je více popsán v kapitole 3. Programy napsané pomocí tohoto jazyka lze přeložit pomocí BRCC do formátu.cpp. K překladu lze využít buď GPU nebo i CPU. Díky této možnosti můžete jednoduše porovnat efektivitu přesunu na grafický hardware. Podporován je jak DirectX 9, tak OpenGL. Můžete si tedy vybrat, co Vám je bližší. V neposlední řadě je samozřejmě i možnost pracovat pod různými operačními systémy. Podporu pak lze hledat na stránkách www.gpgpu.org, kde naleznete fórum, ve kterém Vám na Vaše případné dotazy odpoví sami tvůrci projektu. 37

6.1. Jazyk BrookGPU V této části se seznámíte s prvky jazyka. Dozvíte se, jak lze které využít a co je v jazyce zakázáno. Tento článek by Vám měl poskytnout přehled možností a využití výhod, po jehož přečtení si budete schopni říct, jaká část Vašeho kódu je pro zpracování na grafickém procesoru vhodná a jak jí napsat. Článek čerpá výhradně z [13]. Proud(stram) Stream je nový datový typ, který reprezentuje soubor dat, na kterých lze pracovat paralelně. Deklarace proudu může vypadat například takto: float s<10, 10>; jde zde o dvourozměrný proud hodnot typu float. Každý proud je tvořen proudovými elementy, které jsou zapisovány po řádcích obdobně jako pole v jazyce C. Takže proud <100> je stejný jako proud <1, 1, 100>. I když zde lze nalézt podobnost s polem v jazyce C, jsou tu určité zásadní rozdíly. K jednotlivým prvkům proudu nelze přistupovat podle indexu jinde než v rámci jádra. Proud nelze staticky inicializovat a musí být lokální proměnnou. Číst a zapisovat ho lze pouze v rámci jádra, nebo pomocí speciálních operátorů, které naplní proud pomocí ukazatele a naopak zkopírují proud do proměnné na kterou odkazuje ukazatel. streamread(s, data_s) streamwrite(s, data_s) //naplní proud s<> daty z *data_s //zapíše do *data_s údaje z s<> tyto operace provádí efektivní kopírování, optimalizace ale může být narušena překladačem. Pro GPU představují jednotlivé proudy místa v texturovací paměti. Překladač může analyzovat Váš kód a rozhodnout tak, kdy a kde je vhodné alokovat texturovací paměť. Může také úplně odstranit potřebu ukládání pomocných výpočtů proudu. Float2, float3, float4 Jde o základní typy, kterými Brook rozšiřuje jazyk C a jsou shodné s následujícím zápisem: typedef struct { float x; float y; float z; 38

float w; } float4; vytvořit tyto typy můžete pomocí konstruktoru: float4 a (1.2f, 1.3f, 3.1f, 1.0f) Jádro(Kernel) Jádra jsou speciální funkce, které operují nad jednotlivými proudy. Každé jádro je aplikováno na každý prvek proudu a to tak, že lze zároveň zpracovávat více prvků najednou. Pomocí jádra tedy dosahujeme kýženého paralelismu ve zpracování dat. Zavolání funkce jádra vyvolá for-cyklus, ve kterém budou zpracovány všechny prvky proudu. Pokud je jádro zpracováváno pomocí GPU probíhá celý proces následovně. Proudy jsou přesunuty do videopaměti a jádro je přeloženo do formy fragment programu, který je nad daty ve videopaměti prováděn. Data jsou tedy "renderována". Deklarace jádra velmi připomíná deklaraci klasické funkce v jazyce C. Navíc je zde klíčové slovo "kernel", které celou deklaraci uvádí. Jádro je vždy typu void a pokaždé má jeden z parametrů označen kvalifikátorem out. V rámci jádra nejsou přístupné statické proměnné a globální paměť. Příklad deklarace jádra může vypadat takto: kernel void k(float s<>, float3 f, float a[10][10], out float o<>) zde bude jádro voláno na každý element ze vstupního proudu s. Pro každé volání bude mít proměnná s jinou hodnotu. Proměnná f je naopak konstantní a tedy pro každé volání stejná. V rámci jádra není dovoleno zapisovat do vstupního proudu či měnit konstantní proměnné. Parametr a ukazuje možnost zadat parametr jako prvek pole. Velikost pole nemusí být specifikována, ale je možné, že se pomocí jejího určení urychlí práce překladače. Žádné proudy nesmí být zadány pomocí pole. o je výstupním proudem, jde o parametr do kterého lze pouze zapisovat. Jeho hodnota je dána výpočtem jádra. Jádro je implicitně prováděno na každém elementu proudu s a výsledek zapisuje do proudu o. Jak tělo tak havička jádra jsou omezené na podmnožinu prvků C/C++, kterou podporuje jazyk Cg či HLSL. Volání funkce jádra je obdobné jako volání jakékoli funkce v jazyce C: 39

float a<100>; float b<100>; float c<10,10>; streamread(a, data1); streamread(b, data2); streamread(c, data3); k(a, 3.2f, c, b); streamwrite(b, result); Redukce Brook poskytuje podporu pro paralelní redukci proudu. Redukce je operace, která vyprodukuje z jednoho proudu proud menší nebo přímo jednu konkrétní hodnotu. Operace může být definována jedním binárním operátorem, který je jak asociativní, tak komutativní. Tím je dosaženo toho, že lze z proudu odebírat jeho prvky bez ohledu na pořadí a provádět na nich tyto operace, dokud nedostaneme redukovaný výsledek. Příkladem redukční operace může být sčítání. To je jak asociativní, tak komutativní a ze vstupní řady dat vrací pouhou jednu hodnotu. Povolené redukční operace tedy jsou: součet, násobek, minimum/maximum a bitové operace OR, AND a XOR. Nepovolenými jsou například odečítání a podíl. Je velmi důležité uvědomit si, že pokud například odečítání při redukci použijete, nebudete nijak na chybu upozorněni. Pouze výsledky budou špatné. Překladač není schopen zkontrolovat, zda jsou zadané operace povolené či nikoli. Redukční funkce jsou specifikovány podobně jako funkce jádra. Mají pouze několik doplňujících omezení. Redukce počítající součet všech prvků proudu bude vypadat takto: void reduce sum (float a<>, reduce float result<>) { result = result + a; } Redukční funkce mohou mít pouze dva proudové argumenty, jeden vstupní proud a druhý výstupní označený klíčovým slovem reduce. Ostatní parametry nesmí být proudy. 40

Další podstatnou podmínkou je, že oba proudy musí být stejného typu, aby se zadaná operace mohla úspěšně opakovat až do konce. Jádro tedy smí jak psát, tak číst z redukovaného parametru. Redukční parametr může být jak proud, tak skalární hodnota. Pokud jde o skalární hodnotu, je redukční funkce volána se stejnou výstupní proměnnou pro všechny vstupní parametry. Počáteční hodnota redukce je pak definována jako první hodnota vstupního proudu. Mnohorozměrná redukce Pokud je redukční parametr proud, potom poměr rozměrů vstupního a redukčního proudu určí, jak se redukce provede. Relativní poměr rozměrů vstupního a redukčního proudu je určen při postupujícím voláním redukčního jádra. Není předem vypočítáván. float s<100, 200>; float t<50, 20>; sum(s, t); v tomto příkladě se t liší 2x ve směru y a 10x ve směru x. Redukční jádro je tedy voláno na 2x10 částí proudu s. Každá část vytvoří jednu hodnotu výsledku. Pokud by rozměry neodpovídaly, ohlásí překladač chybu. Aby odpovídaly, je nutné zadat rozměry vstupního proudu takové aby byly násobkem proudu redukčního. Přizpůsobení tvaru proudu V závislosti na tvarech proudů jsou kombinovaně volány operátory streamstride a streamrepeat. kernel void foo (float a<>, out float b<>); float a<10, 20>; float b<50, 10>; foo(a,b); zde je rozměr y výstupního proudu 5x větší než vstupního, naopak rozměr x je o polovinu menší. Jádro foo je voláno jednou pro každý prvek proudu b. Proud je implicitně přetvarován, 41

aby odpovídal velikosti výstupu. v tomto případě ve směru y opakujeme prvky např.(1,1,1,1,2,2,2,2,2,3,3,3,3,3,...), provádíme tedy operaci streamrepeat a ve směru x vynecháváme např.(1,3,5..), používáme tedy streamstride. To se opakuje pro každý vstupní proud. Parametry polí zůstávají neměnné. Opakovací proudy Opakovací proudy jsou speciálním typem proudů, který je předem inicializován jako posloupnost hodnot (1, 2, 3,...). lze je konstruovat pomocí klíčového slova iter: iter float s<100> = iter(0.0f, 100.0f); //inicializace hodnotami 0.0, 1.0, 2.0,..., 99.0 První argument je hodnota prvního prvku požadovaného opakovacího proudu, druhý argument je horní hranice, které nebude v proudu dosaženo. Hodnoty jednotlivých prvků jsou od sebe vzdáleny o stejný kus. Jsou tedy rozloženy rovnoměrně. Jednorozměrný opakovací proud umí pracovat i s typy float2, float3 a float4. Každý z prvků typu je interpolován zvlášť. iter float2 s<10> = iter(float2(0, 0), float2(4, 10)); BrookGPU nepodporuje opakovací proudy o více jak dvou rozměrech. Opakovací proud nesmí být výstupním parametrem jádra. IndexOf tuto funkci lze použít uvnitř jádra k zjištění indexu prvku proudu, se kterým se právě pracuje. indexof(stream); BrookGPU podporuje i funkce gather/scater, které podporují nepřímé čtení/zápis. 42

6.2. Lineární algebra pomocí grafického procesoru Vývoj numerických metod pro řešení diferenciálních rovnic je jedním z tradičních oborů aplikované matematiky. Tyto techniky mají nejrůznější použití ve fyzikálních simulacích a modelování a jsou často využívány v počítačové grafice, aby byla dosažena realistická simulace skutečného světa. Pomocí numerických metod lze například docílit realistického vykreslení vodní hladiny a jejího vlnění. Numerická složitost technik bývá ovšem velice zatěžující. Často spotřebovává mnoho paměti a jejich celková doba výpočtu může být až neúnosně dlouhá. Potom je třeba počítat s různými kompromisy, kdy ubíráme z požadavků na přesnost a získáváme rychlejší výpočet. Znatelný průlom v této problematice lze připočíst právě programovatelným grafickým procesorům a jejich schopnosti paralelního zpracování, které lze velmi efektivně využít k rychlejšímu a zároveň velmi přesnému zpracování. Využití paralelismu grafického hardwaru ovšem není jedinou jeho výhodou. Dalším velice přínosným příspěvkem je i možnost okamžitého zobrazení výsledku, tedy vynechání časově leckdy náročného přesunu dat z CPU na GPU. Tyto dvě hlavní výhody byly dostatečným podmětem k novým implementačním snahám, se kterými se teď seznámíme. V tomto článku si ukážeme jak aplikovat lineární algebru na grafický hardware. Zjistíte, jakým způsobem reprezentovat jednotlivé prvky a jak řešit základní matematické úlohy. Celý článek čerpá ze znalostí zdrojů [14][15]. 6.2.1. Maticová reprezentace Základní myšlenkou maticové reprezentace je jejich vyjádření pomocí textur. Pro zjednodušení se uvažují pouze sloupcové matice, tedy vektory a čtvrcové NxN matice. Ostatní obecné matice lze koneckonců uzpůsobit do tohoto základního tvaru. První úvaha, která Vás pravděpodobně napadne, je reprezentovat matice jako 2D texturu a vektory jako 1D texturu. Tím bychom ale došli k nepříliš ideálnímu řešení. Vektory v podobě 1D textur mají totiž nejednu nevýhodu. První z nich je znatelně omezená délka vektoru, který se do takové mapy vejde. Další velmi podstatný důvod k vypuštění myšlenky o použítí 1D textur může být i fakt, že výpočet takovéto textury je znatelně pomalejší, než výpočet 2D textury se stejným počtem prvků. Dobrým důvodem pro použití 2D textur pro reprezentaci vektorů je i nutnost naaranžovat výsledky vzešlé z výpočtů 1D textur. U 2D lze počítat přímo do požadovaných poloh a rovnou zobrazovat bez dalších manipulací. V neposlední řadě je i efektivnější výpočet 43

příkladů, kde se kombinují jak matice, tak vektory. V okamžiku, kdy jsou oba prvky zapsány stejným způsobem, je práce mnohem snažší, než při kombinování dvou různých zápisů. Matice se reprezentují jako soubor diagonálních vektorů. Vektory pak ukládáme do 2D texturovacích map. Abychom předešli plýtvání pamětí při různě dlouhých diagonálních vektorech, lepíme je k sobě. Prvním vektorem potom je hlavní diagonála matice. Ta se uchová samostatně celá jako jeden vektor, druhým vektorem je diagonála začínající v prvním řádků a druhém sloupci. Ta je však nakonci o jeden prvek kratší. Tento jeden prvek je tedy nahrazen jednoprvkovou diagonálou z posledního řádku a posledního sloupce. Stejným způsobem se při tvoření vektorů postupuje dál. Prázdné prvky vektorů jsou vyplněny nulou. vzniklé vektory se nakonec uloží jako 2D textury z důvodů, které jsme již zmiňovali. Pro lepší představu, jak celý proces probíhá, se můžete podívat na obrázek níže. Obrázek 6.1.: Maticová reprezentace z [15] Důvodem, proč se využívá zrovna diagonální reprezentace, je časté použití diagonálních matic při numerických simulačních technikách. Milou výhodou použitého schématu je i snadná transpozice matic, kdy se pouze zamění pořadí vektorů. Tato operace ani nemusí být přímo provedena, stačí přeznačit číslování, což je snadná úloha pro fragment shader. 6.2.2. Základní operace Nyní si představíme několik základních operací s prvky uloženými způsobem, jaký jsme si právě popsali. Pro každou operaci je určen cíl, kam se mají výsledky směřovat a který může být přímo spojen s 2D texturou a určen pro další použití. Vektorové a maticové kontejnery jsou definovány jako třídy. Oba kontejnery obsahují pole C++ ve kterých je jeden nebo více 44