Jump to content
  • Advertisement
Sign in to follow this  
d h k

SDL too slow for a high-res RTS game? [SOLVED]

This topic is 4128 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

Hello, I know, I know... YET another thread about SDL and its performance by me. I apologize for that. Anyways, maybe you remember me posting about speed-issues before, but now it's got me again. This is the situation: I want to draw isometric tiles sized 128x64 for a 1280x1024 game. This means (I checked via debug-features in my project) around 400 tiles on the screen at a time out of 1200 for example for a 50x30 sized map (in tiles). This already slows my SDL application down to 20 to 25 frames. My computer is four years old, but shouldn't it be able to run faster? Here is some code from the tile drawing for you to check for any errors: Note that my tiles do not use an alpha-channel, in that case, I'd know why it's slow... sprite.cpp
void csprite::init ( const char *filename, bool alpha_channel )
// initializes the sprite
{
	if ( alpha_channel )
	// if the image has an alpha-channel
		// load the bitmap
		bitmap = IMG_Load ( filename );

	else
	// if the image does not have an alpha-channel
	{
		SDL_Surface* temp;

		// load temp
		temp = SDL_LoadBMP ( filename );

		if ( temp == NULL )
		// if there was an error
			return_error ( "Failed to initialize sprite..." );

		// load the bitmap
		bitmap = SDL_DisplayFormat ( temp );

		// free temp	
		SDL_FreeSurface ( temp );

		// activate color keying
		if ( SDL_SetColorKey ( bitmap, SDL_SRCCOLORKEY, SDL_MapRGB ( bitmap->format, 255, 0, 128 ) ) < 0 )
		// if there was an error
			return_error ( "Failed to colorkey sprite..." );
	}

	if ( bitmap == NULL )
	// if there was an error
		return_error ( "Failed to initialize sprite..." );

	// set up other variables
	width = bitmap->w;
	height = bitmap->h;
}

void csprite::init ( const char *filename, bool alpha_channel, int _width, int _height )
// initializes the sprite
{
	// initialize sprite
	init ( filename, alpha_channel );

	// set up other variables
	width = _width;
	height = _height;
}

void csprite::draw ( )
// draws the sprite to the screen
{
	// create a source rectangle
	SDL_Rect source;
	source.x = 0;
	source.y = 0;
	source.w = bitmap->w;
	source.h = bitmap->h;
	
	// create a destination rectangle
	SDL_Rect destination;
	destination.x = ( int ) x;
	destination.y = ( int ) y;
	destination.w = width;
	destination.h = height;

	// blit the bitmap to the screen
	if ( SDL_BlitSurface ( bitmap, &source, SDL_GetVideoSurface ( ), &destination ) < 0 )
	// if there was an error
		return_error ( "Failed to draw sprite..." );
}

void csprite::draw ( float _x, float _y )
// draws the sprite to the screen at a given location
{
	// position the sprite
	x = _x;
	y = _y;
	
	// draw it
	draw ( );
}

void csprite::draw ( int _x, int _y )
// draws the sprite to the screen at a given location
{
	// create a source rectangle
	SDL_Rect source;
	source.x = 0;
	source.y = 0;
	source.w = bitmap->w;
	source.h = bitmap->h;
	
	// create a destination rectangle
	SDL_Rect destination;
	destination.x = _x;
	destination.y = _y;
	destination.w = width;
	destination.h = height;

	// blit the bitmap to the screen
	if ( SDL_BlitSurface ( bitmap, &source, SDL_GetVideoSurface ( ), &destination ) < 0 )
	// if there was an error
		return_error ( "Failed to draw sprite..." );
}

void csprite::draw ( float _x, float _y, int x_frame, int y_frame )
// draws the sprite to the screen at a given location using a frame
{
	// position the sprite
	x = _x;
	y = _y;

	// create a source rectangle
	SDL_Rect source;
	source.x = x_frame * width;
	source.y = y_frame * height;
	source.w = width;
	source.h = height;
	
	// create a destination rectangle
	SDL_Rect destination;
	destination.x = ( int ) x;
	destination.y = ( int ) y;
	destination.w = width;
	destination.h = height;

	// blit the bitmap to the screen
	if ( SDL_BlitSurface ( bitmap, &source, SDL_GetVideoSurface ( ), &destination ) < 0 )
	// if there was an error
		return_error ( "Failed to draw sprite..." );
}

