Framerate control problem.
-
- Posts: 2338
- Joined: May 31, 2005 9:59
- Location: Croatia
- Contact:
Framerate control problem.
Can anybody help me with the issue of assuring that my program will run with the same speed on every PC?
I was in belief that using SCREENSYNC in every loop ensures that the game will run with the same speed on every PC but after releasing Vector X 2006 I realized I was wrong.
VSYNC placed in the Vector X 2006's main loop gave 20 FPS on my PC. The game is in 640*480 resolution and 24 bit depth graphics mode. When I released the game people reported 60 or 80 FPS. Just a note that when I turn off all the graphics in my program and just put VSYNC in a loop I still get 20 FPS. So it's not the issue of my computer's speed. At least I think so.
So I would like for someone to explain me how VSYNC is reliable for using to control the program's framerate, when it isn't and for what reasons.
Also, substitutes? SLEEP is NOT millisecond precise. I tried. TIMER? Someone tried with it?
Isn't it possible to code a millisecond perfect delay routine in ASM that would be very reliable and release it in a form of a function, FB users could use?
I was in belief that using SCREENSYNC in every loop ensures that the game will run with the same speed on every PC but after releasing Vector X 2006 I realized I was wrong.
VSYNC placed in the Vector X 2006's main loop gave 20 FPS on my PC. The game is in 640*480 resolution and 24 bit depth graphics mode. When I released the game people reported 60 or 80 FPS. Just a note that when I turn off all the graphics in my program and just put VSYNC in a loop I still get 20 FPS. So it's not the issue of my computer's speed. At least I think so.
So I would like for someone to explain me how VSYNC is reliable for using to control the program's framerate, when it isn't and for what reasons.
Also, substitutes? SLEEP is NOT millisecond precise. I tried. TIMER? Someone tried with it?
Isn't it possible to code a millisecond perfect delay routine in ASM that would be very reliable and release it in a form of a function, FB users could use?
-
- Posts: 5494
- Joined: Sep 12, 2005 20:06
- Location: California
It's not reliable. Think about this, if you VSYNC, and the amount of time for a monitor refresh has already gone by, that means you have to wait one more refresh until your graphics are updated.So I would like for someone to explain me how VSYNC is reliable for using to control the program's framerate, when it isn't and for what reasons.
Timer works more than well. Generally if you don't care about the speed of the machine you're working on and are assuming a certain machine speed, you could always do something like this
Code: Select all
dim t as double
t = timer
dim fps as integer
fps = 60
do
do stuff
do
loop until timer - t >= (1/fps)
t = timer
loop
Screensync isn't used to time your program, but instead wait until the refresh is about to occur. This will cause smoother graphics, because most graphics won't be in the middle of a drawing process/skipping around at the time of flipping.
Try this for your page changing:
Code: Select all
Screenres 640, 480, 32, 2
dim shared workpage as integer
do
screenset workpage, workpage xor 1
draw stuff
workpage xor = 1
loop until whatever...
Also:
Code: Select all
Line(screenx, screeny)-(screenx + width, screeny + height), RGB(255, 255, 255), bf
Last edited by anonymous1337 on Jun 01, 2006 13:41, edited 2 times in total.
-
- Posts: 2338
- Joined: May 31, 2005 9:59
- Location: Croatia
- Contact:
If you need a faster CLS-method, then use this:
Code: Select all
DECLARE SUB FastCLS (screenptr AS ANY PTR = 0, colour AS LONG = 0)
#include once "crt.bi"
declare function memset cdecl alias "memset" (byval as any ptr, byval as integer, byval as size_t) as any ptr
SUB FastCLS (screenptr AS ANY PTR = 0, colour AS LONG = 0)
DIM w, h, depth
SCREENINFO w,h,depth
memset Screenptr, colour, (w * h) * (depth / 8)
END SUB
Screensync is dependant on the user's refresh rate, which could be anything. It also doesn't seem very reliable - I still got tearing graphics when I tried it, and I've also had reports that it broke the Linux version.
Timer might be more accurate than sleep, but it doesn't ever surrender any execution time meaning 100% CPU usage (I'm not sure what screensync does while it's waiting). Sleep is accurate to about 15ms under Windows, I believe. Personally I would use sleep and accept some inaccuracy. It's probably not a significant problem below about 30fps.
Timer might be more accurate than sleep, but it doesn't ever surrender any execution time meaning 100% CPU usage (I'm not sure what screensync does while it's waiting). Sleep is accurate to about 15ms under Windows, I believe. Personally I would use sleep and accept some inaccuracy. It's probably not a significant problem below about 30fps.
-
- Posts: 5494
- Joined: Sep 12, 2005 20:06
- Location: California
55ms... Generally :PPlayerOne wrote:Timer might be more accurate than sleep, but it doesn't ever surrender any execution time meaning 100% CPU usage [. . .] Sleep is accurate to about 15ms under Windows, I believe.
You can always use a sleep n right before you start your timer checking
Code: Select all
sleep 5
do
loop until timer - t > (1/fps)
Alternately if your app is supposed to run at a certain FPS, then figure out the time it should take per frame, and sleep the rest away.
Code: Select all
dim timerStart as double
dim timerEnd as double
do
timerStart = timer
'do all your stuff here
timerEnd = timer
if (timerEnd - timerStart) < 1 then
sleep ( int( (timerEnd-TimerStart) * 100 )
'Sleep for the remainder * 100 to get MS.
end if
end loop
I usually do this...
Works like a charm...let me explain my reasoning briefly...
-I first set the viewpage to 0 and the workpage to 1
-As you start the loop, make sure you set Timei = Timer so that you get the time it takes you to render your scene inside the Frame time calc
-Next you draw your objects to the offscreen workpage to prevent flickerage
-Process Input so that it remains drawn for the longest time possible (ya its redundant in this algo, but its a habbit after years of not double buffering)
-Update variable states as you see fit
-Flip so that you can see your workpage, meanwhile still working off screen so everything on screen is updated at the same time
-The waiting loop, IF computer rendered scene in less time than it was allowed with Seconds Per Frame, then Delay until it reaches that time
The reason I dont use just sleep, mainly is because if you input any key then it will cut it off short...I do use sleep in the loop however because it idles the program when it waits...therefore makeing you not have 100% CPU time...and if you press a key, the loop will just call another sleep
-Erase the scene however you see fit, you are still working in the offscreen buffer so you wont see the erasage...ever
Well thats it, try it out
Thrawn
Code: Select all
'Needs at least 2 Virtual Screen pages
'Set Screen here
'FPS
Const FPS = 30
'Timei is TIMER var
Dim As Double Timei, SecondsPerFrame
'How long each frame should take to be rendered
SecondsPerFrame = 1 / FPS
'Set up virtual vars
Dim As Integer Viewpage = 0, Workpage = 1
Screenset Workpage, Viewpage
Do
Timei = Timer
DrawScene
ProcessInput
UpdateScene
Flip Workpage, Viewpage
Do: Sleep 1: Loop While Timer - Timei <= SecondsPerFrame
EraseScene
Loop Until SomeCondition
-I first set the viewpage to 0 and the workpage to 1
-As you start the loop, make sure you set Timei = Timer so that you get the time it takes you to render your scene inside the Frame time calc
-Next you draw your objects to the offscreen workpage to prevent flickerage
-Process Input so that it remains drawn for the longest time possible (ya its redundant in this algo, but its a habbit after years of not double buffering)
-Update variable states as you see fit
-Flip so that you can see your workpage, meanwhile still working off screen so everything on screen is updated at the same time
-The waiting loop, IF computer rendered scene in less time than it was allowed with Seconds Per Frame, then Delay until it reaches that time
The reason I dont use just sleep, mainly is because if you input any key then it will cut it off short...I do use sleep in the loop however because it idles the program when it waits...therefore makeing you not have 100% CPU time...and if you press a key, the loop will just call another sleep
-Erase the scene however you see fit, you are still working in the offscreen buffer so you wont see the erasage...ever
Well thats it, try it out
Thrawn
But why not see for yourself?
An example I whipped up...it will keep it close to the FPS specified as possible on any computer and no flickerage whatsoever
(Try clicking, see how many balls you can make...when the FPS start to drop, it isnt cause the Algo is faulty, its cause its doing so many calcs that it cant pysically Render in the time alotted to meet the FPS)
Thrawn
An example I whipped up...it will keep it close to the FPS specified as possible on any computer and no flickerage whatsoever
(Try clicking, see how many balls you can make...when the FPS start to drop, it isnt cause the Algo is faulty, its cause its doing so many calcs that it cant pysically Render in the time alotted to meet the FPS)
Code: Select all
'Needs at least 2 Virtual Screen pages
Screen 19, 32, 2
DefInt A-Z
Option Explicit
Randomize Timer
'FPS
Const FPS = 30
Const SCRX = 800
Const SCRY = 600
Type Col_Type
RR As Integer
GG As Integer
BB As Integer
End Type
Type _2DVector_Type
x As Integer
y As Integer
End Type
Type Ball_Type
x As Integer
y As Integer
OldX As Integer
OldY As Integer
R As Integer
Col As Col_Type
Vector As _2DVector_Type
End Type
Declare Function RandomInt(High As Integer, Low As Integer)
'Note this is a basic example, I was gonna do stuff more complex, but decided not, so please dont pick at my inefficencys ;-)
ReDim Shared As Ball_Type Balls(0)
'Set Up First Ball
Balls(0).R = RandomInt(50,10)
Balls(0).x = RandomInt((SCRX - Balls(0).R), Balls(0).R)
Balls(0).y = RandomInt((SCRY - Balls(0).R), Balls(0).R)
Balls(0).Col.RR = RandomInt(255,30)
Balls(0).Col.GG = RandomInt(255,30)
Balls(0).Col.BB = RandomInt(255,30)
Balls(0).Vector.x = RandomInt(3, -3)
Balls(0).Vector.y = RandomInt(3, -3)
If Balls(0).Vector.x = 0 And Balls(0).Vector.y = 0 Then Balls(0).Vector.x = 2: Balls(0).Vector.y = RandomInt(3, -3)
'Timei is TIMER var
Dim As Double Timei, SecondsPerFrame
'How long each frame should take to be rendered
SecondsPerFrame = 1 / FPS
'Set up virtual vars
Dim As Integer Viewpage = 0, Workpage = 1
Declare Function DrawScene()
Declare Function ProcessInput()
Declare Function UpdateScene()
Declare Function EraseScene()
Declare Function AddBall()
'Calc FPS
Dim FrameCount As Integer
Dim FPSTimei As Double
FrameCount = 0
Screenset Workpage, Viewpage
FPSTimei = Timer
Do
Timei = Timer
DrawScene
ProcessInput
UpdateScene
Flip Workpage, Viewpage
Do: Sleep 1: Loop While Timer - Timei <= SecondsPerFrame
EraseScene
FrameCount += 1
If Timer - FPSTimei >= 1 Then FPSTimei = Timer: WindowTitle "FPS: " & FrameCount: FrameCount = 1
Loop While Not Multikey(&h1)
Function ProcessInput()
Dim As Integer MX, MY, MB
GetMouse MX, MY, ,MB
If MB And 1 Then AddBall()
End Function
Function UpdateScene()
Dim AS Integer i
For i = LBound(Balls) To UBound(Balls)
Balls(i).x += Balls(i).Vector.x
Balls(i).y += Balls(i).Vector.y
If Balls(i).x + Balls(i).R >= SCRX Then Balls(i).x = SCRX - Balls(i).R: Balls(i).Vector.x *= -1
If Balls(i).y + Balls(i).R >= SCRY Then Balls(i).y = SCRY - Balls(i).R: Balls(i).Vector.y *= -1
If Balls(i).x - Balls(i).R <= 0 Then Balls(i).x = Balls(i).R: Balls(i).Vector.x *= -1
If Balls(i).y - Balls(i).R <= 0 Then Balls(i).y = Balls(i).R: Balls(i).Vector.y *= -1
Next i
End Function
Function DrawScene()
Dim As Integer i
For i = LBound(Balls) To UBound(Balls)
Circle(Balls(i).x, Balls(i).y), Balls(i).R, RGB(Balls(i).Col.RR, Balls(i).Col.GG, Balls(i).Col.BB),,,,F
Balls(i).OldX = Balls(i).X
Balls(i).OldY = Balls(i).Y
Next i
End Function
Function EraseScene()
Dim As Integer i
For i = LBound(Balls) To UBound(Balls)
Circle(Balls(i).OldX, Balls(i).OldY), Balls(i).R, RGB(0,0,0),,,,F
Next i
End Function
Function AddBall()
Dim As Integer i
'Preserve
ReDim Temp_Balls(LBound(Balls) To UBound(Balls)) As Ball_Type
For i = LBound(Balls) To Ubound(Balls)
Temp_Balls(i) = Balls(i)
Next i
ReDim Balls(LBound(Temp_Balls) To (Ubound(Temp_Balls) + 1)) As Ball_Type
For i = LBound(Temp_Balls) To UBound(Temp_Balls)
Balls(i) = Temp_Balls(i)
Next i
Balls(UBound(Balls)).R = RandomInt(50,10)
Balls(UBound(Balls)).x = RandomInt((SCRX - Balls(UBound(Balls)).R), Balls(UBound(Balls)).R)
Balls(UBound(Balls)).y = RandomInt((SCRY - Balls(UBound(Balls)).R), Balls(UBound(Balls)).R)
Balls(UBound(Balls)).Col.RR = RandomInt(255,30)
Balls(UBound(Balls)).Col.GG = RandomInt(255,30)
Balls(UBound(Balls)).Col.BB = RandomInt(255,30)
Balls(UBound(Balls)).Vector.x = RandomInt(3, -3)
Balls(UBound(Balls)).Vector.y = RandomInt(3, -3)
If Balls(UBound(Balls)).Vector.x = 0 And Balls(UBound(Balls)).Vector.y = 0 Then Balls(UBound(Balls)).Vector.x = 2: Balls(UBound(Balls)).Vector.y = RandomInt(3, -3)
End Function
Function RandomInt(High As Integer, Low As Integer)
RandomInt = Int(Rnd * (High - Low + 1) + Low)
End Function
Actually the windows timer does have a resolution of about 55ms, it has to do with an internal clock that cannot accurately represent the ticks of time, in fact its off by about 55ms, which means that if you ask for a delay of 1 ms, you could get between 1 and 56 ms of delay, depending on the state of the internal counter when you asked for the delay.
Why not use the windows performance timer? I have some code from rapidQ but I can't get it to work in FB (undef ref in kernal32)
Sorry UNIX dudes, this is a windows thang.
JohnK
Code: Select all
Declare Function QueryPerformanceCounter Lib "kernel32" (BYREF lpPerformanceCount As ULONGINT) As Long
Declare Function QueryPerformanceFrequency Lib "kernel32" (BYREF lpFrequency As ULONGINT) As Long
FUNCTION microTIMER() AS DOUBLE
STATIC AlreadyInit AS INTEGER
STATIC myFreq as DOUBLE
DIM TimerFreq as ULONGINT
DIM TimerMS as ULONGINT
DIM myResult as ULONGINT
IF AlreadyInit = 0 THEN 'get our timer resolution
QueryPerformanceFrequency(TimerFreq)
AlreadyInit = 1
IF TimerFreq > = 0 THEN 'do we have a timer?
myFreq = TimerFreq
ELSE
RESULT = 0
EXIT FUNCTION
END IF
END IF
QueryPerformanceCounter(TimerMS)
microTIMER = TimerMS / myFreq 'result is in relative time
END FUNCTION
'sample code
DIM a AS ULONGINT
a = microTimer 'always call at least once to initialize
a = microTimer
' 'wait for 3.5 milliseconds
DO
Loop until (microTimer - a) > 0.00350
JohnK
This is a quick and dirty app that measures the effective resolution of various timers. The code essentially determines the mean of the step changes in the return values.
Typical results under Windows 2000:
Typical results under Windows 98 SE:
As you can see, the effective resolution of the Performance Counter, and the TIMER function which is based on the Performance Counter, is far lower than the resolution implied by the Performance Frequency.
Code: Select all
#include once "windows.bi"
dim as integer i
dim as ulongint pcfreq, count1, count2
dim as double t1, t2, accum
SetPriorityClass( GetCurrentProcess(), HIGH_PRIORITY_CLASS )
'***************************************************************
for i = 1 to 1000000
t1 = timer
do
t2 = timer
loop until t2 > t1
accum += t2 - t1
next
print using "TIMER:##.## us";accum
print
'***************************************************************
accum = 0
if QueryPerformanceFrequency(cast(PLARGE_INTEGER,@pcfreq)) then
for i = 1 to 1000000
QueryPerformanceCounter( cast(PLARGE_INTEGER, @count1) )
do
QueryPerformanceCounter( cast(PLARGE_INTEGER, @count2) )
loop until count2 > count1
accum += count2 - count1
next
print "PerformanceFrequency: ";pcfreq;" Hz"
print
accum /= pcfreq
print using "PerformanceCounter:##.## us";accum
print
endif
'***************************************************************
accum = 0
for i = 1 to 1000
t1 = GetTickCount
do
t2 = GetTickCount
loop until t2 > t1
accum += t2 - t1
next
accum /= 1000 ' Must adjust for return value in ms.
print using "TickCount:###.## ms";accum
print
'***************************************************************
accum = 0
for i = 1 to 1000
t1 = timer
sleep 1
t2 = timer
accum += t2 - t1
next
print using "SLEEP:###.## ms";accum
print
'***************************************************************
SetPriorityClass( GetCurrentProcess(), NORMAL_PRIORITY_CLASS )
sleep
Code: Select all
TIMER: 1.79 us
PerformanceFrequency: 3579545 Hz
PerformanceCounter: 1.73 us
TickCount: 10.01 ms
SLEEP: 10.01 ms
Code: Select all
TIMER: 6.38 us
PerformanceFrequency: 1193180 Hz
PerformanceCounter: 6.13 us
TickCount: 5.00 ms
SLEEP: 4.98 ms