PDA

View Full Version : mac vbl



nib
01-29-2008, 08:12 AM
Just want to make sure I'm doing vbl correctly. I'm using cocoa. But I know that things boil down to the cgl stuff. Seems to slow my program down much more when enabled -- I assume this is to be expected. Not sure if anything changed in Leopard. I have something like:

if( monitor_sync ) {
const GLint VBL = 1;
CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &VBL);
}
else {
const GLint VBL = 0;
CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &VBL);
}

Thanks again.

arekkusu
01-30-2008, 10:33 PM
That's really all you need to do.

If you're using NSGL, you can do the same thing via
[myctx setValues:&VBL forParameter:NSOpenGLCPSwapInterval]
but CGL works equally well.

And don't think of it so much as "slowing your program down" as "making your program only display as much as you need to see."

There's no need to run at 1000fps other than benchmarking while you bring up your app. You should always enable VBL sync for release.

ZbuffeR
01-31-2008, 07:12 AM
Well in fact no, vsync on is not always the best solution. The problematic case is when framerate drops just a bit below screen refresh rate (say 60hz).
Without vsync, you really get 59fps (with tearing), with it you only get 30 (wihtout tearing). So I think it should be enabled at the user convenience, and not forced on or off.

nib
01-31-2008, 09:16 AM
Interesting. Just wanted to double check because there is not much documentation on monitor syncing. Google search was pretty sparse.

In my program, I had added more trees ( Dryad trees are great for billboards! ) and had shadow mapping on. So, it started to take more time to draw. Yet, at certain view points the frame rate would seriously drop. I thought it was a fill rate issue. But in some cases, I would draw the trees at the faster rate. Ran shark and it seemed that drawing the vbo was taking longer.

I turned the sync off, then bang everything is super fast! Going to set the default to off. :)

I have a apple cinema display. ( big display does not give me eye strain ) Think it runs at a fixed rate around 60 hz.

arekkusu
01-31-2008, 09:22 AM
Without vsync, you really get 59fps

A more accurate way to think about it is: without vsync, you get 0 frames per second, and a whole bunch of partial frames per second.

I'll take 30 frames over 1000 partial frames any time.

nib
01-31-2008, 12:01 PM
One more question. If your drawing to a fbo ( making a shadow map ) will it be synced to the monitor? Or should I disable the kCGLCPSwapInterval? Not sure how things work internally but I'd assume that kCGLCPSwapInterval has something to do with swapping the double buffer which is not related to an fbo.

ZbuffeR
01-31-2008, 12:37 PM
When framerate is important (racing games, precise interactions), 95% of a frame 59 times per second is much better than 100% of a frame 30 times per second.

And when framerate varies slightly around the refresh rate, with vsync you get annoying jumps from 30 to 60 back to 30 etc. In this case, it is better to settle for a constant 30fps ...

The best of both worlds : "vsync if framerate above display refresh, no vsync if below" have a look here :
http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=196733#Post196733
That is what most modern games seem to do, both for PC and consoles.

ZbuffeR
01-31-2008, 12:38 PM
If your drawing to a fbo ( making a shadow map ) will it be synced to the monitor?
No it should not, and there would be no reason to.

nib
01-31-2008, 02:15 PM
Hmm, take statistics and determine to use sync or not. That is interesting.

My app has a simulator thread and a rendering thread with opengl calls. Though, there is some blocking with a mutex because I cannot change certain standard library data structures ( iterator loops ). Right before the buffer swap in the drawing thread, I let go the mutex. Otherwise, I just put a sleep of certain period to limit how fast the threads can run.

I see they talk about glFlush and glFinish in the linked thread. I suppose the main difference is glFinish is going to block. My cocoa code has:

[[self openGLContext] flushBuffer]; //glFlush in there somewhere?

arekkusu
01-31-2008, 04:32 PM
When framerate is important

Again, "frame" is only meaningful if you are actually drawing frames.
Drawing partial frames is just drawing garbage.

Show me a TV or movie projector that draws partial frames.
Why would you settle for a game console or PC that does?

-NiCo-
01-31-2008, 04:46 PM
Drawing partial frames is actually similar to interlacing in TV signals. For every update only half the amount of horizontal lines are updated, the other half are remains of the previous frame. this is exactly the case when disabling vsync, the only difference is that the interlacing updates every other line while the graphics card updates a contiguous region. Interlacing had the same goal: to arrive at a virtual higher framerate with the downside of possible artifacts.

N.

arekkusu
02-01-2008, 12:13 AM
It is fundamentally different. Interlacing redraws exactly one half of the frame each update. This is a conscious design decision to work around early hardware bandwidth limitations in i.e. NTSC.

Un-synchronized swaps tears randomly, depending on how much you're drawing. No modern display system should be designed to tear.

nib
02-01-2008, 08:41 PM
This is a fun real time programming topic. Thinking about this some more. If I can limit the drawing per frame, then I can free up mutex blocking in my program between drawing and simulation.

How about this approach.

//make this thread's idle period super small. Need to query the beam as
//often as possible.

CGDirectDisplayID _the_display = CGMainDisplayID();
CGBeamPosition _beam = CGDisplayBeamPosition( _the_display );
CGRect _b = CGDisplayBounds(_the_display);

bool _draw_your_stuff = _beam < ( _b.size.height / 1.9 );

