When using VolatileImage, keep reference to BufferedImage?

Hi,

When using java2D’s accelerated VolatileImages to show images loaded from the disk, do you keep the reference to the original BufferedImage loaded from the disk? Or once you draw it to the VolatileImage, do you let it be garbage collected. So if you’re VolatileImage ever needs repainting, you read the BufferedImage from the disk again and repaint it on the VolatileImage.

I was just wondering because I’m making a java2D game with a lot of images and when holding onto all of the BufferedImages the game’s memory usage goes up quite a bit. On the other hand, if i throw away the references to the BufferedImages then I’ll have to read them back again from the disk if the VolatileImage needs repainting which might cause a delay…

I was just wondering what everyone else does.

It seems pretty rare that VolatileImages need re-painting because of lost contents, these are the things that make them ‘Volatile’ and cause them to need re-painting (from Chet Haase’s old blog http://weblogs.java.net/blog/chet/archive/2003/09/volatileimage_n.html):
* Another app going into fullscreen mode
* The display mode changing on your screen (whether caused by your application or the user)
* TaskManager being run
* A screensaver kicking in
* The system going into or out of StandBy or Hibernate mode

Thanks,
keith

If those are the only conditions that the native surface gets lost, definitely store the images on disk. The user did something so ‘drastic’ that he certainly will accept some delay in continuing with your app.

You might however print to System.out when you actually need to repaint your volatile image (maybe on minimizing / restore?).

Cases where the surface is lost are “relatively” rare (meaning, not several times per second) - once every once in a while. Unless you have multi-megabyte images, or your users are over a slow connection and your images are loaded from network, I think it’s safe to drop your bufferedimages and load them if the surface is lost.

Cool, thanks a lot Riven and Dmitri.

I’ll do that then. I was actually getting OutOfMemoryErrors when using java2D’s software pipeline (as opposed to the d3d pipeline) and I think it was because the BufferedImages and VolatileImages were being stored in RAM (rather than the VolatileImages being stored in VRAM). Letting the BufferedImages go should halve the memory requirements. I just wanted to make sure it’s the right thing to do.

Thanks heaps

:persecutioncomplex: ;D

Anyway, if you’re going to serialize them, don’t use ImageIO. Just convert your int[] to a byte[] and dump it to disk using a RandomAccessFile (be sure to raf.setLength(long); raf.sync(); first, so that the filesystem doesn’t have to grow your file every few writes).

You’ll have to feed your data back into your BufferedImage, which will never be accelerated when you access its pixels, so create 2 BufferedImages: once to feed it your pixels, once to draw the first on the second. Then flush() the first.

Ok i did it and memory use in software mode halved, so that’s great. And in d3d mode the game hardly uses any memory, I guess it’s all in VRAM.

Gee, sounds like you’ve learned that from experience! :slight_smile: Thanks, but I’m not having to serialize them, the images are packaged in the jar file.

By the way, I pretty much stay clear of BufferedImages for rendering and only use VolatileImages because BufferedImage acceleration is really flaky - seems like there’s 3 levels of acceleration in java2d:

  • not accelerated - like a BufferedImage that has it’s raster modified like what you said.
  • BufferedImage accelerated - some kind of half-way acceleration that seems to either not work, break very easilly or is just slower than VolatileImage acceleration.
  • VolatileImage acceleration - very fast and not breakable so long as you throw in a lot of code to check that it hasn’t lost its contents.

Surely BufferedImages should simply be a wrapped VolatileImage, that handles all of the contents lost/restored exceptional circumstances for you.

If you implement that all yourself, you’ll end up using twice as much VRAM than is necessary - The VolatileImage; the BufferedImage that you use to restore the VolatileImage, and a 2nd VolatileImage that is allocated internally for accelerating the drawing of the BufferedImage onto vram resident surfaces.

:edit:

Oh; nvm just read the first 4 posts of the Thread and realized i’ve repeated what you’d already discovered ;D

You’re right, except I think the VolatileImage keeps the data in VRAM or RAM, not both AFAIK.

I read in Sun’s VolatileImage guide that the VI’s can’t handle all repainting etc automatically because there’s no way to know when the VolatileImage will lose its contents on Windows. So we have to manually paint it ourselves which is unavoidable I suppose.

If it’s helpful to anyone, here’s some code I use to take care of all of the VolatileImage rendering. My game sprite objects just hold a reference to the AcceleratedImage and draw the VolatileImage returned by acceleratedImage.getAndCheckVolatileImage(). All VolatileImages are held in the static ImageBank so there’s only one copy rather than having lots floating around wasting memory.


import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.net.*;
import javax.imageio.*;

/**
 *
 * @author Keith
 */
public class ImageBank{
	static HashMap<String, VolatileImage> volatileImageMap = new HashMap<String, VolatileImage>();
	static String imageDirName = "images/";

	public ImageBank(){
	}
	
	static public void loadAndPutImages(String[] relativeURLNames){
		for (String relativeURLName : relativeURLNames){
			loadAndPutImageFromRelativeURLString(relativeURLName);
		}
	}

	static public void loadAndPutImageFromRelativeURLString(String relativeURLName){
		VolatileImage volatileImage = null;
		volatileImageMap.put(relativeURLName, volatileImage);
	}
	static public void putVolatileImage(String relativeURLName, VolatileImage volatileImage){
		volatileImageMap.put(relativeURLName, volatileImage);
	}
	static public VolatileImage getVolatileImage(String relativeURLName){
		return volatileImageMap.get(relativeURLName);
	}
	public static HashMap<String, VolatileImage> getVolatileImageMap() {
		return volatileImageMap;
	}
	static public BufferedImage readBufferedImage(String relativeURLName){
		URL imageURL = ImageBank.class.getResource(imageDirName + relativeURLName);
		BufferedImage bufferedImage;
		try{
			bufferedImage = ImageIO.read(imageURL);
		}catch(IOException e){
			e.printStackTrace();
			return null;
		}
		System.out.println("ImageBank.loadAndPutImageFromRelativeURLString(): relativeURLName == "+relativeURLName+", w == "+bufferedImage.getWidth()+", h == "+bufferedImage.getHeight());
		return bufferedImage;
	}

	public static VolatileImage createVolatileImage(int width, int height, int transparency) {	
		GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
		GraphicsConfiguration gc = ge.getDefaultScreenDevice().getDefaultConfiguration();
		VolatileImage image = null;

		image = gc.createCompatibleVolatileImage(width, height, transparency);

		int valid = image.validate(gc);

		if (valid == VolatileImage.IMAGE_INCOMPATIBLE) {
			image = createVolatileImage(width, height, transparency);
		}
		//System.out.println(ImageBank.class.getSimpleName() + ": created new VolatileImage");
		return image;
	}
	public static VolatileImage createTransparentVolatileImage(int width, int height) {
		VolatileImage image =  ImageBank.createVolatileImage(width, height, Transparency.TRANSLUCENT);
		Graphics2D g = (Graphics2D)image.getGraphics();
		g.setColor(new Color(0,0,0,0));
		g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OUT));
		g.fillRect(0, 0, image.getWidth(), image.getHeight());
		return image;
	}

}


