PDA

View Full Version : QueryPerformanceCounter problem and questions



lordzero
29-03-2008, 03:08 AM
Hello

I'm trying change GetTickCount from my codes to QueryPerformanceCounter

code:



type TCustomTimer = class
private
PerfFrequency: Int64;
InvPerfFrequency: Single;
public
function gettime: Single;
constructor Create;
end;

type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }

procedure Teste(Sender: TObject; var Done: Boolean);
end;

var
Form1: TForm1;
clock: TCustomTimer;
_start, _end: Double;


implementation

{$R *.dfm}
///////////////
{ TCustomTimer }

procedure TForm1.Teste(Sender: TObject; var Done: Boolean);
begin

_end := clock.gettime;



//1000 = frequencia de intervalo em milesegundos


if (_end-_start) >= 1000 then
begin

memo1.Lines.Add('evento');
_start := _end;


end;

end;

constructor TCustomTimer.Create;
begin
QueryPerformanceFrequency(PerfFrequency);
InvPerfFrequency := 1.0 / PerfFrequency;
end;

function TCustomTimer.gettime: Single;
var
Time: Int64;
begin
QueryPerformanceCounter(Time);
Result := 1000 * InvPerfFrequency * Time;
end;
///////////////

procedure TForm1.FormCreate(Sender: TObject);
begin


clock := TCustomTimer.Create;


_start := clock.gettime;

application.OnIdle := Form1.Teste;


end;

procedure TForm1.FormDestroy(Sender: TObject);
begin

Clock.Free;

end;


my code is ok?


my question...

what is this line?


InvPerfFrequency := 1.0 / PerfFrequency;

waran
29-03-2008, 07:30 AM
You don't know what your own code does :?:
I guess you are calculating the inverse of the frequency since
you don't have to divide (slow) each time you get your time,
but multiply instead.

Your code looks correct to me.
Maybe you should use double because single suffers from
rounding errors using such big numbers.

lordzero
29-03-2008, 03:16 PM
You don't know what your own code does :?:
I guess you are calculating the inverse of the frequency since
you don't have to divide (slow) each time you get your time,
but multiply instead.

Your code looks correct to me.
Maybe you should use double because single suffers from
rounding errors using such big numbers.

I dont have created all of this code, and this is why i''m asking..

about queryperformancecounter sometimes this can fail in some hardware? and i need use a timer with low precision(GetTickCount)?

i know about this reading some webpage... actually this is not much clear...

waran
29-03-2008, 07:39 PM
The PerformanceCounter works in Hardware if equipped (which is common nowadays).
If no PerformanceCounter is available QueryPerformanceFrequency will return 0 (false) - you have to use something else then.

lordzero
29-03-2008, 08:57 PM
The PerformanceCounter works in Hardware if equipped (which is common nowadays).
If no PerformanceCounter is available QueryPerformanceFrequency will return 0 (false) - you have to use something else then.

gettickcount for example?

about queryperformancecounter what value it return? cpu cycles?

waran
29-03-2008, 09:21 PM
The PerformanceCounter, unlike rdtsc, runs independent from your CPU
and the frequency can vary (simply called "ticks per second").
Its an independent hardware part on your mainboard.
Ticks divided with the frequency gives you seconds.

