pygame_sdl2 icon indicating copy to clipboard operation
pygame_sdl2 copied to clipboard

Pygame_SDL2 runs slower when large quantity of sprites are drawn

Open runedevros opened this issue 9 years ago • 13 comments

Introduction

I noticed that Pygame SDL2 seems to be slower when many sprites are drawn on the screen. I created a program a while ago called bullet stress test in order to test the limits of the number of sprites that can be drawn while keeping the framerate up. In this program, a set of bullet images are drawn on the screen at random positions. Periodically 20 more are added, and the framerate is recorded until 2000 bullets are present. CProfile was used to track which function calls are the most costly.

Experimental Procedure

The test was run on the following system. I'm having some difficulty getting Pygame to run properly due to problems that OSX 10.11 introduced but the same behavior is seen in Pygame_SDL2 on OSX.

Python version:

  • Python 2.7.11
  • Pygame 1.9.1 and Pygame_SDL2 Nightly (2016-02-08)
  • Command: python -m cProfile -o outputfile.cprof -s cumtime sprites.py
  • Program: The current version of bullet stress test on the Bitbucket page. In sprites.py, if the variable sdl2_mode is set to False, the program uses Pygame instead of Pygame_SDL2:
#Sprite Test

sdl2_mode = True 

if sdl2_mode:
    try:
        import pygame_sdl2
        pygame_sdl2.import_as_pygame()

    except ImportError:
        print "OOPS Pygame SDL2 not available"

[... rest of program ...]

OS and Hardware:

  • Windows 10 - 64 bit
  • MacBook Pro (Early 2015 Edition, Retina)
  • CPU: 2.7 GHz Intel Core i5
  • RAM: 8 GB
  • GPU: Intel Iris Graphics 6100 1536 MB

Results and Discussion

Figure 1 shows the results of the recorded FPS as a function of number of bullets drawn to the screen. For Pygame 1.9.1 we are able to keep 60 FPS for up to 1000 bullets, dropping to 30 FPS once 2000 bullets are on the screen and the experiment is ended. However, for Pygame_SDL2, the FPS declines starting at roughly 250 bullets, and decreases to below 10 FPS by 2000 bullets.

figure_1 Figure 1. FPS as a function of number of bullets on the screen. While Pygame can maintain 60 FPS up to over 1000+ sprites, Pygame_SDL2's frame rate drops precipitously above 250 bullets.

Cprofile stats are shown in Table 1 and Table 2 for Pygame and Pygame_SDL2 respectively. For over 12 million calls to blit, the cumulative time is less than 40 seconds. In contrast, the cumulative time spent in blit for Pygame_SDL2 is on the order of 400 seconds, representing an order of magnitude increase in time spent in this function.

As for the specific implementation details, I do not know why this occurs in Pygame vs. Pygame_SDL2 but I hope this data will help you be able to make the library run faster.

Tue Feb 09 21:14:43 2016    pygame191.cprof

         36564382 function calls (36562557 primitive calls) in 126.736 seconds

   Ordered by: cumulative time
   List reduced from 896 to 10 due to restriction <10>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.005    0.005  126.737  126.737 sprites.py:3(<module>)
        1    0.443    0.443  126.625  126.625 sprites.py:30(main)
     6061   52.406    0.009   52.406    0.009 {pygame.display.update}
 12122020   36.185    0.000   36.185    0.000 {method 'blit' of 'pygame.Surface' objects}
     6061   24.674    0.004   24.674    0.004 {method 'tick' of 'Clock' objects}
     6061    1.512    0.000   23.238    0.004 sprite.py:478(clear)
     6061    6.469    0.001   22.851    0.004 sprite.py:566(draw)
     6061    2.077    0.000    2.574    0.000 sprite.py:452(update)
  6060000    0.691    0.000    0.691    0.000 {method 'union' of 'pygame.Rect' objects}
  6060000    0.474    0.000    0.474    0.000 {method 'colliderect' of 'pygame.Rect' objects}

Table 1. Cprofile results of the script for Pygame 1.9.1. Note the total time spent in blit is less than 1 minute.


