[java] Optimizing a Tile Based Engine

Started by
9 comments, last by princec 18 years, 3 months ago
Well after many trials and tribulations. I've got a map loaded that was created in Tile Studio and displayed on the screen. I'm running my game in an 800 x 600 window. When I saw the map displayed triumph was mine! I began to wonder how efficient it was. I quickly made some code to display the frame rate. To my disgust I was only getting 50 frames per second! How can this be? I had realized java was sluggish in some aspects. However, I'm supposedly using hardware acceleration I thought to myself. I find it silly that something so simple could be so painfully slow on my system. P4 2.4ghz 512 MB RDRAM RADEON 9700 I'm asking if anyone sees any inherently inefficent methods in my code. I'll do my best to explain the bits and pieces: Here's where I first call the draw map function in my game loop. asge is a wrapper class for graphics. It simplifies my main game loop, especially if I decide to reimplement the graphics engine. mainMap is a Map object. More on that later...

asge.draw(mainMap, 0 , 0);
Here's the ASGE object's draw function. It's simple enough just passes a graphics object and some coordinates to the map object.

    public void draw(Map map, int x, int y)
    {
        map.draw(g, x , y); //draw the map starting at x and y
    }
Here's the meat and potatoes, the map objects draw function:

    public void draw(Graphics g, int xStart, int yStart)
    {
        //find what tiles will be visible starting in the top left of screen
        startXTile = xStart / 32;
        startYTile = yStart / 32;
        //now we need to get the remainder of this in order to offset these tiles the proper amount
        x_off = startXTile & 31;
        y_off = startYTile & 31;
        
        for(int x = startXTile; x < screenTileWidth; x++)
        {
            for(int y = startYTile; y < screenTileHeight; y++)
            {
                tiles.drawTile(mapData[height * y + x],g, x * 32 + x_off, y * 32 + y_off);
                //System.out.println("mapData[" + (height * y + x) + "]: " + mapData[height * y + x]); 
            }
        }
    }

It only draws enough tiles to fill the screen. Well you may wonder if there's a bottleneck in this tiles object you just spotted. Here ya go:

    public void drawTile(int id, Graphics g, int x, int y)
    {
        allTiles[id].draw(g, x ,y);
        //System.out.println("id: " + id); //debug
    }

Now I fill this allTiles[] array in this fashion:

        while(line != null)
        {
            Image image = null; //image object to store what we load from a file
            System.out.println("Line: " + line);
            try
            {
                image = ImageIO.read(getClass().getResource(line)); //load the image file to image
            }
            catch(IOException e)
            {
                System.err.println("Couldn't load " + line);
            }
            Sprite tileSprite = new Sprite(image);
            addTile(tileSprite);
            System.out.println("Added sprite " + line);
            
            line = tileConfigReader.getNextLine(); //contains filename
        }

    }

It's my understanding that image objects will automatically be accelerated in Java 1.5. Maybe I'm incorrect? I've been looking into and nothing has turned up on Sun's site so far. Thanks in advance, -Kris
Advertisement
Here's what I found. I'm not a Java expert so that link might be useless to you.

Hope it helps anyway,
Pat.
Are you creating an image out of all the tiles, and then drawing that to the screen? ie. are you allowing the image to be blitted to the screen at once?

And are you only drawing the parts that changed?
Quote:Original post by Argus2
Are you creating an image out of all the tiles, and then drawing that to the screen? ie. are you allowing the image to be blitted to the screen at once?

And are you only drawing the parts that changed?


I'm drawing the the graphics buffer, g, in my ASGE object. To display it to the screen I flip the buffer.

I've considered drawing a very large map segment, then just copying the displayable part of that to the graphics buffer. As the player reaches the edge of the very large map segment, I'd create another one in memory, so they would seamlessly be displayed. That may be a bad explanation. Basically I'd draw it in chunks rather than tiles.

I redraw the screen each time, disregarding whether it has changed or not. The idea behind this is that because I'm going to have the camera constantly follow the player the only time there will not be a change is when the player stops. The player will only stop in non-critical moments so I've seen no use in implementing the feature yet. I'm more concerned about the frame rate during battle. Where there should be constant movement.