Yes, you can use GetTickCount instead since its purely software-based
(like SDL_GetTicks or Windows' Multimedia-Timer). But the PerfCounter
is usually the best choice if available.

lordzero
29-03-2008, 11:46 PM
The PerformanceCounter, unlike rdtsc, runs independent from your CPU
and the frequency can vary (simply called "ticks per second").
Its an independent hardware part on your mainboard.
Ticks divided with the frequency gives you seconds.

Yes, you can use GetTickCount instead since its purely software-based
(like SDL_GetTicks or Windows' Multimedia-Timer). But the PerfCounter
is usually the best choice if available.

what style of hardware dont have this avaliable?

486 ?

lordzero
29-03-2008, 11:51 PM
is ok now?

TCustomTimer = class
private
PerfFrequency: Int64;
InvPerfFrequency: Double;

function TCustomTimer.gettime: Double;
var
Time: Int64;
begin
QueryPerformanceCounter(Time);
Result := 1000 * InvPerfFrequency * Time;
end;

lordzero
30-03-2008, 12:16 AM
some comments about this optimized counter?

http://17slon.com/blogs/gabr/labels/open%20source.html

look the function:

function GetTickCount64_Acc: int64;

arthurprs
30-03-2008, 12:28 AM
is ok now?

TCustomTimer = class
private
PerfFrequency: Int64;
InvPerfFrequency: Double;

function TCustomTimer.gettime: Double;
var
Time: Int64;
begin
QueryPerformanceCounter(Time);
Result := 1000 * InvPerfFrequency * Time;
end;

the gettime function desnot need to return a float type, since it always result in a plain natural number

waran
30-03-2008, 04:12 AM
The division with the Frequency (or multiplication with InvFrequency,
respective) always produces a floatingpoint number. So at least if you want
to know how much seconds or milliseconds (*1000) passed you need single
or double [which is better for such big numbers].

"what style of hardware dont have this avaliable?"
I don't know. Just know that this feature is abundant nowadays.
Wiki says its even integreated in CPU, so forget my Mainboard statement :)
(English Wiki shows a table how much counters are integrated in which
CPU; it mentions AMD Athlon and Pentium III).

Brainer
30-03-2008, 04:43 AM
Try this:

unit PerfCounter;

interface

uses
Windows;

type
PComp = ^Comp;

{ .: TPerformanceCounter :. }
TPerformanceCounter = class(TObject)
private
{ Private declarations }
StartTime, Freq: Comp;
public
{ Public declarations }
constructor Create();

procedure StartTiming();
function GetTime(): Double;
end;

function QueryPerformanceCounter(lpPerformanceCount: PComp): Boolean; stdcall; external 'KERNEL32.DLL';
function QueryPerformanceFrequency(lpFrequency: PComp): Boolean; stdcall; external 'KERNEL32.DLL';

implementation

{ TPerformanceCounter }

constructor TPerformanceCounter.Create;
begin
inherited Create();

QueryPerformanceFrequency(@Freq);
end;

function TPerformanceCounter.GetTime: Double;
var
X: Comp;
begin
QueryPerformanceCounter(@X);
Result := (X - StartTime) * 1000.0 / Freq;
end;

procedure TPerformanceCounter.StartTiming;
begin
QueryPerformanceCounter(@StartTime);
end;

end.


Let us know if it works as you want. :wink:

arthurprs
30-03-2008, 05:40 AM
The division with the Frequency (or multiplication with InvFrequency,
respective) always produces a floatingpoint number. So at least if you want
to know how much seconds or milliseconds (*1000) passed you need single
or double [which is better for such big numbers].

"what style of hardware dont have this avaliable?"
I don't know. Just know that this feature is abundant nowadays.
Wiki says its even integreated in CPU, so forget my Mainboard statement :)
(English Wiki shows a table how much counters are integrated in which
CPU; it mentions AMD Athlon and Pentium III).

it aways return something.0

(at least here)

waran
30-03-2008, 05:49 AM
ORLY?

uses windows;
var
t, fq: int64;
t_sec: double;
begin
QueryPerformanceFrequency(fq);
QueryPerformanceCounter(t);
t_sec := t/fq;

writeln(fq); // 3579545
writeln(t); // 9629603054
writeln(t_sec:12:12); // 2690.175163044465

readln;
end.
However: If GetTime (didn't test it) returns something.0 something is
foul with the code :)

lordzero
30-03-2008, 02:39 PM
Hello Waran

You think that calculating DeltaTime without interval is ok? inside Application.OnIdle

arthurprs
30-03-2008, 03:42 PM
ORLY?

uses windows;
var
t, fq: int64;
t_sec: double;
begin
QueryPerformanceFrequency(fq);
QueryPerformanceCounter(t);
t_sec := t/fq;

writeln(fq); // 3579545
writeln(t); // 9629603054
writeln(t_sec:12:12); // 2690.175163044465

readln;
end.
However: If GetTime (didn't test it) returns something.0 something is
foul with the code :)

uhm, thats true, my mistake

