Why is my simple python gtk+cairo program running so slowly/stutteringly?
Asked Answered
T

3

11

My program draws circles moving on the window. I think I must be missing some basic gtk/cairo concept because it seems to be running too slowly/stutteringly for what I am doing. Any ideas? Thanks for any help!

#!/usr/bin/python

import gtk
import gtk.gdk as gdk
import math
import random
import gobject

# The number of circles and the window size.
num = 128
size = 512

# Initialize circle coordinates and velocities.
x = []
y = []
xv = []
yv = []
for i in range(num):
    x.append(random.randint(0, size))
    y.append(random.randint(0, size))
    xv.append(random.randint(-4, 4))
    yv.append(random.randint(-4, 4))


# Draw the circles and update their positions.
def expose(*args):
    cr = darea.window.cairo_create()
    cr.set_line_width(4)
    for i in range(num):
        cr.set_source_rgb(1, 0, 0)
        cr.arc(x[i], y[i], 8, 0, 2 * math.pi)
        cr.stroke_preserve()
        cr.set_source_rgb(1, 1, 1)
        cr.fill()
        x[i] += xv[i]
        y[i] += yv[i]
        if x[i] > size or x[i] < 0:
            xv[i] = -xv[i]
        if y[i] > size or y[i] < 0:
            yv[i] = -yv[i]


# Self-evident?
def timeout():
    darea.queue_draw()
    return True


# Initialize the window.
window = gtk.Window()
window.resize(size, size)
window.connect("destroy", gtk.main_quit)
darea = gtk.DrawingArea()
darea.connect("expose-event", expose)
window.add(darea)
window.show_all()


# Self-evident?
gobject.idle_add(timeout)
gtk.main()
Twentyfour answered 31/1, 2010 at 17:4 Comment(1)
Nice program! I would try to randomly colorize the balls to do some plain eye-candy ;o)Divorcee
L
12

One of the problems is that you are drawing the same basic object again and again. I'm not sure about GTK+ buffering behavior, but also keep in mind that basic function calls incur a cost in Python. I've added a frame counter to your program, and I with your code, I got around 30fps max.

There are several things you can do, for instance compose larger paths before actually calling any fill or stroke method (i.e. will all arcs in a single call). Another solution, which is vastly faster is to compose your ball in an off-screen buffer and then just paint it to the screen repeatedly:

def create_basic_image():
    img = cairo.ImageSurface(cairo.FORMAT_ARGB32, 24, 24)
    c = cairo.Context(img)
    c.set_line_width(4)
    c.arc(12, 12, 8, 0, 2 * math.pi)
    c.set_source_rgb(1, 0, 0)
    c.stroke_preserve()
    c.set_source_rgb(1, 1, 1)
    c.fill()
    return img

def expose(sender, event, img):
    cr = darea.window.cairo_create()
    for i in range(num):
        cr.set_source_surface(img, x[i], y[i])        
        cr.paint()
        ... # your update code here

...
darea.connect("expose-event", expose, create_basic_image())

This gives about 273 fps on my machine. Because of this, you should think about using gobject.timeout_add rather than idle_add.

Lampley answered 1/2, 2010 at 0:16 Comment(3)
Is there some way to use this faster method while being able to set the color of the circle at draw time? Thanks!Twentyfour
Nothing that I know of (without doing much research), you would have to create several images in advance.Lampley
The real bottleneck in cairo is the mask generation, so you could create it in advance and fill more times with different sources. This is the relevant cairo thread (implementation details in C though): lists.cairographics.org/archives/cairo/2009-October/018243.htmlBuchholz
W
2

I don't see anything fundamentally wrong with your code. To narrow the problem down I tried a different approach that may be minimally faster, but the difference is almost negligible:

class Area(gtk.DrawingArea):
    def do_expose_event(self, event):
        cr = self.window.cairo_create()

        # Restrict Cairo to the exposed area; avoid extra work
        cr.rectangle(event.area.x,
                     event.area.y,
                     event.area.width,
                     event.area.height)
        cr.clip()

        cr.set_line_width(4)
        for i in range(num):
            cr.set_source_rgb(1, 0, 0)
            cr.arc(x[i], y[i], 8, 0, 2 * math.pi)
            cr.stroke_preserve()
            cr.set_source_rgb(1, 1, 1)
            cr.fill()
            x[i] += xv[i]
            y[i] += yv[i]
            if x[i] > size or x[i] < 0:
                xv[i] = -xv[i]
            if y[i] > size or y[i] < 0:
                yv[i] = -yv[i]
        self.queue_draw()

gobject.type_register(Area)

# Initialize the window.
window = gtk.Window()
window.resize(size, size)
window.connect("destroy", gtk.main_quit)
darea = Area()
window.add(darea)
window.show_all()

Also, overriding DrawingArea.draw() with a stub makes no major difference.

I'd probably try the Cairo mailing list, or look at Clutter or pygame for drawing a large number of items on the screen.

Wilton answered 31/1, 2010 at 18:14 Comment(0)
C
0

I have got the same problem in program was written on C#. Before you leaves Expose event, try to write cr.dispose().

Cyprus answered 10/6, 2010 at 4:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.