Graphics2D and AffineTransform Questions

Hello,

I have a few questions:

  1. Is it OK to set an AffineTransformation on a Graphics2D (with g2d.transform) and then use g2d.rotate()? Or should I modify the AffineTransformation object? If I should NOT use g2d.rotate in this case and I DID, would it have the potential to cause performance/corruption issues?

  2. Is it OK to use the same BufferedImage multiple times during scaling/rotation?
    For example, in my render loop:
    g2d.transform(at); //at contains data for scaling and translation. The scaling and translation can change as the user zooms/drags

//Now I want to rotate the image.
g2d.rotate(rotate, x1, y1);
//Draw the rotated image.
g2d.drawImage(bufferedImage, x2, y2, null);
//Restore the rotation
g2d.rotate(-rotate, x1, y1);

//Now do it again with the same image but some place else
g2d.rotate(rotate, x3, y3);
//Draw the rotated image.
g2d.drawImage(bufferedImage, x4, y4, null);
//Restore the rotation
g2d.rotate(-rotate, x3, y3);

This seems to work fine, but there are rare times when the performance degrades significantly. It is very hard to produce and I am trying to narrow down the reason why it is happening. I suspect I am doing something gravely wrong.

Edit1:
3. Does the AT/G2D modify the image data for scaling, translation, or rotation? I suspect my image data may be getting corrupt or something after awhile some times (it still looks fine on the screen though…)…which may result in longer draw times??
Edit2:
I wrote some code to remove all of the images and reload new ones if the poor performance was detected. This had absolutely no impact on the drawing of the image - it still performed badly on the new ones.
/Edit2
/Edit1

Thanks.

The image data never gets modified so it can’t get corrupt. G2D’s rotate/scale/transform methods call its AffineTransform object so manually setting an AffineTransform will do the same thing as calling G2D’s methods.

However, I don’t know what could cause the performance hit since these methods work fine for me.

  1. It’s best to use a single transform object per image, this way you only need one matrix (affinetransform is a 3x3 matrix)

  2. It’s perfectly fine to use the same bufferedimage multiple times during drawing.

  3. To rotate back, it’s best to save an instance of the current transform like so:


AffineTransform ot = g.getTransform();
g.setTransform(..);
// do your drawing shit
g.setTransform(ot); // restore old transform

as for your image performance issues, it could sometimes be due to the pixels of the image not being compatible with the pixels of the surface you are drawing to… this can be solved with the following code:


    public static BufferedImage toCompatibleImage(BufferedImage image, boolean override) {
		GraphicsConfiguration gfxConfig = GraphicsEnvironment
				.getLocalGraphicsEnvironment()
				.getDefaultScreenDevice()
				.getDefaultConfiguration();
        if (image.getColorModel().equals(gfxConfig.getColorModel()) && !override) {
            return image;
        }
        BufferedImage newImage = gfxConfig.createCompatibleImage(image.getWidth(), image.getHeight(), image.getTransparency());
        Graphics2D g2d = newImage.createGraphics();
        g2d.drawImage(image, 0, 0, null);
        g2d.dispose();

        return newImage;
    }

HTH

Thanks for your replies.

I got lucky and was able to reproduce the problem. This resulted in some data.

But first, a couple additional notes:

  1. I am running on Windows 7 64 bit with the latest JRE from Oracle:
    java.runtime.version=1.6.0_25-b06.
    Here is the VM version:
    java.vm.version=20.0-b11

I have an ATI 5770 HD card.

  1. I am running with the following flags on:
    -Dsun.java2d.translaccel=true
    -Dsun.java2d.accthreshold=0
    -Dsun.java2d.ddforcevram
    -Dsun.java2d.d3d=true
    -Dsun.java2d.ddscale=true

Maybe I have a bad combination of settings here. My knowledge in this area is little and I am learning as I go along. I am trying to accelerate as much as possible. I have NO Volatile Images because I am drawing on a JPanel.

