Sign in to follow this  
d h k

SDL possibly to slow?

Recommended Posts

Hello everybody, Yesterday I put together a nice tile engine with SDL. Now I have the problem that it runs so slow that I doubt SDL can even handle it. Here are the details: - 2 layered tile engine ( 320x200 screen size; 10x10 tile size; 32x20 tiles on scree), first layer is ground layer and second layer is detail layer (rendered with transparent color key) for stones, tables, trees etc. Tiles are not isometric but square sized. - static sprite support, at the moment a soldier is drawn on the tileset, which can be moved by pressing the cursor keys. - a sprite that replaces the hidden standard cursor. - a particle system that creates real-time rendered sparks whenever you click the mouse. I am sure my code is not even close to being optimized, but I'd still like to know whether I can optimize the code I already have in a simple way or whether I should rather switch to something with faster blitting routines. Here is the relevant code as well: The tile engine part:
void TilesDrawRect ( int tile_layer, int tile_value, int x1, int y1, int x2, int y2 )
// draws a rectangle to the tile array variable
{
	for ( int x = x1; x < x2; x++ )
	{
		for ( int y = y1; y < y2; y++ )
		{
			tile[tile_layer][x][y] = tile_value;
		}
	}
}

void TilesDrawLine ( int tile_layer, int tile_value, int x1, int y1, int x2, int y2 )
// draws a line to the tile array variable
{
	if ( x1 == x2 )
	{
		for ( int y = y1; y < y2; y++ )
		{
				tile[tile_layer][x1][y] = tile_value;
		}
	}

	if ( y1 == y2 )
	{
		for ( int x = x1; x < x2; x++ )
		{
				tile[tile_layer][x][y1] = tile_value;
		}
	}
}


void LoadTiles ( int layer )
// loads data for one layer and puts it to the tile variable
{
	if ( layer == 1 )
	{
		TilesDrawRect ( layer, 1, 0, 0, TILES_X, TILES_Y );
		TilesDrawRect ( layer, 2, 14, 0, 18, TILES_Y );
		TilesDrawLine ( layer, 4, 14, 0, 14, TILES_Y );
		TilesDrawLine ( layer, 5, 18, 0, 18, TILES_Y );
	}
	else if ( layer == 2 )
	{
		TilesDrawRect ( layer, 2, 0, 0, TILES_X, TILES_Y );

		for ( int i = 0; i < 20; i++ )
		{
			int rand_x, rand_y, rand_tile_value;

			rand_x = rand ( ) % TILES_X;
			rand_y = rand ( ) % TILES_Y;
			rand_tile_value = /*( rand ( ) % 2 ) +*/ 2;

			tile[layer][rand_x][rand_y] = rand_tile_value;
		}
	}
}

