跳轉至內容

DirectX/10.0/Direct3D/FPS、CPU 使用率和計時器

來自 Wikibooks,開放世界中的開放書籍

本教程將介紹三個新類,它們將封裝每秒幀數計數器、CPU 使用率計數器和高精度計時器的功能。本教程中的程式碼基於 Font Engine 教程中的程式碼。

第一個新類是 FpsClass。FpsClass 將負責記錄應用程式執行的每秒幀數。瞭解每秒渲染的幀數為我們提供了衡量應用程式效能的一個很好的指標。這是用於確定可接受圖形渲染速度的行業標準指標之一。它在實現新功能時也很有用,可以檢視它們對幀速率的影響。如果新功能將幀速率降低了一半,那麼您就可以立即意識到存在重大問題,只需使用這個簡單的計數器即可。請記住,當前計算機的標準 fps 速度為 60 fps。低於 60 fps 被認為效能較差,低於 30 fps 則會被人眼明顯察覺。編碼時的一般規則是最大限度地提高 fps,如果正確實現的新功能嚴重影響了速度,那麼需要證明其合理性,至少要記錄下來。

第二個新類是 CpuClass。它將負責記錄 CPU 使用率,以便我們可以將當前的 CPU 使用率百分比顯示在螢幕上。瞭解 CPU 使用率對於除錯程式碼的新更改很有用,類似於 fps 的用法。它提供了一個簡單且直接的指標,用於識別最近實現的糟糕程式碼或演算法。

最後一個新類是 TimerClass。這是一個高精度計時器,我們可以用它來計時事件並確保應用程式及其各個元件都同步到一個公共時間框架。

框架

[edit | edit source]

本教程的框架,包括三個新類,如下所示

我們將從檢查三個新類開始本教程。

Fpsclass.h

[edit | edit source]

FpsClass 只是一個與計時器關聯的計數器。它計算在一秒鐘內發生的幀數,並不斷更新該計數。

////////////////////////////////////////////////////////////////////////////////
// Filename: fpsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _FPSCLASS_H_
#define _FPSCLASS_H_


/////////////
// LINKING //
/////////////
#pragma comment(lib, "winmm.lib")


//////////////
// INCLUDES //
//////////////
#include <windows.h>
#include <mmsystem.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: FpsClass
////////////////////////////////////////////////////////////////////////////////
class FpsClass
{
public:
	FpsClass();
	FpsClass(const FpsClass&);
	~FpsClass();

	void Initialize();
	void Frame();
	int GetFps();

private:
	int m_fps, m_count;
	unsigned long m_startTime;
};

#endif

Fpsclass.cpp

[edit | edit source]
///////////////////////////////////////////////////////////////////////////////
// Filename: fpsclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "fpsclass.h"


FpsClass::FpsClass()
{
}


FpsClass::FpsClass(const FpsClass& other)
{
}


FpsClass::~FpsClass()
{
}

Initialize 函式將所有計數器設定為零並啟動計時器。

void FpsClass::Initialize()
{
	m_fps = 0;
	m_count = 0;
	m_startTime = timeGetTime();
	return;
}

Frame 函式必須在每一幀呼叫,以便它可以將幀計數增加 1。如果發現已過去一秒,它將把幀計數儲存在 m_fps 變數中。然後,它將重置計數並重新啟動計時器。

void FpsClass::Frame()
{
	m_count++;

	if(timeGetTime() >= (m_startTime + 1000))
	{
		m_fps = m_count;
		m_count = 0;
		
		m_startTime = timeGetTime();
	}
}

GetFps 返回剛過去的最後一秒的每秒幀速率。此函式應該不斷查詢,以便可以將最新的 fps 顯示在螢幕上。

int FpsClass::GetFps()
{
	return m_fps;
}

Cpuclass.h

[edit | edit source]

CpuClass 用於確定每秒發生的總 CPU 使用率百分比。

///////////////////////////////////////////////////////////////////////////////
// Filename: cpuclass.h
///////////////////////////////////////////////////////////////////////////////
#ifndef _CPUCLASS_H_
#define _CPUCLASS_H_

我們使用 pdh 庫來查詢 CPU 使用率。

/////////////
// LINKING //
/////////////
#pragma comment(lib, "pdh.lib")


//////////////
// INCLUDES //
//////////////
#include <pdh.h>


