Marke Eigenbau

eMmiE

Bekanntes Mitglied
Hi,

ich sitze schon seit einiger Zeit an einer 3D - Engine (ich muss dazu sagen, dass ich das ganze Ding mehr oder weniger selbst schreiben möchte, um den Hintergrund zu verstehen)

Hierzu habe ich folgende Klassen erstellt:
DrawFrame - Erbt von JFrame, implementiert Runnable und stellt wiederholt das Bild auf dem Bildschirm dar

Main - Implementiert Runnable und verteilt die Spieldaten (Spieler/Kamera-Position, Bild, Blickrichtung) und sorgt dafür, dass bei Schließaufforderung des DrawFrames alle Threads beendet werden

Container - Implementiert Runnable und enthält mehrere Chunks, die ebenfalls von Runnable erben, und hat die Aufgabe, die festen Bestandteile der Spielwelt zu verwalten (Dreiecksklasse -> Plain). Die Chunks verwalten die Flächen und sortieren sie vor, der Container stellt daraus 2 Listen zusammen. Die 1. Liste wird vom Grafiker benötigt und soll gemalt werden, die 2. vom Physiker und soll zur physik. Berechnung dienen

Physiker - Erbt von Runnable und soll mit den Flächen vom Container realistische Physik ermöglichen

Rechenzentrum - Rechenzentrum ist mehr oder weniger eine Schnittstelle zu Methoden, die das berechnen auf der Grafikkarte ermöglichen sollen. Das mit der Grafikkarte habe ich mithilfe von lwjgl.jocl erstellt.

Grafiker - Die momentan aufwendigste Klasse. Erbt von Runnable und soll die Flächen darstellen. Hierzu müssen die Flächen natürlich gedreht, um die Spielerposition verschoben und auf einen virtuellen Bildschirm transformiert werden

Mein Problem: Der Grafiker läuft im Moment noch (bei einer Fläche) mit 1 - 2 FPS. Das ist natürlich nciht ganz so toll :D Weiter unten findest du die run-Methode.
Wie kann man die so ändern, dass er schneller läuft?
Ist die Programmstruktur so in Ordnung?
Ich hab nämlich so ne leise Ahnung, dass das mit den ganzen Runnables suboptimal sein KÖNNTE (100% Leistungs-Auslastung)
Wie ist die Struktur bei 3D-Engines/-Spielen im allgemeinen?

Ich bin dankbar für jede Antwort

