thursday, 4 october 2007

posted at 23:18
tags:

I committed sdl.hidd today. Its not finished by my own standard but I'm pretty much out of time and frustrated by the fact that to make it go any faster I have to keep copying code from the bitmap baseclass. Better to commit it now, let people play with it, and work to fix the driver interface instead.

I can't remember if I wrote about it previously, so here's the summary. The bitmap baseclass implements its methods by repeatedly calling GetPixel() and PutPixel() in the driver proper. This is reasonable; that way a graphics driver only needs to implement those two methods to get something on screen.

The problem is that in many setups, its necessary to do a second "flush" operation to actually get any changes to appear on the screen. I don't know if this is a problem with real hardware, but it at least has to be done with X11 and SDL. This sucks - with X11 (and for me, SDL, since I develop with it on top of X11) that means a full request/response into the X server. This makes large operations like area filling very slow, as every pixel gets flushed individually.

The way around this, obviously, is to override some the of the higher-level operations to do their work and only flush once. This sucks though if the underlying hardware/library does not actually support the operation natively. At this point, you're left with two options - copy code from the baseclass implementation but have it operate directly on the framebuffer, with one final flush at the end, or don't implement it at all and just take the slow fallback.

If there was only a couple of functions I wouldn't mind copying code from the baseclass, but pretty much every method gets passed something called the graphics context. This contains all sorts of information about how the driver should perform the operation. Should pixels be opaque or transparent, should lines be full, dashed, etc, should area fills be solid or patterned, etc.

x11gfx.hidd can go fast because because X11 too has a concept of a graphics context which has largely the same semantics (in fact I suspect the concept was copied, given that X11 was the first graphics system AROS used), so X11 can accelerate nearly all graphics operations it receives. (Notably one that it can't handle directly is BitMapScale(), which was horribly slow before I fixed it).

Alas for SDL, which has no idea about graphics contexts; indeed, it (by design) has no drawing primitives at all. Libraries like SDL_gfx exist to help with this, but they don't do enough to be useful.

I don't want to implement my own drawing primitives and context stuff, because the baseclass implementation is prefectly good. Its just hamstrung by the fact that plotting millions of individual pixels takes a lot of time, because of the flushes. So I began to look for a way around this.

SDL has the right idea here. All operations you perform on a surface won't appear on screen immediately. It takes a call to SDL_UpdateRect() to make stuff appear. It seems reasonable to add something similar to the bitmap interface. The baseclass implementations would do their plotting and call the flush method when they're done. The baseclass implementation of this method would simply be a no-op, and existing drivers would not implement it, so they'd continue to work as normal. For something like SDL, it wouldn't implement any flushes in PutPixel(), but save all its updates for the flush method (which I've called UpdateRect() also, because it seems to make as much sense as anything else).

The only problem with this is that if you really really wanted to put a pixel (ie somewhere up in graphics.library), then you have to do a 1x1 rectangle flush. I don't consider that a problem really - if you're doing single-pixel plots up at higher levels you're almost certainly doing it wrong.

Finally, every method should be implemented this way. That is, if a driver implements the UpdateRect() method, it should not do flushes anywhere else.

I've already started work on this - I have UpdateRect() stubbed and (no-op) implemented in graphics.hidd in one of my private trees. Next I have to modify all the baseclass fallback methods to call it, then modify graphics.library to call it also, and finally, change sdl.hidd to take advantage of it. Once thats done I should be able to delete a large amount of code from sdl.hidd.

The only thing to realise is that these fallbacks will still be slower than native operations, but the overhead will be the normal method call overhead for each GetPixel()/PutPixel() call, not the flush. Thats a good thing.