Performance issue with 2D graphics

If you are a new Irrlicht Engine user, and have a newbie-question, this is the forum for you. You may also post general programming questions here.
Post Reply
Quillraven
Posts: 62
Joined: Fri Aug 22, 2008 7:22 am

Performance issue with 2D graphics

Post by Quillraven »

!!!The color problem is already solved!!!
Still searching for a batching advise or sthg


Hi,

long time since i last created a topic here, but finally i got a little free time again to play around with irrlicht ;)
and - of course - i'm having a performance problem with the 2d graphics and i wonder, what i'm doing wrong.

my application is creating 2000 32x32 images which are moving randomly within the windowsize. since i know, that with f.e. DarkGDK or SDL i can create more then 2k sprites and still have a fpsrate>60, i am confused, that with irrlicht i only get about 10-15fps.
i know irrlicht is much more complex and u can easily mess things up with stupid mistakes, but i don't really get my problem in my application here.

the important classes are the image,explodeableobject and triangle class.
here they are:

Code: Select all

#ifndef IMAGE_H
#define IMAGE_H

#include "Framework.h"



class Image
{
private:
	irr::video::ITexture*	_texture;
	irr::gui::IGUIImage*	_image;
	irr::video::SColor		_color;
	float					_posX;
	float					_posY;
	float					_scale;
	unsigned int			_width;
	unsigned int			_height;

	void SetBoundingRect()
	{
		_image->setRelativePosition( 
			irr::core::rect<int>( (int)_posX, (int)_posY, (int)(_posX+_width*_scale), (int)(_posY+_height*_scale) ) );
	}	// end SetBoundingRect

public:
	Image( const char* texturepath )
	{
		_texture = VIDEODRIVER->getTexture( texturepath );
		_color = irr::video::SColor( 255, 255, 255, 255 );
		_scale = 1;
		_posX = _posY = 0;
		_width = _texture->getSize().Width;
		_height = _texture->getSize().Height;
		_image = GUIENV->addImage( _texture, irr::core::vector2d<int>((int)_posX,(int)_posY) );
		_image->setScaleImage( true );
	}	// end Konstructor

	~Image()
	{
		VIDEODRIVER->removeTexture( _texture );
		_image->remove();
	}	// end Destructor

	void draw(){ _image->draw(); }
	void SetPosition( float x, float y ){ _posX = x; _posY = y; SetBoundingRect(); }
	void SetScale( float scale ){ _scale = scale; SetBoundingRect(); }
	void SetColor( int r, int g, int b )
	{
		_color = irr::video::SColor( 255, r, g, b );
		_image->setColor( _color );
	}
	inline float GetPositionX() const { return _posX; }
	inline float GetPositionY() const { return _posY; }
	inline float GetScale() const { return _scale; }
	inline float GetWidth() const { return _width*_scale; }
	inline float GetHeight() const { return _height*_scale; }
};


#endif

Code: Select all

#ifndef EXPLODEABLEOBJECT_H
#define EXPLODEABLEOBJECT_H

#include "Image.h"


class ExplodeableObject
{
protected:
	static const float	 SPEED_MIN;
	static const float	 SPEED_MAX;
	static const float	 TIME_EXPLOSION;
	static const float	 SCALE_MAX;				// maximum scale size for explosion
	irr::core::vector2df _velocity;
	irr::core::vector2df _position;
	irr::video::SColor	 _color;
	float				 _scale;
	bool				 _isExploding;
	bool				 _isRemoveable;
	float				 _explosionTime;
	float				 _scaleGain;			// on explosion scalegain
	Image*				 _image;

public:
	ExplodeableObject( Image* img )
	{
		_isExploding = _isRemoveable = false;
		_velocity = irr::core::vector2df( FRAMEWORK->GetRandomFloat( SPEED_MIN, SPEED_MAX ),
										  FRAMEWORK->GetRandomFloat( SPEED_MIN, SPEED_MAX ) );
		switch( FRAMEWORK->GetRandomInt( 1, 4 ) )
		{
		case( 0 ): break;
		case( 1 ): _velocity.X = -_velocity.X; break;
		case( 2 ): _velocity.Y = -_velocity.Y; break;
		case( 3 ): _velocity.X = -_velocity.X; _velocity.Y = -_velocity.Y; break;
		}
		_image = img;
		_scale = 1;
		_position = irr::core::vector2df( (float)FRAMEWORK->GetRandomInt( 0, VIDEODRIVER->getScreenSize().Width ),
			(float)FRAMEWORK->GetRandomInt( 0, VIDEODRIVER->getScreenSize().Height ) );
		_color = irr::video::SColor( 255, FRAMEWORK->GetRandomInt(0,255), FRAMEWORK->GetRandomInt(0,255), FRAMEWORK->GetRandomInt(0,255) );
		_explosionTime = _scaleGain = 0.0f;
	}	// end Konstructor
	virtual	~ExplodeableObject()
	{
		//delete _image;
	}	// end Destructor
	virtual void				OnUpdate( float deltatime ) = 0;
	void						Update( float deltatime )
	{
		if( _isRemoveable )
			return;

		OnUpdate( deltatime );
		_position = irr::core::vector2df( _position.X+_velocity.X*deltatime,
										  _position.Y+_velocity.Y*deltatime );

		// if object is exploding, expand it for TIME_EXPLOSION/2 seconds before
		// it slowly gets smaller and smaller until it disappears
		if( _isExploding )
		{
			if( _explosionTime == 0 )
			{
				// calculate the scalegain per second
				_scaleGain = (SCALE_MAX-_image->GetScale())/(TIME_EXPLOSION*0.5f);
			}
			else if( _explosionTime >= TIME_EXPLOSION )
				_isRemoveable = true;
			else if( _explosionTime >= (TIME_EXPLOSION * 0.5f) && _scaleGain > 0 )
				_scaleGain = -_scaleGain;
			_explosionTime += deltatime;
			_scale += _scaleGain*deltatime;
		}
	}	// end Update