void DrawTiles ( int layer )
// draws all the tiles in one layer to the screen
{
	if ( layer == 1 )
	{
		for ( int i = 0; i < TILES_X; i++ )
		{
			for ( int j = 0; j < TILES_Y; j++ )
			{
				SDL_Surface *image;
				SDL_Rect	rect;

				rect.x = i * TILE_WIDTH;
				rect.y = j * TILE_HEIGHT;

				if ( tile[layer][i][j] == 0 )
					return;
				else if ( tile[layer][i][j] == 1 )
					image = SDL_LoadBMP ( "tiles/tile1.bmp" );
				else if ( tile[layer][i][j] == 2 )
					image = SDL_LoadBMP ( "tiles/tile2.bmp" );
				else if ( tile[layer][i][j] == 3 )
					image = SDL_LoadBMP ( "tiles/tile3.bmp" );
				else if ( tile[layer][i][j] == 4 )
					image = SDL_LoadBMP ( "tiles/tile1-2_x.bmp" );
				else if ( tile[layer][i][j] == 5 )
					image = SDL_LoadBMP ( "tiles/tile2-1_x.bmp" );
				else
					ReturnError ( "Invalid tile value!" );

				if ( image == NULL ) 
					ReturnError ( "Couldn't create surface to draw image!" );

				if ( image->format->palette && screen->format->palette )
					SDL_SetColors ( screen, image->format->palette->colors, 0, image->format->palette->ncolors );

				if ( SDL_BlitSurface ( image, NULL, screen, &rect ) < 0 )
					ReturnError ( "Couldn't blit surface to screen!" );

				SDL_UpdateRect ( screen, TILE_WIDTH * i, TILE_HEIGHT * j, image->w, image->h );

				SDL_FreeSurface ( image );
			}
		}
	}
	else if ( layer == 2 )
	{
		for ( int i = 0; i < TILES_X; i++ )
		{
			for ( int j = 0; j < TILES_Y; j++ )
			{
				SDL_Surface *image;
				SDL_Rect	rect;

				rect.x = i * TILE_WIDTH;
				rect.y = j * TILE_HEIGHT;

				if ( tile[layer][i][j] == 0 )
					return;
				else if ( tile[layer][i][j] == 1 )
					image = SDL_LoadBMP ( "tiles/deco1.bmp" );
				else if ( tile[layer][i][j] == 2 )
					image = SDL_LoadBMP ( "tiles/deco2.bmp" );
				else if ( tile[layer][i][j] == 3 )
					image = SDL_LoadBMP ( "tiles/deco3.bmp" );
				else
					ReturnError ( "Invalid tile value!" );

				SDL_SetColorKey ( image, SDL_SRCCOLORKEY, SDL_MapRGB ( image->format, 0, 0, 0 ) );

				if ( image == NULL ) 
					ReturnError ( "Couldn't create surface to draw image!" );

				if ( image->format->palette && screen->format->palette )
					SDL_SetColors ( screen, image->format->palette->colors, 0, image->format->palette->ncolors );

				if ( SDL_BlitSurface ( image, NULL, screen, &rect ) < 0 )
					ReturnError ( "Couldn't blit surface to screen!" );

				SDL_UpdateRect ( screen, TILE_WIDTH * i, TILE_HEIGHT * j, image->w, image->h );

				SDL_FreeSurface ( image );
			}
		}
	}
}



So much for the 2-layered tile engine. Now let's go to the object system ( for static sprites ):
class object
// object is the base class for any object on the screen
{
	public:
		char	*file_name;
		int		x, x_vel, y, y_vel, speed;
		void	draw ( int x, int y );
		void	refresh ( void );
};

void object::draw ( int x, int y )
{
	SDL_Surface	*image;
	SDL_Rect	rect;

	rect.x = x;
	rect.y = y;

    image = SDL_LoadBMP ( file_name );

	SDL_SetColorKey ( image, SDL_SRCCOLORKEY, SDL_MapRGB ( image->format, 0, 0, 0 ) );
    
	if ( image == NULL ) 
		ReturnError ( "Couldn't create surface to draw image!" );

    if ( image->format->palette && screen->format->palette )
		SDL_SetColors ( screen, image->format->palette->colors, 0, image->format->palette->ncolors );

    if ( SDL_BlitSurface ( image, NULL, screen, &rect ) < 0 )
        ReturnError ( "Couldn't blit surface to screen!" );

    SDL_UpdateRect ( screen, x, y, image->w, image->h );

    SDL_FreeSurface ( image );
}

void object::refresh ( void )
{
	if ( speed < 1 )
		speed = 1;

	for ( int i = 0; i < speed; i++ )
	{
		if ( x_vel < 0 && x <= i )
			x_vel = 0;

		if ( x_vel > 0 && ( x + TILE_WIDTH ) >= ( SCREEN_WIDTH - i ) )
			x_vel = 0;

		if ( y_vel < 0 && y <= i )
			y_vel = 0;

		if ( y_vel > 0 && ( y + TILE_HEIGHT ) >= ( SCREEN_HEIGHT - i ) )
			y_vel = 0;
	}

	x += x_vel;
	y += y_vel;
}

object soldier;
object mouse



