Rendering efficiency with LOTS of displayables.

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Post Reply
Message
Author
User avatar
ChroniclerOfLegends
Regular
Posts: 53
Joined: Thu Feb 16, 2017 5:57 pm
Projects: Scifi Non-Linear Visual Novel/Arcade/Adventure game. (No name yet)
Contact:

Rendering efficiency with LOTS of displayables.

#1 Post by ChroniclerOfLegends »

So I have a question that may be out of the realm of normal visual novel/ Renpy questions.

Before you ask why in the world I would need to have 100+ individual instances of the same displayable displaying at once... Its because I am not really using Renpy for it's intended purpose. I am making an action game with visual novel story elements. I know I could have used more action-oriented engines to do this, but I love Renpy and Pygame. Why am I doing this? I ask myself that sometimes too, but its fun :)

Ok, now onto the actual question.
I have pushed Renpy quite far beyond its intended scope, so I was expecting this issue eventually. Using Fraps to check Frames per second, my game is running 60fps most of the time, but drops to 28- 34 fps under maximum load.

The source of this framerate dip is a large number of custom displayables being rendered to the screen. They are fairly small, only about 108x25 px, and have some transparency. They move very fast, and currently between 80 and 100 of them are being displayed at maximum load.

Efficiencies I have already implemented:
- Do not create a new displayable unless absolutely necessary. Recycle ones that have been destroyed or left bounds of screen. This allows me to have the appearance of hundreds of displayables being created, while only having about a maximum of 250 actually loaded into memory.
- Pre-create the displayables that are being instantiated at start. So during gameplay none of them actually have to be created, you simply reset their visibility/position when needed.

I believe what is hogging the memory is in rendering them. I am using the standard method to render something in Renpy(python) / pygame:

Code: Select all

            
def render( self, renderer, shownTimebase, animationTimebase ):
 # Display this object
 if ( self.active ):
  r = renpy.render( self.image, SCENE_RIGHT, SCENE_DOWN, shownTimebase, animationTimebase )
  renderer.blit( r, (self.position[0], self.position[1]) )
I am saving all active of this type of displayable in a list, and iterating through it calling render on all entries in the list.
Elsewhere on the web, I have heard suggestion that because they all use the same image and are the same size, it would be far more efficient to draw them all in a single step each frame, instead of iterating through them and rendering them one at a time.
I think this would speed things up significantly, but not quite sure how to implement it. I considered making a step where I run all of the displayables renderer.blit in a single block of code, but I don't think that would work. I would need to find some way to combine all of the displayables into a single Image and blit that one image.

I have also heard you can get a large performance boost by converting the alpha of transparent images in pygame.

I was wondering if anybody had any advice for how I can make the rendering step more efficient?

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: Rendering efficiency with LOTS of displayables.

#2 Post by Remix »

You should likely look at Sprites and the SpriteManager Class which is optimized to display many 'caches' of the same image many times. Also have a read through the actual SpriteManager class in the Ren'py source to see how it uses caching.
Frameworks & Scriptlets:

User avatar
ChroniclerOfLegends
Regular
Posts: 53
Joined: Thu Feb 16, 2017 5:57 pm
Projects: Scifi Non-Linear Visual Novel/Arcade/Adventure game. (No name yet)
Contact:

Re: Rendering efficiency with LOTS of displayables.

#3 Post by ChroniclerOfLegends »

Thanks, I will do a bit of research on those.
I can confirm that condensing all of the blits to a single call had no noticible gains in performance.

User avatar
ChroniclerOfLegends
Regular
Posts: 53
Joined: Thu Feb 16, 2017 5:57 pm
Projects: Scifi Non-Linear Visual Novel/Arcade/Adventure game. (No name yet)
Contact:

Re: Rendering efficiency with LOTS of displayables.

#4 Post by ChroniclerOfLegends »

That does look like it might help, but the problem is that every sprite wouldn't necessarily behave the same way. So a single update() method wouldn't work. Some go in a straight line, while others may follow a path.

Can the individual sprites be updated externally? For example, could I create a spritemanager, but then the objects the sprites represent create the sprite using the spritemanager and they give it its positional info every update?

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: Rendering efficiency with LOTS of displayables.

#5 Post by Remix »

A very basic example ...

Code: Select all


init python:

    img1 = Image("images/180.png")
    img2 = Image("images/307.png")

    def repulsor_update(st):

        # For each sprite...
        for i in repulsor_sprites:

            if i.cache.child == img1:
                i.x += renpy.random.randint(0,5)
            else:
                i.y += renpy.random.randint(0,3)

            if i.x > repulsor.width - 2:
                i.x = 2

            if i.y > repulsor.height - 2:
                i.y = 2

        return .01


