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.
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:
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:
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:
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.
![]() 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.
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.
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.
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:
Höfö:
|