	virtual void				OnRender() = 0;
	void						Render()
	{
		if( _isRemoveable )
			return;

		OnRender();
		_image->SetColor( _color.getRed(), _color.getGreen(), _color.getBlue() );
		_image->SetScale( _scale );
		_image->SetPosition( _position.X, _position.Y );
		_image->draw();
	}	// end Render
	void						SetVelocity( float velX, float velY ){ _velocity = irr::core::vector2df( velX, velY ); }
	inline irr::core::vector2df GetVelocity() const { return _velocity; }
	void						Explode(){ _isExploding = true; }
	inline bool					IsExploding() const { return _isExploding; }
	inline bool					IsRemoveable() const { return _isRemoveable; }
	virtual bool				IsColliding( const ExplodeableObject* obj ) = 0;
	const Image*				GetImage(){ return _image; }
};

const float ExplodeableObject::SPEED_MIN		= 12.0f;
const float ExplodeableObject::SPEED_MAX		= 25.5f;
const float ExplodeableObject::TIME_EXPLOSION	= 2.5f;
const float ExplodeableObject::SCALE_MAX		= 3.0f;

#endif

Code: Select all

#ifndef TRIANGLE_H
#define TRIANGLE_H

#include "ExplodeableObject.h"



class Triangle : public ExplodeableObject
{
private:
	irr::core::position2df _vertices[3];

	void UpdateVertices()
	{
		// x and y of the image is the top left corner of the image rect
		// to get the 3 triangle vertices we just need the top middle,
		// left bottom and left right coordinate
		const float x = _image->GetPositionX();
		const float y = _image->GetPositionY();
		const float w = _image->GetWidth();
		const float h = _image->GetHeight();
		_vertices[0] = irr::core::position2df( x + w/2, y );
		_vertices[1] = irr::core::position2df( x, y+h );
		_vertices[2] = irr::core::position2df( x+w, y+h );
	}

public:
	Triangle( Image* img ) : ExplodeableObject( img )
	{
		UpdateVertices();
	}	// end Konstructor
	~Triangle()
	{
	}	// end Destructor
	void OnUpdate( float deltatime )
	{
		UpdateVertices();
		for( int i=0; i<3; ++i )
		{
			// check if a vertex is outside the window
			// if so -> recalculate velocity
			if( _vertices[i].X+_velocity.X*deltatime < 0 || _vertices[i].X+_velocity.X*deltatime > VIDEODRIVER->getScreenSize().Width ||
				_vertices[i].Y+_velocity.Y*deltatime < 0 || _vertices[i].Y+_velocity.Y*deltatime > VIDEODRIVER->getScreenSize().Height )
			{
				_velocity.X = -_velocity.X;
				_velocity.Y = -_velocity.Y;
			}
		}
		//IsColliding( obj );
	}	// end OnUpdate
	void OnRender(){}
	bool IsColliding( const ExplodeableObject* obj )
	{
		return false;
	}	// end IsColliding
};

#endif


and finally the mainloop, where i create and update the objects:

Code: Select all


void Game::Loop()
{
	irr::IrrlichtDevice* device = DEVICE;
	irr::video::IVideoDriver* video = VIDEODRIVER;
	device->setWindowCaption( L"FQ Exploder 2010" );
	FRAMEWORK->PlayMusic( "media/music/background.mp3" );

	while( device->run() && video && _currentState != GAME_STATE_QUIT )
	{
		if( device->isWindowActive() )
		{
			FRAMEWORK->UpdateTime();
			video->beginScene();

			switch( _currentState )
			{
			case( GAME_STATE_MAIN_MENU ): RunMainMenu(); break;
			case( GAME_STATE_GAME ): RunGame(); break;
			case( GAME_STATE_QUIT ): break;
			case( GAME_STATE_VICTORY ): RunVictory(); break;
			case( GAME_STATE_DEFEAT ): RunDefeat(); break;
			}

			video->endScene();
			FRAMEWORK->UpdateTime();
			FRAMEWORK->UpdateInput();
		}
		else
			device->yield();
	}
}	// end Loop



