Efficient scrolling in X11

Lately, I've been trying to make scrolling as efficient as possible. On the surface, it is a simple problem, the algorithm is like this:

def scroll(surface, xscroll, yscroll): # Scroll it. Auto clip in case xscroll or yscroll is negative. surface.draw(surface, xscroll, yscroll) top = (0, 0, surface.width, yscroll) left = (0, 0, xscroll, surface.height) bottom = (0, surface.height + yscroll, surface.width, -yscroll) right = (surface.width + xscroll, 0, -xscroll, surface.height) # Filter out the ones to redraw. rects = [rect for rect in top, left, bottom, right if rect[2] * rect[3] >= 0] for x, y, width, height in rects: # Assume redraw is the expensive step. redraw(surface, x, y, width, height)

So, if you scroll a surface of size 100x100 20 steps to the right and 20 steps down the rectangles become:

xscroll = 20
yscroll = 20

top = (0, 0, 100, 20)
left = (0, 0, 20, 100)
bottom = (0, 120, 100, -20)
right = (120, 0, -20, 100)

bottom and right whose area is negative becomes discarded and you are left with two rectangles totalling 4000 pixels (100 * 20 + 100 * 20) to redraw which is not so bad.

The problem is that this algorithm is freakishly hard to realize on X11 without theComposite extension. X11 doesn't store your surface, so how are you going to redraw it? You could store it yourself, in a GdkPixbuf for example. However, that would mean that the data is stored client side and you'll run into slowdowns when transferring it to the X11 server.

... Client-server is great for some things, but definitely not for computer graphics.

GdkPixbufs are drawn with gdk_draw_pixbuf() which should (or could) be a fast operation because it uses XShmPutImage(), but it is not. GdkPixbuf has a different format from the one that X11 want's so you run into a slowdown caused by the format mismatch.

Next attempt, use a GdkPixmap as the cache. surface.draw(surface, xscroll, yscroll) becomes one fast call to gdk_draw_drawable(). But it still isn't fast enough because pixmaps aren't easy to work with. There is no gdk_pixbuf_scale() equivalent for drawables so we still have to use a pixbuf to store the result of the scale operation. That pixbuf then has to be blitted to our pixmap cache which is then blitted, for real, to the XWindow we are drawing on. In effect, we are using triple buffering and redraws becomes to slow.

Third attempt, use the actual drawable we are drawing on as the cache. It would work except that it totally breaks down when the window is minimized, then your cache is gone.

GtkImageView uses a combination of the first and third method. It special cases scrolling operations and uses the third method to redraw them. By setting exposures to true, it detects when occluded areas becomes visible and then redraws them using the first method. That is far from ideal but is more or less the best thing you can do without composite.

Inga kommentarer:

Bloggarkiv