-Kris
Quote:Original post by darookie
Here's what I found. I'm not a Java expert so that link might be useless to you.

Hope it helps anyway,
Pat.


This link did mention something that I did not realize and may potentially be very helpful. When I launch my game I may need to give the option "-Dsun.java2d.opengl=true"

I'll have to try that too.

-Kris
Well I started up my graphics engine with the opengl arguments I posted earlier. It ran at a painfully slow frame rate. It couldnt have been faster than .5 fps. In fact, my timer read 0 fps.

When I closed the application I got this nasty error:
## An unexpected error has been detected by HotSpot Virtual Machine:##  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x692f81b7, pid=4000, tid=3516## Java VM: Java HotSpot(TM) Client VM (1.5.0_04-b05 mixed mode)# Problematic frame:# C  [atioglxx.dll+0x2f81b7]## An error report file with more information is saved as hs_err_pid4000.log## If you would like to submit a bug report, please visit:#   http://java.sun.com/webapps/bugreport/crash.jsp#


So I looked at the log file:
## An unexpected error has been detected by HotSpot Virtual Machine:##  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x692f81b7, pid=4000, tid=3516## Java VM: Java HotSpot(TM) Client VM (1.5.0_04-b05 mixed mode)# Problematic frame:# C  [atioglxx.dll+0x2f81b7]#---------------  T H R E A D  ---------------Current thread (0x0082a0b0):  VMThread [id=3516]siginfo: ExceptionCode=0xc0000005, reading address 0x0000816cRegisters:EAX=0x00000000, EBX=0x00000000, ECX=0x13030b20, EDX=0x00000001ESP=0x0abcfc5c, EBP=0x00000000, ESI=0x1d223480, EDI=0x1d223200EIP=0x692f81b7, EFLAGS=0x00010206Top of Stack: (sp=0x0abcfc5c)0x0abcfc5c:   13030b48 00000000 13030b20 000000000x0abcfc6c:   693f280d 692f836f 1d223200 13030b200x0abcfc7c:   00000000 00000000 0abcfcc4 690000000x0abcfc8c:   00000001 13030b48 13030b20 692f856c0x0abcfc9c:   00000002 692f6a23 692f6b27 693f48620x0abcfcac:   69000000 00000000 00000001 000000000x0abcfcbc:   0abcfcd8 00183140 0abcfce4 7c9011a70x0abcfccc:   69000000 00000000 00000001 00183140 Instructions: (pc=0x692f81b7)0x692f81a7:   00 eb c8 33 c0 5d 59 c2 0c 00 53 56 8b 74 81 280x692f81b7:   8b 8d 6c 81 00 00 33 db 85 c9 89 74 24 10 7e 19 Stack: [0x0ab90000,0x0abd0000),  sp=0x0abcfc5c,  free space=255kNative frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)C  [atioglxx.dll+0x2f81b7][error occurred during error reporting, step 120, id 0xc0000005]VM_Operation (0x1daaf808): exit, mode: safepoint, requested by thread 0x00842910---------------  P R O C E S S  ---------------Java Threads: ( => current thread )  0x00842910 JavaThread "AWT-EventQueue-0" [_thread_blocked, id=1016]  0x00838170 JavaThread "AWT-Shutdown" [_thread_blocked, id=3164]  0x0082b360 JavaThread "Java2D Disposer" daemon [_thread_blocked, id=2748]  0x00830d80 JavaThread "Low Memory Detector" daemon [_thread_blocked, id=2844]  0x0082fe70 JavaThread "CompilerThread0" daemon [_thread_blocked, id=2272]  0x0082c110 JavaThread "Finalizer" daemon [_thread_blocked, id=708]  0x0082cde0 JavaThread "Reference Handler" daemon [_thread_blocked, id=3844]  0x00822280 JavaThread "main" [_thread_blocked, id=2440]Other Threads:=>0x0082a0b0 VMThread [id=3516]VM state:at safepoint (shutting down)VM Mutex/Monitor currently owned by a thread:  ([mutex/lock_event])[0x008227d0/0x00001720] Threads_lock - owner thread: 0x0082a0b0Heap def new generation   total 576K, used 475K [0x02b90000, 0x02c30000, 0x03070000)  eden space 512K,  91% used [0x02b90000, 0x02c04b38, 0x02c10000)  from space 64K,  13% used [0x02c20000, 0x02c22318, 0x02c30000)  to   space 64K,   0% used [0x02c10000, 0x02c10000, 0x02c20000) tenured generation   total 1408K, used 893K [0x03070000, 0x031d0000, 0x06b90000)   the space 1408K,  63% used [0x03070000, 0x0314f7f0, 0x0314f800, 0x031d0000) compacting perm gen  total 8192K, used 5210K [0x06b90000, 0x07390000, 0x0ab90000)   the space 8192K,  63% used [0x06b90000, 0x070a6888, 0x070a6a00, 0x07390000)No shared spaces configured.Dynamic libraries:0x00400000 - 0x0040c000 	C:\Program Files\Java\jre1.5.0_04\bin\javaw.exe0x7c900000 - 0x7c9b0000 	C:\WINDOWS\system32\ntdll.dll0x7c800000 - 0x7c8f4000 	C:\WINDOWS\system32\kernel32.dll0x77dd0000 - 0x77e6b000 	C:\WINDOWS\system32\ADVAPI32.dll0x77e70000 - 0x77f01000 	C:\WINDOWS\system32\RPCRT4.dll0x77d40000 - 0x77dd0000 	C:\WINDOWS\system32\USER32.dll0x77f10000 - 0x77f57000 	C:\WINDOWS\system32\GDI32.dll0x77c10000 - 0x77c68000 	C:\WINDOWS\system32\MSVCRT.dll0x76390000 - 0x763ad000 	C:\WINDOWS\system32\IMM32.DLL0x629c0000 - 0x629c9000 	C:\WINDOWS\system32\LPK.DLL0x74d90000 - 0x74dfb000 	C:\WINDOWS\system32\USP10.dll0x6d640000 - 0x6d7c9000 	C:\Program Files\Java\jre1.5.0_04\bin\client\jvm.dll0x76b40000 - 0x76b6d000 	C:\WINDOWS\system32\WINMM.dll0x5cd70000 - 0x5cd77000 	C:\WINDOWS\system32\serwvdrv.dll0x5b0a0000 - 0x5b0a7000 	C:\WINDOWS\system32\umdmxfrm.dll0x6d280000 - 0x6d288000 	C:\Program Files\Java\jre1.5.0_04\bin\hpi.dll0x76bf0000 - 0x76bfb000 	C:\WINDOWS\system32\PSAPI.DLL0x6d610000 - 0x6d61c000 	C:\Program Files\Java\jre1.5.0_04\bin\verify.dll0x6d300000 - 0x6d31d000 	C:\Program Files\Java\jre1.5.0_04\bin\java.dll0x6d630000 - 0x6d63f000 	C:\Program Files\Java\jre1.5.0_04\bin\zip.dll0x6d000000 - 0x6d167000 	C:\Program Files\Java\jre1.5.0_04\bin\awt.dll0x73000000 - 0x73026000 	C:\WINDOWS\system32\WINSPOOL.DRV0x774e0000 - 0x7761d000 	C:\WINDOWS\system32\ole32.dll0x5ed00000 - 0x5edcc000 	C:\WINDOWS\system32\opengl32.dll0x68b20000 - 0x68b40000 	C:\WINDOWS\system32\GLU32.dll0x73760000 - 0x737a9000 	C:\WINDOWS\system32\DDRAW.dll0x73bc0000 - 0x73bc6000 	C:\WINDOWS\system32\DCIMAN32.dll0x6d240000 - 0x6d27d000 	C:\Program Files\Java\jre1.5.0_04\bin\fontmanager.dll0x755c0000 - 0x755ee000 	C:\WINDOWS\system32\msctfime.ime0x69000000 - 0x694c0000 	C:\WINDOWS\system32\atioglxx.dll0x7c9c0000 - 0x7d1d5000 	C:\WINDOWS\system32\shell32.dll0x77f60000 - 0x77fd6000 	C:\WINDOWS\system32\SHLWAPI.dll0x773d0000 - 0x774d2000 	C:\WINDOWS\WinSxS\x86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.2600.2180_x-ww_a84f1ff9\comctl32.dll0x5d090000 - 0x5d127000 	C:\WINDOWS\system32\comctl32.dll0x6d190000 - 0x6d1bf000 	C:\Program Files\Java\jre1.5.0_04\bin\cmm.dll0x6d3c0000 - 0x6d3df000 	C:\Program Files\Java\jre1.5.0_04\bin\jpeg.dll0x6d4c0000 - 0x6d4d3000 	C:\Program Files\Java\jre1.5.0_04\bin\net.dll0x71ab0000 - 0x71ac7000 	C:\WINDOWS\system32\WS2_32.dll0x71aa0000 - 0x71aa8000 	C:\WINDOWS\system32\WS2HELP.dll0x6d4e0000 - 0x6d4e9000 	C:\Program Files\Java\jre1.5.0_04\bin\nio.dllVM Arguments:jvm_args: -Dsun.java2d.opengl=truejava_command: GameEnvironment Variables:CLASSPATH=C:\Program Files\Java\jre1.5.0_04\lib\ext\QTJava.zipPATH=C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\Program Files\ATI Technologies\ATI Control Panel;C:\j2sdk1.4.2_06\bin;C:\Program Files\QuickTime\QTSystemUSERNAME=KrisOS=Windows_NTPROCESSOR_IDENTIFIER=x86 Family 15 Model 2 Stepping 7, GenuineIntel---------------  S Y S T E M  ---------------OS: Windows XP Build 2600 Service Pack 2CPU:total 1 family 15, cmov, cx8, fxsr, mmx, sse, sse2, htMemory: 4k page, physical 523244k(73564k free), swap 1279756k(846388k free)vm_info: Java HotSpot(TM) Client VM (1.5.0_04-b05) for windows-x86, built on Jun  3 2005 02:10:41 by "java_re" with MS VC++ 6.0


