2

I am writing a small application in python 2.7 using pygame in which I would like to smoothly display animated tiles on screen. I am writing on Ubuntu, but target platform is Raspberry Pi if that's relevant. The challenge is that the textures for these animated tiles are stored on a web server and are to be loaded dynamically over time, not all at once. I'd like to be able to load these images into pygame with no noticeable hitch in my animation or input response. The load frequency is pretty low, like grabbing a couple jpgs every 30 seconds. I am willing to wait a long time for the image to load in the background if it means the main input/animation thread remains unhitched.

So using the multiprocessing module, I am able to download images from a server asynchronously into a buffer, and then pass this buffer to my main pygame process over a multiprocessing.queues.SimpleQueue object. However, once the buffer is accessible in the pygame process, there is still a hitch in my application while the buffer is loaded into a Surface for blitting via pygame.image.frombuffer().

Is there a way to make this pygame.image.load() call asynchronous so that my animation in the game, etc is not blocked? I can't think of an obvious solution due to GIL.

If I was writing a regular OpenGL program in C, I would be able to asynchronously write data to the GPU using a pixel buffer object, correct? Does pygame expose any part of this API by any chance? I can't seem to find it in the pygame docs, which I am pretty new to, so pardon me if the answer is obvious. Any help pointing out how pygame's terminology translates to OpenGL API would be a big help, as well any relevant examples in which pygame can initialize a texture asynchronously would be amazing!

If pygame doesn't offer this functionality, what are my options? Is there a way to do this is with PySDL2?

EDIT: Ok, so I tried using pygame.image.frombuffer, and it doesn't really cut down on the hitching I am seeing. Any ideas of how I can make this image load truly asynchronous? Here is some code snippets illustrating what I am currently doing.

Here's the async code I have that sits in a separate process from pygame

def _worker(in_queue, out_queue):
    done = False
    while not done:
        if not in_queue.empty():
            obj = in_queue.get()
            # if a bool is passed down the queue, set the done flag
            if isinstance(obj, bool):
                done = obj
            else:
                url, w, h = obj
                # grab jpg at given url. It is compressed so we use PIL to parse it
                jpg_encoded_str = urllib2.urlopen(url).read()
                # PIL.ImageFile
                parser = ImageFile.Parser()
                parser.feed(jpg_encoded_str)
                pil_image = parser.close() 
                buff = pil_image.tostring()
                # place decompressed buffer into queue for consumption by main thread
                out_queue.put((url, w, h, buff))

# and so I create a subprocess that runs _worker function

Here's my update loop that runs in the main thread. It looks to see if the _Worker process has put anything into the out_queue, and if so loads it into pygame:

def update():
    if not out_queue.empty():
        url, w, h, buff = img_buffer_queue.get()
        # This call is where I get a hitch in my application
        image = pygame.image.frombuffer(buff, (w, h), "RGB")
        # Place loaded image on the pygame.Sprite, etc in the callback
        callback = on_load_callbacks.pop(url, None)
        if callback:
            callback(image, w, h)
Ben P
  • 129
  • 9
  • Have you thought about trying to store the streamed image as pixel data in a string, and loading this as a `Surface` through `pygame.image.frombuffer`? http://www.pygame.org/docs/ref/image.html#pygame.image.frombuffer – Haz Feb 11 '14 at 04:27
  • I'll give that a try. It will probably be faster, but not truly asynchronous. I might just have to do larger batches and have a small "loading..." pause each time I preload a batch. But that's lame! :D – Ben P Feb 11 '14 at 07:15
  • @Haz Ok, so I've tried frombuffer() to no avail, still getting a significant hitch during image loads. See edit to main post. – Ben P Feb 18 '14 at 03:54
  • Well you could use something like PyOpenGL to get an OpenGL context, but I suspect you would then have to use it for all of your drawing. Another possibility would be to create an empty surface that matches the size of the streamed image, and then on each update cycle copy a few rows of pixels over to the new surface until it's complete. It will probably take longer overall to stream a single image, but might also have a smaller impact per frame. – Haz Feb 18 '14 at 19:45
  • Some questions: why aren't you using threads instead of multiprocessing? If you used threads you'd have shared memory and you could possible load the image from the buffer in the worker thread? (is this a Raspberry Pi thing) Also is the from_buffer call thread safe? Can you move the loading of the image to an on_idle callback? Do you have control of the mainloop in pygame so you have control of when your image loads from the buffer. – demented hedgehog Feb 24 '14 at 02:14
  • Also, can you break your images into small bits and handle them one at a time (tile the background)? How big are they small or full backgrounds? – demented hedgehog Feb 24 '14 at 02:20
  • @dementedhedgehog Downloading the image in a thread is probably ok since it is I/O bound, but I put it in a separate process to guarantee asynchronous download. Loading it into an SDL surface is a blocking call. But even if I put that call in a separate thread, I still get blocking of my main animation / input threads due to the global interpreter lock. I do have control of when the image loads from the buffer. I am investigating tiling the image and loading it in chunks right now, thanks! – Ben P Feb 24 '14 at 06:12
  • Wow, we're basically doing the same thing! http://stackoverflow.com/questions/28819411/how-to-use-python-multiprocessing-to-prepare-images-for-pygame #smallworld – artfulrobot Mar 02 '15 at 23:37

1 Answers1

0

Perhaps you could try converting the image from a buffer object to a more efficient object in the worker function, perhaps a surface, and then send that computed surface to the out queue? This way, generation of the surface object won't block the main thread.

Paul Belanger
  • 2,354
  • 14
  • 23
  • That unfortunately won't work because the pygame can only create surfaces in the main process. I could make the surface in a separate thread, but that still creates stuttering in my main thread due to GIL. – Ben P Feb 25 '14 at 04:55