///////////////////////////////////////////////////////////////////////////////
// Class name: CpuClass
///////////////////////////////////////////////////////////////////////////////
class CpuClass
{
public:
	CpuClass();
	CpuClass(const CpuClass&);
	~CpuClass();

	void Initialize();
	void Shutdown();
	void Frame();
	int GetCpuPercentage();

private:
	bool m_canReadCpu;
	HQUERY m_queryHandle;
	HCOUNTER m_counterHandle;
	unsigned long m_lastSampleTime;
	long m_cpuUsage;
};

#endif

Cpuclass.cpp

[edit | edit source]
///////////////////////////////////////////////////////////////////////////////
// Filename: cpuclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "cpuclass.h"


CpuClass::CpuClass()
{
}


CpuClass::CpuClass(const CpuClass& other)
{
}


CpuClass::~CpuClass()
{
}

Initialize 函式將設定用於查詢 CPU 使用率的控制代碼。此處設定的查詢將合併系統中所有 CPU 的使用情況,並返回總計,而不是每個 CPU 的使用情況。如果由於任何原因無法獲得查詢控制代碼或輪詢 CPU 使用率,它將把 m_canReadCpu 標誌設定為 false,並將 CPU 使用率保持為零百分比。某些 CPU 和作業系統特權級別可能會導致此操作失敗。我們還會啟動計時器,以便我們只在一秒鐘內取樣一次 CPU 使用率。

void CpuClass::Initialize()
{
	PDH_STATUS status;


	// Initialize the flag indicating whether this object can read the system cpu usage or not.
	m_canReadCpu = true;

	// Create a query object to poll cpu usage.
	status = PdhOpenQuery(NULL, 0, &m_queryHandle);
	if(status != ERROR_SUCCESS)
	{
		m_canReadCpu = false;
	}

	// Set query object to poll all cpus in the system.
	status = PdhAddCounter(m_queryHandle, TEXT("\\Processor(_Total)\\% processor time"), 0, &m_counterHandle);
	if(status != ERROR_SUCCESS)
	{
		m_canReadCpu = false;
	}

	m_lastSampleTime = GetTickCount(); 

	m_cpuUsage = 0;

	return;
}

Shutdown 函式釋放我們用於查詢 CPU 使用率的控制代碼。

void CpuClass::Shutdown()
{
	if(m_canReadCpu)
	{
		PdhCloseQuery(m_queryHandle);
	}

	return;
}

與 FpsClass 一樣,我們必須在每一幀呼叫 Frame 函式。但是,為了減少查詢次數,我們使用 m_lastSampleTime 變數來確保我們只在一秒鐘內取樣一次。因此,每一秒我們都會詢問 CPU 的使用率並將該值儲存在 m_cpuUsage 中。超過這一點就沒有必要了。

