Java2d too slow for 2d games ?

This is part of some test driver code i made for java2d:


package game2d.tests;

import game2d.imp.java2d.Java2DGameCanvas;
import game2d.imp.java2d.Java2DSpriteFactory;
import game2d.imp.java2d.Java2DSprite;

import game2d.space_invaders.GameStatistics;

import java.awt.Graphics2D;
import java.awt.Color;
import javax.swing.JFrame;


public class Java2DSpriteDrawTest {
	
	public static void main(String[] args) {
		new Java2DSpriteDrawTest();
	}
	
	private Java2DSprite sprite;
	private JFrame frame;
	private Java2DGameCanvas canvas;
	Java2DSpriteFactory factory; 
	
	public Java2DSpriteDrawTest() {
		canvas = new Java2DGameCanvas(800,600);
		frame = Java2DGameCanvas.createFrame("",canvas,null);
		factory = Java2DSpriteFactory.getSingleton();
		frame.pack();
		frame.setVisible(true);
		start();
	}
	
	private Graphics2D g2;

	public void start() {
		sprite = Java2DSpriteFactory.getSingleton().getSprite("sprites/pleg.png");
		GameStatistics.start();
		//g2 = canvas.getDrawGraphics();
		while (true) {
			GameStatistics.update();
			frame.setTitle(""+GameStatistics.fpsAvg);
			update();
			draw();
		}
		
	}
	
	private double rot = 0.0;
	
	public void update() {
		rot += Math.PI/48.0;
	}
	
	public void draw() {
		if (GameStatistics.skipTime < 0.0) {
			System.err.println("gc");
			return;
		}
		System.err.println(GameStatistics.skipTime);
		g2 = canvas.getDrawGraphics();
		g2.setColor(Color.black);
		int w = canvas.getWidth();
		int h = canvas.getHeight();
		g2.fillRect(0,0,w,h);
		g2.translate(w/2.0,h/2.0);
		g2.rotate(rot);
		g2.drawImage(sprite.getImage(), 0, 0, null);
		g2.dispose();
		canvas.flip();
	}
	
}

I have set GameStatistics to update every 0.01 seconds, that is, 10 milliseconds and even so sometimes the test driver is skips drawing phases. Sometimes i get three skips in a row and i assumed it was because of garbage collection.


0.006366804002027493
0.007434334998833947
0.00677970600372646
0.007861329002480488
0.005753377001383342
0.007702932998654433
0.007847218003007583
gc
0.009391393003170379
gc
0.009567401000822429
gc
0.00907418400311144
0.005560730998695362
0.007317281000723597
0.0060004970073350705
0.005643210002745036
0.00722158500138903
0.006457108000176959
0.006683585001155734
0.0074173340035486035

It sucks that we have to dispose of the graphics context once rendering is finished. Is there a more eficient way to do this?

And how do we rotate an image around it’s center and not around its upper left corner using drawImage ?

What have you done to tune the GC?
What collector are you using?
What parameters?
What heap size (min/max)?

I would suggest -Xincgc as a first step.

I know this is just a test, but if its smooth rotation of the image that you want then your update method could increment the rotation angle according to the time elapsed since the last draw. This will improve the percieved performance by mitigating the GC impact (to some extent).

I’m disappointed with Java2D & its speed at doing image draws with transforms as well. Especially how scale(1, -1) causes a loss in hardware acceleration when computationally its hardly different to scale(1,1).

Keith

Well…did u use compatible images using GraphicsConfiguration class? Also, if u use BufferStrategy for your buffering,then u can get a lot better results.

Not sure for transformations though…

I developed a small game framework which use those classes and results are excellent.

And one more tip: g.fillRect is sooo slow. If u want to clear the screan make compatible image(black or in some other color) and then draw it instead of filling that rect. I got so much better results that way.

I didn’t mention before but my GameStatistics class ensures that every frame runs for 0.01 seconds, that is 10 milliseconds. I think it’s more than enough to render a small sprite 64x128 in a 800x600 window with true color with a P4 cpu at 3.5Ghz.

The sleep time to fill the 0.01 seconds comes from the GameStatistics.skipTime that is the amount of time the test driver should sleep until the next frame unless this value is negative and in that case the previous frame took more time to render than 0.01 secs. I only update the demo and don’t render anything if that happens.

The gc message is my assumption that the vm is doing it. But if all frames render at aprox. the same time and sudenly this changes drasticaly i think it’s a correct assumption. The amount of time actualy taken is given by GameStatistics.delta.