In the end I define and later on I use two objects, one time the soldier sprite for the player character and one time the fake cursor. And now to the particle system:
void DrawPixel ( SDL_Surface *pSurf, int x, int y, Uint32 color )
{
	int		bpp;
	Uint8	*p;

	if ( x < 0 || x >= pSurf->w || y < 0 || y >= pSurf->h )
		return;

	bpp = pSurf->format->BytesPerPixel;
	p = (Uint8 *)pSurf->pixels + y * pSurf->pitch + x * bpp;

	switch ( bpp )
	{
		case 1:
			*p = color;
		break;
		case 2:
			*(Uint16 *)p = color;
		break;
		case 4:
			*(Uint32 *)p = color;
		break;
		case 3:
			#if SDL_BYTEORDER == SDL_BIG_ENDIAN
				p[0] = (color >> 16) & 0xff;
				p[1] = (color >> 8) & 0xff;
				p[2] = color & 0xff;
			#else
				p[0] = color & 0xff;
				p[1] = (color >> 8) & 0xff;
				p[2] = (color >> 16) & 0xff;
			#endif
		break;
	}
}

particle_t *CreateParticle ( float x, float y, Uint32 color, float vx, float vy )
{
    particle_t *p = (particle_t *)malloc(sizeof(particle_t));

    p->color = color;
    p->x = x;
    p->y = y;
    p->vx = vx;
    p->vy = vy;

    p->next = g_pParticles;
    if (p->next)
        p->next->pprev = &p->next;
    p->pprev = &g_pParticles;
    g_pParticles = p;
    return p;
}

void RemoveParticle ( particle_t *p )
{
    if (p->next)
        p->next->pprev = p->pprev;
    *p->pprev = p->next;

    free ( p );
}

void ParticleThink ( )
{
    particle_t *next = g_pParticles;

    while ( next )
	{
        particle_t *p = next;
        next = p->next;

		p->vy += 300 / 1000.0f * frametime;

		p->x += p->vx * frametime/1000.0f;
        p->y += p->vy * frametime/1000.0f;

		if (p->x < 0 || p->x >= SCREEN_WIDTH ||	p->y >= SCREEN_HEIGHT )
            RemoveParticle ( p );
    }
}

void DrawParticle ( )
{
    particle_t *p;

    for ( p = g_pParticles; p; p = p->next )
        DrawPixel(screen, (int)p->x, (int)p->y, p->color);
}

void EmitParticles ( )
{
	while ( lastemit + PARTICLE_DELAY < curframe )
	{
        float speed, angle;
        float vx, vy;
        Uint32 color;

        lastemit += PARTICLE_DELAY;

        color = SDL_MapRGB ( screen->format, ( rand ( ) % 75 ) + 160, ( rand ( ) % 75 ) + 160, ( rand ( ) % 25 ) + 50 );

		speed = (float)( rand ( ) % 100 );
        angle = (float)( rand ( ) * ( ( 2.0 * MATH_PI ) / RAND_MAX ) );
        vx = (float)( cos ( angle ) * speed );
        vy = (float)( sin ( angle ) * speed );

		CreateParticle ( (float)mouse_x, (float)mouse_y, color, vx, vy );
    }
}

void ExitParticles ( void )
{
	while(g_pParticles)
		RemoveParticle ( g_pParticles );
}

void P_Init ( )
{
	atexit ( ExitParticles );
    lastemit = curframe;
}



ALlright now a last bit of code. Here's how I blit everything to the screen every frame:
void Repeatedly_Execute ( void )
// called every frame
{
	// keep our mouse coordinate variables accurate
	SDL_GetMouseState ( &mouse_x, &mouse_y );

	// draw the soldier
	soldier.draw ( soldier.x, soldier.y );
	soldier.refresh ( );

	// draw the first tile layer
	DrawTiles ( 1 );

	// then draw the second tile layer
	DrawTiles ( 2 );

	mouse.x = mouse_x;
	mouse.y = mouse_y;

	mouse.draw ( mouse.x, mouse.y );

	if ( draw_mouse_particles )
	{
		EmitParticles ( );
	}

	ParticleThink ( );

	//SDL_FillRect(screen, 0, SDL_MapRGB ( screen->format, 0, 0, 0 ) );

	if ( !SDL_LockSurface ( screen ) )
	{
		DrawParticle ( );
		SDL_UnlockSurface ( screen );
	}
	else
		ReturnError ( "Couldn't lock the screen surface! ");

	// flip the buffer
	SDL_Flip ( screen );
}



