ddforcevram and VolatileImage

Hi folks! Another question about volatile images, I’m afraid.

I’ve been working on improving the frame rate of my game, and one thing I’ve tried is replacing buffered images by volatile ones.

Specifically, I’ve got a background tile that I modify once a frame, then draw 48 times so that it covers the entire screen with a repeated image. Since I’m modifying the individual pixels of the tile, I need it to be a BufferedImage object. (In addition, since I’m accessing the BufferedImage through its Raster, I’m creating it explicitly as TYPE_INT_RGB, rather than using createCompatibleImage().) Once the image has been modified, I’m drawing it to the screen (via BufferStrategy) multiple times, and I’d like it to be accelerated if possible. What I’ve tried is to copy the BufferedImage onto a VolatileImage after I’ve modified it, and to draw onto the screen using the latter.

So far so good? (If I’ve gone wrong already, please stop me now…)

What I find is that if I use a VolatileImage instead of the BufferedImage in this way, the frame rate drops massively (drawing the background is slower by something like a factor of four). However, if I set the parameter “sun.java2d.ddforcevram” to “true” then the situation improves, and the VolatileImage approach gives a slightly better frame rate than the original BufferedImage code (depending on what hardware I’m testing on).

Now, the hardware I’m using is relatively underpowered (e.g., PIII 400MHz, 8MB ATI graphics card), so perhaps I’m being optimistic in expecting to get any graphical acceleration out of it. (Other things I should mention: Windows 2000, Java 1.5.0, DirectX 9.)

What I’m concerned about is this: Do I need to always have “ddforcevram” set to true if I am using VolatileImage in order to get reasonable performance? And if so, what happens when the code runs on a non-Windows operating system?

Can anyone clarify this for me?

Thanks,
Simon

P.S. I’ll post example code if anyone wants to see it. But this rant seemed to be long enough already without it.

Hi,

Sorry my laptop keyboard is broken, so simply add the ‘h’ were its missing :wink:

I guess this is really bad news for you: BufferedImages wose raster is stolen will currently (and its very unlikely tat tis will cange soon) receive no accaleration at all. Wat you see wen drawing tose images are very slow system->surface blits. I really recommend to avaid tat as far as possible.

Your approac wit cacing te modified BufferedImages in Volatile images is a good design, you sould keep it. Tis way can can reac maximum possible performance.
If you don’t modify your BufferedImages very often you could also copy tem onto anoter BufferedImage wich raster is not stolen, this will give even better results when using the OpenGL pipeline (since ten textures instead of a pbuffer is used, don’t know how FBOs (or VBOs or PBOs??) perform in this scenario)

I tink your approach is a very good design and you sould keep it under all cirumstances. I guess tat its maybe slow since you VRAM is full anyway, terefor slow bus operations happen anyway.
However on systems wit 16mb vram or more you’ll definitivly see an improvment, not to talk about the new accalerated openg pipeline.
You could try to run your application with -Dsun.java2d.trace=log (or count). Maybe something is happening behind the scenes…

As far as I know such parameters are simply ignored when the default GDI/Ddraw/d3d pipeline isn’t used, so it sould ave no effect at all.
owever I would provide a way for users to configure tose options, tere are reasons wy tese parameters aren’t enabled by default :wink:
A better way (wic sould also ave effect for oter pipelines) could be to set accaleration priority to 1 (image.setAccalerationPriority(1)).
It would be interesting to hear wether this chages anything.

good luck, lg Clemens

Thanks for the reply! That’s made a lot of stuff much clearer for me.

That’s decided it! I’ll use the VolatileImage by default, set ddforcevram to true, and provide options to turn both of these things off if required.

It’s a pity that there doesn’t seem to be a way to do this that’s optimal for all hardware. But a thumbs-up from the Java2D forum is good enough for me!

How the VRAM is used is a complete mystery to me. According to getAvailableAcceleratedMemory(), my test program starts with about 2 meg (out of 8 meg) available to it, and never has less than about 1.4 meg remaining. So it’s not actually running out of VRAM – unless the remainder is reserved for something else I guess.

