computer speed independent programming
-
- Enthusiast
- Posts: 149
- Joined: Wed Apr 27, 2005 11:50 am
- Location: Adelaide, Australia
- Contact:
Blueznl: I'm very curious to see where you are going with this. BTW which timer returns a 64 bit number? I've only looked at timeGetTime_() so far.
My current system seems to work, but that's only because of the way the game (calling it a game when I still haven't gotten past the titlescreen :roll: ) is laid out.
My current system seems to work, but that's only because of the way the game (calling it a game when I still haven't gotten past the titlescreen :roll: ) is laid out.
okay!
i think i got it, haven't coded it yet, but it makes sense...
part 1: retrieve divider
1. get frequency of hpc
2. keep shifting the 64 bit value to the right (ie. up) until bit 30 becomes 1
3. this is the max frequency that can be handled in 31 bits
4. remember the divider
part 2: retrieve counter
1. get counter
2. shift it 'divider' times
3. clear highest bit (make sure only 31 bit ints are returned)
3. return long int (first four bytes)
this is the usable part of the counter, in 31 bits, that wraps in approx 1 second
now let's say something should run at 75 hz, you would have to wait hpc_frequency / wanted_frequency = ticks
i think i got it, haven't coded it yet, but it makes sense...
part 1: retrieve divider
1. get frequency of hpc
2. keep shifting the 64 bit value to the right (ie. up) until bit 30 becomes 1
3. this is the max frequency that can be handled in 31 bits
4. remember the divider
part 2: retrieve counter
1. get counter
2. shift it 'divider' times
3. clear highest bit (make sure only 31 bit ints are returned)
3. return long int (first four bytes)
this is the usable part of the counter, in 31 bits, that wraps in approx 1 second
now let's say something should run at 75 hz, you would have to wait hpc_frequency / wanted_frequency = ticks
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB)
( The path to enlightenment and the PureBasic Survival Guide right here... )
( The path to enlightenment and the PureBasic Survival Guide right here... )
Just a little bit...hey el choni, pupil, thx Smile euhm... it can operate on the same 64 bit block, ie. there are no different source and destinations...
would that be faster?
Code: Select all
Procedure MMXShiftQuadRight(*source.Quad, shift.l)
!MOVD mm1, [esp+4]
!MOV eax, [esp]
!MOVQ mm0, [eax]
!PSRLQ mm0, mm1
; !MOV eax, [esp+4]
!MOVQ [eax], mm0
!EMMS ; clear floating point tag word
EndProcedure
El_Choni
Code: Select all
; use hpc without 64 bits - concept - dunno if this is the right way to do it :-)
; yes, i know, with a few assumptions you could make this easier, but this should work on any platform and counter speed
InitSprite()
z = 10
Dim list.l(z)
Procedure.s x_peekbin(addr.l,Length.l,mode.l)
Protected n.l, s.s
;
If mode = 0
;
; big endian or byte sequence
;
For n = 0 To Length-1
s = s+RSet(Bin(PeekB(addr+n) & $FF),8,"0")
Next n
ProcedureReturn s
;
ElseIf mode = 1
;
; little endian
;
For n = 0 To Length-1
s = RSet(Bin(PeekB(addr+n) & $FF),8,"0")+s
Next n
ProcedureReturn s
;
Else
;
; special mode for debugging purposes, little endian with separation spaces, THIS USE MAY CHANGE
;
For n = 0 To Length-1
s = RSet(Bin(PeekB(addr+n) & $FF),8,"0")+s
If n < Length -1
s = " "+s
EndIf
Next n
ProcedureReturn s
;
EndIf
;
EndProcedure
Procedure MMXShiftQuadRight(*source.LARGE_INTEGER, *dest.LARGE_INTEGER, shift.l)
!MOVD mm1, [esp+8]
!MOV eax, [esp]
!MOVQ mm0, [eax]
!PSRLQ mm0, mm1
!MOV eax, [esp+4]
!MOVQ [eax], mm0
EndProcedure
sixtyfour.LARGE_INTEGER
QueryPerformanceFrequency_(@sixtyfour)
; PokeB(@sixtyfour+7,1) ; this poke is only for testing
Debug x_peekbin(@sixtyfour,8,2)
; shift down so freq will fit in 32 bits (which it does without shifting, by the way :-))
divider = 1
While PeekB(@sixtyfour+7) <> 0 Or PeekB(@sixtyfour+6) <> 0 Or PeekB(@sixtyfour+5) <> 0 Or PeekB(@sixtyfour+4) <> 0
MMXShiftQuadRight(@sixtyfour,@sixtyfour,1)
divider = divider+1
Wend
Debug x_peekbin(@sixtyfour,8,2)
; shift down one more to make sure there's 31 bits so no negative numbers (this will halve accuracy though)
MMXShiftQuadRight(@sixtyfour,@sixtyfour,1)
divider = divider+1
Debug x_peekbin(@sixtyfour,8,2)
frequency.l = PeekL(@sixtyfour)/2 ; dunno why, appears to work
Debug "divider "+Str(divider)
Debug "frequency "+Str(frequency) ; on my machine 1.7 megaherz which is a little disappointing
Debug "1 sec = "+Str(frequency)+" ticks"
Debug "100 millisec = 1/10 sec = 10 hz takes "+Str(frequency/10)+" ticks"
; and now a 32 bit counter that wraps at roughly 1 second or more, play a little with the delay
For n = 1 To z
QueryPerformanceCounter_(@sixtyfour)
MMXShiftQuadRight(@sixtyfour,@sixtyfour,divider)
counter.l = PeekL(@sixtyfour) & $7FFFFFFF
Delay(100) ; 100 milliscond, or frequency / 10 ticks, mismatch is caused by overhead
list(n) = counter
Next n
For n = 1 To z
Debug Str(list(n))+" "+Str(list(n)-list(n-1))
Next n
; now do something at 75 hz
interval = frequency / 175
Debug "75 hz = 1/75 sec takes "+Str(interval)+" ticks"
QueryPerformanceCounter_(@sixtyfour)
MMXShiftQuadRight(@sixtyfour,@sixtyfour,divider)
last.l = PeekL(@sixtyfour) & $7FFFFFFF
n = 0
While n < 10
QueryPerformanceCounter_(@sixtyfour)
MMXShiftQuadRight(@sixtyfour,@sixtyfour,divider)
counter.l = PeekL(@sixtyfour) & $7FFFFFFF
If counter < last ; gotta wrap, what now?
EndIf
If counter > last+interval
last.l = last+interval
n = n+1
list(n) = counter
EndIf
Wend
For n = 1 To z
Debug Str(list(n))+" "+Str(list(n)-list(n-1))
Next n
; the simple way is simply take the frequency, assume it's below 32 bits (which it will be without a doubt)
; then use the last 32 bits of the counter, done... that's a lot simpler than the above :-)
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB)
( The path to enlightenment and the PureBasic Survival Guide right here... )
( The path to enlightenment and the PureBasic Survival Guide right here... )
Hi, made a test with some of your previous code, I'm not sure if this is what you're looking for:
Code: Select all
InitSprite()
w = 200
h = 200
window_nr = 1
window_h = OpenWindow(window_nr,0,0,w,h,#PB_Window_ScreenCentered|#PB_Window_SystemMenu,"test")
OpenWindowedScreen(window_h,0,0,w,h,0,0,0)
QueryPerformanceFrequency_(@sixtyfour.LARGE_INTEGER)
hpc_freq.l
!FILD qword [v_sixtyfour]
!FISTP dword [v_hpc_freq]
Debug "Frequency: "+Str(hpc_freq)+" Hz" ; maybe this value isn't needed?
wanted_frequency = 75 ; Hz, set at will
dt.l
Repeat
event = WindowEvent()
FlipBuffers(1)
QueryPerformanceCounter_(@sixtyfour1.LARGE_INTEGER)
FlipBuffers(1)
QueryPerformanceCounter_(@sixtyfour2.LARGE_INTEGER)
!FILD qword [v_sixtyfour1]
!FILD qword [v_sixtyfour2]
!FSUB st0, st1
!FISTP dword [v_dt]
!FFREE st1
Debug dt
If dt>wanted_frequency
Delay(Int(((1/wanted_frequency)-(1/dt))*1000))
EndIf
Until event=513 Or event=#PB_Event_CloseWindow
El_Choni
euhm, yes and no 
you need to know the frequency as that is the speed with which the counter changes
so to do something every 1/75 th second (ie. 75 hz) you have to wait until hpc_frequency / wanted_frequency in counter ticks have passed
this, in itself, would not be immediately tied into flipbuffers though it could be used as you showed
got some strange values with your code though, gotta' look into it a little bit more

you need to know the frequency as that is the speed with which the counter changes
so to do something every 1/75 th second (ie. 75 hz) you have to wait until hpc_frequency / wanted_frequency in counter ticks have passed
this, in itself, would not be immediately tied into flipbuffers though it could be used as you showed
got some strange values with your code though, gotta' look into it a little bit more
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB)
( The path to enlightenment and the PureBasic Survival Guide right here... )
( The path to enlightenment and the PureBasic Survival Guide right here... )
-
- Enthusiast
- Posts: 149
- Joined: Wed Apr 27, 2005 11:50 am
- Location: Adelaide, Australia
- Contact:
Ok here's some stuff I've been looking into. It's suits my situation, but it might not suit most other people. From the SDL library website:
Timers in games
It is better to move objects in the game based on time rather than framerate, this produces consistent gameplay on both fast and slow systems.
#TickInterval = 30
Global TickNext
timeBeginPeriod_(1)
; Game timer - Counts down from #TickInterval using system timer
Procedure.l TimeLeft()
now = timeGetTime_()
If (TickNext <= now)
TickNext = now + #TickInterval
ProcedureReturn 0
EndIf
ProcedureReturn (TickNext - now)
EndProcedure
; Our main program loop
Repeat
timer = TimeLeft()
If timer = 0
; Do stuff here relating to drawing
EndIf
; Prevent system chewing up time - this might interfere with the
; above however...
Delay(1)
key$ = Inkey()
Until key$ <> ""
timeEndPeriod_(1)
Now the bizarre thing is that I always seem to end up with a speed difference on Win2k and WinXP. My 2k machines are faster even though the system architecture should make those machines slower than the XP machine.
Timers in games
It is better to move objects in the game based on time rather than framerate, this produces consistent gameplay on both fast and slow systems.
#TickInterval = 30
Global TickNext
timeBeginPeriod_(1)
; Game timer - Counts down from #TickInterval using system timer
Procedure.l TimeLeft()
now = timeGetTime_()
If (TickNext <= now)
TickNext = now + #TickInterval
ProcedureReturn 0
EndIf
ProcedureReturn (TickNext - now)
EndProcedure
; Our main program loop
Repeat
timer = TimeLeft()
If timer = 0
; Do stuff here relating to drawing
EndIf
; Prevent system chewing up time - this might interfere with the
; above however...
Delay(1)
key$ = Inkey()
Until key$ <> ""
timeEndPeriod_(1)
Now the bizarre thing is that I always seem to end up with a speed difference on Win2k and WinXP. My 2k machines are faster even though the system architecture should make those machines slower than the XP machine.
i do have strongly my doubts about the waiting for vertical synchronisation of flipbuffers(1)
try this
try this
Code: Select all
InitSprite()
w = 200
h = 200
window_nr = 1
window_h = OpenWindow(window_nr,0,0,w,h,#PB_Window_ScreenCentered|#PB_Window_SystemMenu,"test")
OpenWindowedScreen(window_h,0,0,w,h,0,0,0)
QueryPerformanceFrequency_(@sixtyfour.LARGE_INTEGER)
hpc_f = PeekL(@sixtyfour)
Debug "frequency "+Str(f)
d1 = Second(Date())
Repeat
event = WindowEvent()
FlipBuffers(1)
QueryPerformanceCounter_(@sixtyfour.LARGE_INTEGER)
t1 = PeekL(@sixtyfour)
x1 = gettickcount_()
FlipBuffers(1)
QueryPerformanceCounter_(@sixtyfour.LARGE_INTEGER)
t2 = PeekL(@sixtyfour)
x2 = gettickcount_()
dt = t2-t1
dx = x2-x1
Debug Str(t1)+" "+Str(t2)+" "+Str(dt)
Debug Str(x1)+" "+Str(x2)+" "+Str(dx)
Debug ""
frames = frames+2
Until event=513 Or event=#PB_Event_CloseWindow
d2 = Second(Date())
dd = d2-d1
Debug Str(d1)+" "+Str(d2)+" "+Str(dd)
Debug Str(dd)+" seconds "+Str(frames)+" "+Str(frames/dd)+" frames per second"
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB)
( The path to enlightenment and the PureBasic Survival Guide right here... )
( The path to enlightenment and the PureBasic Survival Guide right here... )
-
- Enthusiast
- Posts: 149
- Joined: Wed Apr 27, 2005 11:50 am
- Location: Adelaide, Australia
- Contact:
I just worked out that with my use of timeGetTime_(), or GetTickCount_() (even with timeBeingPeriod_(1) etc.) that the WinXP machine is always half as fast as the Win2k machine. Is this related to the 64 bit problem that you are discussing? Assuming that the Win2k machine returns a 32 bit number and the WinXP returns a 64 bit number.... I really need some suggestions on this as it's driving me batty. I can't find anything in the API documents to suggest why this is happening.
Forget I asked, I understand what you guys are doing now. I'm an idiot. I'll just go suck my thumb in the corner with the dunce cap on my head.
My problem is getting timing under the console working across different platforms which is a different problem to the one you are both working to solve.
Forget I asked, I understand what you guys are doing now. I'm an idiot. I'll just go suck my thumb in the corner with the dunce cap on my head.

