Xlib stuff

Swinged to e-mail, but back here since it could be useful for people
that want to improve SDL…–
Pierre Phaneuf
Ludus Design, http://ludusdesign.com/
“First they ignore you. Then they laugh at you.
Then they fight you. Then you win.” – Gandhi
-------------- next part --------------
An embedded message was scrubbed…
From: hayward@slothmud.org (hayward at slothmud.org)
Subject: Xlib stuff (No longer on SDL mailing list)
Date: Mon, 3 Jan 2000 13:06:31 -0600 (CST)
Size: 3584
URL: http://lists.libsdl.org/pipermail/sdl-libsdl.org/attachments/20000104/e6863a31/attachment.eml

Brian Hayward wrote:

This is with a 640x480 window in 16 bit, giving something nearly at 30
megabytes per second. My AMD 486DX4/120 with an S3 ViRGE does around 7
megabytes per second with the exact same test program.

Ok… I did leave out an important detail. During my Xlib tests, where I
got better performance with XCopyArea versus XShmPutImage, it was a plain
640x480x16bpp window. (I don’t have FPS numbers from that)

Same window size and color depth here.

The fps I mentioned are after switching to SDL. Using SDL, I am also
doing one extra detail that is important to comment on. I am using an
800x600x16bpp window with a 640x480 subwindow (a clipping region in the
middle) where the high-speed gameplay is done. The clipping may be
costing me some as well. Anyway, this is where I get the specific FPS I
mentioned before.

Maybe this is where the cost is, because the 10 megabytes per second
you’re getting much closer to my 486 than my Pentium, much farther than
your K6-3!

Sorry if I sounded a bit rude, but it just seems a lot of people
improvise themselves Xlib experts these days and run wildly telling me
to use DGA or XShm, like they knew and/or I didn’t… Next thing,
they’ll tell me to use threading to improve my program! :wink:

Hehe… I don’t consider myself an expert, nor do I rule out that I could
have been doing something wrong. But I went through many different
graphics handling routines to find out the fastest one for me.

Mostly went thru the same myself… I am not exactly an X expert, but I
am getting closer to that than many (most?) other people, and I know a
thing or two about graphics and high performance programming and
hardware (my day job is in supercomputers)… :slight_smile:

I am trying out the tiled pixmap trick at this very moment…
Interestingly, it is slower than XCopyArea! I am getting only 24 MB/s
while I got 29 MB/s with XCopyArea…

It still isn’t going into video memory… Hmm, maybe if I put the pixmap
in the “tile” property of the GC? Nope, doesn’t help…

Did you change anything in the GC? Some options in the GC can add
plenty of overhead to both XCopyArea and XShmPutImage.

Again, the FPS I got was stated within SDL, not within XShmPutImage or
XCopyArea. I believe SDL is using XShmPutImage(), though I haven’t
looked. But I’m not doing anything unusual with the GC (it’s hidden from
me with SDL).

Should be fine then… I suspect clipping might be the problem here.

Agreed, when I was struggling with X, I also spoke with Raster about this.
He gleamed of the wonders of XCopyArea with shared memory pixmaps, he said
it was 10% faster.

Did he say to XCopyArea or to XClearArea?

Unfortunately, It wont work with my model: (the 640x480 subwindow) because
to use Shared memory pixmaps, you have to do XClearArea() with the
background of the window being set to that pixmap.

I chose not to update the menu area as fast as game area becaus I knew SDL
and the other interfaces I was using would not support storing images in
video mem. So I wanted to reduce the size of the window that was updated
quickly.

If you have any suggestions for me, I’d be happy to listen.

If you want to update only part of the window, XClearArea let you do
that, doesn’t it? But, as it stands, I’m getting better performance with
XCopyArea rather than XClearArea now…–
Pierre Phaneuf
Ludus Design, http://ludusdesign.com/
“First they ignore you. Then they laugh at you.
Then they fight you. Then you win.” – Gandhi

Hey, I’d appreciate it if you apply whatever lessons you glean from your
Xlib work into the SDL X driver. I’ve done the best I know how, but it
sounds like you’re doing lots of research, and may turn up some tricks. :slight_smile:

BTW, shared memory images and pixmaps will never be in video memory.

-Sam Lantinga				(slouken at devolution.com)

Lead Programmer, Loki Entertainment Software–
“Any sufficiently advanced bug is indistinguishable from a feature”
– Rich Kulawiec

