computer speed independent programming

Share your advanced PureBasic knowledge/code with the community.
Hatonastick
Enthusiast
Enthusiast
Posts: 149
Joined: Wed Apr 27, 2005 11:50 am
Location: Adelaide, Australia
Contact:

Post by Hatonastick »

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.
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6166
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

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
( 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... )
El_Choni
TailBite Expert
TailBite Expert
Posts: 1007
Joined: Fri Apr 25, 2003 6:09 pm
Location: Spain

Post by El_Choni »

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?
Just a little bit...

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
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6166
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

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... )
El_Choni
TailBite Expert
TailBite Expert
Posts: 1007
Joined: Fri Apr 25, 2003 6:09 pm
Location: Spain

Post by El_Choni »

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
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6166
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

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
( 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... )
Hatonastick
Enthusiast
Enthusiast
Posts: 149
Joined: Wed Apr 27, 2005 11:50 am
Location: Adelaide, Australia
Contact:

Post by Hatonastick »

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.
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6166
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

i do have strongly my doubts about the waiting for vertical synchronisation of flipbuffers(1)

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... )
Hatonastick
Enthusiast
Enthusiast
Posts: 149
Joined: Wed Apr 27, 2005 11:50 am
Location: Adelaide, Australia
Contact:

Post by Hatonastick »

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.
Last edited by Hatonastick on Wed Jun 08, 2005 12:50 pm, edited 2 times in total.
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6166
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

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... )
Hatonastick
Enthusiast
Enthusiast
Posts: 149
Joined: Wed Apr 27, 2005 11:50 am
Location: Adelaide, Australia
Contact:

Post by Hatonastick »

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.
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6166
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

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...
( 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... )
El_Choni
TailBite Expert
TailBite Expert
Posts: 1007
Joined: Fri Apr 25, 2003 6:09 pm
Location: Spain

Post by El_Choni »

@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
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Post by Rescator »

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()
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.
User avatar
blueznl
PureBasic Expert
PureBasic Expert
Posts: 6166
Joined: Sat May 17, 2003 11:31 am
Contact:

Post by blueznl »

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...
( 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... )
Post Reply