The difference when a VolatileImage replaces the BufferedImage seems to be that a “Win32::blit” is used instead of a “loops::blit”. I guess that’s what you’d expect. (There’s no change in the trace when I change the ddforcevram parameter… but again I guess that’s what you’d expect.)

Yes, I’m a bit nervous about turning on a parameter that’s off by default. Is there documentation anywhere that can tell me what the possible side-effects of setting ddforcevram to true are?

That’s something I haven’t tried. I’ll give that (and various other things) a go in the next day or two when I’ve got some time.

Thanks again,
Simon

P.S. The game with the missing letter ‘h’ was fun! Try missing out a different letter next time. :wink:

ddforcevram is needed because Windows will ‘punt’ your images (Volatile or BufferedImages) out of VRAM if it is reading from them too often to do stuff like alpha composite operations. This includes bitmask and translucent pixels in your image or even antialiased lines/shapes that you draw on the VRAM-ed image. see http://java.sun.com/j2se/1.5.0/docs/guide/2d/flags.html

It is a very frustrating thing when the image is swapped out since as you discovered the hardware acceleration is effectively killed and the frame rate dies down by 3/4. Especially on crap computers like the one you’re testing, you always need to specify this option for Java2D & also the Dsun.java2d.translaccel=true option too, so look it up in the above link as well.

I have a question: How can you specify these options with WebStart? Please answer in this thread: http://www.java-gaming.org/forums/index.php?topic=14092.0

Thanks,
Keith

I have 2 questions. Could u explain what do u excatly do?U said that u draw onto background tile, then u draw the tile using bufferedstrategy…any explanation, pseudocode maybe :smiley:

To lg clemens: what do u mean by saying that raster is stolen?

Tnx

where you get the raster from the image.

U mean by getting its graphics…ok.

But the thing he said that he drawn first onto buffered image and then to buffer strategy…i usually draw everything directly to bufferstrategy…is this good practice? Or should it be all drawn into bi and then to bs?

Tnx

By popular demand(!) I’ve copied the source code for my test program below.

The test comprises two elements: a background that is drawn by repeating an opaque tile image so that it fills the window, and a translucent sprite image that is drawn multiple times over that background.

This is supposed to be a cut-down version of a game like http://javagamesfactory.org/views/view-game?name=Starbugs.

The tile image is modified once per frame so that the background is animated. I do this by editing the data buffer (Raster) of the BufferedImage object, which is an array of ints. This is messier than calling setRGB() repeatedly, but I’ve found it to be faster. The drawback though is that the image cannot then be accelerated (see Linuxhippy’s reply above). I’m trying to get around this by copying the tile image onto a VolatileImage, and then blitting that repeatedly to the screen.

The main() function sets whether the background is drawn using the VolatileImage or the (unaccelerated) BufferedImage, and also sets some java2d parameters. Trying out different combinations, I get the following frames-per-second results.

  • UseVolatileImage=false, no java2d parameters : 40 fps
  • UseVolatileImage=true, no java2d parameters : 10 fps
  • UseVolatileImage=false, ddforcevram=true, translaccel=true : 37 fps
  • UseVolatileImage=true, ddforcevram=true, translaccel=true : 40 fps
  • UseVolatileImage=false, opengl=true : 47 fps
  • UseVolatileImage=true, opengl=true : 47 fps

As I said before, I’m testing this on a slow computer!

When I’ve tried it on more powerful machines I find that the combination of VolatileImage + ddforcevram gives a much more significant boost to the frame rate. (I hadn’t tried the “sun.java2d.opengl” parameter until now. It works rather well! How long until the OpenGL pipeline is enabled by default?)

Can anyone see anything I should be doing to the code to improve the frame rate? (I’ve tried playing with
Image.setAccelerationPriority() but that hasn’t helped.)

Does anyone see anything wrong with my choice of UseVolatileImage + ddforcevram + translaccel as the default set-up?

Cheers,
Simon

