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

Re: [pygame] Dirty Rectangle Processing



Hallo again


Kamilche schrieb:
DR0ID wrote:

Thank you for that. It's astonishing fast! Congratulations!

You're welcome, I'm glad you found it useful!

I tested your code:
I have ~412 fps in windowed mode and ~610 fps in fullscreen on a PentiumM 1.5Ghz with 1GB ram and WinXP Prof.

Here's some more timings you'll find interesting:

1.4 ghz PowerMac G4 10.3.7 512 megs ram (sandwich Mac) - 432 fps
350 mhz Compaq Presario 160 megs ram, shared video - 51 fps
2.6 gHz Compaq Presario 1 gig ram, shared video - 604 fps


Interesting!

And note - alt-enter even works on the Mac, though the Mac doesn't have an alt key and it's the option key instead.


If you permit I would like to use your fullscreen toggle code furder. ( I borrowed it for the attachet compare.py).



The target frame range on my game engine is 12 fps - as long as the slowest machine can stay above that, they'll be able to play the game no problem.


(my LCD display is slow so I can see 4-6 rects lined up in the moving direction!!)

Mine too, in fullscreen mode! In windowed mode it's slow enough it doesn't do that.


I looked at your code and noticed, that I had forgotten about the clipping on a surface. That can make some thing faster.

Yeah... really only DRAW the bit you need, instead of overdrawing and expecting the clipping area to throw away the unnecessary bits. In my opinion, the current clipping rectangle is a crutch for those who don't want to finish the calculations.


A bit more info:

The frame rate is actually better than this on my actual engine, because I don't have any sprites move every timer tick, as in this example. My 'real' engine frame rate SAYS over 40,000 fps, but that's misleading, because unless you have something being drawn every frame, you don't really know what the FPS is. When I add some 'maximum velocity' sprites (sprites that draw every frame), the frame rates drop to what I posted here.

Still, while I'm developing the engine, I display the FPS at all times even though it's misleading, so if the frame rate drops sharply, I'll know I just implemented something stupid.

Yes, I do the same. Ok until now its only for the sprite engine. If there are no sprites moving or animating than nothing has to be drawen and so its very fast and missleading number, as you say.



Overall, those sprite classes are easy to implement, and don't have to worry about the dirty rect calculations. Basically, just subclass from sprite and override the paint method. The text class is a little more involved because until the text is painted, I don't know how big it is. Once it's been painted, you need to update the 'size' with the final size, so the SetRect calculation will be correct.


Now that you have the example, these points will make more sense:

1. On __init__, paint the sprite immediately (no paint flag involvement).

2. When the sprite moves, dirty the screen by calling 'SetRect' ONLY - its appearance hasn't changed, only its position. SetRect is the magic function behind the dirty rectangle processing, and handles dirtying the screen without errors.

3. When the sprite changes appearance (such as size or text), mark the sprite as needing to be repainted (set the paint flag to 1) and dirty its area on the screen. That's a minor bug in the code I posted - when the text changes in the text class, add 'self.SetRect()' right after the 'self.paint = 1' line. I extracted this from a much larger engine, and that got overlooked. Setting the paint flag and executing self.SetRect always go hand in hand.

4. Finally - the end of paint ALWAYS calls SetRect. ALWAYS. If you create a new class with drawing bugs, look for that error first - forgetting to include a 'SetRect' at the end of Paint.

--Kamilche


I must say your design is flexible and clear. I do not want to copy, but some ideas I would like to put in my sprite engine, if you permit it.


I also borrowed also the code to calculate the framerate and I'm not sure if it is correct that way, so please CHECK the lines 100 until 110 and the line 200!!

Until now my sprite engine is compatible with the old pygame.sprites and that is why it is perhaps not so flexible (I actually use a pygame.sprite.group just to make the update call but I think people will appreciate that they can still use the pygame.sprite.groups, or not?).

This morning as I tested your code, I was astonished about the speed and conerned that I would have to rewrite things. Finally I sat down to write a similar program to see, how my engine would perform in a similar situation. I was astonished! I have attached the code so you can see it by yourself ( sorry for the messy code, it is far from finished and I have to clean up things, for example the "lostrect" thing). But take a look!

