ELTE IK tavaszi félév Valasek Gábor

Slides:



Advertisements
Hasonló előadás
Szimmetriák szerepe a szilárdtestfizikában
Advertisements

Kauzális modellek Randall Munroe.
A TUDOMÁNYOS KUTATÁS MÓDSZERTANA
2.1Jelátalakítás - kódolás
Az úttervezési előírások változásai
Fizika II..
Számítógépes Hálózatok
Profitmaximalizálás  = TR – TC
A járműfenntartás valószínűségi alapjai
Szenzorok Bevezetés és alapfogalmak
Végeselemes modellezés matematikai alapjai
A magas baleseti kockázatú útszakaszok rangsorolása
Szerkezetek Dinamikája
MÉZHAMISÍTÁS.
Hőtan BMegeenatmh 5. Többfázisú rendszerek
BMEGEENATMH Hőátadás.
AUTOMATIKAI ÉPÍTŐELEMEK Széchenyi István Egyetem
Skandináv dizájn Hisnyay – Heinzelmann Luca FG58PY.
VÁLLALATI Pénzügyek 2 – MM
Hőtan BMEGEENATMH 4. Gázkörfolyamatok.
Szerkezetek Dinamikája
Összeállította: Polák József
A TUDOMÁNYOS KUTATÁS MÓDSZERTANA
Csáfordi, Zsolt – Kiss, Károly Miklós – Lengyel, Balázs
Tisztelt Hallgatók! Az alábbi példamegoldások segítségével felkészülhetnek a 15 pontos zárthelyi dolgozatra, ahol azt kell majd bizonyítaniuk, hogy a vállalati.
J. Caesar hatalomra jutása atl. 16d
Anyagforgalom a vizekben
Kováts András MTA TK KI Menedék Egyesület
Az eljárás megindítása; eljárási döntések az eljárás megindítása után
Melanóma Hakkel Tamás PPKE-ITK
Az új közbeszerzési szabályozás – jó és rossz gyakorlatok
Képzőművészet Zene Tánc
Penicillin származékok szabadgyökös reakciói
Boros Sándor, Batta Gyula
Bevezetés az alvás-és álomkutatásba
Kalandozások az álomkutatás területén
TANKERÜLETI (JÁRÁSI) SZAKÉRTŐI BIZOTTSÁG
Nemzetközi tapasztalatok kihűléssel kapcsolatban
Gajdácsi József Főigazgató-helyettes
Követelmények Szorgalmi időszakban:
Brachmann Krisztina Országos Epidemiológiai Központ
A nyelvtechnológia eszközei és nyersanyagai 2016/ félév
Járványügyi teendők meningococcus betegség esetén
Kezdetek októberében a könyvtár TÁMOP (3.2.4/08/01) pályázatának keretében vette kezdetét a Mentori szolgálat.
Poszt transzlációs módosulások
Vitaminok.
A sebész fő ellensége: a vérzés
Pharmanex ® Bone Formula
Data Mining Machine Learning a gyakorlatban - eszközök és technikák
VÁLLALATI PÉNZÜGYEK I. Dr. Tóth Tamás.
Pontos, precíz és hatékony elméleti módszerek az anion-pi kölcsönhatási energiák számítására modell szerkezetekben előadó: Mezei Pál Dániel Ph. D. hallgató.
Bevezetés a pszichológiába
MOSZKVA ZENE: KALINKA –HELMUT LOTTI AUTOMATA.
Bőrimpedancia A bőr fajlagos ellenállásának és kapacitásának meghatározása Impedancia (Z): Ohmos ellenállást, frekvenciafüggő elemeket (kondenzátort, tekercset)
Poimenika SRTA –
Végeselemes modellezés matematikai alapjai
Összefoglalás.
Az energiarendszerek jellemzői, hatékonysága
Varga Júlia MTA KRTK KTI Szirák,
Konzerváló fogászat Dr. Szabó Balázs
Outlier detektálás nagyméretű adathalmazokon
További MapReduce szemelvények: gráfproblémák
Ráhagyások, Mérés, adatgyűjtés
Járműcsarnokok technológiai méretezése
Grafikai művészet Victor Vasarely Maurits Cornelis Escher.
VÁLLALATI PÉNZÜGYEK I. Dr. Tóth Tamás.
RÉSZEKRE BONTOTT SOKASÁG VIZSGÁLATA
Az anyagok fejlesztésével a méretek csökkennek [Feynman, 1959].
Bevezetés a színek elméletébe és a fényképezéssel kapcsolatos fogalmak
Minőségmenedzsment alapjai
Előadás másolata:

ELTE IK 2015-2016 tavaszi félév Valasek Gábor Haladó(bb) OpenGL ELTE IK 2015-2016 tavaszi félév Valasek Gábor

Grafikus API-k általában

Grafikus API-k általában Ajánlott irodalom: Fabian Giesen cikksorozata: https://fgiesen.wordpress.com/2011/07/09/a- trip-through-the-graphics-pipeline-2011-index/ OpenGL Insights: http://openglinsights.com/

Grafikus API-k az OS-ben Program Grafikus API User-mode driver Ütemező(k) Kernel-mode driver GPU busz

Program A saját alkalmazásunk A grafikus hardverhez valamilyen API-n keresztül férünk hozzá De ami azt illeti, minden máshoz is: ez ugyanis user mode-ban fut

Grafikus API Az összes erőforráskezelő és egyéb, grafikus hardvert érintő utasítás ezen keresztül intézhető Emellett számon tartja, hogy mi az aktuális állapota a program grafikus context-(jei)nek, ellenőrzi a paraméterek validitását, hibakeresést stb. végez, esetleg kötegeli az elvégzendő munkákat DirectX-nél shader validálás és linkelés is itt történik (OpenGL-nél driver-ben)

Kernel és user mód Kernel mode: Az itt végrehajtott kódnak korlátozásoktól mentes, teljes hozzáférése van a hardverhez A fizikai memória bármely részét tudja címezni Az itteni crash-ektől száll el az egész rendszer User mode: Az így futó kód nem tud közvetlenül hozzáférni hardverhez és a memóriához, ehhez a rendszer és egyéb API-kat kell használnia

Kernel és user mód Hardveresen is támogatott, pl. x86 architektúrákon négy szintű hozzáférés van

Kernel és user mód Context: a program jelenlegi állapota, beleértve az aktuális pillanatban a regiszterek értékeit is Context switching: amikor a programunk unrunnable-lé válik (mert például éppen a HDD-re várakozik), akkor egy másikat futtat tovább a rendszer Ekkor a két program context-jét egyszerűen kicseréli a CPU (ld. GPU architektúrás óra) Ha más módúak is, még a context switch mellett Ringet is kell váltania a programnak

User mód Program user módban futtatásakor A Windows létrehoz egy process-t a programnak Minden user mode-ú programnak van egy saját virtuális memóriája, amit valahogy az OS lemap-pel a fizikai memóriára (vagy akár disk- re – lásd lapozófájlok Win-ben) A process hozzáférése a virtuális memóriához korlátozott – illetve mivel tényleg virtuális, ezért nem tudnak hozzáférni a másikéhoz sem – hogy ne akadjon össze az OS-sel és a többi process-szel OS API-n keresztül tudnak csak egymással kommunikálni a process-szek

Kernel mód Program kernel módban futtatásakor Minden kernel módú kód ugyanazt a virtuális címteret látja Ennek megfelelően nem csak a többi driver-be tud belenyúlni, hanem az OS-be is → és jön a... (*)

Kernel és user mód

