OpenGL Compute Shader-ek Valasek Gábor
Compute shader OpenGL 4.3 óta része a Core specifikációnak Speciális shaderek, amikben a szokásos GLSL parancsok (és néhány új) segítségével általános számítási feladatokat végezhetünk Anélkül, hogy a grafikus szerelőszalagba bele kellene illesztenünk a feladatot
Compute space A feladatvégzés frekvenciáját egy absztrakt állapottér megadásával tudjuk meghatározni Hierarchikus az állapottér: Work item: a munkavégzés legkisebb egysége, egyetlen szál Work group: egy 1, 2 vagy 3 dimenziós tere a work item-eknek. Az egy workgroup-ba tartozó work itemek egy közös osztott memórián keresztül tudnak egymással kommunikálni Ez a legkisebb egység, ami önállóan végrehajtható Nincs semmilyen garancia arra, hogy a workgroup-on belül milyen sorrendben hajtja végre a GPU a work itemeket. Compute space: work group-ok 1, 2 vagy 3 dimenziós tere. Nincs garancia a work group-ok végehajtási sorrendjére.
Compute space work item
Compute space work group - 1D
Compute space work group - 2D
Compute space work group - 3D
Compute space compute space - 1D
Compute space compute space - 2D
Compute space compute space - 2D
Compute space
Compute space gl_workgroupid.xyz
Compute space gl_workgroupid.xyz gl_localinvocationid.xzy
Compute space gl_workgroupid.xyz gl_globalinvocationid.xyz gl_localinvocationid.xzy
Limitek Workgroup különböző dimenzióinak: glgetintegeri_v(gl_max_compute_work_group_size,dim, &maxsizebydim) Work itemen maximális száma, per dimenzió: glgetintegeri(gl_max_compute_work_group_invocations,&maxinvoc)
Beépített változók a Compute Shaderben in uvec3 gl_numworkgroups: a workgroup-ok xyz dimenzióit határozza meg - a gldispatchcompute hívás paraméterei ezeken keresztül érhetőek el. const uvec3 gl_workgroupsize: egyetlen workgroup dimenzióinak leírása. Az előzőtől különböző dimenziójú is lehet. Ezeket a shader elején állítjuk be a layout-ban local_size_*-gal. in uvec3 gl_workgroupid: ez a változó tárolja, hogy az adott work item milyen indexű workgroup-ba tartozik a compute space-en belül. A work group összes work item-jének ugyanaz ez az értéke.
Beépített változók a Compute Shaderben in uvec3 gl_localinvocationid: a work item work group-on belüli indexei. in uvec3 gl_globalinvocationid: a work item teljes compute space-beli indexe. Számítása gl_globalinvocationid = gl_workgroupid * gl_workgroupsize + gl_localinvocationid in uint gl_localinvocationindex: az előző 1D-s verziója, azaz gl_localinvocationindex = gl_localinvocationid.z*gl_workgroupsize.y*gl_workgroupsize.x + gl_localinvocationid.y*gl_workgroupsize.x + gl LocalInvocationID.x
Globális változók Compute shaderben a globális változók storage qualifier-e shared Viszont ezek olvasását kézzel kell szinkronizálni!
Kommunikáció shaderek és a program között Textúrák sima sampler2d-ként: csak olvasható Image Load Store: írható, olvasható, sőt, atomi műveletek is vannak! Uniform Buffer Object: több shaderben is közös uniform változók külön tárolóba kiszervezése. A minimális kötelező méretük 16KB. Csak olvasható. Shader Storage Buffer Object: írható/olvasható memória, aminek legalább 16MB-nek kell lennie, szintén atomi műveletekkel.
Textúrák - létrehozás Egy textúra több képből áll, attól függően, hogy hány mip-map réteg van definiálva A textúrák létrehozása: glgentextures(n, &address): n darab textúraazonosító deklarása, amelyek kliens-oldali azonosítóit az address címtől kezdve visszaadja a függvény. Egy textúrát (és bármilyen OpenGL-es erőforrás kliens-oldali) azonosítóját egy GLuint-ban tárolja a rendszer glbindtexture(gl_texture_2d, id): egy deklarált textúra azonosító használatba vétele. Első ilyennél egyúttal a textúra típusa is eldől (GL_TEXTURE_2D például egy 2D-s textúra, egy kép lesz). Az ezután kiadott GL_TEXTURE_2D-t érintő utasítások az id textúrára vonatkoznak majd. glteximage2d(...): adott mip-level adattárolásának definiálása. gltexparameteri(target, param, value ): aktuális target szerepű textúra param-jának beállítása. Legalább a min és magfiltereket meg kell adni, hogy definiálva legyen a textúra.
Textúrák - shader oldal A shaderben egy 2D-s textúrát egy sampler2d típusú objektum azonosít A sampler* típus példányait a texture eljárással lehet mintavételezni, ami normalizált, azaz [0,1]-beli u, v textúrakoordinátákat vár: uniform sampler2d img; vec4 col = texture(img, vec2(u,v) ); A sampler* egy úgynevezett átlátszatlan (opaque) típusú objektum, azaz semmit sem tudni a belsejéről A storage qualifier-e uniform kell legyen - értéket sem adhatunk neki a shaderben
Textúrák - shader oldal gvec texturelod(gsampler sampler, vec texcoord, float lod ): explicit a lod réteg mintavételezése de lod lehet nem egész szám (ekkor a mip-map-es mintavételezés szerint járunk el) gvec4 texturegather(gsampler sampler, vec texcoord, int comp ): szűrés nélküli hozzáférés gvec texelfetch(gsampler sampler, ivec texcoord [, int lod ] [, int sample ]): egész koordinátás direkt mintavételezés int texturequerylevels(gsampler sampler ): a paraméterben adott samplerre kötött textúra mip-leveljeinek száma (4.3 óta) gvec textureoffset(gsampler sampler, vec texcoord, ivec offset [, float bias ]): offset-tel odébb mintavételez, ahol offset egész texelkoordinátájú
Textúrák - kliens oldal Több lépésből áll a shadernek átadni egy textúrát mintavételezésre: glactivetexture(gl_texture0 + i): az i-edik textúramintavételező aktiválása. Legalább 80 kell, hogy legyen belőlük a specifikáció szerint. glbindtexture(gl_texture_2d, img_id): aktiválja az img_id-t és a legutóbb aktivált mintavételezőt is ráállítja gluniform1i( loc_img_sampler, i): az i-edik mintavételezőhöz kapcsolja a loc_img location-ön lévő sampler2d-t Ezt lehet kézzel is beállítani előre, a shaderben: layout (binding = i) sampler2d img;
Image Load Store A kliens oldalon olyanok, mint a sima textúrák Shader oldalon image2d és általában image* uniform változók, lista itt Shaderben egy ILS változó definiálásának szignatúrája [access mods] layout ([layout spec]) image2d img; ahol Access mods: hozzáférési módosítók Layout spec: olvasási/tárolási módosítók és a szokásos egyebekt (binding)
Image Load Store - access modifiers Több access qualifier is van, amivel az írás-olvasás vezérelhető: coherent: a sikeresen véget ért írások láthatóak lesznek már ugyanabban feldolgozási blokkban is, azaz a többi shader invocation-ben. Az írások/olvasások sorrendje definiálatlan, így a shader kódban kézzel kell a szükséges szinkronizálásokat megtenni. volatile: alapesetben feltételezi a rendszer, hogy az erőforrás csak szinkronizációk után változik. Ha viszont volatile, akkor jelezzük, hogy ezt az erőforrást más is írhatja (akár egy másik kernek), így amikor olvassuk, mindig be kell tölteni az aktuális értékeket a memóriából. Ugyanígy, ha írjuk, akkor azonnal írást küldünk. restrict: csak ezen a példányok keresztül módosul ez a buffer. SOHA ne hagyd ki, ha ez igaz, különben LHS-ed lesz. const readonly/writeonly
Szinkronizáció Szinkronizációhoz: memorybarrieratomiccounter: várunk, amíg láthatóvá nem válnak az atomic counterek változásai. memorybarrierimage: Image Load Storera memorybarrierbuffer: SSBO-ra memorybarriershared: compute shader-ek shared változóira memorybarrier: minden, ami eddig volt. Nem kell hozzá OGL 4.3. groupmemorybarrier: minden ami fent volt, de csak a work group-on belül - ez egy kisebb szintű szinkronizáció.
Image Load Store A formátumban meghatározzuk, hogy a shader hogyan értelmezi olvasáskor és íráskor a textúra texeleit - ez különbözhet a tárolástól! Size layout: a formátum meghatározásának rövidebb verziója: size1x8 : R8I, R8UI size1x16 : R16I, R16UI size1x32 : R32F, R32I, R32UI size2x32 : RG32F, RG32I, RG32UI size4x32 : RGBA32F, RGBA32I, RGBA32UI Használata: layout (sizenxm) image2d img;
Folytatás: http://cg.elte.hu/~msc_cg/gyak/10/ogl_ 02.odp (60. slide)
Memory barrier Memóriaműveletek közötti relatív sorrend definiálására glmemorybarrier: A paraméterben meg kell adni azt, hogy milyen típusú műveletet szeretnék végezni a barrier után. A GPU ennek megfelelően várakoztatja majd az utána jövő utasítások végrehajtását.