Looking for a speedy alterntive to getSubImage()

Hi All!,

Bit of a Java newbie here so please forgive my rather basic questions :slight_smile:

I’m writing a basic game engine in Java, and am now trying to optimise the way I handle images (best to start early, rather than get too dug-in, I think).

Currently, before the game loop starts I load in an image containing all of the tiles I want to use and store it in a BufferedImage object. The image is composed of 32x32 tiles.

Then, at render time, I make a call to a function that draws the level tiles. At present, it’s just all floor tiles - but I wanted to see what performance I’d be getting.

When drawing the tiles, I cycle through the tile map - a simple 2D array of objects - and work out whether they’ll be in the viewport. If so, I render them using the getSubImage(…) method of the BufferedImage class.

Here’s the level tile rendering block:

for (int y = 0; y < HEIGHT_IN_TILES; y++)
        {
            for (int x = 0; x < WIDTH_IN_TILES; x++)
            {
                // calculate the position of this tile
                double tileX = scrollX + (x * TILE_SIZE);
                double tileY = scrollY + (y * TILE_SIZE);
                double tileCX = tileX + (TILE_SIZE / 2);
                double tileCY = tileY + (TILE_SIZE / 2);
                
                tileCX *= GameTest1.GAME_SCALE;
                tileCY *= GameTest1.GAME_SCALE;  
                
                double halfTile = TILE_SIZE / 2;
                
                // check if this is offscreen
                boolean canRender = false;
                
                if (tileCX >= -(halfTile * GameTest1.GAME_SCALE) && 
                        tileCY >= -(halfTile * GameTest1.GAME_SCALE) && 
                        tileCX < (GameTest1.GAME_WIDTH + halfTile) * GameTest1.GAME_SCALE && 
                        tileCY < (GameTest1.GAME_HEIGHT + halfTile) * GameTest1.GAME_SCALE)
                {
                    canRender = true;
                }
                
                if (canRender)
                {
                    tilesRendered++;
                    g.drawImage(blitTile(art.tilesetMain, tileMap[y][x].imageMapIndex), (int)tileX, (int)tileY, null);
                }
            }
        }

The call made when drawing the actual image references another method, ‘blieTile(…)’, which is as follows:

public static BufferedImage blitTile(BufferedImage bi, int imageIndex)
    {
        int cx0, cx1, cy0, cy1;
        int offX, offY;
        
        // determine the image offset
        if (imageIndex == 0) { offX = 0; offY = 0; }
        else { offX = offY = 0; }
        
        // set up the offset pixel values
        cx0 = Level.TILE_SIZE * offX;
        cy0 = Level.TILE_SIZE * offY;
        cx1 = cx0 + Level.TILE_SIZE;
        cy1 = cy0 + Level.TILE_SIZE;
        
        try
        {
            return (BufferedImage)bi.getSubimage(cx0, cy0, cx1-cx0, cy1-cy0);
        }
        catch (Exception ex)
        {
            System.out.println("Error: Couldn't blit tile");
        }
        
        return null;
    }

This results in the relevant tiles being drawn on-screen, as per the following image:

In a 1024 x 768 window, roughly 221 tiles are drawn each frame

Incidentally, I know this isn’t proper ‘blitting’, per se, but that leads me on to my question.

I’ve read reports about people using methods to copy pixels to/from a (static?) resource at render time - boosting draw performance. At the moment, my code seems to run around the 75/76 FPS mark (with a BufferStrategy in place), but I’ve heard of people managing to take similar projects up into the high hundreds, if not over a thousand FPS by adjusting the way small graphics like this are drawn.

I think it’s got something to do with storing a 2D array of pixel data for each image tile, and then creating a new image from that data at runtime? Or am I way off the mark here?

Are there resources online where you can learn these methods? I’ve tried searching around, but this seems to be a very specific type of drawing method.

I’m not after a code answer, that would be cheating - and obviously wouldn’t help me learn anything at all. If I could be pointed in the right direction, or be told what methodology I should consider, I would be most grateful.

Thanks for your time!

EDIT: Added screenshot of running project / corrected some typos.

Why not loading each subimage before drawing it? Now you load a new subimage every loop. 200+ times. Look into SpriteSheets, I guess that’s what you are looking for.

Ok, so after a very large, strong cup of coffee - I have my solution!

This might help others in the same boat - and it’s definitely a good solution to this problem.

In each tile object, I have a BufferedImage object that stores the cropped tile. During level creation-time, the pixel data for the tile is extracted from the tileset:

public void loadTilePixelData(BufferedImage bi)
    {
        int cx0, cx1, cy0, cy1;
        int offX, offY;
        
        // determine the image offset
        if (imageMapIndex == 0) { offX = 0; offY = 0; }
        else { offX = offY = 0; }
        
        // set up the offset pixel values
        cx0 = Level.TILE_SIZE * offX;
        cy0 = Level.TILE_SIZE * offY;
        cx1 = cx0 + Level.TILE_SIZE;
        cy1 = cy0 + Level.TILE_SIZE;
        
        BufferedImage tileImage = bi.getSubimage(cx0, cy0, cx1-cx0, cy1-cy0);
        
        try
        {
            for (int yy = 0; yy < height; yy++)
            {
                for (int xx = 0; xx < width; xx++)
                {
                    pixels[yy][xx] = tileImage.getRGB(xx, yy);
                    
                    if (count == 1) { System.out.println("Data: " + pixels[yy][xx]); }
                     count++;
                }
            }
        }
        catch (Exception ex)
        {
            System.out.println("Error: Couldn't load tile pixel data");
        }
        
        storeTileImage();
    }

