Double Buffering

My view on double buffering:

it is nice but not everyone does have enough memory for a given
video mode. Page flipping is ‘time cheap’, but if you just replace
it with copying a system memory to the video memory, it’ll slow down
a lot. Add ‘dirty rectangle’ buffering (only modified parts of the
backbuffer get updated on the screen…)

Most applications have specific needs.
An application like asteroids, you only need to update small areas,
you almost always want to do dirty rectangle updates.
An application like DOOM, you’re doing full-screen updates and almost
always want to do double-buffering. Generally, the advantage of double
buffering is the removal of “tearing” artifacts caused by the redrawing
of the video screen while the memory is being changed. Video memory is
often too slow to render to directly in 2D – usually you render in
system memory and do a fast (possibly hardware accelerated) block copy
to video memory.

Note that this will probably change in the near future as memory architectures
and video card architectures improve.

In the SDL implementation, I’ll have a SDL_FLIP flag along with a
SDL_FLIPHW flag. If you specify SDL_FLIPHW, the video mode initialization
will not succeed unless hardware flipping can be enabled, otherwise flipping
will be emulated by a full surface copy.

I’m still open to suggestions.

See ya!
-Sam Lantinga (slouken at devolution.com)–
Author of Simple DirectMedia Layer -
http://www.devolution.com/~slouken/SDL/

usually you render in system memory and do a fast (possibly hardware
accelerated) block copy to video memory.

I usually don’t :slight_smile:

Anyway, in doom, you could only have ONE dirty rectangle [0,0,320,200]

My dirty rect. routines are supposed to optimize the rectangle list before it is
copied, so (almost) nothing is copied twice or more…
I’ll upload it somewhere soon if anyone wants to see it…–
------------------------ Tomas Andrle ~ Virsoft — @Tomas_Andrle -------------

Anyway, in doom, you could only have ONE dirty rectangle [0,0,320,200]

You’d still have tearing because the copy wouldn’t be synchronized with
the vertical retrace.

My dirty rect. routines are supposed to optimize the rectangle list before it is
copied, so (almost) nothing is copied twice or more…
I’ll upload it somewhere soon if anyone wants to see it…

Cool. :slight_smile:

See ya!
-Sam Lantinga (slouken at devolution.com)–
Author of Simple DirectMedia Layer -
http://www.devolution.com/~slouken/SDL/

Sam Lantinga wrote:

Anyway, in doom, you could only have ONE dirty rectangle [0,0,320,200]

You’d still have tearing because the copy wouldn’t be synchronized with
the vertical retrace.

My dirty rect. routines are supposed to optimize the rectangle list before it is
copied, so (almost) nothing is copied twice or more…
I’ll upload it somewhere soon if anyone wants to see it…

Cool. :slight_smile:

See ya!
-Sam Lantinga (slouken at devolution.com)


Author of Simple DirectMedia Layer -
http://www.devolution.com/~slouken/SDL/

here it is (I don’t really think it’s worth the bandwidth…)

–8<–cut to the end of the message–8<–

/*
! not tested yet !
( I don’t have anything to make the screen dirty with yet )

anyone to do this in 3d ?? :-)))

*/

#include <stdlib.h> // NULL

#define X1 0
#define Y1 1
#define X2 2
#define Y2 3

typedef struct DirtyRect
{
int x1;
int y1;
int x2;
int y2;
struct DirtyRect
*prev;
struct DirtyRect
*next;
} DirtyRect;

enum
{
lineclip_none_left,
lineclip_none_right,
lineclip_left,
lineclip_right,
lineclip_inside, // b contains a
lineclip_outside // a contains b
};
enum
{
lineclip_none_up,
lineclip_none_down,
lineclip_up,
lineclip_down
/* … */
};

static DirtyRect
*allocated = NULL; // array, not a linked list here
static int
default_allocate = 2000,
allocated_count = 0;
static DirtyRect
*free_rect = NULL;

void dirty_init()
{
int
i;

allocated = ( DirtyRect* ) malloc( sizeof( DirtyRect ) * default_allocate );
allocated_count = default_allocate;
if ( allocated_count==0 )
	return;
	// how silly...

free_rect = allocated;
allocated[0].next = &allocated[1];
allocated[0].prev = NULL;
for ( i=1; i<allocated_count - 1; i++ )
{
	allocated[i].next = &allocated[i+1];
	allocated[i].prev = &allocated[i-1];
}
allocated[allocated_count].prev = &allocated[allocated_count-1]; //

allocated_count-1 == i
allocated[allocated_count].next = NULL;
}

