Page 1 of 2

Save CPU time instead of wait for vsync

Posted: Tue Oct 19, 2004 7:47 pm
by Psychophanta
This post is a tricky solution for topic viewtopic.php?t=12747

I have read what wilbert points (the web links) in that topic, and my conclusion is to work with DirectX (if we are working with windows), or to work at "ring 0" to be able to obtain VGA Input Status Register value directly; http://www.compuphase.com/vretrace.htm

I have played mpeg video files with a video player at full screen, and I wonder how the CPU time is nicely hardworking, wasting only less than 5% CPU processing. I asked myself how it do it, but I have not an answer yet.

This trick; I am not able to understand how it works so fine, but... it work so fine :!:
The trick is just to replace

Code: Select all

FlipBuffers()
by

Code: Select all

FlipBuffers():Delay(16)
16 if vsync happens 60 times per second (1000ms/60)
Replacing this in PB sources don't modify speed (at least all what i've tested), and CPU works about 90% less.
NOTE: if calculations and drawing tasks are too hard, then decrease the delay time.

Posted: Tue Oct 19, 2004 8:14 pm
by thefool
hmm why not just turn of vsync in the flipbuffers?

Code: Select all

flipbuffers(0)
The optional 'WaitSynchronization' parameter allows to wait or not for the screen synchronization before flipping the buffers (also known as 'Vertical blank synchronization').

It can have one of the following values:
0 : disable synchronization
1 : enable synchronization (default value)

Waiting for the screen synchronization allows the flip to be perfect (no 'tearing' or other visible artifacts) because the flip is performed when the screen has been fully drawed (and when the screen spot is outside of visible screen area). This also link the flip frequence to the actual screen refresh, ie: for 60Hz screen it could have at most 60 flip per seconds, etc. By default, FlipBuffers() waits for the screen synchronization.
and use that blended with delay(1)
that should do some.

Posted: Tue Oct 19, 2004 10:45 pm
by Psychophanta
why not just turn of vsync in the flipbuffers?
Because if you don't wait for vsync, then graphics movements are unsyncronized, this is, not smooth.

Re: Save CPU time instead of wait for vsync

Posted: Wed Oct 20, 2004 10:53 am
by tinman
Psychophanta wrote:This trick; I am not able to understand how it works so fine, but... it work so fine :!:
1000 / 60 = 16.666666666666666666666666666667

So if you FlipBuffers(), you will be synchronised to the display, then you wait for just less than the duration of a frame (16ms in your tip) so your program becomes active just before the next vsync. So you are minimising the amount of time spent between the end of the Delay() and the start of the FlipBuffers().

I'm not sure how that explanation fits in with the windows scheduler though.

Posted: Wed Oct 20, 2004 4:28 pm
by Psychophanta
Yeah! I understood it yesterday just after write the post.
:arrow:
It is curious that some tasks that i thought it take looooot of CPU time, only take less than 0.666666666666666666 milliseconds, because with a Delay(16) after flipping buffers, all move smoooooth and fine on a XP 1800 MHz. :wink:
And of course CPU usage is less than 5% 8O :wink:

Posted: Wed Oct 20, 2004 5:34 pm
by thefool
so vsync is not what you try to disable here?

hmm but if i turn vsync completely of at my graphics control panel, then the games are not running unsmooth, but are exacly equal (no flickering at all), and runs with more fps.

but ok i think if u just turn off vsync cpu usage is still about 100%, but using ur tip its lower.

Posted: Sat Dec 18, 2004 11:20 pm
by Psychophanta
A bit more elegant way using hi-res timing:

Code: Select all

Global t1.LARGE_INTEGER,t2.LARGE_INTEGER,periodns.f

If QueryPerformanceFrequency_(Freq.LARGE_INTEGER)
  periodns.f=1000000000/(Pow(2,32)*Freq\highpart+Freq\lowpart)
Else
  MessageRequester("Sorry","No High-res timer allowed"):End
EndIf

Procedure myFlipBuffers()
  QueryPerformanceCounter_(t2.LARGE_INTEGER):t2\lowpart&$7FFFFFFF
  ti.l=periodns.f*(t2.LARGE_INTEGER\lowpart-t1.LARGE_INTEGER\lowpart)/1000000
  If ti<16
    Delay(16-ti)
  EndIf
  FlipBuffers()
  QueryPerformanceCounter_(t1.LARGE_INTEGER):t1\lowpart&$7FFFFFFF
EndProcedure

Posted: Sat Mar 05, 2005 2:02 pm
by Psychophanta
Next method is the method used by some of emulators for windows, and even more accurated :wink:

Code: Select all

Global t1.LARGE_INTEGER,t2.LARGE_INTEGER,SysFreq.LARGE_INTEGER,MustDelay.f,DispFreq.l
#ENUM_CURRENT_SETTINGS = -1 

If QueryPerformanceFrequency_(@SysFreq.LARGE_INTEGER)=0
  MessageRequester("Sorry","No High-res timer allowed"):End