Incremental gc doesn’t help much.


0.005844679999881919
0.007263820999924064
0.0049673539999730565
0.005739612000070338
0.007528194000087751
0.005259940999849277
0.00736063399995146
0.006153570999913427
gc
0.009264918999861038
gc
0.009364926999978707
gc
0.009271336999972846
0.006534094000016921
0.005809746999830168
0.00570100499999171
0.006461263999881339
0.004781491999892751
0.005934910999940257
gc
0.009576323999908709
0.0061994320001304
0.00721217099999194
0.007307025999807593
0.007299325999838402
0.0073759420001806575
0.00609945099995457
0.007529798999939885
0.00751998900000217
0.007272851999914565

I also tried opengl with incgc and without. The results are much more stable but the incgc option didn’t change anything. This makes me think the opengl pipeline is much more clean and useful for games.

-Xincgc -Dsun.java2d.opengl=true -Dsun.java2d.noddraw=true

Too bad i got an error message from the vm at the end.


0.008450592000031065
0.008485658999916268
0.008762990000036552
0.008470515999988493
0.008741382000039266
0.008797603000061827
0.008421126000030199
0.008671305000007123
0.008520152000073722
0.00817648699990059
0.008380279999983031
0.008691365000004225
0.008460996999929193
0.008536808999906498
0.00833767100004934
0.008686945000022206
0.008864044000006288
0.008501833000082115
#
# An unexpected error has been detected by HotSpot Virtual Machine:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x692fbeb7, pid=168, tid=644
#
# Java VM: Java HotSpot(TM) Client VM (1.5.0_05-b05 mixed mode)
# Problematic frame:
# C  [atioglxx.dll+0x2fbeb7]
#
# An error report file with more information is saved as hs_err_pid168.log
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
#

Instead of using opengl i used other ddraw acelerration options:

-Xincgc -Dsun.java2d.accthreshold=0 -Dsun.java2d.ddforcevram=true -Dsun.java2d.ddscale=true -Dsun.java2d.translaccel=true

But suprise it’s even worse than without these options. I get pauses every 20 frames.


0.007271000999935495
0.007598432999884608
0.006706985999926474
0.007864446999974462
0.007741243000054965
0.006455146000007517
0.007628755000041565
0.007571890999997777
0.007202250000091226
0.005876687000181846
0.006207119999999122
0.007491126000104487
0.0059896360000948334
gc
0.009619745999998486
0.006371826999838959
0.007604398000012225
0.007343829000092228

Now with only:

-Xincgc -Dsun.java2d.ddscale=true

It’s the same thing.

I changed the GameStatistics.frameDuration value from 0.01 to 0.003 (333 frames per second) and i got a pause around every 40 frames still using opengl. Using ddraw would suckup completely.

Setting an absurd amount of ram didn’t help neither the incgc helped a lot. I think incgc is better to avoid very long pauses. In a 2d animation even a small instability will be visible on the animation.

With a 0.003 period i still get a flood of pauses like this ocasional with incgc. It shows on screen with the ship moving with small jumps:


0.0011539669994817814
0.0018680029997995007
gc
0.0025743050000528456
gc
0.0026281499995093327
0.0018487560000721714
6.664609991275938E-4
gc
0.0025961749997804873
0.0016215959994951845

So should we use jogl instead for 2d games? I will make some tests later to see how a jogl based 2d game performs. I must say that java2d accelerated with the opengl option is not that bad.

Edit: I lalso removed the fillRect to clean the background but didn’t notice any big change.

Heres the code of the test driver if you want to do the tests:


package game2d.tests;

import game2d.imp.java2d.Java2DGameCanvas;
import game2d.imp.java2d.Java2DSpriteFactory;
import game2d.imp.java2d.Java2DSprite;

import game2d.space_invaders.GameStatistics;

import java.awt.Graphics2D;
import java.awt.Color;
import javax.swing.JFrame;


public class Java2DSpriteDrawTest {
	
	public static void main(String[] args) {
		new Java2DSpriteDrawTest();
	}
	
	private Java2DSprite sprite;
	private JFrame frame;
	private Java2DGameCanvas canvas;
	private Java2DSpriteFactory factory;
	private int w;
	private int h;
	