Kernel és user mód További részletek az MS honlapján https://msdn.microsoft.com/en- us/library/windows/hardware/ff554836(v=vs.85).as px

User-mode driver (UMD) Egy alacsonyabb szintű API-t valósít meg (DDI), de azért még hasonló ahhoz (DX, OGL stb.), amit a programból hívunk Bizonyos dolgokat egyértelműbben fejez ki (pl. memóriafoglalásokat) Fizikailag pl. a nvd3dum.dll vagy atiumd*.dll fájlokban lakik A hívó process címterében van, amikor használjuk

User-mode driver (UMD) A shaderek fordítása is ezen a szinten történik DirectX-nél ezek nem csak szintaktikailag helyesek már bemenetként is, hanem már magas szintű optimalizáláson is átestek Erre jönnek az alacsony (hw-tól is és meghívó programtól is függő) optimalizálások az UMD- ben DX-nél HLSL → IR → D3D bytekód út Egyes API állapot is végül lehet shaderkóddal lesz implementálva (pl. textúrahatárok)

User-mode driver (UMD) A legtöbb létrehozás/fordítás deferred végrehajtású: csak akkor történik meg, amikor először lesz tényleg szükség a dologra A memóriakezelést a Kernel szintű résztől kapott nagyobb memóriadarabon belül csinálja: azaz a KMD-től kap egy fizikai területet, amin belül foglal (de az UMD címteréből a GPU fizikai címterébe leképezés már Kernel) Rendszer és videomemória közötti adatátviteleket is ütemezi

User-mode driver (UMD) A legfontosabb feladata pedig a command buffer-ek feltöltése Ezeket fizikailag a Kernel mód allokálja és adja át az UMD szintnek A command bufferbe kerül minden utasítás (állapotváltoztatások, rajzolások), méghozzá az adott hardver számára értelmezhető formában

User-mode driver (UMD) A kompatibilitáshoz szükséges dolgokat is itt oldják meg Általában a régi fixed function pipeline kódokat is itt fordítják át a hardvernek megfelelően (nyilván beépített shaderekre)

User-mode driver (UMD) Fontos: a GPU-t egyszerre több program is akarja használni (legalábbis desktop-on!) Így lényegében minden ilyen programnak van egy UMD-je, ahonnan az utasítások az OS GPU ütemezőjén keresztül mennek fel a grafikus kártyára Ez végzi a feladatok párhuzamos végrehajtását időosztásos módon Figyeljetek: ha másik program dolgait kell kirajzolni, akkor (GPU) context switch jön!

User-mode driver (UMD) Fontos, hogy a jelenlegi driverimplementációkat a piaci igények igazítják Rengeteg kézzel beállított, alkalmazásspecifikus dolog van A fenti API/UMD tagolódás a DirectX-re jellemzőbb, sajnos OpenGL-ben a kettő összefolyik

Kernel-mode driver (KMD) A tényleges hardveres dolgokat ez végzi (fizikai memóriára mappelés, inicializálás, megjelenítési módok lekérdezése és beállítása, hardveres egér stb.) Mivel Kernel szintű, csak egy van belőle Ha ez elcrash-sel, akkor BSOD is lehet belőle (de azért nem mindig)

Kernel-mode driver (KMD) Itt történik a command buffer-ek tényleges kezelése UMD parancs végrehajtása: Az UMD összeállítja a command buffer-jét és elküldi az ütemezőnek Az ütemező amikor a GPU hozzáférési sorban a process kerül sorra, átadja a command buffer-jét a KMD-nek A KMD a megkapott command buffert a GPU fő command buffer-jébe helyezi el (amiből ma már több is van)

Kernel-mode driver (KMD) Az utolsó lépésben történhet RAM → GPU RAM átadás is, ha nem tudja a GPU command processor címezni a rendszermemóriát A parancspuffert két pointerrel kezeli (amiket regiszterekben tárol a GPU): Read pointer: a fő command buffer helye a memóriában Write pointer: a KMD meddig írt bele a command buffer-be

Memória

Memória

Memória

Memória Újabb architektúrákon a CPU-RAM sávszélesség maximuma (throughput) 25 Gbps A késleltetés (tehát ha nem a cache-ből, hanem a memóriából kell olvasni) olyan 140 órajel (összehasonlításképp: L1, L2, L3-ból rendre 4, 11, 39 órajel Nehalem-en) Ezzel szemben egy mai közepes (GeForce 950) GPU-n 105, felső-középkategóriában (GeForce 980) 224, míg egy Titan X-nél 331 Gbps De a GPU RAM késleltetése 400-800 órajel!

Memória A DRAM-ban a chip-pek egy 2D tömbként vannak elrendezve Minden egyes memóriacím tehát egy (sor, oszlop) pár DRAM írás/olvasás ettől függetlenül az adott sorban lévő összes elemet érinti a kapcsolások miatt Így akkor érhetjük el a maximális throughput-ot, ha így (=soronként) olvasunk és írunk

Busz A CPU és a GPU (illetve a RAM és a GPU RAM) közötti összeköttetés a PCIe interfészen keresztül történik Figyeljünk arra, hogy itt is adott a sávszélesség Általában akkor válik fájdalmassá, ha streamelnünk kell adatokat a RAM-ból a GPU-ra

Memória - adatátvitel Jelenleg kétféle memória van, amikor „GPU memóriáról” beszélünk: GPU RAM Rendszermemóriába map-pelt GPU memória Ez architektúrától függően különbözőképpen kezelhető Egy jó MMU (memory management unit) is dönthet, ami elintézi, hogy a gyakori adatok 1-be, a kevésbé gyakoriak 2-ben legyenek tárolva...

GDDR GDDR GDDR GDDR A GPU (FU-któl írás/ olvasás, fetch stb.) Controller ... Controller cím adat adat vezérlés GPU mem hub MMU Lokális? PCIe interfész ... Forrás: http://www.farbrausch.de/~fg/gpu/gpu_memory.jpg

Memória adatátvitel Kétféle GPU van a piacon: Integrált: ekkor fizikailag egy lapkán van a CPU és a GPU, így nagyobb az adatátvitel ← de általában kisebb a grafikus teljesítmény Diszkrét: külön slot-ban van a GPU, ekkor DMA controllerrel kell átvinni a memóriát a PCIe sínen

Fő parancsfeldolgozó Az ide vezető memóriautak mind nagy sávszélességű és nagy késleltetési utak Maga a fő parancsfeldolgozó egy (nagyon hosszú) ring bufferből szedi a KMD-ből érkező parancsokat Az aktuális parancsot a Command Processor (CP) dolgozza fel, ami utána azt a szál vagy stream ütemezőnek adja tovább Egy utasítás általában egy memóriadarabra is mutat

Forrás: http://developer.amd.com/gpu_assets/R5xx_Acceleration_v1.2.pdf

Parancsok feldolgozása A fő command buffer-re úgy is lehet gondolni, mint két szál (a CPU-n futó grafikus program és a GPU) közötti kommunikációs csatornára Egyúttal ez egy osztott erőforrás, ennek megfelelően szinkronizálni is kell: ezt a fence- eken keresztül teszik Ezeket a GPU beolvassa és megfelelő regiszterek módosításával tudatja a CPU-val, hogy az adott feltétel, amit a fence igényelt, teljesült

Fő parancsfeldolgozó Mivel nagyon sokszor van igény arra, hogy a GPU olvassa a rendszermemóriát, ezért modern GPU-kon van DMA erre a célra (Fermi-n például kettő is) Illetve egy MMU, ami lényegében virtualizálja a GPU-n futó programok számára a memóriát (l. korábban) Honnan tudja a driver, hogy mehet az utasítás? Például DX alatt flush, present, lock!