Anybody have any ideas on why this is so slow? I am really stuck with this. Am I doing something obviosly the wrong way? EDIT: I should probably say that it's even to slow without the particle system and the fake cursor and soldier. The 2-layered tile engine is already slow and then the rest adds up to "unplayableness".

Share this post


Link to post
Share on other sites
You definitely do not want to be loading the tile images many times every frame. Load them when the game or level starts up, and store them into an array or vector, then instead of doing a whole bunch of if-else statements for each tile image, like you're doing:

else if ( tile[layer][i][j] == 1 )
image = SDL_LoadBMP ( "tiles/tile1.bmp" );

you just do this once per tile:

image = tile_images[ tile[layer][i][j] ];

or something to that effect.

Fix this and you'll see a huge performance increase; it's not SDL's fault.

EDIT:

Also, you don't want to do SDL_UpdateRect for every tile. Just do it once at the end of the drawing loop as SDL_UpdateRect(screen,0,0,0,0) to update the entire screen.

You don't want to be setting SDL_SetColors once per tile, either...in fact, why are you using it at all? The documentation states that SDL_SetColors is only for 8-bit surfaces, and I doubt you're intentionally working in 8-bit color.

Good luck with your game

Share this post


Link to post
Share on other sites
I understand your idea, but I thought I need to redraw the tiles every single frame to erase the background. Otherewise the moving sprites like the soldier etc. would not get updated correctly...

Thanks for posting though, maybe try to describe closer what you mean.

Oh and sorry for being such a newbie with SDL... ;)

EDIT: Oh, did you mean that I should redraw the tiles every frame but only use the SDL_LoadBMP once in the beginning / initialization process?

Share this post


Link to post
Share on other sites
Yes, that's what I meant. Also, read my EDIT from the previous post for more optimizations.

Share this post


Link to post
Share on other sites
Allright. Thanks a lot - that helped.

NOw I just wonder why I didn't think about that myself...

Anyways, thank you.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Another way to get a large performance increase is to only blit the tiles that are on the screen.

Share this post


Link to post
Share on other sites
Another technique you can use in the future is called dirty rectangles - only blit parts of the screen that have changed. But if you're just getting started don't worry about it for now.

Share this post


Link to post
Share on other sites
I can't see how you can possibly have performance problems with 320x200 software rendering on modern (well, post 1997) hardware.

Specifically, if you're not doing some uber-rediculous 3d lightmapping, stuff, it seems impossible. Certainly if you're not doing any rotating or blending (which it doesn't sound like you are), then you should have no problem.

The only things that I can think of which could possibly make it slow are:
- Doing inappropriate stuff inside your game loop (like loading gfx every frame)
- A wildly inappropriate frustum culling technique (i.e. trying to draw a lot of items way outside the visible area)

With even late-90s hardware, you could overdraw 320x200 half a dozen times in software and still get full frame rates.

Mark

Share this post


Link to post
Share on other sites
Just a question, why ARE you loading everything every frame? From your source code, I can see that your drawing function has SDL_LoadBMP() in it. Why is it in there? When they say to redraw everything every frame, they don't mean load it in every frame, too!

Share this post


Link to post
Share on other sites
I highly recommend you start to use a profiler. In one run you can find exactly what function is slowing you down, instead of guessing.

Share this post


Link to post
Share on other sites
Allright, thanks for all your interest, guys.

I was using the SDL_LoadBitmap function every single frame (because I didn't know any better) - that's why my 320x200 scene was so slow. Now I fixed it and everythings doing great.

Oh, I don't have any outside tiles at the moment, just one screen full...

Share this post


Link to post
Share on other sites

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