Code:
Code:
while(running) {
  if (c.getReadyG()) {
    //Erst die Variablen setzen, die man für den Durchlauf benötigt
	final float r = this.R; //Winkel nach oben/unten in Radiant
	final float b = this.B;//Winkel nach links/rechts in Radiant
	final float[] spieler = s.retList(); gibt die Spielerposition in einem float[] zurück
	this.buf = new BufferedImage(width,height,BufferedImage.TYPE_3BYTE_BGR);
	this.g = buf.createGraphics();
	delta = System.nanoTime();
	//Hier jetzt die ganzen Umformungen
	//Alle Flächen holen
	this.plains = convert(c.getGraphlist()); //c ist Instanz von Container
	c.unreadyG(); //Teilt Container mit, dass die Liste geholt wurde	
	if (this.plains.length != 0) {						
		//	Alle Punkte um minus S verschieben
		plains = mux(mux(move(demux(demux(plains)),spieler)));
		//move() kann nur mit einem float[] arbeiten, daher müssen wir den DDDPoint[] erst zu float[] umformen (1 * Plain -> DDDPoint[3] -> float[9])
		//	Alle Punkte um R und S rotieren
		plains = mux(mux(turn(demux(demux(plains)),-r,-b)));
		//@turn() s.o.			
					
		//	Alle Flächen aussortieren, die hinter dem Spieler liegen (Mittelpunkt)
		boolean[] bools = Xsmaller0(plains);
                //Blickrichtung x-Achse 
		int counter = 0;
		for (int i = 0;i < bools.length;i++) {
			if (bools[i]) {
				counter++;
			}
		}
		Plain[] a = new Plain[counter];
		counter = 0;
		for (int i = 0;i < bools.length;i++) {
			if (bools[i]) {
				a[counter++] = plains[i];
			}
		}
		plains = a;
					
		//	Nach Entfernung sortieren...
		float[] Entfernung = new float[plains.length];
		float[] mids = new float[plains.length * 3];
		for (int i = 0;i < plains.length;i++) {
			DDDPoint[] p = new DDDPoint[] {plains[i].getMid()};
			float[] work = demux(p);
			mids[i * 3 + 0] = work[0];
			mids[i * 3 + 1] = work[1];
			mids[i * 3 + 2] = work[2];
		}
					
		this.r.init("mult"); 
                //Das ist nötig, damit er die Kernel (JOCL) initialisieren kann
		counter = mids.length;
		while (true) {
			float[] work;
			if (counter > 256) {
		        	work = new float[256];
				for (int i = 0;i < work.length;i++) work[i] = mids[i + counter - 256];
				work = this.r.mult(work, work);
		        	for (int i = 0;i < work.length;i++) mids[i + counter - 256] = work[i];
				counter -= 256;
			} else {
				work = new float[counter];
				for (int i = 0;i < work.length;i++) work[i] = mids[i];
				work = this.r.mult(work, work);
				for (int i = 0;i < counter;i++) mids[i] = work[i];
				counter = 0;
				break;
			}
		}
		this.r.finish("mult"); 
                //Gibt Ressourcen wieder frei
					
		//--> 0,2 Sekunden =(
				
		//jetzt Entfernung ausrechnen
		this.r.init("heron");
                //@"heron" -> Wurzelnäherungsverfahren nach heron
		float[] sum = new float[mids.length / 3];
		for (int i = 0;i < mids.length / 3;i++) {
			sum[i] = mids[i * 3 + 0] + mids[i * 3 + 1] + mids[i * 3 + 2];
		}
		counter = sum.length;
		while (true) {
			float[] work;
			if (counter > 512) {
				work = new float[512];
				for (int i = 0;i < work.length;i++) work[i] = sum[i + counter - 512];
				work = this.r.sqrt(work, 5);
				for (int i = 0;i < work.length;i++) sum[i + counter - 512] = work[i];
				counter -= 512;
			} else {
				work = new float[counter];
				for (int i = 0;i < work.length;i++) work[i] = sum[i];
				work = this.r.sqrt(work, 5);
				for (int i = 0;i < counter;i++) sum[i] = work[i];
				counter = 0;
				break;
			}
		}
		this.r.finish("heron");
		
		//--> 0,2 Sekunden =(
					
		//	jetzt sortieren
		qs.parallelSortieren(sum, plains);
				
		//--> Sehr kurz =)
					
		counter = sum.length-1;
		while(sum[counter] > this.renderwidth) {
			counter--;
		}
					
		Plain[] work = new Plain[counter];
		for (int i = 0;i < counter;i++) work[i] = plains[i];
					
		//	umformen
		Triangle[] t = this.tr.transform(work);
		
		//	einfach nur malen, da die sowieso schon sortiert sind
		for (int i = t.length;i > 0;i--) {
			g.setColor(java.awt.Color.green);
			g.fillRect(0, 0, this.width, this.height);
			t[i].paint(g);
		}
					
		delta = System.nanoTime() - delta;
		fps = (int)(1 / (float)((float)delta / 1000000000) + 0.5);
				
		this.toRecieve = this.buf; 
                //Buffer-Bild wird umgeschrieben, damit immer nur das "fertige" genommen werden kann
		}			
	}
}

Eine besondere Frage zu den Zahlen nach den r.init() -Aufrufen:
Soweit ich das verstanden habe, macht jeder Core in der Grafikkarte eine eigene Berechnung und ruft dabei aus den CLMem - Objekten denjenigen Index auf, dem er zugeteilt worden ist. Da aber nur begrenzt viele Cores zur Verfügung stehen muss ich das ja irgenwie beschränken (Momentan mit counter > x)
Wie krieg ich aber genau raus, wieviele Cores ich benutzen darf?

Ich danke jedem, der sich auch nur die Mühe gemacht hat, das alles hier duchzulesen :toll:

Gruß eMmiE
 

Ruzmanz

Top Contributor
Ein haufen Threads sind sicherlich nicht vom Vorteil, aber das ist nicht das Problem. Wenn du ein Programm unendlich lange ohne Pause arbeiten lässt, dann ist er nunmal 100% beschäftigt. Unabhängig von der Anzahl der Threads. Wo genau in deinem Programm hast du berücksichtigt, dass das Betriebsystem evtl. auch noch Rechenzeit benötigt?

Ich habe den Code nicht verstanden, aber so viele while(true) sind definitiv kein guter Stil. Das lässt sich normalerweise auch anders lösen!

Typische Fehler, wie das initialisieren von Objekten / Arrays in Schleifen. Sowas braucht seine Zeit.

Kopieren von Arrays mit Schleifen ... System.arraycopy();

