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.