[SDL] How to get constant framerates without busywaits ...

David Olofson david.olofson at reologica.se
Tue Mar 19 11:27:01 PST 2002


On Tuesday 19 March 2002 19:36, Stephen Anthony wrote:
[...]
> OK, you lost me again here :(  What are you considering a flip to be?
> Are you assuming double-buffering?  Because right now I don't use
> double-buffering.  Are you saying that this method will only work if
> the hardware supports retrace sync'ed flips?

A "flip" here can be whatever mentod you use of making a frame visible. 
It *will* work better if you're running at full frame rate with retrace 
sync, but it's not critical.


[...]
> How do I interpret this last table?  What does the 0 and 1 in the
> engine frame represent?  The fact that you should use buffer 0 or
> buffer 1?  Or that you should not / should do the next logic frame??

Sorry; it's "Engine frames" - simply the number of logic/engine frames to 
"run" before you render the output frame. You calculate this once per 
output frame, after checking the current time.


> Also, does this solve the problem of busy-waiting?  Where / how would
> you use a sleep call?

No. There's still no way to do that, short of retrace sync properly 
implemented in the driver.


> I'm sorry if I seem a bit dense here.  I really want to understand how
> this works.

It seems trivial to me, but it's still hard to explain - and I'm not 
particularly good at describing anything right now, it seems... *heh*


> Maybe you could provide a simple example in pseudocode?  I
> sort of understand what you're saying, I just don't know how to go
> about writing it :)

There are many ways to write it... Here's some code from the Spitfire 
Engine, used in Kobo Deluxe:

---8<-----------------------------------------------------------
void cs_engine_advance(cs_engine_t *e, float to_frame)
{
	if(to_frame > 0)
	{
		int frames = floor(to_frame) - floor(e->time);
		if(frames > 0)
		{
			while(frames--)
			{
				__run_all(e);
				e->on_frame(e);
			}
		}
	}
	e->time = to_frame;
	if(e->wx || e->wy)
		__wrap_all(e);
	__update_points(e, to_frame - floor(to_frame));
}
----------------------------------------------------------->8---

__run_all() updates all "object" (sprites and "points" for scrolling etc) 
positions, based on their velocity and acceleration values, evaluates 
collisions and stuff. (None of this is used in Kobo Deluxe.)

on_frame() is your logic/engine callback - this is where you hook your 
emulator frame() function in. (Actually, this is wrapped by the C++ API, 
so you "hook the callback in" by deriving from gfxengine_t and throwing 
in your own frame() method in your new class.)

__wrap_all() "fixes" object coordinates for wrapping levels, like those 
in Kobo Deluxe.

__update_points() implements the interpolation; this is where the actual 
graphics coordinates for all objects are calculated for each rendered 
frame.


---8<-----------------------------------------------------------
void gfxengine_t::run()
{
	open();
	show();
	start_engine();
	is_running = 1;
	while(is_running)
	{
		int tick = SDL_GetTicks() - start_tick;
		float toframe = (float)tick / ticks_per_frame;
		cs_engine_advance(csengine, toframe);
		pre_render();
		window->select();
		cs_engine_render(csengine);
		post_render();
		if(autoinvalidate)
			window->invalidate();
		flip();
	}
	stop_engine();
}
----------------------------------------------------------->8---

The first two lines in the loop is where I check the time, and translate 
that into a fractional time, expressed in logic frames. That is, the 
integer part is which frame I want to render, and the fractional part 
says how close we are to the *next* logic frame.

Next, I call cs_engine_advance() (above), to run the whole game logic 
until the right frame.

cs_engine_render() renders all objects (sprites) into the output 
"window", whereas pre_render() and post_render() are hooks for the game 
to render stuff before and after the objects are rendered, respectively. 
(Kobo Deluxe uses the first one for the background, and the second for 
overlay text, any debugging stuff and finally, the frame with the rounded 
corners.)

flip() can perform SDL_UpdateRects(), SDL_FlipSurface() and other stuff, 
depending on the selected engine "buffer mode".


//David Olofson --- Programmer, Reologica Instruments AB

.- M A I A -------------------------------------------------.
|      Multimedia Application Integration Architecture      |
| A Free/Open Source Plugin API for Professional Multimedia |
`----------------------------> http://www.linuxdj.com/maia -'
.- David Olofson -------------------------------------------.
| Audio Hacker - Open Source Advocate - Singer - Songwriter |
`-------------------------------------> http://olofson.net -'




More information about the SDL mailing list