import java.util.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.geom.*;
import sydneyengine.shooter.*;
import java.net.*;
/**
 *
 * @author woodwardk
 */
public class AcceleratedImage extends SSAdapter{
	String imageURLString;
	int width;
	int height;

	public AcceleratedImage(){
		width = -1;
		height = -1;
	}
	public AcceleratedImage(String imageURLString){
		this.imageURLString = imageURLString;
		// record the width and height
		VolatileImage volatileImage = getAndCheckVolatileImage();
		width = volatileImage.getWidth();
		height = volatileImage.getHeight();
	}

	protected void drawOntoVolatileImage(VolatileImage vImage, BufferedImage bufferedImage){
		Graphics2D g = vImage.createGraphics();
		g.drawImage(bufferedImage, 0, 0, null);
		g.dispose();
	}
	public VolatileImage getAndCheckVolatileImage(){
		//System.out.println(this.getClass().getSimpleName() + ": drawOntoImage(), "+this.getIndexX()+", "+this.getIndexY()+", time == "+v.getWorld().getTimeNowSeconds());
		VolatileImage vImage = ImageBank.getVolatileImage(imageURLString);
		GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
		GraphicsConfiguration gc = ge.getDefaultScreenDevice().getDefaultConfiguration();
		if (vImage == null || vImage.validate(gc) != VolatileImage.IMAGE_OK) {
			BufferedImage bufferedImage = ImageBank.readBufferedImage(imageURLString);
			vImage = ImageBank.createTransparentVolatileImage(bufferedImage.getWidth(), bufferedImage.getHeight());
			ImageBank.putVolatileImage(this.imageURLString, vImage);
			drawOntoVolatileImage(vImage, bufferedImage);
		}
		do {
			int valid = vImage.validate(gc);
			if (valid == VolatileImage.IMAGE_INCOMPATIBLE) {
				BufferedImage bufferedImage = ImageBank.readBufferedImage(imageURLString);
				vImage = ImageBank.createTransparentVolatileImage(bufferedImage.getWidth(), bufferedImage.getHeight());
				ImageBank.putVolatileImage(this.imageURLString, vImage);
				drawOntoVolatileImage(vImage, bufferedImage);
			}
		} while (vImage.contentsLost());
		return vImage;
	}

	public String getImageURLString() {
		return imageURLString;
	}

	public int getHeight() {
		return height;
	}

	public int getWidth() {
		return width;
	}

}

Actually, that’s not true.

BufferedImage -> texture
VolatileImage -> pBuffer (or maybe FBO these days?)

[quote] * BufferedImage accelerated - some kind of half-way acceleration that seems to either not work, break very easilly or is just slower than VolatileImage acceleration.
[/quote]
Could you elaborate on this? If system memory is not an issue BIs should be just as fast as VIs, but w/o the hassle of managing it yourself.

When I used to try using just BufferedImages, if I painted too many translucent pixels to the BufferedImage it would make it really slow all of a sudden, it’s as if the BufferedImage would lose its acceleration. That was before java6 update 10, so I’ll try it again and let you know.

A couple of points: until 6u10 translucent BI weren’t accelerated by default (unless you enabled opengl pipeline, or the old d3d pipeline).
Second point is that if you render to the buffered image and then copy from it, it won’t be accelerated - it requires at least one copy from such image in order for it to be cached in vram. So buffered images are better used for sprites.

As someone mentioned: VI == pbuffer/fbobject (ogl) or RTT (d3d), BI == texture (d3d,ogl)

BTW, translucent VIs weren’t accelerated until 6u10 either.

That makes sense, thanks. I just replaced the VI’s with BI’s in my game and got the same performance on a 6u10 VM so that’s great, you’re right.

I think you could accelerate VI’s with a pre 6u10 JVM using VM options like -Dsun.java2d.d3d=true and translaccel=true but they’re non-standard (http://java.sun.com/j2se/1.5.0/docs/guide/2d/flags.html).

Is there difference between a texture and a pbuffer (ogl) or an RTT (d3d)? I guess they’re the same?

thanks,
keith

PBuffer/FBO has higher overhead.

Thanks, i googled them too and they seem pretty much the same except one is more recent than the other.