	public Java2DSpriteDrawTest() {
		canvas = new Java2DGameCanvas(800,600);
		frame = Java2DGameCanvas.createFrame("",canvas,null);
		factory = Java2DSpriteFactory.getSingleton();
		frame.pack();
		frame.setVisible(true);
		start();
	}
	
	private Graphics2D g2;

	public void start() {
		sprite = Java2DSpriteFactory.getSingleton().getSprite("sprites/pleg.png");
		GameStatistics.start();
		//g2 = canvas.getDrawGraphics();
		w = canvas.getWidth();
		h = canvas.getHeight();
		while (true) {
			GameStatistics.update();
			frame.setTitle(""+GameStatistics.fpsAvg);
			update();
			draw();
		}
		
	}
	
	private double rot = 0.0;
	
	public void update() {
		rot += Math.PI/48.0;
	}
	
	public void draw() {
		if (GameStatistics.skipTime < 0.0) {
			System.err.println("gc");
			return;
		}
		System.err.println(GameStatistics.skipTime);
		g2 = canvas.getDrawGraphics();
		g2.setColor(Color.black);
		g2.fillRect(0,0,w,h);
		g2.translate(w/2.0,h/2.0);
		g2.rotate(rot);
		g2.drawImage(sprite.getImage(), 0, 0, null);
		g2.dispose();
		canvas.flip();
	}
	
}


package game2d.imp.java2d;

import game2d.GameCanvas;
import game2d.GameCallback;

import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Java2DGameCanvas extends Canvas implements GameCanvas 
{
	public static JFrame createFrame(String title, Java2DGameCanvas canvas, 
			GameCallback cb) {
		JFrame frame = new JFrame(title);
		final GameCallback callback = cb;
		JPanel panel = (JPanel) frame.getContentPane();
		panel.setPreferredSize(canvas.getPreferredSize());
		panel.setLayout(null);
		canvas.setBounds(0,0,canvas.getPreferredSize().width,
				             canvas.getPreferredSize().height);
		panel.add(canvas);
		canvas.setIgnoreRepaint(true);
		frame.setResizable(false);
		frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				if (callback != null) {
					callback.windowClosed();
				} else {
					System.exit(0);
				} } });
		frame.addNotify();
		canvas.requestFocus();
		canvas.createBufferStrategy(2);
		return frame;
	}

	private int width;
	private int height;
	
	public Java2DGameCanvas(int width, int height) {
		this.width = width;
		this.height = height;
		setPreferredSize(new Dimension(width,height));
	}
	
	public Graphics2D getDrawGraphics() {
		return (Graphics2D) getBufferStrategy().getDrawGraphics();
	}

	public void flip() {
		getBufferStrategy().show();
	}

	public int getWidth() {
		return width;
	}
	
	public int getHeight() {
		return height;
	}
	
}


package game2d.imp.java2d;

import game2d.imp.java2d.Java2DSprite;
//import game2d.imp.java2d.Java2DGameCanvas;

import java.util.HashMap;
import java.io.IOException;
import java.net.URL;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;

public class Java2DSpriteFactory 
{
	private static Java2DSpriteFactory single;
	
	public static Java2DSpriteFactory getSingleton() {
		if (single == null) {
			 single = new Java2DSpriteFactory();		
		}
		return single;
	}
	

	private HashMap<String,Java2DSprite> store;
	//private Java2DGameCanvas canvas;

	private Java2DSpriteFactory() {
		store = new HashMap<String,Java2DSprite>();
	}
	
	//public Java2DGameCanvas getGameCanvas() {
	//	return canvas;
	//}

	//public void setGameCanvas(Java2DGameCanvas canvas) {
	//	if (canvas == null) throw new IllegalArgumentException();
	//	this.canvas = canvas;
	//}

	//public Java2DSprite getSprite(String key) {
	//	return getSprite(canvas, key);
	//}

	public Java2DSprite getSprite(
			//Java2DGameCanvas canvas, 
			String key) {
		//if (canvas == null) throw new IllegalArgumentException();
		if (key == null) throw new IllegalArgumentException();
		Java2DSprite aSprite = store.get(key);
		if (aSprite == null) {
			BufferedImage sourceImage = null;		
			try {
				URL url = this.getClass().getClassLoader().getResource(key);			
				if (url == null) {
					System.err.println("Can't find ref: " + key);
					System.exit(0);
				}
				sourceImage = ImageIO.read(url);
			} catch (IOException e) {
				System.err.println("Failed to load: " + key);
				System.exit(0);
			}
			GraphicsConfiguration gc = 
				GraphicsEnvironment.getLocalGraphicsEnvironment().
				getDefaultScreenDevice().getDefaultConfiguration();
			Image image = gc.createCompatibleImage(
				sourceImage.getWidth(),sourceImage.getHeight(),
				Transparency.BITMASK);
			image.getGraphics().drawImage(sourceImage,0,0,null);
			//aSprite = new Java2DSprite(canvas, image);
			aSprite = new Java2DSprite(image);
			store.put(key, aSprite);
		}
		return aSprite;
	}

}


