Sign in to follow this  

SDL + (2 by 2) TileMap = SLOW?

This topic is 2829 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm making a game where the player can dig tunnels through dirt. I've got the multi-layered level drawn without slowdown, but now that I've added a 2x2 tile tunnel map on top of the rest of the level, the game runs very slowly. A 16 X 16 tile tunnel map makes the game run at a passable speed, but then the tunnels aren't precise enough! (Note: I'm only drawing what's on screen) Is this the fault of SDL? Could switching to OpenGL fix my problem? What about SDL + OpenGL? Is SDL antiquated? I'll post code if necessary. [Edited by - jpetrie on March 18, 2010 8:57:32 PM]

Share this post


Link to post
Share on other sites
Are you allocating/freeing the tiles every single frame? (i.e. SDL_FreeSurface). I'm not accusing you, its just a common mistake. Also, are you using hardware accelerated surfaces?

Share this post


Link to post
Share on other sites
> Are you allocating/freeing the tiles every single frame?
Nope, just loading them once.

> Also, are you using hardware accelerated surfaces?
My screen surface is hardware accelerated, but I'm not sure about individual images. I load an image with IMG_Load(...) and optimize it with SDL_DisplayFormat(...);

Some additional info: I added code to skip transparent pixels, and that speeds it up quite a bit until a lot of tunnels are drawn.

Share this post


Link to post
Share on other sites
So you're saying that each 'diggable' tile is 2x2 pixels? What's your screen resolution?

I'm not cure how SDL works under the hood, but the naive way of drawing tiles using OpenGL (which is what it's using if the surface is hardware accelerated) is to put each tile in a single draw call -- this is very easy to do, but also terribly inneficient.

The efficient way to do things is to batch identical tiles into a single draw call. Have you read anything in the SDL docs about batching, sprite batches, or similar terms? If so, the associated functions are probably the right direction to go in.

It could be that you use a different draw function to activate the more efficient path. In Direct3D that's what the sprite batch APIs do.

Share this post


Link to post
Share on other sites
Hi:

It's difficult to guess the problem without see the code, but perhaps you could try the following:

- Do you have tile classes? In that instance, you could try to change it using an static array of images and let each instance to have an index to its image. This is most similar to Ravyne's solution you can get in SDL, because as far as I know, SDL isn't able to batch draw calls.

On the other hand, you could do some management, and only draw those tiles that have changed in the screen

Good luck and sorry about my English

PS: If you could paste or upload your code, perhaps someone find the problem

Share this post


Link to post
Share on other sites
I'm slightly embarrassed to show the least modular function in my code, but here it goes:

