51. fejezet - Core és compatibility profile


Azt mondják, hogy az OpenGL azért rossz, mert folyamatosan toldozgatják: ugyanarra a funkcionalitásra rengeteg lehetőség van, amik névben se nagyon különböznek, így könnyű összekeverni. Külön probléma ez, amikor olyan funkciókról van szó, amiknek használatáért évek óta megkövezés jár (glBegin, glEnd, display listák, vertex array, stb.) és az új generációt nem ezen ortodox módszerekre kéne megtanítani.

Gondolták erre a Khronos nevű alkoholelvonó-terápiás csoportban, hogy a 3.0 verzótól kezdve ne lehessen ezeket a régi dolgokat használni, mert nem hatékonyak, meg amúgyis propagálni kell a shaderek használatát. Nem elhanyagolható mennyiségű feles érvelés után végül arra jutottak, hogy régi játékokkal is szeretnénk még játszani, úgyhogy a régi funkcionalitást is meg kell tartani, de az újat ajánlott használni. Ez utóbbit illették meg a nem túl ötletes core profile névvel, míg a régi dolgokat támogató változat a compatibility profile lett.

A probléma az, hogy azóta eltelt néhány év, és bizony a régi funkciók kezdenek hardver szinten is kihalni (ha még elérhető, az jó eséllyel emuláció). Azaz ott lóg a levegőben, hogy a compatibility profile teljesen megszűnik (pl. az új tableteken egyáltalán nincs). Bár Windows-on erre kevesebb az esély, az Apple szereti a deprekált dolgokat időről időre kidobálni (mondjuk úgy hetente...), így valószínű, hogy az OpenGL-el is meg fogja ezt tenni hamarosan.

Ellentmondás

Ahhoz, hogy core profile contextet csinálj, explicit meg kell mondani a kivezetett API-nak, hogy te most olyat akarsz. Ha van egyáltalán. Azt persze addig nem tudod, hogy van-e, amíg nem csináltál egy normál contextet. Ez ilyen Windows-os logika, mint amikor a víziló úgy rak fészket, hogy a medence egyik végéből a másikba hordja a vizet.

CODE
HWND dummy = CreateWindow( "TestClass", "Dummy", WS_CLIPCHILDREN|WS_CLIPSIBLINGS, 0, 0, 100, 100, 0, 0, GetModuleHandle(0), 0);

Ha nem mutatod meg senkinek, akkor olyan, mintha nemis létezne. Erre a szokásos módon létre kell hozni egy teljesen veszélytelen GL contextet, amitől aztán le lehet kérdezni, hogy van-e core profile (3.2-től lehet csak). Ami természetesen az OGL jó szokásához híven egy (illetve két) extension... Aminek a lekérdezéséhez szintén egy extension kell (a glGetString nem adja ide).