package game2d.imp.java2d;

import game2d.Sprite;
//import game2d.GameCanvas;
import java.awt.Image;

public class Java2DSprite implements Sprite 
{
	private Image image;
	//private Java2DGameCanvas canvas;
	
	public Java2DSprite(
			//Java2DGameCanvas canvas,
			Image image) {
		//if (canvas == null) throw new IllegalArgumentException();
		if (image == null) throw new IllegalArgumentException();
		this.image = image;
		//this.canvas = canvas;
	}
	
	public int getWidth() {
		return image.getWidth(null);
	}

	public int getHeight() {
		return image.getHeight(null);
	}
	
	//public GameCanvas getGameCanvas() {
	//	return canvas;
	//}

	public Image getImage() {
		return image;
	}

	public void rescale(int w, int h) {
		if (w <= 0) throw new IllegalArgumentException();
		if (h <= 0) throw new IllegalArgumentException();
		Image scaledImage = image.getScaledInstance(w,h,Image.SCALE_DEFAULT);
		image = scaledImage;
	}
	
}


package game2d.space_invaders;

import game2d.util.Timer;

public class GameStatistics {

	public static Timer timer = Timer.getSingleton();
	public static double frameDuration = 0.003;
	public static double skipTime;
	public static double delta;
	public static double lastUpdate;
	public static double lastFpsTime;
	public static int fps;	
	public static int fpsAvg;	

	public static void start() {
		lastUpdate = timer.getTime();
	}
	
	public static void update() {
		skipTime = (lastUpdate + frameDuration) - timer.getTime();
		if (skipTime > 0 && skipTime < frameDuration) timer.sleep(skipTime);
		delta = timer.getTime() - lastUpdate;
		lastUpdate = timer.getTime();
		lastFpsTime += delta;
		fps++;
		if (lastFpsTime >= 1.0) {
			lastFpsTime = 0.0;
			fpsAvg = (fpsAvg + fps) / 2;
			fps = 0;
		}
	}

}


package game2d.util;

public class Timer 
{
	private static Timer timer;

	public static Timer getSingleton() {
		if (timer == null) {
			timer = new Timer();
		}
		return timer;
	}
	

	private Timer() {
	}
	
	public double getTime() {
		return System.nanoTime() / 1000000000.0;
	}
	
	public void sleep(double duration) {
		if (duration < 0) return;
		try{
			Thread.sleep((long)(duration*1000.0),0);
		} catch(InterruptedException e) { } 
	}

}

Rotating around the center point of the image requires you to set the, what I call, the “anchor” point.

By default, this “anchor” point is 0,0 … meaning it’s rotating around the top-left corner. But since you want to rotate around the middle, you need to find the centre point of your image, and you need to use rotate(angle, anchorX, anchorY) … like this:



g.rotate(angle, image.getWidth()/2, image.getHeight()/2);
g.drawImage(image);
g.rotate(-angle, image.getWidth()/2, image.getHeight()/2);


appel, be careful about rotating back. I think you should backup the transform before rotating, then restore it so you’re sure the floating point system won’t lead to off-by-infinitely-little errors. This could result in Java2D wasting time doing almost invisible (ideally nonexistent) rotations. Basically you can never be sure of these things with floating point numbers, which is why I would advice against risking it.

Java2D is not slow, as far as i experienced. In the same vein, i didn’t find GC to cause problems. Did you profile for the reasons of your pauses? Not that i exclude GC to be the cause, but i wonder what is the cause of the long GC. I would suggest that you use the GC log options to get rapid idea of the importance of GCs and their type. (fullGC?)
You can show pretty bunches of alpha composited sprites realtime zoomed and rotated in a game that runs without hiccups, even under heavy (heavy heavy) GC conditions. I did that, and there’s no magic, not even a single GC/VM option tweak.

yeah this point cant be emphasised enough, if you don’t store the transform before the rotation, and restore it afterwards, your code is quite simply broken.