void Renderer::drawScreen(ScreenState &screenState, const Level &level, const Map &map, const Map &tunnelMap,
const std::vector<Sprite> &sprites, const Player &player,
const std::map<std::string, Image> &images, const Cursor &cursor,
double cameraX, double cameraY)
{
//Ensure proper initial conditions
if (cameraX < 0)
cameraX = 0;
if (cameraY < 0)
cameraY = 0;

int screenWidth;
int screenHeight;
screenState.getDimensions(screenWidth, screenHeight);

int levelWidth;
int levelHeight;
level.getDimensions(levelWidth, levelHeight);

int levelRow;
int levelCol;
int endLevelRow;
int endLevelCol;

int tunnelLevelRow;
int tunnelLevelCol;
int tunnelEndLevelRow;
int tunnelEndLevelCol;

double modifiedX = -map.tileSize - fmod(cameraX, map.tileSize);
double modifiedY = -map.tileSize - fmod(cameraY, map.tileSize);

double modifiedTunnelX = -tunnelMap.tileSize - fmod(cameraX, tunnelMap.tileSize);
double modifiedTunnelY = -tunnelMap.tileSize - fmod(cameraY, tunnelMap.tileSize);

getStartIndexes(map, cameraX, cameraY, levelRow, levelCol);
getEndIndexes(map, screenState, levelRow, levelCol, endLevelRow, endLevelCol);

getStartIndexes(tunnelMap, cameraX, cameraY, tunnelLevelRow, tunnelLevelCol);
getEndIndexes(tunnelMap, screenState, tunnelLevelRow, tunnelLevelCol, tunnelEndLevelRow, tunnelEndLevelCol);

//Tiles
//Start at the current row, which is an index in the map array
// contiue with the number of tiles that fit within the screen
// i.e. row = 0, i < (row * (screenHeight = 640 / tileSize = 16))
// 40 tiles total will be displayed
for (int layer = 0; layer < map.indexOfLastBackgroundLayer; ++layer)
{
for (int i = levelRow; i < endLevelRow; ++i)
{
for (int j = levelCol; j < endLevelCol; ++j)
{
drawLayer(map, layer, i, j, levelRow, levelCol, modifiedX, modifiedY);
}
}
}

//TunnelTiles
for (int layer = 0; layer < tunnelMap.indexOfLastBackgroundLayer; ++layer)
{
for (int i = tunnelLevelRow; i < tunnelEndLevelRow; ++i)
{
for (int j = tunnelLevelCol; j < tunnelEndLevelCol; ++j)
{
drawLayer(tunnelMap, layer, i, j, tunnelLevelRow, tunnelLevelCol, modifiedTunnelX, modifiedTunnelY);
}
}
}

for (int layer = map.indexOfLastBackgroundLayer; layer < map.layers; ++layer)
{
for (int i = levelRow; i < endLevelRow; ++i)
{
for (int j = levelCol; j < endLevelCol; ++j)
{
drawLayer(map, layer, i, j, levelRow, levelCol, modifiedX, modifiedY);
}
}
}

//Draw all the sprites in the spriteList
for (unsigned int i = 0; i < sprites.size(); ++i)
{
if (!spriteOnScreen(sprites[i], 0, 0, levelWidth, levelHeight))
continue;

double drawPositionX = 0;
double drawPositionY = 0;

getSpriteOnScreenCoordinates(sprites[i], cameraX, cameraY,
drawPositionX, drawPositionY);
drawSprite(sprites[i], drawPositionX, drawPositionY);
}

//For the player too
if (spriteOnScreen((Sprite) player, 0, 0, levelWidth, levelHeight))
{
double drawPositionX = 0;
double drawPositionY = 0;

getSpriteOnScreenCoordinates((Sprite) player, cameraX, cameraY,
drawPositionX, drawPositionY);
drawSprite((Sprite) player, drawPositionX, drawPositionY);
}

double cursorX = 0;
double cursorY = 0;
cursor.getCoordinates(cursorX, cursorY);

int currentZ = -1;

while (getNextLowest(currentZ, images) == true)
{
for (std::map<std::string, Sheet>::iterator it = this->images.begin();
it != this->images.end(); ++it)
{
if (images.find(it->first)->second.getZ() == currentZ && images.find(it->first)->second.getShowing())
drawImage(it->first, images.find(it->first)->second.getX(), images.find(it->first)->second.getY());
}
}

drawCursor(cursor, cursorX, cursorY);
drawScreen();
}



void Renderer::getStartIndexes(const Map &map, double x, double y, int &rowIndex, int &colIndex)
{
rowIndex = ((int) y) / map.tileSize - NUM_BORDER_TILES;
colIndex = ((int) x) / map.tileSize - NUM_BORDER_TILES;
}



void Renderer::getEndIndexes(const Map &map, ScreenState screenState, int levelRow, int levelCol, int &endLevelRow, int &endLevelCol)
{
int screenWidth;
int screenHeight;
screenState.getDimensions(screenWidth, screenHeight);

endLevelRow = levelRow + screenHeight / map.tileSize + NUM_BORDER_TILES * 2;
endLevelCol = levelCol + screenWidth / map.tileSize + NUM_BORDER_TILES * 2;
}



void Renderer::drawLayer(const Map &map, int layer, int row, int col, int levelRow,
int levelCol, double modifiedX, double modifiedY)
{
double curY = modifiedY + (row - levelRow) * map.tileSize;
double curX = modifiedX + (col - levelCol) * map.tileSize;

Tile curTile = {0, 0, map.filePath};

if (!validateBoundaries(map, row, col))
return;

getSheetCoordinates(map, map.mapData[layer][row][col], curTile.row, curTile.col);

if (curTile.row == TILE_TRANSPARENT && curTile.col == TILE_TRANSPARENT)
return;

drawTile(curTile, curX, curY);
}




