Struktur des SDL-OpenGL-Programmgerüstes




Einige Hinweise sind den Kommentaren im Quelltext zu entnehmen, doch um zu verstehen, wie die Dinge zusammenhängen, scheinen mir einige zusätzliche Erklärungen sinnvoll zu sein.

SDL und das Modul winsys.cpp

Die wichtigsten Funktionen im Zusammenhang mit dem Programmfenster und der OpenGL-Einbindung wurden in der Klasse CWinsys gekapselt. Die Klasse enthält auch die wesentlichen Fensterparameter sowie die Funktionen zum Programmablauf einschließlich der Loop, die die Tastatur-, Maus- und Joystickabfragen abfängt und die entsprechenden Signale an den gerade aktuellen Programmmodus sendet. Eine typische Befehlssequenz in der Main-Funktion könnte so aussehen:

Winsys.InitSDL ();
Winsys.InitOgl ();

Winsys.SetViewport ();
Winsys.SetPerspective ();

Winsys.ClearScreen ();
... Renderfunktionen ...
Winsys.Swap ();

Winsys.Loop ();
return 0;

Zuerst wird SDL initialisiert, was die Initialisierung des Joysticks einschließt. Dann erfolgt die Initialisierung von OpenGL, die aber lediglich in einem Bündel von OpenGL-Einstellungen besteht. Hierzu eine kleine Anmerkung: Die OpenGL-Funktionen sind im Modul ogl.cpp untergebracht, aber da sie oft auf die Fensterparameter zurückgreifen, wurden sie zusätzlich in Winsys integriert. Das macht die Aufrufe sicherer und kürzer.

Mit SetViewport wird das OpenGL-Fenster innerhalb des SDL-Programmfensters definiert. Meistens wird das gesamte Programmfenster genutzt, so auch in diesem ersten Programmgerüst. Das muss aber nicht sein, denn gelegentlich möchte man mehrere eigenständige Viewports im Programmfenster unterbringen, was problemlos geht. Dann aber müssen die flexibleren Funktionen in ogl.cpp benutzt werden.

Mit SetPerspektive werden die perspektivischen Darstellungseigenschaften gesetzt. Damit kann z.B. die "Brennweite" eingestellt werden, oder der Darstellungsraum wird begrenzt. Daraus geht schon hervor, dass diese Einstellung für räumliche Szenerien in Frage kommt. Soll nur in 2D gearbeitet werden (Menüs usw.), dann wird stattdessen die Funktion SetOrtho aufgerufen. Sowohl SetViewport als auch SetPerspektive (bzw. SetOrtho) können mehrmals im Programm aufgerufen werden. Deshalb müssen sie nicht in der Main-Funktion stehen, sondern können irgendwo untergebracht sein, z.B. in der Init-Funktion eines Programmmodus.

ClearScreen löscht den OpenGL-Bildschirm und wird jedesmal aufgerufen, bevor der Viewport neu gezeichnet wird. Bei bewegten, animierten Szenen geschieht das praktisch in jedem Frame. Dann erfolgt der Aufruf aber nicht in der Main-Funktion, sondern üblicherweise in der Loop-Funktion des aktuellen Programmmodus. Diese wiederum wird von der SDL-Hauptschleife angesprochen, die dann ggfs. auch für die richtigen Zeitintervalle sorgt. Ähnliches gilt für die darauf folgenden Renderfunktionen sowie das abschließende Swap. Die Swap-Funktion sorgt dafür, dass das im Hintergrund gezeichnete OpenGL-Bild nach vorne gebracht und sichtbar wird. Das Dreigespann (Clear, Render und Swap) steht also normalerweise nicht in der Main-Funktion. Nur zu Testzwecken, wenn es um ein einmaliges Zeichnen geht, ist das vertretbar.

Zuletzt geht es dann mit Loop in die Hauptschleife, die erst am Ende des Programms verlassen wird. Von dieser Schleife aus werden alle weiteren Programmfunktionen gesteuert.

Programmmodi und Hauptschleife

Ein normales OpenGL-Programm arbeitet nicht mit mehreren Programmfenstern. Somit müssen auch Dinge, die sonst in eigenen Fenstern erledigt werden (z.B. Dialoge) im Hauptfenster ablaufen. Wenn sich aber alles im selben Programmfenster (nicht zu verwechseln mit dem OpenGL-Viewport) abspielt, muss es möglich sein, dieses Fenster für die verschiedensten Zwecke in beliebiger Reihenfolge zu nutzen. Das geschieht z.B. durch die Programmmodi. So könnte der Anfangsmodus in einem Splashscreen bestehen, worauf dann verschiedene Menüs folgen. Der Hauptmodus wird in einem Spiel der eigentliche Game-Modus mit seinen verschiedenen Interaktionen sein, und wenn das Ziel erreicht ist oder was auch immer, geht es irgendwohin zurück.

In all diesen verschiedenen Modi wird ganz unterschiedlich auf Tastendrucke oder Mausklicks reagiert, und auch die Renderanweisungen zum Zeichnen des Bildschirms unterscheiden sich. Andererseits gibt es nur eine Hauptschleife, die die Useraktionen abfängt. Sie muss nun diese Aktionen an den richtigen Programmmodus weiterleiten. Das kann z.B. mit Funktionszeigern geschehen, die bei jedem Moduswechsel neu eingestellt werden. In dem Programmgerüst habe ich aber einen anderen Weg gewählt, und zwar über eine abstrakte Modusklasse CMode, in der alle Funktionen, die von der Loop aus angesteuert werden, deklariert sind. Für die konkrete Implementation dieser Funktionen sind dann verschiedene Ableitungsklassen zuständig. Hier kann für jeden Modus genau programmiert werden, was im Falle eines Tastendrucks oder Mausklicks geschehen soll. In der Hauptschleife werden die abgeleiteten Modusklassen, die sich in einem Array vom Typ der abstrakten Elementarklasse befinden, über einen simplen Modusindex angesprochen. Ein Moduswechsel ist infolgedessen nichts anderes als das Setzen eines anders Modusindex.

Wenn ein Moduswechsel staffinden soll, wird der neue Modus in new_mode eingetragen. Beim nächsten Schleifendurchlauf stellt Winsys, dass new_mode nicht mit dem aktuellen Modus curr_mode übereinstimmt. Daraufhin wird die Term-Funktion des aktuellen Modus aufgerufen, und anschließend die Init-Funktion des neuen Modus, der daraufhin zum aktuellen wird. Es kann nun die richtige Loop-Funktion, in der das Rendering vonstatten geht, angestoßen werden. Dieses Dreigespann (Init, Loop und Term) gehört wie die Funktionen zum Beantworten von User-Eingaben zum Standard-Funktionssatz von CMode.