CODE
WGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL; WGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = (WGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress("wglGetExtensionsStringARB"); if( wglGetExtensionsStringARB ) { if( IsSupported(wglGetExtensionsStringARB, hdc, "WGL_ARB_create_context") && IsSupported(wglGetExtensionsStringARB, hdc, "WGL_ARB_create_context_profile") ) { wglCreateContextAttribsARB = (WGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB"); } }

Ezek után a dummy ablakot és contextet lehetne is törölni, bár akkor megeshet, hogy a lekérdezett pointer azonnal invalid lesz, de eddig ilyen nem történt (a DLL mágikus módon betöltve marad).

CODE
if( wglCreateContextAttribsARB ) { int contextattribs[9] = { 0x2091, // WGL_CONTEXT_MAJOR_VERSION_ARB major, 0x2092, // WGL_CONTEXT_MINOR_VERSION_ARB minor, 0x2094, // WGL_CONTEXT_FLAGS_ARB 0x0002, // WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x9126, // WGL_CONTEXT_PROFILE_MASK_ARB 0x00000001, // WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0 }; hrc = wglCreateContextAttribsARB(hdc, NULL, contextattribs); wglMakeCurrent(hdc, hrc); }

Ennek mindenki nagyon örül, az első crash-ig, ami nem rosszabb helyen fog jönni, mint a glGetString(GL_EXTENSIONS) hívásban. Az ugyanis nincs többé (persze minden máshoz még mindig ezt kell használni). Az extension-öket mostantól a hihetetlen kreativitásról tanúbizonyságot tevő glGetStringi függvénnyel kell lekérdezni. Ami mi más is lehetne, mint egy extension, hát úgyis annyira szeretjük nem?

CODE
typedef const GLubyte* (APIENTRY *GLGETSTRINGIPROC)(GLenum name, GLuint index); GLGETSTRINGIPROC glGetStringi = (GLGETSTRINGIPROC)wglGetProcAddress("glGetStringi");

Ennek használata annyiban más, hogy az extension sorszámához megadja a nevét. A sorszámokat persze senki nem tudja (ehhez el kell látogatni az opengl.org-ra és leszedni a glcorearb.h headert). Kevésbé türelmes programozóknak javaslok egy ciklust 0-tól glGetIntegerv(GL_NUM_EXTENSIONS, &numext);-ig.

A másik jó hír, hogy a függvénypointereket is máshogy kell lekérni (ARB és EXT szuffix nélkül), de ez nem minden kártyán okoz problémát (nekem Intel HD 4000-en jött elő).


Mac-en jobb a helyzet

Ez kivételesen egy jó pont, hacsak nem rontod el (nekem sikerült). Ugyanis nem mindegy, hogy milyen attribútumokat adsz meg. Külön érdekesség, hogy ha ilyet csinálsz, akkor azt fogja mondani magáról, hogy 3.2-t tud (jelenleg ez a maximum Mac-en), egyébként 2.1-et mond (pedig a hardver tudna 4.1-et is akár).

CODE
@interface GLViewController : NSOpenGLView @end @implementation GLViewController - (void)awakeFromNib { NSOpenGLPixelFormatAttribute attributes[] = { NSOpenGLPFAColorSize, 24, NSOpenGLPFAAlphaSize, 8, NSOpenGLPFADepthSize, 24, NSOpenGLPFAStencilSize, 8, NSOpenGLPFADoubleBuffer, NSOpenGLPFAAccelerated, NSOpenGLPFANoRecovery, NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, 0 }; NSOpenGLPixelFormat* format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:format shareContext:nil]; [self setOpenGLContext:context]; [context makeCurrentContext]; // ... } @end

Viszont van pár dolog, amiben az OS X szigorúbb, konkrétan az extension nevek közül minden olyan ki lett dobva, ami a core profile-ba bekerült (pl. ARB_vertex_buffer_object), szóval hiába keresed.


Megint nem rajzol, cserébe elszáll

A core profile-al bizonyos eddig ritkán használt dolgok kötelezőek lettek:

  • vertex buffer
  • shaderek
  • vertex array object
Amit viszont nem szabad csinálni:
  • 0-t bindolni buffer, textúra vagy shader névnek (ezt mondjuk még a Mac is elnézi, de a gDebugger nem)
  • régi GLSL szintaxist használni
A kötelező dolgok mindenhol kötelezőek, a tiltott dolgok Windows-on tipikusan működnek, Mac-en GL_INVALID_OPERATION-t dobnak. Én ebben a Mac-nek adok igazat, úgyhogy tessék betartani a szabályokat.

Na de akkor most megmutatom, milyen remek dolog ez a VAO (nagyon nem keverendő a vertex array-el!). Ez az objektum kvázi egy batch-hez szükséges dolgokat fogja össze, úgy mint:
  • vertex buffer
  • index buffer
  • vertex layout
  • instancing infó
Kódban ez valahogy így néz ki (az inicializáló részben!):

CODE
glGenVertexArrays(1, &vertexdecl); glBindVertexArray(vertexdecl); { glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexbuffer); glEnableVertexAttribArray(attrib_Position); glEnableVertexAttribArray(attrib_Normal); glVertexAttribPointer( attrib_Position, 3, GL_FLOAT, GL_FALSE, sizeof(CommonVertex), 0); glVertexAttribPointer( attrib_Normal, 3, GL_FLOAT, GL_FALSE, sizeof(CommonVertex), (const GLvoid*)(3 * sizeof(float))); } glBindVertexArray(0);

A VAO-t mindig első bindoláskor lehet definiálni, utána már nem fog változni az állapota, bármit is csinálsz. A kódból kiderül két fontos dolog: ez a fajta VAO függ a vertex buffertől is és a shadertől is (attribútumok). A shadertől való függést meg lehet úgy szüntetni, hogy a glBindVertexAttribLocation() függvénnyel explicit megmondod, hogy melyik attribútum hol legyen. A baj csak az, hogy hiba esetén nincs fallback lehetőség... Az index buffert nem kötelező itt megadni, úgy is meg lehet, hogy rajzoláskor a VAO után bindolod be, de én ezzel vigyáznék.

CODE
glBindVertexArray(vertexdecl); // NOTE: ide át lehet rakni az index buffer bindolást glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, 0); glBindVertexArray(0);

A VAO nyilvánvaló előnye, hogy a driver optimálisabban tudja kezelni az állapotokat, illetve órajelet is spórol. Állítólag 4.3-tól van lehetőség az attribútumok és a VBO teljes szétválasztására, de ezt nem tudtam kipróbálni.


GLSL szintaxis

Windowson működik a régi fajta is, Mac-en viszont nem (és szerintem Windows-on is driverfüggő). Hihetetlen változásokra azért nem kell számítani:

  • kötelező megadni a #version makrót és ez kell legyen az első
  • FFP-re vonatkozó beépített dolgok megszűntek (ezt egyébként se illik használni jó régóta)
  • attribute helyett in
  • varying helyett out és in
  • textúrából olvasás a texture függvénnyel (2D és Cube szuffix megszűnt)
  • gl_FragColor és gl_FragData[n] megszűnt
A fragment colort mostantól külön kell hozzábindolni egy output regiszterhez a glBindFragDataLocation() függvénnyel. Ha nem bindoltál be semmit, akkor nem definiált, hogy mi történik (de általában megoldja a driver).

CODE
#version 150 in vec3 my_Position; in vec3 my_Normal; uniform mat4 matWorld; uniform mat4 matWorldInv; uniform mat4 matViewProj; uniform vec4 lightPos; uniform vec4 eyePos; out vec3 wnorm; out vec3 vdir; out vec3 ldir; void main() { vec4 wpos = matWorld * vec4(my_Position, 1); ldir = lightPos.xyz - wpos.xyz; vdir = eyePos.xyz - wpos.xyz; wnorm = (matWorld * vec4(my_Normal, 0)).xyz; gl_Position = matViewProj * wpos; }
CODE
#version 150 in vec3 wnorm; in vec3 vdir; in vec3 ldir; out vec4 outColor; void main() { vec3 n = normalize(wnorm); vec3 l = normalize(ldir); vec3 v = normalize(vdir); vec3 h = normalize(v + l); float d = clamp(dot(l, n), 0.0, 1.0); float s = clamp(dot(h, n), 0.0, 1.0); s = pow(s, 80.0); outColor.rgb = vec3(d) + vec3(s); outColor.a = 1.0; }

Core profile-al ez a minimális verzió (macen ennél kisebb le se fordul), és tud geometry shadert is.


Konkrét megvalósítás az engine-ben

Először úgy gondoltam, hogy a gl2 implementációt bővítem ki úgy, hogy menjen core profile-al is, de rövid idő alatt kiderült, hogy ez nem jó ötlet, bonyolult lesz tőle a kód. Másrészt ez a fajta implementáció inkább az ES-re hasonlít, mint a GL-re (de annál sokkal többet tud), úgyhogy végülis leválasztottam gl4 néven.

A probléma világos: három nagyon hasonló implementáció (gl2, gl4, es2), mindegyik több ezer sor... Ez hosszú távon nem kifizetődő a redundancia miatt. Ezért azt találtam ki, hogy a három implementációból amit csak lehet összevonok egy common_gl modulba (és nagyon sok mindent össze is lehet vonni). Kezdeném rögtön egy zanzásított UML diagrammal:

uml1

Ami a cikk szempontjából fontos, az a graphics tier, de kiemelném a kapcsolatát a GL context-el, az ugyanis nem ebben a részlegben jön létre (hiszen platformfüggő), hanem a platform tier-ben. A kontext létrehozásakor meg lehet adni egy típust (core, compatibility vagy embedded), ami alapján megpróbálja megcsinálni. Ha nem sikerül neki, nem történik fallback, ezt felül kell lekezelni (a qGame lekezeli). Az embedded profile az ES-t szimulálja desktopon, EGL-el (ilyen amúgy nem létezik).

Megjegyezném, hogy a GL implementációk nincsenek context típushoz kötve, sőt a context létrehozása nem garantálja, hogy használni is fogja valaki. De persze egy adott GL implementáció csak a neki megfelelő contexttel fog normálisan működni.

Látható, hogy az extension manager a közös részben van, pedig a context létrehozásakor szükség van rá (de csak windowson); ezért van ott egy dependency nyíl. Az extension-öket mindig meg lehet tőle kérdezni, de az ő magánügye, hogy honnan kérdezi le. Például core profile esetén a legtöbbet joggal true-ra állítja.

A qGLMaps osztály tartalmazza az absztrakt tokenek opengl-re való fordításait (pl. textúra cimzési módok). Annak ellenére, hogy ez is különbözhet a speciális implementációkban, a közös részbe vettem. Ami például hiányzik ES-ben, azt #define-oltam valami értelmes létező tokenre, vagy egy error tokenre. Amennyiben ez nem oldható meg, akkor a specializált device felülírja (pl. luminace formátumok nincsenek GL4-ben). Bár ez kicsit nehezíti a követhetőséget, az absztrakt felület felé biztosítani tudja a transzparenciát (az alsó részt meg úgyse adod oda akárkinek).

A feltüntetett qGLDevice az említett közösített implementációja a háromféle GL device-nak. Mint mondtam az ábra zanzásított, helyhiány miatt; hasonlóan kell elképzelni a többi osztályt is (mesh, material, texture, cubetexture, offscreentarget).

Az alábbi táblázatban összefoglaltam néhány dolgot ami az egyes GL implementációkban speciális. Ahol ki van húzva, ott a közös implementáció értendő. Ha a közös van kihúzva, akkor mindenhol speciális.

Feature neve Common GL2 GL4 ES2
Mesh feltöltés írható/olvasható - - csak írható
Rajzolás - fallback FFP-re, ha szükséges csak shaderrel + VAO csak shaderrel
Fixed function pipeline - vertex array, glLightfv, glMaterialfv shader emuláció shader emuláció
Vertex layout kezelés az engine által adott mesh definition-ben - vertex array object-ben -
Shaderek linkelése attribok a driverre bízva - attribok explicit elhelyezve -
Sysmemből rajzolás mesh a shared memory-ban; rajzoláskor lock - - -
Aszinkron olvasás rendertargetből - pixel buffer object pixel buffer object -
MSAA - glBlitFramebuffer glBlitFramebuffer platformfüggő

Shaderekkel nem kellett sokat törődnöm, ugyanis saját shader fordítóm van (ld. korábbi bejegyzés). Annyit kellett bővíteni rajta, hogy explicit force-olni lehessen egy adott shader model-t.


Teljesítmény és stabilitás

A cikk írásának pillanatában Mac-en a teljesítmény drasztikusan, kb. 10-szer rosszabb. A problémát beírtam a stackoverflow-ra, és az Apple-nek is jelentettem.

Windows-on végeztem egy kis teljesítmény tesztet gDebugger-el. A felbontás minden esetben 1360x768. A kártya típusa Asus ENGTS 250 (GeForce 9800 GTX).

Teszt program FPS (comp) Frame time (comp) GL calls (comp) FPS (core) Frame time (core) GL calls (core)
HDR demo: pixel shader intenzív, sok FBO, dynamic VBO ~190 ~5.0 ~1100 ~190 ~5.0 ~700
Caustics demo: nagyon shader intenzív 66 15.4 940 66 15.2 630
BIMx-es teszt program: komplex geometria (7 millió poligon) 18 53 12300 16 58 4523

Az eredmény meglepő. Az látszik, hogy a core profile kb. feleannyi GL hívással se tudja mindig azt a teljesítményt hozni, mint a compatibility profile. Azt azért megszavazhatom, hogy elenyésző a különbség, de ez alapján a VAO-nak semmi értelme nincs.

pic1 pic2 pic3



Summarum

Megmutattam hogyan lehet core profile contextet létrehozni windows-on és mac-en. Összefoglaltam a core profile-ra való áttérés lépéseit és buktatóit. A cikk végén még egyszer összeszedném a fontosabb változásokat:

  • FFP megszűnt
  • VBO kötelező
  • VAO kötelező
  • GLSL 1.5 szintaxis kötelező
  • 0-t nem lehet bindolni bufferre és textúrára
  • luminance formátumok megszűntek (helyette GL_R8 és társai)
  • GL_CLAMP megszűnt
  • függvénypointereket ARB/EXT nélkül kell lekérni
A kód letölthető itt.


Höfö:
  • Készülj fel a következő GL verzióra, mert jó eséllyel megint dobhatod ki a kódod!

back to homepage

Valid HTML 4.01 Transitional Valid CSS!