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