Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length

 
Advanced search

1375894 Posts in 65195 Topics- by 57444 Members - Latest Member: Rendah Games

April 21, 2020, 10:04:38 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Allegro game loop horrors
Pages: [1]
Print
Author Topic: Allegro game loop horrors  (Read 2927 times)
impulse9
Guest
« on: November 26, 2012, 07:29:23 AM »

I'm having a bit of a problem concerning the main game loop code in my engine. Long story short, I wrote a (relatively simple) tile-based game engine in C++ using Allegro 4.4 and AllegroGL. Now that it's actually close to being finished (design-wise at least), I found a bug involving fullscreen rendering and it is giving me a headache ever since. I'll try to explain what the problem is as clearly as I can, or what I've managed to make of it so far.

Simply put, the problem is that the game stutters when in fullscreen mode. It feels like rendering is falling behind on logic, and it has to keep up by kind of "shaking" the screen every few moments. It's subtle, but noticeable. I have a scrolling screen mechanism in my engine/game, and the effect is rather obvious when the game screen is moving about (this is why I never noticed this until I actually had a huge amount of work already done). It is interesting that this only happens in fullscreen. In windowed mode the rendering is perfect.

After about two weeks of investigating this from many angles, I'm pretty sure now that the problem has something to do with Allegro timers and my main loop code.

This is my main game loop (simplifed to highlight essential parts):

Code:
bool quitSignal = false;

do {

while (ticks == 0)
rest(1); // yield CPU time

while (ticks > 0)
{
const unsigned int old_ticks = ticks;

// logic step

if (quitSignal = DoLogic())
break;

ticks--;

if (old_ticks <= ticks)
break;
}

// draw step
allegro_gl_set_allegro_mode();
RenderToScreen();
allegro_gl_unset_allegro_mode();
allegro_gl_flip();

} while (!quitSignal);


Here is how I initialize the timer:

Code:
install_timer();
LOCK_VARIABLE(ticks);
LOCK_FUNCTION(Ticker);
install_int_ex(Ticker, BPS_TO_TIMER(60));

And this is the callback function that increments the ticks variable:

Code:
volatile unsigned int ticks = 0;

void Ticker()
{
ticks++;
} END_OF_FUNCTION(Ticker)

Basically this is the approach recommended in this article. For the most part, it seems to be working perfectly.

This probably isn't the issue here, but here is how I initialize AllegroGL (i've tried many variations of this as well):

Code:
allegro_gl_set(AGL_DOUBLEBUFFER, TRUE);
allegro_gl_set(AGL_COLOR_DEPTH, desktop_color_depth());
allegro_gl_set(AGL_WINDOW_X, (screenW - w)/2);
allegro_gl_set(AGL_WINDOW_Y, (screenH - h)/2);
allegro_gl_set(AGL_SUGGEST, AGL_DOUBLEBUFFER | AGL_COLOR_DEPTH | AGL_WINDOW_X | AGL_WINDOW_Y);

And the fullscreen switching code:

Code:
if (set_gfx_mode((fullscreen)?GFX_OPENGL_FULLSCREEN:GFX_OPENGL_WINDOWED,w,h,0,0) < 0)
{
// ... error checking ...
}




Things I tried:



- I tried a variant of the game loop that uses a redraw flag to optimize drawing instead of breaking the inner loop if it takes too long, it behaves almost exactly like the one above:

Code:
bool quitSignal = false;
bool redraw = true;

do {
while (ticks > 0)
{
// logic step

if (quitSignal = DoLogic())
break;

ticks--;

redraw = true;
}

if (redraw)
{
// draw step
allegro_gl_set_allegro_mode();
RenderToScreen();
allegro_gl_unset_allegro_mode();
allegro_gl_flip();

redraw = false;
}

} while (!quitSignal);

- I tried having a separate "frames" variable in my main loop code and checked against ticks like so:

Code:
while (frames < ticks) { ... frames++; }

Supposedly it's not a good idea to change the ticks variable from two places because the operations might be executed at once in two threads. However, this didn't solve my problem and in fact didn't seem to have any effect.

- I tried creating a separate thread that would redraw the screen while logic ran at the same time. I stupidly assumed that somehow this would solve the problem. It didn't.

- I tried switching off hardware acceleration and commented out all the lines concerning AllegroGL. Interestingly enough, the game ran smoothly but this introduced a whole range of new problems. Now, when in fullscreen and with camera scrolling around, the game would either show a tearing scan-line that went from the bottom of the screen toward the top in a kind of regular pattern, or, if I called vsync() every redraw, it kind of lagged like before, only worse and with a longer period. The game did run quite smooth though. If everything else fails, this is my last resort I guess.

- I tried to rearrange the main loop code in many mysterious ways. I won't go into details here because I tried too many things. Strangely enough, this code does both windowed and fullscreen rendering perfectly (so this gives me reason to believe that it is possible Tongue):