Fő parancsfeldolgozó A bejövő utasítások lehetnek tényleges parancsok, állapotváltások, adatok A fő parancsfeldolgozó tárolja a GPU aktuális állapotát Lényegében egy állapotgépnek tekinthető, ami a belső állapotát a végrehajtott utasításoknak megfelelően módosítja, a CPU program leképezett regisztereit frissíti és megszakításokat küld a CPU-nak

Fő parancsfeldolgozó A végrehajtandó parancsok között lehetnek 2D és 3D parancsok (sőt, akár text mode-beliek is), ezek általában dedikált egységekhez mennek Arra is volt példa, hogy a 2D-s és 3D-s parancsokat külön feldolgozóegység hajtotta végre

Állapotváltások Az állapotváltozást hozó utasítások a GPU-n problémásak, mivel egyszerre nagyon sok szál is érintett lehet egyetlen memóriadarab módosításában A GPU-n pedig egyszerre nagyon sok szál fut A különböző típusú állapotoknál más-más megoldásokhoz nyúlnak a vendorok

Állapotváltások Ennek a megoldása nem triviális: Kikényszeríthetjük, hogy az állapotváltás előtt minden, az érintett memóriát referáló utasítás végetérjen Az állapotot az utasítások részévé is tehetjük, azaz egy kvázi plusz paraméterként továbbadhatjuk a parancsok mellett (ha eléggé kicsi az állapottér) Használhatunk double buffering-et is az állapotokra (márhogy ezt a double bufferinget, l. majd a valósidejű tervmintás órát) Stb.

Szinkronizáció Kétféle megközelítést használ a GPU arra, hogy értesítse a CPU-t bizonyos feltételek teljesüléséről: Megszakításokat küld (a ritkán történő, de magas prioritású eseményekről, például a vertical retrace-ről) ← interrupt alapú A saját regisztereiben eltárolja az események paramétereit, amiket a CPU utána bármikor lekérdezhet ← polling alapú

Szinkronizáció Maguk a feltételek a fence-ek lennének A GPU-n belüli várakozásokat „feltétel teljesüléséig várakozz” típusú utasításokkal valósítják meg – vagy bármi más barrier-rel

OpenGL

Utasítások kiadása Az OpenGL-es utasításokból egy vagy több tényleges command queue-ba kerülő utasítás születik Ha egy utasítás felfért a GPU-n lévő fő parancssorra, akkor 'issued' állapotú lesz Ha nem fért fel, akkor 'unissued' Ha fence utasítást hajt végre a GPU, akkor jelez, hogy ez a fence sikeresen teljesítve Arra figyeljünk, hogy a driver tipikusan néhány frame-mel előrébb jár (általában 3)

Szinkronizáció OpenGL-ben Explicit szinkronizáció: glFinish(): blokkolja a hívó szálat amíg az összes, glFinish()-ig kiadott OpenGL utasítás végrehajtása véget nem ér glFlush(): blokkolja a hívó szálat, amíg az összes, glFlush()-ig kiadott rajzolási utasítás rá nem kerül a GPU fő paranccsorára Szinkronizáció objektumokon keresztül

Szinkronizáció OpenGL-ben: fence GLsync fence = glFenceSync( GL_SYNC_GPU_COMMANDS_COMPLETE, 0 ); ... // utasítások, amiket be kell várni a folytatás előtt GLenum result = glClientWaitSync( fence, GL_SYNC_FLUSH_COMMANDS_BIT, GLuint64(5000000000)); // ellenőrizzük, hogy a fence teljesült-e vagy más van: if (result == GL_CONDITION_SATISFIED ) ...

Szinkronizáció OpenGL-ben: fence GLsync fence = glFenceSync( GL_SYNC_GPU_COMMANDS_COMPLETE, 0 ); ... GLenum result = glClientWaitSync( fence, GL_SYNC_FLUSH_COMMANDS_BIT, GLuint64(5000000000)); if (result == GL_CONDITION_SATISFIED ) ... Egyelőre (OpenGL 4.5 – azaz örökre is) ez a két paraméter kötelezően ez kell legyen.

Szinkronizáció OpenGL-ben: fence GLsync fence = glFenceSync( GL_SYNC_GPU_COMMANDS_COMPLETE, 0 ); ... GLenum result = glClientWaitSync( fence, GL_SYNC_FLUSH_COMMANDS_BIT, GLuint64(5000000000)); if (result == GL_CONDITION_SATISFIED ) ... Ez viszont lehet 0 is, de akkor adjunk ki egy glFlush()-t a parancs előtt, különben előfordulhat, hogy új rajzolási parancs híján örökre várakozunk. (U.i. a GPU nem jelzi, hogy kiürült a command queue-ja, még ha van egy rakás kliensoldali unissued parancs is!) A glFlush()-t ha nem akarjuk kézzel kiadni, akkor használjuk ezt a paramétert itt!

Szinkronizáció OpenGL-ben: fence GLsync glFenceSync(GLenum condition, GLbitfield flags) Létrehoz egy új szinkronizációs objektumot és berakja az OpenGL parancssorba (driver oldaliba) Amikor ezt feldolgozza a GPU, akkor minden ezen várakozót továbbenged A függvény visszatérési érték egy nemnulla azonosító lesz

Szinkronizáció OpenGL-ben: fence GLsync glFenceSync(GLenum condition, GLbitfield flags) Csak GL_SYNC_GPU_COMMANDS_COMPLETE lehet

Szinkronizáció OpenGL-ben: fence GLsync glFenceSync(GLenum condition, GLbitfield flags) Mivel egyetlen ilyen flag-et sem specifikáltak végül az OpenGL-ben, ezért csak nullát tudunk ide írni

Szinkronizáció OpenGL-ben: fence void glDeleteSync(GLsync sync) Egyszerűen törli a paraméterben kapott szinkronizációs objektumot, ha épp nincs használatban Különben ad neki egy „törlendő” flag-et és amint elfüstöl (=GPU feldolgozza és mindenki, aki rajta blokkolt kilép a várakozásból) törli Ha nullát adunk meg neki, akkor szép csendben nem csinál semmit

Szinkronizáció OpenGL-ben: fence enum glClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) Blokkolja a szálat amíg a GPU fő parancssora fel nem dolgozza a sync fence-et, vagy hiba nem történik

Szinkronizáció OpenGL-ben: fence enum glClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) A visszatérési értékből tudjuk mi is volt végül: GL_ALREADY_SIGNALED: már kiadta, nem kell várni GL_TIMEOUT_EXPIRED: lejárt a max várakozás GL_CONDITION_SATISFIED: várakozás közben, de még timeout előtt feldolgozta a GPU a fence-t GL_WAIT_FAILED: valami más baj van (glGetError-ból kiderül, hogy mi)

Szinkronizáció OpenGL-ben: fence enum glClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) A fence, aminek a feldolgozására várunk

Szinkronizáció OpenGL-ben: fence enum glClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) Ez ennek az utasításnak a flush-olását irányítja (és nyilván ezen keresztül a megelőzőkét is, akik még nem kerültek fel a sorba) Vagy semmi (ekkor figyeljünk arra, hogy kézzel kell egy glFlush()) vagy pedig GL_SYNC_FLUSH_COMMANDS_BIT

Szinkronizáció OpenGL-ben: fence enum glClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) Legfeljebb mennyi ideig várakozzon a kliens a fence teljesülésére, nanosecundum-ban Ha nullát írunk, akkor nem várunk egyáltalán (a visszatérési értékből viszont tudjuk, hogy teljesült- e vagy sem a fence)

