New Pyglet User - Maximum Sprite Count

95 views
Skip to first unread message

Kevin S.

unread,
Feb 6, 2017, 10:06:49 PM2/6/17
to pyglet-users
Hello, I started playing around with Pyglet recently and it was going fine until I started trying to fill the screen with images.  I ended up with an atrocious frame rate, to the point where user mouse clicks weren't working properly and animations looked really bad.  In order to try to test the limitations, I created the following test game to see what would happen:

import random
import pyglet
from pyglet import gl


class Game(object):
    width = 1000
    height = 600
    images = [
        {"filepath": "image100x100.jpeg", "count": 300}
    ]
    background_color = (0.3, 0.3, 0.3, 1)
    report_interval = 5.0

    def start(self):
        self.pyglet_window = pyglet.window.Window(width=self.width, height=self.height, vsync=False)
        self.pyglet_window.event(self.on_draw)
        self.initializeGL()
        self.reset_report_timer()
        self.initialize_sprites()
        pyglet.clock.schedule_interval(self.tick, 0.01)
        pyglet.app.run()

    def initialize_sprites(self):
        self.sprites = []
        for image_set in self.images:
            filepath = image_set["filepath"]
            count = image_set["count"]
            image = pyglet.image.load(filepath)
            for i in range(count):
                x, y = random.randint(0, self.width), random.randint(0, self.height)
                sprite = pyglet.sprite.Sprite(image, x=x, y=y)
                self.sprites.append(sprite)

    def print_report(self):
        print "======================="
        print "  Call count: {}".format(self.num_calls)
        print "  FPS: {}".format(self.num_calls / self.report_interval)

    def reset_report_timer(self):
        self.report_timer = 0.0
        self.num_calls = 0

    def initializeGL(self):
        gl.glEnable(gl.GL_TEXTURE_2D)
        gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_NEAREST)
        gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST)
        gl.glEnable(gl.GL_BLEND)
        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
        gl.glPushAttrib(gl.GL_ENABLE_BIT)

    def tick(self, seconds_passed):
        self.num_calls += 1
        self.report_timer += seconds_passed
        if self.report_timer >= self.report_interval:
            self.print_report()
            self.reset_report_timer()


    def on_draw(self):
        self.pyglet_window.clear()
        pyglet.gl.glColor4f(*self.background_color)
        pyglet.graphics.draw(4, pyglet.gl.GL_QUADS,
            ('v2i', (0, 0, self.width, 0, self.width, self.height, 0, self.height))
        )
        pyglet.gl.glColor4f(1, 1, 1, 1)
        self.draw_sprites()

    def draw_sprites(self):
        for sprite in self.sprites:
            sprite.draw()


if __name__ == "__main__":
    game = Game()
    game.start()

I attached the file I used as well.  The GL commands I used were designed to get pixel perfect graphics, and I set them up a long time ago and don't remember exactly what they do.  If you think those are teh problem please let me know and I will test it out.

The results were pretty crumby.  I found that with 100 images I was sitting at about 60 fps, with 200 images I went down to 30 fps, with 300 images I was down to 20 fps.

I wanted to test out some features like the atlas and resource packages, but I figured I would double check to make sure I am not making any obvious mistakes here.  Can anyone confirm that the limitations I am experiencing are normal?  If not, what can I try to get things working a bit better?

My specs are:
  • MacBook Pro (15-inch, Mid 2012)
  • Processor - 2.6 GHz Intel Core i7
  • Memory - 8 GB 1600 MHz DDR3
  • Graphics - NVIDIA GeForce GT 650M 1024 MB
image100x100.jpeg

Benjamin Moran

unread,
Feb 7, 2017, 4:57:17 AM2/7/17
to pyglet-users
Hi Kevin,

There are a lot of very inificient things you are doing in your code. Calling the draw() method directly on each sprite is not recommended, since it has to set/unset the OpenGL state for each and every one.  What you want to do instead is to create a batch:  *batch = pyglet.graphics.Batch*, and pass this into each sprite on creation. Then, you can draw everything together with a single call to *batch.draw()* in your on_draw method. There is a bit of information about Batches in the programming guide, but it does need more detail:
http://pyglet.readthedocs.io/en/pyglet-1.2-maintenance/programming_guide/graphics.html#batched-rendering

That said, pyglet can easily render thousands of sprites if batched properly.

Benjamin Moran

