はかせのラボ

私の頭の中を書いていく雑記ブログです

プログラミング タイムベースで処理を進める

あいさつ

どうも、はかせです。
今回は前回のアドバイスにあった
タイムベース処理を実装してみました。

理屈

理屈は極めて単純です。
処理が1/60秒以内に終わった場合、
1/60秒まで処理を止める
ことでフレームレートを固定します。

この方法の利点は何といっても
環境を選ばないことです。

もちろん時間計測に使うAPIとかによって
多少の差はあれどもフレームベースでやることに比べたら微々たるものです。

実装

実装です。

//1フレームの時間
const float FRAME_TIME = 1.0f / 60.0f;
//フレームの経過時間
float frameTime = 0;
//計測開始時間
LARGE_INTEGER timeStart;
//計測終了時間
LARGE_INTEGER timeEnd;
//計測周波数
LARGE_INTEGER timeFreq;

//今回のメイン関数
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hInstancePrev, LPSTR pCmdLine, int nCmdShow) 
{
	// ウィンドウクラスを登録する
	WNDCLASS windowClass = { 0 };
	windowClass.lpfnWndProc = WinProc;
	windowClass.hInstance = hInstance;
	windowClass.lpszClassName = mClassName.c_str();
	RegisterClass(&windowClass);
	// ウィンドウの作成
	mWindowHandle = CreateWindow(mClassName.c_str(), mWindowName.c_str(), WIN_STYLE, CW_USEDEFAULT, CW_USEDEFAULT, WindowWidth, WindowHeight, NULL, NULL, hInstance, NULL);
	if (mWindowHandle == NULL) return 0;
	ShowWindow(mWindowHandle, nCmdShow);
	// メッセージループの実行
	MSG msg = { 0 };
	//周波数取得
	QueryPerformanceFrequency(&timeFreq);
	//計測開始時間の初期化
	QueryPerformanceCounter(&timeStart);
	//何フレーム目か
	int frameCount = 0;
	//ログを出力する回数
	int outputLogCount = 10;
	float fps = 0.0f;
	while (msg.message != WM_QUIT) 
	{
		
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) 
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else 
		{
			
			// 今の時間を取得
			QueryPerformanceCounter(&timeEnd);
			// (今の時間 - 前フレームの時間) / 周波数 = 経過時間(秒単位)
			frameTime = static_cast<float>(timeEnd.QuadPart - timeStart.QuadPart) / static_cast<float>(timeFreq.QuadPart);

			//経過時間が1/60秒未満(処理時間に余裕がある)
			if (frameTime < FRAME_TIME) 
			{ 
				//Sleepの時間を計算
				DWORD sleepTime = static_cast<DWORD>((FRAME_TIME - frameTime) * 1000);
				//分解能を上げる(こうしないとSleepの精度はガタガタ)
				timeBeginPeriod(1); 
				//寝る
				Sleep(sleepTime);  
				//戻す
				timeEndPeriod(1);   
				//処理を中断しループへ
				continue;
			}
			else
			{ 
				frameCount++;
				bool isOutputLog = frameCount % outputLogCount == 0;
				fps = mFPSCounter->GetFPS();
#ifdef _DEBUG
#ifdef UNICODE
				std::wstringstream stream;
#else
				std::stringstream stream;
#endif
				if(isOutputLog)
				{
					stream << fps << " FPS" << std::endl;
					OutputDebugString(stream.str().c_str());
				}
#endif
			}
			//計測終了時間を計測開始時間に
			timeStart = timeEnd; 
			// DirectXのループ処理                        
			mDXManager->Update();
		}
	}
	return 0;
}

QueryPerformanceFrequencyはカウントアップする周波数を取得します。
QueryPerformanceCounterは現在のカウント数を取得します。
カウント数を周波数で割ることで経過時間を求めることができます。

あとはこの経過時間をみて
時間内ならSleepして時間外なら処理をするって感じです。

では結果です。
今回はFPS固定の実装なのでログに出力したFPSの画像です。
f:id:hakase0274:20190615202728p:plain
まぁ最大FPSの固定なので、
58ぐらいに落ち着くのは妥当と言えば妥当でしょう。

あとがき

今回は時間を用いたFPSの固定でした。
正直動作環境が固定されているならば
私が今までやっていたフレームベースの方が実装も楽ですし、
何よりテアリングという画面のチラツキが絶対発生しないのでいいと思います。

ただPCで動作するものとかだと各PCの性能によってばらつきが出てしまうので
今回みたいな時間で固定する必要があります。

それでは今回はこの辺でノシ
今回作ったものはgithubに上げました
github.com