Szinkronizáció OpenGL-ben: fence void glWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) A GL szerver (=GPU) blokkol addig, amíg az első argumentumban adott sync objektum signaled állapotba nem kerül Jelenleg a flags csak 0 nulla Jelenleg a timeout csak GL_TIMEOUT_IGNORED lehet

Szinkronizáció OpenGL-ben: fence Ezen kívül úgy általában le tudjuk kérdezni egy sync objektum attribútumait a glGetSync segítségével

Szinkronizáció OpenGL-ben Egyes utasítások implicit glFinish()-t vagy glFlush()-t provokálnak ki Például framebuffer váltásnál ha még van rajzoló utasítás az előző FBO-ra, akkor jöhet szinkronizáció FBO olvasása a CPU-ról szintén szinkronizálást provokál, ahogy általában a GPU-s pufferek olvasási műveletei is

Adatátadás CPU és GPU között

DMA A DMA (direct memory access) vezérlő segítségével mozgat adatokat a rendszermemória és GPU memória között a rendszer Az adatátadás aszinkron (a CPU szemszögéből) és nem kell a CPU-nak felügyelnie De GPU oldalról ez szekvencializálva is megjelenthet (pl. még Fermi sem tudott egyszerre adatot átvinni és renderelni, de a Quadro-k egy ideje már képesek rá)

Pinned memory Hívják még pin-locked-nak vagy page-locked-nak A rendszermemória olyan darabja, amit a GPU DMA tud címezni Emellett nem engedi a lapozót, hogy disk-re helyezze át

Buffer Object Legáltalánosabban, OpenGL-ben egy buffer egy memóriadarabot kezelő objektum Akkor kap szemantikát, ha egy „buffer target”-re bind-oljuk (pl. így dől el, hogy ő vertexeket tároló puffer lesz) A GPU-n történő allokációt ÉS a rendszermemóriából történő feltöltést a glBufferData utasítás végzi Fontos viszont: a glBufferData MINDIG allokál Tehát ha csak frissíteni akarunk, akkor más kell

Pufferek feltöltése: glBufferData Hasonló a memcpy-hez (sőt: néha az!), ha a glBufferData data pointere nem null, akkor: A driver átmásolja az adatokat data-ról egy GPU DMA által látható memóriaterületre A driver elküldi az adatátviteli parancsot és visszatér a glBufferData hívásból Ezzel aszinkron, a GPU MMU vagy rendszer RAM-ban hagyja a memóriát egy pin-locked lapon, vagy pedig átviszi a GPU-ra – usage hint-ektől és implementációtól függően

Pufferek feltöltése: glMapBuffer A glMapBuffer és glUnmapBuffer utasítások már közvetlen a GPU DMA által elérhető pin-lockolt rendszer RAM címet adják át az alkalmazásnak (vagy nem – implementáció függő) Ezzel megúszhatjuk a memcpy-t rendszermemórián belül, de... ...a driver csak azután tud visszatérni, hogy megvan a memóriacím, amihez lehet meg kell várnia egy GPU → RAM másolást! Megfelelő extension-ökkel az is elérhető, hogy a map- pelt memória 64 bájtos rácsra illeszkedjen

Kitérő: alignment Egy leírás arról, hogy miért fontos az, hogy garantáltan valamilyen rácsra illeszkedik-e a visszaadott pointer vagy sem: https://www.ibm.com/developerworks/library/pa- dalign/

Kitérő: alignment Memória a programozó szerint Memória a processzor szerint

Kitérő: alignment 0-s és 1-es bájt olvasása, bájt granularitású CPU-val 0-s és 1-es bájt olvasása, 2 bájt granularitású CPU-val

Kitérő: alignment

glBufferData A usage hint-ek figyelembevétele teljesen GPU és driver függő (az OpenGL Insights-ból, Intel Core i5 760-nal és GeForce GTX 470-nel): Eljárás Usage hint Hová került Adatátviteli sebesség glBufferData/ glBufferSubData GL_STATIC_DRAW device 3.79 GB/s glMapBuffer/ glUnmapBuffer GL_STREAM_DRAW pinned system rendszermemóriából használta 5.73 GB/s

Pufferek közti adatmozgatás A következőket glCopyBufferSubData és glTexImage2D függvényekkel csinálták, GL_RGBA8 formátummal (ez utóbbi elég rossz ötlet is lehet, vendortól függően):

glBufferSubData void glBufferSubData( GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid * data) Frissíti az aktuálisan target-re bindolt puffert a data által mutatott rendszermemóriabeli adatokkal offset (byte) buffer object size (byte)

Buffer Object Az előbbi hasznos, ha valamiért már eleve rendelkezésünkre áll a rendszermemóriában az, amit fel szeretnénk tölteni Például ha minden GPU-n lévő erőforrásnak van egy másolata a RAM-ban Viszont van, hogy nem tudunk mindent redundánsan eltárolni De lehetőség van arra, hogy a buffer object tartalmát bemásoljuk a RAM-ba és utána a módosított értékeket visszatöltsük

glMapBuffer void * glMapBuffer( GLenum target, GLenum access) Az aktív target-re bind-olt buffer object teljes tartalmát bemásolja a kliens (=program CPU-n) memóriaterébe (=RAM) A visszatérési érték a visszamásolt tartalom kezdőpontja (hiba esetén NULL) Az access lehet GL_READ_ONLY, GL_WRITE_ONLY, GL_READ_WRITE

glUnmapBuffer GLboolean glUnmapBuffer(GLenum target) Visszatölti a target kliens memóriaterében lévő (akár módosított) leképezését a host memóriaterébe (GPU) Ha hiba történt, false-t ad vissza Eddig a pontig garantált csak, hogy a glMapBuffer által visszaadott címen a lekérdezett puffer adatai találhatóak

Map/UnMap glBindBuffer(GL_ARRAY_BUFFER, buf); void* ptr = glMapBuffer( GL_ARRAY_BUFFER, GL_WRITE_ONLY); memcpy(ptr, data, sizeof(data)); glUnmapBuffer(GL_ARRAY_BUFFER);

Map/UnMap Természetesen ennek is vannak különböző verziói: glMapBufferRange / glFlushMappedBufferRange glMapNamedBufferRange / glFlushMappedNamedBufferRange

glMapBufferRange A glMapBufferRange és glFlushMappedRange extra access paraméterei: GL_MAP_READ_BIT, GL_MAP_WRITE_BIT: a visszadott pointer írásra/olvasásra használható csak. Ha ez nem teljesül, nincs hibaüzenet, de a viselkedés definiálatlan. GL_MAP_PERSISTENT_BIT: a kliens tartósan a visszaadott címen akarja tudni a puffert. Eközben lehet rajzolási utasításokat is kiadni. GL_MAP_COHERENT_BIT: perzisztens mapping legyen koherens, vagyis ha írunk a pufferbe (akár CPU-ról, akár GPU-ról), arról a másik is értesül

glMapBufferRange A glMapBufferRange és glFlushMappedRange extra access paraméterei: GL_MAP_INVALIDATE_BUFFER_BIT/ GL_MAP_INVALIDATE_RANGE_BIT: a puffer egészének/részének előző tartalmát dobja el a driver. Read-del ne használjuk! GL_MAP_FLUSH_EXPLICIT_BIT: ha módosítjuk a range-en belül a memóriát, akkor egy glFlush lesz GL_MAP_UNSYNCHRONIZED_BIT: bízza ránk az OpenGL, hogy mikor rakjuk vissza a GPU-ra az adatokat