The method at the bottom - storeTileImage() - is where this data is then copied into the Tile object’s own BufferedImage object:

public void storeTileImage()
    {
        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        
        for (int yy = 0; yy < height; yy++)
        {
            for (int xx = 0; xx < width; xx++)
            {
                bi.setRGB(xx, yy, pixels[yy][xx]);
            }
        }
        
        image = bi;
    }

Then, at runtime, I just call the relevant tile to be rendered, passing its own BufferedImage back to the graphics object:

if (canRender)
                {
                    tilesRendered++;
                    g.drawImage(tileMap[y][x].image, (int)tileX, (int)tileY, null);
                }

The result? A drop in render time from 14ms -> 1ms, and an increase in FPS from 76 fps -> 1213 fps!

Now that’s a result! Thanks to anyone who replied / considered replying between the time of my original post, and the time it took me to write this up!

You could just store the sub-image as mentioned above.

Cheers,

Kev

Moving pixels by hand makes no sense at all, I’d say.
Either create and cache subimages once before rendering or even draw your map directly from one big image.
Even better, use VolatileImage.

Thanks. I think that’s what I’m doing now, with the BufferedImage stored in the individual Tile objects?

The tile map is diced up before the game loop actually starts, and each individual tile ‘slice’ is stored ina BufferedImage for that tile, which is composited from the pixeldata extrapolated from the main tile map image.

Either way, the frame rate increase is pretty impressive, and certain cures my render woes!

Thanks for the insights!

Ok, but really, what’s the point of copying pixel by pixel ?
Use getSubImage() or drawImage() to copy between images.
Take a look at GraphicsConfiguration to get images of appropriate formats.
And if you dont want to modify your sub images separately, you don’t even need them at all.
I love optimizing ;D

DO NOT use setRGB or modify pixel data directly! That makes the BufferedImage unmanaged and it will not be accelerated by the graphics card. The fastest way is to cut everything up into a 2D array using getSubimage like the others posted.

Perhaps I should explain my reasons before I get shot down on this one :slight_smile:

I’m using setRGB because I’m storing two versions of the same image. One which is an exact 1:1 pixel copy, and one which is slightly red-shifted to use as a ‘pain’ image.

I have read before about the arguments for staying away from unmanaged image objects, but if we’re talking about, say 10 or 20 32-pixel images at the most - is it really going to have a massive impact on hardware acceleration?

Let’s just assume for a moment I were to change the way I split the images up from the tile map (forget about the palette shifting for now). Would using a normal BufferedImage[][] array set remain hardware accelerated if I simply fill it with images returned from a getSubImage(…) call?

Thanks for your insights.

The impact it has on hardware acceleration is that you don’t get any at all. The size of the image is not all that material, it’s the unoptimized memory accesses and copies it has to do, and how many times you do them. getSubImage gives you managed images, so if you use only that you’re fine.

I recall trembovetski saying that calling subimage() will make the result not hardware accelerated by default
of course, it has been a while, so who knows

I would just create a compatible/managed image, get the subimage and draw it onto it, using getGraphics, and then it should be good to go.

edit: of course store it in an array, loading it before and stuff; buffering…

Should I be looking at Volatile Images instead, then, as suggested earlier on in this thread?

If I’m honest, I’m not sure of the pros / cons of using them over the BufferedImage class. I know at least that BufferedImage provides all those handy manipulation methods, but unsure if Volatile is essentially the same thing?

I’ve been getting conflicting answers as to whether getSubImage returns a managed image or not, but it seems to be that getSubImage does indeed return an unmanaged image. Creating a new image and drawing into it appears to be the proper way to go. As performance tuning advice goes, “don’t listen to sproingie” is probably a good place to start :stuck_out_tongue:

Well its not documented D=

Seriously for simple 2D stuff, just use Slick
you never know when an image is accelerated, without really trying to find out, and slicks API is basically java2D

This is what I use and the start up time is slower but it runs fast after words.


public BufferedImage loadImage( String i, int x, int imageWidth, int imageHeight )
	{
		try
	    {	
			GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
			GraphicsDevice gs = ge.getDefaultScreenDevice();
			GraphicsConfiguration gc = gs.getDefaultConfiguration();
			
	        BufferedImage tempBuff = ImageIO.read(new File(i));
	        BufferedImage a = gc.createCompatibleImage(imageWidth, imageHeight, Transparency.TRANSLUCENT);;
	        Graphics tempG = a.getGraphics();
	        tempG.drawImage(tempBuff.getSubimage(x, 0, imageWidth, imageHeight), 0, 0, null);
	        tempG.dispose();
	        return a;
	           
	    }
		catch(IOException ioexception) 
		{ 
			System.out.println("error");
			return null;
		} 
	}

You just draw onto another graphics object. That way its managed. I am pretty sure that getSubImage is NOT managed. Why? because its so slow. Maybe I need to update java but for me it is slow.