Again, the FPS I got was stated within SDL, not within XShmPutImage or
XCopyArea. I believe SDL is using XShmPutImage(), though I haven’t
looked. But I’m not doing anything unusual with the GC (it’s hidden from
me with SDL).

Should be fine then… I suspect clipping might be the problem here.

When I implemented clipping for my own optimized XImage blits under Xlib
(before switching to SDL), all I did for clipping was a few compares
and incrimenting or decrimenting srcx,dstx,srcy,dsty values. I’m not sure
if clipping is done differently in SDL, but when I’m tiling a 640x480
display with a 256x128 tile, you only do those calculations 12 times. It
shouldn’t cause a major slowdown.

Did he say to XCopyArea or to XClearArea?

Yep. was a misstype on my part.

If you want to update only part of the window, XClearArea let you do
that, doesn’t it? But, as it stands, I’m getting better performance with
XCopyArea rather than XClearArea now…

This is true, I could create an 800x600 offscreen image, draw to the
center of it, then do XClearArea on that part.

I’m going to have to pull out my tests again, I can’t believe you’re
getting so much better performance with XCopyArea than the shared memory
alternatives, because that was not my experience.–
Brian

Pierre,

Also, you might consider taking your tests one step further. Rather than
just use XCopyArea(), XShmPutImage(), XPutImage(), etc.

Try extending your test for transparent blitting.

Now that I think about it, that is where the difference between shared mem
XImages and non-shared-memory blitting got really bad.

Of course, for shared memory XImages, to be able to do a color-key blit,
you need to write your own blit routine that accesses the XImage->data
directly. At least, thats how my color-key and even non-color-key blits
were implemented. Even though XGetPixel/XPutPixel is portable, it’s slow
as a dog. They should never be used for blitting.–
Brian

Sam Lantinga wrote:

Hey, I’d appreciate it if you apply whatever lessons you glean from your
Xlib work into the SDL X driver. I’ve done the best I know how, but it
sounds like you’re doing lots of research, and may turn up some tricks. :slight_smile:

Yes, of course. I don’t know if I’ll have to do the work myself (I do
not actively use SDL, I only look at the sources to learn some stuff and
see how other people do it), but you can be sure that if I make a
breakthrough, I’ll keep you informed!

BTW, shared memory images and pixmaps will never be in video memory.

Yes, I know. That’s why I only use them to “upload” data to the X
server. I already explained that our design separates local and
non-local surfaces. Local surfaces are only used to upload data and for
often-updated surfaces, like the main viewscreen in Doom would be for
example (most of the time, those wouldn’t profit much from being in
video memory anyway).–
Pierre Phaneuf
Ludus Design, http://ludusdesign.com/
“First they ignore you. Then they laugh at you.
Then they fight you. Then you win.” – Gandhi

hayward at slothmud.org wrote:

When I implemented clipping for my own optimized XImage blits under Xlib
(before switching to SDL), all I did for clipping was a few compares
and incrimenting or decrimenting srcx,dstx,srcy,dsty values. I’m not sure
if clipping is done differently in SDL, but when I’m tiling a 640x480
display with a 256x128 tile, you only do those calculations 12 times. It
shouldn’t cause a major slowdown.

Maybe the clipping in SDL is more generic? I have no idea, really.
Compile and run the program I sent in attachment (quit with Escape), and
tell me what output it gives you… Make sure all of the window it
create is visible, or else the numbers will be screwed!

I’m going to have to pull out my tests again, I can’t believe you’re
getting so much better performance with XCopyArea than the shared memory
alternatives, because that was not my experience.

I do not use shared pixmaps or images, only pixmaps. XCopyArea is just
as fast (or slightly faster) than XShmPutImage, because it works very
similarly.

The problem with XCopyArea is that they only work with Drawables, and
you can’t access the pixel data of Drawables. XImage (with XPutImage)
let you access the pixel data, but is very slow.

Shared pixmaps is a way for the client to access the pixel data of
Pixmap (Window pixel data is in video memory, which cannot be shared).
Shared images accelerate the XPutImage by letting the server access the
data that is in the client directly (without going through the slow X
stream).

Those are two different solutions to two different (but related)
problems.