Pufferek másolása source offset (byte) size (byte) source buffer object glCopyBufferSubData destination offset (byte) dest. buffer object size (byte)

glCopyBufferSubData void glCopyBufferSubData( GLenum readtarget, GLenum writetarget, GLintptr readoffset, GLintptr writeoffset, GLsizeiptr size) A read és write target-ek különbözőek kell, hogy legyenek (GL_ARRAY_BUFFER stb.) A lehetőségek közül kettőt kiemelnék: GL_COPY_READ_BUFFER GL_COPY_WRITE_BUFFER

glCopyBufferSubData GLfloat vertexData[] = { ... }; glBindBuffer( GL_COPY_READ_BUFFER, vbo1); glBindBuffer( GL_COPY_WRITE_BUFFER, vbo2); glCopyBufferSubData( GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));

glCopyBufferSubData GLfloat vertexData[] = { ... }; glBindBuffer( GL_ARRAY_BUFFER, vbo1); glBindBuffer( GL_COPY_WRITE_BUFFER, vbo2); glCopyBufferSubData( GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));

glCopyBufferSubData GLfloat vertexData[] = { ... }; glBindBuffer( GL_COPY_WRITE_BUFFER, vbo1); glBindBuffer( GL_ARRAY_BUFFER, vbo2); glCopyBufferSubData( GL_COPY_WRITE_BUFFER, GL_ARRAY_BUFFER, 0, 0, sizeof(vertexData));

Implicit szinkronizáció veszély A GPU tipikusan 2-3 frame-mel előrébb jár, mint az alkalmazás Viszont ez azt is jelenti, hogy az aktuális frame- ben glBufferSubData-val vagy glMapBuffer[Range] segítségével módosítunk olyan puffert, amiből még épp kirajzolna a GPU

Pufferek kezelése Ezzel még csak elkezdődnek a dolgok Néhány érdekes részlet: http://www.seas.upenn.edu/~pcozzi/OpenGLInsig hts/OpenGLInsights- AsynchronousBufferTransfers.pdf Egyébként maga a könyv még mindig rengeteg hasznos információt tartalmaz: http://www.amazon.com/OpenGL-Insights-Patrick- Cozzi/dp/1439893764 Az egyik szerző blogja pedig még naprakészebb infók tárháza: http://www.g-truc.net/

Streaming Ezt csináljuk, amikor nagyon sűrűn töltünk fel adatok a GPU-ra Persze lehet glSubBufferData-val Három népszerű, „okosabb” módszer: Több puffer round-robinolva Puffer újraspecifikációja (orphaning) Kézi szinkronizáció glMapBufferRange segítségével

Streaming: 0. verzió Simán csak töltsük fel újra a puffer megfelelő darabját Ez bizony implicit szinkronizációt hoz Kiindulási kód: http://cg.elte.hu/~msc_cg/Gyak/10/NativeOGLBen chmark.zip

Streaming: round robin Ne egyetlen puffert foglaljunk az adatunknak, hanem többet (n-et) A programban az n-ediket frissítjük, amíg az (n-1)-edikből rajzolunk ki → így nem kell szinkronizálni Ezt akár többszálú programban is megcsinálhatjuk – a főszálból rajzolunk, míg a dolgozó szálban újrageneráljuk a puffer tartalmát (amit akár mondjuk diskről is szedhetünk be közben)

Streaming: orphaning Ugyanaz, mint a round robin, csak itt az egész a driver-en belül történik: glBufferData data pointere legyen nullptr és a mérete illetve usage hint-je maradjon meg Ezután glBufferData-val töltsük fel ismét! GlBindBuffer( GL_ARRAY_BUFFER, my_buffer_object); GlBufferData( GL_ARRAY_BUFFER, data_size, nullptr, GL_STREAM_DRAW); GlBufferData( GL_ARRAY_BUFFER, data_size, data_ptr, GL_STREAM_DRAW);

Streaming: orphaning Alternatívaként megoldható glMapBufferRange segítségével is: glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, size, nullptr, GL_STREAM_DRAW); void* data = glMapBufferRange(GL_ARRAY_BUFFER, 0, size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT ); ... glUnmapBuffer(GL_ARRAY_BUFFER);

Streaming: manuális sync Itt lényegében glMapBufferRange-et hívunk GL_MAP_UNSYNCHRONIZED_BIT paraméterrel Itt is több puffert használunk Egy fence segítségével meggyőződünk arról, hogy a most írásra került puffert már senki sem használja ...de általában a driver „maximum 3 frame”-mel jár előrébb, így hit alapon használhatunk egy 3 elemből álló chain-t is...

Streaming: manuális sync Algoritmus: Update()-ben: glClientWaitSync-eljünk a pufferunkon Miután visszatér belőle a rendszer, glDeleteSync Adatstream glMapBufferRange a következő paraméterekkel: GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT Végül glUnmapBuffer Render() végén: m_fences[m_current] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);

Image Load Store OpenGL 4.2 óta core-ban is elérhető lehetőség, hogy textúrákat olvassunk és írjunk Mivel írás is van, ezért szükségünk van arra, hogy az írás/olvasás szinkronizálási problémákat kezelni tudjuk – erre vannak a memory qualifier-ek (és barrier-ekkel) Maga az imageXd a következő típusú tárolókból olvashat:

Image Type Corresponding Texture Type gimage1D​ GL_TEXTURE_1D​ single layer from GL_TEXTURE_1D_ARRAY​ gimage2D​ GL_TEXTURE_2D​ single layer from: GL_TEXTURE_2D_ARRAY​ GL_TEXTURE_CUBE_MAP​ GL_TEXTURE_CUBE_MAP_ARRAY​ GL_TEXTURE_3D​ gimage3D​ gimageCube​ gimage2DRect​ GL_TEXTURE_RECTANGLE​ gimage1DArray​ gimage2DArray​ gimageCubeArray​ GL_TEXTURE_CUBE_MAP_ARRAY​  (requires GL 4.0 or  ARB_texture_cube_map_array) gimageBuffer​ GL_TEXTURE_BUFFER​ gimage2DMS​ GL_TEXTURE_2D_MULTISAMPLE​ GL_TEXTURE_2D_MULTISAMPLE_ARRAY​ gimage2DMSArray​

Image Load Store A shaderben „kép” méretét az ivec imageSize(gimage image) paranccsal kérhetjük le Olvasni egész koordinátákról tudunk: gvec4 imageLoad( gimage image, IMAGE_COORD) Írni pedig a következőképpen: void imageStore(gimage image, IMAGE_COORD, gvec4 data)

Image Load Store Atomi műveletek segítségével végezhetünk műveleteket is Például imageAtomicExchange, imageAtomicCompSwap, imageAtomicAdd, imageAtomicAnd, imageAtomicOr, imageAtomicXor, imageAtomicMin, imageAtomicMax

Image Load Store Kliens oldalról az Image Object a textúrához hasonlóan kezelendő Minden shader stage-hez tehát van valahány Image Unit, amikhez konkrét Image-eket lehet rendelni A glBindImageTexture segítségével A shaderekben található image* uniformokat pedig valahányas indexű Image Unit-hoz bindoljuk

Shader Storage Buffer Object Inkoherens író/olvasó utasításokkal elérhető tároló (mint az ILS) A megengedett méretnek legalább 16MB-osnak kell lennie minden OGL implementációban GLSL-ben buffer típusnévvel jelölendőek

