original in en Miguel A Sepulveda
en to nl Guus Snijders
Dit artikel gaat verder met onze serie over GLUT, de GL Utility Toolkit van Mark Kilgard voor OpenGL applicaties. Zoals genoemd in ons vorige artikel (Vensters en Animaties), is GLUT een erg interessante en bruikbare toolkit voor iedere OpenGL ontwikkelaar omdat het je toestaat portable (overdraagbare) code te schrijven. GLUT schermt de ontwikkelaar af voor de details van de onderligende windows manager en GUI interface.
GLUT is verdeeld in een aantal subAPIs. In dit artikel zullen we de Windows Management subAPI. Zoals de naam al aangeeft, zorgt deze voor taken die gerelateerd zijn aan de vensters die worden gebruikt voor je OpenGL applicatie: creëren, vernietigen, minimaliseren van een venster; pushing, popping, verbergen, verplaatsen; en het instellen van titels, posities, enz...
Hier is een volledige lijst van de functies die ondersteund worden door het windows management in GLUT (sinds versie 3.6):
int glutCreateWindow(char *name) | Creëert een nieuw top-level venster |
int glutCreateSubWindow(int win, int x, int y, int breedte, int hoogte) |
Creëert een sub-venster |
void glutSetWindow(int winId) | Zet het venster met ID winID als huidig venster |
int glutGetWindow(void) | Vraag de identificatie op van het huidige venster |
void glutDestroyWindow(int winId) | Vernietigd het venster met het opgegeven winID |
void glutPostRedisplay(void) | Verteld de GLUT gebeurtenis processor dat het huidige venster opnieuw weergegeven dient te worden |
void glutSwapBuffers(void) | Wisselt (swap) de buffers voor het huidige venster om |
void glutPositionWindow(int x, int y) | Vraagt een verandering aan voor de positie van het venster |
void glutReshapeWindow(int breedte, int hoogte) | |
void glutFullScreen() | Vraagt om het huidige venster op volledig beeld weer te geven |
void glutPopWindow(void) void glutPushWindow(void) |
Push of Pop het huidige venster relatief aan de anderen in de stack |
void glutShowWindow(void) void glutHideWindow(void) void glutIconifyWindow(void) |
Geef weer, verberg of minimaliseer het huidige venster |
void glutSetWindowTitle(char *name) void glutSetIconTitle(char *name) |
Geeft de titel balk voor venster of geminimaliseerde venster |
Het gebruik van de meeste van de bovenstaande functies is erg simpel. Sommige zijn besproken in ons eerste artikel: glutPostRedisplay, glutCreateWindow, glutPositionWindow, glutSwapBuffers,..enz. Andere zijn, ondanks dat ze nieuw zijn, eenvoudig te begrijpen en de bovenstaande beschrijvingen vertellen ongeveer wat ze doen, zoals glutSetIconTitle, glutFullScreen, ...enz. Het gebruik van sub-Windows is echter niet zo eenvoudig, zodat we besloten om je een simpel voorbeeld te geven en de details van de implementatie te bespreken.
Hier is de bron code voor een kleine OpenGL-GLUt demo (example1.c, Makefile). Het doel is om je te laten zien: (a) Hoe een subwindow te behandelen, (b) Hoe je je toetsenbord kan gebruiken voor interactie met je OpenGL applicatie, (c) Hoe tekst te renderen in een OpenGL venster.
(Print alsjeblieft de bron code van uit, of zorg dat je hem bij de hand hebt terwijl we verder gaan met de bespreking.)Laten we eerst eens kijken naar de main() functie. Deze start als iedere andere GLUT applicatie met initialisaties: de opdrachtregel opties parsen, de weergave modus selecteren en positie en afmetingen van het initiële venster zetten. Daar onze applicatie meer dan een venster gaat gebruiken, moeten we de integer window ID, geretourneerd door het glutCreateWindow statement, oplsaan. De variabele winIdMain is een window handle. Dan is het tijd om de callback functies op te zetten voor de gebeurtenissen die we willen associëren met het venster winIdMain; er worden meerdere callback functies gedefinieerd en deze vervullen alle een taak op het main window: een weergave functie (mainDisplay) die de scene tekent, een reshape (veranderen van de afmetingen) functie (mainReshape) welke transformaties van het window frame afhandeld, keyboard om de acties die via het toetsenbord worden getriggerd af te handelen en idle voor het beheren van de animatie als er geen andere gebeurtenissen wachten. (zie Vensters en Animaties voor een gedetailleerdere beschrijving van de rol van idle;
Het eerste belangrijke ding om te weten, is dat in een GLUT applicatie er maar een idle callback functie kan zijn. De idle functie is globaal voor alle vensters in de applicatie. Let hier dus op bij het ontwerpen van idle() functies, deze moeten het verversen van alle vensters en subvensters in je applicatie voor hun rekening nemen.
Vervolgens komt in de code het creëren van een sub-window (winIDSub). Om een sub-venster te maken, moet je het ID van het bovenste (top level) venster geven, in ons geval winIDMain, de x en y coördinaten in pixels voor het sub-venster, relatief aan de interne coördinaten van winIDMain, en de breedte en hoogte in pixels van het gevraagde sub-venster. Na het creëren van het sub-venster retourneert GLUT een handle aan ons programma en zijn we klaar om de gewenste callback functies voor winIDSub op te zetten. In onze demo zetten we twee callback functies: een display (subDisplay) en een reshape (subReshape).
Wanneer GLUT een subvenster opent, voorziet het deze van een volledige OpenGL context. Er treedt dan wel een beperking van de performance op omdat de driver van de grafische kaart het geheugen gebied voor ieder venster in verschillende stappen moet verversen. Door de onafhankelijke OpenGL contexts heeft ieder venster bijvoorbeeld wel een eigen coördinaten systeem. In ../../common/March1998/example1.c worden de coördinaat systemen respectievelijk in mainDisplay() en subDisplay() opgegeven. Bestudeer deze beide weergave functies, ze zijn vrij eenvoudig en als je ons january artikel over OpenGL hebt gevolgt ("Eenvoudige Polygoon Rederinging"), zul je geen problemen ondervinden bij het begrijpen ervan.
De functie mainDisplay() tekent een driehoek met Rode, Groene en Blauwe vectoren. OpenGL interpoleert kleuren tussen de drie vectoren om de polygoon te vullen. Nog voor het renderen van de driehoek hebben we een glRotate statement toegevoegd die de driehoek rond zijn z-as roteerd (loodrecht op het venster), de rotatie hoek (spin) wordt langzaam verhoogd in idle() om de illusie van een draaiend figuur te creëren.
Wat betreft de weergave functie die is geassocieerd met winIdSub, deze is ook recht-toe-recht-aan. Eerst schoont het de achtergrond op met een grijze kleur, dan tekent het een groene rand om het subvenster en rendert tenslotte wat tekst. Hoe de tekst rendering werkt onder GLUT, zullen we later bekijken. Voor het moment is het voldoende op te merken dat glRasterPos2f(x,y) de positie aangeeft waar getekend gaat worden, merk ook op dat de gebruikte x, y coördinaten relatief zijn aan het coördinaten systeem van het subvensters (gedefinieerd in subReshape()).
Het subvenster doet dienst als tekst bord voor data die uit de animatie komt. Het is een malle applicatie; we zouden gewoon het tekst bord op het hoofdvenster kunnen tekenen en zo het zelfde resultaat (zelfs efficiënter) bereiken. Onder bepaalde omstandigheden zijn er redenen om een subvenster voor de tekst uitvoer te gebruiken. Als bijvoorbeeld de animatie in 3D is met lichten en omgevingseffecten, wil je waarschijnlijk niet dat je tekst wordt vervormd met irritante lichten, perspectief effecten, schaduwen, mist, enz. Onder deze omstandigheden is een subvenster handig, omdat deze volledig geïsoleerd is van de 3D animatie.
Er is een cruciaal verschil tussen de reshape callback functie voor een bovenliggend venster en voor een subvenster. Als er een callback functie wordt geactiveerd, wordt alleen de reshape callback functie voor het bovenliggende venster gestart, in ons voorbeeld dus mainReshape(). We moeten de reshape callback functie subReshape vanuit mainReshape aanroepen. Dit is duidelijk omdat de locatie en vorm van subvensters gebonden zijn aan de grootte en vorm van hun bovenliggende vensters. Als je dus nu de code leest, zie je dat we eerst de projectie matrix voor het bovenliggende venster instellen en vervolgens overschakelen op het winIDsub subvenster en dan de reshape functie van dit subvenster aanroepen met de breedte en hoogte relatief aan winIDMain.
Het was al eerder gemeld dat de idle() callback functie alle bovenliggende en subvensters in de OpenGL applicatie moet updaten. In ons voorbeeld, update idle() eerst de status variabelen van de animatie (time en spin) en dan verzoekt het de herweergave van zowel de hoofd- als de subvensters.
Ik heb twee actieve toetsen toegevoegd aan het programma. Door op de "i"-toets te drukken kun je het tekst bord in- en uitschakelen en met de "q"-toets kun je de applicatie afsluiten. Probeer ze :)
Iedere keer dat je een toets op je toetsenbord indrukt, registreert GLUT's gebeurtenis-verwerkings driver een toetstenbord gebeurtenis. Deze gebeurtenissen (events) worden afgehandeld door de callback functies voor het toetsenbord. In principe heeft ieder venster zijn eigen callback functie. Als de muis zich op een locatie (x, y) binnen een bepaald venster (of subvenster) is en er wordt een toetsenbord event gestart, zal de keyboard callback functie voor dat venster worden aangeroepen. Deze callback functie neemt als argument de ASCII unsigned char die geassocieerd is met de toets en de x, y locatie van de cursor op dat moment. In ../../common/March1998/example2.c is er geen gebruik voor x, y, maar ik weet zeker dat je applicaties kunt bedenken waarin je je voordeel kunt doen met deze aardige feature.
In onze demo heeft alleen het bovenste venster een keyboard callback. Als je de toetsen "i" of "q" indrukt terwijl de cursor zich in het subvenster bevindt, zul je merken dat er niets gebeurd. Standaard is het zo, dat als er een venster wordt gecreëerd en er geen keyboard callback functies worden geregistreerd, dat dan alle toetsaanslagen worden genegeerd. Houdt dit in de toekomst in gedachten als je meerdere vensters gebruikt en een actief toetsenbord wilt.
Tenslotte nog de opmerking dat het generen van keyboard callbacks uitgeschakeld kan worden door NULL aan glutKeyBoradFunc() mee te geven.
Het renderen van tekst onder OpenGL en GLUT zuigt! Sorry dat ik dat zo zeg, maar het is waar. Het is me niet helemaal duidelijk waarom tekst-rendering is genegeerd in de OpenGL library. De oude GL library van SGI had een paar high-level functies voor het renderen van tekst in graphische modus en er was een aanvullende hulp-library voor het veranderen van fonts. OpenGL biedt alleen erg primitieve richtlijnen voor het renderen van bitmaps, dat betekend dus dat je je eigen library van bitmaps moet aanleggen voor ieder karakter, rekening houdend met de resolutie, schalen van fonts... noem maar op!
GLUT lost het dillemma van tekst gebruiken met OpenGL enigzins op. Het biedt glutBitmapCharacter die een enkel karakter op het scherm renderd op de locatie die wordt aangegeven door glRasterPos. Ik heb enkele functies toegevoegd, drawString() en drawStringBig() die je het leven iets eenvoudiger maken, wat betreft het renderen van karakter strings.
Dit sluit een erg simpele introductie van het gebruik van GLUT subvensters af. Op dit punt moet ik melden dat hoewel het geod is om ermee te experimenteren en testen op verschillende platformen, GLUT subvensters niet in alle omgevingen volledig functioneel zijn. Gebruikers met 3Dfx gebaseerde kaarten zullen merken dat subvensters nog niet werken door hardware beperkingen. Ook is gebleken dat het op sommige platformen subvensters een aanslag op de performance leveren. Op mijn Linux Alpha met een 2Mb Matrox Millenium maakt het gebruik van subvensters de applicatie ongeveer twee zo traag, waarschijnlijk doordat de X server voor Alpha's nog steeds geen hardware acceleratie ondersteund. Aan de andere kant werkt dezelfde applicatie onder windows 95 met een 2Mb ATI RageII met SGI's OpenGL driver wonderwel.
Daar de ontwikkelingen in Linux zo snel gaan, is het mogelijk dat in de nabije toekomst veel van deze performance problemen zijn opgelost en incompatibiliteiten zullen verdwijnen. Houdt voor het moment in gedachten dat ze bestaan en wees dus voorzichtig met het gebruik van meerdere vensters.
Geavanceerdere gebruikers kunnen natuurlijk altijd een manier om de subvensters heen vinden door te spelen met de matrix stack maar omdat we die nog niet bestudeerd hebben, vergeef je me hopelijk dat ik die achterwege heb gelaten... ;)).