On another note, I didn’t try XDBE (double buffering extension) yet, but
jwz told me it was a real joke, and I didn’t see anybody else using it,
so I don’t have much faith into this (will try it soon anyway)…–
Pierre Phaneuf
Ludus Design, http://ludusdesign.com/
“First they ignore you. Then they laugh at you.
Then they fight you. Then you win.” – Gandhi

hayward at slothmud.org wrote:

Also, you might consider taking your tests one step further. Rather than
just use XCopyArea(), XShmPutImage(), XPutImage(), etc.

Try extending your test for transparent blitting.

Now that I think about it, that is where the difference between shared mem
XImages and non-shared-memory blitting got really bad.

No, what I am running for is getting hardware acceleration from my X
server, and shared memory is garanteed not to give it to me. When I do
get good acceleration, I will have to hope that clip_mask is accelerated
too. :slight_smile:

Of course, for shared memory XImages, to be able to do a color-key blit,
you need to write your own blit routine that accesses the XImage->data
directly. At least, thats how my color-key and even non-color-key blits
were implemented. Even though XGetPixel/XPutPixel is portable, it’s slow
as a dog. They should never be used for blitting.

Yes, I already have that stuff written for Quadra, but even better code
is already in SDL (the RLE acceleration for example). But this isn’t
really a pure blit: you first do your own colorkey blit into the
XImage, then use XShmPutImage to blit into a window.

Like I said, when you have image data that changes often, client-local
access to pixel data (like shared images or shared pixmaps gives you) is
important, and hardware acceleration won’t help you much anyway.–
Pierre Phaneuf
Ludus Design, http://ludusdesign.com/
“First they ignore you. Then they laugh at you.
Then they fight you. Then you win.” – Gandhi

Maybe the clipping in SDL is more generic? I have no idea, really.
Compile and run the program I sent in attachment (quit with Escape), and
tell me what output it gives you… Make sure all of the window it
create is visible, or else the numbers will be screwed!

Again, from my original question, do your tests do any color-key
blitting. That was the primary point of my last email that seems to have
been overlooked in your reply.

BTW. You didn’t attach the program, you might try again and I’ll take
your suggestion and try it out.

I was just curious about your performance results, thats all. Because my
results, especially with color key blitting, are different. I wasn’t
really asking for a description of which method solves which problems.

I do not use shared pixmaps or images, only pixmaps. XCopyArea is just
as fast (or slightly faster) than XShmPutImage, because it works very
similarly.

The problem with XCopyArea is that they only work with Drawables, and
you can’t access the pixel data of Drawables. XImage (with XPutImage)
let you access the pixel data, but is very slow.

chuckle not even slow, not if you know how to write a decent blit that
accesses the pixel data of your shared memory “backbuffer”, and you use
XShmPutImage() to spit the backbuffer to the screen. Thats how the people
who ported quake (earlier versions) to X did their stuff. Ok, it’s
slow if you compare it to DirectX, but its’ fast compared to the other X11
methods, especially for color-key.

On another note, I didn’t try XDBE (double buffering extension) yet, but
jwz told me it was a real joke, and I didn’t see anybody else using it,
so I don’t have much faith into this (will try it soon anyway)…

I tried DBE and had poor performance from it. It just adds another layer
of copying in system memory, basically. It wasn’t providing hardware
double buffering when I tried it.–
Brian Hayward

ahh, here is your reply about color-key blitting :slight_smile:

server, and shared memory is garanteed not to give it to me. When I do
get good acceleration, I will have to hope that clip_mask is accelerated
too. :slight_smile:

Ok, refresh my memory. Do video cards provide accelerated color_key
blits, or do they provide accelerated clip_mask blits. I seem to remember
hearing about video cards supporting color-key blits, not clip-mask ones.
But I could be wrong here.–
Brian

hayward at slothmud.org wrote:

BTW. You didn’t attach the program, you might try again and I’ll take
your suggestion and try it out.

Oops! Here it is!–
Pierre Phaneuf
Ludus Design, http://ludusdesign.com/
“First they ignore you. Then they laugh at you.
Then they fight you. Then you win.” – Gandhi
-------------- next part --------------
#include <sys/time.h>
#include <stdio.h>
#include <X11/Xlib.h>

#undef TILE_TRICK
#undef GRAB_SERVER

#define WINDOW_X 640
#define WINDOW_Y 480
#define BLIT_X 640
#define BLIT_Y 480