void Game::RunMainMenu()
{
}	// end RunMainMenu



void Game::RunGame()
{
	const int numtris = 2000;
	static Triangle* tri[numtris];
	static Image* img = new Image( "media/images/Triangle.png" );
	if( tri[0] == NULL )
	{
		for( int i = 0;i<numtris; ++i )
			tri[i] = new Triangle( img );
	}
	if( FRAMEWORK->IsKeyPressed( irr::KEY_ESCAPE ) )
	{
		for( int i = 0;i<numtris; ++i )
			delete tri[i];
		delete img;
		_currentState = GAME_STATE_QUIT;
		return;
	}

	if( FRAMEWORK->IsMouseLeftPressed() )
		tri[FRAMEWORK->GetRandomInt( 0, numtris-1 )]->Explode();

	const float deltat = FRAMEWORK->GetDeltaTime();
	for( int i = 0;i<numtris; ++i )
	{
		tri[i]->Update( deltat );
		tri[i]->Render();
	}

	irr::core::stringw text = "";
	text += VIDEODRIVER->getFPS();
	DEVICE->setWindowCaption( text.c_str() );
}	// end RunGame

if i comment the tri->Update( deltat ); line i still only get ~10fps so there must be some mistakes within the render method and i can't find them.

some hints pls :)



edit:
k i found out, that i don't need the iguiimage. and with a little code refactoring the application now runs 3time faster (~30fps instead of 10). however i now got a weird color problem. the triangles seem more transparent now and switching their colors continuously. an explanation would help me there, if someone knows why.

here the new image code:

Code: Select all

#ifndef IMAGE_H
#define IMAGE_H

#include "Framework.h"



class Image
{
private:
	irr::video::ITexture*	_texture;
	irr::video::SColor		_color;
	float					_posX;
	float					_posY;
	float					_scale;
	unsigned int			_width;
	unsigned int			_height;

public:
	Image( const char* texturepath )
	{
		_texture = VIDEODRIVER->getTexture( texturepath );
		_color = irr::video::SColor( 255, 255, 255, 255 );
		_scale = 1;
		_posX = _posY = 0;
		_width = _texture->getSize().Width;
		_height = _texture->getSize().Height;
	}	// end Konstructor

	~Image()
	{
		//VIDEODRIVER->removeTexture( _texture );
	}	// end Destructor

	void draw()
	{ 
		VIDEODRIVER->draw2DImage( _texture,
			irr::core::rect<int>( (int)_posX, (int)_posY, (int)(_posX+_width*_scale), (int)(_posY+_height*_scale) ),
			irr::core::rect<int>(0,0,_width,_height),
			0,&_color,true);
	}	// end draw
	void SetPosition( float x, float y ){ _posX = x; _posY = y; }
	void SetScale( float scale ){ _scale = scale; }
	void SetColor( int r, int g, int b )
	{
		_color = irr::video::SColor( 255, r, g, b );
	}
	inline float GetPositionX() const { return _posX; }
	inline float GetPositionY() const { return _posY; }
	inline float GetScale() const { return _scale; }
	inline float GetWidth() const { return _width*_scale; }
	inline float GetHeight() const { return _height*_scale; }
};


#endif
edit2:
here are 2 screenshots:

the first shows the old version with the correct color but low fps.
the second one shows the new version with wrong colors but higher fps.

Image
Image


edit3:
colors Array of 4 colors denoting the color values of the corners of the destRect
API ftw ;)

but still. isn't 30 fps low for 2k moving images?

system:
intel core 2duo @ 2.1ghz
2gb ram
mobile intel 965 express chipset family (guess 256 ram)
Last edited by Quillraven on Mon Apr 26, 2010 7:28 pm, edited 1 time in total.
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

No, it's not slow. The only problem here is that Irrlicht does not batch the 2d calls. You have to do this on your own. The images have to be batched into common textures and the draw calls should use the 2d batch image method. This requires some work, but it should get much faster that way.
Josie
Posts: 44
Joined: Sat Jul 25, 2009 4:08 am
Location: Griffin, Georgia, US
Contact:

Post by Josie »

