Archived

This topic is now archived and is closed to further replies.

Aram

FPS counter problem

Recommended Posts

Hi!! I have made an fps counter that doesn''t seem to work right. If the fps is between 33 and 50 the counter shows correctly but when it goes below or above it just show 33 or 50. I dont undertand why because as I see it this is just some simple math.
  
			if(CURRENTTIME - FRAMESTART >= FRAMETIME)
			{
				FRAMES = 1000/(CURRENTTIME - FRAMESTART);
				FRAMESTART = timeGetTime();
					DrawScene();
						
			}
			CURRENTTIME = timeGetTime();
  

  
void ShowFrames(UINT frames)
{
	UCHAR THOUSANDS,HUNDREDS,TENS,NUMBERS;
	THOUSANDS = frames/1000;
	HUNDREDS = (frames/100)%10;
	TENS = (frames/10)%10;
	NUMBERS = frames%10; 
switch(THOUSANDS)
	{
		case 0:
			D3dDevice->SetTexture(0, t_FrameNumber[0]);
			break;
		case 1:
			D3dDevice->SetTexture(0, t_FrameNumber[1]);
			break;
		case 2:
			D3dDevice->SetTexture(0, t_FrameNumber[2]);
			break;
		case 3:
			D3dDevice->SetTexture(0, t_FrameNumber[3]);
			break;
		case 4:
			D3dDevice->SetTexture(0, t_FrameNumber[4]);
			break;
		case 5:
			D3dDevice->SetTexture(0, t_FrameNumber[5]);
			break;
		case 6:
			D3dDevice->SetTexture(0, t_FrameNumber[6]);
			break;
		case 7:
			D3dDevice->SetTexture(0, t_FrameNumber[7]);
			break;
		case 8:
			D3dDevice->SetTexture(0, t_FrameNumber[8]);
			break;
		case 9:
			D3dDevice->SetTexture(0, t_FrameNumber[9]);
			break;
	}

	D3dDevice->SetStreamSource(0, vb_FrameCounter[3], sizeof(PANELVERTEX));
	D3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);

	switch(HUNDREDS)
	{
		case 0:
			D3dDevice->SetTexture(0, t_FrameNumber[0]);
			break;
		case 1:
			D3dDevice->SetTexture(0, t_FrameNumber[1]);
			break;
		case 2:
			D3dDevice->SetTexture(0, t_FrameNumber[2]);
			break;
		case 3:
			D3dDevice->SetTexture(0, t_FrameNumber[3]);
			break;
		case 4:
			D3dDevice->SetTexture(0, t_FrameNumber[4]);
			break;
		case 5:
			D3dDevice->SetTexture(0, t_FrameNumber[5]);
			break;
		case 6:
			D3dDevice->SetTexture(0, t_FrameNumber[6]);
			break;
		case 7:
			D3dDevice->SetTexture(0, t_FrameNumber[7]);
			break;
		case 8:
			D3dDevice->SetTexture(0, t_FrameNumber[8]);
			break;
		case 9:
			D3dDevice->SetTexture(0, t_FrameNumber[9]);
			break;
	}

	D3dDevice->SetStreamSource(0, vb_FrameCounter[2], sizeof(PANELVERTEX));
	D3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);

	switch(TENS)
	{
		case 0:
			D3dDevice->SetTexture(0, t_FrameNumber[0]);
			break;
		case 1:
			D3dDevice->SetTexture(0, t_FrameNumber[1]);
			break;
		case 2:
			D3dDevice->SetTexture(0, t_FrameNumber[2]);
			break;
		case 3:
			D3dDevice->SetTexture(0, t_FrameNumber[3]);
			break;
		case 4:
			D3dDevice->SetTexture(0, t_FrameNumber[4]);
			break;
		case 5:
			D3dDevice->SetTexture(0, t_FrameNumber[5]);
			break;
		case 6:
			D3dDevice->SetTexture(0, t_FrameNumber[6]);
			break;
		case 7:
			D3dDevice->SetTexture(0, t_FrameNumber[7]);
			break;
		case 8:
			D3dDevice->SetTexture(0, t_FrameNumber[8]);
			break;
		case 9:
			D3dDevice->SetTexture(0, t_FrameNumber[9]);
			break;
	}

	D3dDevice->SetStreamSource(0, vb_FrameCounter[1], sizeof(PANELVERTEX));
	D3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);

	switch(NUMBERS)
	{
		case 0:
			D3dDevice->SetTexture(0, t_FrameNumber[0]);
			break;
		case 1:
			D3dDevice->SetTexture(0, t_FrameNumber[1]);
			break;
		case 2:
			D3dDevice->SetTexture(0, t_FrameNumber[2]);
			break;
		case 3:
			D3dDevice->SetTexture(0, t_FrameNumber[3]);
			break;
		case 4:
			D3dDevice->SetTexture(0, t_FrameNumber[4]);
			break;
		case 5:
			D3dDevice->SetTexture(0, t_FrameNumber[5]);
			break;
		case 6:
			D3dDevice->SetTexture(0, t_FrameNumber[6]);
			break;
		case 7:
			D3dDevice->SetTexture(0, t_FrameNumber[7]);
			break;
		case 8:
			D3dDevice->SetTexture(0, t_FrameNumber[8]);
			break;
		case 9:
			D3dDevice->SetTexture(0, t_FrameNumber[9]);
			break;
	}

	D3dDevice->SetStreamSource(0, vb_FrameCounter[0], sizeof(PANELVERTEX));
	D3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);
}
  