EndIf
QueryPerformanceCounter_(@t1.LARGE_INTEGER);<- checkpoint1

Procedure myFlipBuffers()
  QueryPerformanceCounter_(@t2.LARGE_INTEGER);<- checkpoint2
  !fld1
  !fidiv dword[v_DispFreq];<-now in st0 is seconds amount per screen frame
  !fild qword[v_t2];<-now in st0 is checkpoint2. In st1 is seconds amount per screen frame
  !fild qword[v_t1];<-now in st0 is checkpoint1. in st1 is checkpoint2. In st2 is seconds amount per screen frame
  !fsubp;<-now in st0 is checkpoint2-checkpoint1. In st1 is seconds amount per screen frame
  !fild qword[v_SysFreq];<-now in st0 is SysFreq. In st1 is checkpoint2-checkpoint1. In st2 seconds amount per screen frame
  !fdivp ;<-now in st0 is (checkpoint2-checkpoint1)/SysFreq. In st1 is seconds amount per screen frame
  !fsubp ;<-now in st0 is 1/DispFreq-(checkpoint2-checkpoint1)/SysFreq.
  !fstp dword[v_MustDelay];<-  MustDelay.f=1/DispFreq-(checkpoint2-checkpoint1)/SysFreq.
  MustDelay.f*1000
  If MustDelay.f>1
    Sleep_(Int(MustDelay.f))
  EndIf
  FlipBuffers()
  QueryPerformanceCounter_(@t1.LARGE_INTEGER);<- checkpoint1
EndProcedure



; ----------------------------------------------------------------------------------------------------
;NOTE next line must be executed after screen is open:
EnumDisplaySettings_(#NULL,#ENUM_CURRENT_SETTINGS,@dm.DEVMODE):DispFreq.l=dm\dmDisplayFrequency
Can be tested with this snip:

Code: Select all

Global t1.LARGE_INTEGER,t2.LARGE_INTEGER,SysFreq.LARGE_INTEGER,MustDelay.f,DispFreq.l
#ENUM_CURRENT_SETTINGS = -1 

If QueryPerformanceFrequency_(@SysFreq.LARGE_INTEGER)=0
  MessageRequester("Sorry","No High-res timer allowed"):End
EndIf
QueryPerformanceCounter_(@t1.LARGE_INTEGER);<- checkpoint1

Procedure myFlipBuffers()
  QueryPerformanceCounter_(@t2.LARGE_INTEGER);<- checkpoint2
  !fld1
  !fidiv dword[v_DispFreq];<-now in st0 is seconds amount per screen frame
  !fild qword[v_t2];<-now in st0 is checkpoint2. In st1 is seconds amount per screen frame
  !fild qword[v_t1];<-now in st0 is checkpoint1. in st1 is checkpoint2. In st2 is seconds amount per screen frame
  !fsubp;<-now in st0 is checkpoint2-checkpoint1. In st1 is seconds amount per screen frame
  !fild qword[v_SysFreq];<-now in st0 is SysFreq. In st1 is checkpoint2-checkpoint1. In st2 seconds amount per screen frame
  !fdivp ;<-now in st0 is (checkpoint2-checkpoint1)/SysFreq. In st1 is seconds amount per screen frame
  !fsubp ;<-now in st0 is 1/DispFreq-(checkpoint2-checkpoint1)/SysFreq.
  !fstp dword[v_MustDelay];<-  MustDelay.f=1/DispFreq-(checkpoint2-checkpoint1)/SysFreq.
  MustDelay.f*1000
  If MustDelay.f>1
    Sleep_(Int(MustDelay.f))
    Debug Int(MustDelay.f)
  EndIf
  QueryPerformanceCounter_(@t1.LARGE_INTEGER);<- checkpoint1
EndProcedure



; ----------------------------------------------------------------------------------------------------
;NOTE next line must be executed after screen is open:
EnumDisplaySettings_(#NULL,#ENUM_CURRENT_SETTINGS,@dm.DEVMODE):DispFreq.l=dm\dmDisplayFrequency



For t=1 To 400 
        Sleep_(6) 
myFlipBuffers() 
Next

Posted: Mon May 23, 2005 10:35 am
by THCM
The testsnip is missing a FlipBuffers() or did I miss something here?

Posted: Mon May 23, 2005 11:27 am
by THCM
I tried your code with my game, but movement of sprites gets jerky. I'm using SetFrameRate(60) to limit gamespeed. When I don't use this command and set my monitorrefresh to 60hz everything seems fine. Any clue?

Posted: Mon May 23, 2005 12:43 pm
by Psychophanta
Hi;
I must say that the trick explained is technically perfect for any Hz rate.
I mean, that this trick allows to liberate CPU from those rendering-task surplus millisecs used and bad-wasted by FlipBuffers() command and by DX7 Vsync functions.
What that this trick do while Vsync time is to ensure rendering time is not cutted while ensures rendering surplus time is not CPU badwasted (of course, ensuring tearing effect free too)

Perhaps you have inserted it in a bad way.
1) You must insert this before you open the graphics screen in your code:

Code: Select all

Global t1.LARGE_INTEGER,t2.LARGE_INTEGER,SysFreq.LARGE_INTEGER,MustDelay.f,DispFreq.l 
#ENUM_CURRENT_SETTINGS = -1 

If QueryPerformanceFrequency_(@SysFreq.LARGE_INTEGER)=0 
  MessageRequester("Sorry","No High-res timer allowed"):End 
EndIf 
QueryPerformanceCounter_(@t1.LARGE_INTEGER);<- checkpoint1 

!macro myFlipBuffers{
  QueryPerformanceCounter_(@t2.LARGE_INTEGER);<- checkpoint2
  !fld1
  !fidiv dword[v_DispFreq];<-now in st0 is seconds amount per screen frame
  !fild qword[v_t2];<-now in st0 is checkpoint2. In st1 is seconds amount per screen frame
  !fild qword[v_t1];<-now in st0 is checkpoint1. in st1 is checkpoint2. In st2 is seconds amount per screen frame
  !fsubp;<-now in st0 is checkpoint2-checkpoint1. In st1 is seconds amount per screen frame
  !fild qword[v_SysFreq];<-now in st0 is SysFreq. In st1 is checkpoint2-checkpoint1. In st2 seconds amount per screen frame
  !fdivp ;<-now in st0 is (checkpoint2-checkpoint1)/SysFreq. In st1 is seconds amount per screen frame
  !fsubp ;<-now in st0 is 1/DispFreq-(checkpoint2-checkpoint1)/SysFreq.
  !fstp dword[v_MustDelay];<-  MustDelay.f=1/DispFreq-(checkpoint2-checkpoint1)/SysFreq.
  MustDelay.f*1000
  If MustDelay.f>1
    Sleep_(Int(MustDelay.f))
  EndIf
  FlipBuffers()
  QueryPerformanceCounter_(@t1.LARGE_INTEGER);<- checkpoint1
!}
2) You must insert this after open the graphics screen in your code:

Code: Select all

; ---------------------------------------------------------------------------------------------------- 
;NOTE next line must be executed after screen is open: 
EnumDisplaySettings_(#NULL,#ENUM_CURRENT_SETTINGS,@dm.DEVMODE):DispFreq.l=dm\dmDisplayFrequency 
3) You must replace FlipBuffers() command in the MAIN program section by !myFlipBuffers

That's all :)

And Well; even technically perfect, this trick is not efficiently perfect: since current PCs system time resolution is 1 millisec for the Sleep_() or Delay() functions, there are a portion time less than 1 millisec which is badwasted by FlipBuffers() function existing in my routine, but always less than 1 millisec/frame will be badwasted.


On the other hand there is another different solution for your games, etc. which consists in adding static delay time just after FlipBuffers(); a Delay(16), Delay(15), or less values if machine is slow, or if rendering task are too hard (the higher delay value, the less time badwasted, but faster machine is required). This method allows graphics movements to go smooth on fastest computers, but jerky on slower machines. Try values from Int(1000/Refresh Rate) down to 1 by 1, and watch how smooth is going.

Posted: Mon May 23, 2005 12:53 pm
by THCM
Hi Psyhophanta!

I exchanged your old procedure against your new macro and my code does everything in the right order, as you said above. I still have little hicups while moving sprites on screen.

The original FlipBuffers() command works like a charm.

Posted: Mon May 23, 2005 1:01 pm
by Psychophanta
THCM wrote:I exchanged your old procedure against your new macro and my code does everything in the right order, as you said above. I still have little hicups while moving sprites on screen.

The original FlipBuffers() command works like a charm.
There are no significative difference using macro or procedure.

Original FlipBuffers() works, but only in appearance; it is a deceit because it catch all CPU for itself unnecessarily. Run your Windows tasks manager, run then your program, and then see there what happened; 100% CPU tiem was eaten by it :shock:

Posted: Mon May 23, 2005 1:14 pm
by THCM
FlipBuffers() eats the unused processor time, that's correct, but your approach is causing many hicups. It's hard to notice, but if you move a sprite on a black screen from left to right you'll notice it. Here it only happens, when I use SetFrameRate().

Posted: Mon May 23, 2005 1:57 pm
by Psychophanta
SetFrameRate() must be after OpenScreen(), but before EnumDisplaySettings_() command.

Just do a try:
insert:
Else:Beep_(500,5)
just after:
Sleep_(Int(MustDelay.f))
line in myFlipBuffers macro.
If you hear some beep while executing your program, theorically it should mean that the max time for rendering tasks was reached. But pity:seems the Sleep_() command; it doesn't delay the exact milliseconds in its parameter :cry: , or QueryPerformanceCounter_() or something ... however it is used by some emulators :roll:
So, as i've said the trick is well done in theory, but in practice it is not useful cause some imprecisions of windows timing.
As alternative i suggest the FlipBuffers():Delay(n) option :)