start compar.py and then you have these key:

s : activates the step mode, press any key except s to step one frame furder
d : activates debug modus (it's full screen redraw, so you see it's slow)
f : toggles 2 fps and fastest as it can
alt+enter: toggles fullscreen ( I hope this works, have to revision my screen changing handling)
esc : to exit


And I did not clip anything yet and I'm sure there are still more speedups. And it uses the dirty screen area minimizing code I posted in the past.


I have on a PentiumM 1.5GHz w 1GB ram and WinXP prof.: windowed: ~660 fps fullscreen: ~640 more or less the same ( perhaps little bit slower )

I made another test: I increased the number of moving rectangles to 80. I feel a bit sorry, but my sprite engine had still ~100 fps, but your dropped to ~50 on my machine. To test it you have to change line 133 in compare.py and in your code lines 210 and 211 to:
#----------------------------------
for i in range(80):
Square(backcolor = colors[i%10])
#------------------------------------


I'm wondering what other people will get on their machines?

~DR0ID

import pygame



##class Sprite(pygame.sprite.Sprite):
##    
##    def __init__(self, img, *groups):
##        pygame.sprite.Sprite.__init__(self, *groups)
##        self.image = img
##        self.rect = img.get_rect()
##        self._rendererRemove = Renderer()._sprites.remove # for layer its: Renderer()._sprites[layer].remove
##        self._rendererAppend = Renderer()._sprites.append # Renderer()._sprites[layer].append
##        self.dirty = 0 # has to be last entry because of __setattr__()!!
##        
##    def __setattr__(self, name, value):
##        object.__setattr__(self, name, value)
##        if(name=="dirty" and value>0):
##            self._rendererAppend(self)
##            
##    def kill():
##        try:
##            self._rendererRemove(self)
##        except:
##            pass
##        

###============================================================================== 
##class NewRect(pygame.Rect):
##        
###------------------------------------------------------------------------------
##    def __setattr__(self, name, value):
##        pygame.Rect.__setattr__(self, name, value)
##        if name in ['size','width','height', 'w', 'h']:
##            pygame.Rect.__setattr__(self, 'area', self.width * self.height)
##        elif 'area' == name:
##            raise TypeError, str(self.__class__.__name__) +" area attribute is read only!"
##    
###------------------------------------------------------------------------------
##    def __getattr__(self, name):
##        if 'area' == name:
##            pygame.Rect.__setattr__(self, 'area', self.width * self.height)
##            return self.area
##        else:
##            return pygame.Rect.__getattribute__(self, name)
##        
###------------------------------------------------------------------------------
##    def move(self, x, y):
##        return NewRect(pygame.Rect.move(self, x, y))    
##        
###------------------------------------------------------------------------------
##    def inflate(self, x, y):
##        return NewRect(pygame.Rect.inflate(self, x, y))
##        
###------------------------------------------------------------------------------
##    def inflate_ip(self, x, y):
##        pygame.Rect.inflate_ip(self, x, y)
##        pygame.Rect.__setattr__(self, 'area', self.width * self.height)
##        
###------------------------------------------------------------------------------
##    def clamp(self,rect):
##        return NewRect(pygame.Rect.clamp(self,rect))
##        
###------------------------------------------------------------------------------
##    def clamp_ip(self,rect):
##        pygame.Rect.clamp_ip(self,rect)
##        pygame.Rect.__setattr__(self, 'area', self.width * self.height)
##    
###------------------------------------------------------------------------------
##    def clip(self,rect):
##        return NewRect(pygame.Rect.clip(self,rect))
##    
###------------------------------------------------------------------------------
##    def clip_ip(self,rect):
##        pygame.Rect.clip_ip(self,rect)
##        pygame.Rect.__setattr__(self, 'area', self.width * self.height)
##    
###------------------------------------------------------------------------------
##    def union(self,rect):
##        return NewRect(pygame.Rect.union(self,rect))
##    
###------------------------------------------------------------------------------
##    def union_ip(self,rect):
##        pygame.Rect.union_ip(self,rect)
##        pygame.Rect.__setattr__(self, 'area', self.width * self.height)
##        
###------------------------------------------------------------------------------
##    def unionall(self, *rect):
##        return NewRect(pygame.Rect.unionall(self,rect))
##    
###------------------------------------------------------------------------------
##    def unionall_ip(self, *rect):
##        pygame.Rect.unionall_ip(self,rect)
##        pygame.Rect.__setattr__(self, 'area', self.width * self.height)
##    
###------------------------------------------------------------------------------
##    def fit(self,rect):
##        return NewRect(pygame.Rect.fit(self,rect))
##        
###------------------------------------------------------------------------------
##    def normalize(self):
##        pygame.Rect.normalize(self)
##        pygame.Rect.__setattr__(self, 'area', self.width * self.height)
##        
###------------------------------------------------------------------------------
##    def overlapArea(self,rect):
##        """
##        Size of the overlaping area.
##        """
##        return self.clip(rect).area
##        
###------------------------------------------------------------------------------
##    def unionArea(self,rect):
##        """
##        Size of the area after union the two rects.
##        """
##        return self.union(rect).area
##        
###------------------------------------------------------------------------------
##    def nonOverlapUnionArea(self,rect):
##        """
##        Size of the non overlaping area of the united rects.
##        """
##        return self.unionArea(rect)-self.overlapArea(rect)
##        
###------------------------------------------------------------------------------
##    def nonOverlapArea(self,rect):
##        """
##        Size of non overlaping area, r1.area+r2.area-r1.clip(r2).area
##        """
##        return self.area+rect.area-self.clip(rect).area
##        
###------------------------------------------------------------------------------
##    def percentOverlapOfUnionArea(self,rect):
##        return float(self.overlapArea(rect))/self.unionArea(rect)
##        
###------------------------------------------------------------------------------
##    def percentOverlapOfArea(self,rect):
##        return float(self.overlapArea(rect))/(self.area+rect.area)
##    
###============================================================================== 
##    
#==============================================================================
class ObjectBase(object):
    pass

