Save CPU time instead of wait for vsync

Share your advanced PureBasic knowledge/code with the community.
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

Save CPU time instead of wait for vsync

Post 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.
thefool
Always Here
Always Here
Posts: 5875
Joined: Sat Aug 30, 2003 5:58 pm
Location: Denmark

Post 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.
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

Post 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.
User avatar
tinman
PureBasic Expert
PureBasic Expert
Posts: 1102
Joined: Sat Apr 26, 2003 4:56 pm
Location: Level 5 of Robot Hell
Contact:

Re: Save CPU time instead of wait for vsync

Post 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.
If you paint your butt blue and glue the hole shut you just themed your ass but lost the functionality.
(WinXPhSP3 PB5.20b14)
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

Post 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:
thefool
Always Here
Always Here
Posts: 5875
Joined: Sat Aug 30, 2003 5:58 pm
Location: Denmark

Post 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.
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

Post 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
http://www.zeitgeistmovie.com

while (world==business) world+=mafia;
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

Post 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
http://www.zeitgeistmovie.com

while (world==business) world+=mafia;
THCM
Enthusiast
Enthusiast
Posts: 276
Joined: Fri Apr 25, 2003 5:06 pm
Location: Gummersbach - Germany
Contact:

Post by THCM »

The testsnip is missing a FlipBuffers() or did I miss something here?
The Human Code Machine / Masters' Design Group
THCM
Enthusiast
Enthusiast
Posts: 276
Joined: Fri Apr 25, 2003 5:06 pm
Location: Gummersbach - Germany
Contact:

Post 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?
The Human Code Machine / Masters' Design Group
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

Post 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.
Last edited by Psychophanta on Mon May 23, 2005 12:56 pm, edited 1 time in total.
http://www.zeitgeistmovie.com

while (world==business) world+=mafia;
THCM
Enthusiast
Enthusiast
Posts: 276
Joined: Fri Apr 25, 2003 5:06 pm
Location: Gummersbach - Germany
Contact:

Post 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.
The Human Code Machine / Masters' Design Group
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

Post 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:
http://www.zeitgeistmovie.com

while (world==business) world+=mafia;
THCM
Enthusiast
Enthusiast
Posts: 276
Joined: Fri Apr 25, 2003 5:06 pm
Location: Gummersbach - Germany
Contact:

Post 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().
The Human Code Machine / Masters' Design Group
User avatar
Psychophanta
Always Here
Always Here
Posts: 5153
Joined: Wed Jun 11, 2003 9:33 pm
Location: Anare
Contact:

Post 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 :)
http://www.zeitgeistmovie.com

while (world==business) world+=mafia;
Post Reply