Any comments on how to optimize is also welcom.... Thanks!!!

Share this post


Link to post
Share on other sites
i dont bother with miliseconds, integer arithmatic is no way to go for these things.

i use inline assembly with the RDTSC instructiona and a delay to get the actual number of cycles done in a certain abount of time. each frame, i use 64-bit math to find the number of ticks since the last frame and divide.

voila! a floating point time accurate to several decimal places calculated in only a few cycles. it is a lot easier to work with in animation and physics etc (Pos+Vel*t) and if you invert it you have the FPS.

Share this post


Link to post
Share on other sites
What on earth are those switch statements for? Surely all this:

  
switch(THOUSANDS)
{
case 0:
D3dDevice->SetTexture(0, t_FrameNumber[0]);
break;
case 1:
D3dDevice->SetTexture(0, t_FrameNumber[1]);
break;
case 2:
D3dDevice->SetTexture(0, t_FrameNumber[2]);
break;
case 3:
D3dDevice->SetTexture(0, t_FrameNumber[3]);
break;
case 4:
D3dDevice->SetTexture(0, t_FrameNumber[4]);
break;
case 5:
D3dDevice->SetTexture(0, t_FrameNumber[5]);
break;
case 6:
D3dDevice->SetTexture(0, t_FrameNumber[6]);
break;
case 7:
D3dDevice->SetTexture(0, t_FrameNumber[7]);
break;
case 8:
D3dDevice->SetTexture(0, t_FrameNumber[8]);
break;
case 9:
D3dDevice->SetTexture(0, t_FrameNumber[9]);
break;
}

...could be done like this:

  
D3dDevice->SetTexture(0, t_FrameNumber[THOUSANDS]);

There''s an optimisation for you right there. As for your frame counter, you''re probably better off taking a windowed average, by timing how long it has taken to draw the last 10 frames and dividing that by 10. I also don''t know what kind of precision timeGetTime has, so it might not be too accurate when called too often.

[ MSVC Fixes | STL | SDL | Game AI | Sockets | C++ Faq Lite | Boost | Asking Questions | Organising code files ]

Share this post


Link to post
Share on other sites
By the way...I''ve heard of something called QueryPerformanceFrequency and QueryPerformanceCounter. Are these more accurate and should I use these instead?

//Aram

Share this post


Link to post
Share on other sites
They are much more accurate because the so called Performance Counter can count in micro seconds because it is a kind of microprocessor running on 3.16 Mhz (I believe) and it is independent from the main CPU.
But it is not installed on every system so you will have to query for it first (QueryPerformanceFrequency will return 0 in the parameter if no Performance Counter is installed).

Im Anfang war die Tat...
Faust

Share this post


Link to post
Share on other sites
Okay. But how do I use QueryPerformanceFrequency and QueryPerformanceCounter? What value does QueryPerformanceFrequency and QueryPerformanceCounter return?

Is it that QueryPerformanceFrequency returns a some number that is equal one microsecond and if so should I use it like this?

MICROSECS = QueryPerformanceCounter/QueryPerformanceFrequency

//Aram

Share this post


Link to post
Share on other sites
I shouldn''t have to tell you this, since you should already have SEARCHED FOR IT(!):
There is an article right here on gamedev on using both timeGetTime() and the performance timers, and how to nicely use both.

Share this post


Link to post
Share on other sites
Just for info, here are the performances of various timing mechanisms, profiled on a 733MHZ machine:

Timer Profile (RDTSC):
Frequency: 733373724Hz
Resolution: 1.36356e-09s
Error: 0.000227604s
Speed: 1.38006e-07s