void csprite::draw_cropped ( int percentage )
// draws the sprite to the screen cropped at a given location
{
	// create a source rectangle
	SDL_Rect source;
	source.x = 0;
	source.y = 0;
	source.w = ( int ) ( ( 1.0 / 100.0 ) * ( float ) percentage * ( float ) width );
	source.h = bitmap->h;
	
	// create a destination rectangle
	SDL_Rect destination;
	destination.x = ( int ) x;
	destination.y = ( int ) y;
	destination.w = ( int ) ( ( 1.0 / 100.0 ) * ( float ) percentage * ( float ) width );
	destination.h = height;
	
	// blit the bitmap to the screen
	if ( SDL_BlitSurface ( bitmap, &source, SDL_GetVideoSurface ( ), &destination ) < 0 )
	// if there was an error
		return_error ( "Failed to draw sprite..." );
}

void csprite::destroy ( void )
// destroys all used memory of a sprite and frees it
{
	// free the surface
	SDL_FreeSurface ( bitmap );
}




tile.cpp
int tile_offset_x, tile_offset_y;

int tiles_drawn;

ctile tile[TILES_X][TILES_Y];

csprite tileset;

extern screen_width, screen_height;

void ctile::init ( int _x, int _y, tile_type _type )
// initializes the tile
{
	if ( _type == dirt )
	{
		// assign vertical frame
		y_frame = 0;

		// randomize horizontal frame
		x_frame = rand ( ) % 3;
	}

	else if ( _type == grass )
	{
		// assign vertical frame
		y_frame = 1;

		// randomize horizontal frame
		x_frame = rand ( ) % 3;
	}
	/*else
	{
		// assign vertical frame
		y_frame = 2;

		if ( _type == tra_grass_dirt_t )
			x_frame = 0;
		else if ( _type == tra_grass_dirt_tr )
			x_frame = 1;
		else if ( _type == tra_grass_dirt_r )
			x_frame = 2;
		else if ( _type == tra_grass_dirt_br )
			x_frame = 3;
		else if ( _type == tra_grass_dirt_b )
			x_frame = 4;
		else if ( _type == tra_grass_dirt_bl )
			x_frame = 5;
		else if ( _type == tra_grass_dirt_l )
			x_frame = 6;
		else if ( _type == tra_grass_dirt_tl )
			x_frame = 7;
	}*/

	// save position
	x = _x;
	y = _y;

	// save type
	type = _type;
}

void ctile::draw ( void )
// draws the tile
{
	// draw the sprite
	tileset.draw ( ( float ) x, ( float ) y, x_frame, y_frame );
}

void init_tiles ( int start_position_x, int start_position_y )
// inits the tiles
{
	int	row_index = 0;

	tiles_drawn = 0;

	// init tileset
	tileset.init ( "tilesets/jungle.bmp", false, TILE_WIDTH, TILE_HEIGHT );

	for ( int y = 0; y < TILES_Y; y++ )
	// loop through all tiles vertically
	{
		for ( int x = 0; x < ( TILES_X / 2 ); x++ )
		// loop through all tiles horizontally
		{
			if ( row_index == 0 )
				// init tile
				tile[x * 2][y].init ( ( x * TILE_WIDTH ) - start_position_x, ( y * ( ( TILE_HEIGHT + 1 ) / 2 ) ) - start_position_y, grass );

			else if ( row_index == 1 )
				// init tile
				tile[( x * 2 ) + 1][y].init ( ( x * TILE_WIDTH ) + ( TILE_WIDTH / 2 ) - start_position_x, ( ( y - 1 ) * ( ( TILE_HEIGHT + 1 ) / 2 ) ) + ( ( TILE_HEIGHT + 1 ) / 2 ) - start_position_y, grass );
		}

		// alternate index
		if ( row_index == 0 )
			row_index = 1;
		else
			row_index = 0;
	}
}

void draw_tiles ( void )
// draws the tiles
{
	tiles_drawn = 0;

	for ( int y = 0; y < TILES_Y; y++ )
	// loop through all tiles vertically
	{
		for ( int x = 0; x < TILES_X; x++ )
		// loop through all tiles horizontally
		{
			if ( tile_offset_x > 0 )
			// left scrolling
			{
				if ( tile[0][0].x >= -( TILE_WIDTH / 2 ) )
					tile_offset_x = 0;
			}

			else if ( tile_offset_x < 0 )
			// right scrolling
			{
				if ( tile[0][0].x <= -( ( TILES_X / 2 ) * TILE_WIDTH ) - 2 + screen_width )
					tile_offset_x = 0;
			}

			if ( tile_offset_y > 0 )
			// up scrolling
			{
				if ( tile[0][0].y >= -( ( TILE_HEIGHT + 1 ) / 2 ) )
					tile_offset_y = 0;
			}

			else if ( tile_offset_y < 0 )
			// down scrolling
			{
				if ( tile[0][0].y <= -( ( TILES_Y / 2 ) * ( TILE_HEIGHT + 1 ) ) - 2 + screen_height )
					tile_offset_y = 0;
			}

			// update position
			tile[x][y].x += tile_offset_x;
			tile[x][y].y += tile_offset_y;

			if ( tile[x][y].x >= -TILE_WIDTH && tile[x][y].x <= screen_width )
			// if tile is horizontally in view
			{
				if ( tile[x][y].y >= -TILE_HEIGHT && tile[x][y].y <= screen_height )
				// if tile is vertically in view
				{
					// draw tile
					tile[x][y].draw ( );

					tiles_drawn++;
				}
			}
		}
	}
}

