[SDL] Why does my program slow down in fullscreen? [SOLVED]

Started by
15 comments, last by d h k 17 years ago
Hello everybody, I'm really making good progress with one of my applications at the moment, I have written a basic GUI for it and it works fine and I have isometric tiles on several layers included. It all works, but it's slow - this leads me to the question: I draw 20x20 simple bitmap tiles to the screen every frame. Each tile has a width of 18 and a height of 9, but that shouldn't matter. It looks like this: Ignore the GUI and the (two) cursors for now, they are not the problem, I tested that. When I run the program at 640x480 for example in windowed mode, it's fast, but when I run it in fullscreen, it really drops the framerate to around 2-6 fps. The problem are the tiles, so I guess I'll show you how I draw my tiles and how I set up my window: The init- and draw-function for my tiles, those are called in loops for every tile...

void ctile::init ( SDL_Surface* screen_surface, int _x, int _y, TILE_TYPE _type )
// initializes the tile
{
	// initialize the sprite
	switch ( _type )
	{
		case tile_grass:
			sprite.init ( screen_surface, "tiles/grass.bmp" );
		break;
		case tile_concrete:
			sprite.init ( screen_surface, "tiles/concrete.bmp" );
		break;
		case tile_dirt:
			sprite.init ( screen_surface, "tiles/dirt.bmp" );
		break;
		case tile_grass_ramp_x:
			sprite.init ( screen_surface, "tiles/grass_ramp_x.bmp" );
		break;
		case tile_grass_ramp_y:
			sprite.init ( screen_surface, "tiles/grass_ramp_y.bmp" );
		break;
		case tile_grass_ramp_xy:
			sprite.init ( screen_surface, "tiles/grass_ramp_xy.bmp" );
		break;
		case tile_rail_y:
			sprite.init ( screen_surface, "tiles/rail_y.bmp" );
		break;
		default:
		break;
	}	

	// position the tile
	sprite.x = ( float ) _x;
	sprite.y = ( float ) _y;

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

	// save type
	type = _type;
}

void ctile::draw ( void )
// draws the tile
{
	if ( type != tile_empty )
	// if the tile is not empty
		// draw the sprite
		sprite.draw ( x, y );
}



And now you'll also need to see the sprite-drawing functions I use, I guess, so here we go:

void csprite::init ( SDL_Surface* screen_surface, const char *filename, int _width, int _height )
// initializes the sprite
{
	// load the bitmap
	bitmap = SDL_LoadBMP ( filename );

	// set the sprites surface to screen_surface
	surface = screen_surface;

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

	// 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;
	
	// activate color keying
	/*if ( !*/SDL_SetColorKey ( bitmap, SDL_SRCCOLORKEY, SDL_MapRGB ( bitmap->format, 255, 0, 128 ) ); //)
	// if there was an error
		//return_error ( "Failed to colorkey sprite..." );

	// blit the bitmap to the screen
	/*if ( !*/SDL_BlitSurface ( bitmap, &source, surface, &destination );// )
	// 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 ( );
}


And now how I set up my window:

void cwindow::create ( const char* title, const char* filename )
// creates the window
{
	// load the settings
	load_settings ( filename );

	// initialize systems
	SDL_Init ( SDL_INIT_VIDEO );
	atexit ( SDL_Quit );

	if ( fullscreen )
	// if we want a fullscreen window
		// create a fullscreen window
		surface = SDL_SetVideoMode ( width, height, bpp, SDL_SWSURFACE | SDL_ANYFORMAT | SDL_FULLSCREEN );

	if ( !fullscreen )
	// if we don't want a fullscreen window
		// create a windowed window
		surface = SDL_SetVideoMode ( width, height, bpp, SDL_SWSURFACE | SDL_ANYFORMAT );

	if ( surface == NULL )
	// if there was an error
		return_error ( "Failed to create window..." );

	// set window title
	SDL_WM_SetCaption ( title, NULL );

	// store screen dimensions
	screen_width = width;
	screen_height = height;
	
	// hide standard mouse cursor
	//SDL_ShowCursor ( SDL_DISABLE );
}

void cwindow::clear ( void )
// clears the window
{
	// clear the window
	SDL_FillRect ( surface, NULL, 0 );
}

void cwindow::update ( void )
// updates the window
{
	// update the window
	SDL_Flip ( surface );
}


In my main-function, I call window.clear every frame first, then draw everything, then window.update... Has anybody got a clue what might be wrong here? I really hate having my program work only in windowed mode or extremely slow, so I'd appreciate any help. Thanks ahead of time. [Edited by - d h k on April 23, 2007 7:04:52 AM]
Advertisement
One thing that might help is to make sure that your surfaces have the same format as your screen surface. Otherwise SDL must convert your surfaces every time you blit them. You can fix this by converting all your surfaces at loading-time with the function SDL_Display_Format(). So your image loading would look something like this:


	// Loading the image	SDL_Surface * tmp = SDL_LoadBMP( filename );    	if( !tmp )    	{       		// something's wrong    	}		SDL_Surface * optSurface = NULL;    	optSurface = SDL_DisplayFormat( tmp );    	if( !optSurface )    	{        	//something's wrong    	}	// Don't forget this		SDL_FreeSurface( tmp );


Other than that I don't know. Hope it helps :)
Sorry for offtopic: But should i free the converted surface after i finished using it? In your case its "optSurface" should i free it?
Thansk.

I would love to change the world, but they won’t give me the source code.

First of all, set the color key once, during loading.

Other than that, one big difference between windowed mode and fullscreen mode is that fullscreen mode may allow your prorgam to get a hardware screen surface. Hardware surfaces can be faster at regular blitting, but can be a lot slower for alpha blending or surface reads.