Timer Profile (PerformanceCounter):
Frequency: 3579545Hz
Resolution: 2.79365e-07s
Error: 0.000274057s
Speed: 1.44208e-06s

Timer Profile (TimeGetTime):
Frequency: 1000Hz
Resolution: 0.001s
Error: 0.01s
Speed: 1.82705e-07s

Timer Profile (GetTickCount):
Frequency: 1000Hz
Resolution: 0.001s
Error: 0.01s
Speed: 5.39175e-08s

Timer Profile (Clock):
Frequency: 1000Hz
Resolution: 0.001s
Error: 0s
Speed: 3.71584e-06s

Frequency and resolution are pretty obvious. Error is the average error when timing for a second and will probably be a bit off (it''s difficult to calculate error when you don''t have a fully accurate timer to test against). Speed is the time taken to execute the timer mechanism.

Enigma

Share this post


Link to post
Share on other sites
Newer Intel and AMD processors (don't know about other makes) support an instruction to read the processor Time Stamp Counter (which updates every cycle). This gives you a very accurate and very fast timer. It also requires inline assembly to use.

The code to use it under the Borland compiler with TASM is:

      
/* Code to check whether RDTSC is available */
DWORD timerAvailable = 0x1;

_asm{
; Get CPU ID information
mov eax, 0
cpuid

; check if CPU is GenuineIntel
cmp ebx, 'uneG'
jne not_intel
cmp edx, 'Ieni'
jne not_intel
cmp ecx, 'letn'
jne not_intel
jmp test_RDTSC

not_intel:
; check is CPU is AuthenticAMD
cmp ebx, 'htuA'
jne not_available
cmp edx, 'itne'
jne not_available
cmp ecx, 'DMAc'
jne not_available

test_RDTSC:
; get CPU supported features
mov eax, 1
cpuid

; check if TSC supported
bt edx, 4
jnc not_available

; TSC supported
xor timerAvailable, 1h

not_available:
xor timerAvailable, 1h
}

/* Code to read Time Stamp Counter */

int64 time;
int64* addr = &time;
__emit__(0x0f);
__emit__(0x31);
_asm{
; read TSC
mov esi, addr
mov [esi], eax
mov [esi + 4], edx
}

And to do it under gcc:

  
/* Code to check whether RDTSC is available */
DWORD timerAvailable = 0x1;

asm(
" /* Get CPU ID information*/\n"
" movl $0, %%eax\n"
" cpuid\n"

" /* check if CPU is GenuineIntel*/\n"
" cmpl $0x756e6547, %%ebx\n"
" jne not_intel\n"
" cmpl $0x49656e69, %%edx\n"
" jne not_intel\n"
" cmpl $0x6c65746e, %%ecx\n"
" jne not_intel\n"

" jmp test_RDTSC\n"

"not_intel:\n"

" /* check is CPU is AuthenticAMD*/\n"
" cmpl $0x68747541, %%ebx\n"
" jne not_available\n"
" cmpl $0x69746e65, %%edx\n"
" jne not_available\n"
" cmpl $0x444d4163, %%ecx\n"
" jne not_available\n"

"test_RDTSC:\n"

" /* get CPU supported features*/\n"
" mov $1, %%eax\n"
" cpuid\n"

" /* check if TSC supported*/\n"
" bt $4, %%edx\n"
" jnc not_available\n"

" /* TSC supported*/\n"
" xor $0x1, %1\n"

"not_available:\n"

" xor $0x1, %1\n"
:
"=r" (timerAvailable)
:
"r" (timerAvailable)
:
"%eax", "%ebx", "%ecx", "%edx"
);

/* Code to read Time Stamp Counter */

int64 time;
DWORD tscLowDWORD;
DWORD tscHighDWORD;
asm(
" rdtsc\n"
" mov %%eax, %0\n"
" mov %%edx, %1\n"
:
"=a" (tscLowDWORD),
"=d" (tscHighDWORD)
);
time = (((int64)tscHighDWORD) << 32) | tscLowDWORD;

Note - the gcc code could probably be optimised better, I haven't used gcc assembler as much.

If you want more detail, there's an article at the Intel website:
http://www.intel.com/design/Xeon/applnots/24161821.pdf

Enigma

Edit: small typo, then edit caused source boxes to merge.

[edited by - Enigma on July 31, 2002 5:16:37 AM]

Share this post


Link to post
Share on other sites
Hmm...Havent woked much in assembly before, just some in school for hardware programmming... but thanks anyway maybe I''ll manage to get something off it.

Thanks!

Share this post


Link to post
Share on other sites