void dirty_exit()
{
free( ( void* ) allocated );
free_rect = NULL;
allocated_count = 0;
}

DirtyRect *dirty_new()
{
DirtyRect
* d;

if ( free_rect==NULL )
	return NULL;
	// FIXME allocate more (and transfer all
	// older dirty rects into the newly allocated area)
else
{
	d = free_rect;
	free_rect = free_rect->next;
	free_rect->prev = NULL;
	d->next = NULL;
	return d;
}

}

void dirty_free( DirtyRect *d )
{
d->next = free_rect;
free_rect->prev = d;
d->prev = NULL;
free_rect = d;
}

static int lineclip( int a1, int a2, int b1, int b2 )
/* a1 >= a2
b1 >= b2 */
{
if ( a1 < b1 )
{
if ( a2 < b1 )
return lineclip_none_left;
else
{
if ( a2 < b2 )
return lineclip_left;
else
return lineclip_outside;
}
}
else
{
if ( a1 < b2 )
{
if ( a2 < b2 )
return lineclip_inside;
else
return lineclip_right;
} else
return lineclip_none_right;
}
}

/* this is horrible but it should work /
/
it’s not pixel perfect yet (some overlaps remain) */
void dirty_optimize( DirtyRect *d, DirtyRect *dlist )
{
DirtyRect
*d2, *diter;

diter = dlist;
while ( ( d != NULL ) && ( diter != NULL ) )
{
	switch ( lineclip( d->x1, d->x2, diter->x1, diter->x2 ) )
	{
		case lineclip_none_left:
		case lineclip_none_right:
			// no contact, quit
			break;
		case lineclip_inside:
			switch ( lineclip( d->y1, d->y2, diter->y1, diter->y2 ) )
			{
				case lineclip_none_up:
				case lineclip_none_down:
					// no contact, quit
					break;
				case lineclip_inside:
					d = NULL;
					break;
				case lineclip_outside:
					d2 = dirty_new();
					d2->x1 = d->x1;
					d2->x2 = d->x2;
					d2->y1 = d->y1;
					d2->y2 = diter->y1;
					d->y1 = diter->y2;
					dirty_optimize( d2, diter );// yes, start at diter
					// and go on with d now
					break;
				case lineclip_up:
					d->y2 = diter->y1;
					break;
				case lineclip_down:
					d->y1 = diter->y2;
					break;
			}
			break;
		case lineclip_outside:
			switch ( lineclip( d->y1, d->y2, diter->y1, diter->y2 ) )
			{
				case lineclip_none_up:
				case lineclip_none_down:
					// no contact, quit
					break;
				case lineclip_inside:
					d2 = dirty_new();
					d2->x1 = d->x1;
					d2->x2 = diter->x1;
					d2->y1 = d->y1;
					d2->y2 = d->y2;
					dirty_optimize( d2, diter ); // yes, start at diter
					// and go on with d now
					break;
				case lineclip_outside:
					if ( diter->next != NULL )
						diter->next->prev = diter->prev;
					if ( diter->prev != NULL )
						diter->prev->next = diter->next;
					break;
				case lineclip_up:
					diter->y1 = d->y2;
					break;
				case lineclip_down:
					diter->y2 = d->y1;
					break;
			}
			break;
		case lineclip_left:
			switch ( lineclip( d->y1, d->y2, diter->y1, diter->y2 ) )
			{
				case lineclip_none_left:
				case lineclip_none_right:
					// no contact, quit
					break;
				case lineclip_inside:
					d->x2 = diter->x1;
					break;
				case lineclip_outside:
					diter->x1 = d->x2;
					break;
				case lineclip_up:
					// two equal ways how to split the shit
					d2 = dirty_new();
					d2->x1 = d->x1;
					d2->x2 = diter->x1;
					d2->y1 = d->y1;
					d2->y2 = d->y2;
					d->x1 = diter->x1;
					d->y2 = diter->y1;
					dirty_optimize( d2, diter ); // yes, start at diter
					// and go on with d now
					break;
				case lineclip_down:
					// two equal ways how to split the shit
					d2 = dirty_new();
					d2->x1 = d->x1;
					d2->x2 = diter->x1;
					d2->y1 = d->y1;
					d2->y2 = d->y2;
					d->x1 = diter->x1;
					d->y1 = diter->y2;
					dirty_optimize( d2, diter ); // yes, start at diter
					// and go on with d now
					break;
			}
			break;
		case lineclip_right:
			switch ( lineclip( d->y1, d->y2, diter->y1, diter->y2 ) )
			{
				case lineclip_none_left:
				case lineclip_none_right:
					// no contact, quit
					break;
				case lineclip_inside:
					d->x1 = diter->x2;
					// FIXME
					break;
				case lineclip_outside:
					diter->x2 = d->x1;
					// FIXME
					break;
				case lineclip_up:
					// FIXME
					break;
				case lineclip_down:
					// FIXME
					break;
			}
			break;
	}
	diter = diter->next;
}

}