Note that specifying SDL_SWSURFACE will not force a software surface as SDL_SWSURFACE is acutally a bitmask of 0, so it is ignored. To check if the surface is hardware or not compare (screen->flags & SDL_HWSURFACE) and SDL_HWSURFACE. Also take a look at the SDL enviorment variable SDL_VIDEODRIVER. On windows there is a directX and a windib driver, see if changing from one to the other improves anything (research SDL_setenv or whatever the function is).

Anther thing you might want to look into is dirty rectangles. If your game doesnt have a lot of fast moving animations this can really help. The idea is that you only redraw the screen in places where something has changed. It requires more work in the blitting code, but it might be worth it. I would use this as a last resort though.
Quote:Original post by Minios
Sorry for offtopic: But should i free the converted surface after i finished using it? In your case its "optSurface" should i free it?
Thansk.


No, that becomes the surface you use. You would assign it to "bitmap". You free it when destructing the csprite.

Also this is a handy function you might like, SDL_GetVideoSurface(). It can reduce handing the screen surface around to every drawing function.
Thanks for your help. I did that, now my function looks like this:

void csprite::init ( SDL_Surface* screen_surface, const char *filename, int _width, int _height )// initializes the sprite{	SDL_Surface* temp = NULL;	// load the bitmap	temp = SDL_LoadBMP ( filename );	if( !temp )    {		//something's wrong	}	    bitmap = SDL_DisplayFormat ( temp );	if ( bitmap == NULL )	// if there was an error		return_error ( "Failed to initialize sprite..." );   	// Don't forget this		SDL_FreeSurface ( temp );	// set the sprites surface to screen_surface	surface = screen_surface;	// set up other variables	width = _width;	height = _height;}


It works, but doesn't help. Still very slow (if not slower than before somehow).
Thanks to you, too, rip-off. When I posted, your answers weren't there, which is why I didn't react...

Unfortunately, I am not sure how to proceed.

So, you say that fullscreen-mode may allow hardware-mode, which could be bad and the problem, right? While the windowed mode would always use software which would be faster and good?

If so, you told me how to detect in what mode I am, which is nice, but in fact, I'd need to either force software-mode for both windowed and fullscreen or improve performance so that both works...

AND, shouldn't really both hardware and software mode be fast enough to draw 400 simple sprites with SDL?

Sorry for all those questions... :)

EDIT: One more thing, for the newest version of my sprite-init function, when I uncomment the lines around the colorkey-call, it crashes with that return_error function, can this be part of the problem?

void csprite::init ( const char *filename, int _width, int _height )// initializes the sprite{	SDL_Surface* temp = NULL;	// 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 );	if ( bitmap == NULL )	// if there was an error		return_error ( "Failed to initialize sprite..." );   	// free temp		SDL_FreeSurface ( temp );	// activate color keying	//if ( !SDL_SetColorKey ( bitmap, SDL_SRCCOLORKEY, SDL_MapRGB ( bitmap->format, 255, 0, 128 ) ) )	// if there was an error		//return_error ( "Failed to colorkey sprite..." );	// set up other variables	width = _width;	height = _height;}


[Edited by - d h k on April 22, 2007 12:56:56 PM]
SDL_SetColorKey (and pretty much every SDL function that doesn't return a pointer ) will return 0 on success, and will return less than 0 if an error occurs. Use SDL_GetError() to get a human readable error code.

Yes, modern computers should be fast enough to draw that many sprites. Another problem could simply be memory cache thrashing, if you load all the same image file into 400 different sprites, thats not as fast as using 1 SDL_Surface to draw from.

Look into managed pointers such as boost::shared_ptr<>, which makes the handling of this relatively easy (simple example):

// image.hpp#include <string>#include <boost/smart_ptr.hpp>typedef boost::shared_ptr<SDL_Surface> SharedSurface;class Image {public:    Image( const std::string &file );    void draw( float x, float y );    // the restprivate:    SharedSurface surface;};// image.cpp#include <map>namespace {    std::map<std::string,SharedSurface> loadedImages;    SharedSurface getImage( const std::string &file )    {         std::map<std::string,SharedSurface>::iterator it = loadedImages.find(file);         if( it == loadedImages.end() )         {                SDL_Surface *surf = SDL_LoadBMP(file.c_str());                // error checking, SDL_DiplayFormat(), SDL_SetColorKey(), etc...                SharedSurface shared( surf, &SDL_FreeSurface );                loadedImages.insert( std::make_pair(file,shared) );                return shared;         }         else         {                return it->second;         }    }} // end anon namespaceImage::Image( const std::string &file ):      surface( getImage(file) ){}void Image::draw( float x, float y ){      SDL_Rect dest = {x,y,surface->w,surface->h};      SDL_BlitSurface( surface.get(), NULL, SDL_GetVideoSurface(), &dest );}


Note: the above has not been compiled or tested.

Also note how Image lacks a destructor, boost::shared_ptr takes care of that for us. The only difficulty with the above code is that the SharedSurfaces in the map will be destructed after calling SDL_Quit(), which isn't a good idea. We would rewrite the above to use boost::weak_ptr in the map, but that is a bit beyond this example ( which goes too far as it is probably [smile] ).
Thanks for the help!

And here I thought it was simple to write a standard tile program using SDL with some logic, a few for-loops and objects... I guess I'll have to take a good look at those managed pointers.
Quote:Original post by d h k
Thanks for the help!

And here I thought it was simple to write a standard tile program using SDL with some logic, a few for-loops and objects... I guess I'll have to take a good look at those managed pointers.


I actually meant to mention this but forgot, you may be able to force a fullscreen software surface by changing the video driver ( the environmental variable I alluded to earlier ). See here. I believe the directx driver may give you a hardware surface, while the windib one won't.

This topic is closed to new replies.

Advertisement