##    def __init__(self,...):
##          self.rect = ....            #  collision rect, holds position and size
##          self.sprite = sprite,    #image, rect of image has offset position, you have to move it also when moving the Object
##                                          #needs rect for grphical processing

#==============================================================================


class TestSprite(pygame.Rect):
    
    def __init__(self, *groups):
        pygame.sprite.Sprite(*groups)
        self.__g = {} # The groups the sprite is in
        if groups: self.add(groups)

    def add(self, *groups):
        """add(group or list of of groups, ...)
           add a sprite to container

           Add the sprite to a group or sequence of groups."""
        has = self.__g.has_key
        for group in groups:
            if hasattr(group, '_spritegroup'):
                if not has(group):
                    group.add_internal(self)
                    self.add_internal(group)
            else: self.add(*group)

    def remove(self, *groups):
        """remove(group or list of groups, ...)
           remove a sprite from container

           Remove the sprite from a group or sequence of groups."""
        has = self.__g.has_key
        for group in groups:
            if hasattr(group, '_spritegroup'):
                if has(group):
                    group.remove_internal(self)
                    self.remove_internal(group)
            else: self.remove(*group)

    def add_internal(self, group):
        self.__g[group] = 0

    def remove_internal(self, group):
        del self.__g[group]

    def update(self, *args):
        pass

    def kill(self):
        """kill()
           remove this sprite from all groups

           Removes the sprite from all the groups that contain
           it. The sprite still exists after calling this,
           so you could use it to remove a sprite from all groups,
           and then add it to some other groups."""
        for c in self.__g.keys():
            c.remove_internal(self)
        self.__g.clear()

    def groups(self):
        """groups() -> list of groups
           list used sprite containers

           Returns a list of all the groups that contain this
           sprite. These are not returned in any meaningful order."""
        return self.__g.keys()

    def alive(self):
        """alive() -> bool
           check to see if the sprite is in any groups

           Returns true if this sprite is a member of any groups."""
        return (len(self.__g) != 0)

    def __repr__(self):
        return "<%s sprite(in %d groups)%s>" % (self.__class__.__name__, len(self.__g), pygame.Rect.__repr__(self))


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