package Untitled;

import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.JFrame;

public class TestWindow extends JFrame implements Runnable {

  private static boolean mUseVolatileImage;
  
  public static void main(String args[]) {

    mUseVolatileImage = true;
    
    System.setProperty("sun.java2d.ddforcevram","true");
    System.setProperty("sun.java2d.translaccel","true");

    //System.setProperty("sun.java2d.opengl","true");
        
    System.setProperty("sun.java2d.trace","count");
    
    new TestWindow();

  }

  private BufferedImage mSprite = null;
  private BufferedImage mTile = null;
  private VolatileImage mVTile = null;

  private Thread         mMainLoop       = null; 
  private BufferStrategy mBufferStrategy = null;
  private Random         mRandom         = null;
  
  public TestWindow() {
    reportAcceleratedMemory();

    Canvas canvas = new Canvas();
    Dimension canvasDim = new Dimension(512, 384);
    canvas.setSize(canvasDim);
    canvas.setPreferredSize(canvasDim);
    canvas.setMinimumSize(canvasDim);
    canvas.setMaximumSize(canvasDim);

    getContentPane().removeAll();
    getContentPane().setLayout(new BorderLayout());
    getContentPane().add(canvas, BorderLayout.CENTER);

    setLocation(64, 64);

    setIgnoreRepaint(true);
    setResizable(false);
    pack();   
    
    addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) { exit(); }
        public void windowDeiconified(WindowEvent e) { start(); }
        public void windowIconified(WindowEvent e) { stop(); }
      }
    );

    validate();
    setVisible(true);

    canvas.createBufferStrategy(2);
    mBufferStrategy = canvas.getBufferStrategy();

    mRandom = new Random();
    
    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice gd = ge.getDefaultScreenDevice();
    GraphicsConfiguration gc = gd.getDefaultConfiguration();

    prepareImages(gc);
    
    start();
  }
  
  public void start() {
    if ( mMainLoop == null ) {
      mMainLoop = new Thread(this);
      mMainLoop.start();
    }
  }
  
  public void stop() {
    if ( mMainLoop != null ) mMainLoop.interrupt();
    mMainLoop = null;
  }

  public void exit() {
    reportAcceleratedMemory();
    stop();
    System.exit(0);
  }
  
  private void reportAcceleratedMemory() {
    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice gd = ge.getDefaultScreenDevice();
    int am = gd.getAvailableAcceleratedMemory();
    System.out.println("accelerated memory: "  + (am/1024) + "K"); 
  }

  private void prepareImages(GraphicsConfiguration gc) {
    // prepare sprite image
    mSprite = gc.createCompatibleImage(16, 16, Transparency.TRANSLUCENT);
    Graphics2D g2 = mSprite.createGraphics();
    g2.setBackground(new Color(0,0,0,0));
    g2.clearRect(0, 0, 16, 16);
    g2.setColor(Color.MAGENTA);
    g2.fillOval(1, 1, 15, 15);
    g2.dispose();
  
    // prepare background tile image
    mTile = new BufferedImage(64, 64, BufferedImage.TYPE_INT_RGB);
    updateTileImage();
    if ( mUseVolatileImage ) {
      mVTile = gc.createCompatibleVolatileImage(64, 64, Transparency.OPAQUE);
      refreshVolatileImage();
    }
  }

  private void updateTileImage() {
    WritableRaster raster = mTile.getRaster();
    int pixels[] = ((DataBufferInt)(raster.getDataBuffer())).getData();
    for ( int i = 0 ; i < 64*64 ; i++ ) pixels[i] = mRandom.nextInt(256);
  }
  
  private void refreshVolatileImage() {
    Graphics2D g2 = mVTile.createGraphics();
    g2.drawImage(mTile, 0, 0, null);
    g2.dispose();
  }
  
  public void run() {
    long prevNanos = System.nanoTime();
    int frameCount = 0;
    
    while ( mMainLoop == Thread.currentThread() ) {
      drawGameScreen();
      
      long newNanos = System.nanoTime();
      frameCount++;
      
      if ( newNanos > prevNanos + 3e9 ) {
        System.out.println("fps: " + ((frameCount*1e9f)/(newNanos-prevNanos)));
        prevNanos = newNanos;
        frameCount = 0;
      }
      
      Thread.yield();
    }
    
    mMainLoop = null;
  }
  
  private void drawGameScreen() {
    Graphics2D g2 = (Graphics2D)mBufferStrategy.getDrawGraphics();

    // draw background (repeated tiles)
    updateTileImage();
    if ( mUseVolatileImage ) {
      refreshVolatileImage();
      for ( int j = 0 ; j < 6 ; j++ ) {
        for ( int i = 0 ; i < 8 ; i++ ) {
          do {
            if ( mVTile.contentsLost() ) refreshVolatileImage();
            g2.drawImage(mVTile, 64*i, 64*j, null);
          } while ( mVTile.contentsLost() );
        }
      }
    } else {
      for ( int j = 0 ; j < 6 ; j++ ) {
        for ( int i = 0 ; i < 8 ; i++ ) {
          g2.drawImage(mTile, 64*i, 64*j, null);
        }
      }
    }
    
    // draw lots of sprites (translucent images)
    for ( int j = 0 ; j < 24 ; j++ ) {
      for ( int i = 0 ; i < 32 ; i++ ) {
        if ( (i+j)%2 == 0 ) {
          g2.drawImage(mSprite, 16*i + mRandom.nextInt(7)-3, 
                                16*j + mRandom.nextInt(7)-3, null);
        } 
      }
    }

    g2.dispose();
    Toolkit.getDefaultToolkit().sync();
    if ( !mBufferStrategy.contentsLost() ) mBufferStrategy.show();   
  }

}

