Pygame Just Got Fast!
01 January 2021

A few weeks back pygame released version 2.0 on its 20th anniversary. I played with pygame long time ago but performance was not great and I eventually move onto other things. Other than seeing it used a fair bit in teaching and learning I have not really paid much attention to it.

The notes for version 2.0 show a huge number of improvements and caught my interest. I have not followed pygame closely but I guess moving to SDL2 in 2020 means it stalled for a while or perhaps development was done off the main branch for a long time?

Somewhere I heard the draw performance had improved so I thought I might take a look as my head seems to be in python at the moment.

Initially I was disappointed, following a simple tutorial to draw a static image I found the frame rate started to drop below 60FPS when more than about 55 image where draw each frame. That did not feel like an improvement. I was not writing tight code and I know pygame had dirty rect tricks to improve things but really that should be a thing of the past. Important in the days when Amiga ruled.

After a bit of digging I found what I should really be using is import pygame._sdl2. I can then use my knowledge of SDL2 to create textures stored on the GPU and start to use hardware acceleration. Code is at the bottom of this post.

We move from being able to draw 55 images per frame to being able to draw about 16000 images per frame before dropping below 60FPS. I use Linux on a first gen Ryzen 5 with a GTX 1660.

That is about a 290 times more images drawn per frame. We move from good for experimental small bits of code to yep got the draw speed for real 2D games.


Did you notice the underscore in the import. This means not stable and may go away! So there is that risk. For me if I was to use pygame seriously then I would have to take that risk as without it the draw is not fast enough. I may be able to jump through hoops to get what I wanted but honestly not having to think about it is better.

Apparently you can use openGL with pygame. I always assumed you could not so that would be an option if you were serious about pygame.

I did not find the documentation great. I guess it is something that will improve and given I am using an unstable API I should not expect too much, look in the example directory if you are interested.

There is no point creating a game if players cannot play it. Distribution is always fun. pygame mention they have support for packaging up a game on android but have not documented it yet. I would be interested in the performance here.

Personally in 2021 I feel systems/libaries should make packaging as simple as possible so if their goal is not something like python -m pygame.package_for_android mygame.py then I am disappointed. Sure have a config file or more command line options for control. Have the system tell you what is missing and perhaps even download what it needs in terms of dev tools. Then similar for making a Window/Linux/MacOS build, bonus point if you can cross build between desktop platforms.

Once they have a bit of documentation up on building for android I might take a look at how simple it is.

I am quite positive on pygame and could see myself messing around with it. Before committing to anything more than a few hours I would need to get myself certain on how to package up a game and would love to the SDL2 2D features become stabilised.


The code:


#  Uncomment this to turn off the console ouput telling us the version number
#  import os
#  os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide"

import random
import pygame
from pygame.locals import (
    K_q,
    K_ESCAPE,
    K_a,
    K_d,
    KEYDOWN,
    KEYUP,
    QUIT,
)

from pygame._sdl2 import Window, Texture, Image, Renderer


class Obj:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def draw(self):
        ren.blit(tex, pygame.Rect(self.x, self.y, 100 , 150), pygame.Rect(0, 0, 100 , 150))


pygame.init()
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600

win = Window("hello", (SCREEN_WIDTH, SCREEN_HEIGHT), resizable=True)
ren = Renderer(win)

img = pygame.image.load('character_zombie_sheet.png')
tex = Texture.from_surface(ren, img)
clock = pygame.time.Clock()
font = pygame.font.SysFont("Arial", 18)
running = True

objs = [Obj(10, 10), Obj(100, 100)]

add_obj = False
remove_obj = False

while running:

    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
        elif event.type == KEYDOWN:
            if event.key == K_ESCAPE or event.key == K_q:
                running = False
            elif event.key == K_a:
                add_obj = True
            elif event.key == K_d:
                remove_obj = True
        elif event.type == KEYUP:
            if event.key == K_a:
                add_obj = False
            elif event.key == K_d:
                remove_obj = False
            
    if add_obj: 
        for i in range(0,100):
            x = random.randint(10, SCREEN_WIDTH - 10)
            y = random.randint(10, SCREEN_HEIGHT - 10)
            objs.append(Obj(x, y))
    if remove_obj and len(objs)>0:
        objs.pop()
        
    fps = str(int(clock.get_fps()))
    title = fps + f" obj count: {len(objs)}"
    win.title = title

    ren.draw_color = (0, 100, 0, 255)
    ren.clear()
    
    for obj in objs:
        obj.draw()

    ren.present()

    clock.tick(60)

pygame.quit()