Could you try not using getScaledInstance() and create a scaled copy as described here:
http://java.sun.com/products/java-media/2D/reference/faqs/index.html#Q_How_do_I_create_a_resized_copy
This may be the reason the image doesn’t get accelerated.

The crash on ATI is a known drivers bug, sorry.

Also, could you try on mustang with only one option: -Dsun.java2d.d3d=true ?

Thanks,
Dmitri

Thanks appel that did the trick. Heres the new code:


public void draw() {
	if (GameStatistics.skipTime < 0.0) {
		System.err.println("*skip*");
		System.err.println(GameStatistics.delta + "  " + GameStatistics.skipTime);
		return;
	}
	System.err.println(GameStatistics.delta + "  " + GameStatistics.skipTime);
	g2 = canvas.getDrawGraphics();
	g2.setColor(Color.black);
	g2.fillRect(0,0,w,h);
	g2.translate((w-sw)/2.0,(h-sh)/2.0);
	g2.rotate(rot,sw/2.0,sh/2.0);
	g2.drawImage(sprite.getImage(), 0, 0, null);
	g2.dispose();
	canvas.flip();
}

I think the problem is that i have to dispose the gc everytime i finish drawing and flip the scene. I tried using a global gc by obtaining it from the strategy right before the loop but once the scene is fliped the gc becomes worthless.

With -verbose:gc the results show that there are farmes being skiped because they just take a tad longer but this is normal.


0.0029285519995028153  0.0018841809942387044
*skip*
0.0031606579941581003  -1.5700399671914056E-4
0.0036874939978588372  0.00227994399756426

When gc kicks in it’s usualy a huge impact on the animation:


[ParNew 3968K->1053K(16320K), 0.0165145 secs]
*skip*
0.01942424500157358  -0.016419088002294302

The animation has acumulated around 3MB of trash in just a couple of seconds and it took 0.016 seconds. For a frame duration of 0.003 this is a lot.

If you have the time to provide a small demo that would be cool. Maybe we can turn this into a performance guide or a peformance tuning tutorial.

I will try that and post the results tomorrow.

Yes, certainly. just click the jnlp link in my signature. feel free to report the informations that will be copied into your clipboard after the gamechmark ends.
If you want more informations on the project, go to my home page and read the pdf.

Just looking at your code, a likely cause of the lag may actually be becuase of inaccuracies in Thread.sleep(int) which you call every frame.

Sometimes Thread.sleep() sleeps for longer than you want, and if it does your code will skip that frame, won’t it?

On my Windows XP box, I’ve found that Thread.sleep() is entirely unpredictable. Passing in 1 or 2,3,4,5,… can all cause sleeps of 2 milliseconds. For larger sleeps, there seem to be discrete amounts that sleep will actually do (for example between 20 and 29 ms sleep arguments, sleep occurs for 20ms).

See if that’s the problem by finding out how often sleep(…) overshoots.

I think you hit a spot here. Sleep is, in fact, very inacurate and has a huge latency. This is starnge because there is a sleep method with a milliseconds and a nano seconds argument that sugest this method would be much more accurate than it realy is. When working with 1 millisecond frames sleep causes much unnecessary skips.

I change the GameStatistics code to measure sleep latency.

And by the way i also changed the sprite rescale method according to the java2d faq but it isn’t interfering with the demo.

Heres the version of the update method that uses sleep. The comented code inside the skip loop doesn’t use sleep. Instead it loops while probing the timer until the skip time is over.


package game2d.space_invaders;

import game2d.util.Timer;

public class GameStatistics {
	
	public static Timer timer = Timer.getSingleton();
	public static double frameDuration = 0.001;
	public static double skipTime;
	public static double skipDelta;
	public static double skipLatency;
	public static double updateTime;
	public static double updateDelta;
	public static double fpsTime;
	public static double fpsDelta;
	public static int fps;	
	public static int fpsAvg;	

	public static void start() {
		updateTime = timer.getTime();
		skipTime = updateTime;
		fpsTime = updateTime;
	}
	