Code:
bool quitSignal = false;

do {
while (ticks > 0)
ticks--;

// logic step
if (quitSignal = DoLogic())
break;

// draw step
allegro_gl_set_allegro_mode();
RenderToScreen();
allegro_gl_unset_allegro_mode();
allegro_gl_flip();

} while (!quitSignal);

But now the problem is that it doesn't run with constant speed across different PCs. It runs fine on my desktop PC, on my laptop it runs too fast and in a test VM it runs too slow. It also doesn't respond properly if I make changes to timer BPS. Which I guess it shouldn't. I thought I understood something about game loops, but I have no clue really why the code above won't run in constant speed.


Things I didn't try:


- I didn't try implementing the loop using semaphores, as suggested in this article. I don't have much hope that this would solve the problem though.



So it seems like I have two choices: either to have smooth fullscreen rendering, or to have the game run at constant time. The solution that would make both work eludes me.

Anyway sorry for the lengthy post. I'm hoping that there are some Allegro ninjas here that would help me solve this before I go completely mad. Smiley
Logged
Udderdude
Level 10
*****



View Profile WWW
« Reply #1 on: November 26, 2012, 07:58:00 AM »

1. Did you post this in the Allegro forums/IRC yet?

2. Allegro 4.4 and AllegroGL are somewhat new, aren't they?  You might want to confirm it's not an Allegro related bug.

3. Sure it's not a vsync issue?

I've worked with Allegro extensively, but I have yet to delve into Allegro 4.4 or AllegroGL, good luck finding a solution. D:
Logged

Evan Balster
Level 10
*****


I live in this head.


View Profile WWW
« Reply #2 on: November 26, 2012, 08:17:57 AM »

Your system adds one to the number of frames "left to do" 60 times a second, even while working through them in the main thread.  Suppose a frame takes 1/59 of a second to compute logic for, and your system is munching through them.  They pile up slightly faster than they can be processed, and the game only renders when you're lucky enough to have a frame run quickly.

My advice is never to let more than 2-3 of those "pile-up" frames accrue -- if you're using more CPU than is available, there's no way you're not going to fall behind with fixed-timestep physics.  And don't take that as an endorsement for variable-timestep physics -- those cause even more problems.

Also yes, you should definitely avoid reading _and_ writing "ticks" (or any unprotected variable) from two separate threads.  While it's not the most pragmatic thing to do, an easier and more performance-friendly solution than locking is to mark the "ticks" variable volatile, have it count up in the timer thread, and have a separate variable in the main thread which is incremented each timestep until it's equal to "ticks".  Make that variable skip to the end after two or three steps to avoid the pileup issue.

(if the semaphore technique that has been suggested does not involve any actual blocking, ignore the advice with the volatile variable -- just know that using blocking code can contribute subtly to hitching in games)
Logged

Creativity births expression.  Curiosity births exploration.
Our work is as soil to these seeds; our art is what grows from them...


Wreath, SoundSelf, Infinite Blank, Cave Story+, <plaid/audio>
impulse9
Guest
« Reply #3 on: November 26, 2012, 08:26:25 AM »

1. Did you post this in the Allegro forums/IRC yet?
I've been to #allegro on freenode, a guy there suggested I use a separate "frames" variable which at first I thought it works but basically what I did was the same as in my last example above. After I fixed it I found out that the problem remains. I didn't post it on the forums yet.

2. Allegro 4.4 and AllegroGL are somewhat new, aren't they?  You might want to confirm it's not an Allegro related bug.
I've worked with Allegro 4.2 but I used software rendering. This is the first time I'm using AllegroGL and for the most part I was really satisfied with the result. This is the only real problem I've had with it. I hope it's not Allegro or even compiler related, as that will be quite difficult to overcome.

3. Sure it's not a vsync issue?
Pretty sure as allegro_gl_flip() does the sync as well. If I use software rendering, then vsync() makes everything lag horribly. Also it messes up with the timing routines.


@Evan Balster: Thanks for advice. I am ever so slightly running out of options here. Is there a known game loop implementation that fixes this frame skipping problem?

Edit: sorry, I didn't read your post well enough. I'll give it a shot when I have some time on my hands.

« Last Edit: November 26, 2012, 08:41:17 AM by impulse9 » Logged
vids
Level 0
***


View Profile WWW
« Reply #4 on: November 26, 2012, 10:47:07 AM »

Allegro 5 was officially released on Allegrocc quite a while back and supports full OpenGL functionality. AllegroGL only goes up to 4.4 stable, and I don't think it's under active development anymore, since it's whole purpose was to merge with Allegro, which it pretty much has now.

I've updated a 4.2 project to 5 once, a time consuming process but no OpenGL was involved.
Logged

impulse9
Guest
« Reply #5 on: November 26, 2012, 01:25:51 PM »

