Java-Gaming.org    
Featured games (81)
games approved by the League of Dukes
Games in Showcase (491)
Games in Android Showcase (112)
games submitted by our members
Games in WIP (556)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  Looking for a speedy alterntive to getSubImage()  (Read 3842 times)
0 Members and 1 Guest are viewing this topic.
Offline pixelprime

Junior Member


Medals: 3



« Posted 2012-02-24 09:36:23 »

Hi All!,

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

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:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
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:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
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:

Click to Play

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.
Offline R.D.

Senior Member


Medals: 2
Projects: 1


"For the last time, Hats ARE Awesome"


« Reply #1 - Posted 2012-02-24 10:20:08 »

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.
Offline pixelprime

Junior Member


Medals: 3



« Reply #2 - Posted 2012-02-24 10:41:30 »

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:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
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:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
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:

1  
2  
3  
4  
5  
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!
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline kevglass

JGO Kernel


Medals: 159
Projects: 23
Exp: 18 years


Coder, Trainee Pixel Artist, Game Reviewer


« Reply #3 - Posted 2012-02-24 10:49:47 »

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

Cheers,

Kev

Offline 65K
« Reply #4 - Posted 2012-02-24 11:54:42 »

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.

Offline pixelprime

Junior Member


Medals: 3



« Reply #5 - Posted 2012-02-24 13:24:58 »

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!
Offline 65K
« Reply #6 - Posted 2012-02-24 13:38:43 »

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 Grin

Offline ra4king

JGO Kernel


Medals: 345
Projects: 3
Exp: 5 years


I'm the King!


« Reply #7 - Posted 2012-02-25 04:15:29 »

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.

Offline pixelprime

Junior Member


Medals: 3



« Reply #8 - Posted 2012-02-28 00:23:47 »

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

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.
Offline sproingie

JGO Kernel


Medals: 202



« Reply #9 - Posted 2012-02-28 00:30:44 »

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.
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Cero
« Reply #10 - Posted 2012-02-28 00:34:27 »

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...

Offline pixelprime

Junior Member


Medals: 3



« Reply #11 - Posted 2012-02-28 00:40:19 »

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?
Offline sproingie

JGO Kernel


Medals: 202



« Reply #12 - Posted 2012-02-28 00:43:24 »

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 Tongue

Offline Cero
« Reply #13 - Posted 2012-02-28 01:11:09 »

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 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

Offline StumpyStrust
« Reply #14 - Posted 2012-02-28 01:12:48 »

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

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
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.


Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

Nickropheliac (15 views)
2014-08-31 22:59:12

TehJavaDev (23 views)
2014-08-28 18:26:30

CopyableCougar4 (29 views)
2014-08-22 19:31:30

atombrot (41 views)
2014-08-19 09:29:53

Tekkerue (38 views)
2014-08-16 06:45:27

Tekkerue (35 views)
2014-08-16 06:22:17

Tekkerue (25 views)
2014-08-16 06:20:21

Tekkerue (35 views)
2014-08-16 06:12:11

Rayexar (72 views)
2014-08-11 02:49:23

BurntPizza (49 views)
2014-08-09 21:09:32
List of Learning Resources
by Longor1996
2014-08-16 10:40:00

List of Learning Resources
by SilverTiger
2014-08-05 19:33:27

Resources for WIP games
by CogWheelz
2014-08-01 16:20:17

Resources for WIP games
by CogWheelz
2014-08-01 16:19:50

List of Learning Resources
by SilverTiger
2014-07-31 16:29:50

List of Learning Resources
by SilverTiger
2014-07-31 16:26:06

List of Learning Resources
by SilverTiger
2014-07-31 11:54:12

HotSpot Options
by dleskov
2014-07-08 01:59:08
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!