#------------------------------------------------------------------------------
    
##    def __init__(self, img, *groups):
    def __init__(self, groups=(), register=True):
        pygame.sprite.Sprite.__init__(self, groups)
##        self.image = img
##        self.rect = pygame.Rect(-1000,-1000,1,1)#NewRect(img.get_rect())
##        self.area = self.rect.width * self.rect.height # area = widht * height
        self.dirty = 1
##        self._layer = 0 # read only !
        self._renderer = Renderer
        if register:
            self._renderer.add(self)
##        self._createDirtyRect = True
            
#------------------------------------------------------------------------------
    def __setattr__(self, name, value):
        pygame.sprite.Sprite.__setattr__(self, name, value)
        if(name=="rect"):
##            pygame.sprite.Sprite.__setattr__(self,"rect",NewRect(self.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._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!!!"
        
# Basic Algorithme:
# 1. determine dirty rects on screen (optimize dirty rects by overlap checks, tricky)
# 2. blit background area
# 2. find all sprites colliding with dirty rects
# 3. blit all found sprites
        
#dirty == -1 -> -1 do nothing (this ones are not on screen, but still in rendererqueue)
#dirty == 0 -> 0 do nothing (this ones are on screen but dont need repaint)
#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)
###dirty == 3 -> -1 clear but do not draw and reset dirty = -1 (this ones are on screen, but they are erased and not repainted)
        
#------------------------------------------------------------------------------
        
    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):
##                if not hasattr(sprite, "area"):
##                    sprite.area = sprite.rect.width*sprite.rect.height
                self.changeSpriteLayer(sprite, 0)
                self._spriteOldRect[sprite] = 0
            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._lostRect.append(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 and toLayer>-1:
            sprite.dirty = 1
        if 0>toLayer:
            self._lostRect.append(sprite.rect)
         
#------------------------------------------------------------------------------
        
    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
        
#------------------------------------------------------------------------------
    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
        
#------------------------------------------------------------------------------
        
    def fpsSet(self, n=0):
        self._fps = n
#------------------------------------------------------------------------------
    def fpsGet(self):
        return self._fps
#------------------------------------------------------------------------------
    def notifyScreenChange(self):
        # check if screen has changed
##        if self._screen!=self._getScreen():
            # screen has changed, check if new screen is valid
            self._screen = self._getScreen()
            if self._screen is None:
                self._initialized = False
                self._init()
            self.flip()
                
        # 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)
                    self._background.fill(self._background_color) 
                #self._screen.blit(self._background, (0,0))
                self._lostRect.append(self._screen.get_rect())
                self._initialized = True
                self.render = self._render
                self.flip = self._flip
                self.flip()
##                self.render()
                pygame.display.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._lostRect.append(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):
        
##        self._update.append(rect) # works fine nor optimization
        _update = self._update
        _update_append = _update.append
        _update_remove = _update.remove
        toremove = []
        toremove_append = toremove.append
        _unionR = 0
        if -1!=rect.collidelist(_update):
            idxlist = rect.collidelistall(_update)
            toremove[:] = []
            for idx in idxlist: # check if rect is inside a dirty area
                                # if so nothing to do,not append, not union, etc.
                r = _update[idx]
                # if not inside:
                _unionR = r.union(rect)
                if _unionR.width*_unionR.height< rect.width*rect.height+r.width*r.height:
                    toremove_append(r)
                    rect = _unionR
                    
            _update_append(rect)
            for r in toremove:
                _update_remove(r)
        else:
            _update_append(rect)
#------------------------------------------------------------------------------
    def _render(self):
        
        # speedups
        _blit = self._screen.blit
        _background = self._background 
        _lost = self._lostRect
        _spriteOldRect = self._spriteOldRect
        self._update = []
        _update = self._update
##        _update = [] # [pygame.Rect(100,100,200,200)]
        _update_append = self._appendDirtyRect
        # blit lost rects with background, they are dirty rects on screen
        for r in _lost:
            _update_append( _blit(_background, r, r) )#### use optimized update algo
        _lost[:] = []
        _oldRect = 0
        _unionR = 0
        # 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 and -1<self._sprites[spr]: # TODO perhaps better to check concrete value?
                if 1 == spr.dirty:
                    spr.dirty = 0
                
                _update_append( _blit(_background, _oldRect, _oldRect) )
                
                
##                if _oldRect != spr.rect:
##                    _update_append(spr.rect)
                _update_append(spr.rect)
                
##                if _oldRect.colliderect(spr):
##                    _unionR = _oldRect.union(spr)
##                    if _unionR.w*_unionR.h < 2*spr.area:#_oldRect.w*_oldRect.h:
##                        _update_append(_unionR)
##                    else:
##                        _update_append(spr.rect)
##                        _update_append(_oldRect)
##                    #_update_append(_oldRect.union(spr))
##                else:
##                    _update_append(spr.rect)
##                    _update_append(_oldRect)
        # 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 layer in self._sortedLayers:
            if -1<layer: # layer smaller than 0 are not visible
                for spr in self._layers[ layer ]:
                    if -1!=spr.rect.collidelist(_update): # intersects with a dirty rect on screen so blit it
                        _spriteOldRect[spr] = _blit(spr.image, spr.rect)#_blit(spr.image, spr.rect)
                    
        self._display_update(_update)
        self._clock_tick(self._fps)
        
#------------------------------------------------------------------------------
            
    def _DEBUGrender(self):
        print "DEBUGrender"
        
        # speedups
        _blit = self._screen.blit
        _background = self._background 
        _lost = self._lostRect
        _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
        for r in _lost:
            _update_append( _blit(_background, r, r) )
            _debugLostRects_append(r)  #### use optimized update algo
##            print "lost: ", r
##        print "update: ", _update, "\n"
        _lost[:] = []
        _oldRect = 0
        _optimized = []
        
        # 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:
                if 1 == spr.dirty:
                    spr.dirty = 0
                    
                _blit(_background, _oldRect, _oldRect)
                #### from here on use optimized algo
                _update_append( _oldRect )
                
                if _oldRect != spr.rect:
                    _update_append(spr.rect)
##                if _oldRect.colliderect(spr):
##                    _unionR = _oldRect.union(spr)
##                    if _unionR.w*_unionR.h < 2*spr.area:
##                        _update_append(_unionR)
##                    else:
##                        _update_append(spr.rect)
##                        _update_append(_oldRect)
##                else:
##                    _update_append(spr.rect)
##                    _update_append(_oldRect)
                _debugOldRects_append(_oldRect)
                _debugNewRects_append(spr.rect)
                    
##                print "oldRect blit: ", _oldRect
##        for r in _update:
                # test: screen update area optimiziation
####  optimize here             
##                newr = spr.rect # _update[-1]
##                if -1!=newr.collidelist(_optimized):
##                    idxlist = newr.collidelistall(_optimized)
##                    toremove = []
##                    for idx in idxlist:
##                        r = _optimized[idx]
##                        u = r.union(newr)
##                        if u.width*u.height< newr.width*newr.height+r.width*r.height:
##                            toremove.append(r)
##                            newr = u
##                    _optimized.append(newr)
##                    for r in toremove:
##                        _optimized.remove(r)
##                else:
##                    _optimized.append(newr)
####  optimize end                  
##                if _oldRect!=spr.rect:  # different position or size  
##                    newr = _oldRect # _update[-1]
##                    if -1!=newr.collidelist(_optimized):
##                        idxlist = newr.collidelistall(_optimized)
##                        toremove = []
##                        for idx in idxlist:
##                            r = _optimized[idx]
##                            u = r.union(newr)
##                            if u.width*u.height< newr.width*newr.height+r.width*r.height:
##                                toremove.append(r)
##                                newr = newr.union(r)
##                        _optimized.append(newr)
##                        for r in toremove:
##                            _optimized.remove(r)
##                    else:
##                        _optimized.append(newr)
            
##        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 -1!=spr.rect.collidelist(_update): # intersects with a dirty rect on screen so blit it
                        _spriteOldRect[spr] = self._screen.blit(spr.image, spr.rect)#_blit(spr.image, spr.rect)
                        _debugCollidingRects_append(_spriteOldRect[spr])
##                        print "colliding obj, rect: ", spr.__class__.__name__, spr.rect
##        print "update: ", _update, "\n"

                    
##        print "\nupdate: ", _update, "\n"
        #pygame.display.update(_update)
        
        # debugging code
        _lostRectsArea = 0
        _oldRectsArea = 0
        _collidingRectsArea = 0
        _newRectsArea = 0
        _updateArea = 0
        for r in _debugLostRects:
            pygame.draw.rect(self._screen,_colorLostR, r, 5) # yellow
            _lostRectsArea += r.width*r.height
        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,3) # red
            _updateArea += r.width*r.height
        _optArea = 0
        for r in _optimized:
            pygame.draw.rect(self._screen, (255,255,255), r, 1)
            _optArea += r.width*r.height
            
        print "screen area:        \t", self._screen.get_width()*self._screen.get_height()
        print "\n"
        print "lost rects ", len(_debugLostRects), " area:    \t", _lostRectsArea
        print "old rects ",len(_debugOldRects)," area:     \t", _oldRectsArea
        print "colliding rects ",len(_debugCollidingRects)," area:\t", _collidingRectsArea
##        print "new rects ",len(_debugNewRects)," area:     \t", _newRectsArea
        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(_debugLostRects)+len(_debugOldRects)+ \
            len(_debugCollidingRects)," area:  \t", _lostRectsArea+_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!)
##        pygame.display.update(_update)
        _blit(self._background, (0,0)) # rebuild entire screen (slow!)
        for layer in self._sortedLayers:
            for spr in self._layers[layer]:
##                if spr._createDirtyRect:
                    _blit(spr.image, spr)
        self._clock_tick(self._fps)    
        
#------------------------------------------------------------------------------
    def _flip(self):
        _blit = self._screen.blit
        _blit(self._background, (0,0))
        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()

    
#   
# (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


# try to import psyco, its not needed to run
##psyco=None
##try:
##	import psyco
##	if psyco.__version__>=17105136:
##		from psyco.classes import __metaclass__
##	else:
##		print """Note: Psycho version """, psyco.__version__, """  detected, upgrade to psyco 1.5 for better performance. """
##
##except ImportError:
##	print """Note: No psyco detected. Install psycho to improve performance."""




__author__      = 'DR0ID'
__version__     = '0.1'	# change also top of this file!!
__versionnumber__  = 0,1   # versionnumber seperatly for comparing 
__license__     = 'public domain'
__copyright__   = '(c) DR0ID 03.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= random.randint(5,20)
        self.vy = random.randint(5,20)
        self.path_factor = 0.1
        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 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.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))
            else:
                self.image = self.font.render(str(s), 0, (255,255,255))
            self.rect = self.image.get_rect(topleft=self.rect.topleft)
            self.ctr = 0
            self.nexttime = now + 1
            self.dirty = 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)
    
    #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(mObj)
##    g.add(mObj2)
    for i in range(10):
        g.add(Quadrat())
    g.add(Circle())
    sprite.Renderer.setBackgroundColor((0,0,0))

##    fpslist = []
##    for n in range(500):
##        fpslist.append(100)
    background = pygame.Surface(screen.get_size()).convert()
    clock = pygame.time.Clock()
    text = Text((0,0))
    text2 = Text((150,0))
    sprite.Renderer.setBackgroundColor((0,0,80))
    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()==2:
                        sprite.Renderer.fpsSet(0)
                    else:
                        sprite.Renderer.fpsSet(2)
                elif event.key == K_s:
                    stepmodus = not stepmodus
##                    pygame.time.wait(1000)
                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()
                
                    
##        fpslist.pop(0)
##        fpslist.append(clock.get_fps())
                    
        text.update( time.time()) 
        text2.update(time.time(),"Hit ESC to stop, ALT-ENTER to toggle fullscreen mode.")
        g.update()
        sprite.Renderer.render()
        clock.tick()        
    
    
    
#if __name__ == '__main__': main()
if __name__=="__main__":
	main()