unread,
Feb 7, 2017, 5:50:47 AM2/7/17
to pyglet-users
Here's a quick script-ish snippet that shows how to use the batch and resource module. It also illustrates the built-in fps display. This should probably work OK, but I can't really test it on my machine at the moment.

import random
import pyglet
from pyglet import gl


COUNT = 1000


window = pyglet.window.Window(width=1000, height=600)
batch = pyglet.graphics.Batch()
fps_display = pyglet.clock.ClockDisplay(color=(0.9, 0.0, 0.2, 1.0))
delta_time_display = pyglet.text.Label(x=12, y=70, font_size=25, color=(255, 0, 25, 255))

pyglet.resource.path.append('.')
pyglet.resource.reindex()

sprites = []


def initialize_sprites():
for i in range(COUNT):
image = pyglet.resource.image("image100x100.jpeg")
x = random.randint(0, window.width)
y = random.randint(0, window.height)
sprite = pyglet.sprite.Sprite(image, x=x, y=y, batch=batch)
sprites.append(sprite)


def initialize_gl():

gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_NEAREST)
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST)


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


def update_game(dt):
delta_time_display.text = "Delta Time: " + str(round(dt, 4))
# update game stuff here


if __name__ == "__main__":
initialize_sprites()
initialize_gl()
pyglet.clock.schedule_interval(update_game, 1/60.0)
pyglet.app.run()

Benjamin Moran

unread,
Feb 7, 2017, 8:28:23 AM2/7/17
to pyglet-users
Just wanted to say that this does work, and for reference it nets about 40,000 sprites at 60fps on my AMD R9 380 GPU. GPU usage is at about 75-80% in this case. The efficiency comes from the fact that the only one texture is in use, and that the bulk of the data is already residing on the GPU. This number of sprites is far more than you would need in a typical 2D game or application, but it's just to illustrate what you can get.

The real gotcha when it comes to performance is modifying a sprite's x, y, rotation, scale, etc. properties. This will cause it's internal vertex list to be updated, which occurs on the CPU. A common mistake is when trying to implemnt scrolling in a 2D tiled game, some users will try to update every sprite's x or y attribute each frame. Instead, it's better to do this on the GPU with a call to something like glTranslatef. Then, only the non-static sprite movements will need to be done on the CPU.

Kevin Steffler

unread,
Feb 7, 2017, 2:56:00 PM2/7/17
to pyglet...@googlegroups.com
Hi Benjamin,

Thanks so much for your feedback, I figured that I must be missing something.

I will give this a go when I get back from work. I will let you know.

--
You received this message because you are subscribed to a topic in the Google Groups "pyglet-users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/pyglet-users/2WCsQqL8B4E/unsubscribe.
To unsubscribe from this group and all its topics, send an email to pyglet-users+unsubscribe@googlegroups.com.
To post to this group, send email to pyglet...@googlegroups.com.
Visit this group at https://groups.google.com/group/pyglet-users.
For more options, visit https://groups.google.com/d/optout.

Benjamin Moran

unread,
Feb 7, 2017, 11:01:12 PM2/7/17
to pyglet-users
No worries, glad to help.
Of course, there is certainly further OpenGL level optimization you can do if you want more raw performance, and want to tweak things for your exact use case. The default modules are powerful, but make certain assumptions in the effort to be easy to use.
To unsubscribe from this group and all its topics, send an email to pyglet-users...@googlegroups.com.

Kevin Steffler

unread,
Feb 8, 2017, 11:10:05 AM2/8/17
to pyglet...@googlegroups.com
Hi Benjamin,

Tried it out last night, got up to about 9000 sprites before having issues.  Thanks a lot for the tip for scrolling as well, I was definitely making that mistake so you saved me a lot of hassle.

The next problem I have is ordering the sprite rendering with a z value, and it looks like I should read this thread to get up to speed.

Thanks for all the help!

Kevin Steffler
kevin5s...@gmail.com

Benjamin Moran

unread,
Feb 9, 2017, 9:47:25 AM2/9/17
to pyglet-users
Just wanted to add that if you only have a few layers (background, midsection, foreground, etc.), then using the pyglet OrderedGroups should be just fine. The limitation is that an OpenGL DrawArrays is required for each group. It's fine if you only have a few Groups, but won't scale well if you do too many. In that case, definitely check out the ZSprite or OrderedSprite implementations from the other thread. Both are working fine, but are dfiferent approaches.

Kevin Steffler
kevin5s...@gmail.com

To unsubscribe from this group and all its topics, send an email to pyglet-users...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages