Ziel dieses Kapitels ist es ein Fenster incl. Open GL Kontext zu initialisieren. Dieses Fenster soll per Tastenkombination geschlossen oder maximiert werden können.

Fehlerbehandlung in der Main

Zuerst aktualisieren wir unsere main.cpp. Hier wird einerseits die Datei renderer.h eingebunden um Zugriff auf die Renderer Klasse zu bekommen. Andererseits wird per try+catch auf Fehler geprüft.

main.cpp

#include "renderer.h" #include <iostream> int main() { try { Renderer renderer("Grundlagen der Computergrafik", 1280, 720); renderer.start(); } catch (const std::exception &e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }

Die Klasse Renderer

In C++ bestehen Klassen üblicherweise aus zwei Dateien. In der Header Datei .h wird die Klasse definiert und die Methoden der Klasse deklariert. In der passenden .cpp Datei werden die einzelnen Methoden definiert bzw. implementiert.

Hier ist die Header Datei unserer Renderer Klasse.

renderer.h

#pragma once #define GLFW_INCLUDE_GLEXT #include <GLFW/glfw3.h> #include <string> class Renderer { public: Renderer(const std::string &title, uint32_t width, uint32_t height); ~Renderer(); void start(); void onKeyboardInput(GLFWwindow *window, int key, int scancode, int action, int mods); private: GLFWwindow *window = nullptr; };

Konstruktor

Die Implementierung der einzelnen Methoden kommt in die .ccp Datei. Hier im Konstruktor wird GLFW initialisiert und im Fehlerfall eine Exception geworfen.

renderer.cpp

#include "renderer.h" #include <iostream> Renderer::Renderer(const std::string &title, uint32_t width, uint32_t height) { glfwSetErrorCallback([](int error, const char *description) { std::cerr << "GLFW Error: " << description << std::endl; }); if (!glfwInit()) { throw std::runtime_error("Failed to initialize GLFW"); } window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr); if (!window) { glfwTerminate(); throw std::runtime_error("Failed to open window"); } glfwMakeContextCurrent(window); }

Fehlerbehandlung

Damit wir mitbekommen ob wahrend der Laufzeit unserer Anwendung irgendetwas bei der Kommunikation mit der Grafikkarte schief läuft, benötigen wir eine Ausgabe von Fehlermeldungen.

GLFW gibt uns hier die sehr einfache Möglichkeit ein Fehler Callback mittels glfwSetErrorCallback zu implementieren. Sie können dies oben im Konstruktor sehen. Das Prinzip kennen Sie bereits von Hello World, wir machen eine Ausgabe auf die Kommandozeile. Statt std::cout verwenden wir hier allerdings std::cerr und klassifizieren unsere Ausgabe damit als Fehlermeldung.

Destruktor

Analog dazu werden im Destruktor alle Ressourcen von GLFW wieder freigegeben:

renderer.cpp

Renderer::~Renderer() { glfwDestroyWindow(window); glfwTerminate(); }

Message Loop

Um Echtzeitgrafik anzuzeigen muss der Computer viele Einzelbilder generieren und in schneller Folge anzeigen. Würde man dafür ein einfaches while(true) verwenden, wäre die Anwendung vollkommen ausgelastet und nicht mehr in der Lage beispielsweise auf Maus oder Tastatur zu reagieren.

Aus diesem Grunde implementiert man einen sogenannten Message Loop. Hier in der start() Methode des Renderers sehen wir das. Unsere Anwendung wird in einer Endlosschleife immer wechselseitig ein Bild rendern und dann beim Betriebssystem nachfragen ob eine Nachricht vorliegt. Eine solche Nachricht könnte z.B. sein “Benutzer möchte Fenster maximieren”. Solange Nachrichten vorliegen werden diese abgearbeitet und anschließend ein weiteres Bild gezeichnet.

renderer.cpp

void Renderer::start() { while (!glfwWindowShouldClose(window)) { glfwSwapBuffers(window); glfwPollEvents(); } }

Tastatureingaben

Um Tastatureingaben entgegen zu nehmen, müssen wir im Anschluss an die Erstellung des Fensters ein KeyCallback registrieren. Das Prinzip ist das gleiche wie beim Fehler Callback wir implementieren nun allerdings eine Klassenmethode um die Eingaben zu verarbeiten.

renderer.cpp

void Renderer::onKeyboardInput(GLFWwindow *window, int key, int scancode, int action, int mods) { if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { glfwSetWindowShouldClose(window, GL_TRUE); } else if (key == GLFW_KEY_M && action == GLFW_PRESS) { if (glfwGetWindowAttrib(window, GLFW_MAXIMIZED)) { glfwRestoreWindow(window); } else { glfwMaximizeWindow(window); } } }

Callback registrieren

Wir erinnern uns: GLFW ist eine C-Bibliothek und hat keine Ahnung von Objektorientierung. Entsprechend können wir nicht so einfach die eben definierte Methode Renderer::onKeyboardInput als Callback übergeben. Stattdessen verwenden wir glfwSetWindowUserPointer() um eine Verbindung zwischen dem von GLFW verwalteten Fenster und unserer Instanz des Renderers zu hinterlegen. Mit glfwGetWindowUserPointer() im Callback können wir dann wieder auf den Renderer zugreifen und das eigentliche Callback aufrufen.

renderer.cpp

Renderer::Renderer(const std::string title, uint32_t width, uint32_t height) { ... glfwSetWindowUserPointer(window, this); glfwSetKeyCallback(window, [](GLFWwindow *window, int key, int scancode, int action, int mods) { Renderer *self = static_cast<Renderer *>(glfwGetWindowUserPointer(window)); self->onKeyboardInput(window, key, scancode, action, mods); }); }

Anwendung starten

Um zu testen ob alles geklappt hat, ist es das einfachste im Terminal die folgenden beiden Befehle einzugeben.

$ make run_cgb_02

Hausaufgabe

  • Räumen Sie Ihren Code auf, schreiben Sie Kommentare und machen Sie sich Notizen für die Prüfung.
  • Bereiten Sie eigenständig bis zur nächsten Veranstaltung Kapitel 3 vor: Implementieren Sie dazu das Tutorial von meiner Website und ziehen Sie weitere Quellen zu Rate um exakt zu verstehen was die Software macht und warum.