Just a few thoughts on your code:

1.) Do the pixel values really need to be that random?
Java’s Random-Class is thread-safe which is what you don’t need in this case at all. Try others (cheaper) random implementations, simply look at google.
I guess this is why you only get 47fps when using the opengl pipeline.

2.) Do you really need to grab the raster? Is there no other way to archive the effect you would like to have without calling tons of primitive functions?

3.) Do you really need to call Thread.yield each iteration?

4.) I don’t know much about bufferStrategy but do you really need to call Toolkit.sync()?

Otherwise your code is more or less fine (a bit messy).

lg Clemens

Thanks for the comments.

The random numbers are simply there as a quick way to animate the background tile. Previously I was just colouring the pixels as 1, 2, 3, 4, etc., but I thought random numbers would make for a more interesting test! You’re probably right about Random slowing things down – the OpenGL case ran at 53 fps before I randomized the pixel values.

Information about thread-safe Random is noted for future reference.

Hmm. I’ll have to think about that. I’ve been playing around a bit with generating images by modifying individual pixels, and grabbing the Raster seems to be the most direct way of doing that (and it’s faster than setRGB() – at least on my machine!).

Both good questions. I don’t know in either case. I’ve seen discussions on these forums in which both Thread.yield() and Toolkit.sync() are recommended, but I don’t know for sure whether they’re necessary (or whether they’re needed once per frame). Possibly it varies with hardware and operating system? I’m including both to be on the safe side. Any further information would be gratefully received!

Thanks,
Simon

If you’re using the AWT/Swing you need to call yield after every frame or else you may find that key presses are delayed or mouse moves are laggy.

This is because the AWT’s Event Dispatch Thread needs to be given a go at doing its job. I actually call Thread.sleep(1) since once I got laggy input on a windows XP laptop which seemed to ignore the Thread.yield() .

Thanks for posting those performance metrics, interesting… You should try Mustang’s openGL pipeline, it flys 8).

Sorry that it brought not more … thought that this could be a bottleneck :-/