void destroy_tiles ( void )
// destroy the tiles
{
	// destroy tileset
	tileset.destroy ( );
}




So, my question: is there any error in there from my side or is SDL just not fast enough for 1280x1024-sized games with tiles? Thanks ahead of time. [Edited by - d h k on May 24, 2007 7:02:50 AM]

Share this post


Link to post
Share on other sites
Advertisement
Hidden
Quote:
So, my question: is there any error in there from my side or is SDL just not fast enough for 1280x1024-sized games with tiles?

The answer: No, SDL isn't fast enough - or rather - a graphic card isn't able to handle it in software mode. Why? Let's examine this a little bit.... a 1280x1024 24-bit screen translates to about 3,75 mb of raw data. Take a screenshot in Windows and save it as a .BMP and you'll see! If you are drawing the entire screen in each update, and you have an FPS of 60, this translates to 60 * 3,75 mb = 225 mb of data per second, which is sent to the graphics card. In software mode!

The solution: SDL uses the Windows display, if you're on Windows, so changing to 256 colours or even less doesn't really do much. And it's not a very elegant solution either. Luckily, you can use OpenGL with SDL, and you can actually use OpenGL to draw 2D graphics not much harder than what you do with SDL. You can still use SDL for keyboard and mouse input, timers, etc etc. Give it a try! If you want me to give some basic code for this, let me know and I'll paste it here. It works like a charm, and it's much, much, MUCH faster than letting SDL do the blitting. Plus you can use all the other fancy effects OpenGL provide, like lightning, rotation, scaling, etc etc.

Share this post


Link to post
Okay, then I'll try to work with OpenGL (which I luckily know pretty good). Or would it make sense to switch to hardware-mode and stay with SDL (because I'd really like to stay with it)?

EDIT: Some example code would be well appreciated! :)

Share this post


Link to post
Share on other sites
Hidden
You can give it a try, but I think you'll find that it won't do much difference.

In your SDL_SetVideoMode(), change SDL_SWSURFACE to SDL_HWSURFACE but personally I have never seen any difference between these two modes.

Share this post


Link to post
Yeah, tried that, but didn't seem to change anything.

Okay, so I guess I'll be using OpenGL with SDL from now on.

Share this post


Link to post
Share on other sites
Hidden
Quote:
EDIT: Some example code would be well appreciated! :)

Hehe, allright! Many posts today. :)


#define SCREEN_WIDTH 1024
#define SCREEN_HEIGHT 768
#define NUM_TILES 16
#define TILE_SIZE 32 /* use 32x32 tiles */

/* remember some stuff when we upload textures */
struct BitmapStruct {
GLuint texture_number;
GLfloat texcoord[4];
};

struct BitmapStruct GFX_Tiles[NUM_TILES];

SDL_Surface *MainScreen;
Uint32 bpp, rmask, gmask, bmask, amask;

void InitScreen() {
if (SDL_Init(SDL_INIT_VIDEO) < 0)
exit(1);

putenv("SDL_VIDEO_CENTERED=center");

MainScreen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, 0, SDL_OPENGL);

bpp = MainScreen->format->BitsPerPixel;
rmask = MainScreen->format->Rmask;
gmask = MainScreen->format->Gmask;
bmask = MainScreen->format->Bmask;
amask = MainScreen->format->Amask;

atexit(SDL_Quit);

glEnable(GL_TEXTURE_2D);
glEnable(GL_ALPHA_TEST);
glDisable(GL_DEPTH_TEST);

glAlphaFunc(GL_GREATER, 0);
glClearColor(0.0, 0.0, 0.0, 1.0f);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0, 0.0, 1.0);

glMatrixMode(GL_MODELVIEW);
}

/* here you can load all the graphics for your game */
void LoadGraphicFiles() {
LoadBitmapToStruct("gfx/tiles.bmp", GFX_Tiles, TILE_SIZE, TILE_SIZE, NUM_TILES);
}