if( _draw_your_stuff ) {
//draw opengl here
draw();
[[self openGLContext] flushBuffer]; // has a glFlush in there...
}
else {
//simulator thread is doing its thing with no blocks...
}

---

Not sure how to set a flag that would make sure you draw once a cycle. Also, this assumes os x is going to call that thread without interruption or there might be a skip! I suppose if it takes too long then its a hosed situation anyways. :)

ZbuffeR
02-02-2008, 01:44 AM
Yes, the worst case could be that this thread is awaken only when the beamposition is at the top of the screen : no draw() at all.
Maybe solved if the simulator need only small time chunks, and a time threshold is added to force draw even if it will tear, ie. if you are more than 2 or 3 sim steps late.

arekkusu will not be happy, but hey, I really do think that having a tearing frame is better than no frame at all during a long time :)

The nice side of using completely separate threads for drawing and simulation, is that it allows a fixed tick rate for physics sim, makes the user experience reproductible (ex. needed for validation of replays for online highscore).
And it can take advantage of multiple CPU cores too.

arekkusu
02-02-2008, 02:16 AM
//make this thread's idle period super small. Need to query the beam as
//often as possible.


Balancing multiple execution threads is certainly an interesting topic, and one that deserves experimentation.

For knowing when to draw though, there are two approaches that work reasonably well on the Mac:

1) use a timer. An NSTimer at 1000 Hz will fire your callback to enqueue a render. That thread will block on VBL, subsequent timer firings are ignored until the thread returns to the runloop. You want a relatively high frequency timer (not i.e. 60Hz) to ensure that your callback is hit soon after the VBL, so you have plenty of time for the next frame. Pictorially, that is:

VBL__^_render____block_________VBL_^_render____blo ck__________VBL__^_.......

whereas a 60Hz timer has no guaranteed start offset relative to the VBL, so worst case you end up with something like

VBL___________________^_rende_VBL_r____block______ ___________VBL___________________^_rende_VBL_...

In a multitasking non-realtime OS this is never going to be perfect, but it works well enough for most apps.
FWIW, here's an old thread about this topic. (http://www.idevgames.com/forum/showthread.php?t=6928)

2) use a Core Video Display link (http://developer.apple.com/documentation/GraphicsImaging/Conceptual/CoreVideo/CVProg_Concepts/chapter_2_section_3.html#//apple_ref/doc/uid/TP40001536-CH202-DontLinkElementID_11). Video folks wanted a way to be notified on each VBL, and this is it. You can abuse it for your own means with a small amount of setup code (http://developer.apple.com/documentation/GraphicsImaging/Conceptual/CoreVideo/CVProg_Tasks/chapter_3_section_2.html#//apple_ref/doc/uid/TP40001536-CH203-DontLinkElementID_1).

This, btw, is implemented as a high priority thread which knows when to wake up based on the refresh rate of the display. So no need to reinvent the wheel polling the beam position, if that's what you're after.




arekkusu will not be happy, but hey, I really do think that having a tearing frame is better than no frame at all during a long time :)


Luckily, I'm in a position to ship product where I get to enforce policy that makes it impossible for you to tear. ;)

nib
02-02-2008, 12:42 PM
Think there is another solution without having to track the beam. This looks like a good solution. Appears to work well.

//get the main display and bounds
CGDirectDisplayID _the_display = CGMainDisplayID();
CGRect _b = CGDisplayBounds(_the_display);

//set parameters for wait call
//follow the apple example.
CGBeamPosition lowerScanLine = (CGBeamPosition)(_b.origin.y + _b.size.height);
CGBeamPosition upperScanLine = 0;

//in the drawingthread, which is running frequency of 1000hz,
//wait for the scan line to start at the top of the monitor.
//works on os 10.0 or greater.
//Does the driver support this? ( might be the only drawback )
CGDisplayWaitForBeamPositionOutsideLines( _the_display, upperScanLine, lowerScanLine );

//draw the frame
//should finish drawing one time before the scan completes.
draw();

//swap the buffer
[[self openGLContext] flushBuffer];

//repeat the loop in the thread. Since this is its own thread we can block
//with no problems.

OneSadCookie
02-03-2008, 11:16 PM
Seriously, don't do that. Use CoreVideo or CGLFlushBuffer to block until the appropriate moment.

Arekkusu's theory of partial frames is all well and good as a theory, but missing vsync can easily make a game unplayable. I struggled with unplayability on Outnumbered, particularly on low-end systems, until I disabled vsync when the frame-rate dropped too low. 29fps with tearing is playable, 15fps is not. 59fps with tearing isn't as pretty as 30fps without, but a lot less motion-sickness-inducing and a lot more responsive.

nib
02-04-2008, 12:54 PM
CGLFlushDrawable ( CGLGetCurrentContext() ); //blocks for kCGLCPSwapInterval


I took some statistics via opengl profiler on the nvidia 7300 gt card. Think this card has less bandwidth than my other radeon 9800. Really illustrates things:

frame draws

no sync

25 vertex and fragment shaders with vanilla gl shadow map
41 vertex and fragment shaders
93 vanilla gl

sync ( with kCGLCPSwapInterval )

19 vertex and fragment shaders with vanilla gl shadow map ( looks bad )
30 vertex and fragment shaders
60 vanilla gl