Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is pyglet so slow compared to pygame?

Tags:

pygame

pyglet

Here is the code. 5000 bouncing spinning red squares. (16x16 png) On the pygame version I get 30 fps but 10 fps with pyglet. Isnt OpenGl supposed to be faster for this kind of thing?

pygame version:

import pygame, sys, random
from pygame.locals import *
import cProfile

# Set FPS
FPS = 60.0
clock = pygame.time.Clock()

# Set window
WINDOWWIDTH= 800
WINDOWHEIGHT = 600

pygame.init()
screen = pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT))

screen.fill((0,0,0))
background = screen.copy().convert()
image = pygame.image.load("square.png").convert()

class Square(object):
    def __init__(self,x,y):
        self.x = x
        self.y = y
        self.v_x = random.randint(1,100)
        self.v_y = random.randint(1,100)
        self.v_r = random.randint(-100,100)
        self.rotation = 0

    def __rep__(self):
        return "Square %d,%d"%(self.x,self.y)

    def update(self,dt):
        if self.x > WINDOWWIDTH:
            self.v_x *= -1
        elif self.x < 0:
            self.v_x *= -1
        if self.y > WINDOWHEIGHT:
            self.v_y *= -1
        elif self.y < 0:
            self.v_y *= -1

        self.x += self.v_x * dt
        self.y += self.v_y * dt
        self.rotation += self.v_r * dt

    def draw(self):
        screen.blit(pygame.transform.rotate(image,self.rotation),(self.x,self.y))


sqrs = []
for _ in range(5000):
    sqrs.append( Square(random.randint(0,WINDOWWIDTH-1),random.randint(0,WINDOWHEIGHT-1)) )


def main_loop():
    tick = 0.0
    elapsed = 0.0

    while elapsed < 10.0:
        dt = tick/1000.0
        # Events
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

        # Logic
        for s in sqrs:
            s.update(dt)

        # Drawing
        screen.blit(background,(0,0))
        for s in sqrs:
            s.draw()

        pygame.display.update()
        pygame.display.set_caption('test program FPS: %s'%(clock.get_fps() ) )
        tick = clock.tick(FPS)
        elapsed += tick/1000.0
    pygame.quit()

cProfile.run("main_loop()")
i = input("...")

pyglet version:

import cProfile
import pyglet, random
# Disable error checking for increased performance
pyglet.options['debug_gl'] = False

from pyglet import clock

clock.set_fps_limit(60)

WINDOWWIDTH = 800
WINDOWHEIGHT = 600
FPS = 60.0

batch = pyglet.graphics.Batch()
window = pyglet.window.Window(WINDOWWIDTH,WINDOWHEIGHT)
fps_display = pyglet.clock.ClockDisplay()

image = pyglet.resource.image("square.png")

class Square(pyglet.sprite.Sprite):
    def __init__(self,x,y):
        pyglet.sprite.Sprite.__init__(self,img = image,batch=batch)
        self.x = x
        self.y = y
        self.v_x = random.randint(1,100)
        self.v_y = random.randint(1,100)
        self.v_r = random.randint(-100,100)

    def update(self,dt):
        if self.x > WINDOWWIDTH:
            self.v_x *= -1
        elif self.x < 0:
            self.v_x *= -1
        if self.y > WINDOWHEIGHT:
            self.v_y *= -1
        elif self.y < 0:
            self.v_y *= -1

        self.x += self.v_x * dt
        self.y += self.v_y * dt
        self.rotation += self.v_r * dt

sqrs = []
for _ in range(5000):
    sqrs.append( Square(random.randint(0,WINDOWWIDTH-1),random.randint(0,WINDOWHEIGHT-1)) )

elapsed = 0.0

def update(dt):
    global elapsed
    elapsed += dt
    if elapsed >= 10.0:
        clock.unschedule(update)
        window.close()
    else:
        for s in sqrs:
            s.update(dt)

@window.event
def on_draw():
    window.clear()
    batch.draw()
    fps_display.draw()


clock.schedule_interval(update, 1.0/FPS)

if __name__ == '__main__':
    cProfile.run("pyglet.app.run()")
    c = input("...")