It looked to me like it may only be useful to a Sun developer. I posted it in hopes that someone will make some sort of heads or tails of it.

The one thing you are missing is that you are blitting BufferedImages, which is what ImageIO returns. BufferedImages are painfully slow to blit, as a general rule.

After loading your BufferedImages, you need to convert them to screen native format. This means getting your component and calling createCompatibleImage on it to create an appropriate tile-sized image, blitting the tile to it, and then that's what you store in your tiles array, not the BufferedImages.

You'll find that these are about 5x as fast to blit.

You can get even cleverer by using VolatileImages but that's getting kinda fiddly and there's some tutorials floating around on Sun's site explaining them.

I hope also that you are blitting using a BufferStrategy rather than in some paint() command.

Cas :)
You might want to try a different method for loading your images. ImageIcon has a getImage() method that returns a simple Image. So, you might want to try something like this:

URL url = getClass().getResource();Image image = new ImageIcon(url).getImage();


- Rob
In fact, to twiddle with your code a bit, we can get the whole process down to only three lines within the while loop:

while(line != null) {	URL url = getClass().getResource();	addTile(new Sprite(new ImageIcon(url).getImage());	line = tileConfigReader.getNextLine();}


However, this doesn't handle error-checking. All you would have to do there is add a check for the returned Image being null.

- Rob
>BufferedImages are painfully slow to blit, as a general rule.

You mean unmanaged BufferedImages ;)

ImageIO returns managed images by default if your using 1.5+ (otherwise you need that copy step). The images should be either opaque or using bitmask transparency. Additionally the pixel size of the drawn images shouldn't exceed 2^16 pixels. (If you're using getSubimage you still reference the original image. So, instead create a new compatible image and draw a specific part of the bigger image over.)

I get about 280fps in 640x480x32 using 32x32 tiles (K7 500 + ati 9100). Well, I'm using lwjgl, but java2d should be able to reach about 66% of the speed on windows. Btw the java2d opengl pipeline is *slower* for this kind of stuff. Try -Dsun.java2d.noddraw=true instead.

This topic is closed to new replies.

Advertisement