Tile Map Rendering routine

BlitzMax Forums/BlitzMax Beginners Area/Tile Map Rendering routine

juankprada(Posted 2013) [#1]
Hi all, I am in the process of creating a tile map optimized routine and I am wondering which is the best way to actually draw the tiles. Right now I am using DrawImage for each tile in the map, so if the map is 30X30 tiles, the DrawImage would be called 900 times!!!

Is that the right way to do it? should I look at some other alternatives?, if so, what are those other alternatives?

Thanks


GfK(Posted 2013) [#2]
That's pretty much it, but you can optimize it by only drawing tiles which are within the boundaries of the screen (which will probably get you out of having to draw all 900 tiles).


Hardcoal(Posted 2013) [#3]
You mean gfk not to use cls command therfor leaving the tiles drawn?


GfK(Posted 2013) [#4]
No - you can't do that. It will all go Pete Tong when you use Flip.


Who was John Galt?(Posted 2013) [#5]
Don't know if Max implements image buffers these days, (I'm out of touch!), but if so you could implement a 'cache mapper' to speed things up significantly.

http://www.blitzmax.com/codearcs/codearcs.php?code=682


juankprada(Posted 2013) [#6]
@Gfk,
Well doing that droped my framerate from 700~800 to 180. Is there any optimization for the rendering other than relying on VBO or vertex arrays?


Who was John Galt?(Posted 2013) [#7]
Only drawing the tiles within the screen boundary dropped your frame rate? If so, you're doing it wrong.

WRONG->
Iterate through every tile in the map, check if it's on screen and draw if it is.

RIGHT->
Figure out which range of tiles will be on screen with an algorithm returning min and max x indices and min and max y indices, then loop through these and draw.


juankprada(Posted 2013) [#8]
@John
Right now I am rendering the exact number of Tiles that should be shown ina 800*600 screen. Now my tiles are 32*32 so I am doing the following:

When creating the map I just do the following for testing:

Local x:Int = 0
Local y:Int = 0
For Local i:Int = 1 To 25 Step 1
    For Local j:Int = 1 To 19 Step 1
        scene.tile.AddPosition(x + (32 * i), y + (32 * j))
				
    Next
Next



Now, on every loop the following is executed

For pos = EachIn positions
    image.Draw(pos.x, pos.y, animRange.startFrame)
Next


As you can see, the positions added to that tile are exactly the number of tiles that fit in a 800*600 screen (25*19 Tiles)


Who was John Galt?(Posted 2013) [#9]
Okay, looks decent code. So what are you saying you tried that dropped your framerate in post 6?

The code looks good, and I usually recommend to code for clarity first, but adding each tile to a list, then looping through the list to draw is extra overhead. I would just loop and draw immediately. (I can see however this doesn't matter in your test example.)

I would also save a bunch of operations by not adding x and y onto each tile position. This is presumably used to implement scrolling. Just do that with setorigin.


juankprada(Posted 2013) [#10]
@John
The first code snippet is only run once when the application starts and "loads the map"

There is actually only one tile and many positions. So that the tile is created only once and the second code snippet just iterate over all the positions in which that tile should be drawn

The part that actually droped my framerate is drawing the tiles. If i remove the tile draw routine, my framerate gets back up.


Who was John Galt?(Posted 2013) [#11]
Looks like a cache map is out- Max doesn't have the required graphics routines.

I guess you could so some trickery with a flat mesh, set the texture coordinates to display the correct tile. It's a bit more low level though.

Another option is a 3rd party solution. I've heard the name Mappy thrown around the forums. It may do what you want faster.


Hardcoal(Posted 2013) [#12]
Thats what i do for tiles. I use flat mesh.


TomToad(Posted 2013) [#13]
I noticed that you are offsetting the coordinates by 1. The rightmost tile is being rendered past the right side of the screen. I believe you want to change the loop like so:
Local x:Int = 0
Local y:Int = 0
For Local i:Int = 0 To 24 Step 1
    For Local j:Int = 0 To 18 Step 1
        scene.tile.AddPosition(x + (32 * i), y + (32 * j))
				
    Next
Next

I don't think that is where your slowdown is though. I would say you are doing something in your Image.Draw() method. It is also possible that your graphics card is unable to handle that many draw calls. I noticed that if I loop through and draw an image 475 times, my graphics card speed drops by about 78% from just looping through and drawing nothing. That seems to agree with your numbers above. Difference is that my card runs 6300 FPS without a draw command and 1400 with 475 draw commands.

One thing to try, if your map doesn't change on the level at all, you can render the screen before the game loop and GrabImage the entire 800,600 screen. Then you only need to DrawImage ScreenImage,0,0 each loop. I managed to almost triple the speed on my test program by doing this.

If your interested, this is the test program I used to determine how fast my card can render 475 images, just comment out the DrawImage command to see the difference in speed.



Who was John Galt?(Posted 2013) [#14]
My bad- there is a grabimage command!

If tomtoad's example gives you a decent speed up, then you can get close to that kind of speed with the advantage of being able to handle any size map you wish by implementing a cache map.


juankprada(Posted 2013) [#15]
Well, I am working on an RPG here, and i dont think that rendering the screen before the game loop is actually a good idea as map should be updated depending on the itneraction of the player with the environment.

So, does that mean that my only alternative is to rely directly on rewriting the DrawImage to use VBO/Vertex Arrays to speed up rendering?

Thanks to all of you for your help


Who was John Galt?(Posted 2013) [#16]
So, does that mean that my only alternative is to rely directly on rewriting the DrawImage to use VBO/Vertex Arrays to speed up rendering?
It's your only option if you dismiss all the other options mentioned here, i.e. cache map, flat mesh, 3rd party module, all of which potentially don't require rendering the screen before the main game loop.


TaskMaster(Posted 2013) [#17]
A couple things that speed this sort of stuff up.

1. Have all of your tiles in one image if you can and use drawrect to draw the correct tile from it. This keep from swapping textures in memory and is usually faster.

2. Another thing you can try, if your complete map is not too big and doesn't change, is draw the whole map one time onto a pixmap. Then use drawrect to only draw the portion of the whole map that fills the screen instead of all of the individual tiles. Of course, this will only work if your complete map is 1024x1024 or something like that. Many video cards are not going to let you use an image larger than that.


juankprada(Posted 2013) [#18]
Well, I was using the D3D7Max2DDriver for that. Tired using D3D9Max2DDriver and things got worse, but then I started using GLMax2DDriver and I went from ~200 FPS to ~400 FPS

Didnt know OpenGL was actually faster than Direct X in Windows. I think 400 FPS is way more than enough so Im quite happy with that