My problem is getting timing under the console working across different platforms which is a different problem to the one you are both working to solve.
Last edited by Hatonastick on Wed Jun 08, 2005 12:50 pm, edited 2 times in total.
Code: Select all
InitSprite()
OpenScreen(800,600,32,"")
Delay(10)
z = 1000
Dim list.s(z)
QueryPerformanceFrequency_(@sixtyfour.LARGE_INTEGER)
hpc_f = PeekL(@sixtyfour)
Debug "frequency "+Str(f)
d1 = Second(Date())
Repeat
FlipBuffers(1)
Delay(10)
QueryPerformanceCounter_(@sixtyfour.LARGE_INTEGER)
t1 = PeekL(@sixtyfour)
x1 = gettickcount_()
FlipBuffers(1)
Delay(10)
QueryPerformanceCounter_(@sixtyfour.LARGE_INTEGER)
t2 = PeekL(@sixtyfour)
x2 = gettickcount_()
dt = t2-t1
dx = x2-x1
n = n+1
list(n) = Str(t1)+" "+Str(t2)+" "+Str(dt)
n = n+1
list(n) = Str(x1)+" "+Str(x2)+" "+Str(dx)
n = n+1
list(n) = ""
frames = frames+2
Until n >= z-10
CloseScreen()
d2 = Second(Date())
dd = d2-d1
For nn = 1 To n
Debug list(nn)
Next nn
Debug Str(d1)+" "+Str(d2)+" "+Str(dd)
Debug Str(dd)+" seconds "+Str(frames)+" "+Str(frames/dd)+" frames per second"
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB)
( The path to enlightenment and the PureBasic Survival Guide right here... )
( The path to enlightenment and the PureBasic Survival Guide right here... )
-
- Enthusiast
- Posts: 149
- Joined: Wed Apr 27, 2005 11:50 am
- Location: Adelaide, Australia
- Contact:
Woo hoo! Woo hoo! Yes! *Dances about the room like an idiot* Well I've finally gotten a solution to my timing problem under the console - it won't help you guys I'm afraid, but finally I seem to have the same speed on a Win2k box and WinXp with my console game. I'm, sorry to say, that I've wasted 3 days trying to work this out. Possibly more... I'm surprised I've still got hair and the computer is still in one piece... Anyway I hope you guys get the timing issue for "normal" pb games under control.
add a few notes...
1. it appears flipbuffers() is somewhat flawed... and i seem to vaguely recall something about that, yes, that it eats op cpu time...
2. a quick search on the internet shows some issues with direct-x's 'wait for vertical blank' so i assume flipbuffers is using that function internally
3. windows (not being 'really' pre-emptive) doesn't properly update the 'high performance counter' if it's tied up in other things (such as waiting (!) for a vblank)
more to come...
1. it appears flipbuffers() is somewhat flawed... and i seem to vaguely recall something about that, yes, that it eats op cpu time...
2. a quick search on the internet shows some issues with direct-x's 'wait for vertical blank' so i assume flipbuffers is using that function internally
3. windows (not being 'really' pre-emptive) doesn't properly update the 'high performance counter' if it's tied up in other things (such as waiting (!) for a vblank)
more to come...
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB)
( The path to enlightenment and the PureBasic Survival Guide right here... )
( The path to enlightenment and the PureBasic Survival Guide right here... )
@bluenzl: you can't aassume that a 64 bit result will fit in 32 bit. It doesn't here. I've modified your code a bit, although I admit I don't understand what it does very well...
Code: Select all
Procedure DSub(*BigDouble.LARGE_INTEGER, *SmallDouble.LARGE_INTEGER, *LongResult.LONG)
!MOV eax, [esp]
!FILD qword [eax]
!MOV eax, [esp+4]
!FILD qword [eax]
!FSUB st0, st1
!MOV eax, [esp+8]
!FISTP dword [eax]
!FFREE st1
EndProcedure
InitSprite()
OpenWindow(0, 100, 100, 800, 600, #PB_Window_SystemMenu, "hola")
OpenWindowedScreen(WindowID(), 0, 0, 800,600, 0, 0, 0)
Delay(10)
z = 1000
Dim list.s(z)
QueryPerformanceFrequency_(@sixtyfour.LARGE_INTEGER)
hpc_f = PeekL(@sixtyfour)
Debug "frequency "+Str(hpc_f)
d1 = Second(Date())
dt.l
Repeat
FlipBuffers(1)
Delay(10)
QueryPerformanceCounter_(@sixtyfour1.LARGE_INTEGER)
x1 = GetTickCount_()
FlipBuffers(1)
Delay(10)
QueryPerformanceCounter_(@sixtyfour2.LARGE_INTEGER)
DSub(@sixtyfour1, @sixtyfour2, @dt)
x2 = GetTickCount_()
dx = x2-x1
list(n+1) = Str(dt)
list(n+2) = Str(dx)
list(n+3) = ""
n+3
frames+2
Until n >= z-10
CloseScreen()
d2 = Second(Date())
dd = d2-d1
For nn = 1 To n
Debug list(nn)
Next nn
Debug Str(d1)+" "+Str(d2)+" "+Str(dd)
Debug Str(dd)+" seconds "+Str(frames)+" frames "+Str(frames/dd)+" frames per second"
El_Choni
Maybe make a alternate code too that uses Delay(0)
it won't hog the cpu but will use whatever cpu is available for the taking!
From Windows SDK Sleep() function, and applicable to Delay() as they are practicaly the same! Fred said once that is was basicaly a wrapper for Sleep()
it won't hog the cpu but will use whatever cpu is available for the taking!
From Windows SDK Sleep() function, and applicable to Delay() as they are practicaly the same! Fred said once that is was basicaly a wrapper for Sleep()
Parameters
dwMilliseconds
[in] Minimum time interval for which execution is to be suspended, in milliseconds.
A value of zero causes the thread to relinquish the remainder of its time slice to any other thread of equal priority that is ready to run. If there are no other threads of equal priority ready to run, the function returns immediately, and the thread continues execution.
A value of INFINITE indicates that the suspension should not time out.
Return Values
This function does not return a value.
Remarks
This function causes a thread to relinquish the remainder of its time slice and become unrunnable for at least the specified number of milliseconds, after which the thread is ready to run. In particular, if you specify zero milliseconds, the thread will relinquish the remainder of its time slice but remain ready. Note that a ready thread is not guaranteed to run immediately. Consequently, the thread may not run until some time after the specified interval elapses. For more information, see Scheduling Priorities.
el choni, the onli thing that counts is if frequency fits in 32 bits
if it always does, i don't have to pay attention to the higher bits of counter, just have to cover wrap around
think i'm on the right track now
- i can use hpcounter for timed operations, ie. code that should run same speed on any machine, this would be thread 1 in a game, the part that does logic
- i'm thinking about a 'smart' replacement for flipbuffers, modifying your sample somewhat, that releases all available time back to windows until it's very close to the next flipbuffers event, this would be thread 2 in a game, the part that does graphics
gotta think this over a little more...
if it always does, i don't have to pay attention to the higher bits of counter, just have to cover wrap around
think i'm on the right track now
- i can use hpcounter for timed operations, ie. code that should run same speed on any machine, this would be thread 1 in a game, the part that does logic
- i'm thinking about a 'smart' replacement for flipbuffers, modifying your sample somewhat, that releases all available time back to windows until it's very close to the next flipbuffers event, this would be thread 2 in a game, the part that does graphics
gotta think this over a little more...
( PB6.00 LTS Win11 x64 Asrock AB350 Pro4 Ryzen 5 3600 32GB GTX1060 6GB)
( The path to enlightenment and the PureBasic Survival Guide right here... )
( The path to enlightenment and the PureBasic Survival Guide right here... )