Non-blocking VBL sync?

Sage
Posts: 1,199
Joined: 2004.10
Post: #1
All

I've recently discovered that my game performs *very* poorly on multiprocessor machines. It's fascinating, because my game is quite fast and responsive on a 1 Ghz G4, but actually performs *worse* on a dual 1.8 Ghz g5.

Now, I'm pretty certain I know why [though I could be wrong]. My game is naively multithreaded -- the app loop, via a timer set to run at max speed -- redraws the scene with VBL sync ( using CGLSetParameter with kCGLCPSwapInterval ). This means the app thread is blocking while waiting for the right moment to display.

So, while the app thread blocks, I've got a separate thread running the physics and game logic in the background. Since I'm using ODE for physics -- and since ODE doesn't play too well with multithreading -- I have a mutex which keeps the display & physics acting synchronously. So in other words, display will never be called while physics is running, and visa versa. I'm well aware that this more or less obviates the advantages of multithreading but it does allow physics to be run while the display is blocking, and still allows for more or less synchronous execution -- which means it's easier to debug and behavior is far more deterministic.

I know this is pretty naive, but on my laptop and on a handful of ( single processor ) machines I've tested on it works beautifully. I get solid framerates and physics.

I'm *guessing* this is bad for multiprocessor machines, however, since there's a pretty good chance the two threads will be running on different CPUs, and this means there's a buttload of synchronization required between the two processors.

So, what I'm wondering is if there's a non-blocking version of the VBL sync mechanism? Ideally, it could run something like this ( pseudocode, in one thread )

Code:
/*
    Game::timout() would be called from an NSTimer
    running at maxium speed.
*/
void Game::timeout( void )
{
    /*
        VBLSyncReady would be some magic function that
        returns true if you could draw, now, and not have
        tearing.
    */

    if ( VBLSyncReady( glContext ))
    {
        display();
    }
    else
    {
        /*
            physicsTimeDeltaElapsed would be a calculation
            to check if it's time to run the physics
        */
        if ( physicsTimeDeltaElapsed )
        {
            stepODE();
        }
        
        stepGameLogic();
    }

}

Any ideas?

Provided there *isn't* a non-blocking way to do VBL syncing, can anybody proffer a suggestion as to how to get my physics and game logic running on the same thread -- without losing VBL sync?

Edit: I just read Sacred software's article about ime based animation -- looks like a good approach, but still the question of non-blocking VBL sync is important.
Quote this message in a reply
Sage
Posts: 1,232
Joined: 2002.10
Post: #2
VBL will block the flushing thread. Too bad. But consider that the flushing thread need not be the same as the render thread...

Shaun Wexler has outlined this approach to ensure VBL synched updates from a synchronizer thread, which is intended to work around the problems inherent in 10.3 but has the side effect of not blocking the render thread:
Code:
-contextLock {
    return &your_choice_of_mutex_primitive;
}

-renderScene {
    draw
    setFenceAPPLE
}

-drawRect: {
    if (windowNeedsConfig || newContext || boundsSizeChanged) {
        lock contextLock
        [context update]
        set viewport & matrices
        needsRendered = NO
        renderScene
        if (doubleBuffered) glFlush else glFlushRenderAPPLE
        needsFlushed = NO
        unlock contextLock
    }
}

-render {
    if trylock contextLock {
        renderScene
        glFlushRenderAPPLE
        needsRendered = NO
        needsFlushed = YES
        unlock contextLock
    }
    if needsFlushed enqueue flushSwap
    collect garbage
}

-flushSwap {
    if trylock contextLock {
        if needsFlushed
            if testFenceAPPLE {
                if (doubleBuffered) glSwapAPPLE else glFlush
                needsFlushed = NO
        } else requeue flushSwap
        unlock contextLock
    }
    if needsRendered enqueue render
    if needsFlushed enqueue flushSwap
}

-setNeedsRendered {
    needsRendered = YES
    enqueue render
}
In this approach:
-drawRect is only called by the main thread in response to user action.
-render is called from a second thread. You could intersperse with physics etc. Of course it is still up to you to limit the rendering to what is needed (<= display Hz.)
-flushSwap is called from a third "synchronizer" thread. This is the tricky part:

In Shaun's implementation, he has one synchronizer thread per attached display (because they could be running at different Hz) and their only duty is to sit around until VBL time and then flush all of the enqueued contexts on that display. Shaun wrote his own code to manually check the beam position with some logic to sleep the thread the proper amount (handing display Hz around mode changes, sleep etc.) This allows him to circumvent the OS's VBL mechanism-- these GL contexts don't use kCGLCPSwapInterval, they just rely on the synchronizer thread to swap at the proper time.

If that sounds like a lot of work, I agree. The OS should be doing this for you. But I had a chance to see Shaun's code running firsthand, and it works really well. Live window resizing at 60fps without slowing down the GL animation, etc.

Of course I can not talk about Tiger, but let's say that I strongly recommend upgrading when it ships. Wink
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #3
That's fantastic -- but to be honest I don't trust my ability to write the flush synchronization thread.

I have *no* idea how I'd get beam position or, for that matter, how I'd sleep the thread correctly. In my experience, sleeping threads is fairly inaccurate ( well, my experience is mainly with pthreads, and represents an incomplete understanding... ), and syncing with beam position would require a hell of a good scheduler.

<sigh>

I'm going to have to contemplate this a bit.
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #4
I wanted to post a followup. Over the weekend I decided to at the very least, determin if writing a single-threaded version would result in a speedup on a dual-processor machine.

Well, I did, and when I went to test it I thought "what if the dual g5 is set on the 'Automatic' energy saver mode"? Well, it was.

It turns out my game, which ran *horribly* on friday, now gets a cool 30 to 60fps, where it was chugging at about 15 before. I tried out my single-threaded version and it prformed worse than the multithreaded version. I have a new respect for dual processor g5s, and will likely remember to check energy-saver settings in the future.

d'oh.
Quote this message in a reply
Sage
Posts: 1,232
Joined: 2002.10
Post: #5
D'oh.

Yeah, "Automatic" isn't, really.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Key blocking Miglu 40 14,718 Sep 13, 2010 12:39 PM
Last Post: sealfin
  blocking function for timing purposes unknown 6 3,923 Feb 20, 2006 11:03 AM
Last Post: Skorche