I have checked to see if my images are hw accelerated and the method returns true. I do really get amazing performance when things go correctly. There are times where I can draw 600+ translucent images with a 0 MS draw time (Yes, I am using nano time for my benchmarking)!! This is fantastical.

  1. I am creating the BufferedImages with the following code:

public static BufferedImage loadBufferedImage(String imageFile)
	{
		BufferedImage bufferedImage;
		try
		{
			Image image = ImageIO.read(new File(imageFile));
			GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
			GraphicsConfiguration gc = ge.getDefaultScreenDevice().getDefaultConfiguration();
			bufferedImage = gc.createCompatibleImage(image.getWidth(null), image.getHeight(null), Transparency.TRANSLUCENT);
			
			Graphics2D g2d = (Graphics2D)bufferedImage.createGraphics();

			g2d.drawImage(image, 0, 0, null);
			g2d.dispose();
			return bufferedImage;
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	
		return null;
	}

So it is very similar to what you have (though it looks like you are doing a convert if it is not a compatible image). 99% of my images have partial transparency. There are some images, namely, icons for the user interface that do not but I use the same method to load them. Would this cause the problem?

As mentioned at the beginning of the reply, I did get some more data. The program runs fine and then “something happens”. Then the performance goes down the tubes. The funny thing is, it happens at the same point within in the program even if I reload new images from disk. For example, every 500th image seems to take an extra 100 MS to rotate, draw, and rotate. But all of the ones before and after it, take less than 1 MS. I have not acquired more data yet to see if this is how it behaves each time the performance goes down. Maybe this was a one time instance and there are other times that multiple images take additional times. I had an idea where I would detect long rotate, draw and rotate times and remove the image from the list to be drawn. Then I would see if my performance went back up to normal.

Here are some numbers I acquired:

drawImage only:
Scale of 0.4f:


graphicLocations: before(9472): : 4 MS.
graphicLocations(drawn: 149. Transform checks: 0): after: 24 MS.     (This took 20 MS to draw: 24-4=20)
(I drew 149 out of 9472 images)

rotate, drawImage, rotate:
Scale of 1.0f:


graphicLocations: before(9472): : 4 MS.
graphicLocations(drawn: 44. Transform checks: 0): after: 138 MS.    (This took 134 MS to draw: 138-4=134)
(I drew 44 out of 9472 images)

Note: I was not able to get the behavior at the same scale because the game will only rotate if you are zoomed in close enough to see it. But the evidence looks fairly strong.

Here are some numbers when everything is working fine:

rotate, drawImage, rotate:
Scale of 1.0f:


graphicLocations: before(9472): : 0 MS.
graphicLocations(drawn: 23. Transform checks: 0): after: 0 MS.

drawImage only:
Scale of 0.05f:


graphicLocations: before(9870): : 0 MS.
graphicLocations(drawn: 6672. Transform checks: 0): after: 9 MS.     !!!!! 9 MS to draw over 6000 images.

I looked at the GC and this is the report when execution stops:


Heap
 def new generation   total 127936K, used 45871K [0x04590000, 0x0d060000, 0x14590000)
  eden space 113728K,  35% used [0x04590000, 0x06cfe700, 0x0b4a0000)
  from space 14208K,  38% used [0x0c280000, 0x0c7dd6e8, 0x0d060000)
  to   space 14208K,   0% used [0x0b4a0000, 0x0b4a0000, 0x0c280000)
 tenured generation   total 284228K, used 223198K [0x14590000, 0x25b21000, 0x34590000)
   the space 284228K,  78% used [0x14590000, 0x21f87888, 0x21f87a00, 0x25b21000)
 compacting perm gen  total 12288K, used 2821K [0x34590000, 0x35190000, 0x38590000)
   the space 12288K,  22% used [0x34590000, 0x348517f0, 0x34851800, 0x35190000)
    ro space 10240K,  54% used [0x38590000, 0x38b0e770, 0x38b0e800, 0x38f90000)
    rw space 12288K,  55% used [0x38f90000, 0x39634ac8, 0x39634c00, 0x39b90000)

It seems the GC collects around 100 MB per collection (it seems to be around 1-2 seconds). The amount it collects goes up slightly (maybe 5-10MB) when things start to go bad.

Here is the GC collection report for a short run when everything went fine:


Heap
 def new generation   total 123136K, used 54532K [0x04590000, 0x0cb20000, 0x14590000)
  eden space 109504K,  45% used [0x04590000, 0x07659e98, 0x0b080000)
  from space 13632K,  33% used [0x0bdd0000, 0x0c247500, 0x0cb20000)
  to   space 13632K,   0% used [0x0b080000, 0x0b080000, 0x0bdd0000)
 tenured generation   total 273416K, used 204071K [0x14590000, 0x25092000, 0x34590000)
   the space 273416K,  74% used [0x14590000, 0x20cd9db8, 0x20cd9e00, 0x25092000)
 compacting perm gen  total 12288K, used 1866K [0x34590000, 0x35190000, 0x38590000)
   the space 12288K,  15% used [0x34590000, 0x34762830, 0x34762a00, 0x35190000)
    ro space 10240K,  54% used [0x38590000, 0x38b0e770, 0x38b0e800, 0x38f90000)
    rw space 12288K,  55% used [0x38f90000, 0x39634ac8, 0x39634c00, 0x39b90000)

Finally, here are some Java2D traces:
Here is the trace for when everything went bad (I ran the program for around 10 minutes):


7025 calls to D3DDrawLine
649 calls to sun.java2d.loops.Blit::Blit(FourByteAbgr, SrcNoEa, IntArgb)
1 call to sun.java2d.d3d.D3DSurfaceToGDIWindowSurfaceBlit::Blit("D3D Surface", AnyAlpha, "GDI")
990959 calls to sun.java2d.loops.MaskFill::MaskFill(AnyColor, Src, IntRgb)
11150 calls to sun.java2d.loops.Blit$GeneralMaskBlit::Blit(IntArgbPre, SrcOverNoEa, IntRgb)
11150 calls to sun.java2d.loops.MaskBlit::MaskBlit(IntArgbPre, SrcOver, IntRgb)
20748 calls to sun.java2d.loops.FillRect::FillRect(AnyColor, SrcNoEa, AnyInt)
55583 calls to sun.java2d.d3d.D3DTextureToSurfaceTransform::TransformBlit("D3D Texture", AnyAlpha, "D3D Surface")
2082 calls to sun.java2d.loops.DrawGlyphList::DrawGlyphList(AnyColor, SrcNoEa, AnyInt)
432305 calls to D3DFillAAParallelogram
5062 calls to D3DDrawRect
6000 calls to sun.java2d.loops.MaskBlit::MaskBlit(IntArgbPre, SrcOver, IntArgbPre)
78977 calls to sun.java2d.loops.Blit$GeneralMaskBlit::Blit("D3D Surface (render-to-texture)", SrcNoEa, IntRgb)
1 call to sun.java2d.windows.GDIBlitLoops::Blit(IntRgb, SrcNoEa, "GDI")
78977 calls to sun.java2d.d3d.D3DSurfaceToSwBlit::Blit("D3D Surface", SrcNoEa, IntArgb)
261632 calls to sun.java2d.d3d.D3DMaskFill::MaskFill(OpaqueColor, SrcNoEa, "D3D Surface")
64 calls to sun.java2d.loops.Blit::Blit(ThreeByteBgr, SrcNoEa, IntArgbPre)
763031 calls to sun.java2d.loops.TransformHelper::TransformHelper(IntArgbPre, SrcNoEa, IntArgbPre)
7 calls to D3DCopyArea
78977 calls to sun.java2d.loops.MaskBlit$General::MaskBlit("D3D Surface (render-to-texture)", SrcNoEa, IntRgb)
10732 calls to sun.java2d.loops.DrawRect::DrawRect(AnyColor, SrcNoEa, AnyInt)
301365 calls to D3DDrawGlyphs
35578 calls to sun.java2d.loops.DrawGlyphListLCD::DrawGlyphListLCD(AnyColor, SrcNoEa, IntRgb)
78977 calls to sun.java2d.loops.MaskBlit::MaskBlit(IntArgb, AnyAlpha, IntRgb)
1 call to sun.java2d.loops.Blit::Blit(ByteIndexed, SrcNoEa, IntArgbPre)
649 calls to sun.java2d.loops.Blit$GeneralMaskBlit::Blit(FourByteAbgr, SrcOverNoEa, IntArgbPre)
10488 calls to sun.java2d.d3d.D3DTextureToSurfaceBlit::Blit("D3D Texture", AnyAlpha, "D3D Surface")
66838 calls to sun.java2d.loops.MaskFill::MaskFill(AnyColor, Src, IntArgbPre)
649 calls to sun.java2d.loops.MaskBlit::MaskBlit(IntArgb, SrcOver, IntArgbPre)
649 calls to sun.java2d.loops.MaskBlit$General::MaskBlit(FourByteAbgr, SrcOverNoEa, IntArgbPre)
3901 calls to sun.java2d.d3d.D3DSwToSurfaceBlit::Blit(IntRgb, AnyAlpha, "D3D Surface")
6000 calls to sun.java2d.loops.Blit$GeneralMaskBlit::Blit(IntArgbPre, SrcOverNoEa, IntArgbPre)
10595 calls to D3DFillRect
2400240 calls to sun.java2d.d3d.D3DTextureToSurfaceScale::ScaledBlit("D3D Texture", AnyAlpha, "D3D Surface")
70261 calls to sun.java2d.d3d.D3DRTTSurfaceToSurfaceBlit::Blit("D3D Surface (render-to-texture)", AnyAlpha, "D3D Surface")
1062 calls to sun.java2d.d3d.D3DSwToTextureBlit::Blit(IntArgbPre, SrcNoEa, "D3D Texture")
36437 calls to D3DDrawAAParallelogram
25771 calls to sun.java2d.loops.DrawLine::DrawLine(AnyColor, SrcNoEa, AnyInt)
5864573 total calls to 38 different primitives

Here is a short trace for when everything went fine:
(I didn’t do much here so there are less calls overall)


6000 calls to sun.java2d.loops.Blit$GeneralMaskBlit::Blit(IntArgbPre, SrcOverNoEa, IntArgbPre)
590642 calls to sun.java2d.d3d.D3DTextureToSurfaceScale::ScaledBlit("D3D Texture", AnyAlpha, "D3D Surface")
154 calls to sun.java2d.d3d.D3DSwToTextureBlit::Blit(IntArgbPre, SrcNoEa, "D3D Texture")
14541 calls to D3DDrawGlyphs
262 calls to sun.java2d.d3d.D3DTextureToSurfaceBlit::Blit("D3D Texture", AnyAlpha, "D3D Surface")
635 calls to sun.java2d.loops.MaskBlit::MaskBlit(IntArgb, SrcOver, IntArgbPre)
661 calls to sun.java2d.d3d.D3DTextureToSurfaceTransform::TransformBlit("D3D Texture", AnyAlpha, "D3D Surface")
3593 calls to sun.java2d.d3d.D3DRTTSurfaceToSurfaceBlit::Blit("D3D Surface (render-to-texture)", AnyAlpha, "D3D Surface")
635 calls to sun.java2d.loops.MaskBlit$General::MaskBlit(FourByteAbgr, SrcOverNoEa, IntArgbPre)
635 calls to sun.java2d.loops.Blit::Blit(FourByteAbgr, SrcNoEa, IntArgb)
21920 calls to D3DFillAAParallelogram
210 calls to D3DFillRect
1 call to D3DDrawRect
65580 calls to sun.java2d.loops.MaskFill::MaskFill(AnyColor, Src, IntArgbPre)
83878 calls to sun.java2d.d3d.D3DMaskFill::MaskFill(OpaqueColor, SrcNoEa, "D3D Surface")
1927 calls to D3DDrawAAParallelogram
6000 calls to sun.java2d.loops.MaskBlit::MaskBlit(IntArgbPre, SrcOver, IntArgbPre)
635 calls to sun.java2d.loops.Blit$GeneralMaskBlit::Blit(FourByteAbgr, SrcOverNoEa, IntArgbPre)
61 calls to sun.java2d.loops.Blit::Blit(ThreeByteBgr, SrcNoEa, IntArgbPre)
797970 total calls to 19 different primitives

Well that is all of the information I have now. It is a lot because I am trying to really track down this issue. I will put in the code to perform a convert on the buffered image to the correct transparency and see if that improves things.

Well, thanks for reading and I hope that you guys can find an error in the information I provided. I am going to continue working on this with the new buffered image code provided and try to get some additional data, etc.

Thanks again!

I decided to make a new post instead of editing my last one because it contains so much information.

I put in the code to ensure the right buffered image and it did not have any impact.

I kept that code in and then removed -Dsun.java2d.ddforcevram. I am currently playing the game without any issue and it has passed the point where it “usually” occurs. I don’t know if this is just a fluke (I’m sure it’ll happen now that I said it isn’t…you know how these things work!) or something but I will have to keep trying it.

I am googling ddforcevram to see if I can learn more on its specifics and see if this is an issue. But, feel free to provide information :slight_smile:

Thanks.

EDIT:
I did not encounter the issue (hurra!) and ended up quitting - this was the longest I have been able to play without the performance degradation. Here is the Java2D trace and the GC report:


737872 calls to D3DDrawLine
80 calls to D3DCopyArea
659 calls to sun.java2d.loops.Blit$GeneralMaskBlit::Blit(FourByteAbgr, SrcOverNoEa, IntArgbPre)
2549575 calls to sun.java2d.d3d.D3DMaskFill::MaskFill(OpaqueColor, SrcNoEa, "D3D Surface")
2750151 calls to D3DDrawGlyphs
1 call to sun.java2d.loops.Blit::Blit(ByteIndexed, SrcNoEa, IntArgbPre)
66916 calls to sun.java2d.loops.MaskFill::MaskFill(AnyColor, Src, IntArgbPre)
64 calls to sun.java2d.loops.Blit::Blit(ThreeByteBgr, SrcNoEa, IntArgbPre)
466 calls to sun.java2d.d3d.D3DSwToTextureBlit::Blit(IntArgbPre, SrcNoEa, "D3D Texture")
1 call to sun.java2d.d3d.D3DSwToTextureBlit::Blit(ByteIndexed, SrcNoEa, "D3D Texture")
6724 calls to sun.java2d.loops.MaskBlit::MaskBlit(IntArgbPre, SrcOver, IntArgbPre)
659 calls to sun.java2d.loops.Blit::Blit(FourByteAbgr, SrcNoEa, IntArgb)
321708 calls to sun.java2d.d3d.D3DTextureToSurfaceBlit::Blit("D3D Texture", AnyAlpha, "D3D Surface")
6724 calls to sun.java2d.loops.Blit$GeneralMaskBlit::Blit(IntArgbPre, SrcOverNoEa, IntArgbPre)
659 calls to sun.java2d.loops.MaskBlit::MaskBlit(IntArgb, SrcOver, IntArgbPre)
2714445 calls to D3DFillAAParallelogram
607091 calls to D3DFillRect
393209 calls to D3DDrawRect
578482 calls to sun.java2d.d3d.D3DRTTSurfaceToSurfaceBlit::Blit("D3D Surface (render-to-texture)", AnyAlpha, "D3D Surface")
22568154 calls to sun.java2d.d3d.D3DTextureToSurfaceScale::ScaledBlit("D3D Texture", AnyAlpha, "D3D Surface")
255622 calls to D3DDrawAAParallelogram
1 call to sun.java2d.d3d.D3DSwToTextureBlit::Blit(IntArgb, SrcNoEa, "D3D Texture")
659 calls to sun.java2d.loops.MaskBlit$General::MaskBlit(FourByteAbgr, SrcOverNoEa, IntArgbPre)
355730 calls to sun.java2d.d3d.D3DTextureToSurfaceTransform::TransformBlit("D3D Texture", AnyAlpha, "D3D Surface")
33915652 total calls to 24 different primitives


Heap
 def new generation   total 129024K, used 109940K [0x04590000, 0x0d190000, 0x14590000)
  eden space 114688K,  95% used [0x04590000, 0x0b0e93e8, 0x0b590000)
  from space 14336K,   0% used [0x0b590000, 0x0b593f10, 0x0c390000)
  to   space 14336K,   0% used [0x0c390000, 0x0c390000, 0x0d190000)
 tenured generation   total 286576K, used 235254K [0x14590000, 0x25d6c000, 0x34590000)
   the space 286576K,  82% used [0x14590000, 0x22b4d9c8, 0x22b4da00, 0x25d6c000)
 compacting perm gen  total 12288K, used 3139K [0x34590000, 0x35190000, 0x38590000)
   the space 12288K,  25% used [0x34590000, 0x348a0c30, 0x348a0e00, 0x35190000)
    ro space 10240K,  54% used [0x38590000, 0x38b0e770, 0x38b0e800, 0x38f90000)
    rw space 12288K,  55% used [0x38f90000, 0x39634ac8, 0x39634c00, 0x39b90000)

Glad you were able to solve your problem :slight_smile:

EDIT:
Here is something about that flag: http://download.oracle.com/javase/1.5.0/docs/guide/2d/flags.html#ddforcevram
Keeping an image in VRAM is quite expensive if you draw it a lot, so it is best to not have that flag :slight_smile:

Thanks for the link (and reply). I suspect that my video card was eventually filling up with memory because I was drawing SO many of them (Every 50 MS 15,000 images where checked and depending on what zoom I was at, it could range from 20 images to 9,000 being drawn) and it probably was starting to have issues from that. Just a guess.

I will check out your link you included. I am surely certain it will be useful to me.

Thanks again for your guys help.

EDIT (I love to edit posts): I have given you both appreciation. ;D 8)

I really hate to bring an old post back from the dead but I just wanted to give an update about this. It may help others.

In the end, the above changes did not help me. I still ran into the issue.

I could not figure it out to save my life. Then I had a friend of mine use my game and he said that when a tool tip appeared on the Close icon on a window, he noticed the slow down. So I tested this out and sure enough, the tool tips were causing the slow down! I was really surprised. I turned the tool tips off and then the problem went away.

I’m not really sure why the tool tips were causing issues with drawing but the problem is gone now and I am very pleased.

Maybe this is useful for someone else out there that are seeing weird slow downs.

Which tool tips?

Sounds like the tooltip added by the window manager. Could be a problem with the desktop compositor maybe?

I’m using JInternalFrames so whatever happens to be default to Swing and that component.

You’re using Swing for your games?!?!? O_O Why?!?

I wonder what kind of game that use JInternalFrame. I learned that the only one swing’s friend that you can use in game is JFrame and JPanel only.

Not even JPanel!
You should only use JFrame + Canvas. That’s it.

Why? ???

Why even use JPanel?

Also a JFrame is also overkill. java.awt.Frame + java.awt.Canvas is all you need. No need for the extra bloat of Swing if you’re not gonna use it.

I like LWJGL.

@theagentd
Completely irrelevant to this thread. >:(

Okay, I like LWJGL because I don’t have to use Swing or AWT to create a window. Happy?

;D