Direct State Access Program bind-olása nélkül is írhatunk a uniformá változókba: glUseProgram(prog1); glGetUniformLocation("foo", &loc1); glUniform3f(loc1, 1, 2, 3); glUseProgram(prog2); glGetUniformLocation("bar", &loc2); glUniform3f(loc2, 3, 2, 1); <=> glProgramUniform3f(prog1, loc1, 1, 2, 3); glProgramUniform3f(prog2, loc2, 3, 2, 1);

Direct State Access OpenGL Object Type Context Prefix DSA Prefix Texture Object "Tex" "Texture" Framebuffer Object "Framebuffer" "NamedFramebuffer" Buffer Object "Buffer" "NamedBuffer" Transform Feedback Object N/A1 "TransformFeedback" Vertex Array Object "VertexAttrib" "VertexArray" Sampler Object N/A2 "Sampler" Query Object "Query"

Shader Storage Buffer Object Érdemes figyelni arra, hogy core-ban SSBO OpenGL 4.3, DSA 4.5 (!) feature Ennek megfelelően mielőtt használnád, győződj meg arról, hogy a megrendelő is hasznát tudja látni, különben extension-höz kell nyúlni Az meg vendor-specifikus, szóval tesztelj (és ne feledd: Intel (72.8%) >> NVIDIA (15.7%) > AMD (11.5%), globális piaci részesedésben) Persze specifikusabban más számok is kijöhetnek: http://store.steampowered.com/hwsurvey/

Shaderek

Shaderek linkelése Linkelés előtt a következő beállításokat kell megtennünk: Vertex shader bemeneti változóinak attribútum location-jeit meghatározni (vagy kliens oldalon glBindAttrib* fv-vel, vagy shaderben layout location-nel) Fragment shader kimeneti location-jeit meghatározni (vagy kliens oldalon glFragData* fv-vel, vagy shaderben layout location-nel) A transform feedback kimenetet beállítani (ha van) A multiprogram shaderekben a kódkiválasztást megtenni

Interfész blokkok GLSL input, output, uniform vagy tároló puffer változók csoportja storage_qualifier block_name { <define members here> } instance_name;

Interfész blokkok A storage qualifier lehet in out uniform buffer (OGL 4.3-tól fölfelé) A block_name-et használja a linker, hogy megfeleltesse egymásnak a különböző shaderek input és output interfészét, illetve a több helyen előforduló uniform változókat

Interfész blokkok Egy interface block-nak van egy block indexe: a szám, ami az adott intefész blokkot azonosítja GLuint glGetUniformBlockIndex( GLuint program, const char *name) Ha az interfész blokk puffer tárolású (uniform vagy buffer storage qualifier-es), akkor a block index-hez egy bindolási pontot kell megeadni void glUniformBlockBinding( GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding)

Interfész blokkok A bindolási pontra pedig rárakhatunk egy puffert void glBindBufferBase(GLenum target, GLuint index, GLuint buffer) A binding pontok maximális számát a következőképpen lehet lekérdezni: glGetIntegerv( GL_MAX_UNIFORM_BUFFER_BINDINGS,&va lue) A Buffer Object-et GL_UNIFORM_BUFFER target-re kell bind-olni létrehozáskor

Interfész blokkok

Uniform block Több shader változó használhatja a program szempontjából ugyanazon uniform változókat Ezeket mégis külön-külön kellene feltöltenünk stb., mert ugyanannak a uniform változónak más lesz a címe a különböző shader programokban A uniform block segítségével ezen segíthetünk (csak használjunk shared layout-ot)

Uniform block – a shaderben uniform BlobSettings { vec4 InnerColor; vec4 OuterColor; float RadiusInner; float RadiusOuter; };

Uniform block A uniform változók adatait tartalmazó puffer objektum a uniform buffer object A változókra hivatkozásnál elég az adattag nevét írni, nem kell prefixelni az UBO nevével (tehát pl. elég az InnerColor, nem kell BlobSettings.InnerColor)

Uniform block GLuint blockIndex = glGetUniformBlockIndex( programHandle, "BlobSettings"); GLint blockSize; glGetActiveUniformBlockiv( programHandle, blockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize); GLubyte * blockBuffer= (GLubyte *)malloc(blockSize);

Uniform block const GLchar *names[] = { "InnerColor", "OuterColor", "RadiusInner", "RadiusOuter" }; GLuint indices[4]; glGetUniformIndices( programHandle, 4, names, indices); GLint offset[4]; glGetActiveUniformsiv( programHandle, 4, indices, GL_UNIFORM_OFFSET, offset);

Uniform block GLfloat outerColor[] = {0.0f, 0.0f, 0.0f, 0.0f}; GLfloat innerColor[] = {1.0f, 1.0f, 0.75f, 1.0f}; GLfloat innerRadius = 0.25f, outerRadius = 0.45f; memcpy(blockBuffer + offset[0], innerColor, 4 * sizeof(GLfloat)); memcpy(blockBuffer + offset[1], outerColor, 4 * sizeof(GLfloat)); memcpy(blockBuffer + offset[2], &innerRadius, sizeof(GLfloat)); memcpy(blockBuffer + offset[3], &outerRadius, sizeof(GLfloat));

Uniform block GLuint uboHandle; glGenBuffers( 1, &uboHandle ); glBindBuffer( GL_UNIFORM_BUFFER, uboHandle ); glBufferData( GL_UNIFORM_BUFFER, blockSize, blockBuffer, GL_DYNAMIC_DRAW ); glBindBufferBase( GL_UNIFORM_BUFFER, blockIndex, uboHandle );

Layout qualifiers Befolyásolja, hogy egy adott változó tárolása miképp történik és egyéb dolgokat Általánosan így néz ki: layout(qualifier1, qualifier2 = value, ...) variable_definition Az értékek (value) egész típusúak kell legyenek (vagy 4.4-től felfelé fordításidejű egész konstans kifejezés)

Interface layout A vertex shader input interfészének és a VAO attribútumindexeknek az összerendelését a layout paranccsal is meg lehet tenni: Ekkor layout ( location = 0 ) vec4 pos; és glBindAttribLocation(prg,”pos”,0); ugyanazt csinálja (megfelelő előzmények mellett) ...csak előbb meg kell tanulni location-öket számolni :)

Location A memória absztrakt reprezentációja Rendkívül fontos, mert ez az alapja annak, hogy mik feleltethetőek meg egymásnak és mik nem! (Ugyanannyi location-t kell foglalniuk) Rule of thumb-ként: egy vec4 = 1 location De sajnos a location fajtájától függően ez változhat Továbbá hogy egy változó beleszámítson a location-ök számolásába aktívnak kell lennie (a shader eredményéhez hozzá kell járulnia)

Location Háromféle location van: Attribútum location: array pufferek és vertex shader input interface-ek közötti megfeleltetésre (vagyis a „VAO indexek”) Varying location: a különböző programozható fázisok közötti kommunikációra használt in és out változók location-jei Fragment output location: a fragment shader kimenetei változóinak a megfeleltetése a glDrawBuffers táblával

Location (OGL Insights)

Location A GL_MAX_VERTEX_ATTRIBS határozza meg az egy vertexhez tartozó location-ök maximális számát (legalább 16) Ha egy attribútum egy location (vagyis <= vec4), akkor ennyi attribútuma lehet egy vertexnek (nem pedig annyi, ahányszor vec4-nyi a foglalás!!!) A fragment shader kimeneteinek max száma a GL_MAX_COLOR_ATTACHMENTS segítségével kérhető le (legalább 8)

Location

Location A vertex shader input interface VAO-val egyeztetése (interface match) location alapú (=vec4 alapú), ezért alapesetben nagyon megengedő Ha egy attribútum vec3-ként van VBO-ban, de a shader vec4-ként definiálja, akkor automatikusan castolódik Méghozzá a vec4(0,0,0,1) alapértéknek megfelelően → ingyen DKR→homogén konverzió! De a double...