bool Renderer::spriteOnScreen(Sprite sprite, double topLeftX, double topLeftY, int screenWidth,
int screenHeight)
{
double spriteX = 0;
double spriteY = 0;

sprite.getCoordinates(spriteX, spriteY);

if (spriteX < topLeftX || spriteX > topLeftX + screenWidth ||
spriteY < topLeftY || spriteY > topLeftY + screenHeight)
return false;

return true;
}



void Renderer::getSpriteOnScreenCoordinates(Sprite sprite, double topLeftX, double topLeftY,
double &drawPositionX, double &drawPositionY)
{
double spriteX = 0;
double spriteY = 0;

sprite.getCoordinates(spriteX, spriteY);

drawPositionX = spriteX - topLeftX;
drawPositionY = spriteY - topLeftY;
}



bool Renderer::getNextLowest(int &currentZ, const std::map<std::string, Image> &images)
{
const bool FOUND_NEXT = true;
int lastZ = currentZ;
int nextZ = INT_MAX;

for (std::map<std::string, Sheet>::iterator it = this->images.begin();
it != this->images.end(); ++it)
{
if (images.find(it->first)->second.getZ() > lastZ &&
images.find(it->first)->second.getZ() < nextZ)
{
currentZ = images.find(it->first)->second.getZ();
nextZ = currentZ;
}
}

if (lastZ != currentZ)
return FOUND_NEXT;

return !FOUND_NEXT;
}



void Renderer::getSheetCoordinates(const Map &map, int tileNum, int &rowIndex, int &colIndex)
{
rowIndex = tileNum / map.numOfTilesHigh;
colIndex = tileNum % map.numOfTilesWide;
}



void Renderer::drawScreen()
{
if (SDL_Flip(screen) == -1)
exit(EXIT_FAILURE);
}



bool Renderer::validateBoundaries(const Map &map, int row, int col)
{
const int LBOUND_ROW = 0;
const int LBOUND_COL = 0;
const int UBOUND_ROW = map.mapDataHeight - ARRAY_OFFSET;
const int UBOUND_COL = map.mapDataWidth - ARRAY_OFFSET;

if (row < LBOUND_ROW || row > UBOUND_ROW || col < LBOUND_COL || col > UBOUND_COL)
return false;

return true;
}

Share this post


Link to post
Share on other sites
First of all, try to change those nested-nested loops. You are calling DrawLayer and DrawTile with each tile on the screen!! It's better pass the layer to drawLayer and draw the whole layer in one pass, reducing the overhead of function calling.

Also, you doesn't need to calculate screen dimensions each frame.

PS: sorry for the !!. It's only to emphasize how bad are nested loops.

[Edited by - Juanxo on March 1, 2010 4:34:24 PM]

Share this post


Link to post
Share on other sites
You really, really should invest in a structure to represent a coordinate. I believe SDL already provides one, even, so use that. It will make your life a lot easier by un-cluttering the code and letting you write in a more natural way. For example, instead of having x and y values returned separately using by-reference "out" parameters, you can just return the value normally, as an instance of the structure.

Share this post


Link to post
Share on other sites
I've started using SDL + OpenGL. It wasn't too difficult of a transition.

Quote:
Original post by Ravyne
So you're saying that each 'diggable' tile is 2x2 pixels? What's your screen resolution?

The efficient way to do things is to batch identical tiles into a single draw call. Have you read anything in the SDL docs about batching, sprite batches, or similar terms?


Do you mean between glBegin(GL_QUADS) and glEnd()? Is that what batching identical tiles into a single draw is?

Share this post


Link to post
Share on other sites
To be honest I didn't read through your code but SDL in software mode should be able to handle this easily.