/* this loads a bitmap file (*.bmp) and uploads it to textures */
void LoadBitmapToStruct(char *src_file, struct BitmapStruct *array, int width, int height, int frames) {
SDL_Surface *image, *temp;
int i, x = 0, y = 0, w, h;
SDL_Rect area;
GLuint texture;

image = SDL_LoadBMP(src_file);
if (!image)
exit(1);
SDL_SetColorKey(image, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(image->format, 255, 0, 255));
w = power_of_two(width);
h = power_of_two(height);

for (i = 0; i < frames; i++) {
array.texcoord[0] = 0.0f;
array.texcoord[1] = 0.0f;
array.texcoord[2] = (GLfloat)width / w;
array.texcoord[3] = (GLfloat)height / h;

/* blit from bitmap to temporary surface */
temp = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, bpp, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
area.x = x * width;
area.y = y * height;
area.w = width;
area.h = height;
SDL_BlitSurface(image, &area, temp, NULL);

/* create GL texture */
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, temp->pixels);

array.texture_number = texture;

SDL_FreeSurface(temp);

if ((x * width) + width < image->w)
x++;
else {
y++;
x = 0;
}
}

SDL_FreeSurface(image);
}

static int power_of_two(int input) {
int value = 1;

while (value < input)
value <<= 1;

return value;
}

/* this replaced the SDL_BlitSurface() function */
void GLBlit(struct BitmapStruct *array, int x, int y, int w, int h) {
GLfloat texMinX, texMinY;
GLfloat texMaxX, texMaxY;

/* easier to understand the coordinates for the texture */
texMinX = array->texcoord[0];
texMinY = array->texcoord[1];
texMaxX = array->texcoord[2];
texMaxY = array->texcoord[3];

glBindTexture(GL_TEXTURE_2D, array->texture_number);

/* draw polygon */
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(texMinX, texMinY); glVertex2i(x, y);
glTexCoord2f(texMaxX, texMinY); glVertex2i(x + w, y);
glTexCoord2f(texMinX, texMaxY); glVertex2i(x, y + h);
glTexCoord2f(texMaxX, texMaxY); glVertex2i(x + w, y + h);
glEnd();
}

/* easier call to GLBlit() */
void BlitTile(int tile, int x, int y) {
GLBlit(&GFX_Tiles[tile], x, y, TILE_SIZE, TILE_SIZE);
}



Assuming all tiles are 32x32, you can now draw tiles by just doing:

BlitTile(number_for_the_file, xpos, ypos);

I hope this works. :)

Share this post


Link to post
SDL in hardware mode may be usable (AFAIK hardware is only supported in fullscreen) but it will come down to the fill rate of your video card which can vary greatly between cards.

One thing I can see that might speed things up (admitedly probably not much if at all) is calculating the source rectangle for a sprite once during initialization instead of each time you draw a sprite (potentially quite a few times per frame).

That being said, OpenGL is definitely the way to go with something of this size (meaning 1280x1024). You will also find that OpenGL will give you some more options with alpha blending, scaling, rotation and even primitives like lines and circles since they can all be done in hardware.

Share this post


Link to post
Share on other sites
Well, I already switched to OpenGL now, no turning back... :)

One problem with your code though:

In LoadBitmapToStruct ( )

/* blit from bitmap to temporary surface */
temp = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, window.bpp, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
area.x = x * width;
area.y = y * height;
area.w = width;
area.h = height;
SDL_BlitSurface(image, &area, temp, NULL);

/* create GL texture */
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, temp->pixels); // <- CRASH!*!*!**!*!*!*!*!

array.texture_number = texture;

SDL_FreeSurface(temp);





The marked line makes my application quit instantly with no errors from SDL or anything. Just quits. What could be the problem? The rest works fine. If I comment that line out, I can see OpenGL drawing the tiles, just they are white because of the missing texture. Any help?

Thanks for the help so far.

Share this post


Link to post
Share on other sites
Sounds like temp->pixels isn't pointing to anything - check it against NULL. While you're there, check the error code returned by SDL_BlitSurface - if it's -1, then the blit failed. Also you could check temp itself against NULL after creating it, as SDL_CreateRGBSurface returns NULL on failure.

Then at least you'll know where the problem is.

Share this post


Link to post
Share on other sites
Okay, thanks, I did that.

Turns out, temp does not point to NULL anywhere, but in fact this returns an error:


if ( temp == NULL )
return_error ( "Failed to create surface..." );
area.x = x * width;
area.y = y * height;
area.w = width;
area.h = height;

if ( !SDL_BlitSurface(image, &area, temp, NULL))
return_error ( "Failed to create texture..." ); // this returns an error

/* create GL texture */
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);





What is wrong with SDL_BlitSurface(image, &area, temp, NULL) though? Seems right to me.

EDIT: Sorry, that got me again. The test is wrong, BlitSurface returns 0 on SUCCESS and -1 on FAILURE. I changed that and now no errors are returned, yet still the game quits when calling that line marked above with glTexImage2d.

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!