Well, setRGB introduces a lot of overhead if you intend to modify a large amount of pixels and the result remains the same - the changed area has to go to VRam over the system bus.
What I ment is wether it would not be possible to archieve the same behaviour with primitives like fillRect, drawLine, copyArea or stuff like that.
Of course a lot of those primitive-calls are slower than accessing the raster directly (e.g. filling the whole image with 1x1 fillrects will have terrible performance), but maybe you can do faster with a few primitive operations?
However this could introduce the problem that with default pipelines (DDraw, X11) only very basic operations are accalerated, everything else causes software operations and again ram->vram copies.

Ok, this has been answers by a much more professional developer than me (I’ve actually myself only once programmed a game :wink: ).

I think 53fps is acceptable for what you’re doing, if you want more I can just recommend you to try the Netbeans profiler out. Its free and a really great tool.

Good luck, lg Clemens

That’s well worth knowing. I may hedge my bets and alternate between calling yield() and calling sleep(1). That should ensure correct operation on every machine. :wink:

I’d be interested to see what the FPS results look like when running on more up-to-date hardware. But I can’t help thinking that I’m reinventing the wheel a bit here… Surely there must be benchmarks and performance figures for different pipelines posted somewhere already? On Sun’s site maybe?

By the way, Commander Keith, regarding your thread on setting command line options (http://www.java-gaming.org/forums/index.php?topic=14092.0), I’m using the System.setProperty() approach, as you can probably tell from my source code. I wasn’t aware that the “ddforcevram”, “translaccel” and “opengl” properties are all insecure as far as WebStart is concerned… and that does rather spoil my plans!

Cheers,
Simon

Omg,i ve just tested this on my laptop…

It reported about 250-300 fps…wtf this is awesome.

My laptop is 1.4m celeron, 256 mb ram and integrated graphics. It really pwned it…

I have a few questions…what is the benefit of copying to volatile image? Why not directly draw onto bufferStrategy?I see that u r drawing sprites directly…i mean i understand that u get speed boost,but i m interested in knowing why :smiley:


 do {
            if ( mVTile.contentsLost() ) refreshVolatileImage();
            g2.drawImage(mVTile, 64*i, 64*j, null);
          } while ( mVTile.contentsLost() );

Why do u use that do…while…it has the same results without it…


 while ( mMainLoop == Thread.currentThread() ) 

So far i ve never seen this construct for main loop…how is it possible to have any other thread as mMainLoop? I guess that it is always true…:slight_smile: why not putting only while(true)or something like that?

Ok, tnx in advance

My code’s never run so fast! :slight_smile:
I’m really going to have to upgrade my machine one of these days… drag myself into the 21st century…

The background tile is drawn 48 times each frame. Because its Raster has been modified, it never gets accelerated by the hardware, so Time Taken = 48*slow.

If the tile is copied onto a VolatileImage at the start of each frame, then that’s an additional slow draw operation. But all the subsequent draws are fast. Time Taken = 1slow + 48fast, which is quicker overall.

The sprites are BufferedImages that should be accelerated anyway, so there’s no need to bother with VolatileImages for them.

That’s how I understand it anyway.

Yes, it has the same results without the do-while for me too! In theory though, a VolatileImage can become corrupted at any time (not sure of exact reasons, something to do with graphics hardware), in which case contentsLost() returns true. So in principle you need to keep refreshing and redrawing the VolatileImage until you manage to get it onto the screen without any corruption – even though in practice corruption never seems to occur. (Possibly it’s a hardware thing – maybe someone somewhere finds that their VolatileImages lose contents regularly. But it doesn’t seems to affect the machines I’ve test on.)

As I understand it, when a BufferedImage is accelerated it becomes something like a VolatileImage in terms of where it exists in the graphics memory, but it handles all of the contentsLost()/refresh business automatically for you. So always use BufferedImages when you can!

BufferStrategy also has the same contentsLost() issues.

You’re probably right, it’s probably bad code. :-\ I copied the structure of the main loop from somewhere (these forums?) many, many years ago, and never bothered to question it – it’s been passed down from program to program ever since.

Cheers,
Simon

Ok…now i get it all…i mean u r using Volatile just coz when raster gets ripped of, java never accelerates ur image…

Tnx a lot.