label start:#repulsor_demo:

    python:
        # Create a sprite manager.
        repulsor = SpriteManager(update=repulsor_update)#, event=repulsor_event)
        repulsor_sprites = [ ]
        repulsor_pos = None

        # Add 400 sprites.
        for i in range(400):
            repulsor_sprites.append(
                renpy.random.choice(
                    [ repulsor.create(img1), repulsor.create(img2) ] ) )

        # Position the 400 sprites.
        for i in repulsor_sprites:
            i.x = renpy.random.randint(2, 798)
            i.y = renpy.random.randint(2, 598)

        del i

    # Add the repulsor to the screen.
    show expression repulsor as repulsor

    "..."

    hide repulsor

    # Clean up.
    python:
        del repulsor
        del repulsor_sprites
        del repulsor_pos
That simply uses the SpriteManager to throw 400 sprites on the screen as either Image("images/180.png") or Image("images/307.png") then tells them that one image type moves vertically and the other horizontally. I used a simple -- if i.cache.child == img1: -- test, though you could extend both classes (and SpriteManager.create() ) to just pass in an extra parameter such as Sprite.type...
Frameworks & Scriptlets:

User avatar
ChroniclerOfLegends
Regular
Posts: 53
Joined: Thu Feb 16, 2017 5:57 pm
Projects: Scifi Non-Linear Visual Novel/Arcade/Adventure game. (No name yet)
Contact:

Re: Rendering efficiency with LOTS of displayables.

#6 Post by ChroniclerOfLegends »

I got it partially working. but how do you manually render the spritemanager?

Code: Select all

self.spriteManagerName.render(renderer, shownTimebase, animationTimebase)
Doesn't seem to work. It wants 5 arguments (4 given if you include self)

renpy.show(self.spriteManagerName) results in the error that Spritemanager has no attribute 'split'

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: Rendering efficiency with LOTS of displayables.

#7 Post by Remix »

ChroniclerOfLegends wrote: Wed Apr 25, 2018 10:01 pm I got it partially working. but how do you manually render the spritemanager?

Code: Select all

self.spriteManagerName.render(renderer, shownTimebase, animationTimebase)
Doesn't seem to work. It wants 5 arguments (4 given if you include self)

renpy.show(self.spriteManagerName) results in the error that Spritemanager has no attribute 'split'
I do not understand why you want to call the render method like that? Is not screen language or a label enough?
Did you even read the class definitions in the Ren'py source? If you did, you'd see "def render(self, width, height, st, at):" which should be hint enough.
Frameworks & Scriptlets:

User avatar
ChroniclerOfLegends
Regular
Posts: 53
Joined: Thu Feb 16, 2017 5:57 pm
Projects: Scifi Non-Linear Visual Novel/Arcade/Adventure game. (No name yet)
Contact:

Re: Rendering efficiency with LOTS of displayables.

#8 Post by ChroniclerOfLegends »

Sorry, I know it's confusing, but I am not actually using the screen language very much, instead going straight to python.
The game I am making swaps between two modes. The story segments are 90% renpy screen language with a little bit of python for flags, but the main gameplay segments barely use any renpy screen language. Mostly working directly with pygame with a little bit of renpy's features. It may be possible to mix the screen language and python more in the action segments but I have been keeping it consistent.

I appreciate the help a lot, sorry if my questions were frustrating.

User avatar
Remix
Eileen-Class Veteran
Posts: 1628
Joined: Tue May 30, 2017 6:10 am
Completed: None... yet (as I'm still looking for an artist)
Projects: An un-named anime based trainer game
Contact:

Re: Rendering efficiency with LOTS of displayables.

#9 Post by Remix »

ChroniclerOfLegends wrote: Thu Apr 26, 2018 4:19 pm I appreciate the help a lot, sorry if my questions were frustrating.
Not too frustrating, I just wondered if you were choosing the best option and whether what you are doing might actually work as well or better just in Ren'py.

Modern Ren'py uses a pygame SDL2 API which exposes most of the standard old sdl 1.n API of pygame with a few omissions (bitmask collision detection among them, grrr - working on that though ;) ) ... for info SDL2 went stable in Aug 2013 which kind of shows how old the hardware abstraction layer for pygame is. The more modern SDL2 helps Ren'py run smoothly on more platforms, so the benefits outweigh the costs.

Screen language, Sprites, Transforms, Events and all those features of Ren'py can (with a little work and study) accomplish a lot more than just VN game play. If you are actually writing the pygame part, you might find it more useful to learn to do it through Ren'py instead/as well.
Frameworks & Scriptlets:

Post Reply

Who is online

Users browsing this forum: No registered users