Render Thread

Aus DGL Wiki
Wechseln zu: Navigation, Suche

Ein Render Thread seperiert die Programm Logik von der Darstellungs Logik und erlaubt so eine Lockere Bindung, wiederverwendung bzw. Änderung von der jeweiligen Logik und unterschiedliche Frequenzen bei der Ausführung.

Ein Render Thread bringt ein Problem mit sich, man muss ein Thread erzeugen und eine Kommunikation mit den zu rendernen Daten herstellen. Hier gibt es 2 Prinzipien, 1. der Thread greift direkt auf die Logikdaten zu und interpretiert die Renderbefehle oder 2. die Logik gibt dem Thread die Renderbefehle.

Starke Bindung

Die 1. Möglichkeit macht den Render Thread sehr mächtig und unflexibel, weil er stark an die Programmlogik gekoppelt wird und damit nicht mehr in anderen Programmen, mit anderer Logik, funktionieren kann aber dafür ist es einfach schnelle Ergebnisse zu erzielen und einfacher zu optimieren. Der Render Thread greift auf Thread Safe Daten zu und kann anhand dieser die OpenGL Befehle erzeugen.

Schwache Bindung

Die 2. Möglichkeit macht den Render Thread wiederverwendbar aber benötigt mehr Zeit für das Design, Entwickeln und Optimieren. Der Render Thread nimmt ein Intermediate Befehlsliste entgegen und kann diese dann in OpenGL Befehle umsetzen. Dabei kann man verschiedene Implementierungen machen, wie z.B. 2 Thread safe Queues für Front- und Back-Buffer, die geswapped werden oder Tripple Buffer. Bei Tripple Buffer werden 2 Back-Buffer(B1,B2) und ein Front-Buffer(FB) erzeugt, der Front-Buffer wird von der Programmlogik befüllt und am Ende wird ein Swap angefordert. Nun wird der Front-Buffer mit dem 1. Back-Buffer(B1) geswapped, sollte nun der Renderer durch sein, wird er B1 mit B2 austauschen und beginnt mit der verarbeitung von B1, während die Programmlogik FB und B2 swappen kann. Der Sinn ist, dass nun Render Thread und Logik mit unterschiedlichen Frequenzen arbeiten können und immer ein aktueller vollständiger Stand verfügbar ist. Mit dieser Variante muss man sich nur um Multithreading sichere Programmierung bei den 3 Queues kümmern. Dies kann man auch Lösen, in dem man Lock-Free Queues benutzt.

Die Kunst bei diesem Prinzip ist, dass man ein möglichst kleines Kommunikations Protokoll entwickelt. Je kleiner das Protokoll ist, des so mehr Code wird wieder verwendet, weniger Fehler können gemacht werden und schneller ist die Kommunikation.

Hier ein paar Beispiele um die Unterschiede besser wieder zu geben. Zu spezielle Befehle erkennt man in der Regel, dass diese keine Parameter haben und sehr selten verwendet werden.

RenderThread::Enqueue(RedTintedCatMessage());

Zu Atomare Befehle erkennt man in der Regel, dass man sehr große Codeblöcke auf der Logikseite schreiben muss, um einzelne Daten zu visualisieren.

RenderThread::Enqueue(BeginMessage(FaceType::Triangle));
RenderThread::Enqueue(ColorMessage(FaceColor::Red));
RenderThread::Enqueue(VertexMessage(-1,-1,0);
RenderThread::Enqueue(VertexMessage(1,-1,0);
RenderThread::Enqueue(VertexMessage(0,1,0);
RenderThread::Enqueue(EndMessage());

Eine einfache Möglichkeit ist Renderdaten als Objekte zu zerlegen und auf diesen auf zu bauen.

Mesh catMesh("cat.mesh");
Shader redTinting("tinting.shader");
RenderThread::Enqueue(BindMeshMessage(catMesh));
RenderThread::Enqueue(BindShaderMessage(redTinting));
RenderThread::Enqueue(SetShaderVariable("tintingColor", FaceColor::Red));
RenderThread::Enqueue(Draw(catMesh.StartIndex(), catMesh.LastIndex()));