Location

Location A különböző shader fázisok közötti interface match location-ökön keresztül is elérhető Ekkor ez (.vert) layout(location = 0) out vec4 color; és ez (.frag) layout(location = 0) in vec4 diffuseAlbedo; egyezést fog adni

Layout qualifiers Vigyázzunk, hogy jól adjuk meg a location-öket! Mik lesznek itt a location-ök? struct MyData { vec3 field1; float field2[3]; }; layout (location = 0) out vec3 arr[3]; layout (location = ?) out MyData outDat; layout (location = ?) out mat4 trf;

Layout qualifiers Vigyázzunk, hogy jól adjuk meg a location-öket! Mik lesznek itt a location-ök? struct MyData { vec3 field1; float field2[3]; }; layout (location = 0) out vec3 arr[3]; layout (location = 3) out MyData outDat; layout (location = 7) out mat4 trf;

Layout qualifiers A fentieket felül lehet írni (nyilván nem úgy, hogy rátolod egy korábbi adattag location-jére): layout(location = 0) out Block { vec2 first; dvec4 second[3]; vec4 third; layout(location = 10) vec2 fourth; dvec4 fifth; layout(location = 8) dvec3 sixth; vec3 seventh; };

Layout qualifiers Uniform, shader storage object-ek és minden átlátszatlan típus (pl. sampler2D) binding indexét is meghatározhatjuk: layout(binding = 3) uniform sampler2D mainTexture; layout(binding = 1, std140) uniform MainBlock { vec3 data; };

Layout qualifiers Sőt, a uniform-ok location-jeit is: layout(location = 2) uniform mat4 modelToWorldMatrix; Ezután a glGetUniformLocation( prog, "modelToWorldMatrix"); biztosan 2-t ad vissza

Layout qualifiers Még azt is meg kell valahogyan határozni, hogy a memóriában hogyan vannak egymás után az adatok Lehet packed, shared, std140 és std430 Az std-k előnye, hogy nem kell egyesével lekérdezni az adattag offset-eket

Beépített változók Vertex shader - input in int gl_VertexID; in int gl_InstanceID;

Beépített változók Vertex shader - output out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; };

Beépített változók Tessellation control shader - input in int gl_PatchVerticesIn; in int gl_PrimitiveID; in int gl_InvocationID;

Beépített változók Tessellation control shader - output patch out float gl_TessLevelOuter[4]; patch out float gl_TessLevelInner[2]; out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; } gl_out[];

Beépített változók Tessellation eval shader - input in vec3 gl_TessCoord; in int gl_PatchVerticesIn; in int gl_PrimitiveID; patch in float gl_TessLevelOuter[4]; patch in float gl_TessLevelInner[2]; in gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; } gl_in[gl_MaxPatchVertices];

Beépített változók Tessellation eval shader - output out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; };

Beépített változók Geometry shader - input in int gl_PrimitiveIDIn; in int gl_InvocationID; in gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; } gl_in[];

Beépített változók Geometry shader - output out int gl_PrimitiveID; out int gl_Layer; out int gl_ViewportIndex; out gl_PerVertex { vec4 gl_Position; float gl_PointSize; float gl_ClipDistance[]; };

Beépített változók Fragment shader - input in vec4 gl_FragCoord; in bool gl_FrontFacing; in vec2 gl_PointCoord; in int gl_SampleID; in vec2 gl_SamplePosition; in int gl_SampleMaskIn[]; in float gl_ClipDistance[]; in int gl_PrimitiveID; in int gl_Layer; in int gl_ViewportIndex;

Beépített változók Fragment shader - uniform struct gl_DepthRangeParameters { float near; float far; float diff; }; uniform gl_DepthRangeParameters gl_DepthRange;

Beépített változók Fragment shader - output out float gl_FragDepth; out int gl_SampleMask[];

Fragment shader Tanultuk, hogy amennyiben a fragment shader nem változtatja a fragment mélységét, akkor early z culling-ot tud használni a GPU Viszont van lehetőségünk, hogy vállalást tegyünk, milyen módon változtatja a mélységértéket a fragment shader layout(depth_<condition>) out float gl_FragDepth <condition> \in {any, greater, less, unchanged }

Fragment shader A gl_FrontFacing hasznos tud lenni, ha a hátrafelé néző lapokat is ki akarjuk rajzolni, de megkülönböztetve az előrefelé nézőktől Csak kapcsoljuk ki a backface culling-ot, hiszen ha az aktív, akkor a vágásnál (geometry shader után) eldobja a rendszer a hátrafelé néző primitíveket

Szubrutinok Egyeseket nagyon csábítja a lehetőség, hogy über-shadereket csináljanak Amik hatalmas elágazásokkal kezdődnek, valamilyen uniform változó értékétől függően (pl. most hányadfokú polinom gyökeit keresem, ennek függvényében vagy megoldóképletet, vagy gyökkeresést használok) A szubrutinokkal ezek lesznek kiválthatóak – kicsit hasonlóak lesznek, mint a függvénypointerek C-ben

Szubrutinok Először definiáljuk a függvény szignatúráját subroutine vec4 colorRedBlue (); Aztán csinálunk variációkat a témára subroutine (colorRedBlue ) vec4 redColor() { return vec4(1, 0, 0, 1); } És subroutine (colorRedBlue ) vec4 blueColor() { return vec4(0, 1, 0, 1); }

Szubrutinok Felveszünk egy új uniform változót subroutine uniform colorRedBlue myRedBlueSelection; Aztán használjuk void main() { color = myRedBlueSelection(); gl_Position = pvm * position ; }

Szubrutinok GLuint routineC1 = glGetSubroutineIndex(p, GL_VERTEX_SHADER, "redColor"); GLuint routineC2 = glGetSubroutineIndex(p, GL_VERTEX_SHADER, "blueColor"); GLuint v1 = GlGetSubroutineUniformLocation( program_ID, GL_VERTEX_SHADER, "myRedBlueSelection");

Szubrutinok Ezután már csak a glUseProgram után és a kirajzolás előtt be kell állítani, hogy melyik megvalósításra mutasson a subroutine változó Ehhez a glUniformSubroutinesuiv hívást használjuk A megfelelő nevű szubrutin indexét glGetSubroutineIndex(prg, <shader>, „nev”) adja vissza

int maxSub,maxSubU,activeS,countActiveSU; char name[256]; int len, numCompS; glGetIntegerv(GL_MAX_SUBROUTINES, &maxSub); glGetIntegerv(GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS, &maxSubU); glGetProgramStageiv(p, GL_VERTEX_SHADER, GL_ACTIVE_SUBROUTINE_UNIFORMS, &countActiveSU);

