[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

Re: [pygame] sprite engine Top this!



Hi


I worked up a small example to illustrate the dirtyrect
processing method I mentioned earlier.
If you can make this faster, post your results here!

Yes, I can!

Note that all sprites have an alpha channel that must be maintained.


To make a comparable program it must have:

   all drawen surface must have the pygame.SRCALPHA flag.
   I use 32 bit for the bitdepth.

10 moving rectangles of 30x30 pixels filled with any color but alpha = 128
1 moving circle of r=50 ( that a 100x100pixel rectangle) filled with any color and alpha = 128
3 static rectangles topleft (100,300), size(100,300)
(50,450), size(600,100)
(140,400), size(300,300) filled with any color and alpha = 128


fps string display on screen at topleft (0,0)
static text "Hit ESC to stop, ALT-ENTER to toggle fullscreen mode." at topleft (150, 0)


Note: static boxes should not be rendered to the background (faster) and the text should stay in front of the moving parts!

And do not use psyco ore pyrex, just pygame and python!


Results on my machine:
PentiumM 1.5GHz, 1GB ram, WinXP prof. ~880 fps windowed and more or less the same in fullscreen.




Another test is to add 100 moving rectangles! (change line 133 in compare.py)
with 100 moving rectangles: ~106 fps


To run my code start "compare.py" press "h" to get help on the keys (console).


I have made some changes to my code:

added cliping so now I only blit needed pixels
use the code posted by Brian Fisher to get the dirty screen areas because its the fastest one ( some more pixel to update has lesser cost then updating many rects):


          # Redraw part of screen
           R = rect.clip(self.rect)
           i = R.collidelist(self.dirtyrects)
           if i > -1:
               # Add to existing dirty rectangle
               while i > -1:
                   R.union_ip(self.dirtyrects[i])
                   del self.dirtyrects[i]
                   i = R.collidelist(self.dirtyrects)
               self.dirtyrects.append(R)




As I said, the code is not finished and can have some bugs. If you see one, please tell me.
I'm wondering which fps you get on your machines. Please tell me so I can build a little statistic.


~DR0ID


#   
# (C) DR0ID 03.2006
# 
# you can contact me at: http://www.mypage.bluewin.ch/DR0ID/pygame
#
# or on IRC: irc.freenode.net 6667 #pygame 
#
#







import pygame
from pygame.locals import *
import random
import sprite as sprite
import time






__author__      = 'DR0ID'
__version__     = '0.2'	# change also top of this file!!
__versionnumber__  = 0,2   # versionnumber seperatly for comparing 
__license__     = 'public domain'
__copyright__   = '(c) DR0ID 05.2006'



class Quadrat(sprite.NewSprite):
    
    def __init__(self, register = True):
        sprite.NewSprite.__init__(self,(), register)
        self.image = pygame.Surface((30,30), pygame.SRCALPHA, 32).convert_alpha()
        self.image.fill( (random.randint(0,255),random.randint(0,255),random.randint(0,255),128) )
##        self.image.set_alpha(128)
##        self.image = self.image.convert_alpha()
        self.rect = self.image.get_rect()
        self.dirty = 2
        self.vx = 0
        while 0==self.vx:
            self.vx= random.randint(-3,3)
        self.vy = 0
        while 0==self.vy:
            self.vy = random.randint(-3,3)
        self.path_factor = 0.01
        self.maxX = (1-self.path_factor)*pygame.display.get_surface().get_width()
        self.minX = self.path_factor*pygame.display.get_surface().get_width()
        self.maxY = (1-self.path_factor)*pygame.display.get_surface().get_height()
        self.minY = self.path_factor*pygame.display.get_surface().get_height()
        self.rect.center = (random.randint(300,600),random.randint(300,400))
        
    def update(self):
        if self.rect.centerx > self.maxX or self.rect.centerx< self.minX:
            self.vx = -self.vx
        if self.rect.centery > self.maxY or self.rect.centery< self.minY:
            self.vy = -self.vy
        self.rect.move_ip(self.vx, self.vy)
        

class StaticQuadrat(sprite.NewSprite):
    
    def __init__(self, pos, size):
        sprite.NewSprite.__init__(self,(), False)
        self.image = pygame.Surface(size, pygame.SRCALPHA, 32).convert_alpha()
        self.image.fill((random.randint(0,255),random.randint(0,255),random.randint(0,255),128) )
        self.rect = pygame.Rect(pos, size)
        self.vx = self.vy =0
        self.dirty = 1
        self._renderer.add(self)
        
class Circle(Quadrat):
    
    def __init__(self):
        Quadrat.__init__(self, False)
        self.image = pygame.Surface((100,100), pygame.SRCALPHA, 32).convert_alpha()
        self.image.fill((0,0,0,0))
        pygame.draw.circle(self.image,(random.randint(0,255),random.randint(0,255),random.randint(0,255),128),(50,50) , 50)     
##        self.image.set_alpha(128)
##        self.image.convert_alpha()
        self._renderer.add(self)
        self.rect = self.image.get_rect()
        self.rect.center = (random.randint(300,600),random.randint(300,400))

        
class Text(sprite.NewSprite):
    
    def __init__(self, pos):
        sprite.NewSprite.__init__(self)
        self.dirty = 1
        self.font =  pygame.font.Font(pygame.font.get_default_font(), 24)
        self.image = self.font.render(str(0.0), 0, (255,255,255))
        self.image = self.font.render(str("Hit ESC to stop, ALT-ENTER to toggle fullscreen mode."), 0, (255,255,255))
        self.rect = self.image.get_rect()
        self.rect.move_ip(pos)
        self.ctr = 0
        self.nexttime = 0
        
        
    def update(self, now, s=''):
        self.ctr += 1
        if now > self.nexttime:
            if s=='':
                self.image = self.font.render(("%d FPS" % self.ctr), 0, (255,255,255))
                self.dirty = 1
                self.rect = self.image.get_rect(topleft=self.rect.topleft)
            self.ctr = 0
            self.nexttime = now + 1


#example 
#keys:
# s : stepmodus, press any key except s to step one frame furder
# d: change to debug modus
# f: change to slow 2 fps
# alt+enter: toggle fullscreen

def main():
    # setup pygame
    pygame.init()
    screen = pygame.display.set_mode((800,600),1,32)
    
    #prepare stuff, load images
    # convert them it will nearly double your fps (frames per second)
    # group for easier handling and add ouer objects
    g = pygame.sprite.OrderedUpdates()
    g.add(StaticQuadrat((100,300), (100,300)))
    for i in range(10):
        g.add(Quadrat())
    g.add(StaticQuadrat((50,450), (600,100)))
    g.add(Circle())
    g.add(StaticQuadrat((140,400), (300,300)))
##    for i in range(500):
##        g.add(StaticQuadrat((random.randint(0,800),random.randint(0,600)), (32,32)))

    text = Text((0,0))
    text2 = Text((150,0))
    sprite.Renderer.setBackgroundColor((0,0,80))
    pygame.display.set_caption("press 'h' for help (in console)")
    stepmodus = False
    fullscreen = False
    while 1:
        # eventhandling keys
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                return
            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE:
                    pygame.quit() # if you run it from console....
                    return
                elif event.key == K_d:
                    sprite.Renderer.switchDEBUG()
                elif event.key == K_f:
                    if sprite.Renderer.fpsGet()==20:
                        sprite.Renderer.fpsSet(0)
                    else:
                        sprite.Renderer.fpsSet(20)
                elif event.key == K_s:
                    stepmodus = not stepmodus
                elif event.key == K_h:
                    print "Help:"
                    print "s  toggles stepmode, press any key except s to step"
                    print "d  toggles debugmode"
                    print "f  toggles to 20 fps"
                    print "alt+enter toggles fullscreen"
                    print "esc  to exit"
                    print "h  for this help"
                    
                elif event.key == pygame.K_RETURN:
                    if pygame.key.get_mods() & pygame.KMOD_ALT:
                        print "toggle fullscreen"
                        fullscreen = not fullscreen
                        if fullscreen:
                            flags = pygame.FULLSCREEN
                        else:
                            flags = 0
                        minbitdepth = 16
                        bitdepth = 32
                        size = screen.get_size()
                        bitdepth = pygame.display.mode_ok(size, flags, bitdepth)
                        if bitdepth < minbitdepth:
                            raise Exception("Your system is unable to display %d bit color in an %d x %d window!" % (minbitdepth, size[0], size[1]))
                        screen = pygame.display.set_mode(size, flags, bitdepth)
                        sprite.Renderer.notifyScreenChange()
                            
                            
                            
        if stepmodus:
            pygame.event.clear()
            event = pygame.event.wait()
            while event.type is not KEYUP:
                if event.type == (pygame.constants.KEYDOWN or pygame.constants.KEYUP):
                    if event.key == pygame.constants.K_s:
                        stepmodus = not stepmodus
                event = pygame.event.wait()
                
                    
                    
        text.update( time.time()) 
        g.update()
        sprite.Renderer.render()
    
    
    
    
#if __name__ == '__main__': main()
if __name__=="__main__":
	main()
import pygame



#==============================================================================
class NewSprite(pygame.sprite.Sprite):

#------------------------------------------------------------------------------
    
    def __init__(self, groups=(), register=True):
        pygame.sprite.Sprite.__init__(self, groups)
        self.dirty = 1
        self.rect = pygame.Rect(-99, -99, 0, 0)
        self._renderer = Renderer
        if register:
            self._renderer.add(self)
            
#------------------------------------------------------------------------------
##    def __setattr__(self, name, value):
##        pygame.sprite.Sprite.__setattr__(self, name, value)
##        if(name=="rect"):
##            pygame.sprite.Sprite.__setattr__(self, "area", self.rect.width*self.rect.height)

#------------------------------------------------------------------------------
    def kill(self):
        pygame.sprite.Sprite.kill(self)
        self._renderer.remove(self)
            
#------------------------------------------------------------------------------
    def getLayer(self):
        return self._renderer.getLayerOfSprite(self)
        
#------------------------------------------------------------------------------
    def changeLayer(self, toLayer, removeEmptyLayers = True):
        self._renderer.changeSpriteLayer(self, toLayer, removeEmptyLayers)
        
        
#------------------------------------------------------------------------------
#==============================================================================
#TODO: RLEACCEL flag and use convert() and conver_alpha() in resourcemanager!!        

class _Renderer(object):
        
        
    _instance = 0
#------------------------------------------------------------------------------
        
    def __init__(self):
        # Singleton pattern
        if self._instance is not 0:
            raise ReferenceError , "dont try to instanciate the Renderer, use .Renderer or _Renderer._instance"
        _Renderer._instance = self
        # Sprite lists
        self._sprites = {} # {spr:layerNr, ...} global list of sprites
        self._layers = {} # {layer:[spr,...]} global list of layers
        self._sortedLayers = [] # sorted layers self._layers.keys().sort()
        self._needSortLayers = True
        # Rect lists
##        self._lostRect = [] # removed sprites to clear
        self._spriteOldRect = {} # {sprite:rect} old position rects
        self._update = []
        # screen, background
        self._screen = 0
##        self._scree_rect = 0
        self._background = 0
        self._background_color = (255,0,255)
        self._getScreen = pygame.display.get_surface # function pointer
        self._display_update = pygame.display.update
        # timing
        self._clock = pygame.time.Clock()
        self._clock_tick = self._clock.tick
        self._fps = 0
        # init
        self._initialized = False
        # function pointers: .render(self) and .flip(self)
        self.render = self._init    # first time it points to _init() after that to _render()
        self.flip = self._flipInit  # first points to _flipInit() after that to _flip()
        
#------------------------------------------------------------------------------
            
    def switchDEBUG(self, fps=-1):
        if(self._initialized):
            if self.render == self._render:
                if -1!=fps:
                    self._fps = fps
                self.render = self._DEBUGrender
            else:
                self._flip()
                self.render = self._render
        else:
            raise TypeError, "Renderer not initialized!!!"
        
#dirty == 1 -> 0 clear and draw and reset dirty = 0 (this ones are on screen and need repaint, but perhaps next time not)
#dirty == 2 -> 2 clear and draw and do not reset dirty flag (for animation, allway needs repaint)
        
#------------------------------------------------------------------------------
        
    def add(self, *sprites):
        """
        Add a sprite to renderer. One can also pass a iterable sequence of sprites.
        """
        for sprite in sprites:
            if isinstance(sprite, NewSprite):
                self.changeSpriteLayer(sprite, 0)
                self._spriteOldRect[sprite] = 0
                self._spriteOldRect[sprite] = sprite.rect
            else:
                self.add(*sprite)
                
#------------------------------------------------------------------------------
            
    def remove(self, sprite, removeEmptyLayers=True):
        """
        Revmoves a sprite from renderer.
        """
        if self._sprites.has_key(sprite):
            self._layers[ self._sprites[sprite] ].remove(sprite)
            if len( self._layers[ self._sprites[sprite] ] )==0 and removeEmptyLayers: # if layer is empty
                del self._layers[ self._sprites[sprite] ]       # remove it
                self._needSortLayers = True
            del self._sprites[sprite]
            del self._spriteOldRect[sprite]
            self._appendDirtyRect(sprite.rect)
            
#------------------------------------------------------------------------------
            
    def changeSpriteLayer(self, sprite, toLayer, removeEmptyLayers=True):
        """
        Changes the layer of a sprite and removes empty layers. Set "removeEmptyLayers" to
        False if you want to retain empty layers.
        """
        if self._sprites.has_key(sprite): # remove sprite from old layer if exists
            self._layers[ self._sprites[sprite] ].remove(sprite)
            if len( self._layers[ self._sprites[sprite] ] )==0 and removeEmptyLayers: # if layer is empty
                del self._layers[ self._sprites[sprite] ]       # remove it
                self._needSortLayers = True
        self._sprites[sprite] = toLayer # assign new layer to sprite
        if self._layers.has_key(toLayer): # and sprite to layer
            self._layers[toLayer].append(sprite) # if layer already exists then append
        else:
            self._layers[toLayer] = [sprite] # else new list
            self._needSortLayers = True
        if 2 != sprite.dirty:
            sprite.dirty = 1
         
#------------------------------------------------------------------------------
        
    def getLayerOfSprite(self, sprite):
        if self._sprites.has_key(sprite):
            return self._layers[ self._sprites[sprite] ]
        else:
            return None
#------------------------------------------------------------------------------
    def getSpritesOfLayer(self, layerNr):
        if self._layers.has_key(layerNr):
            return list(self._layers[layerNr])
        else:
            return None
#------------------------------------------------------------------------------
    def moveLayer(self, toLayer, removeEmptyLayer=True):
        raise NotImplementedError
    
#------------------------------------------------------------------------------
        
    def clear(self):
        """
        Removes all sprites from renderer!
        """
        for spr in self._sprites:
            self.remove(spr)
        self.render()
        self._sprites.clear()
        self._layers.clear()
        self._needSortLayers = True
        
#------------------------------------------------------------------------------
        
    def setBackground(self, bgdSurface):
        """
        Set the background to use to erase the sprites.
        """
        # TODO: stretch or warning if background has not same size as screen???
        self._background = bgdSurface.convert_alpha()
        
#------------------------------------------------------------------------------
    def getBackground(self):
        """
        Return a copy of background surface in use.
        """
        return self._background.copy()
#------------------------------------------------------------------------------
    def getBackgroundReference(self):
        """
        Returns a reference to background surface in use.
        """
        return self._background
#------------------------------------------------------------------------------
        
    def setBackgroundColor(self, color):
        """
        Set the color of the background. color = (R,G,B)
        """
        self._background_color = color
        if self._background != 0:
            self._background.fill(self._background_color)
            self.flip()
        
#------------------------------------------------------------------------------
        
    def fpsSet(self, n=0):
        self._fps = n
#------------------------------------------------------------------------------
    def fpsGet(self):
        return self._fps
#------------------------------------------------------------------------------
    def notifyScreenChange(self):
        # check if screen has changed
            # screen has changed, check if new screen is valid
            self._screen = self._getScreen()
            if self._screen is None:
                self._initialized = False
                self._init()
            self._appendDirtyRect(self._screen.get_rect())
                
        # screen ok
        
#------------------------------------------------------------------------------
        
    def _init(self):
        if(pygame.display.get_init()):
            if(not self._initialized):
                self._screen = pygame.display.get_surface()
                if self._screen is None:
                    raise TypeError, "Could not get a valid screen surface: pygame.display.get_surface()!!"
                if 0 == self._background:
                    self._background = pygame.Surface(self._screen.get_size(), self._screen.get_flags(), self._screen).convert_alpha()
                    self._background.fill(self._background_color) 
                #self._screen.blit(self._background, (0,0))
                self._appendDirtyRect(self._screen.get_rect())
                self._initialized = True
                self.render = self._render
                self.flip = self._flip
                self.flip()
            else:
                raise UserWarning, "Renderer.init(): you should initialize the renderer only once!"
        else:
            raise ReferenceError , "Renderer.init():you must have pygame.display initialized!!"        
        
#------------------------------------------------------------------------------
        
    def _flipInit(self):
        if(pygame.display.get_init()):
            if(not self._initialized):
                self._screen = pygame.display.get_surface()
                if self._screen is None:
                    raise TypeError, "Could not get a valid screen surface: pygame.display.get_surface()!!"
                if 0 == self._background:
                    self._background = pygame.Surface(self._screen.get_size(), self._screen.get_flags(), self._screen)
                    self._background.fill((255,0,255))
                #self._screen.blit(self._background, (0,0))
                self._appendDirtyRect(self._screen.get_rect())
                self._initialized = True
                self.flip = self._flip
                self._flip()
                self.render = self._render
            else:
                raise UserWarning, "Renderer.init(): you should initialize the renderer only once!"
        else:
            raise ReferenceError , "Renderer.init():you must have pygame.display initialized!!"        
        
#------------------------------------------------------------------------------
    def _appendDirtyRect(self, rect):
        

        _update = self._update
        _unionR = pygame.Rect(rect)
        
        i = _unionR.collidelist(_update)
        while -1<i:
            _unionR.union_ip(_update[i])
            del _update[i]
            i = _unionR.collidelist(_update)
        _update.append(_unionR)
            

#------------------------------------------------------------------------------
    def _render(self):
        
        # speedups
        _blit = self._screen.blit
        _background = self._background 
        _spriteOldRect = self._spriteOldRect
        _update = self._update
        _update_append = self._appendDirtyRect
        # find dirty rects on screen and erase dirty sprites from screen(OLD postition!)
        # old and new position of dirty sprite are dirty rects on screen
        for spr in self._sprites.keys():
            if 0<spr.dirty and -1<self._sprites[spr]: # TODO perhaps better to check concrete value?                
                _update_append(_spriteOldRect[spr])# _oldRect)
                _update_append(spr.rect)
                
        # find sprites colliding with dirty rects and blit them
        
        if self._needSortLayers: # only make a sort if needed
            self._needSortLayers = False
            self._sortedLayers = self._layers.keys()
            self._sortedLayers.sort() 
        for r in _update:
            _blit(_background, r, r)
            
        for layer in self._sortedLayers:
            if -1<layer: # layer smaller than 0 are not visible
                for spr in self._layers[ layer ]:
                    
                    if 0==spr.dirty:
                        _spr_rect = spr.rect
                        for idx in _spr_rect.collidelistall(_update):
                        # clip
                            
                            self._screen.set_clip(_update[idx])
                            _blit(spr.image, _spr_rect)
                            self._screen.set_clip()
                        
                    else:
                        _spriteOldRect[spr] = _blit(spr.image, spr.rect)#_blit(spr.image, spr.rect)
                        if 1 == spr.dirty:
                            spr.dirty = 0
                    
        self._display_update(_update)
        self._clock_tick(self._fps)
        _update[:] = []

#------------------------------------------------------------------------------
            
    def _DEBUGrender(self):
        print "DEBUGrender"
        
        # speedups
        _blit = self._screen.blit
        _background = self._background 
        _spriteOldRect = self._spriteOldRect
        _update = self._update
        _update[:] = [] # [pygame.Rect(100,100,200,200)]
        _update_append = self._appendDirtyRect
        # debugging vars
        _debugLostRects = [] # lost rects
        _debugLostRects_append = _debugLostRects.append
        _colorLostR = (255,255,0) # yellow
        _debugOldRects = [] # old position
        _debugOldRects_append = _debugOldRects.append
        _colorOldR = (255,216,66) # orange
        _debugNewRects = [] # new position
        _debugNewRects_append = _debugNewRects.append
        _colorNewR = (0,255,0) # green
        _debugCollidingRects = [] # colliding
        _debugCollidingRects_append = _debugCollidingRects.append
        _colorCollidingR = (0,255,255) # blue
        # debugging vars_update   # update rects
        _colorUpdateR = (255,0,0,128) # red
        _optimizedUpdate = []
        _optimizedUpdate_append = _optimizedUpdate.append
        
        # blit lost rects with background, they are dirty rects on screen
        _oldRect = 0
        _optimized = []
        
        _blit(self._background, (0,0)) # rebuild entire screen (slow!)
        for layer in self._sortedLayers:
            for spr in self._layers[layer]:
                    _blit(spr.image, spr)
        
        
        # find dirty rects on screen and erase dirty sprites from screen(OLD postition!)
        # old and new position of dirty sprite are dirty rects on screen
        for spr in self._sprites.keys():
            _oldRect = _spriteOldRect[spr]
            if 0<spr.dirty and 0!=_oldRect:
                    
                _update_append( _oldRect )
                _update_append(spr.rect)
                
                _debugOldRects_append(_oldRect)
                _debugNewRects_append(spr.rect)
                    
##        print "update: ", _update, "\n"
        # find sprites colliding with dirty rects and blit them
        if self._needSortLayers: # only make a sort if needed
            self._sortedLayers = self._layers.keys()
            self._sortedLayers.sort() 
            self._needSortLayers = False
        for layer in self._sortedLayers:#self._layers.keys().sort():
            if -1<layer:
                for spr in self._layers[ layer ]:
                    if 0==spr.dirty:
                        _spr_rect = spr.rect
                        for idx in _spr_rect.collidelistall(_update):
                        # clip
                            clip = _spr_rect.clip(_update[idx])
##                            _debugCollidingRects_append( _blit(spr.image, clip, pygame.Rect(clip[0]-_spr_rect[0], clip[1]-_spr_rect[1], clip[2], clip[3])) )
                            _debugCollidingRects_append(clip)
                    else:
                        _spriteOldRect[spr] = spr.rect #_blit(spr.image, spr.rect)#_blit(spr.image, spr.rect)
                        if 1 == spr.dirty:
                            spr.dirty = 0
##        print "update: ", _update, "\n"

                    
##        print "\nupdate: ", _update, "\n"
        #pygame.display.update(_update)
        
        # debugging code
        _oldRectsArea = 0
        _collidingRectsArea = 0
        _newRectsArea = 0
        _updateArea = 0
        for r in _debugOldRects:
            pygame.draw.rect(self._screen, _colorOldR, r, 4) # orange
            _oldRectsArea += r.width*r.height
        for r in _debugCollidingRects:
            pygame.draw.rect(self._screen, _colorCollidingR, r,3) # blue
            _collidingRectsArea += r.width*r.height
        for r in _debugNewRects:
            pygame.draw.rect(self._screen, _colorNewR, r, 2) # green
            _newRectsArea += r.width*r.height
        for r in _update:
            pygame.draw.rect(self._screen, _colorUpdateR, r,1) # red
            _updateArea += r.width*r.height
        _optArea = 0
            
        print "screen area:        \t", self._screen.get_width()*self._screen.get_height()
        print "\n"
        print "old rects ",len(_debugOldRects)," area:     \t", _oldRectsArea
        print "colliding rects ",len(_debugCollidingRects)," area:\t", _collidingRectsArea
        print "\n"
        print "screen update rects ",len(_update)," area: \t", _updateArea
        print "optimized screen update rects ",len(_optimized)," area: \t", _optArea
        print "double blittet area: ", _updateArea-_optArea, " # rects diff: ", len(_update)-len(_optimized)
        print "\n"
        print "total blitet rects ",+len(_debugOldRects)+ \
            len(_debugCollidingRects)," area:  \t", _oldRectsArea+_collidingRectsArea
        print "\n"
        _pygame_sprite_update_area = 0
        for s in self._sprites.keys():
            _pygame_sprite_update_area += s.rect.w*s.rect.h*2
        print "pygame.sprite would have a screen update area between max ", \
            _pygame_sprite_update_area, " and min ", _pygame_sprite_update_area/2
        print "and ",len(self._sprites.keys())*2," rects blitet\n\n"
        
        pygame.display.flip() # update entire screen (slow!)
        self._clock_tick(self._fps)    
        
#------------------------------------------------------------------------------
    def _flip(self):
        _blit = self._screen.blit
        _blit(self._background, (0,0))
        if self._needSortLayers: # only make a sort if needed
            self._needSortLayers = False
            self._sortedLayers = self._layers.keys()
            self._sortedLayers.sort() 
        for layer in self._sortedLayers:
            for spr in self._layers[layer]:
                _blit(spr.image, spr)
        pygame.display.flip()
        self._clock_tick(self._fps)
        
#------------------------------------------------------------------------------
        
#==============================================================================

# if you import this module mor than one time only one instance can exist!
if 0==_Renderer._instance:
    Renderer = _Renderer()