	public static void update() {
		skipTime = timer.getTime();
		skipDelta = (updateTime + frameDuration) - skipTime;
		skipLatency = 0.0; 
		if (skipDelta > 0 && skipDelta < frameDuration) {
			timer.sleep(skipDelta);
			/*
			for (double d = timer.getTime(); d - skipTime < skipDelta;
					d = timer.getTime()) 
				;
			*/
			skipLatency =  timer.getTime() - skipTime - skipDelta;
		}
		updateDelta = skipDelta + skipLatency;
		updateTime  = timer.getTime();
		fpsTime += updateDelta;
		fps++;
		if (fpsTime >= 1.0) {
			fpsTime = 0.0;
			fpsAvg = (fpsAvg + fps) / 2;
			fps = 0;
		}
	}

}

These are some measure for a 1 millisecond frame duration using Thread.sleep:


-0.000332384  -0.000332384  0.000000000
0.001888388  0.000319831  0.001568557
*skip*
-0.000198140  -0.000198140  0.000000000
0.001184005  0.000455106  0.000728899
*skip*
-0.000213290  -0.000213290  0.000000000
0.001170110  0.000454082  0.000716028
*skip*
-0.000280316  -0.000280316  0.000000000
0.001065140  0.000418216  0.000646924
*skip*
-0.000271146  -0.000271146  0.000000000
0.001098201  0.000448745  0.000649456
*skip*
-0.000164873  -0.000164873  0.000000000
0.001227444  0.000471801  0.000755643
*skip*
-0.000198070  -0.000198070  0.000000000
0.001181951  0.000453062  0.000728889
*skip*
-0.000843403  -0.000843403  0.000000000

The problem here is that Thread.sleep has a latency of almost one millisecond ariound 0.0007 most of the time. That is the time sleep wastes besides the amount he has to.

Rotating the sprite and drawing it on screen actualy takes a bit more that half a millisecond so the demo should run flowlessly with a 1 millisecnd frame. Let’s recal im using a 800x600 window with true color and a translucent sprite of 64x128 so it’s not as bad as i tought.

Among all this garbage collecting is not the biggest problem. I think Pepe is right on this. gc is not a big problem until we start choosing frames bellow 1 millis.


0.001461790  0.000521001  0.000940789
*skip*
-0.001702895  -0.001702895  0.000000000
[ParNew 5022K->1056K(16320K), 0.0014588 secs]
0.001272824  0.000256475  0.001016349
*skip*
-0.002082955  -0.002082955  0.000000000

Just 0.00145 for a gc is not that bad considering it only hapens ocasionaly between 100 frame cycles sort of. Most skips are cause by the innacuracy of the sleep method.

Now commenting the sleep method and using the brute force aproach:


-0.000062952  -0.000062952  0.000000000
0.000444169  0.000443458  0.000000711
*skip*
-0.000096172  -0.000096172  0.000000000
0.000527536  0.000526755  0.000000781
*skip*
-0.000030175  -0.000030175  0.000000000
0.000535903  0.000535127  0.000000776
*skip*
-0.000066581  -0.000066581  0.000000000
0.000524665  0.000523716  0.000000949
*skip*
-0.000067761  -0.000067761  0.000000000
0.000527942  0.000526841  0.000001101
*skip*
-0.001124621  -0.001124621  0.000000000
*skip*
-0.000004088  -0.000004088  0.000000000
0.000557728  0.000556999  0.000000729
*skip*

Latency is in the order of nano seconds so the brute force skip time method is very accurate. But im still geting too many skips.

On another note i tried the mustang version and the d3d pipeline. It looks like it has some advantages mostly related to garbage collection that happens a lot faster. I will post some results tomorrow.

PS: Maybe i should log the times in an ArrayList and print them later instead of using System.out.println()

Sorry if I ask that provocant but doesn’t anybody consider a deisgn which relies on gc cycles < 1ms broken?
To be honest I would rely on nothing less than 50ms keeping in mind all the different OS/JVM/Hardware combinations out there.

lg Clemens

What do you mean by this?

The title is “Java2D too slow for games” and after reading further I found tons of GC timing informations and worries about when a GC takes longer than some very low limits.
Furthermore its mentioned that hardcoded time-points are used (10ms) and that some frames are skipped due to garbage collection.
Its my believe that a design which relies on that low limits is broken, maybe the guy who needs this should reconsider other timing/animation algorythmns.

lg Clemens

After tweeking my tests a bit and removing the Thread.sleep() inacuracies aparently gc isn’t such a problem IF the values that verbose:gc outputs are the all thing. I think gc may even provide a performance improvement by doing what a pooler would for you. The incremental gc appears to work in another thread waking up ocasionaly to clean up things a bit. Unless we don’t abuse of memory allocation this may work very well.