Jump to content
  • Advertisement
Sign in to follow this  
EnigmaticCoder

SDL + (2 by 2) TileMap = SLOW?

This topic is 3139 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
Advertisement
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, 0, 0, levelWidth, levelHeight))
continue;

double drawPositionX = 0;
double drawPositionY = 0;

getSpriteOnScreenCoordinates(sprites, cameraX, cameraY,
drawPositionX, drawPositionY);
drawSprite(sprites, 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
In openGL, batch draws are things like glVertexArrays, glInterleavedArrays...
google it.

Oh man, you have give up too early xD

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!