void CpuClass::Frame()
{
	PDH_FMT_COUNTERVALUE value; 

	if(m_canReadCpu)
	{
		if((m_lastSampleTime + 1000) 

GetCpuPercentage 函式將當前 CPU 使用率的值返回給任何呼叫函式。同樣,如果由於任何原因無法讀取 CPU,我們只需將使用率設定為零。

int CpuClass::GetCpuPercentage()
{
	int usage;

	if(m_canReadCpu)
	{
		usage = (int)m_cpuUsage;
	}
	else
	{
		usage = 0;
	}

	return usage;
}

Timerclass.h

[edit | edit source]

TimerClass 是一個高精度計時器,它測量執行幀之間的時間間隔。它的主要用途是同步需要標準時間框架進行移動的物件。在本教程中,我們將不會使用它,但我們將它實現到程式碼中,以便您瞭解如何將其應用於您的專案。TimerClass 的最常見用法是使用幀時間來計算當前幀中經過了多少百分比的秒數,然後按該百分比移動物件。

////////////////////////////////////////////////////////////////////////////////
// Filename: timerclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TIMERCLASS_H_
#define _TIMERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <windows.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: TimerClass
////////////////////////////////////////////////////////////////////////////////
class TimerClass
{
public:
	TimerClass();
	TimerClass(const TimerClass&);
	~TimerClass();

	bool Initialize();
	void Frame();

	float GetTime();

private:
	INT64 m_frequency;
	float m_ticksPerMs;
	INT64 m_startTime;
	float m_frameTime;
};

#endif

Timerclass.cpp

[edit | edit source]
///////////////////////////////////////////////////////////////////////////////
// Filename: timerclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "timerclass.h"


TimerClass::TimerClass()
{
}


TimerClass::TimerClass(const TimerClass& other)
{
}


TimerClass::~TimerClass()
{
}

Initialize 函式首先查詢系統以檢視它是否支援高頻率計時器。如果它返回頻率,我們將使用該值來確定每毫秒將發生多少個計數器滴答。然後,我們可以在每一幀中使用該值來計算幀時間。在 Initialize 函式的末尾,我們將查詢此幀的開始時間以啟動計時。

bool TimerClass::Initialize()
{
	// Check to see if this system supports high performance timers.
	QueryPerformanceFrequency((LARGE_INTEGER*)&m_frequency);
	if(m_frequency == 0)
	{
		return false;
	}

	// Find out how many times the frequency counter ticks every millisecond.
	m_ticksPerMs = (float)(m_frequency / 1000);

	QueryPerformanceCounter((LARGE_INTEGER*)&m_startTime);

	return true;
}

Frame 函式由主程式在每次執行迴圈中呼叫。這樣我們就可以計算迴圈之間的時間差,並確定執行此幀所花費的時間。我們將查詢、計算並將此幀的時間儲存到 m_frameTime 中,以便任何呼叫物件都可以將其用於同步。然後,我們將當前時間儲存為下一幀的開始時間。

void TimerClass::Frame()
{
	INT64 currentTime;
	float timeDifference;


	QueryPerformanceCounter((LARGE_INTEGER*)& currentTime);

	timeDifference = (float)(currentTime - m_startTime);

	m_frameTime = timeDifference / m_ticksPerMs;

	m_startTime = currentTime;

	return;
}

GetTime 返回計算的最近的幀時間。

float TimerClass::GetTime()
{
	return m_frameTime;
}

Systemclass.h

[edit | edit source]

現在我們已經查看了三個新類,我們可以檢查它們如何融入框架。對於所有三個新類,它們都將位於 SystemClass 下。

////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _SYSTEMCLASS_H_
#define _SYSTEMCLASS_H_


///////////////////////////////
// PRE-PROCESSING DIRECTIVES //
///////////////////////////////
#define WIN32_LEAN_AND_MEAN


//////////////
// INCLUDES //
//////////////
#include <windows.h>


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "inputclass.h"
#include "graphicsclass.h"

我們在此包含三個新類。

#include "fpsclass.h"
#include "cpuclass.h"
#include "timerclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: SystemClass
////////////////////////////////////////////////////////////////////////////////
class SystemClass
{
public:
	SystemClass();
	SystemClass(const SystemClass&);
	~SystemClass();

	bool Initialize();
	void Shutdown();
	void Run();

	LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM);

private:
	bool Frame();
	void InitializeWindows(int&, int&);
	void ShutdownWindows();

private:
	LPCWSTR m_applicationName;
	HINSTANCE m_hinstance;
	HWND m_hwnd;

	InputClass* m_Input;
	GraphicsClass* m_Graphics;

我們為三個新類中的每一個建立一個新物件。

	FpsClass* m_Fps;
	CpuClass* m_Cpu;
	TimerClass* m_Timer;
};


/////////////////////////
// FUNCTION PROTOTYPES //
/////////////////////////
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


/////////////
// GLOBALS //
/////////////
static SystemClass* ApplicationHandle = 0;


#endif

Systemclass.cpp

[edit | edit source]

我們將只介紹自字型教程以來的更改。

////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "systemclass.h"


SystemClass::SystemClass()
{
	m_Input = 0;
	m_Graphics = 0;

在類建構函式中將三個新物件初始化為 null。

	m_Fps = 0;
	m_Cpu = 0;
	m_Timer = 0;
}


bool SystemClass::Initialize()
{
	int screenWidth, screenHeight;
	bool result;


	// Initialize the width and height of the screen to zero before sending the variables into the function.
	screenWidth = 0;
	screenHeight = 0;

	// Initialize the windows api.
	InitializeWindows(screenWidth, screenHeight);

	// Create the input object.  This object will be used to handle reading the keyboard input from the user.
	m_Input = new InputClass;
	if(!m_Input)
	{
		return false;
	}

	// Initialize the input object.
	result = m_Input->Initialize(m_hinstance, m_hwnd, screenWidth, screenHeight);
	if(!result)
	{
		MessageBox(m_hwnd, L"Could not initialize the input object.", L"Error", MB_OK);
		return false;
	}

	// Create the graphics object.  This object will handle rendering all the graphics for this application.
	m_Graphics = new GraphicsClass;
	if(!m_Graphics)
	{
		return false;
	}

	// Initialize the graphics object.
	result = m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd);
	if(!result)
	{
		return false;
	}

建立並初始化 FpsClass。

	// Create the fps object.
	m_Fps = new FpsClass;
	if(!m_Fps)
	{
		return false;
	}

	// Initialize the fps object.
	m_Fps->Initialize();

建立並初始化 CpuClass。

	// Create the cpu object.
	m_Cpu = new CpuClass;
	if(!m_Cpu)
	{
		return false;
	}

	// Initialize the cpu object.
	m_Cpu->Initialize();

建立並初始化 TimerClass。

	// Create the timer object.
	m_Timer = new TimerClass;
	if(!m_Timer)
	{
		return false;
	}

	// Initialize the timer object.
	result = m_Timer->Initialize();
	if(!result)
	{
		MessageBox(m_hwnd, L"Could not initialize the Timer object.", L"Error", MB_OK);
		return false;
	}

	return true;
}


void SystemClass::Shutdown()
{

在 Shutdown 函式中釋放三個新類物件。

	// Release the timer object.
	if(m_Timer)
	{
		delete m_Timer;
		m_Timer = 0;
	}

	// Release the cpu object.
	if(m_Cpu)
	{
		m_Cpu->Shutdown();
		delete m_Cpu;
		m_Cpu = 0;
	}

	// Release the fps object.
	if(m_Fps)
	{
		delete m_Fps;
		m_Fps = 0;
	}

	// Release the graphics object.
	if(m_Graphics)
	{
		m_Graphics->Shutdown();
		delete m_Graphics;
		m_Graphics = 0;
	}

	// Release the input object.
	if(m_Input)
	{
		m_Input->Shutdown();
		delete m_Input;
		m_Input = 0;
	}

	// Shutdown the window.
	ShutdownWindows();
	
	return;
}

最後修改的是 Frame 函式。每個新類都需要為應用程式執行的每一幀呼叫自己的 Frame 函式。一旦呼叫了每個類的 Frame 函式,我們就可以查詢每個類的更新資料並將其傳送到 GraphicsClass 以供使用。

bool SystemClass::Frame()
{
	bool result;


	// Update the system stats.
	m_Timer->Frame();
	m_Fps->Frame();
	m_Cpu->Frame();

	// Do the input frame processing.
	result = m_Input->Frame();
	if(!result)
	{
		return false;
	}

	// Do the frame processing for the graphics object.
	result = m_Graphics->Frame(m_Fps->GetFps(), m_Cpu->GetCpuPercentage(), m_Timer->GetTime());
	if(!result)
	{
		return false;
	}

	// Finally render the graphics to the screen.
	result = m_Graphics->Render();
	if(!result)
	{
		return false;
	}

	return true;
}

Graphicsclass.h

[編輯 | 編輯原始碼]
////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;

在本教程中,我們停用了 vsync,以便應用程式可以儘可能快地執行。

const bool VSYNC_ENABLED = false;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "textclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame(int, int, float);
	bool Render();

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;
	TextClass* m_Text;
};