Apparently this code does the trick.

Code:
bool quitSignal = false;
frames = 0;

do {

while (frames < ticks)
{
const unsigned int old_ticks = ticks;

// logic step

if (quitSignal = DoLogic())
break;

frames++;

if (old_ticks <= ticks)
break;

}

// draw step
allegro_gl_set_allegro_mode();
RenderToScreen();
allegro_gl_unset_allegro_mode();
allegro_gl_flip();

} while (!quitSignal);

Coupled with some logic that sets the frame variable to ticks when the game goes into fullscreen (this is to prevent some odd happenings that cause the game to skip a few frames to "catch up" with game speed), this approach seems to work. I tested it on 3 machines just now and it looked promising. It ran smooth and in constant time in both fullscreen and windowed modes.

I'm still worried that this might backfire somehow. But for now I think I'm satisfied. Thanks to everyone for your help, especially Evan Balster.
Logged
Evan Balster
Level 10
*****


I live in this head.


View Profile WWW
« Reply #6 on: November 26, 2012, 02:45:10 PM »

I implemented this sort of system in Cave Story+, and as far as I've seen it behaves itself well.  When the game performs poorly it skips every other frame, which is ugly but preserves the flow of gameplay.  The technique's weakness is that it does little to address lag that comes from game logic -- to do so it'd have to be able to make cutbacks in game logic which are beyond the scope of our discussion here.
Logged

Creativity births expression.  Curiosity births exploration.
Our work is as soil to these seeds; our art is what grows from them...


Wreath, SoundSelf, Infinite Blank, Cave Story+, <plaid/audio>
impulse9
Guest
« Reply #7 on: November 28, 2012, 06:19:44 AM »

The code I posted above doesn't work properly because it allows for delta time (time between ticks and frames) to increase indefinitely, which causes all sort of funky time dilation (it took me a while and a lot of testing to confirm this effect).

More things I tried:

- I tried separating the code that drew to the backbuffer from the code that did only logic. I thought this was maybe the bottleneck of my program. Before this I drew into the backbuffer in the logic loop itself, but I separated logic and drawing routines long ago, so the loop just kind of coupled the two. Separating them was very easy to accomplish, but had no observable effect. I thought it made more sense like this so I kept it anyway.

- I tried implementing triple buffer rendering (without AllegroGL of course), it took me a while but I gave up soon after since there was a big problem with video bitmaps that would cause parts of screen to blink, and I could only solve this by vsyncing the scene every redraw (which slowed everything down again). It's likely that I implemented it poorly or that it is not compatible with the rest of my code.

- I tried to mess around with compiler settings, needless to say this was a waste of time.

- I considered porting the whole thing to Allegro 5. After some reading on how that is supposed to be done, I found out that this would probably take me a great deal of time and would require me to rewrite huge chunks of code. And it is questionable whether this would solve the problem anyway. So I gave up with this plan.

- I tried modelling the loop after example Allegro code. This had colorful results but nothing that would make the annoying fullscreen lag go away completely. In one instance, I managed to reverse the situation somehow - it was working smoothly in fullscreen and laggy in windowed mode. WTF

- After what seemed like an eternity of googling, I found this. I saw the poster there used a different approach with coding the main loop, so I gave it a shot (I thought, why the hell not, I tried every other possible thing anyway). My code is now something like this:

Code:
bool quitSignal = false;
unsigned int frames = 0;

do {

// perform black magic
while (!ticks)
{
rest(0);
}

// logic step
if (quitSignal = HandleLogic())
break;

ticks = 0;
frames++;


// draw step
HandleDrawing();

#if defined(_USE_AGL)
allegro_gl_set_allegro_mode();
#else
vsync();
#endif
RenderToScreen();

#if defined(_USE_AGL)
allegro_gl_unset_allegro_mode();
allegro_gl_flip();
#endif


} while (!quitSignal);

I don't know how it works, I don't know why it works, I have no idea why the other solutions didn't work, but I don't care anymore. All I know is that it finally, really, without a doubt, works. Even though it introduces some minor flickering on lower-end hardware, it does in fact run in constant speed. I've tested this extensively because that's basically the primary goal I had in mind. Second, the rendering is smooth as butter most of the time. Most of the time because sometimes it will still kind of lag for a short while, but this happens very rarely and for once, not in a regular pattern. I can live with that.

This has been an extremely traumatic experience. Tongue I hope I can finally proceed with the actual game and leave this horrible episode behind.



Edit:

For the love of everything that is holy ...

Quote from: nVidia driver updates
Key Fixes
  • Fixes an intermittent vsync stuttering issue with GeForce GTX 600-series GPUs.

Edit2:

129060 ticks of smoothly rendered tilemap, and counting.

Edit3:

I'm an idiot. Disregard this topic.
« Last Edit: December 03, 2012, 04:20:41 AM by impulse9 » Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic