Ich habe ein Problem mit meinem ersten OpenGL-Projekt. Es stellt dreidimensionale Fraktale her, und weil das unter umständen etwas dauert, ist die Berechnung in einen zweiten Thread ausgelagert worden. Die Anzeige übernimmt ein "OpenGLPanel" (im www gefunden).
Das Programm arbeitet mit einer DisplayList, die während der Berechnung mit den Befehlen zum Zeichnen des Fraktals gefüllt wird. Weil ja nun eine DisplayList irgendwie Rendercontextabhängig ist und dieser wiederum vom Thread abhängt, musste auch die Renderfunktion (in der das Fraktal von der DisplayList gezeichnet wird, ohne Neuberechnung) in den Thread wandern, sonst wird nix angezeigt. Das gleiche gilt für die Initialisierungsmethode des OpenGL-Panels.
Damit beginnt das Problem: Seit sich die Renderfunktion nicht mehr in dem Thread befindet, in dem das OGL-Panel liegt, zeigt sich ein merkwürdiges Phänomen beim Vergrößern der Anwendung: Das OGLPanel vergrößert sich zwar, das angezeigt Fraktal wird aber kleiner! Außerdem bleiben die neu gewonnenen Flächen schwarz.
Ich habe eine kleine Beispielanwendung erstellt, die auf das wesentliche reduziert ist. Man kann darin zwischen 1-Thread (= null Problem) und 2-Thread umschalten. Vielleicht könnt Ihr mir helfen, den Fehler im 2-Thread-Modus zu finden.
Lasst euch bitte nicht davon abschrecken, dass es jetzt was zum herunterladen gibt, ich verspreche euch, es lohnt sich! Die Beispielanwendung verzichtet nämlich nicht darauf, zufällige 3D-Fraktale zu generieren...
dartrax
Du hast keine ausreichende Berechtigung, um die Dateianhänge dieses Beitrags anzusehen.
Zuletzt geändert von dartrax am Mo Jan 08, 2007 17:20, insgesamt 1-mal geändert.
Registriert: Do Dez 05, 2002 10:35 Beiträge: 4234 Wohnort: Dortmund
Ich habe auch schon mal mit einem Thread gerendert. Der hat zwar nicht viel berechnet aber das sollte kein Problemdarstellen. Wenn du mit Threads arbeitest muss du darauf achten, dass ALLES was OpenGL ist in ein und dem selben Thread gezeichnet wird.
Ich rate dir aber davon ab, dass du in Threads zeichnest. Dadurch hat man nur mehr Nach als Vorteile. Zum Beispiel die Größenänderungen. Die werden von der VCL an dem Fenster vorgenommen und du müsstest dann diese Änderungen noch an den Thread senden damit er OpenGL anpassen kann. Dazu muss man das alles umständlich synchronisieren. Was in meinen Augen recht kompliziert ist. Und selbst dann ist es ein Drahtseilakt.
Was hindert dich daran, dass du dein Panel im VCL Thread erstellst/initialisierst und dort auch zeichnest. Die Ergebnisse des Rechenthreads übergibst du dann mittels Synchronize an den VCL Thread. Also immer einen aktuellen Stand oder so. Der VCL Thread nimmt es dann synchron entgegen, liest sich seinen Teil aus und verarbeitet es. Evtl kannst du es dann noch so machen, dass du so schnell wie möglich das Synchronize wieder verlässt damit der Thread weiter rechnen kann. Und im Zeichnen oder so generierst du dir dann deine neue Liste. Du müsstest aus dem Synchronize ein Flag setzen damit du weißt, dass sie neu erstellt werden soll. Damit wäre das Zeichnen und die Fensterbehandlung komplett in einem Thread (VCL) und die Berechnungen in einem anderem.
Wenn du aus dem Rechenthread auch noch zeichnest geht irgendwie auch ein bisschen der Sinn des Threads verlohren. Du lagerst dann so nur die gesammte arbeit in einen anderen Thread aus. Du müsstest du unterschiedliche Arbeit aber auf Threads verteilen.
Danke für deine Antwort. Ich hoffe, ich habe sie größtenteils richtig verstanden:
Lossy eX hat geschrieben:
Ich habe auch schon mal mit einem Thread gerendert. Der hat zwar nicht viel berechnet aber das sollte kein Problemdarstellen. Wenn du mit Threads arbeitest muss du darauf achten, dass ALLES was OpenGL ist in ein und dem selben Thread gezeichnet wird.
Ich rate dir aber davon ab, dass du in Threads zeichnest. Dadurch hat man nur mehr Nach als Vorteile. Zum Beispiel die Größenänderungen. Die werden von der VCL an dem Fenster vorgenommen und du müsstest dann diese Änderungen noch an den Thread senden damit er OpenGL anpassen kann. Dazu muss man das alles umständlich synchronisieren. Was in meinen Augen recht kompliziert ist. Und selbst dann ist es ein Drahtseilakt.
Aha. Sollte ich das machen (müssen), müsstest du mir nochmal erklären, wie ich die OpenGL dazu bringen kann, das zu kapieren und sich anzupassen. Aber vorher erscheint mir vernünftiger, was du im Folgenden schreibst:
Lossy eX hat geschrieben:
Was hindert dich daran, dass du dein Panel im VCL Thread erstellst/initialisierst und dort auch zeichnest. Die Ergebnisse des Rechenthreads übergibst du dann mittels Synchronize an den VCL Thread. Also immer einen aktuellen Stand oder so. Der VCL Thread nimmt es dann synchron entgegen, liest sich seinen Teil aus und verarbeitet es. Evtl kannst du es dann noch so machen, dass du so schnell wie möglich das Synchronize wieder verlässt damit der Thread weiter rechnen kann. Und im Zeichnen oder so generierst du dir dann deine neue Liste. Du müsstest aus dem Synchronize ein Flag setzen damit du weißt, dass sie neu erstellt werden soll. Damit wäre das Zeichnen und die Fensterbehandlung komplett in einem Thread (VCL) und die Berechnungen in einem anderem.
Bis jetzt hindert mich daran, das während des Rechnens, das schon mal eine Minute dauern kann, _kontinuierlich_ Informationen zu den einzelnen Vektoren erzeugt werden, die ja irgendwie weiterverarbeitet werden müssen. Dieses Weiterverarbeiten ist im Moment einfach das Speichern in eine DisplayList. Und weil die DisplayList wegen dem RC nur innerhalb ein und desselben Threads funktioniert, findet eben auch das Zeichnen in dem Berechnungsthread statt. Sollte das Zeichnen in dem VCL-Thread vonstatten gehen, müsste auch die Erstellung der DisplayList im VCL-Threat stattfinden, und jeder einzelne Vector (bei einer 75,5 Sekunden Rechenzeit kommen da 78380 Vektoren zusammen) mittels Sync an den VCL-Threat gesendet werden. Ich kenne mich mit Sync nicht aus, bleibt der VCL-Thread dabei vollständig bedienbar? Die andere Möglichkeit wäre, auf anderem Wege die Vektoren zu speichern und nach der Berechnung an den VCL-Threat zu senden. Die DisplayList könnte ich mir dann wahrscheinlich ganz sparen. Ihr Vorteil war jedoch neben der Geschwindigkeit, dass ich später leicht auch noch andere Sachen als Vektoren darin speichern kann.
Lossy eX hat geschrieben:
Wenn du aus dem Rechenthread auch noch zeichnest geht irgendwie auch ein bisschen der Sinn des Threads verlohren. Du lagerst dann so nur die gesammte arbeit in einen anderen Thread aus. Du müsstest du unterschiedliche Arbeit aber auf Threads verteilen.
Naja, das Anzeigen "Call" der DisplayList findet ja erst nach der Berechnung statt. Und während der Berechnung bleibt die Bedienoberfläche vollständig Reaktionsfähig, es kann also ohne weiteres Status und Geschwindigkeit der Berechnung angezeigt werden oder diese evt. abgebrochen werden.
Eine Möglichkeit wäre eine VertexBuffer zu nehmen, zu mappen und nur den Zeiger dem anderen Thread zu übergeben, der ihn dann mit seinen Daten füllt, aber dafür keine weiteren OpenGL Funktion benötigt.
Dann hat man OpenGL auch nur aus einem Thread her aufgerufen.
Registriert: Do Dez 05, 2002 10:35 Beiträge: 4234 Wohnort: Dortmund
78.000 Syncaufrufe sind sicherlich möglich. Allerdings muss ich gestehen, dass das ganz schön viel ist. Jeder Syncaufruft benötigt etwas an Zeit, da das Sync über WindowsMessages geht. Der VCL Thread sollte davon recht unbeeindruckt bleiben. Außer du musst in dem Event dann massig Sachen noch verarbeiten. Aber du solltest nicht vergessen, dass bei einem Sync der Rechenthread stehen bleibt so lange bis das Sync abgearbeitet wurde. Und von daher würde ich eher 100-1000 Vektoren sammeln und diese dann per Sync übergeben. Evtl kann man da auch noch eine andere Möglichkeit finden.
Aber Vertexbuffer ist da sicher eine Alternative die du dir mal überlegen solltest. Den müsstest du nicht immer wieder komplett neu aufbauen. Wobei man den Buffer auch nicht alle Nase lang locken und wieder unlocken sollte. Das zerrt dann nämlich auch an der Leistung. Bzw kannst du den aber auch nicht die ganze Zeit gelockt lassen, weil du Updates wärend der Berechnung haben willst, oder?
Am besten zwei Buffer nehmen, aus dem einen Rendern und den anderen mappen und schonmal für den nächsten Frame füllen, falls das möglich ist. Dann braucht man auch nur einen Buffer pro Frame zu mappen und das sollte im Vergleich zu der teuren Berechnung vernachlässigbar sein. Danach kann man die Funktion der beiden Buffer tauschen.
vielen Dank für die tolle Hilfe.
Ich habe jetzt ein wenig über die VertexBufferObjects gelesen, sie sollen ja viel schneller sein als so eine DisplayList. Deshalb möchte ich das gerne ausprobieren.
Ich versuch' gerade, ein bisschen VertexBufferObject in meine Testanwendung zu implementieren. Allerdings lese ich heute zum ersten Mal davon und stehe noch ziemlich auf dem Schlauch. Von LarsMiddendorfs letzten Post habe ich so gut wie gar nichts verstanden - Ich bin einfach noch nicht soweit. Was ist ein Buffer, was ein Frame? Ich weiß, wahrscheinlich sollte ich mich mit den grundlegensten Dingen ersteinmal theoretisch auseinandersetzen, nur habe ich keine Lust dazu, ich implementiere lieber zuerst
Habt ihr vielleicht einen Link mit einem Tutorial oder Beispielanwendung parat? Bei dem Tutorial hier ist leider keine Beispielanwendung dabei und der Code recht knapp/unvollständig. Mit Copy&Paste ist da nichts...
Also ich brauche keine Animation oder ähnliches, deswegen hatte mir die DL auch gereicht, abgesehen von der Geschwindigkeit. Was meint ihr mit Updates während der Berechnung? Das dabei die Anzeige schonmal aufgebaut wird? Ist unwichtig. Erstmal alles berechnen und anzeigen, danach die Sintflut
Es hatte erst den Anschein, als sollten die Daten später aktualisiert werden.
Bei statischen Vertexdaten kann man sich die zwei Buffer dann sparen und nur den einen befüllen.
Dieses Beispiel verwendet die Methode mit glMapBuffer() um eine Höhenkarte zu rendern.
In dem Fall würde es ausreichen alles zwischen glMapBufferARB und glUnmapBufferARB in den Thread zu packen.
danke für die Links. Ich habe mir alles angeschaut und so gut ich konnte in mein Projekt eingebaut.
Jetzt ist mir was passiert, dass ich denke, ich höre lieber auf und geh' schlafen. Der entwickelt ein Eigenleben!!! Selbst wenn ich alles auf 0 stelle, kommen da mystische Gebilde heraus, die ich nie im Leben da reinprogrammiert habe. Es fing an mit zwei Linien, die endlich angezeigt wurden, doch als ich merkte, dass es überhaupt nicht mein Code ist, der die Farbe und Form definiert, habe ich alle Koordinaten und Farbangaben auf 0 gesetzt und
Code:
glDrawArrays(GL_LINES, 0, 4);
einfach mal in
Code:
glDrawArrays(GL_LINES, 0, 4000000);
geändert.
Das Ergebnis seht Ihr im Anhang. Ich sage euch: Es ist riesig! Woher kommt es?
(Der Code ist auch wieder im Anhang. Ich poste ihn aber nochmal, vielleicht sieht jemand auf den ersten Blick, was falsch ist)
dartrax
Du hast keine ausreichende Berechtigung, um die Dateianhänge dieses Beitrags anzusehen.
Hier nochmal der Code. Es handelt sich zwar um die Deklaration einer Threading-Klasse, die CreateDL- und Render-Funktionen werden aber von dem VCL-Thread aufgerufen (hat also ansich noch nichts mit Threading zu tun, der einfachheit halber). Also nicht wundern, dass diese beiden Funktionen hier nirgens aufgerufen werden, das macht die VCL-Klasse.
Code:
type TOpenGLThread = class(TThread)
private
VBO: GLuint;
VBOPointer: Pointer;
public
// Verweis auf das OpenGL-Panel im Hauptanwendungsthread
OpenGLPanel: TOGLPanel;
// Constructor: Eine neue Instanz dieser Klasse wird erstellt
constructor Create(OpenGLPanel: TOGLPanel);
// Thread-Methode: Wird beim Ausführen aufgerufen. Wenn sie abbreicht, ist der Thread beendet
procedure Execute(); override;
procedure CreateDL();
procedure Render();
end;
type
TVertex = packed record
r, g, b, a: Byte;
x, y, z: Single;
end;
implementation
uses dglOpenGL;
// Constructor: Eine neue Instanz dieser Klasse wird erstellt
Hallo,
ich habe den Fehler gefunden. Ich hätte mich nicht dazu verleiten dürfen, statt
Code:
inc(Integer(VBOPointer), SizeOf(TVertex));
die falsche Zeile
Code:
inc(Integer(VertexPointer), SizeOf(TVertex));
zu verwenden.
Jetzt läuft alles und...
mit einer sagenhaften Geschwindigkeitsverbesserung! Ich habe getestet:
Eine Berechnung und Anzeige von 349524 Vektoren hat mittels DisplayList 76,266 Sekunden gebraucht. Das Berechnen war unter einer Sekunde erledigt (Geschwindigkeit: 3519389 Vektoren pro Sekunde), das einzige, was 76 Sekunden gedauert hat, war der call-Befehl der DisplayList.
Ohne die DisplayList dürfte die Berechnung innerhalb einer Sekunde vonstatten gegangen sein. Und tatsächlich: Das Fraktal wird innerhalb von (gefühlten) 3 Sekunden angezeigt! Das nenne ich einen Durchbruch!!
Nochmals vielen Dank für eure Hilfe, ich habe viel dazugelernt und mein Projekt entscheidend verbessert.
Registriert: Do Dez 05, 2002 10:35 Beiträge: 4234 Wohnort: Dortmund
Eine kleine Anmerkung bzw "Verbesserung". Weiß nicht. Ich mache das gerne anders.
Anstelle von
Code:
inc(Integer(VertexPointer),SizeOf(TVertex));
kannst du auch
Code:
inc(VertexPointer);
benutzen.
Inc erkennt automatisch die Größe sobald es sich um einen typisierten Pointer handelt und erhöht diesen um den IncWert * Größe. Mit anderen Worten inc(VertexPointer) setzt den Pointer direkt auf das nächste Vertex.
PS: Ich finde es gut, dass es so gut funktioniert bzw, dass du dich selber durch ein neues Thema gewühlt und einen Fehler gefunden hast. respekt.
Danke,
deinen Änderungsvorschlag werde ich übernehmen. Evt. war es vorher leichter verständlich, aber das sollte besser eine Kommentarzeile übernehmen.
Danke auch für dein "PS". Durch eure schnelle und kompetente Hilfe tragt ihr aber "Mitschuld"
Mitglieder in diesem Forum: 0 Mitglieder und 8 Gäste
Du darfst keine neuen Themen in diesem Forum erstellen. Du darfst keine Antworten zu Themen in diesem Forum erstellen. Du darfst deine Beiträge in diesem Forum nicht ändern. Du darfst deine Beiträge in diesem Forum nicht löschen. Du darfst keine Dateianhänge in diesem Forum erstellen.