hybrid wrote:No, it's not slow. The only problem here is that Irrlicht does not batch the 2d calls. You have to do this on your own. The images have to be batched into common textures and the draw calls should use the 2d batch image method. This requires some work, but it should get much faster that way.
Hybrid is completely and 100% correct. A large amount of 2d calls requires a lot of calls to the graphics card (think state changes), so for every 2d triangle, the graphics card has to set the state, draw the triangle, and restore the state.

Batching these calls together as much as possible dramatically increases performance. What you want to do is minimize the state changes and draw as many as you can in the same state. I'm not sure how complex this would be to do in irrlicht, but I implemented it in my 2d renderer without a lot of trouble.

Here's a video of my renderer (phoenixcore) batching a cubic buttload of quads. This is the kind of performance you'd expect from moderately new hardware ( I have an 8600GT ).

If you'd like to look at my renderer and gank some code from it, the link is in my signature. I can share some more technical details if you'd like.
Lonesome Ducky
Competition winner
Posts: 1123
Joined: Sun Jun 10, 2007 11:14 pm

Post by Lonesome Ducky »

Josie, your renderer looks really interesting. Is there any license or anything on the code?

EDIT: Open source, apparently I have a reading problem :lol:
netpipe
Posts: 670
Joined: Fri Jun 06, 2008 12:50 pm
Location: Edmonton, Alberta, Canada
Contact:

Post by netpipe »

Quillraven keep us posted on your progress, im very interested in this
Murloc992
Posts: 272
Joined: Mon Apr 13, 2009 2:45 pm
Location: Utena,Lithuania

Post by Murloc992 »

OffTopic: Quilraven> what's your 2nd desktop wallpaper? :D
Quillraven
Posts: 62
Joined: Fri Aug 22, 2008 7:22 am

Post by Quillraven »

thx for the replies. i read some threads now about batching because i didn't know anything about it yet. it seems like the best way for me would be an atlas, where i "paste" all my triangles on the great dummytexture and then draw just this one dummytexture.

is this is possible with irrlicht? if no:
what are the draw2dimagebatch functions for? i think they would be another good idea for my problem here, but they lack the possibilty to scale the textures differently?
so what exactly can i do with irrlicht to batch the 2k draw calls per gameloop?


@murloc: google -> search picture -> settings: resolution 1280x800 -> search word: fire ice
you will find it then :)
Josie
Posts: 44
Joined: Sat Jul 25, 2009 4:08 am
Location: Griffin, Georgia, US
Contact:

Post by Josie »

Lonesome Ducky wrote:Josie, your renderer looks really interesting. Is there any license or anything on the code?

EDIT: Open source, apparently I have a reading problem :lol:
MIT, so you can steal it all you want.

Quillraven, you'd have to write something to handle all your 2d draws. The way phoenix does this is that all draw calls create a 'Geometry' class, which has depth, texture, group, and primitive type, as well as the vertices. The batch renderer keeps track of these and when it's time to draw, it organizes them all draws everything it can together. All geometry that have the same properties will be drawn at once, so if you draw 2k triangles with the same texture at the same depth, it will only translate, bind the texture, and send data to the graphics card once.

Texture atlases greatly improve performance between a lot of different things can share the same texture.
Quillraven
Posts: 62
Joined: Fri Aug 22, 2008 7:22 am

Post by Quillraven »

the idea behind it is clear, but i need an advice of how to do that with irrlicht.

i tried some things with IImage and the copyTo functions to copy all my triangles to a dummy IImage which size is the window size and then only draw this one iimage. however the performance was pretty bad and i couldn't see any triangle. it was only one great grey texture over the whole screen.

then i tried the draw2dimagebatch function by giving it 2k positions and 2k sourcerects. i was surprised that the performance was worse than with the 2k draw2dimage calls.

so i m kind of stuck here now with the possibilities of irrlicht ;) any advice or hint would be nice. i cant try and check all possible 2d "tricks" with all the irrlicht functions.
BlindSide
Admin
Posts: 2821
Joined: Thu Dec 08, 2005 9:09 am
Location: NZ!

Post by BlindSide »

Are you running it in OpenGL or Direct3D9? Can you see if theres a difference on either driver?
ShadowMapping for Irrlicht!: Get it here
Need help? Come on the IRC!: #irrlicht on irc://irc.freenode.net
Quillraven
Posts: 62
Joined: Fri Aug 22, 2008 7:22 am

Post by Quillraven »

@blindside: if you mean the performance -> the fps are even lower with opengl (about half) which seems to "normal" (i read that it in other topics). the opengl 2d rendering is worse implemented in irrlicht then direct3d or sthg like that.

if you mean the problem from the first post - it's already solved i will mention it now :)
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

Ehh, no. That's simply due to the driver versions you need (hw driver, i.e. from the gfx card vendor) and how OpenGL is implemented there. You can even find gfx cards/drivers where OpenGL offers better performance. But in generla it should be slightly lower than d3d9. But that's not really Irrlicht's fault.
Post Reply