My suggestion is instead of drawing a bunch of tiles for dirt or empty space - render the dirt to a surface once at the beginning of a level. Then as you dig through the dirt use SDL_FillRect in small portions on the dirt surface using the color keyed color. Then the next time you blit your dirt surface the places you dug out won't show up.

This works for games similar to dig dug and worms where the "terrain" is static with the exception of what you are removing. You can also use the dirt surface as a pixel perfect collision map - anything without the color key value is solid for both the dirt and the sprite.

Share this post


Link to post
Share on other sites
Quote:
Original post by evillive2
My suggestion is instead of drawing a bunch of tiles for dirt or empty space - render the dirt to a surface once at the beginning of a level.

Are you saying to render the dirt to a surface other than the screen and then apply that surface to the screen every frame?

Quote:
Then as you dig through the dirt use SDL_FillRect in small portions on the dirt surface using the color keyed color.

Now that I'm using OpenGL, I've switched to alpha blending. Is there still a way to achieve this? Also, if the dirt becomes invisible, how do I get it to show a brownish color under it? Won't it just show the screen background? Perhaps I could do SDL_FillRect with the brownish color instead of making it invisible?

Quote:
You can also use the dirt surface as a pixel perfect collision map - anything without the color key value is solid for both the dirt and the sprite.

Pixel perfect collision detection would be cool, and I appreciate the suggestion, but I don't want to implement it in this particular game.

Share this post


Link to post
Share on other sites
Quote:
Are you saying to render the dirt to a surface other than the screen and then apply that surface to the screen every frame?

Yes - generate the dirt surface once at load time and then apply that surface each frame.

Since you are using OpenGL now the above suggestion is not really a good option anymore. My initial point was more that it is not necessary to use OpenGL for this type of game to have decent performance.

That being said I would draw a textured mesh of triangles using per vertex color/alpha values where you have dug to "remove" the dirt. I use a similar approach to transition between terrain types for a 2d RPG instead of 8 or more transition images for each terrain type.

Also - be aware that immediate mode is being deprecated in future versions of OpenGL (glBegin/glEnd) so some of the OpenGL enthusiasts may point you towards vertex buffer objects (VBOs) and shaders for this type of thing. From my own research I don't believe you have to worry about immediate mode not working any time in the near future (read few years) but it is something to consider.

Share this post


Link to post
Share on other sites
Quote:
Original post by evillive2
Yes - generate the dirt surface once at load time and then apply that surface each frame.

Will this take up a lot of memory? Maybe I should apply the entire map to the surface at the beginning of a level, since it's static?

Quote:
Since you are using OpenGL now the above suggestion is not really a good option anymore. My initial point was more that it is not necessary to use OpenGL for this type of game to have decent performance.
That being said I would draw a textured mesh of triangles using per vertex color/alpha values where you have dug to "remove" the dirt.


Do I apply this to the dirt surface or the screen surface? How small can I make these triangles? What exactly is a textured mesh?

Quote:
Also - be aware that immediate mode is being deprecated in future versions of OpenGL (glBegin/glEnd) so some of the OpenGL enthusiasts may point you towards vertex buffer objects (VBOs) and shaders for this type of thing. From my own research I don't believe you have to worry about immediate mode not working any time in the near future (read few years) but it is something to consider.

Noted.

Thanks for all your help so far. That goes for everybody in this thread.

Share this post


Link to post
Share on other sites
Quote:
Original post by evillive2
Also - be aware that immediate mode is being deprecated in future versions of OpenGL (glBegin/glEnd) so some of the OpenGL enthusiasts may point you towards vertex buffer objects (VBOs) and shaders for this type of thing. From my own research I don't believe you have to worry about immediate mode not working any time in the near future (read few years) but it is something to consider.


Does that mean that if I finish the game and distribute it, the executable file won't work when immediate mode is depreciated?

Share this post


Link to post
Share on other sites
For those of you who search this post and are in need of a solution, I finally figured out a way to speed the game up. SDL_FillRect works great for me. All I needed was solid rectangles anyway.

Note: I switched back to SDL from OpenGL to try this solution.

Share this post


Link to post
Share on other sites

This topic is 2829 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this