Sieht so aus, als wäre das Array sortiert. Eine Binäre Suche geht definitiv schneller:
Java:
		//	jetzt sortieren
		qs.parallelSortieren(sum, plains);
				
		//--> Sehr kurz =)
					
		counter = sum.length-1;
		while(sum[counter] > this.renderwidth) {
			counter--;
		}
 

Androbin

Bekanntes Mitglied
Ruzmanz hat gesagt.:
Ein haufen Threads sind sicherlich nicht vom Vorteil, aber das ist nicht das Problem. Wenn du ein Programm unendlich lange ohne Pause arbeiten lässt, dann ist er nunmal 100% beschäftigt. Unabhängig von der Anzahl der Threads. Wo genau in deinem Programm hast du berücksichtigt, dass das Betriebsystem evtl. auch noch Rechenzeit benötigt?

Java:
while ( true ) {
	
	try   { Thread.sleep( 10 ) }
	catch ( InterruptedException e ) { }
	
	doLogic();
	
	repaint();
	
}
 

eMmiE

Bekanntes Mitglied
@Ruzmanz:
Binäre Suche werde ich reinbauen.
Meinst du mit den zu vielen while(true)s die in der Methode oder generell auf die run() Methoden bezogen?
 

Ruzmanz

Top Contributor
Habe grade keine Zeit es zu testen, probiers einfach aus:
Java:
		float[] work = new float[256];
		int counter = mids.length;
		while(counter >= 256) { // Einen Schleifendurchlauf mehr, da der else-Zweig entfällt.
			System.arraycopy(mids, counter - work.length, work, 0, work.length);
			work = this.r.mult(work, work);
			System.arraycopy(work, 0, mids, counter - work.length, work.length);
			counter -= 256;
		}
		// Sofern es den Fall counter < 256 gibt:
		if(counter < 256) {
			work = new float[counter];
			System.arraycopy(mids, counter, work, 0, counter);
			work = this.r.mult(work, work);
			System.arraycopy(work, 0, mids, 0, work.length);
		}
 

Ruzmanz

Top Contributor
Hmm, das müsste dann aber
Code:
if(counter > 0 && counter < 256)
lauten? Außer das "mids"-Array hat immer ein Vielfaches von 256 (d.h. 0, 256, 512, ...), dann kann man auch das if-Statement weglassen.
 
Zuletzt bearbeitet:

eMmiE

Bekanntes Mitglied
Nein,


ich hab bei der while Schleife counter > 256 und dann bei if counter != 0, das geht schon so

Aber darum geht es ja gar nicht, es geht darum, dass das Programm momentan noch sehr inperformant ich ganz gerne doch mehr als 2 FPS habe. Da ich mich nicht wirklich damit auskenne, wie man jetzt Runnables umgehen kann (evtl. damit, dass man die Grafiker-/ Physiker- /etc.-Threads einfach als Kernels definiert und dann ALLES direkt auf der GPU rechnet (... gar nicht mal so ne blöde Idee...:oops:)) wollte ich hier eher die Grundsturktur und Umsetzung hinterfragen...
Was sollte ich auf jeden Fall sein lassen?
Was hat schon immer gut funktioniert ?(merke: ich wills selbst bauen und so gut es geht APIs auslassen (JMonkeyEngine, ...), die das ALLES für mich machen...
Tipps/Anregungen, aber auch gerne wa zum Code direkt

Danke für bisherige Antworten

Gruß eMmiE
 
Zuletzt bearbeitet:

eMmiE

Bekanntes Mitglied
Ich habe jetzt mal die Laufzeiten von den einzelnen Programmteilen ausgeben lassen und da geht der größte Teil der Zeit dafür drauf, den Speicherplatz für die einzelnen Buffer-Objekte (FloatBuffer) zu belegen

Es handelt sich speziell um die Methode CL10.clEnqueueWriteBuffer.
Kann man da etwas anderes machen oder ist das eben so?

Kann man das irgendwie umgehen/ anders machen oder ist das etwas, das eben seine Zeit dauert?
 
Zuletzt bearbeitet:

eMmiE

Bekanntes Mitglied
Dass clEnqueueWirteBuffer so viel Zeit benötigt, um den Speicherplatz zu sichern

Allgemein zielt der Thread hier ja darauf ab, dass ich was lerne :)

Wie machst du/ macht ihr das denn, sodass es funktioniert?
Ich dachte eigentlich erst, dass das Programm durch die Benutzung von OpenCL wesentlich schneller würde, aber zeittechnisch hat sich kein Unterschied hervorgetan

Gruß eMmiE
 

Neue Themen


Oben