#endif

Graphicsclass.cpp

[編輯 | 編輯原始碼]

我只介紹自上一個字型教程以來更改過的函式。

////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"

Frame 函式現在接受 fps、cpu 和計時器計數作為引數。fps 和 cpu 計數在 TextClass 中設定,以便可以將其渲染到螢幕上。

bool GraphicsClass::Frame(int fps, int cpu, float frameTime)
{
	bool result;


	// Set the frames per second.
	result = m_Text->SetFps(fps, m_D3D->GetDeviceContext());
	if(!result)
	{
		return false;
	}

	// Set the cpu usage.
	result = m_Text->SetCpu(cpu, m_D3D->GetDeviceContext());
	if(!result)
	{
		return false;
	}

	// Set the position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -10.0f);

	return true;
}

Textclass.h

[編輯 | 編輯原始碼]
////////////////////////////////////////////////////////////////////////////////
// Filename: textclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTCLASS_H_
#define _TEXTCLASS_H_

///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "fontclass.h"
#include "fontshaderclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: TextClass
////////////////////////////////////////////////////////////////////////////////
class TextClass
{
private:
	struct SentenceType
	{
		ID3D11Buffer *vertexBuffer, *indexBuffer;
		int vertexCount, indexCount, maxLength;
		float red, green, blue;
	};

	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
	};