Tue Feb 09 21:23:10 2016    pygamesdl2.cprof

         36571201 function calls in 455.481 seconds

   Ordered by: cumulative time
   List reduced from 212 to 10 due to restriction <10>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.005    0.005  455.481  455.481 sprites.py:3(<module>)
        1    6.541    6.541  455.453  455.453 sprites.py:30(main)
 12122020  419.963    0.000  419.963    0.000 {method 'blit' of 'pygame_sdl2.surface.Surface' objects}
     6061    6.467    0.001  252.535    0.042 sprite.py:566(draw)
     6061    1.506    0.000  184.180    0.030 sprite.py:478(clear)
     6061    8.444    0.001    8.444    0.001 {pygame_sdl2.display.update}
  6060000    4.636    0.000    4.636    0.000 {method 'union' of 'pygame_sdl2.rect.Rect' objects}
  6060000    3.411    0.000    3.411    0.000 {method 'colliderect' of 'pygame_sdl2.rect.Rect' objects}
     6061    1.957    0.000    2.429    0.000 sprite.py:452(update)
     6061    0.876    0.000    0.981    0.000 {pygame_sdl2.event.get}

Table 2. Cprofile results of the same script for Pygame_SDL2. The total time spent in blit time is over an order of magnitude greater than in Pygame.

runedevros avatar Feb 10 '16 04:02 runedevros

I don't see a way to fix this.

As best as I can tell, SDL1 had an mmx-optimized alpha blitter, which is gone in SDL2. In fact, the blit semantics have changed so much between versions that we can't use the SDL2 alpha blitter, we generally used our own.

But even when I modified pygame_sdl2 to be as close to pygame as possible - forcing the use of an the SDL blitter, modifying various surfaces so the masks and flags are all the same - I still get a similar slowdown.

I don't know what your way forwards is. If an optimized alpha blitter were to become available, I'd merge it. It might also be possible to use the pygame_sdl2.renderer to do some sort of accelerated blitting - to be honest, I didn't write and haven't looked at that code. Perhaps the best way - but also the most intrusive - would be to come up with a pygame_sdl2.gl API.

I'll leave this open, in the hopes someone will drop by and tell me what I'm doing wrong. But I don't see a simple fix.

renpytom avatar Feb 11 '16 04:02 renpytom

The renderer module will probably be a little faster, but SDL2 doesn't provide a way to do draw call batching. I'll see if I can add something with OpenGL that would be simple to use.

pkdawson avatar Feb 14 '16 16:02 pkdawson

I think the way forward would be retiring the surface/blit concept and start teaching how to use SDL2's excellent Renderer. It will offer a gentle introduction to GPU programming and also make things fast. Holding on to surface/blit rendering is just going to slow down progress.

bitcraft avatar Mar 14 '16 22:03 bitcraft

So I have a rather extensive codebase tied to the Pygame surface/blit system where our game has been engine-stable for about a year now. How hard would it be to port everything to the proposed SDL2 GPU thing that bitcraft suggested?

runedevros avatar Mar 14 '16 22:03 runedevros

If you have something similar to pygame's sprite and group system, I don't think it would be too difficult, as SDL2s renderer is not much different conceptually. On the other hand, if you have blits spread out all over, then it may be more difficult. -- you will have to manage and track textures yourself. You can follow any migration guide for SDL 1.2 => 2.x and it will be more or less that same, provided that there is a Renderer api in pygame2...is there?

On Mon, Mar 14, 2016 at 5:48 PM runedevros [email protected] wrote:

So I have a rather extensive codebase https://bitbucket.org/featheredmelody/lost-sky-project/wiki/Home tied to the Pygame surface/blit system where our game has been engine-stable for about a year now. How hard would it be to port everything to the proposed SDL2 GPU thing that bitcraft suggested?

— Reply to this email directly or view it on GitHub https://github.com/renpy/pygame_sdl2/issues/36#issuecomment-196553756.

bitcraft avatar Mar 14 '16 22:03 bitcraft

I tried experimenting with the pygame_sdl2 renderer and I see a very huge difference in the fps.

image

Modified Code:

#Sprite Test

sdl2_mode = True

if sdl2_mode:
    try:
        import pygame_sdl2
        pygame_sdl2.import_as_pygame()

    except ImportError:
        print "OOPS Pygame SDL2 not available"