cProfile result for pygame:

         5341607 function calls in 9.429 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    9.429    9.429 <string>:1(<module>)
  1335000    2.259    0.000    2.259    0.000 pygame-test.py:32(update)
  1335000    1.323    0.000    5.969    0.000 pygame-test.py:46(draw)
        1    0.772    0.772    9.429    9.429 pygame-test.py:55(main_loop)
        1    0.000    0.000    9.429    9.429 {built-in method exec}
      267    0.020    0.000    0.020    0.000 {built-in method get}
        1    0.237    0.237    0.237    0.237 {built-in method quit}
  1335000    3.479    0.000    3.479    0.000 {built-in method rotate}
      267    0.013    0.000    0.013    0.000 {built-in method set_caption}
      267    0.067    0.000    0.067    0.000 {built-in method update}
  1335267    1.257    0.000    1.257    0.000 {method 'blit' of 'pygame.Surface' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
      267    0.000    0.000    0.000    0.000 {method 'get_fps' of 'Clock' objects}
      267    0.001    0.000    0.001    0.000 {method 'tick' of 'Clock' objects}

Pyglet cProfile output: - Very long, this is partial output, full version here.

         9982775 function calls (9982587 primitive calls) in 10.066 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      123    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:1596(_handle_fromlist)
        1    0.000    0.000   10.067   10.067 <string>:1(<module>)
       11    0.000    0.000    0.000    0.000 __init__.py:1055(_ensure_string_data)
       11    0.000    0.000    0.000    0.000 __init__.py:1061(_get_gl_format_and_type)
       58    0.000    0.000    0.000    0.000 __init__.py:1140(clear)
       75    0.000    0.000    0.012    0.000 __init__.py:1148(dispatch_event)
        1    0.000    0.000   10.067   10.067 __init__.py:115(run)
   ...
        1    0.000    0.000    0.000    0.000 lib.py:124(decorate_function)
     1108    0.005    0.000    0.005    0.000 lib_wgl.py:80(__call__)
   285000    1.409    0.000    9.872    0.000 pyglet-test.py:29(update)
       58    0.105    0.002    9.982    0.172 pyglet-test.py:49(update)
   ...
   855000    5.436    0.000    7.551    0.000 sprite.py:378(_update_position)
   285000    0.172    0.000    2.718    0.000 sprite.py:441(_set_x)
   851800    0.177    0.000    0.177    0.000 sprite.py:445(<lambda>)
   285000    0.174    0.000    2.670    0.000 sprite.py:451(_set_y)
   851115    0.155    0.000    0.155    0.000 sprite.py:455(<lambda>)
   285000    0.182    0.000    2.692    0.000 sprite.py:461(_set_rotation)
   285000    0.051    0.000    0.051    0.000 sprite.py:465(<lambda>)
   ...
     4299    0.007    0.000    0.025    0.000 vertexattribute.py:308(get_region)
        1    0.000    0.000    0.000    0.000 vertexattribute.py:380(__init__)
      116    0.000    0.000    0.000    0.000 vertexattribute.py:384(enable)
      116    0.000    0.000    0.000    0.000 vertexattribute.py:387(set_pointer)
        1    0.000    0.000    0.000    0.000 vertexattribute.py:461(__init__)
      116    0.000    0.000    0.000    0.000 vertexattribute.py:466(enable)
      116    0.000    0.000    0.000    0.000 vertexattribute.py:469(set_pointer)
        1    0.000    0.000    0.000    0.000 vertexattribute.py:501(__init__)
      116    0.000    0.000    0.000    0.000 vertexattribute.py:508(enable)
      116    0.000    0.000    0.000    0.000 vertexattribute.py:511(set_pointer)
        3    0.000    0.000    0.000    0.000 vertexbuffer.py:293(__init__)
      348    0.000    0.000    0.001    0.000 vertexbuffer.py:311(bind)
      348    0.000    0.000    0.001    0.000 vertexbuffer.py:314(unbind)
        3    0.000    0.000    0.000    0.000 vertexbuffer.py:381(__init__)
      348    0.001    0.000    0.004    0.000 vertexbuffer.py:388(bind)
     4299    0.006    0.000    0.016    0.000 vertexbuffer.py:420(get_region)
        3    0.000    0.000    0.000    0.000 vertexbuffer.py:424(resize)
     4299    0.002    0.000    0.002    0.000 vertexbuffer.py:460(__init__)
   855232    0.735    0.000    1.053    0.000 vertexbuffer.py:466(invalidate)
   ...
   855058    0.687    0.000    1.762    0.000 vertexdomain.py:581(_get_vertices)
   ...
     4300    0.002    0.000    0.002    0.000 {built-in method POINTER}
   ...
   841451    0.162    0.000    0.162    0.000 {built-in method cos}
   ...
2417/2415    0.000    0.000    0.000    0.000 {built-in method len}
   855489    0.142    0.000    0.142    0.000 {built-in method max}
   855469    0.176    0.000    0.176    0.000 {built-in method min}
  465/407    0.000    0.000    0.000    0.000 {built-in method next}
   ...
   841451    0.072    0.000    0.072    0.000 {built-in method radians}
       62    0.000    0.000    0.000    0.000 {built-in method setattr}
   841451    0.120    0.000    0.120    0.000 {built-in method sin}
   ...
like image 624
No Harm In Trying Avatar asked Nov 05 '13 00:11

No Harm In Trying


2 Answers

The bottleneck is in pyglet sprite rotation. If you comment the 'self.rotation' line in Square update() method, your fps will almost double.

like image 104
msarch Avatar answered Nov 17 '22 03:11

msarch


Pyglet images (and sprites) can be rotated around an arbitrary anchor. If you look at pyglet/sprite.py, you will see that this is done using the Python math module, which is why it's rather slow. It would seem there is room for optimiziation here, by rotating sprites using OpenGL glRotate or even a vertex shader.

like image 36
Ingmar Hupp Avatar answered Nov 17 '22 01:11

Ingmar Hupp