public:
	TextClass();
	TextClass(const TextClass&);
	~TextClass();

	bool Initialize(ID3D11Device*, ID3D11DeviceContext*, HWND, int, int, D3DXMATRIX);
	void Shutdown();
	bool Render(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX);

現在我們有兩個新函式用於設定 fps 計數和 cpu 使用率。

	bool SetFps(int, ID3D11DeviceContext*);
	bool SetCpu(int, ID3D11DeviceContext*);

private:
	bool InitializeSentence(SentenceType**, int, ID3D11Device*);
	bool UpdateSentence(SentenceType*, char*, int, int, float, float, float, ID3D11DeviceContext*);
	void ReleaseSentence(SentenceType**);
	bool RenderSentence(ID3D11DeviceContext*, SentenceType*, D3DXMATRIX, D3DXMATRIX);

private:
	FontClass* m_Font;
	FontShaderClass* m_FontShader;
	int m_screenWidth, m_screenHeight;
	D3DXMATRIX m_baseViewMatrix;
	SentenceType* m_sentence1;
	SentenceType* m_sentence2;
};

#endif

Textclass.cpp

[編輯 | 編輯原始碼]

我只介紹自上一個字型教程以來更改過的函式。

///////////////////////////////////////////////////////////////////////////////
// Filename: textclass.cpp
///////////////////////////////////////////////////////////////////////////////
#include "textclass.h"

SetFps 函式接受給定的 fps 整數值,並將其轉換為字串。一旦 fps 計數以字串格式,它就會連線到另一個字串,使其具有表示 fps 速度的字首。之後,它將儲存在句子結構中以供渲染。SetFps 函式還將 fps 字串的顏色設定為綠色(如果超過 60 fps)、黃色(如果低於 60 fps)和紅色(如果低於 30 fps)。

bool TextClass::SetFps(int fps, ID3D11DeviceContext* deviceContext)
{
	char tempString[16];
	char fpsString[16];
	float red, green, blue;
	bool result;


	// Truncate the fps to below 10,000.
	if(fps > 9999)
	{
		fps = 9999;
	}

	// Convert the fps integer to string format.
	_itoa_s(fps, tempString, 10);

	// Setup the fps string.
	strcpy_s(fpsString, "Fps: ");
	strcat_s(fpsString, tempString);

	// If fps is 60 or above set the fps color to green.
	if(fps >= 60)
	{
		red = 0.0f;
		green = 1.0f;
		blue = 0.0f;
	}

	// If fps is below 60 set the fps color to yellow.
	if(fps 

SetCpu 函式與 SetFps 函式類似。它獲取 cpu 值並將其轉換為字串,然後將其儲存在句子結構中並進行渲染。

bool TextClass::SetCpu(int cpu, ID3D11DeviceContext* deviceContext)
{
	char tempString[16];
	char cpuString[16];
	bool result;


	// Convert the cpu integer to string format.
	_itoa_s(cpu, tempString, 10);

	// Setup the cpu string.
	strcpy_s(cpuString, "Cpu: ");
	strcat_s(cpuString, tempString);
	strcat_s(cpuString, "%");

	// Update the sentence vertex buffer with the new string information.
	result = UpdateSentence(m_sentence2, cpuString, 20, 40, 0.0f, 1.0f, 0.0f, deviceContext);
	if(!result)
	{
		return false;
	}

	return true;
}

現在,我們可以在渲染場景時看到 FPS 和 CPU 使用率。此外,我們現在擁有精確計時器,用於確保物件的平移和旋轉始終保持一致,無論應用程式執行的幀速率如何。

1. 重新編譯程式碼並確保可以看到 fps 和 cpu 值。按 Esc 退出。

2. 在 graphicsclass.h 檔案中開啟 vsync,檢視你的顯示卡/顯示器將應用程式鎖定到哪個重新整理速度。

華夏公益教科書