Hello,

It seems that double buffering on full-screen under X (DGA) is extremely
slow. Is there something to do to avoid it? Is it due to my hardware or
is there the same problem in every configuration?

Thank you.

What is the recommended way to double buffer an application, in the
absence of hardware support? Draw into one screen (offscreen), then blit
that to the screen once (at the end of the event loop)? Is Maelstrom the
best example, or should I be looking at another app?–
Marc Lepage
Software Developer
Molecular Mining Corporation
http://www.molecularmining.com/

What is the recommended way to double buffer an application, in the
absence of hardware support? Draw into one screen (offscreen), then blit
that to the screen once (at the end of the event loop)? Is Maelstrom the
best example, or should I be looking at another app?

When you create a surface with SDL_SetVideoMode(), you can specify that
you want it to be double-buffered.

For example:

screen = SDL_SetVideoMode(640, 480, 16, SDL_DOUBLEBUF);

/* draw stuff onto screen /
/
(don’t use any SDL_UpdateRect() functions!) */

SDL_Flip(screen);

I assume that in the absense of hardware support, that SDL does this with
software. Am I right?

-bill!

What is the recommended way to double buffer an application, in the
absence of hardware support? Draw into one screen (offscreen), then blit
that to the screen once (at the end of the event loop)? Is Maelstrom the
best example, or should I be looking at another app?

Create a display surface (SDL_SetVideoMode()) with the SDL_DOUBLEBUF flag.
Write directly to video memory (or offscreen memory if real double buffering
isn’t supported), and then call SDL_Flip() at each frame end.

If you know that you can optimize the drawing when real double-buffering
support isn’t available, you should check the flags in the surface returned
by SDL_SetVideoMode() and act accordingly.

The testsprite example has pretty good coverage of all of this.

See ya!
-Sam Lantinga (slouken at devolution.com)

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

When you draw to your main window, you are in fact drawing to a
buffer I believe… thats why you call “SDL_UpdateRect()” to transfer
data from the “backbuffer” to the screen.

It uses hardware double buffering if it exists on a platform, from what
I’ve heard.–
Brian Hayward

What is the recommended way to double buffer an application, in the
absence of hardware support? Draw into one screen (offscreen), then blit
that to the screen once (at the end of the event loop)? Is Maelstrom the
best example, or should I be looking at another app?


Marc Lepage
Software Developer
Molecular Mining Corporation
http://www.molecularmining.com/

ok, pardon me… your information was a bit more accurate…

Am I correct… In X, hardware double buffering is not supported?
I did read that on platforms where double-buffering is not supported, it
just calls SDL_UpdateRect() updating the entire screen anyway.

In cases where hardware double buffering is not supported… It may be
much more efficient to update only a smaller section of the screen when
necessary.

For example, an 800x600 screen. Gameplay is a smaller viewport (say
640x480). The gameplay area needs fast updates, so updating it with
SDL_UpdateRect() would be faster in software than doing double-buffering
on the entire screen in software?

Like in doom, updating the “stats” or “life” areas would need only updated
on a less frequent basis. And in those cases, you can call
SDL_UpdateRect() on just the small updated region.–
Brian

When you create a surface with SDL_SetVideoMode(), you can specify that
you want it to be double-buffered.

For example:

screen = SDL_SetVideoMode(640, 480, 16, SDL_DOUBLEBUF);

/* draw stuff onto screen /
/
(don’t use any SDL_UpdateRect() functions!) */

SDL_Flip(screen);

I assume that in the absense of hardware support, that SDL does this with
software. Am I right?

-bill!

Yeah, do it offscreen and then blit on screen… a better way I think is
to only blit the stuff that needs to be changed and leave everything else
the same.On Tue, 21 Dec 1999, Marc Lepage wrote:

What is the recommended way to double buffer an application, in the
absence of hardware support? Draw into one screen (offscreen), then blit
that to the screen once (at the end of the event loop)? Is Maelstrom the
best example, or should I be looking at another app?


Marc Lepage
Software Developer
Molecular Mining Corporation
http://www.molecularmining.com/

hayward at slothmud.org wrote:

When you draw to your main window, you are in fact drawing to a
buffer I believe… thats why you call “SDL_UpdateRect()” to transfer
data from the “backbuffer” to the screen.

That’s what I’m doing now, except that many of the functions I use
update the screen after they’ve done their work. For example, it may
blit an image then update that region.

Therefore, I get lots of flicker as the screen (visible, that the end
user sees) is built up layer by layer.

You are saying I should remove all those intermediate update rect calls?
What if they are in a lib? I’m using SDL Console and SGE. [I suppose I will have to investigate for lower level functions that don’t update the screen directly.]–
Marc Lepage
http://www.antimeta.com/
Minion open source game, RTS game programming, etc.

Put all the regions you need to update in an array, and then call
update_recs and pass that array at the very end…On Tue, 21 Dec 1999, Marc Lepage wrote:

hayward at slothmud.org wrote:

When you draw to your main window, you are in fact drawing to a
buffer I believe… thats why you call “SDL_UpdateRect()” to transfer
data from the “backbuffer” to the screen.

That’s what I’m doing now, except that many of the functions I use
update the screen after they’ve done their work. For example, it may
blit an image then update that region.

Therefore, I get lots of flicker as the screen (visible, that the end
user sees) is built up layer by layer.

You are saying I should remove all those intermediate update rect calls?
What if they are in a lib? I’m using SDL Console and SGE. [I suppose I will have to investigate for lower level functions that don’t update the screen directly.]


Marc Lepage
http://www.antimeta.com/
Minion open source game, RTS game programming, etc.

Sam, do you know if X supports this while in windowed mode ?
Or is the only way to get hardware double buffering (on capable GFX cards)
to switch to fullscreen ?

If hw doublebuffering isn’t supported in windowed mode,
does anyone know if this will be supported under XFree4.0 ?

cheers,
Benno.On Tue, 21 Dec 1999, Sam Lantinga wrote:

What is the recommended way to double buffer an application, in the
absence of hardware support? Draw into one screen (offscreen), then blit
that to the screen once (at the end of the event loop)? Is Maelstrom the
best example, or should I be looking at another app?

Create a display surface (SDL_SetVideoMode()) with the SDL_DOUBLEBUF flag.
Write directly to video memory (or offscreen memory if real double buffering
isn’t supported), and then call SDL_Flip() at each frame end.

If you know that you can optimize the drawing when real double-buffering
support isn’t available, you should check the flags in the surface returned
by SDL_SetVideoMode() and act accordingly.

Or is the only way to get hardware double buffering (on capable GFX cards)
to switch to fullscreen ?

That’s correct, on all operating systems that I know of.

If hw doublebuffering isn’t supported in windowed mode,
does anyone know if this will be supported under XFree4.0 ?

I don’t think so.

-Sam Lantinga				(slouken at devolution.com)

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

You are saying I should remove all those intermediate update rect calls?
What if they are in a lib? I’m using SDL Console and SGE. [I suppose I will have to investigate for lower level functions that don’t update the screen directly.]

I’m not sure about SDL Console, but SGE has an option to turn off updating
by default in all of their functions:

sge_Update_OFF() i believe is the function you should call.

then you must call SDL_UpdateRect yourself (or the sge_ equivalent).–
Brian

hayward at slothmud.org wrote:

You are saying I should remove all those intermediate update rect calls?
What if they are in a lib? I’m using SDL Console and SGE. [I suppose I will have to investigate for lower level functions that don’t update the screen directly.]

I’m not sure about SDL Console, but SGE has an option to turn off updating
by default in all of their functions:

sge_Update_OFF() i believe is the function you should call.

then you must call SDL_UpdateRect yourself (or the sge_ equivalent).

I just discovered this 2 minutes ago. Now there is no flicker, and fps
increased from 15 to 22.

That’s still very low, considering I’m hardly drawing anything, and
I’d like to get 20fps when all is said and done.

Do people find span lists a good way to increase FPS? Where can I find
out more about SDL’s RLE support?

Is there a FAQ for obtaining the highest fps in SDL? Things like:

a) convert surface to native format etc.
b) try to use hardware accelerations (these platforms work: foo, bar,
…)
c) otherwise do this…–
Marc Lepage
Software Developer
Molecular Mining Corporation
http://www.molecularmining.com/

I wrote:

That’s still very low, considering I’m hardly drawing anything, and
I’d like to get 20fps when all is said and done.

Here’s what I’m doing.

You can view a screenshot (640x480) at
http://www.antimeta.com/projects/minion/. It is an RTS game similar to
StarCraft. See the gallery at
http://www.antimeta.com/projects/minion/gallery.html for more complete
screenshots of my older GTK+ version to get a further handle on what I’m
doing.

I need to draw the various areas of the screen. The main view (480x480
on the left) is composed of 32x32 tiles; I need to iterate over the map
drawing each (they won’t all be grass). Eventually, I would like to
blend tiles, for example grass into dirt. After the terrain is drawn, I
optionally draw grid lines.

Then I draw buildings, units, etc. At the moment, units are circles:
points with a radius and orientation. Eventually they will be graphics
facing one of, say, 16 directions. Selections and other interface
decorations are drawn on top.

So, drawing in the main view is layered. The naive (painter’s) algorithm
results in overdraw, of course. It’s not as complicated as it could be,
however, because the view is top-down (and not, for instance, at an
isometric angle).

The mini view is in the top right 160x160. Each pixel corresponds to a
tile on the map. To draw it, I iterate over the map, drawing a colour
depending on terrain type and whatever units/buildings/resources are
present.

The next lower box will likely contain selected units with health, etc.
That is, icons and bars.

The bottom right box may contain other stuff, probably status
information. I haven’t yet decided.

Fog of war will have to be handled in the main and mini views. It will
involve drawing unexplored tiles black, and explored but currently
un-seen tiles shaded. The latter case may draw upon alpha blending.

In short, is there a best way to draw this other than the naive method:
draw everything into a back buffer without update, then flip to
onscreen? That’s how I did it in GTK+. I didn’t have much of a problem
attaining 30fps for few visible units, 20fps for many. Yet, as you can
see from the screenshot, that particular window takes 25fps to draw
without computing game logic or drawing much game action.

The entire code that draws that window is in one file,
http://www.antimeta.com/projects/minion/main.cpp. It relies on SDL, SGE,
and SDL Console. [Comments appreciated. The sooner I can get these
issues out of the way, the sooner I can start getting the actual game
logic back in.]

Could it be my use of SGE and SDL Console that is so slow? My use of
BMPs (I haven’t converted images or anything)?

Thanks for all your help on this list! PS, any other RTS games done in
SDL?–
Marc Lepage
Software Developer
Molecular Mining Corporation
http://www.molecularmining.com/

Sam Lantinga wrote:

Or is the only way to get hardware double buffering (on capable GFX cards)
to switch to fullscreen ?

That’s correct, on all operating systems that I know of.

If hw doublebuffering isn’t supported in windowed mode,
does anyone know if this will be supported under XFree4.0 ?

I don’t think so.

Actually, if you have a video card that does overlays, I think DirectX
can do hardware double-buffering in windowed mode. And XFree86 4.0 is
supposed to support overlays, but I don’t know what it will actually end
up meaning…–
Pierre Phaneuf
Ludus Design, http://ludusdesign.com/
“First they ignore you. Then they laugh at you.
Then they fight you. Then you win.” – Gandhi

In short, is there a best way to draw this other than the naive method:
draw everything into a back buffer without update, then flip to
onscreen? That’s how I did it in GTK+. I didn’t have much of a problem
attaining 30fps for few visible units, 20fps for many. Yet, as you can
see from the screenshot, that particular window takes 25fps to draw
without computing game logic or drawing much game action.

If you specify SDL_SWSURFACE when setting the video mode, you will
essentially be drawing into the back-buffer and then you can use SDL_Flip()
to update that on the screen. Your best bet is probably to draw into
that surface and call SDL_Flip(). Later you might look at optimizing
the rectangle updates.

I haven’t used SGI or the SDL console, so I don’t know how they affect
performance.

Thanks for all your help on this list! PS, any other RTS games done in
SDL?

There are a bunch on the games page, though I don’t think any of them
are finished. Civilization: Call To Power is probably the closest
finished game to what you’re doing.

-Sam Lantinga				(slouken at devolution.com)

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