Thread: Creating waits and processing delays

  1. #1
    Math wizard
    Join Date
    Dec 2006
    Location
    USA
    Posts
    582

    Creating waits and processing delays

    How do you syncronize delays with the refresh rate of the monitor? That is, my monitor's refresh rate is 60 Hz. How do I get it so that, when the previous "frame" is done being processed, a delay occurs stopping all function executions, then, when the monitor displays that frame, the function resumes execution and when done, it pauses again until the monitor displays the frame. How is this done? As far as I can tell, it's the sleep function, but since that's in milliseconds and constant (and 16 2/3 is not possible), this doesn't seem to be an option. But then, what if the end user has another refresh rate (like 75 Hz) - how do I get this detail? The "GetSystemMetrics" function doesn't seem to have this. In my other tool I used, I'd use these two functions:

    wait(4); // waits 4 frames
    sleep(2); // waits 2 seconds (not 2 milliseconds)

    The other tool didn't synchronize the frame rate with the monitor, one of its many limitations, and it causes sudden jerks and pauses as a result (since the frame rate was never constant - varying from 55 to 66 fps, averaging 63). With C, I have a chance to have a solution to this issue.

  2. #2
    Registered User
    Join Date
    Mar 2005
    Location
    Mountaintop, Pa
    Posts
    1,058
    But then, what if the end user has another refresh rate (like 75 Hz) - how do I get this detail?
    You may be able to use WMI CIM_VideoControllerResolution for determining refresh rate.

    Also, check out QueryPerformancecounter to create a high resolution timer that may be of some help to you.

  3. #3
    Math wizard
    Join Date
    Dec 2006
    Location
    USA
    Posts
    582
    Thanks for the details for the high resolution timer, however, is there a way to get the refresh rate of the monitor that can be used with Windows 95 (98 at the very latest)? The one you gave only goes as far back as 2000, way too new for what I'm targetting. The games I intend on making at first are very simple, they could run on old Windows 95 systems quite well. I do have a work around for this, but users may find it a little inconvienient (but it's a one-time thing only). Thanks though and I'll look into the timer and run some tests/experiments.

    Edit:
    if the installed hardware does not support a high-resolution performance counter, the function fails
    Seeing this, I'm just wondering how common high-resolution timers are.

    Edit #2:
    I can't seem to get the timer to work as it won't let me use comparitive operations or even math operations. Here's my code:

    Code:
    	LARGE_INTEGER timer_count; // the performance functions use a large integer - this is the count
    	LARGE_INTEGER *ptimer_count; // pointers are needed for these functions
    	LARGE_INTEGER timer_frequency; // this is the frequency - count divided by frequency gives time
    	LARGE_INTEGER *ptimer_frequency;
    	ptimer_count = &timer_count; // set the pointers
    	ptimer_frequency = &timer_frequency;
    	QueryPerformanceFrequency(ptimer_frequency); // obtain the frequency first
    	QueryPerformanceCounter(ptimer_count); // then the count
    	
    	while (timer_count/timer_frequency < 1) // a 1-second duration
    	{
    		QueryPerformanceCounter(ptimer_count); // query the timer_count value
    	}
    	sprintf(error_msg, "Debug values read:\n\ntimer_frequency - %d\ntimer_count - %d",
    		timer_frequency, timer_count);
    	MessageBox(hwnd, error_msg, "Debug results", MB_OK);
    The build log has this:

    error C2088: '/' : illegal for union
    It wouldn't let me use the "<" either. Without the loop, I see that the frequency is 3 million something and the count is zero.
    Last edited by ulillillia; 12-19-2006 at 05:01 AM.

  4. #4
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,659
    > I can't seem to get the timer to work as it won't let me use comparitive operations or even math operations
    Look up the declaration of LARGE_INTEGER
    You need timer_count.QuadPart say to access the part of the union which interests you.

    > ptimer_count = &timer_count; // set the pointers
    > ptimer_frequency = &timer_frequency;
    This is pointless use of pointers.
    Try QueryPerformanceCounter( &timer_count );


    > How do you syncronize delays with the refresh rate of the monitor?
    Which graphics API are you using?
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  5. #5
    Math wizard
    Join Date
    Dec 2006
    Location
    USA
    Posts
    582
    Quote Originally Posted by Salem
    > I can't seem to get the timer to work as it won't let me use comparitive operations or even math operations
    Look up the declaration of LARGE_INTEGER
    You need timer_count.QuadPart say to access the part of the union which interests you.
    I tried that and the loop never seems to be true or even execute. This is my updated code:

    Code:
    	LARGE_INTEGER timer_count; // the performance functions use a large integer - this is the count
    	LARGE_INTEGER timer_frequency; // this is the frequency - count divided by frequency gives time
    	double time_value; // actual time in seconds - double since 7 significant figures for a float isn't quite enough
    	QueryPerformanceFrequency(&timer_frequency); // obtain the frequency first
    	QueryPerformanceCounter(&timer_count); // then the count
    
    	time_value = timer_count.QuadPart/timer_frequency.QuadPart;
    	while (time_value < 1) // a 1-second duration - seems to never be true even though 0 < 1
    	{
    		MessageBox(hwnd, "This loop is running.", "Loop test", MB_OK);
    		QueryPerformanceCounter(&timer_count); // query the timer_count value
    		time_value = timer_count.QuadPart/timer_frequency.QuadPart;
    	}
    	sprintf(error_msg, "Debug values read:\n\ntimer_frequency - %d\ntimer_count - %d\ntime_value - %5.8f",
    		timer_frequency.QuadPart, timer_count.QuadPart, time_value);
    	MessageBox(hwnd, error_msg, "Debug results", MB_OK);
    With the Message Box instruction, I can see the values of the variables. The frequency value shows 3 4/7 million (to the nearest common fraction) but the "timer_count.QuadPart" value as well as the "time_value" value are both zero. When the program reaches the loop, it sees it as "while zero is less than one", a true statement, but the Message Box instruction isn't executing indicating the condition is somehow false. This isn't making any sense.

    > ptimer_count = &timer_count; // set the pointers
    > ptimer_frequency = &timer_frequency;
    This is pointless use of pointers.
    Try QueryPerformanceCounter( &timer_count );
    I'm fairly new to C programming so I'm not used of this. I'm still used of the methods I needed to use in my previous heavily limited tool so things like this would take some time to get used to. I did make the changes though.

    > How do you syncronize delays with the refresh rate of the monitor?
    Which graphics API are you using?
    Graphics API? I have no idea what a "Graphics API" is other than it has something to do with graphics. I'm using DrawDibDraw to render the 2D images I'll be using. I would like to avoid using DirectX and have it work with Windows 95 (Windows 98 at the latest). If I can get the timer to work as I expect it to, I have a way to work around this, but it may cause a minor inconvienience.

  6. #6
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,659
    If you look at say DrawDibBegin(), in particular the DDF_BUFFER mode.

    By always drawing incrementally to an off-screen buffer, you can avoid a lot (or all) the flickering issues which trying to sync up to the vertical refresh of the monitor can cause.

    Just an idea.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  7. #7
    Math wizard
    Join Date
    Dec 2006
    Location
    USA
    Posts
    582
    Can you explain why the timer doesn't work? It's showing a value of zero and, strangely enough, the while loop is false even though 0 < 1 is indeed true. This is very confusing and isn't making any sense. What's going on that's causing this? Suspecting it's the use of "large integer", a 64-bit integer (when I only have a 32-bit processor), I tried using a float or double instead, but I still get zero for the counter no matter what I use. Typecasting to a float or double doesn't work as I still get zero. This is really confusing.

    I do know that counts divided by frequency gives time. The frequency is, for me anyway, 3579545 and thus I get about 3 4/7 million counts per second. If I wanted 1/60 second, my monitor's refresh rate, I'd have the loop run for frequency/60 or 59659 counts (to the nearest integer). I'm doing a test for one second so I need the count to be equal to the frequency, but I get no pause or anything and the while loop isn't even running. It makes no sense.

    Edit: Here's my code again as I made a few small changes to it:

    Code:
    	LARGE_INTEGER timer_count; // the performance functions use a large integer - this is the count
    	LARGE_INTEGER timer_frequency; // this is the frequency - count divided by frequency gives time
    	double time_value;
    	QueryPerformanceFrequency(&timer_frequency); // obtain the frequency first
    	QueryPerformanceCounter(&timer_count); // then the count
    	time_value = (double)(timer_count.QuadPart/timer_frequency.QuadPart);
    	
    	while (time_value < 2) // a 1-second duration
    	{
    		MessageBox(hwnd, "This loop is running.", "Loop test", MB_OK);
    		QueryPerformanceCounter(&timer_count); // query the timer_count value
    		time_value = timer_count.QuadPart/timer_frequency.QuadPart;
    	}
    	sprintf(error_msg, "Debug values read:\n\ntimer_frequency - %d\ntimer_count - %d\ntime_value - %5.8f",
    		timer_frequency.QuadPart, timer_count.QuadPart, time_value);
    	MessageBox(hwnd, error_msg, "Debug results", MB_OK);
    I also tried slipping this between the file loading stuff for reading the BMP file, which should certainly take at least a millisecond and would definitely register here, but even that still yields zero. It's as if it's just not counting (but then, why isn't the loop running even though it's true (0 < 1 is true, the < with the wider part pointing to the larger number and the tip to the smaller one (a trick to differentiate between < and >)).
    Last edited by ulillillia; 12-19-2006 at 12:19 PM.

  8. #8
    Registered User
    Join Date
    Mar 2005
    Location
    Mountaintop, Pa
    Posts
    1,058
    Hopefully, a small example can be of some help to you.

    Code:
    #pragma comment(lib, "winmm.lib") // Needed for timeGetTime()
    #include <windows.h>
    #include <stdio.h>
    
    int main(void)
    {
    	BOOL performance_flag;    		
    	double factor;
    	double time_span;  		
    	LONGLONG last_time;    	
    	LONGLONG current_time;
    	LONGLONG performance_counter;
    	
    	if (QueryPerformanceFrequency((LARGE_INTEGER *) &performance_counter))
    	{
    		factor=1.0/performance_counter;
    		QueryPerformanceCounter((LARGE_INTEGER *) &last_time);
    		performance_flag = TRUE;
    	}
    	else
    	{
    		// Performance Data Helper high res counter not available
    		// use multi media high res counter;
    		last_time=timeGetTime();
    		performance_flag=FALSE;
    		factor=0.001;
    	}
    	if (performance_flag)
    			QueryPerformanceCounter((LARGE_INTEGER *) &current_time);
    		else
    			current_time=timeGetTime();
    	time_span=(current_time-last_time)*factor;
    	while(time_span <= 1.0f)
    	{
    		printf("time span = %f\n", time_span);
    		last_time=current_time;
    		Sleep(990);
    		if (performance_flag)
    			QueryPerformanceCounter((LARGE_INTEGER *) &current_time);
    		else
    			current_time=timeGetTime();
    		time_span=(current_time-last_time)*factor; 
    	}
    }

  9. #9
    Math wizard
    Join Date
    Dec 2006
    Location
    USA
    Posts
    582
    I copied the code you gave and made a few changes to get it to work with C (since, for example, bool does work). The timer thing seems to work to some extent, but why, when I comment out the sleep instruction or change it to 100 or something, does it cause the program to stop responding (and when commented out, taking 5 minutes to shut down since my computer wasn't responding due to the program using up all the time for processing)? With 100 for the sleep value, the program doesn't respond. Unlike my previous code where I saw "3579545" for the frequency, I'm getting values that do not match - 7. The "last time" variable is negative, probably from variable overflow (even though I'm using the "large_integer thing instead of longlong, which is what I had before). This is making me even more confused.

    This is all I'm after:

    1. Accuracy to within 1/10,000 second (within 1/100,000 preferrably). The high res timer seems to have 1/3579545 of a second precision, more than I'm intending on using but it'll help.
    2. I pause a function for the value of the refresh rate (i.e. I'm using 60 Hz monitor refresh rate, so the pause time would be 0.016666666666666667 of a second. If the user who downloaded my program used 80 Hz for the refresh rate, the pause time would be 0.0125).
    3. It must have support for Windows 95, Windows 98 at the extreme earliest.

    If I have this within a function:

    Code:
    void change_img_location()
    {
    	mounts1.x = main_pos.x/mounts_scale[0]; // closest mountains in 2D scene
    	mounts2.x = main_pos.x/mounts_scale[1]; // furthest mountains in 2D scene
    	redraw_scene(); // rerender the images with the new positions set
    	wait_frames(1); // wait one frame, the number of seconds in one monitor refresh
    	// some other code
    }
    It would set the positions of the images to be rendered, redraw the scene, then wait the amount of time it takes for a monitor refresh. Since my refresh rate is 60 Hz, the time it'd wait would be 1/60 of a second. If someone used 80 Hz refresh rate, the wait time would be shorter. The function stops all execution until the waiting time is over with and from there, it continues with the next instruction below it within the function that called for the waiting time. It causes all other functions to stop running as well. This is all I'm after trying to do. The last thing I have left before I can begin making useful Windows programs is having a function called upon pressing a key and I've found quite a bit of details on this. It's the timer and stopping execution part that's causing me a lot of trouble at the moment.

  10. #10
    Registered User
    Join Date
    Mar 2005
    Location
    Mountaintop, Pa
    Posts
    1,058
    (and when commented out, taking 5 minutes to shut down since my computer wasn't responding due to the program using up all the time for processing)? With 100 for the sleep value, the program doesn't respond.
    You really need a Sleep statement in that loop. It's a fairly tight loop and is very cpu intensive. The Sleep statement yields the cpu to other processes etc. Not using a Sleep statement essentially starves all the other processes of cpu time thus causing the 5 minute shutdown time.

    I'm not sure why 100 for a sleep value causes the program to not respond. I can't recreate this problem.

  11. #11
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,659
    But sleep isn't going to help you is it?

    I mean, if you manage to sync up to the middle of the CRT refresh, then you're going to get the jaggies every time.

    You really need to sync to either the vertical refresh event itself, or use some kind of double buffering if you want a smooth display.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  12. #12
    Math wizard
    Join Date
    Dec 2006
    Location
    USA
    Posts
    582
    I made a few changes to my code, again, and the timer now works as expected, but why are my debug values showing such bizarre results? I took a screenshot to explain what I'm seeing. Why is the "time_value" variable showing 14.7 quinquaseptuagintillion (or 1.47E229), the start value as zero and, after some time, the current value going negative? If it's a 64-bit variable, variable overflow is not the case. This is my updated code:

    Code:
    void wait_time(float seconds)
    {
    	LARGE_INTEGER timer_start; // the performance functions use a large integer - this is the starting point
    	LARGE_INTEGER timer_current; // this is the current time point - current minus start gives the amount of time that passed
    	LARGE_INTEGER timer_frequency; // this is the frequency - difference divided by frequency gives time in seconds
    	double time_value; // the time, in seconds
    	QueryPerformanceFrequency(&timer_frequency); // obtain the frequency first
    	QueryPerformanceCounter(&timer_start); // then the start time
    	QueryPerformanceCounter(&timer_current); // then the start time
    	time_value = (double)((timer_current.QuadPart-timer_start.QuadPart)/timer_frequency.QuadPart); // find the time value, in seconds - typecast to allow floating point precision rather than problematic integerical division
    	
    	while (time_value < seconds) // a 1-second duration
    	{
    		// MessageBox(hwnd, "This loop is running.", "Loop test", MB_OK);
    		Sleep(100); // to reduce CPU consumption
    		QueryPerformanceCounter(&timer_current); // query the timer_count value
    		time_value = (double)((timer_current.QuadPart-timer_start.QuadPart)/timer_frequency.QuadPart); // repeatedly reobtain the time passage
    	}
    	sprintf(debug_msg, "Debug values obtained:\n\ntimer_frequency - %d\ntimer_start - %d\ntimer_current - %d\ntime_value - %5.8f",
    		timer_frequency.QuadPart, timer_start.QuadPart, timer_current.QuadPart, time_value);
    	MessageBox(hwnd, debug_msg, "Debug results", MB_OK);
    }
    The timer runs just fine and I call it by using "wait_time(2.0);" somewhere in the program to wait two seconds or "wait_time(0.25);" to wait a quarter second (15 monitor refreshes).

    As to the case with the sleep value being 100 in my earlier case (it works in my code above), the program would just keep running nonstop although it wouldn't get out of the loop. There was no CPU usage as a result of this. If I changed it back to the 990 it was before, the loop would terminate. Is there a better way to monitor variables than through message boxes or using text to display values? In my old tool, I'd just define a panel (a struct) with a digits element in it to get values to display in realtime. The message box only shows them at the very instance it appears. For example:

    Code:
    panel debug_panel
    {
    	pos_x = 4; // position it somewhere near the top left part of the window
    	pos_y = 3;
    	digits = 0, 0, 12.3, some_font, 1, var_to_monitor;
    	digits = 0, 0, 12.3, some_font, 1, another_var_to_monitor;
    }
    The 12.3 is otherwise %7.3f in a sprintf() instruction. The zeros at the beginning are the positions of the digits relative to the top left corner of the panel. The 1 is the multiplier.

  13. #13
    Math wizard
    Join Date
    Dec 2006
    Location
    USA
    Posts
    582
    Quote Originally Posted by Salem
    But sleep isn't going to help you is it?

    I mean, if you manage to sync up to the middle of the CRT refresh, then you're going to get the jaggies every time.

    You really need to sync to either the vertical refresh event itself, or use some kind of double buffering if you want a smooth display.
    I've heard of double buffering with my previous tool, but how do you sync with the monitor's refresh rate (and be compatible with Windows 95 (or Windows 98 at the very latest as a last resort) - I've only seen it go as far back as 2000, way too new)?

  14. #14
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,659
    > but why are my debug values showing such bizarre results?
    Because %d is not the correct thing to use for a quadpart.
    Read the printf() manual for the correct format.

    > but how do you sync with the monitor's refresh rate
    Why would you need to?
    There are much easier clocks to get a hold of if you want to progress at a steady rate.

    Historically, people waited for the vsync, then started to update the video memory whilst the CRT beam flew back from the bottom of the screen to the top. But since this is an entirely artificial concept if you have an LCD monitor, it doesn't work so well.

    Double buffering just means you can memcpy from off-screen to on-screen any time you like. Because the old and new scenes look so similar from one frame to the next, you don't notice the changeover.

    None of your timing attempts actually synchronise to the vsync, it's just approximately in step with it. You've no idea where the CRT beam logically is with respect to your clock.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  15. #15
    Math wizard
    Join Date
    Dec 2006
    Location
    USA
    Posts
    582
    Well, I don't have a manual for printf, but Google is just as good. It appears as if I need %I64i to get it to display properly and %lf to get the double to display properly. That's all good and well and when I try using 0.01666666667 for the waiting time, I'm getting a value of 0.031..., of which is two frames.

    Why would you need to?
    Let's say I have a frame rate of 72 fps and my monitor runs at 60 Hz refresh. The frame rate is constant. The first 4 frames display consecutively but on the fifth frame, there is the appearance of a frame being skipped since two frames are being processed during the monitor refresh. This is a known problem with my 2D game as I had it with my old tool - it ran with random frame rates from 55 to 66 fps (averaging 63) causing a lot of jerky motion (frame skips and a sudden one-frame stop, mostly skips)*. It was a limitation of the old tool I used and there was nothing I can do about it. If I can synchronize the displaying of the scene with the monitor, I wouldn't have this and it would be smoother than smooth can get - no jerks or sudden stops at all.

    This is the whole point of this thread - getting some system (originally as a timer) to synchronize with the monitor's refresh rate (it's even the very first sentence of the original post of this thread). I do use a CRT monitor** and I'm aware of it's limitations and design. I don't know how to do any of this "vsync", "double buffering" and other stuff. I'm after something that at least works with Windows 98 and would strongly prefer it to work with Windows 95. Any assistance with this would be appreciated. At least a few instructions or pieces of code that meets my needs is all I need. Then it's the last thing left, of which I've pretty much got answered myself.



    Footnotes:
    * My 2D game has a free download (4 MB), but, due to limitations of the tool I used and having to use the buggy Molebox to pack the game as a result of these limitations, you may notice strange effects (don't save to the desktop - it's a known issue and one of many reasons why I'm trying to move to C programming). PM me if you want the link to it.
    ** Since no LCD's support 1920x1440 resolution (and even this seems rather small) and they're triple the price (more than double if the huge shipping fee for CRT's is included), and with the many downsides they have, they don't seem worth getting at all for me.

Popular pages Recent additions subscribe to a feed