# Wie rendert Java2D/AWT BufferedImages



## eddgedd (12. Feb 2012)

Hallo,

ich beschäftige mich im Moment mit der Programmierung eines 2D-Spieles.
Bisher habe ich für die Grafische Ausgabe Java2D benutzt.
Ich habe ein BufferedImage mit dem Typ INT_RGB erstellt, dann via ((DataBufferInt)image.getRaster().getDataBuffer()).getData() ein int[] besorgt und dort dann die Pixel gesetzt. Anschliesend habe ich das BufferedImage mit BufferStrategy in ein Canvas gerendert.
Dies funktionierte auch super (ich erhielt eine Bildwiederholrate von mehr als 200 FPS), jedoch nahm die Performance mit einer Erhöhung der Auflösung ab. 

Deshalb habe ich LWJGL ausprobiert, da man sich damit den Umweg über Java2D spart und direkt auf OpenGL zugreifen kann.
In Java2D habe ich bisher meine Sprites mit ImageIO geladen, dann die Pixel besorgt und gespeichert. Diese Bilder enthielten aber nur verschiedene Graustufen. Beim Rendern eines Sprites habe ich dann die einzelnen Pixel manipuliert und "eingefärbt" (z.B mit einem &-Operator und einem Farbwert) und dann in das int[] Array des Hauptsbildes geschrieben. So konnte ich viele verschieden farbige Sprites zeichen ohne viel Speicher zu verbrauchen. Nun habe ich dies versucht in LWJGL umzusetzen, leider vergebens. Ich habe ein DataBuffer erzeugt es versucht mit glDrawPixels zu rendern. Dies hat zwar funktioniert, war aber viel zu langsam. 

Und nun meine Frage : Wie schafft es Java2D die BufferedImages relativ schnell zu rendern und gleichzeitig einen Zugriff auf die Pixel zu erlauben. Ich meine, wenn man jederzeit die Pixel verändern kann, dann müssten die Daten doch im Haupspeicher liegen, oder ?

Habe schon überall nach einer Antwort gesucht und im OpenJDK-Quellcode gestöbert.

Ich hoffe ihr könnt mir helfen. Danke.


----------



## Marco13 (12. Feb 2012)

In verschiedenen Threads (z.B. in http://www.java-forum.org/spiele-multimedia-programmierung/120318-performance-bufferedimages.html und den von dort aus verlinkten) hatte ich schonmal geschrieben, dass das Zeichnen von BufferedImages viel mit Magie zu tun hat. Ein Stichwort ist dabei "Managed Images". GROB vereinfacht(!) sind das Bilder, die im Grafikkartenspeicher gehalten werden können. Ein direkter Zugriff auf die Pixel (so, wie du es beschrieben hast) kann dann dazu führen, dass die Bilder "unmanaged" werden, und das Zeichnen danach deutlich langsamer wird. (Allerdings (und diese Aussage mit VIEL Vorbehalt: ) hatte ich manchmal den Eindruck, dass das nicht immer so sein muss)

Für eine genauere Analyse, warum der Ansatz mit LWJGL langsamer war, bräuchte man wohl mehr Code. (Und ein KSKB, wo man mit möglichst wenig Code drumerhum die Vergleiche machen kann, wäre wohl auch für dich für die Analyse praktisch). Beim Zeichnen mit OpenGL kommt ja auch noch die Frage dazu, welches Format die Daten innerhalb von OpenGL haben. Ein paar Worte dazu wurden in http://www.java-forum.org/spiele-mu...ment-exception-glteximage2d-2.html#post861519 oder http://www.java-forum.org/spiele-mu...xtur-verschiedenen-bit-depths.html#post856337 gesagt. (Wenn Fancy sich endlich mal registrieren bzw. wieder http://www.java-forum.org/members/11768.html benutzen würde, könnte man da viel leichter danach suchen  )


----------



## Guest2 (12. Feb 2012)

Moin,

wenn man das mit OpenGL realisieren möchte, gibt es dazu mindestens tausende verschiedene Varianten das zu implementieren. Vermutlich sind allerdings leider auch die schnellsten nicht nur die kompliziertesten, sondern auch die, welche von deinem bisherigen Ansatz am meisten abweichen.

Wenn es nur um das reine Darstellen einer CPU generierten Grafik geht, dann ist Java2D und BufferedImage gar nicht so schlecht (abgesehen von den Hürden, die Marco erwähnt hat). Mit OpenGL lässt sich vielleicht ein wenig rausholen, damit das aber wirklich (evtl. viel) schneller wird, muss man weniger auf der CPU und mehr auf der GPU arbeiten.

(OT: Ich suche meistens nach Viele Grüße Fancy um meine eigenen Beiträge wiederzufinden, dass erzeugt erstaunlich wenig rauschen )

Viele Grüße,
Fancy


----------



## Spacerat (12. Feb 2012)

Autch... möcht' ich mal sagen, denn möglicherweise ging des dann so... (ohne Codeanalyse... "...dann in das int[] Array des Hauptsbildes geschrieben..." hat dich eigentlich schon genug verraten. )
1. Bild laden
2. Bildinhalt in ein int-Array kopieren und dort halten
3. int-Array in einen DirectBuffer wandeln bzw. kopieren (in LWJGL für Texturen bzw. Bilder notwendig).
4. DirectBuffer mit entsprechender Methode an Graka übergeben.
5. Elemente des int-Arrays modifiziert
6. Loop 3.

um einiges performanter aber wäre das:
1. Bild laden
2. Bildinhalt in ein int-Array kopieren und dort halten
3. int-Array in einen DirectBuffer wandeln bzw. kopieren (in LWJGL für Texturen bzw. Bilder notwendig).
4. DirectBuffer mit entsprechender Methode an Graka übergeben.
5. Elemente des DirectBuffers modifiziert
6. Loop 4.

Winzige Kleinigkeiten sind essentiel... ein Kopiervorgang (DirectBuffer -> Graka) ist stets performanter als zwei (+ int-Array -> DirectBuffer).
[EDIT]@Marco13: Wer hatte mir anno dazumal eigentlich sagen müssen, dass man in DirectBuffer nicht wrappen kann (http://www.java-forum.org/spiele-mu...ngl-vernuenftige-tutorial-2.html#post852619)? Naja... spätestens bei solchen Experimenten meinerseits, fiels mir auch so auf.
@TS: Ach ja... um die eigentliche Frage zu beantworten, wie Java2D das so schnell macht; Dazu muss man tiefer in die verschiedenen JVMs einsteigen... Bei der aktuellen Oracle/SUN-JVM geschieht das alles native über einen SurfaceManager.[/EDIT]


----------



## eddgedd (12. Feb 2012)

Erstmal danke für die Antworten. 

@Spacerat Den Teil den du genannt hast bezog sich auf ein BufferedImage. Dort kann man direkt in das Array schreiben, so das sich das Bild ändert.
Bezüglich LWJGL hast du natürlich recht. Dort ist es wahrscheinlich performanter, direkt den Buffer zu modifizieren.

Mir ist auch klar, dass es nativ gerendert wird. Ich dachte nur, weil Java2D auch OpenGL nutzt , muss es doch möglich sein das alles ungefähr gleich performant zu realisieren. Deshalb würde ich gern wissen, welche OpenGL-Funktionen Java2D benutzt um die Pixeldaten, die wie bereits von  @Marco13 genannt "unmanaged" sind und somit sich im Hauptspeicher befinden, mit immerhin 120 FPS zu rendern(Bei einem 800x600 großem Bild ohne Skalierung). Mir erscheint da glDrawPixel sinnvoll, deshalb habe ich einen einfachen Test ausgeführt :


```
import java.nio.ByteBuffer;
import java.util.Random;

import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;


public class Test {
	
	public static void main(String[] args) throws LWJGLException, InterruptedException {
		//Initialisieren des Bildschirms
		Display.setDisplayMode(new DisplayMode(800,600)); 
		Display.create(); 								  
		//Erstellung des Buffers
		ByteBuffer buffer = BufferUtils.createByteBuffer(800*600*3); 
		
		if(buffer.hasArray()) {
			new Random().nextBytes(buffer.array());  
		} else {
			byte[] array = new byte[800*600*3];
			new Random().nextBytes(array);             
			buffer.put(array); 						   
			buffer.flip();
		}
		
		int frames = 0;
		long fps = System.nanoTime();
		// Loop
		while(!Display.isCloseRequested()) {
			
			GL11.glDrawPixels(800,600,GL11.GL_RGB,GL11.GL_BYTE,buffer);
			
			Display.update();
			
			frames++;
			long now = System.nanoTime();
			if(now-fps > 1000000000) {
				System.out.println(frames);		//Ausgabe der FPS
				fps = now;
				frames = 0;
			}
		}

		Display.destroy();
	}
	
}
```

Dieser liefert bei mir 70-90 FPS und ist somit langsamer als die Java2D Variante.

Vielleicht gibt es auch bessere Varianten von glDrawPixel (habe es auch mit GL_RGB und GL_UNSIGNED_INT_8_8_8_8 probiert, liefert aber nur ein schwarzes Bild). 

Würde mich über Tipps freuen


----------



## Spacerat (12. Feb 2012)

Hmm... Java2D kann keine unmanaged Images rendern... kannst es ja mal selbst versuchen...
	
	
	
	





```
class MyImageImpl
extends java.awt.Image
{
// ...benötigte Methoden rin... bla bla... unwichtig und Zeugs
}
```
Bei jedem [c]Graphics.drawImage()[/c] krachts da gewaltig... nur bei vollständig geladenen ToolkitImages und bei BufferedImages nicht. Beide bekommen bei ihrer Instanzierung einen SurfaceManager, möglicherweise ist's sogar stets der selbe... kein Plan... auf jeden bekommen sie den von der nativen Seite.
Ob Java2D OpenGL benutzt sei mal dahingestellt, ich glaube im übrigen kaum daran. In Windows würde sich nämlich DirectDraw mehr als nur anbieten. Auf jeden Fall aber verwendet die JVM zwangsläufig einen BS-kompatiblen SurfaceManager.


----------



## Guest2 (12. Feb 2012)

@eddgedd:

Dein Beispiel läuft hier mit etwa 1700 FPS. (Ein Texturequad wird mit etwa 6000 FPS gerendert)

Grundsätzlich ist glDrawPixels aber (heute) nicht "richtig schnell", da es einen synchronen Datentransfer zwischen CPU und GPU benötigt (meistens ist es trotzdem schnell genug). Ein PBO (Streaming Texture Uploads
) kann das jedoch auch asynchron und passt damit besser zur heutigen GPU Pipeline.

Imho ist der Anwendungsfall das Bild auf der CPU zu berechnen und OpenGL nur zur Darstellung zu nutzen aber vermutlich nicht ideal.

Viele Grüße,
Fancy


----------



## eddgedd (12. Feb 2012)

@Fancy und @Spacerat : Ihr habt recht. OpenGL ist dafür einfach nicht dafür geeignet. Wie es sich herausgestellt hat, benutzt Java2D im Normalfall auf Windows gar kein OpenGL sondern Direct3D. Und als ich per -Dsun.java2d.opengl=true und 
-Dsun.java2d.d3d=false Java2D gezwungen hab OpenGL zu benutzen, ist die Performance rapide auf 20-30 FPS gesunken.

Damit hat sich der Fall erledigt. Dann muss ich eben auf individuel eingefärbte Sprites verzichten. 

Trotzdem nochmals Danke für eure Hilfe. Ihr habt mir sehr geholfen.


----------



## Spacerat (12. Feb 2012)

[OT]Kleine Korrektur... 
Also kurz gesagt, ich weis beim besten Willen nicht, wo bei den in Graphics verwendbaren Bildern der SurfaceManager herkommt, geschweige denn, wann er initialisiert und/oder, wenn überhaupt, wieder gekillt wird. Das einzige, was ich herausfinden konnte, war der Name der konkreten Klasse in Windows. Dort heisst er [c]D3DCachingSurfaceManager[/c], wobei die ersten drei Buchstaben mit wenig Phantasie tatsächlich auf die Verwendung von DirectX schliessen lassen.
Zu dumm, dass bei diesen Tests nun auch meine Annahme dahin ist, es läge am besagten Manager, warum sich eigene Klassen, die [c]java.awt.Image[/c] erweitern, nicht per [c]Graphics.drawImage()[/c] anzeigen bzw. rendern lassen. [/OT]
[EDIT]@TS: Diesen Text habe ich kaum in 3 Minuten verfasst... die Idee hatte ich also schon vor deinem Post. Aber selbst wenn dem so ist... ich jedenfalls gebe nicht auf [/EDIT]


----------



## Guest2 (12. Feb 2012)

Direct3D und OpenGL unterscheiden sich in der Geschwindigkeit nicht wesentlich.

Warum OpenGL beim TO so langsam ist, kann ich allerdings nicht sagen. Wie bereits geschrieben, das Beispiel läuft hier mit 1700 FPS auf ner veralteten ehemals 120€ Budget Karte...

Und ich meinte auch nicht das OpenGL dafür nicht geeignet ist, sondern das, wenn man OpenGL dafür verwenden möchte, man wohl sein ganzes Programm/Verfahren darauf ausrichten muss. Und eben nicht nur das reine Darstellen eines CPU generierten Images.

Viele Grüße,
Fancy


----------