for (int i = 0; i < countActiveSU; ++i) { glGetActiveSubroutineUniformName(p, GL_VERTEX_SHADER, i, 256, &len, name); glGetActiveSubroutineUniformiv(p, GL_VERTEX_SHADER, i, GL_NUM_COMPATIBLE_SUBROUTINES, &numCompS); int *s = new int[numCompS]; glGetActiveSubroutineUniformiv(p, GL_VERTEX_SHADER, i, GL_COMPATIBLE_SUBROUTINES, s); for (int j=0; j < numCompS; ++j) { glGetActiveSubroutineName(p, GL_VERTEX_SHADER, s[j], 256, &len, name); } delete s;

Shader binárisok Linkelés előtt megmondhatjuk, hogy a program bináris tárgykódja lekérhető legyen glProgramParameteri( programHandle, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE); Majd inkelés után, elmentéshez lekérjük a hosszát: glGetProgramiv(programHandle, GL_PROGRAM_BINARY_LENGTH, &bufSize);

Shader binárisok A bináris kódot és formátumát lekérjük GLenum binaryFormat; glGetProgramBinary(programHandle, bufSize, NULL, &binaryFormat, &buffer[0]); Egy programnak pedig így tudjuk odaadni std::vector buf(bufSize); glProgramBinary(prg2Handle, binaryFormat, &buf[0], bufSize);

Shader binárisok A bináris csak a linkelés pillanatában aktuális állapotát őrzi meg a programnak Tehát például a uniform változók az alapértelmezett kezdőértékeikkel lesznek kiírva Viszont nem platformfüggetlen a bináris kód Nem is eszközfüggetlen Sőt, ugyanazon a gép, ugyanazon eszközén sem biztos, hogy nem kell újrafordítani egy driverfrissítés után...

Shader binárisok

Shader binárisok Program binary formats are not intended to be transmitted. It is not reasonable to expect different hardware vendors to accept the same binary formats. It is not reasonable to expect different hardware from the same vendor to accept the same binary formats. Indeed, you cannot expect the cached version to work even on the same machine. Driver updates between when the data was cached and when it is reloaded can change the acceptable binary formats. Therefore, glProgramBinary​ can fail frequently. If you use this functionality, you must have a fallback for creating your shaders if the binary is rejected. https://www.opengl.org/wiki/Shader_Compilation#Binary_upload

Transform feedback

OpenGL több context-tel Lehetőség van arra, hogy több OpenGL context- et is létrehozzunk Sőt, ezek bizonyos erőforrásait meg is oszthatjuk egymással Arra figyeljünk, hogy a konkrét ablakozórendszertől függő „tegyük aktívvá X kontextet” eljárást hívjuk meg mindig, amikor a saját contextünket használnánk

OpenGL több context-tel Szálak esetén: Minden szálnak egyetlen aktív OpenGL context-je lehet egy adott pillanatban Viszont különböző időpontokban különböző context-ek is lehetnek aktívak a szálon belül Mivel a fő parancsfeldolgozónál úgyis sorba kell állni, ezért nem lesz sebességgyorsulás, hogy ha több szálon rajzolunk De ha egy szálon rajzolunk, a többin pedig adatot feltöltünk már rögtön más a helyzet!

OpenGL több context-tel - megosztás Az adatjellegű erőforrások megoszthatóak: VBO-k Index bufferek Shader programok (de vigyázz, magaddal viszed a uniform-ok bind értékeit) Textúrák PBO-k samplerek

OpenGL több context-tel - megosztás Állapot jellegű (vagy inkább tároló, összefoglaló) erőforrások nem oszthatóak meg: VAO FBO

OpenGL több context-tel - megosztás A megosztás előtt össze kell kapcsolni a két context-et Például Windows-on: glrc1=wglCreateContext(dc); glrc2=wglCreateContext(dc); wglShareLists(glrc1, glrc2); Ezzel összekapcsoljuk az erőforrásaik névtereit

OpenGL több context-tel - megosztás A közös névtér azt jelenti, hogy az erőforrások számlálói közös indexre kerülnek Azaz ha létrehozunk egy VBO-t glrc1-ben és 1-es az azonosítója, akkor ha glrc2-ben is létrehozunk egy VBO-t, akkor az ő azonosítója 2-es lesz (m_VBOid == 2)

Framebuffer Object

FBO A context-hez tartozik egy default framebuffer Ez az operációs rendszer adja át És az OS is kezeli (a felbontását, formátumát stb.) A default framebuffer (a fentiek miatt) GL-ből nem módosítható, nem törölhető Viszont lehetőség van az aktuális rajzolási parancsokat egy saját FBO-ba irányítani

FBO Logikailag a hátterében pixelek kétdimenziós tömbje áll A pixelekhez különböző adatok tartozhatnak, amik úgynevezett logikai pufferekben vannak: Szín(ek) – tehát ez akár több puffer is lehet Stencil Mélységi A default FBO-ban 4 puffer van: front left, front right illetve back left, back right

FBO pufferek A különböző logikai pufferekben tehát minden pixelhez tartoznak adatok Ezek az adatok valahány bitből állnak Ha úgy gondoljuk azt, hogy ezeket az attribútumokat egymásra pakoljuk a pixeleken, akkor a megfelelő attribútumok megfelelő bitjei úgynevezett bitplane-eket alkotnak [0,0]:b1 [1,0]:b1 [2,0]:b1 [0,0]:b0 [1,0]:b0 [2,0]:b0 pixel [0,0]:g1 [1,0]:g1 [2,0]:g1 bitplane [0,0]:g0 [1,0]:g0 [2,0]:g0 [0,0]:r1 [1,0]:r1 [2,0]:r1 [0,0]:r0 [1,0]:r0 [2,0]:r0

FBO A színkomponensek különbözőképp lehetnek tárolva (normalizált fix-pontos vagy lebegőpontos stb.) De minden komponenst (R, G, B, A) ugyanúgy kell eltárolni

Default és saját FBO A default FBO-t az OS adja Neki van a színpufferekből potenciálisan 4 változata ( {front|back}{left|right} ) - de implementációfüggő, hogy valójában mennyit ad (van-e right, van-e back) Az alkalmazásban tetszőleges számú saját FBO-t csinálhatunk, amik viszont néhány dologban mások: Pixel ownership teszten minden pixele átmegy Lehet különböző méretűek az attachment-be kerülő képek, de ilyenkor a legnagyobb közös részen túli darabok definiálatlanok

FBO létrehozása Először a glGenFrameBuffers segítségével le kell generálni a szükséges számú azonosítót Ezután a glBindFrameBuffer segítségével bind- olunk, ahol 2(+1) target lehetséges: GL_DRAW_BUFFER: a rajzolási parancsok ide írnak GL_READ_BUFFER: az olvasási parancsok innen olvasnak – megegyezhet az előzővel GL_FRAMEBUFFER: mindkettő (de másutt nem feltétlen!)

FBO specifikálása Az FBO különböző logikai puffereket kezel: Színpufferek (GL_COLOR_ATTACHMENT0- n) Mélységi puffer (GL_DEPTH_ATTACHMENT) Stencil puffer (GL_STENCIL_ATTACHMENT) Ezekre a szerepekre textúrák és úgynevezett renderbuffer-ek is hozzárendelhetőek

FBO attachment-ek Több FBO-nak is lehet ugyanaz a fizikai erőforrás (textúra vagy renderbuffer) egy attachment-je Az attachment általában egy 2D-s tömb Azaz mip-map-elt textúra esetén az egyik szint Cube map-nek az egyes lapjai külön-külön attachment-ek kellenek, hogy legyenek (mip- szintenként) Viszont lehet 3D-s dolgot is (cube map, texture array stb.), de ekkor a különböző elemek más- más layer lesznek

Renderbuffer Egyszerű adattárolásért felelős objektum Egyetlen képet tartalmaz egy belsőleg használható formátumon Szokásos módon kezelendő: glGenRenderbuffers glBindRenderBuffers Adatfeltöltés glRenderbufferStorage, glNamedRenderbufferStorage stb. segítségével glDeleteRenderBuffers

Renderbufer attach void glFramebufferRenderbuffer( enum target, enum attachment, enum renderbuffertarget, uint renderbuffer ) A target-en aktív FBO-nak az attachment csatolási pontjára kapcsolja a renderbuffer-t A renderbuffertarget csak és kizárólag GL_RENDERBUFFER lehet