Pyrogine
30-03-2008, 05:24 PM
Ok, so the idea here is to decouple updating from rendering. You want your game loop to render graphics as fast and as smoothly as possible while your simulation continues to update at a known and predictable rate and do this across different machine configurations. A frame-base timing system is way to achieve this goal. One problem with FBT is that if there a large delta from the last frame your objects will have a big speed change for the next frame. The speed of the object will be same, but the visual impact of this will be disconcerting. Your code should be able to handle this situation and keep it to a minimum.

Maybe this code can be of use to someone. It's the frame-base timing code that I use (for the past 7 years) in PyroGine SDK. It will try to use the performance counter by default and if not found will drop down to the Windows multimedia timer. With it you can set your desired simulation rate and the maximum number of frames to try and keep up your desired simulation rate. It can also return the your game loop frame rate in addition to performing some other useful timing functions.

type

{ TPyroTime }

TPyroTimer = class(TPyroObject)
private
function GetTickCountPriv: Int64;
protected
FPCAvail: Boolean;
FPCFreq: Int64;
FCurTime: Int64;
FLastTime: Int64;
FDesiredFPS: Single;
FMinElapsedTime: Single;
FMaxElapsedTime: Single;
FElapsedTimeScale: Single;
FFPSTimeScale: Single;
FFPSElapsedTime: Single;
FElapsedTime: Single;
FFrameRate: Cardinal;
FFrameCount: Cardinal;
FTimer: Single;
public
constructor Create;
destructor Destroy; override;

procedure Init(aDesiredFPS, aMaxElapsedTime: Single);
procedure Clear;
procedure Update;

function GetElapsedTime: Single;
function GetFrameRate: Cardinal;
function GetDesiredFPS: Single;

function FrameElapsed(aFrames: Single): Boolean; overload;
function FrameElapsed(var aTimer: Single; aFrames: Single): Boolean; overload;

procedure ResetFrameElapsed(aFrame: Single); overload;
procedure ResetFrameElapsed(var aTimer: Single; aFrame: Single); overload;

function FrameSpeed(aSpeedFPS: Single): Boolean; overload;
function FrameSpeed(var aTimer: Single; aSpeedFPS: Single): Boolean; overload;

function ResetFrameSpeed(aSpeedFPS: Single): Single; overload;
function ResetFrameSpeed(var aTimer: Single; aSpeedFPS: Single): Single; overload;

function GetTickCount: Cardinal;

property ElapsedTime: Single read GetElapsedTime;
property FrameRate: Cardinal read GetFrameRate;
property DesiredFPS: Single read GetDesiredFPS;
end;

{ TPyroTimer }
function TPyroTimer.GetTickCountPriv: Int64;
var
Ticks: Int64;
begin
if FPCAvail then
begin
QueryPerformanceCounter(Ticks);
end
else
begin
timeBeginPeriod(1);
Ticks := timeGetTime();
timeEndPeriod(1);
end;

Result := Ticks;
end;

constructor TPyroTimer.Create;
begin
inherited Create;
Init(35.0, 2.0);
end;

destructor TPyroTimer.Destroy;
begin
inherited Destroy;
end;

procedure TPyroTimer.Clear;
begin
FTimer := 0;
FFPSElapsedTime := 1;
FLastTime := 1000;
Update;
end;

function TPyroTimer.GetTickCount: Cardinal;
var
Ticks: Int64;
Ms : Cardinal;
begin
if FPCAvail then
begin
QueryPerformanceCounter(Ticks);
Ms := (Ticks * 1000) div FPCFreq;
end
else
begin
timeBeginPeriod(1);
Ms := timeGetTime();
timeEndPeriod(1);
end;

Result := Ms;
end;

procedure TPyroTimer.Init(aDesiredFPS, aMaxElapsedTime: Single);
begin
// check for performace counter
FPCAvail := QueryPerformanceFrequency(FPCFreq);
if FPCAvail then
begin
QueryPerformanceFrequency(FLastTime);
end
else
begin
FLastTime := 1000;
end;
FDesiredFPS := aDesiredFPS;
FMinElapsedTime := 0.0;
FMaxElapsedTime := aMaxElapsedTime;
if FMaxElapsedTime < 1 then
FMaxElapsedTime := 1
else if FMaxElapsedTime > 1000 then
FMaxElapsedTime := 1000;