#define DEPTH(display) DefaultDepth(display, DefaultScreen(display))

int main() {
unsigned int frames;
int i, j;
int buf;
Display* dpy;
XPixmapFormatValues* pixmapfmt;
Window win;
GC gc;
Pixmap bufs[2];
struct timeval time1;
struct timeval time2;
unsigned int diff;
unsigned int bytes_per_pixel = 0;
double mbps;
XGCValues gcvalues;

dpy = XOpenDisplay(NULL);

win = XCreateSimpleWindow(dpy,
DefaultRootWindow(dpy),
0, 0, /* x, y /
WINDOW_X, WINDOW_Y, /
width, height /
0, 0, /
border_width, border /
None); /
background */

XSelectInput(dpy, win, ExposureMask | KeyPressMask);

XMapWindow(dpy, win);

bufs[0] = XCreatePixmap(dpy,
win,
BLIT_X, BLIT_Y, /* width, height /
DEPTH(dpy)); /
depth */

bufs[1] = XCreatePixmap(dpy,
win,
BLIT_X, BLIT_Y, /* width, height /
DEPTH(dpy)); /
depth */

gcvalues.function = GXcopy;
gcvalues.plane_mask = AllPlanes;
gcvalues.fill_style = FillSolid;
#ifdef TILE_TRICK
gcvalues.tile = bufs[0];
gcvalues.ts_x_origin = 0;
gcvalues.ts_y_origin = 0;
#endif
gcvalues.graphics_exposures = False;

gc = XCreateGC(dpy,
win,
GCFunction
| GCPlaneMask
| GCFillStyle
#ifdef TILE_TRICK
| GCTile
| GCTileStipXOrigin
| GCTileStipYOrigin
#endif
| GCGraphicsExposures,
&gcvalues);

XSetForeground(dpy,
gc,
WhitePixel(dpy, DefaultScreen(dpy)));

XFillRectangle(dpy,
bufs[0],
gc,
0, 0, /* x, y /
BLIT_X, BLIT_Y); /
width, height */

XFillRectangle(dpy,
win,
gc,
0, 0,
640, 240);

XSetForeground(dpy,
gc,
BlackPixel(dpy, DefaultScreen(dpy)));

XFillRectangle(dpy,
bufs[1],
gc,
0, 0, /* x, y /
BLIT_X, BLIT_Y); /
width, height */

pixmapfmt = XListPixmapFormats(dpy, &i);

for(j = 0; j < i; j++) {
printf(“Pixmap format #%i\n”, j);
printf(" depth = %i\n", pixmapfmt[j].depth);
printf(" bits_per_pixel = %i\n", pixmapfmt[j].bits_per_pixel);
printf(" scanline_pad = %i\n", pixmapfmt[j].scanline_pad);
if(pixmapfmt[j].depth == DEPTH(dpy)) {
printf(" is default pixmap format\n");
bytes_per_pixel = pixmapfmt[j].bits_per_pixel / 8;
}
}

XFree(pixmapfmt);

while(1) {
XEvent ev;

XNextEvent(dpy, &ev);

if(ev.type == Expose) {
  printf("got Expose\n");
  break;
}

}

frames = 0;
buf = 0;

XSetGraphicsExposures(dpy,
gc,
False);

gettimeofday(&time1, NULL);

#ifdef GRAB_SERVER
XGrabServer(dpy);
#endif

while(1) {
#ifdef TILE_TRICK
XSetWindowBackgroundPixmap(dpy, win, bufs[buf]);
XClearArea(dpy, win, 0, 0, BLIT_X, BLIT_Y, False);
#else
XCopyArea(dpy,
bufs[buf],
win,
gc,
0, 0, /* src_x, src_y /
BLIT_X, BLIT_Y, /
width, height /
0, 0); /
dest_x, dest_y */
#endif

XSync(dpy, False);

if(XPending(dpy)) {
  XEvent ev;

  XNextEvent(dpy, &ev);

  if(ev.type == KeyPress && ev.xkey.keycode == 9)
break;
}

buf = 1 - buf;
frames++;

}

#ifdef GRAB_SERVER
XUngrabServer(dpy);
#endif

gettimeofday(&time2, NULL);

diff = (time2.tv_sec * 1000) + (time2.tv_usec / 1000);
diff -= (time1.tv_sec * 1000) + (time1.tv_usec / 1000);

printf(“frames = %i\n”, frames);
printf(“ms = %i\n”, diff);
printf(“bpp = %i\n”, bytes_per_pixel);
printf(“fps = %i\n”, (frames * 1000) / diff);

mbps = (frames * 1000) / diff;
mbps = mbps * (BLIT_X * BLIT_Y * bytes_per_pixel);
mbps = mbps / (1024 * 1024);

printf(“MB/sec = %f\n”, mbps);

XUnmapWindow(dpy, win);
XDestroyWindow(dpy, win);

XFreePixmap(dpy, bufs[0]);
XFreePixmap(dpy, bufs[1]);

XCloseDisplay(dpy);

return 0;
}

hayward at slothmud.org wrote:

server, and shared memory is garanteed not to give it to me. When I do
get good acceleration, I will have to hope that clip_mask is accelerated
too. :slight_smile:

Ok, refresh my memory. Do video cards provide accelerated color_key
blits, or do they provide accelerated clip_mask blits. I seem to remember
hearing about video cards supporting color-key blits, not clip-mask ones.
But I could be wrong here.

Most video cards supporting partial blitting supports only color-key, if
I remember correctly. You are right in saying that in your case, which
needs color-key ability, a pixmap will probably not suffice, no matter
how accelerated.

I am very sorry about this part, but since this isn’t part of the goals
I have at the moment, I will have to delay this part of the research. I
am pretty sure this will come up tho.–
Pierre Phaneuf
Ludus Design, http://ludusdesign.com/
“First they ignore you. Then they laugh at you.
Then they fight you. Then you win.” – Gandhi

hayward at slothmud.org wrote:

Again, from my original question, do your tests do any color-key
blitting. That was the primary point of my last email that seems to have
been overlooked in your reply.

I answered that one in another post.

I was just curious about your performance results, thats all. Because my
results, especially with color key blitting, are different. I wasn’t
really asking for a description of which method solves which problems.

All this while, I was thinking about the non-transparent 17 fps you told
me. Color-key blitting is a problem in X11. The only Xlib-native way to
do this is by using a clip_mask, which has unknown effects on
acceleration (which isn’t there yet! :frowning: ). The only other way is to mix
your own in a client-local buffer and blit the mixed result (which is
doing in two stages, with according performance penalty).

The best thing would be an extension that would let us specify the
color-key for an XCopyArea. DGA 2.0 has such a call, but it must require
DGA, obviously (but on the other hand, I think that this request is
executed on the server side, maybe we can trick DGA into doing it into a
regular window, I should look into that). DGA 2.0 is only available in
XFree86 3.9.x.

The problem with XCopyArea is that they only work with Drawables, and
you can’t access the pixel data of Drawables. XImage (with XPutImage)
let you access the pixel data, but is very slow.

chuckle not even slow, not if you know how to write a decent blit that
accesses the pixel data of your shared memory “backbuffer”, and you use
XShmPutImage() to spit the backbuffer to the screen. Thats how the people
who ported quake (earlier versions) to X did their stuff. Ok, it’s
slow if you compare it to DirectX, but its’ fast compared to the other X11
methods, especially for color-key.

I was talking about Xlib-only, without using any extension like XShm.
Any kind of “shared” stuff will prevent the data from being put in the
video memory and be fully accelerated, which is my current goal.

On another note, I didn’t try XDBE (double buffering extension) yet, but
jwz told me it was a real joke, and I didn’t see anybody else using it,
so I don’t have much faith into this (will try it soon anyway)…

I tried DBE and had poor performance from it. It just adds another layer
of copying in system memory, basically. It wasn’t providing hardware
double buffering when I tried it.

Ok, they are a joke. :-)–
Pierre Phaneuf
Ludus Design, http://ludusdesign.com/
“First they ignore you. Then they laugh at you.
Then they fight you. Then you win.” – Gandhi

G200/K6-3 400/128MB ram
Here’s what I get from your test program, Pierre:

Pixmap format #0
depth = 1
bits_per_pixel = 1
scanline_pad = 32
Pixmap format #1
depth = 16
bits_per_pixel = 16
scanline_pad = 32
is default pixmap format
got Expose
frames = 680
ms = 8770
bpp = 2
fps = 77
MB/sec = 45.117188–
Brian

hayward at slothmud.org wrote:

G200/K6-3 400/128MB ram
Here’s what I get from your test program, Pierre:

MB/sec = 45.117188

Okay, this is rather in the proportions I was expecting. Programmed I/O
scales in some proportion with the CPU speed, as it is done by the CPU.
Ideally, we’d have similar numbers because we have similar video cards,
the CPU speed difference shouldn’t affect the number much.

Rem is hacking a DirectX version of my test specifically made to work in
a comparable manner, which should be ready soon (Win32/DirectX is harder
than Unix/Xlib).–
Pierre Phaneuf
http://ludusdesign.com/

G200/K6-3 400/128MB ram
Here’s what I get from your test program, Pierre:
[…]
MB/sec = 45.117188

hmm… this is what i got from Millennium/Pentium200MMX/128MB RAM
with XFree86 3.9.16

Pixmap format #0
depth = 1
bits_per_pixel = 1
scanline_pad = 32
Pixmap format #1
depth = 4
bits_per_pixel = 8
scanline_pad = 32
Pixmap format #2
depth = 8
bits_per_pixel = 8
scanline_pad = 32
Pixmap format #3
depth = 15
bits_per_pixel = 16
scanline_pad = 32
Pixmap format #4
depth = 16
bits_per_pixel = 16
scanline_pad = 32
is default pixmap format
Pixmap format #5
depth = 24
bits_per_pixel = 32
scanline_pad = 32
got Expose
frames = 362
ms = 2594
bpp = 2
fps = 139
MB/sec = 81.445312From: hayward@slothmud.org
Subject: Re: [SDL] Re: Xlib stuff
Date: Tue, 4 Jan 2000 22:18:30 -0600 (CST)

Yasushi Shoji wrote:

Pixmap format #4
depth = 16
bits_per_pixel = 16
scanline_pad = 32
is default pixmap format
Pixmap format #5
depth = 24
bits_per_pixel = 32
scanline_pad = 32
got Expose
frames = 362
ms = 2594
bpp = 2
fps = 139
MB/sec = 81.445312

Hmm, a lot of pixmap formats! Are you in 16 bits per pixel or in 32 bits
per pixel? Good performance by the way!–
Pierre Phaneuf
http://ludusdesign.com/

Hmm, a lot of pixmap formats! Are you in 16 bits per pixel or in 32 bits
per pixel? Good performance by the way!

I usually use 24bits but downed to 16 to do the test :slight_smile:

Pixmap format #4
depth = 16
bits_per_pixel = 16
scanline_pad = 32
is default pixmap format
^^^^^^^^^^^^^^^^^^^^^^^^

now I’m in 24bits and the test shows me

MB/sec = 86.718750From: pp@ludusdesign.com (Pierre Phaneuf)
Subject: [SDL] Re: Xlib stuff
Date: Wed, 05 Jan 2000 07:56:34 -0500


yashi

hmm… this is what i got from Millennium/Pentium200MMX/128MB RAM
with XFree86 3.9.16

Wow, huge difference… I’ll have to get 3.9.x–
Brian

Yasushi Shoji wrote:

Hmm, a lot of pixmap formats! Are you in 16 bits per pixel or in 32 bits
per pixel? Good performance by the way!

I usually use 24bits but downed to 16 to do the test :slight_smile:

Okay, good! Because normally, fewer pixmap formats are available…
Still, 80-something MB/sec is much better than what you’d get with
3.3.x. This number looks like hardware accelerated system to video
blitting.

It is also very similar to the system->video number my DirectX-hacking
colleague gave me for his Voodoo Banshee on a Pentium 200 MMX, so this
is looking very good in the just-as-fast-as-DirectX department!

Raster told me that by killing a few other X clients that were “breaking
the pipeline” (presumably by having their pixmaps fill up the video
memory), he could get in the 200 MB/sec range with my test! And he’s
running Xinerama!

If you use the change the #undef on the GRAB_SERVER line to a #define,
do you see an improvement?

now I’m in 24bits and the test shows me

MB/sec = 86.718750

Yes, the MB/sec number should stay pretty stable for the same hardware,
indenpendently of the color depth (the frame rate will change).

It seems performance gets much better by using XFree86 3.9.x… When is
the 4.0 version supposed to get out? :-)–
Pierre Phaneuf
Ludus Design, http://ludusdesign.com/
“First they ignore you. Then they laugh at you.
Then they fight you. Then you win.” – Gandhi