import pygame
from pygame.locals import *
import os
import sys
import random

from pygame.render import *

class Bullet(pygame.render.Sprite):
    def __init__(self,img_tex):
        pygame.render.Sprite.__init__(self, img_tex)


def main():
    pygame.init()
    screen = pygame.display.set_mode((840,630))
    renderer = Renderer(None)
    renderer.render_present()

    img = pygame.image.load(os.path.join('images','bullets','02-orangeorb.png')) # SDL_Surface
    img_tex = renderer.load_texture(img) # SDL_Texture
    file = open('output_data.txt','w')

    menu_flag = True
    clock = pygame.time.Clock()
    bullets = []
    counter = 0
    bullet_num = 0
    fps_list = []
    data_list = []

    while menu_flag:


        for event in pygame.event.get():
            if event.type == QUIT:
                exit()
            if event.type == KEYDOWN:
                menu_flag = False
        if counter == 60:
            # spawn 20 new bullets
            for i in xrange(0,20):
                position = (random.randint(0, 840), random.randint(0,630))
                new_bullet = Bullet(img_tex)
                new_bullet.render(position) # SDL_RenderCopy()
                renderer.render_present()   # SDL_RenderPresent() 
                bullet_num += 1
                avg_fps = sum(fps_list)/len(fps_list)

            print (bullet_num,avg_fps)
            data_list.append((bullet_num,avg_fps))

            fps_list = []

            counter = 0

            # End Simulation at 2000 bullets
            if bullet_num > 2000:
                menu_flag = False

        clock.tick(60)
        fps_list.append(clock.get_fps())
        counter += 1

    print "Exporting Data"
    [file.write("%s    %s \n"%(str(data_pt[0]),str(data_pt[1]))) for data_pt in data_list]

if __name__ == "__main__":
    main()

In summary, I believe pygame_sdl2 should take full advantage of the SDL2 render and texture for sprites transformation and blit surface.

http://wiki.libsdl.org/MigrationGuide

eshikafe avatar Oct 01 '16 12:10 eshikafe

The way forward is with hardware rendering.

bitcraft avatar Oct 02 '16 02:10 bitcraft

Guys, when running eshikafe's modified code (two posts above) using the SDL2 render and texture for sprites: there's a magenta border around every sprite.

kazam1

I've had the same result (magenta borders) in two computers running Xubuntu 16.04. One with intel's integrated graphics and the other with nvidia video card and proprietary drivers. With the default ubuntu package "python-pygame-sdl2" as well as the most recent version built from source (today).

I've opened issue #65 for this. Is it a bug or is it just me?

thomanimation avatar Jan 24 '17 15:01 thomanimation

I have just provided a solution for this issue in #65

Here is the result: image

eshikafe avatar Apr 24 '17 02:04 eshikafe

I think issue #36 can be closed. This is already solved by using the render module.

eshikafe avatar Apr 24 '17 02:04 eshikafe

How do you use the render module on android? I get the error: File "main.py", line 7, in I/python (26081): from pygame.render import * I/python (26081): File "renpy/display/render.pyx", line 26, in init renpy.display.render (gen/renpy.display.render.c:27950) I/python (26081): ImportError: No module named renpy

I am using rapt to package my game. It is extremely slow. How do you make it use the GPU?

Pololot64 avatar Aug 27 '17 16:08 Pololot64

Well, actually I left this render method as "PC only" feature, because I couldn't run it on Android. I also tried to import "Renderer" instead of "*" from pygame but it returns the same error.

My code:

try:
    import pygame_sdl2
    pygame_sdl2.import_as_pygame() #It's normally imported on Android
    from pygame.render import Renderer #Returns exception "No module named Renderer" on Android
    SDL2 = True
    SDL2_Render = False
except:
    SDL2 = False
    SDL2_Render = False

If SDL2 is True, User will be offered to choose one of two render methods.

OSA413 avatar Sep 20 '17 18:09 OSA413

Which render method should I use on android to speed up my game? To see my code, look at the latest issue which I created. I use canvas.blit and Pygame.display.update.

Pololot64 avatar Sep 20 '17 18:09 Pololot64