FElapsedTimeScale := FDesiredFPS / FLastTime;
FFPSTimeScale := 1.0 / FLastTime;
FTimer := 0;
end;

procedure TPyroTimer.Update;
begin
// calc elapsed time
FCurTime := GetTickCountPriv;
FElapsedTime := (FCurTime - FLastTime) * FElapsedTimeScale;
if FElapsedTime < FMinElapsedTime then
FElapsedTime := FMinElapsedTime
else if FElapsedTime > FMaxElapsedTime
then FElapsedTime := FMaxElapsedTime;

// calc frame rate
Inc(FFrameCount);
FFPSElapsedTime := FFPSElapsedTime + ((FCurTime - FLastTime) * FFPSTimeScale);

if FFPSElapsedTime >= 1 then
begin
FFPSElapsedTime := 0;
FFrameRate := FFrameCount;
FFrameCount := 0;
end;

FLastTime := FCurTime;
end;

function TPyroTimer.GetElapsedTime: Single;
begin
Result := FElapsedTime;
end;

function TPyroTimer.GetFrameRate: Cardinal;
begin
Result := FFrameRate;
end;

function TPyroTimer.GetDesiredFPS: Single;
begin
Result := FDesiredFPS;
end;

function TPyroTimer.FrameElapsed(var aTimer: Single; aFrames: Single): Boolean;
begin
Result := False;
aTimer := aTimer + (1.0*FElapsedTime);
if aTimer > aFrames then
begin
aTimer := 0;
Result := True;
end;
end;

function TPyroTimer.FrameElapsed(aFrames: Single): Boolean;
begin
Result := FrameElapsed(FTimer, aFrames);
end;

procedure TPyroTimer.ResetFrameElapsed(var aTimer: Single; aFrame: Single);
begin
aTimer := aFrame + 1;
end;

procedure TPyroTimer.ResetFrameElapsed(aFrame: Single);
begin
ResetFrameElapsed(FTimer, aFrame);
end;

function TPyroTimer.FrameSpeed(var aTimer: Single; aSpeedFPS: Single): Boolean;
var
ScaleTime: Single;
begin
Result := False;
aTimer := aTimer + FElapsedTime;
ScaleTime := (FDesiredFPS / aSpeedFPS);
if aTimer > ScaleTime then
begin
aTimer := aTimer - ScaleTime;
Result := True;
end;
end;

function TPyroTimer.FrameSpeed(aSpeedFPS: Single): Boolean;
begin
Result := FrameSpeed(FTimer, aSpeedFPS);
end;

function TPyroTimer.ResetFrameSpeed(var aTimer: Single; aSpeedFPS: Single): Single;
begin
Result := (FDesiredFPS / aSpeedFPS);
aTimer := Result;
end;

function TPyroTimer.ResetFrameSpeed(aSpeedFPS: Single): Single;
begin
Result := ResetFrameSpeed(FTimer, aSpeedFPS);
end;


Example usage:

// init simulation to 35 fps and try to keep this up to two elapsed frames
Timer.Init(35,0);

// in your game loop call Update once per frame
Timer.Update;

// get current framerate
FrameRate := Timer.FrameRate;

// get elapsed time value which is based on desired framerate
// multiply by speed value to keep objects moving at 35 fps
ElapsedTime := Timer.ElapsedTime;

// update object speed
myobj.speed := myobj.speed * ElapsedTime


Now as your game loop speed may vary wildly from frame to frame but your simulation rate will continue to update in a predictable manner and this is what we are after here. We need to be able to have control over the simulation as much as possible.

Also if VSync is off then you will experience "tearing." The result will be a "popping" effect. Note that this is purely visual but it does detract from the overall experience. You have to assume that you will have no control over VSync so as the developer you must be prepared to minimize this problem. I've found that using a simulation rate that is a value slightly above a multiple of the monitor's refresh rate will yield the best results. 60hz is common to all monitors so traditionally I've used 35 fps as a simulation rate in all of my game projects.

Note: The code tags is messing with some of the code for some reason. The line in TPyroTimer.Init should be the following:

if FMaxElapsedTime [LessThan] 1 then
FMaxElapsedTime := 1
else if FMaxElapsedTime [GreaterThan] 1000 then
FMaxElapsedTime := 1000;

and in TPyroTimer.Update:

if FElapsedTime [LessThan] FMinElapsedTime then
FElapsedTime := FMinElapsedTime
else if FElapsedTime [GreaterThan] FMaxElapsedTime
then FElapsedTime := FMaxElapsedTime;

It seems the forum does not like the GreaterThan/LessThan constructs.

lordzero
30-03-2008, 06:26 PM
i cant compile your code with delphi 7

error:

[Error] Unit2.pas(136): Incompatible types
[Error] Unit2.pas(150): Incompatible types
[Fatal Error] Project1.dpr(6): Could not compile used unit 'Unit2.pas'

procedure TPyroTimer.Init(aDesiredFPS, aMaxElapsedTime: Single);
begin
// check for performace counter
FPCAvail := QueryPerformanceFrequency(FPCFreq);
if FPCAvail then
begin
QueryPerformanceFrequency(FLastTime);
end
else
begin
FLastTime := 1000;
end;
FDesiredFPS := aDesiredFPS;
FMinElapsedTime := 0.0;
FMaxElapsedTime := aMaxElapsedTime;
if FMaxElapsedTime <1> 1000 then
FMaxElapsedTime := 1000;

FElapsedTimeScale := FDesiredFPS / FLastTime;
FFPSTimeScale := 1.0 / FLastTime;
FTimer := 0;
end;


procedure TPyroTimer.Update;
begin
// calc elapsed time
FCurTime := GetTickCountPriv;
FElapsedTime := (FCurTime - FLastTime) * FElapsedTimeScale;
if FElapsedTime <FMinElapsedTime> FMaxElapsedTime
then FElapsedTime := FMaxElapsedTime;

// calc frame rate
Inc(FFrameCount);
FFPSElapsedTime := FFPSElapsedTime + ((FCurTime - FLastTime) * FFPSTimeScale);

if FFPSElapsedTime >= 1 then
begin
FFPSElapsedTime := 0;
FFrameRate := FFrameCount;
FFrameCount := 0;
end;

FLastTime := FCurTime;
end;

Pyrogine
30-03-2008, 06:48 PM
Because the forum does not seem to allow you to have GreaterThan/LessThan code symbols in your post for some reason. It will not even allow this without the code tags.

So... here is a unit that you can download and add to the uses section:

PyroTimer (http://pyroginegames.com/downloads/PyroTimer_v1.0.1.zip) - Frame-base timing system.

lordzero
30-03-2008, 07:37 PM
Because the forum does not seem to allow you to have GreaterThan/LessThan code symbols in your post for some reason. It will not even allow this without the code tags.

So... here is a unit that you can download and add to the uses section:

PyroTimer (http://pyroginegames.com/downloads/PyroTimer_v1.0.1.zip) - Frame-base timing system.

than you

please, can you see my private message?

Robert Kosek
30-03-2008, 07:43 PM
You must check the option "Disable HTML in this post" beneath the edit if you use greater-than or less-than symbols in your code. I believe that I have said this a few times now.

Pyrogine
30-03-2008, 07:53 PM
Arrg... I keep getting post errors....

what I was trying to say is that later I realized I forgot to disable HTML. Should be fixed now.

Something is wrong with the forum... yesterday and now today when I post... it get several errors: either can not mail or something about debug and then my post is not saved or messed up.

Pyrogine
30-03-2008, 08:42 PM
lordzero
check your PM. Let me know.

lordzero
31-03-2008, 03:15 AM
lordzero
check your PM. Let me know.

thank you man...

very very good! :)

i need your help just more one time...

see my private.. :)

Pyrogine
31-03-2008, 05:00 AM
no prob, glad to help out.

check your pm... it should be [FrameRate] rather than [ElapsedTime].. sorry about that.

let me know.

lordzero
31-03-2008, 03:18 PM
no prob, glad to help out.

check your pm... it should be [FrameRate] rather than [ElapsedTime].. sorry about that.

let me know.


now is ok...

its working!

thank you again! :)